rumale-manifold 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 129fa15faab4aa96cb7badb23a2d5c6f602e07c27e0b8725ea16a5de3a19b5b8
4
- data.tar.gz: 9f22d8bbc9fbc753c0db71752067d17a6bf9a043a9d6129381e0c2d315e59202
3
+ metadata.gz: a617aca87458b95ebab75cc6913fe8064c2eeb96ab002483bda708f1a40aafe1
4
+ data.tar.gz: e8e9186bbf583fc504aabd283a3b2f5bdfcff9c79cab7d52fe3b6049c2bd233b
5
5
  SHA512:
6
- metadata.gz: 92ef0dd980810f43102574b423af8cbf752080f525f0149d398107ea5e7864284d79419bdce7454bc55421b25b3d04cffd615dd798cebd89cfe4761e2a93581d
7
- data.tar.gz: 8b503c822441c1a4d57849d1589b29ca739e2a5cbd64c5985a77b7b719b0ad62239736a210472fc56bfef93b0c264ae56c96520d93aee6e8fa4383c5713e4f8b
6
+ metadata.gz: 30d1e6d74f61535d0285e40d98c54b15344128c8144ec9694bc76bb8d26a70f932accf3b77e7c6c4b55c7225e99b3b7ef354dba85684678e47f722a5a60a602b
7
+ data.tar.gz: b6b56afeec456babec131c9bc253ec31d9324c9b39d40b71e0825df930176cb6c8854df57f26d5cdd546aabada022c6e671abb2236ea52c8200cf9b834f449fc
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rumale/base/estimator'
4
+ require 'rumale/base/transformer'
5
+ require 'rumale/pairwise_metric'
6
+ require 'rumale/validation'
7
+
8
+ module Rumale
9
+ module Manifold
10
+ # LaplacianEigenmaps is a class that implements Laplacian Eigenmaps.
11
+ #
12
+ # @example
13
+ # require 'numo/linalg/autoloader'
14
+ # require 'rumale/manifold/laplacian_eigenmaps'
15
+ #
16
+ # lem = Rumale::Manifold::LaplacianEigenmaps.new(n_components: 2, n_neighbors: 15)
17
+ # z = lem.fit_transform(x)
18
+ #
19
+ # *Reference*
20
+ # - Belkin, M., and Niyogi, P., "Laplacian Eigenmaps and Spectral Techniques for Embedding and Clustering," Proc. NIPS'01, pp. 585--591, 2001.
21
+ class LaplacianEigenmaps < Rumale::Base::Estimator
22
+ include Rumale::Base::Transformer
23
+
24
+ # Return the data in representation space.
25
+ # @return [Numo::DFloat] (shape: [n_samples, n_components])
26
+ attr_reader :embedding
27
+
28
+ # Create a new transformer with Laplacian Eigenmaps.
29
+ #
30
+ # @param n_components [Integer] The number of dimensions on representation space.
31
+ # @param gamma [Nil/Float] The parameter of RBF kernel. If nil is given, the weight of affinity matrix sets to 1.
32
+ # @param n_neighbors [Integer] The number of nearest neighbors for k-nearest neighbor graph construction.
33
+ def initialize(n_components: 2, gamma: nil, n_neighbors: 10)
34
+ super()
35
+ @params = {
36
+ n_components: n_components,
37
+ gamma: gamma,
38
+ n_neighbors: [1, n_neighbors].max
39
+ }
40
+ end
41
+
42
+ # Fit the model with given training data.
43
+ #
44
+ # @overload fit(x) -> LaplacianEigenmaps
45
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
46
+ # @return [LaplacianEigenmaps] The learned transformer itself.
47
+ def fit(x, _y = nil)
48
+ raise 'LaplacianEigenmaps#fit requires Numo::Linalg but that is not loaded' unless enable_linalg?(warning: false)
49
+
50
+ x = Rumale::Validation.check_convert_sample_array(x)
51
+
52
+ distance_mat = Rumale::PairwiseMetric.squared_error(x)
53
+ neighbor_graph = k_neighbor_graph(distance_mat, @params[:n_neighbors], true)
54
+ affinity_mat = if @params[:gamma].nil?
55
+ neighbor_graph
56
+ else
57
+ neighbor_graph * Numo::NMath.exp(-@params[:gamma] * distance_mat)
58
+ end
59
+ degree_mat = affinity_mat.sum(axis: 1).diag
60
+ laplacian_mat = degree_mat - affinity_mat
61
+
62
+ _, eig_vecs = Numo::Linalg.eigh(laplacian_mat, degree_mat, vals_range: 1...(1 + @params[:n_components]))
63
+
64
+ @embedding = @params[:n_components] == 1 ? eig_vecs[true, 0].dup : eig_vecs.dup
65
+ @x_train = x.dup
66
+
67
+ self
68
+ end
69
+
70
+ # Fit the model with training data, and then transform them with the learned model.
71
+ #
72
+ # @overload fit_transform(x) -> Numo::DFloat
73
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
74
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data
75
+ def fit_transform(x, _y = nil)
76
+ unless enable_linalg?(warning: false)
77
+ raise 'LaplacianEigenmaps#fit_transform requires Numo::Linalg but that is not loaded'
78
+ end
79
+
80
+ fit(x).transform(x)
81
+ end
82
+
83
+ # Transform the given data with the learned model.
84
+ #
85
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The data to be transformed with the learned model.
86
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data.
87
+ def transform(x)
88
+ x = Rumale::Validation.check_convert_sample_array(x)
89
+
90
+ distance_mat = Rumale::PairwiseMetric.squared_error(x, @x_train)
91
+ neighbor_graph = k_neighbor_graph(distance_mat, @params[:n_neighbors], false)
92
+ affinity_mat = if @params[:gamma].nil?
93
+ neighbor_graph
94
+ else
95
+ neighbor_graph * Numo::NMath.exp(-@params[:gamma] * distance_mat)
96
+ end
97
+ normalizer = Numo::NMath.sqrt(affinity_mat.mean * affinity_mat.mean(axis: 1))
98
+ n_train_samples = @x_train.shape[0]
99
+ weight_mat = 1.fdiv(n_train_samples) * (affinity_mat.transpose / normalizer).transpose
100
+ weight_mat.dot(@embedding)
101
+ end
102
+
103
+ private
104
+
105
+ def k_neighbor_graph(distance_mat, n_neighbors, contain_self)
106
+ n_samples = distance_mat.shape[0]
107
+ if contain_self
108
+ neighbor_graph = Numo::DFloat.zeros(n_samples, n_samples)
109
+ n_samples.times do |n|
110
+ neighbor_ids = (distance_mat[n, true].sort_index.to_a - [n])[0...n_neighbors]
111
+ neighbor_graph[n, neighbor_ids] = 1
112
+ end
113
+ Numo::DFloat.maximum(neighbor_graph, neighbor_graph.transpose)
114
+ else
115
+ neighbor_graph = Numo::DFloat.zeros(distance_mat.shape)
116
+ n_samples.times do |n|
117
+ neighbor_ids = distance_mat[n, true].sort_index.to_a[0...n_neighbors]
118
+ neighbor_graph[n, neighbor_ids] = 1
119
+ end
120
+ neighbor_graph
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rumale/base/estimator'
4
+ require 'rumale/base/transformer'
5
+ require 'rumale/pairwise_metric'
6
+ require 'rumale/validation'
7
+
8
+ module Rumale
9
+ module Manifold
10
+ # LocallyLinearEmbedding is a class that implements Loccaly Linear Embedding.
11
+ #
12
+ # @example
13
+ # require 'numo/linalg/autoloader'
14
+ # require 'rumale/manifold/locally_linear_embedding'
15
+ #
16
+ # lem = Rumale::Manifold::LocallyLinearEmbedding.new(n_components: 2, n_neighbors: 15)
17
+ # z = lem.fit_transform(x)
18
+ #
19
+ # *Reference*
20
+ # - Roweis, S., and Saul, L., "Nonlinear Dimensionality Reduction by Locally Linear Embedding," J. of Science, vol. 290, pp. 2323-2326, 2000.
21
+ class LocallyLinearEmbedding < Rumale::Base::Estimator
22
+ include Rumale::Base::Transformer
23
+
24
+ # Return the data in representation space.
25
+ # @return [Numo::DFloat] (shape: [n_samples, n_components])
26
+ attr_reader :embedding
27
+
28
+ # Create a new transformer with Locally Linear Embedding.
29
+ #
30
+ # @param n_components [Integer] The number of dimensions on representation space.
31
+ # @param n_neighbors [Integer] The number of nearest neighbors for k-nearest neighbor graph construction.
32
+ # @param reg_param [Float] The reguralization parameter for local gram matrix.
33
+ def initialize(n_components: 2, n_neighbors: 10, reg_param: 1e-3)
34
+ super()
35
+ @params = {
36
+ n_components: n_components,
37
+ n_neighbors: [1, n_neighbors].max,
38
+ reg_param: reg_param
39
+ }
40
+ end
41
+
42
+ # Fit the model with given training data.
43
+ #
44
+ # @overload fit(x) -> LocallyLinearEmbedding
45
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
46
+ # @return [LocallyLinearEmbedding] The learned transformer itself.
47
+ def fit(x, _y = nil)
48
+ raise 'LocallyLinearEmbedding#fit requires Numo::Linalg but that is not loaded' unless enable_linalg?(warning: false)
49
+
50
+ x = Rumale::Validation.check_convert_sample_array(x)
51
+
52
+ n_samples = x.shape[0]
53
+ tol = @params[:reg_param].fdiv(@params[:n_neighbors])
54
+ distance_mat = Rumale::PairwiseMetric.squared_error(x)
55
+ neighbor_ids = neighbor_ids(distance_mat, @params[:n_neighbors], true)
56
+
57
+ affinity_mat = Numo::DFloat.eye(n_samples)
58
+ n_samples.times do |n|
59
+ x_local = x[neighbor_ids[n, true], true] - x[n, true]
60
+ gram_mat = x_local.dot(x_local.transpose)
61
+ gram_mat += tol * gram_mat.trace * Numo::DFloat.eye(@params[:n_neighbors])
62
+ weights = Numo::Linalg.solve(gram_mat, Numo::DFloat.ones(@params[:n_neighbors]))
63
+ weights /= weights.sum + 1e-8
64
+ affinity_mat[n, neighbor_ids[n, true]] -= weights
65
+ end
66
+
67
+ kernel_mat = affinity_mat.transpose.dot(affinity_mat)
68
+ _, eig_vecs = Numo::Linalg.eigh(kernel_mat, vals_range: 1...(1 + @params[:n_components]))
69
+
70
+ @embedding = @params[:n_components] == 1 ? eig_vecs[true, 0].dup : eig_vecs.dup
71
+ @x_train = x.dup
72
+
73
+ self
74
+ end
75
+
76
+ # Fit the model with training data, and then transform them with the learned model.
77
+ #
78
+ # @overload fit_transform(x) -> Numo::DFloat
79
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
80
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data
81
+ def fit_transform(x, _y = nil)
82
+ unless enable_linalg?(warning: false)
83
+ raise 'LocallyLinearEmbedding#fit_transform requires Numo::Linalg but that is not loaded'
84
+ end
85
+
86
+ fit(x).transform(x)
87
+ end
88
+
89
+ # Transform the given data with the learned model.
90
+ #
91
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The data to be transformed with the learned model.
92
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data.
93
+ def transform(x)
94
+ x = Rumale::Validation.check_convert_sample_array(x)
95
+
96
+ n_samples = x.shape[0]
97
+ tol = @params[:reg_param].fdiv(@params[:n_neighbors])
98
+ distance_mat = Rumale::PairwiseMetric.squared_error(x, @x_train)
99
+ neighbor_ids = neighbor_ids(distance_mat, @params[:n_neighbors], false)
100
+ weight_mat = Numo::DFloat.zeros(n_samples, @x_train.shape[0])
101
+
102
+ n_samples.times do |n|
103
+ x_local = @x_train[neighbor_ids[n, true], true] - x[n, true]
104
+ gram_mat = x_local.dot(x_local.transpose)
105
+ gram_mat += tol * weight_mat.trace * Numo::DFloat.eye(@params[:n_neighbors])
106
+ weights = Numo::Linalg.solve(gram_mat, Numo::DFloat.ones(@params[:n_neighbors]))
107
+ weights /= weights.sum + 1e-8
108
+ weight_mat[n, neighbor_ids[n, true]] = weights
109
+ end
110
+
111
+ weight_mat.dot(@embedding)
112
+ end
113
+
114
+ private
115
+
116
+ def neighbor_ids(distance_mat, n_neighbors, contain_self)
117
+ n_samples = distance_mat.shape[0]
118
+ neighbor_ids = Numo::Int32.zeros(n_samples, n_neighbors)
119
+ if contain_self
120
+ n_samples.times { |n| neighbor_ids[n, true] = (distance_mat[n, true].sort_index.to_a - [n])[0...n_neighbors] }
121
+ else
122
+ n_samples.times { |n| neighbor_ids[n, true] = distance_mat[n, true].sort_index.to_a[0...n_neighbors] }
123
+ end
124
+ neighbor_ids
125
+ end
126
+ end
127
+ end
128
+ end
@@ -5,6 +5,6 @@ module Rumale
5
5
  # Module for data embedding algorithms.
6
6
  module Manifold
7
7
  # @!visibility private
8
- VERSION = '0.25.0'
8
+ VERSION = '0.26.0'
9
9
  end
10
10
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'numo/narray'
4
4
 
5
+ require_relative 'manifold/laplacian_eigenmaps'
6
+ require_relative 'manifold/locally_linear_embedding'
5
7
  require_relative 'manifold/mds'
6
8
  require_relative 'manifold/tsne'
7
9
  require_relative 'manifold/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rumale-manifold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.25.0
4
+ version: 0.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yoshoku
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-18 00:00:00.000000000 Z
11
+ date: 2023-02-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.25.0
33
+ version: 0.26.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.25.0
40
+ version: 0.26.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rumale-decomposition
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.25.0
47
+ version: 0.26.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.25.0
54
+ version: 0.26.0
55
55
  description: |
56
56
  Rumale::Manifold provides data embedding algorithms,
57
57
  such as Multi-dimensional Scaling and t-distributed Stochastic Neighbor Embedding,
@@ -65,6 +65,8 @@ files:
65
65
  - LICENSE.txt
66
66
  - README.md
67
67
  - lib/rumale/manifold.rb
68
+ - lib/rumale/manifold/laplacian_eigenmaps.rb
69
+ - lib/rumale/manifold/locally_linear_embedding.rb
68
70
  - lib/rumale/manifold/mds.rb
69
71
  - lib/rumale/manifold/tsne.rb
70
72
  - lib/rumale/manifold/version.rb