rumale-decomposition 0.28.0 → 0.29.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: 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