rumale-manifold 0.25.0 → 0.26.0

Sign up to get free protection for your applications and to get access to all the features.
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