spmkit-learn 1.0.0__tar.gz
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.
- spmkit_learn-1.0.0/LICENSE +21 -0
- spmkit_learn-1.0.0/PKG-INFO +36 -0
- spmkit_learn-1.0.0/README.md +15 -0
- spmkit_learn-1.0.0/pyproject.toml +47 -0
- spmkit_learn-1.0.0/setup.cfg +4 -0
- spmkit_learn-1.0.0/spmkit/__init__.py +1 -0
- spmkit_learn-1.0.0/spmkit/assets/Arial.ttf +0 -0
- spmkit_learn-1.0.0/spmkit/models/__init__.py +4 -0
- spmkit_learn-1.0.0/spmkit/models/exhaustive_search.py +314 -0
- spmkit_learn-1.0.0/spmkit/models/stability_selection.py +335 -0
- spmkit_learn-1.0.0/spmkit/plots.py +320 -0
- spmkit_learn-1.0.0/spmkit_learn.egg-info/PKG-INFO +36 -0
- spmkit_learn-1.0.0/spmkit_learn.egg-info/SOURCES.txt +15 -0
- spmkit_learn-1.0.0/spmkit_learn.egg-info/dependency_links.txt +1 -0
- spmkit_learn-1.0.0/spmkit_learn.egg-info/requires.txt +3 -0
- spmkit_learn-1.0.0/spmkit_learn.egg-info/top_level.txt +1 -0
- spmkit_learn-1.0.0/test/test.py +29 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shota Inoue
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spmkit-learn
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Meta-Sparse Modeling Library for Chemical Data Prediction.
|
|
5
|
+
Author-email: Shota Inoue <inoue.shota@st.kitasato-u.ac.jp>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Classifier: License :: Other/Proprietary License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy<=1.26.4,>=1.25.2
|
|
18
|
+
Requires-Dist: scikit-learn==1.6.0
|
|
19
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# spmkit
|
|
23
|
+
|
|
24
|
+
**spmkit** is meta-sparse modeling library for chemical data prediction.
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# via wheel binary
|
|
32
|
+
pip install spmkit-***.whl
|
|
33
|
+
|
|
34
|
+
# via gzip source
|
|
35
|
+
pip install spmkit-***.tar.gz
|
|
36
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spmkit-learn"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Meta-Sparse Modeling Library for Chemical Data Prediction."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Shota Inoue", email = "inoue.shota@st.kitasato-u.ac.jp" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"numpy >= 1.25.2, <= 1.26.4",
|
|
18
|
+
"scikit-learn == 1.6.0",
|
|
19
|
+
"matplotlib >= 3.7.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
license = { text = "Proprietary" }
|
|
23
|
+
|
|
24
|
+
classifiers = [
|
|
25
|
+
"License :: Other/Proprietary License",
|
|
26
|
+
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
|
|
33
|
+
"Intended Audience :: Science/Research",
|
|
34
|
+
|
|
35
|
+
"Topic :: Scientific/Engineering :: Chemistry",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
include = ["spmkit*"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools]
|
|
42
|
+
include-package-data = true
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
spmkit = [
|
|
46
|
+
"assets/*.ttf",
|
|
47
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import models, plots
|
|
Binary file
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import multiprocessing as mp
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from functools import partial
|
|
4
|
+
from itertools import combinations
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from sklearn.linear_model import LinearRegression
|
|
9
|
+
from sklearn.metrics import (
|
|
10
|
+
mean_squared_error,
|
|
11
|
+
)
|
|
12
|
+
from sklearn.model_selection import KFold
|
|
13
|
+
from tqdm.auto import tqdm
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Result:
|
|
18
|
+
score: float
|
|
19
|
+
weights: np.ndarray
|
|
20
|
+
feature: np.ndarray
|
|
21
|
+
model: LinearRegression | list[LinearRegression]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# 回帰器
|
|
25
|
+
def _regressor(combo, X, y, sort_criteria):
|
|
26
|
+
# 特徴量を切り出す
|
|
27
|
+
idx = np.array(combo)
|
|
28
|
+
X_slice = X[:, idx]
|
|
29
|
+
|
|
30
|
+
# モデル定義
|
|
31
|
+
model = LinearRegression()
|
|
32
|
+
|
|
33
|
+
# fit
|
|
34
|
+
model.fit(X_slice, y)
|
|
35
|
+
|
|
36
|
+
# predict
|
|
37
|
+
y_pred = model.predict(X_slice)
|
|
38
|
+
|
|
39
|
+
# score
|
|
40
|
+
score = (
|
|
41
|
+
_get_aic(X_slice, y, y_pred)
|
|
42
|
+
if sort_criteria == "aic"
|
|
43
|
+
else _get_bic(X_slice, y, y_pred)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# idx, coef は元の特徴量ベクトル (size p) へ埋め込む
|
|
47
|
+
weights = np.zeros(X.shape[1], dtype=float)
|
|
48
|
+
weights[idx] = model.coef_
|
|
49
|
+
feature = np.zeros(X.shape[1], dtype=int)
|
|
50
|
+
feature[idx] = 1
|
|
51
|
+
|
|
52
|
+
return Result(
|
|
53
|
+
score=score,
|
|
54
|
+
weights=weights,
|
|
55
|
+
feature=feature,
|
|
56
|
+
model=model,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _regressor_cv(combo, X, y, split):
|
|
61
|
+
# 特徴量を切り出す
|
|
62
|
+
idx = np.array(combo)
|
|
63
|
+
X_slice = X[:, idx]
|
|
64
|
+
|
|
65
|
+
# モデル定義
|
|
66
|
+
model = LinearRegression()
|
|
67
|
+
|
|
68
|
+
# internal k-fold split
|
|
69
|
+
cves = []
|
|
70
|
+
coefs = []
|
|
71
|
+
models = []
|
|
72
|
+
|
|
73
|
+
for train_index, test_index in split:
|
|
74
|
+
# split
|
|
75
|
+
X_train, X_test = X_slice[train_index], X_slice[test_index]
|
|
76
|
+
y_train, y_test = y[train_index], y[test_index]
|
|
77
|
+
|
|
78
|
+
# fit
|
|
79
|
+
model.fit(X_train, y_train)
|
|
80
|
+
|
|
81
|
+
# save model
|
|
82
|
+
models.append(model)
|
|
83
|
+
|
|
84
|
+
# predict
|
|
85
|
+
y_pred = model.predict(X_test)
|
|
86
|
+
|
|
87
|
+
# compute cve
|
|
88
|
+
cve = mean_squared_error(y_test, y_pred)
|
|
89
|
+
cves.append(cve)
|
|
90
|
+
|
|
91
|
+
# coefs
|
|
92
|
+
coefs.append(model.coef_)
|
|
93
|
+
|
|
94
|
+
# fold 平均 CVE, coefs
|
|
95
|
+
cve = np.mean(cves)
|
|
96
|
+
coefs = np.mean(coefs)
|
|
97
|
+
|
|
98
|
+
# idx, coef は元の特徴量ベクトル (size p) へ埋め込む
|
|
99
|
+
weights = np.zeros(X.shape[1], dtype=float)
|
|
100
|
+
weights[idx] = coefs
|
|
101
|
+
feature = np.zeros(X.shape[1], dtype=int)
|
|
102
|
+
feature[idx] = 1
|
|
103
|
+
|
|
104
|
+
return Result(
|
|
105
|
+
score=cve,
|
|
106
|
+
weights=weights,
|
|
107
|
+
feature=feature,
|
|
108
|
+
model=models,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# スコア計算関数
|
|
113
|
+
def _get_aic(X, y, y_pred):
|
|
114
|
+
rss = np.sum((y - y_pred) ** 2)
|
|
115
|
+
n = len(y)
|
|
116
|
+
k = X.shape[1] + 1
|
|
117
|
+
aic = n * np.log(rss / n) + 2 * k
|
|
118
|
+
return aic
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_bic(X, y, y_pred):
|
|
122
|
+
rss = np.sum((y - y_pred) ** 2)
|
|
123
|
+
n = len(y)
|
|
124
|
+
k = X.shape[1] + 1
|
|
125
|
+
bic = n * np.log(rss / n) + k * np.log(n)
|
|
126
|
+
return bic
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# exhaustive search
|
|
130
|
+
class ExhaustiveSearch:
|
|
131
|
+
"""線形重回帰モデルの全探索
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
sort_criteria : {"cve", "aic", "bic"}, default="cve"
|
|
136
|
+
学習済みモデルをソートする基準 (交差検証誤差もしくは AIC, BIC)
|
|
137
|
+
random_state : int, default=42
|
|
138
|
+
ランダムシード
|
|
139
|
+
n_fold : int, default=5
|
|
140
|
+
交差検証を用いる場合の分割数
|
|
141
|
+
|
|
142
|
+
Attributes
|
|
143
|
+
----------
|
|
144
|
+
result_ : ndarray
|
|
145
|
+
基準に基づてソートされた学習済みモデルの重みとスコア
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
sort_criteria: Literal["cve", "aic", "bic"] = "cve",
|
|
152
|
+
random_state: int = 42,
|
|
153
|
+
n_fold: int = 5,
|
|
154
|
+
):
|
|
155
|
+
if sort_criteria not in {"cve", "aic", "bic"}:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"sort_criteria must be one of 'cve', 'aic', 'bic', got {sort_criteria!r}",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
self.sort_criteria = sort_criteria
|
|
161
|
+
self.n_fold = n_fold
|
|
162
|
+
self.random_state = random_state
|
|
163
|
+
|
|
164
|
+
self.results_ = None
|
|
165
|
+
self._best_mask = None
|
|
166
|
+
|
|
167
|
+
# ランダムシード固定
|
|
168
|
+
np.random.seed(self.random_state)
|
|
169
|
+
|
|
170
|
+
def fit(self, X, y, n_jobs: int = -1):
|
|
171
|
+
"""モデルを学習させる
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
X : array-like of shape (n_samples, n_features)
|
|
176
|
+
訓練データのデザイン行列
|
|
177
|
+
y : array-like of shape (n_samples,)
|
|
178
|
+
教師ラベルの配列
|
|
179
|
+
n_jobs : int, default=-1
|
|
180
|
+
CPU プロセス数 (-1 を指定すると全ての CPU コアを消費)
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
self
|
|
185
|
+
学習済みインスタンス
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
# ndarray にキャスト
|
|
189
|
+
X, y = np.asarray(X), np.asarray(y)
|
|
190
|
+
|
|
191
|
+
# 全ての特徴量の組み合わせを生成する
|
|
192
|
+
combos = []
|
|
193
|
+
for k in range(1, X.shape[1] + 1):
|
|
194
|
+
combos.extend(combinations(range(X.shape[1]), k))
|
|
195
|
+
|
|
196
|
+
print(f"model sorting criteria to be used: {self.sort_criteria.upper()}")
|
|
197
|
+
|
|
198
|
+
if self.sort_criteria == "cve":
|
|
199
|
+
# k-fold
|
|
200
|
+
kf = KFold(
|
|
201
|
+
n_splits=self.n_fold,
|
|
202
|
+
random_state=self.random_state,
|
|
203
|
+
shuffle=True,
|
|
204
|
+
)
|
|
205
|
+
split = list(kf.split(X, y))
|
|
206
|
+
worker = partial(
|
|
207
|
+
_regressor_cv,
|
|
208
|
+
X=X,
|
|
209
|
+
y=y,
|
|
210
|
+
split=split,
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
worker = partial(
|
|
214
|
+
_regressor,
|
|
215
|
+
X=X,
|
|
216
|
+
y=y,
|
|
217
|
+
sort_criteria=self.sort_criteria,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# 並列実行
|
|
221
|
+
with mp.Pool(processes=mp.cpu_count() if n_jobs == -1 else n_jobs) as pool:
|
|
222
|
+
results = list(
|
|
223
|
+
tqdm(
|
|
224
|
+
pool.imap_unordered(worker, combos),
|
|
225
|
+
total=len(combos),
|
|
226
|
+
desc="progress",
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# sort
|
|
231
|
+
results.sort(key=lambda res: res.score)
|
|
232
|
+
self.results_ = np.array(results)
|
|
233
|
+
return self
|
|
234
|
+
|
|
235
|
+
def get_scores(self):
|
|
236
|
+
"""ソート済みのスコアを取得する
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
ndarray of shape (2**n_features - 1,)
|
|
241
|
+
ソートされたスコアの配列
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
return np.array([res.score for res in self.results_])
|
|
245
|
+
|
|
246
|
+
def get_weights(self):
|
|
247
|
+
"""基準に基づいてソートされた学習済みモデルの重みを取得する
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
ndarray of shape (n_features, 2**n_features - 1)
|
|
252
|
+
ソートされた学習済みモデルの重み行列
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
return np.array([res.weights for res in self.results_]).T
|
|
256
|
+
|
|
257
|
+
def get_features(self):
|
|
258
|
+
"""基準に基づいてソートされた、各特徴量の有無を表す One-hot ベクトルを取得する
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
ndarray of shape (2**n_features - 1,)
|
|
263
|
+
各特徴量の有無を表す One-hot ベクトルの配列
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
return np.array([res.feature for res in self.results_])
|
|
267
|
+
|
|
268
|
+
def predict(self, X, rank: int = 0):
|
|
269
|
+
"""指定したランクの学習済みモデルによる予測を行う
|
|
270
|
+
|
|
271
|
+
Parameters
|
|
272
|
+
----------
|
|
273
|
+
X : array-like of shape (n_samples, n_features)
|
|
274
|
+
デザイン行列
|
|
275
|
+
rank : int, default=0
|
|
276
|
+
モデルランク
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
ndarray of shape (n_samples,)
|
|
281
|
+
指定したランクの学習済みモデルによる予測値
|
|
282
|
+
|
|
283
|
+
"""
|
|
284
|
+
# rank を指定してモデルを取り出す
|
|
285
|
+
model = self.results_[rank].model
|
|
286
|
+
|
|
287
|
+
# ndarray にキャスト
|
|
288
|
+
X = np.asarray(X)
|
|
289
|
+
|
|
290
|
+
# スライス
|
|
291
|
+
X_slice = X[:, self.results_[rank].feature.astype(bool)]
|
|
292
|
+
|
|
293
|
+
if self.sort_criteria == "cve":
|
|
294
|
+
y_preds = []
|
|
295
|
+
for fold in model:
|
|
296
|
+
y_pred = fold.predict(X_slice)
|
|
297
|
+
y_preds.append(y_pred)
|
|
298
|
+
return np.mean(y_preds, axis=0)
|
|
299
|
+
|
|
300
|
+
y_pred = model.predict(X_slice)
|
|
301
|
+
return y_pred
|
|
302
|
+
|
|
303
|
+
def get_ave_importances(self):
|
|
304
|
+
"""特徴量重要度 (平均絶対標準化回帰係数) とその標準偏差を取得する
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
ndarray of shape (n_features, 2)
|
|
309
|
+
特徴量重要度 (平均絶対標準化回帰係数) とその標準偏差
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
weights = self.get_weights()
|
|
313
|
+
weights_abs = np.abs(weights)
|
|
314
|
+
return np.vstack([weights_abs.mean(axis=1), weights_abs.std(axis=1)]).T
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import multiprocessing as mp
|
|
2
|
+
from functools import partial
|
|
3
|
+
from itertools import product
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from sklearn.linear_model import Lasso
|
|
8
|
+
from sklearn.preprocessing import StandardScaler
|
|
9
|
+
from tqdm.auto import tqdm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# 各イテレーションで特徴量選択を行い、特徴量ごとに選択されたかどうかを返すメソッド
|
|
13
|
+
def _selector(iters, X, y, alphas, eps, max_iter, random_state):
|
|
14
|
+
# イテレータ
|
|
15
|
+
subsample_idx, alpha_idx = iters
|
|
16
|
+
|
|
17
|
+
# サブサンプル
|
|
18
|
+
X_subsample = X[subsample_idx]
|
|
19
|
+
y_subsample = y[subsample_idx]
|
|
20
|
+
|
|
21
|
+
# alpha
|
|
22
|
+
alpha = alphas[alpha_idx]
|
|
23
|
+
|
|
24
|
+
# 標準化
|
|
25
|
+
scaler = StandardScaler()
|
|
26
|
+
X_subsample_scaled = scaler.fit_transform(X_subsample)
|
|
27
|
+
|
|
28
|
+
# モデル定義
|
|
29
|
+
model = Lasso(
|
|
30
|
+
alpha=alpha,
|
|
31
|
+
random_state=random_state,
|
|
32
|
+
max_iter=max_iter,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# 学習
|
|
36
|
+
model.fit(X_subsample_scaled, y_subsample)
|
|
37
|
+
|
|
38
|
+
# 選択された特徴量を 0/1 で収集
|
|
39
|
+
selection = (np.abs(model.coef_) > eps).astype(int)
|
|
40
|
+
|
|
41
|
+
return selection, alpha_idx
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Stability Selection を行うクラス
|
|
45
|
+
class StabilitySelection:
|
|
46
|
+
"""Stability Selection (N. Meinshausen and P. Bühlmann, J. R. Stat. Soc. B (2010).).
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
subsample_iter : int, default=100
|
|
51
|
+
サブサンプリング回数
|
|
52
|
+
subsample_frac : float, default=0.5
|
|
53
|
+
サブサンプリング比率 (0.0 から 1.0 の値)
|
|
54
|
+
n_alphas : int, default=100
|
|
55
|
+
正則化係数 (alpha) の値をサンプリングする回数
|
|
56
|
+
min_alpha : float, default=1e-6
|
|
57
|
+
サンプリングされる alpha の最小値
|
|
58
|
+
max_alpha : float, default=1.0
|
|
59
|
+
サンプリングされる alpha の最大値
|
|
60
|
+
eps : float, default=1e-6
|
|
61
|
+
LASSO が特徴量選択を行う際の閾値
|
|
62
|
+
lasso_max_iter : int, default=1000
|
|
63
|
+
LASSO の最大反復回数
|
|
64
|
+
random_state : int, default=42
|
|
65
|
+
ランダムシード
|
|
66
|
+
|
|
67
|
+
Attributes
|
|
68
|
+
----------
|
|
69
|
+
alphas_ : ndarray of shape (n_alphas,)
|
|
70
|
+
サンプリングされた alpha の配列
|
|
71
|
+
selection_path_ : ndarray of shape (n_features, n_alphas)
|
|
72
|
+
各特徴量における alpha ごとの選択確率プロファイル
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
subsample_iter: int = 100,
|
|
79
|
+
subsample_frac: float = 0.5,
|
|
80
|
+
n_alphas: int = 100,
|
|
81
|
+
min_alpha: float = 1e-6,
|
|
82
|
+
max_alpha: float = 1.0,
|
|
83
|
+
eps: float = 1e-6,
|
|
84
|
+
lasso_max_iter: int = 1000,
|
|
85
|
+
random_state: int = 42,
|
|
86
|
+
):
|
|
87
|
+
self.subsample_iter = subsample_iter # サブサンプリング回数
|
|
88
|
+
self.subsample_frac = subsample_frac # サブサンプリング比率
|
|
89
|
+
self.n_alphas = n_alphas # LASSO の正則化係数の探索回数
|
|
90
|
+
self.min_alpha = min_alpha # LASSO の正則化係数の最小値
|
|
91
|
+
self.max_alpha = max_alpha # LASSO の正則化係数の最大値
|
|
92
|
+
self.eps = eps # スパースとみなす閾値
|
|
93
|
+
self.lasso_max_iter = lasso_max_iter # LASSO の最大反復回数
|
|
94
|
+
self.random_state = random_state # ランダムシード
|
|
95
|
+
|
|
96
|
+
self.alphas_ = None # alpha のプロファイル
|
|
97
|
+
self.selection_path_ = None # 選択確率
|
|
98
|
+
|
|
99
|
+
# ランダムシード固定
|
|
100
|
+
np.random.seed(self.random_state)
|
|
101
|
+
|
|
102
|
+
# 学習を実行する関数
|
|
103
|
+
def fit(self, X, y, n_jobs: int = -1):
|
|
104
|
+
"""モデルを学習させる
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
X : array-like of shape (n_samples, n_features)
|
|
109
|
+
訓練データのデザイン行列
|
|
110
|
+
y : array-like of shape (n_samples,)
|
|
111
|
+
教師ラベルの配列
|
|
112
|
+
n_jobs : int, default=-1
|
|
113
|
+
CPU プロセス数 (-1 を指定すると全ての CPU コアを消費)
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
self
|
|
118
|
+
学習済みインスタンス
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
# ndarray にキャスト
|
|
122
|
+
X, y = np.asarray(X), np.asarray(y)
|
|
123
|
+
|
|
124
|
+
# データサイズ
|
|
125
|
+
n_sample = X.shape[0]
|
|
126
|
+
n_subsample = int(n_sample * self.subsample_frac)
|
|
127
|
+
|
|
128
|
+
# サブサンプリング用の index を生成
|
|
129
|
+
# 重複を許さない
|
|
130
|
+
subsample_indices = []
|
|
131
|
+
seen = set()
|
|
132
|
+
subsample_count = 0
|
|
133
|
+
subsample_max_iter = int(self.subsample_iter * 10)
|
|
134
|
+
|
|
135
|
+
while len(subsample_indices) < self.subsample_iter:
|
|
136
|
+
if subsample_count > subsample_max_iter:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
subsample_count += 1
|
|
140
|
+
|
|
141
|
+
indice = np.random.choice(n_sample, n_subsample, replace=False)
|
|
142
|
+
key = tuple(sorted(indice))
|
|
143
|
+
|
|
144
|
+
if key not in seen:
|
|
145
|
+
subsample_indices.append(indice)
|
|
146
|
+
seen.add(key)
|
|
147
|
+
|
|
148
|
+
# alpha を走査
|
|
149
|
+
self.alphas_ = np.logspace(
|
|
150
|
+
np.log10(self.min_alpha),
|
|
151
|
+
np.log10(self.max_alpha),
|
|
152
|
+
self.n_alphas,
|
|
153
|
+
)
|
|
154
|
+
alpha_indices = list(range(len(self.alphas_)))
|
|
155
|
+
|
|
156
|
+
# 特徴量選択器の初期化
|
|
157
|
+
worker = partial(
|
|
158
|
+
_selector,
|
|
159
|
+
X=X,
|
|
160
|
+
y=y,
|
|
161
|
+
alphas=self.alphas_,
|
|
162
|
+
eps=self.eps,
|
|
163
|
+
max_iter=self.lasso_max_iter,
|
|
164
|
+
random_state=self.random_state,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# ループするイテレータ
|
|
168
|
+
iters = product(subsample_indices, alpha_indices)
|
|
169
|
+
|
|
170
|
+
# 並列で選択を実行
|
|
171
|
+
tot_iters = len(subsample_indices) * len(alpha_indices)
|
|
172
|
+
with mp.Pool(processes=mp.cpu_count() if n_jobs == -1 else n_jobs) as pool:
|
|
173
|
+
selections = list(
|
|
174
|
+
tqdm(
|
|
175
|
+
pool.imap_unordered(worker, iters),
|
|
176
|
+
total=tot_iters,
|
|
177
|
+
desc="progress",
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# 特徴量・alpha ごとの選択回数
|
|
182
|
+
selection_counts = np.zeros((X.shape[1], len(alpha_indices)), dtype=int)
|
|
183
|
+
for sele in selections:
|
|
184
|
+
selection_counts[:, sele[1]] += sele[0]
|
|
185
|
+
|
|
186
|
+
# 特徴量・alpha ごとの選択確率
|
|
187
|
+
self.selection_path_ = selection_counts / self.subsample_iter
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
# 選択確率を取得
|
|
191
|
+
def get_selection_path(self):
|
|
192
|
+
"""各特徴量における alpha ごとの選択確率プロファイルを取得する
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
selection_path_ : ndarray of shape (n_features, n_alphas)
|
|
197
|
+
各特徴量における alpha ごとの選択確率プロファイル
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
return self.selection_path_
|
|
201
|
+
|
|
202
|
+
# alphas を取得
|
|
203
|
+
def get_alphas(self):
|
|
204
|
+
"""サンプリングされた alpha の配列を取得する
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
alphas_ : ndarray of shape (n_alphas,)
|
|
209
|
+
サンプリングされた alpha の配列
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
return self.alphas_
|
|
213
|
+
|
|
214
|
+
# alpha のスライス
|
|
215
|
+
def _get_alpha_domain(
|
|
216
|
+
self,
|
|
217
|
+
lower_alpha: float | None,
|
|
218
|
+
upper_alpha: float | None,
|
|
219
|
+
):
|
|
220
|
+
if lower_alpha is None:
|
|
221
|
+
lower_alpha = self.min_alpha
|
|
222
|
+
if upper_alpha is None:
|
|
223
|
+
upper_alpha = self.max_alpha
|
|
224
|
+
|
|
225
|
+
if lower_alpha < self.min_alpha:
|
|
226
|
+
return ValueError(
|
|
227
|
+
"'lower_alpha' must be greater than or equal to 'min_alpha'.",
|
|
228
|
+
)
|
|
229
|
+
if upper_alpha > self.max_alpha:
|
|
230
|
+
return ValueError(
|
|
231
|
+
"'upper_alpha' must be less than or equal to 'max_alpha'.",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# alpha を指定した領域に制限
|
|
235
|
+
alpha_indice = np.where(
|
|
236
|
+
(self.alphas_ >= lower_alpha) & (self.alphas_ < upper_alpha),
|
|
237
|
+
)[0]
|
|
238
|
+
domain = self.selection_path_[:, alpha_indice]
|
|
239
|
+
return domain
|
|
240
|
+
|
|
241
|
+
def get_ave_selection_probs(
|
|
242
|
+
self,
|
|
243
|
+
lower_alpha: float | None = None,
|
|
244
|
+
upper_alpha: float | None = None,
|
|
245
|
+
):
|
|
246
|
+
"""指定した alpha の領域について、特徴量ごとの平均選択率とその標準偏差を取得する
|
|
247
|
+
|
|
248
|
+
Parameters
|
|
249
|
+
----------
|
|
250
|
+
lower_alpha : float, optional
|
|
251
|
+
alpha の下界
|
|
252
|
+
upper_alpha : float, optional
|
|
253
|
+
alpha の上界
|
|
254
|
+
|
|
255
|
+
Returns
|
|
256
|
+
-------
|
|
257
|
+
ndarray of shape (n_features, 2)
|
|
258
|
+
特徴量ごとの平均選択率とその標準偏差
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
domain = self._get_alpha_domain(
|
|
262
|
+
lower_alpha=lower_alpha,
|
|
263
|
+
upper_alpha=upper_alpha,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# 平均選択率とその標準偏差
|
|
267
|
+
return np.vstack([domain.mean(axis=1), domain.std(axis=1)]).T
|
|
268
|
+
|
|
269
|
+
def get_mask(
|
|
270
|
+
self,
|
|
271
|
+
lower_alpha: float | None = None,
|
|
272
|
+
upper_alpha: float | None = None,
|
|
273
|
+
selection_th: float = 0.5,
|
|
274
|
+
astype: Literal["bool", "int"] = "bool",
|
|
275
|
+
):
|
|
276
|
+
"""指定した alpha の領域について、特徴量が選択されたかどうかを表すマスク配列を取得する
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
lower_alpha : float, optional
|
|
281
|
+
alpha の下界
|
|
282
|
+
upper_alpha : float, optional
|
|
283
|
+
alpha の上界
|
|
284
|
+
selection_th : float, default=0.5
|
|
285
|
+
特徴量選択の閾値 (0.0 から 1.0 の値)
|
|
286
|
+
astype : {"bool", "int"}, default="bool"
|
|
287
|
+
マスク配列の型
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
ndarray of shape (n_features,)
|
|
292
|
+
特徴量が選択されたかどうかを表すマスク配列
|
|
293
|
+
|
|
294
|
+
"""
|
|
295
|
+
selection_probs = self.get_ave_selection_probs(
|
|
296
|
+
lower_alpha=lower_alpha,
|
|
297
|
+
upper_alpha=upper_alpha,
|
|
298
|
+
)
|
|
299
|
+
mask = selection_probs[:, 0] >= selection_th
|
|
300
|
+
if astype == "int":
|
|
301
|
+
mask = mask.astype(int)
|
|
302
|
+
return mask
|
|
303
|
+
|
|
304
|
+
def transform(
|
|
305
|
+
self,
|
|
306
|
+
X,
|
|
307
|
+
lower_alpha: float | None = None,
|
|
308
|
+
upper_alpha: float | None = None,
|
|
309
|
+
selection_th: float = 0.5,
|
|
310
|
+
):
|
|
311
|
+
"""指定した alpha の領域について、デザイン行列をスパース表現に変換する
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
lower_alpha : float, optional
|
|
316
|
+
alpha の下界
|
|
317
|
+
upper_alpha : float, optional
|
|
318
|
+
alpha の上界
|
|
319
|
+
selection_th : float, default=0.5
|
|
320
|
+
特徴量選択の閾値 (0.0 から 1.0 の値)
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
ndarray of shape (n_samples, n_features)
|
|
325
|
+
デザイン行列のスパース表現
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
X = np.asarray(X)
|
|
329
|
+
mask = self.get_mask(
|
|
330
|
+
lower_alpha=lower_alpha,
|
|
331
|
+
upper_alpha=upper_alpha,
|
|
332
|
+
selection_th=selection_th,
|
|
333
|
+
)
|
|
334
|
+
X[:, ~mask] = 0.0
|
|
335
|
+
return X
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from importlib.resources import files
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import matplotlib.colors as cl
|
|
5
|
+
import matplotlib.font_manager as fm
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import matplotlib.ticker as tick
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _init():
|
|
12
|
+
arial_path = files("spmkit.assets") / "Arial.ttf"
|
|
13
|
+
arial = fm.FontProperties(fname=arial_path)
|
|
14
|
+
plt.rcParams["font.family"] = arial.get_name()
|
|
15
|
+
plt.rcParams["mathtext.fontset"] = "cm"
|
|
16
|
+
plt.rcParams["xtick.direction"] = "in"
|
|
17
|
+
plt.rcParams["ytick.direction"] = "in"
|
|
18
|
+
plt.rcParams["font.size"] = 14
|
|
19
|
+
plt.gca().spines["right"].set_visible(True)
|
|
20
|
+
plt.gca().spines["top"].set_visible(True)
|
|
21
|
+
plt.gca().spines["bottom"].set_visible(True)
|
|
22
|
+
plt.gca().spines["left"].set_visible(True)
|
|
23
|
+
plt.gca().spines["top"].set_linewidth(2)
|
|
24
|
+
plt.gca().spines["left"].set_linewidth(2)
|
|
25
|
+
plt.gca().spines["bottom"].set_linewidth(2)
|
|
26
|
+
plt.gca().spines["right"].set_linewidth(2)
|
|
27
|
+
plt.xticks(fontproperties=arial.get_name())
|
|
28
|
+
plt.yticks(fontproperties=arial.get_name())
|
|
29
|
+
plt.tick_params(left=True, width=2, length=5)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def selection_path_plot(ax, selection_path, alphas, feature_names=None):
|
|
33
|
+
"""各特徴量における alpha ごとの選択確率プロファイルを表示する
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
ax : matplotlib.axes.Axes
|
|
38
|
+
`matplotlib.axes.Axes`
|
|
39
|
+
selection_path : array-like of shape (n_features, n_alphas)
|
|
40
|
+
各特徴量における alpha ごとの選択確率プロファイル
|
|
41
|
+
alphas_ : array-like of shape (n_alphas,)
|
|
42
|
+
サンプリングされた alpha の配列
|
|
43
|
+
feature_names : array-like of shape (n_features,), optional
|
|
44
|
+
各特徴量の名称
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
matplotlib.axes.Axes
|
|
49
|
+
更新済みの `matplotlib.axes.Axes`
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
_init()
|
|
53
|
+
selection_path, alphas = np.asarray(selection_path), np.asarray(alphas)
|
|
54
|
+
if feature_names is None:
|
|
55
|
+
feature_names = [f"feature {l + 1}" for l in range(selection_path.shape[0])]
|
|
56
|
+
|
|
57
|
+
for i, feat in enumerate(selection_path):
|
|
58
|
+
ax.plot(alphas, feat, label=feature_names[i])
|
|
59
|
+
ax.set_xscale("log", base=10)
|
|
60
|
+
ax.set_ylim(0.0, 1.0)
|
|
61
|
+
ax.set_xlabel(r"$\alpha$", fontsize=16)
|
|
62
|
+
ax.set_ylabel("Selection probability", fontsize=16)
|
|
63
|
+
ax.legend()
|
|
64
|
+
return ax
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ave_selection_probs_bar_plot(
|
|
68
|
+
ax,
|
|
69
|
+
selection_probs,
|
|
70
|
+
selection_th=None,
|
|
71
|
+
feature_names=None,
|
|
72
|
+
bar_color="limegreen",
|
|
73
|
+
vline_color="red",
|
|
74
|
+
errorbar=False,
|
|
75
|
+
):
|
|
76
|
+
"""特徴量ごとの平均選択率を表示する
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
ax : matplotlib.axes.Axes
|
|
81
|
+
`matplotlib.axes.Axes`
|
|
82
|
+
selection_probs : ndarray of shape (n_features, 2)
|
|
83
|
+
特徴量ごとの平均選択率とその標準偏差
|
|
84
|
+
selection_th : float, optional
|
|
85
|
+
特徴量選択の閾値 (0.0 から 1.0 の値)
|
|
86
|
+
feature_names : array-like of shape (n_features,), optional
|
|
87
|
+
各特徴量の名称
|
|
88
|
+
bar_color : str, default="limegreen"
|
|
89
|
+
バーの色
|
|
90
|
+
vline_color : str, default="red"
|
|
91
|
+
分割線の色
|
|
92
|
+
errorbar : bool, default=False
|
|
93
|
+
エラーバーを表示するかどうか
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
matplotlib.axes.Axes
|
|
98
|
+
更新済みの `matplotlib.axes.Axes`
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
_init()
|
|
102
|
+
selection_probs = np.asarray(selection_probs)
|
|
103
|
+
if feature_names is None:
|
|
104
|
+
feature_names = [f"feature {l + 1}" for l in range(selection_probs.shape[0])]
|
|
105
|
+
|
|
106
|
+
# ソート
|
|
107
|
+
sort_indice = np.argsort(selection_probs[:, 0])
|
|
108
|
+
selection_probs_sorted = selection_probs[sort_indice]
|
|
109
|
+
feature_names_sorted = np.array(feature_names)[sort_indice]
|
|
110
|
+
|
|
111
|
+
ax.barh(
|
|
112
|
+
feature_names_sorted,
|
|
113
|
+
selection_probs_sorted[:, 0],
|
|
114
|
+
xerr=selection_probs_sorted[:, 1] if errorbar else 0,
|
|
115
|
+
capsize=2,
|
|
116
|
+
height=1.0,
|
|
117
|
+
edgecolor="black",
|
|
118
|
+
color=bar_color,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if selection_th is not None:
|
|
122
|
+
ax.axvline(
|
|
123
|
+
selection_th,
|
|
124
|
+
linestyle="dashed",
|
|
125
|
+
linewidth=1.5,
|
|
126
|
+
label="Threshold",
|
|
127
|
+
color=vline_color,
|
|
128
|
+
)
|
|
129
|
+
ax.legend()
|
|
130
|
+
|
|
131
|
+
ax.set_xlim(0.0, 1.0)
|
|
132
|
+
ax.set_xlabel("Average of selection probability", fontsize=16)
|
|
133
|
+
ax.set_ylabel("Features", fontsize=16)
|
|
134
|
+
return ax
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def weight_diagram(
|
|
138
|
+
ax,
|
|
139
|
+
weights,
|
|
140
|
+
feature_names=None,
|
|
141
|
+
pos_color="red",
|
|
142
|
+
neg_color="dodgerblue",
|
|
143
|
+
mid_color="whitesmoke",
|
|
144
|
+
xscale: Literal["linear", "log2"] = "linear",
|
|
145
|
+
):
|
|
146
|
+
"""全ての学習済みモデルの重みを表示する
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
ax : matplotlib.axes.Axes
|
|
151
|
+
`matplotlib.axes.Axes`
|
|
152
|
+
weights : array-like of shape (n_features, 2**n_features - 1)
|
|
153
|
+
ソートされた学習済みモデルの重み行列
|
|
154
|
+
feature_names : array-like of shape (n_features,), optional
|
|
155
|
+
各特徴量の名称
|
|
156
|
+
pos_color : str, default="red"
|
|
157
|
+
重みが正となる場合の色
|
|
158
|
+
neg_color : str, default="dodgerblue"
|
|
159
|
+
重みが負となる場合の色
|
|
160
|
+
mid_color : str, default="lightgray"
|
|
161
|
+
重みがゼロとなる場合の色
|
|
162
|
+
xscale : {"linear", "log2"}, default="linear"
|
|
163
|
+
横軸のスケール
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
matplotlib.collections.QuadMesh
|
|
168
|
+
更新済みの `matplotlib.collections.QuadMesh`
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
_init()
|
|
172
|
+
weights = np.asarray(weights)
|
|
173
|
+
n_features, n_models = weights.shape
|
|
174
|
+
|
|
175
|
+
if feature_names is None:
|
|
176
|
+
feature_names = [f"feature {l + 1}" for l in range(n_features)]
|
|
177
|
+
|
|
178
|
+
# ranks = np.arange(1, n_models + 1)
|
|
179
|
+
x = np.arange(0.5, n_models + 1.5)
|
|
180
|
+
y = np.arange(n_features + 1)
|
|
181
|
+
|
|
182
|
+
im = ax.pcolormesh(
|
|
183
|
+
x,
|
|
184
|
+
y,
|
|
185
|
+
weights,
|
|
186
|
+
cmap=cl.LinearSegmentedColormap.from_list(
|
|
187
|
+
"weight_diagram",
|
|
188
|
+
[neg_color, mid_color, pos_color],
|
|
189
|
+
),
|
|
190
|
+
norm=cl.TwoSlopeNorm(
|
|
191
|
+
vcenter=0.0,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
ax.set_xlim(1, n_models + 1)
|
|
196
|
+
if xscale == "log2":
|
|
197
|
+
ax.set_xscale("log", base=2)
|
|
198
|
+
ax.xaxis.set_major_locator(tick.LogLocator(base=2))
|
|
199
|
+
ax.xaxis.set_major_formatter(tick.LogFormatterMathtext(base=2))
|
|
200
|
+
ax.set_yticklabels(feature_names)
|
|
201
|
+
ax.set_yticks(np.arange(len(feature_names)) + 0.5)
|
|
202
|
+
ax.set_xlabel("Rank", fontsize=16)
|
|
203
|
+
ax.set_ylabel("Features", fontsize=16)
|
|
204
|
+
return im
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def coefs_waterfall_plot(
|
|
208
|
+
ax,
|
|
209
|
+
weights,
|
|
210
|
+
rank=0,
|
|
211
|
+
feature_names=None,
|
|
212
|
+
pos_color="red",
|
|
213
|
+
neg_color="dodgerblue",
|
|
214
|
+
):
|
|
215
|
+
"""指定したランクのモデルの重みを絶対値順にソートして表示する
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
ax : matplotlib.axes.Axes
|
|
220
|
+
`matplotlib.axes.Axes`
|
|
221
|
+
weights : array-like of shape (n_features, 2**n_features - 1)
|
|
222
|
+
ソートされた学習済みモデルの重み行列
|
|
223
|
+
rank : int, default=0
|
|
224
|
+
モデルランク
|
|
225
|
+
feature_names : array-like of shape (n_features,), optional
|
|
226
|
+
各特徴量の名称
|
|
227
|
+
pos_color : str, default="red"
|
|
228
|
+
重みが正となる場合の色
|
|
229
|
+
neg_color : str, default="dodgerblue"
|
|
230
|
+
重みが負となる場合の色
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
matplotlib.axes.Axes
|
|
235
|
+
更新済みの `matplotlib.axes.Axes`
|
|
236
|
+
|
|
237
|
+
"""
|
|
238
|
+
_init()
|
|
239
|
+
weights = np.asarray(weights)
|
|
240
|
+
coefs = weights[:, rank]
|
|
241
|
+
n_features = coefs.shape[0]
|
|
242
|
+
|
|
243
|
+
if feature_names is None:
|
|
244
|
+
feature_names = [f"feature {l + 1}" for l in range(n_features)]
|
|
245
|
+
|
|
246
|
+
colors = [pos_color if v >= 0 else neg_color for v in coefs]
|
|
247
|
+
|
|
248
|
+
# 回帰係数がゼロとなる特徴量を落とす
|
|
249
|
+
filter_indice = np.where(coefs != 0)[0]
|
|
250
|
+
coefs_filtered = coefs[filter_indice]
|
|
251
|
+
feature_names_filtered = np.array(feature_names)[filter_indice]
|
|
252
|
+
|
|
253
|
+
# ソート
|
|
254
|
+
sort_indice = np.argsort(np.abs(coefs_filtered))
|
|
255
|
+
coefs_sorted = coefs_filtered[sort_indice]
|
|
256
|
+
feature_names_sorted = feature_names_filtered[sort_indice]
|
|
257
|
+
|
|
258
|
+
ax.barh(
|
|
259
|
+
feature_names_sorted,
|
|
260
|
+
coefs_sorted,
|
|
261
|
+
color=colors,
|
|
262
|
+
height=1.0,
|
|
263
|
+
edgecolor="black",
|
|
264
|
+
)
|
|
265
|
+
ax.axvline(x=0, color="black", linewidth=2)
|
|
266
|
+
ax.set_xlabel("Standardized coefficient", fontsize=16)
|
|
267
|
+
ax.set_ylabel("Features", fontsize=16)
|
|
268
|
+
return ax
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def ave_importances_bar_plot(
|
|
272
|
+
ax,
|
|
273
|
+
importances,
|
|
274
|
+
feature_names=None,
|
|
275
|
+
bar_color="limegreen",
|
|
276
|
+
errorbar=False,
|
|
277
|
+
):
|
|
278
|
+
"""特徴量重要度 (平均絶対標準化回帰係数) を表示する
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
ax : matplotlib.axes.Axes
|
|
283
|
+
`matplotlib.axes.Axes`
|
|
284
|
+
importances : array-like of shape (n_features, 2)
|
|
285
|
+
特徴量重要度 (平均絶対標準化回帰係数) とその標準偏差
|
|
286
|
+
feature_names : array-like of shape (n_features,), optional
|
|
287
|
+
各特徴量の名称
|
|
288
|
+
bar_color : str, default="limegreen"
|
|
289
|
+
バーの色
|
|
290
|
+
errorbar : bool, default=False
|
|
291
|
+
エラーバーを表示するかどうか
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
matplotlib.axes.Axes
|
|
296
|
+
更新済みの `matplotlib.axes.Axes`
|
|
297
|
+
|
|
298
|
+
"""
|
|
299
|
+
_init()
|
|
300
|
+
importances = np.asarray(importances)
|
|
301
|
+
if feature_names is None:
|
|
302
|
+
feature_names = [f"feature {l + 1}" for l in range(importances.shape[0])]
|
|
303
|
+
|
|
304
|
+
# ソート
|
|
305
|
+
sort_indice = np.argsort(importances[:, 0])
|
|
306
|
+
importances_sorted = importances[sort_indice]
|
|
307
|
+
feature_names_sorted = np.array(feature_names)[sort_indice]
|
|
308
|
+
|
|
309
|
+
ax.barh(
|
|
310
|
+
feature_names_sorted,
|
|
311
|
+
importances_sorted[:, 0],
|
|
312
|
+
xerr=importances_sorted[:, 1] if errorbar else 0,
|
|
313
|
+
capsize=2,
|
|
314
|
+
height=1.0,
|
|
315
|
+
edgecolor="black",
|
|
316
|
+
color=bar_color,
|
|
317
|
+
)
|
|
318
|
+
ax.set_xlabel("Feature importance", fontsize=16)
|
|
319
|
+
ax.set_ylabel("Features", fontsize=16)
|
|
320
|
+
return ax
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spmkit-learn
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Meta-Sparse Modeling Library for Chemical Data Prediction.
|
|
5
|
+
Author-email: Shota Inoue <inoue.shota@st.kitasato-u.ac.jp>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Classifier: License :: Other/Proprietary License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy<=1.26.4,>=1.25.2
|
|
18
|
+
Requires-Dist: scikit-learn==1.6.0
|
|
19
|
+
Requires-Dist: matplotlib>=3.7.0
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# spmkit
|
|
23
|
+
|
|
24
|
+
**spmkit** is meta-sparse modeling library for chemical data prediction.
|
|
25
|
+
|
|
26
|
+
-----
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# via wheel binary
|
|
32
|
+
pip install spmkit-***.whl
|
|
33
|
+
|
|
34
|
+
# via gzip source
|
|
35
|
+
pip install spmkit-***.tar.gz
|
|
36
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
spmkit/__init__.py
|
|
5
|
+
spmkit/plots.py
|
|
6
|
+
spmkit/assets/Arial.ttf
|
|
7
|
+
spmkit/models/__init__.py
|
|
8
|
+
spmkit/models/exhaustive_search.py
|
|
9
|
+
spmkit/models/stability_selection.py
|
|
10
|
+
spmkit_learn.egg-info/PKG-INFO
|
|
11
|
+
spmkit_learn.egg-info/SOURCES.txt
|
|
12
|
+
spmkit_learn.egg-info/dependency_links.txt
|
|
13
|
+
spmkit_learn.egg-info/requires.txt
|
|
14
|
+
spmkit_learn.egg-info/top_level.txt
|
|
15
|
+
test/test.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spmkit
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
# Wine Quality データセットをダウンロードする
|
|
4
|
+
from sklearn.datasets import fetch_openml
|
|
5
|
+
from spmkit.models import StabilitySelection
|
|
6
|
+
|
|
7
|
+
# データフェッチ
|
|
8
|
+
dataset = fetch_openml(data_id=43257, as_frame=True)
|
|
9
|
+
|
|
10
|
+
# 不要な列を削除し、X, y に分割
|
|
11
|
+
X = dataset.data.drop(["quality", "id"], axis=1)
|
|
12
|
+
y = dataset.data["quality"]
|
|
13
|
+
|
|
14
|
+
# StabilitySelection モデルを定義
|
|
15
|
+
model = StabilitySelection(
|
|
16
|
+
subsample_iter=1000,
|
|
17
|
+
n_alphas=100,
|
|
18
|
+
min_alpha=1e-6,
|
|
19
|
+
max_alpha=1.0,
|
|
20
|
+
random_state=42,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# 特徴量選択を実行
|
|
24
|
+
model.fit(X, y)
|
|
25
|
+
|
|
26
|
+
# alpha ごとの選択確率を算出
|
|
27
|
+
selection_path = pd.DataFrame(model.selection_path_, index=X.columns)
|
|
28
|
+
|
|
29
|
+
print(selection_path)
|