rumale-decomposition 0.28.0 → 0.29.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: c8af7f0f9ed9935345159cdc99580adf42a196e25e873572d065d3297831769d
4
- data.tar.gz: 1cde7e86b60af3eb3013764479205bc7644b15d65e1efd4f89b5d399ded94125
3
+ metadata.gz: 84715613fb41ef3b6961b9280f6750fc831daa161ddee4e3666ab906e0670599
4
+ data.tar.gz: c3079e09e5ca5b011d8067457eb89a451b8fd0819a8f996630018a1b4cac41e7
5
5
  SHA512:
6
- metadata.gz: ecc70ed7d6d49b0b4d9fc3a074cfce03f72a9d9bdcb53fe043059256f38826c9988e22312ab028ae3ae04a451da752e01f4483678e6441963b9aded4e0121f5b
7
- data.tar.gz: b90020afa33252d132e7936711df156f13d89ceb23a49ef21f8d8ab654772b37417ea2f581ac4ef42c55fd467a6056e9aac2fd38d349b748bebfae0446c52e4e
6
+ metadata.gz: 8568f69c7acdf8ccc2c14ba6c9281e1ba145bcd400bf80ef9cbbdedd12f337455614170b1c5eff94c45ae8453ba00fb515deaad4a1ca0a56576a023264006f31
7
+ data.tar.gz: de013d05ad22b323d03e3eecac12edff941c3ecf9ad02e21a2302ee75cd6574cd06ccb1612d15bf758315191960065098212f70f8ea6980a26417dee3ef6090d
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2022-2023 Atsushi Tatsuma
1
+ Copyright (c) 2022-2024 Atsushi Tatsuma
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -57,7 +57,7 @@ module Rumale
57
57
  alpha: alpha,
58
58
  max_iter: max_iter,
59
59
  tol: tol,
60
- random_seed: (random_seed || srand)
60
+ random_seed: random_seed || srand
61
61
  }
62
62
  @rng = Random.new(@params[:random_seed])
63
63
  end
@@ -42,7 +42,7 @@ module Rumale
42
42
  max_iter: max_iter,
43
43
  tol: tol,
44
44
  eps: eps,
45
- random_seed: (random_seed || srand)
45
+ random_seed: random_seed || srand
46
46
  }
47
47
  @rng = Random.new(@params[:random_seed])
48
48
  end
@@ -62,7 +62,7 @@ module Rumale
62
62
  solver: 'fpt',
63
63
  max_iter: max_iter,
64
64
  tol: tol,
65
- random_seed: (random_seed || srand)
65
+ random_seed: random_seed || srand
66
66
  }
67
67
  @params[:solver] = 'evd' if (solver == 'auto' && enable_linalg?(warning: false)) || solver == 'evd'
68
68
  @rng = Random.new(@params[:random_seed])
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rumale/base/estimator'
4
+ require 'rumale/base/transformer'
5
+ require 'rumale/utils'
6
+ require 'rumale/validation'
7
+
8
+ module Rumale
9
+ module Decomposition
10
+ # SparsePCA is a class that implements Sparse Principal Component Analysis.
11
+ #
12
+ # @example
13
+ # require 'numo/tiny_linalg'
14
+ # Numo::Linalg = Numo::TinyLinalg
15
+ #
16
+ # require 'rumale/decomposition/sparse_pca'
17
+ #
18
+ # decomposer = Rumale::Decomposition::SparsePCA.new(n_components: 2, reg_param: 0.1)
19
+ # representaion = decomposer.fit_transform(samples)
20
+ # sparse_components = decomposer.components
21
+ #
22
+ # *Reference*
23
+ # - Macky, L., "Deflation Methods for Sparse PCA," Advances in NIPS'08, pp. 1017--1024, 2008.
24
+ # - Hein, M. and Bühler, T., "An Inverse Power Method for Nonlinear Eigenproblems with Applications in 1-Spectral Clustering and Sparse PCA," Advances in NIPS'10, pp. 847--855, 2010.
25
+ class SparsePCA < ::Rumale::Base::Estimator
26
+ include ::Rumale::Base::Transformer
27
+
28
+ # Returns the principal components.
29
+ # @return [Numo::DFloat] (shape: [n_components, n_features])
30
+ attr_reader :components
31
+
32
+ # Returns the mean vector.
33
+ # @return [Numo::DFloat] (shape: [n_features])
34
+ attr_reader :mean
35
+
36
+ # Return the random generator.
37
+ # @return [Random]
38
+ attr_reader :rng
39
+
40
+ # Create a new transformer with Sparse PCA.
41
+ #
42
+ # @param n_components [Integer] The number of principal components.
43
+ # @param reg_param [Float] The regularization parameter (interval: [0, 1]).
44
+ # @param max_iter [Integer] The maximum number of iterations.
45
+ # @param tol [Float] The tolerance of termination criterion.
46
+ # @param random_seed [Integer] The seed value using to initialize the random generator.
47
+ def initialize(n_components: 2, reg_param: 0.001, max_iter: 1000, tol: 1e-6, random_seed: nil)
48
+ super()
49
+
50
+ warn('reg_param should be in the interval [0, 1].') unless (0..1).cover?(reg_param)
51
+
52
+ @params = {
53
+ n_components: n_components,
54
+ reg_param: reg_param,
55
+ max_iter: max_iter,
56
+ tol: tol,
57
+ random_seed: random_seed || srand
58
+ }
59
+ @rng = Random.new(@params[:random_seed])
60
+ end
61
+
62
+ # Fit the model with given training data.
63
+ #
64
+ # @overload fit(x) -> SparsePCA
65
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
66
+ # @return [SparsePCA] The learned transformer itself.
67
+ def fit(x, _y = nil)
68
+ x = ::Rumale::Validation.check_convert_sample_array(x)
69
+
70
+ # initialize some variables.
71
+ @components = Numo::DFloat.zeros(@params[:n_components], x.shape[1])
72
+
73
+ # centering.
74
+ @mean = x.mean(axis: 0)
75
+ centered_x = x - @mean
76
+
77
+ # optimization.
78
+ partial_fit(centered_x)
79
+
80
+ @components = @components[0, true].dup if @params[:n_components] == 1
81
+
82
+ self
83
+ end
84
+
85
+ # Fit the model with training data, and then transform them with the learned model.
86
+ #
87
+ # @overload fit_transform(x) -> Numo::DFloat
88
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The training data to be used for fitting the model.
89
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data
90
+ def fit_transform(x, _y = nil)
91
+ x = ::Rumale::Validation.check_convert_sample_array(x)
92
+
93
+ fit(x).transform(x)
94
+ end
95
+
96
+ # Transform the given data with the learned model.
97
+ #
98
+ # @param x [Numo::DFloat] (shape: [n_samples, n_features]) The data to be transformed with the learned model.
99
+ # @return [Numo::DFloat] (shape: [n_samples, n_components]) The transformed data.
100
+ def transform(x)
101
+ x = ::Rumale::Validation.check_convert_sample_array(x)
102
+
103
+ (x - @mean).dot(@components.transpose)
104
+ end
105
+
106
+ private
107
+
108
+ def partial_fit(x)
109
+ sub_rng = @rng.dup
110
+ n_samples, n_features = x.shape
111
+ cov_mat = x.transpose.dot(x) / n_samples
112
+ prj_mat = Numo::DFloat.eye(n_features)
113
+ @params[:n_components].times do |i|
114
+ f = ::Rumale::Utils.rand_normal(n_features, sub_rng)
115
+ xf = x.dot(f)
116
+ norm_xf = norm(xf, 2)
117
+ coeff = coeff_numerator(f).fdiv(norm_xf)
118
+ mu = cov_mat.dot(f) / norm_xf
119
+ @params[:max_iter].times do |_t|
120
+ g = sign(mu) * Numo::DFloat.maximum(coeff * mu.abs - @params[:reg_param], 0)
121
+ f = g / norm(x.dot(g), 2)
122
+ mu = cov_mat.dot(f) / norm(x.dot(f), 2)
123
+ coeff_new = coeff_numerator(f)
124
+
125
+ break if (coeff - coeff_new).abs.fdiv(coeff) < @params[:tol]
126
+
127
+ coeff = coeff_new
128
+ end
129
+
130
+ # deflation
131
+ q = prj_mat.dot(f)
132
+ qqt = Numo::DFloat.eye(n_features) - q.outer(q)
133
+ x = x.dot(qqt)
134
+ cov_mat = qqt.dot(cov_mat).dot(qqt)
135
+ prj_mat = prj_mat.dot(qqt)
136
+ f /= norm(f, 2)
137
+
138
+ @components[i, true] = f.dup
139
+ end
140
+ end
141
+
142
+ def coeff_numerator(f)
143
+ (1 - @params[:reg_param]) * norm(f, 2) + @params[:reg_param] * norm(f, 1)
144
+ end
145
+
146
+ def sign(v)
147
+ r = Numo::DFloat.zeros(v.size)
148
+ r[v.lt(0)] = -1
149
+ r[v.gt(0)] = 1
150
+ r
151
+ end
152
+
153
+ def norm(v, ord)
154
+ nrm = if defined?(Numo::Linalg)
155
+ Numo::Linalg.norm(v, ord)
156
+ elsif ord == 2
157
+ Math.sqrt(v.dot(v))
158
+ else
159
+ v.abs.sum
160
+ end
161
+ nrm.zero? ? 1.0 : nrm
162
+ end
163
+ end
164
+ end
165
+ end
@@ -5,6 +5,6 @@ module Rumale
5
5
  # Module for matrix decomposition algorithms.
6
6
  module Decomposition
7
7
  # @!visibility private
8
- VERSION = '0.28.0'
8
+ VERSION = '0.29.0'
9
9
  end
10
10
  end
@@ -6,4 +6,5 @@ require_relative 'decomposition/factor_analysis'
6
6
  require_relative 'decomposition/fast_ica'
7
7
  require_relative 'decomposition/nmf'
8
8
  require_relative 'decomposition/pca'
9
+ require_relative 'decomposition/sparse_pca'
9
10
  require_relative 'decomposition/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rumale-decomposition
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.0
4
+ version: 0.29.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-11-12 00:00:00.000000000 Z
11
+ date: 2024-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.28.0
33
+ version: 0.29.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.28.0
40
+ version: 0.29.0
41
41
  description: |
42
42
  Rumale::Decomposition provides matrix decomposition algorithms,
43
43
  such as Principal Component Analysis, Non-negative Matrix Factorization, Factor Analysis, and Independent Component Analysis,
@@ -55,6 +55,7 @@ files:
55
55
  - lib/rumale/decomposition/fast_ica.rb
56
56
  - lib/rumale/decomposition/nmf.rb
57
57
  - lib/rumale/decomposition/pca.rb
58
+ - lib/rumale/decomposition/sparse_pca.rb
58
59
  - lib/rumale/decomposition/version.rb
59
60
  homepage: https://github.com/yoshoku/rumale
60
61
  licenses:
@@ -80,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  requirements: []
83
- rubygems_version: 3.4.20
84
+ rubygems_version: 3.5.7
84
85
  signing_key:
85
86
  specification_version: 4
86
87
  summary: Rumale::Decomposition provides matrix decomposition algorithms with Rumale