🐈

Act 26. Pythonで主成分分析を試す

2024/11/29に公開

はじめに

Act 01. AIで外国為替を自動売買するまでの道のりをベースに学習を進めて行く。

前回は主成分分析の概要について学習した。
なので今回は実際にpythonで主成分分析を使ったデータの次元削減を行う。
また、最後に次元削減後のデータを使って予測しているので、もしよかったら見てね。

データセットが必要になるため、お馴染みのpythonで提供されているデータセットを使用する。
今回は特徴量の数が多いワインのデータセット(load_wine)を用いてコードを書いていく。

主成分分析(PCA)

結論

まずはいつも通りコードを載せておく。

Act26.py
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import pandas as pd
import matplotlib.pyplot as plt

# データセットの読み込み
wine = load_wine()
data = wine.data  # 特徴量(13次元)
target = wine.target  # クラスラベル

# 特徴量の標準化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# PCAの適用
pca = PCA(n_components=3)  # 主成分を3つに削減
data_pca = pca.fit_transform(data_scaled)

# 主成分の寄与率
explained_variance = pca.explained_variance_ratio_
print("各主成分の寄与率:", explained_variance)
print("累積寄与率:", explained_variance.cumsum())

# 主成分をデータフレームに変換(クラスラベル付き)
pca_df = pd.DataFrame(data_pca, columns=['PC1', 'PC2', 'PC3'])
pca_df['target'] = target

# 主成分1と2の散布図を可視化
plt.figure(figsize=(8, 6))
for t, label in zip([0, 1, 2], wine.target_names):
    plt.scatter(
        pca_df.loc[pca_df['target'] == t, 'PC1'], 
        pca_df.loc[pca_df['target'] == t, 'PC2'], 
        label=label
    )
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('PCA of Wine Dataset (PC1 vs PC2)')
plt.legend()
plt.show()

fig = plt.figure(figsize=(10, 7))
ax = fig.add_subplot(111, projection='3d')
for t, label in zip([0, 1, 2], wine.target_names):
    ax.scatter(
        pca_df.loc[pca_df['target'] == t, 'PC1'], 
        pca_df.loc[pca_df['target'] == t, 'PC2'], 
        pca_df.loc[pca_df['target'] == t, 'PC3'], 
        label=label
    )
ax.set_xlabel('Principal Component 1')
ax.set_ylabel('Principal Component 2')
ax.set_zlabel('Principal Component 3')
ax.set_title('PCA of Wine Dataset (3D Plot)')
plt.legend()
plt.show()

出力は以下の通り。
まずはコンソールに以下が出力される。

各主成分の寄与率: [0.36198848 0.1920749  0.11123631]
累積寄与率: [0.36198848 0.55406338 0.66529969]

その後にプロットが表示される。

×でウィンドウを閉じると、さらに別のプロットが表示される。

コードの説明

ということでコードについて説明していく。
毎回説明している個所については割愛する。

データセット

まずはwineデータセットについて。
このデータセットでは、以下の特徴量が存在している。

  • Alcohol: ワインに含まれるアルコールの量(%)
  • Malic Acid: りんご酸の量
  • Ash: 灰分量(ワインを燃焼させた後に残るミネラル含有量)
  • Alcalinity of Ash: 灰分のアルカリ度
  • Magnesium: マグネシウム含有量
  • Total Phenols: 総フェノール量
  • Flavanoids: フラボノイドの量
  • Nonflavanoid Phenols: 非フラボノイドフェノールの量
  • Proanthocyanins: プロアントシアニジン(色素化合物)
  • Colour Intensity: 色の濃度
  • Hue: 色相(色の質)
  • OD280/OD315 of diluted wines: 280nmおよび315nmでの光学密度の比率
  • Proline: プロリン(アミノ酸の一種)の含有量

何がどうとかは正直どうでも良いけど、一応こんな感じの特徴量があるよってことで。

続いてターゲットについて。
このデータセットでは、3つのターゲットが存在する。

確認してみたが、['class_0' 'class_1' 'class_2']3つだった。
具体的に何かは分からない…。

まあ、とりあえずターゲットは3つだということだけ理解しておく。

モデルの学習

まず初めに、特徴量それぞれの単位やサイズが異なるため、データの標準化を行う。
標準化はfrom sklearn.preprocessing import StandardScalerでインポートしている通り、StandardScalerクラスのfit_transform()メソッドを使用する。

Act26.py
from sklearn.datasets import load_wine
from sklearn.preprocessing import StandardScaler
import pandas as pd

# データセットの読み込み
wine = load_wine()
data = wine.data  # 特徴量(13次元)
target = wine.target  # クラスラベル

# 特徴量の標準化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# 標準化前のデータ
df = pd.DataFrame(data, columns=wine.feature_names)
print(df.head(10))

# 標準化後のデータ
df_scaled = pd.DataFrame(data_scaled, columns=wine.feature_names)
print(df_scaled.head(10))

出力は以下の通りとなる。
桁の異なる様々なデータが標準化により分析しやすい値となった。

   alcohol  malic_acid   ash  alcalinity_of_ash  magnesium  total_phenols  flavanoids  nonflavanoid_phenols  proanthocyanins  color_intensity   hue  od280/od315_of_diluted_wines  proline
0    14.23        1.71  2.43               15.6      127.0           2.80        3.06                  0.28             2.29             5.64  1.04                          3.92   1065.0
1    13.20        1.78  2.14               11.2      100.0           2.65        2.76                  0.26             1.28             4.38  1.05                          3.40   1050.0
2    13.16        2.36  2.67               18.6      101.0           2.80        3.24                  0.30             2.81             5.68  1.03                          3.17   1185.0
3    14.37        1.95  2.50               16.8      113.0           3.85        3.49                  0.24             2.18             7.80  0.86                          3.45   1480.0
4    13.24        2.59  2.87               21.0      118.0           2.80        2.69                  0.39             1.82             4.32  1.04                          2.93    735.0
5    14.20        1.76  2.45               15.2      112.0           3.27        3.39                  0.34             1.97             6.75  1.05                          2.85   1450.0
6    14.39        1.87  2.45               14.6       96.0           2.50        2.52                  0.30             1.98             5.25  1.02                          3.58   1290.0
7    14.06        2.15  2.61               17.6      121.0           2.60        2.51                  0.31             1.25             5.05  1.06                          3.58   1295.0
8    14.83        1.64  2.17               14.0       97.0           2.80        2.98                  0.29             1.98             5.20  1.08                          2.85   1045.0
9    13.86        1.35  2.27               16.0       98.0           2.98        3.15                  0.22             1.85             7.22  1.01                          3.55   1045.0

    alcohol  malic_acid       ash  alcalinity_of_ash  magnesium  total_phenols  ...  nonflavanoid_phenols  proanthocyanins  color_intensity       hue  od280/od315_of_diluted_wines   proline       
0  1.518613   -0.562250  0.232053          -1.169593   1.913905       0.808997  ...             -0.659563         1.224884         0.251717  0.362177                      1.847920  1.013009       
1  0.246290   -0.499413 -0.827996          -2.490847   0.018145       0.568648  ...             -0.820719        -0.544721        -0.293321  0.406051                      1.113449  0.965242       
2  0.196879    0.021231  1.109334          -0.268738   0.088358       0.808997  ...             -0.498407         2.135968         0.269020  0.318304                      0.788587  1.395148       
3  1.691550   -0.346811  0.487926          -0.809251   0.930918       2.491446  ...             -0.981875         1.032155         1.186068 -0.427544                      1.184071  2.334574       
4  0.295700    0.227694  1.840403           0.451946   1.281985       0.808997  ...              0.226796         0.401404        -0.319276  0.362177                      0.449601 -0.037874       
5  1.481555   -0.517367  0.305159          -1.289707   0.860705       1.562093  ...             -0.176095         0.664217         0.731870  0.406051                      0.336606  2.239039       
6  1.716255   -0.418624  0.305159          -1.469878  -0.262708       0.328298  ...             -0.498407         0.681738         0.083015  0.274431                      1.367689  1.729520       
7  1.308617   -0.167278  0.890014          -0.569023   1.492625       0.488531  ...             -0.417829        -0.597284        -0.003499  0.449924                      1.367689  1.745442       
8  2.259772   -0.625086 -0.718336          -1.650049  -0.192495       0.808997  ...             -0.578985         0.681738         0.061386  0.537671                      0.336606  0.949319       
9  1.061565   -0.885409 -0.352802          -1.049479  -0.122282       1.097417  ...             -1.143031         0.453967         0.935177  0.230557                      1.325316  0.949319

余談だがStandardScalerクラスにはfit()メソッドとfit_transform()メソッドが存在する。
fit()メソッドは計算のみ(平均値や標準偏差など)を行い、fit_transform()メソッドは、その計算結果をもとにデータの標準化を行うメソッドになる。

データセットを標準化したため、次に主成分分析(PCA)を行う。
前回の説明であんなに苦戦したのに、コードだとめちゃくちゃ簡単にできちゃうの凄いね…。

Act26.py
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import pandas as pd

# データセットの読み込み
wine = load_wine()
data = wine.data  # 特徴量(13次元)
target = wine.target  # クラスラベル

# 特徴量の標準化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# PCAの適用
pca = PCA(n_components=3)  # 主成分を3つに削減
data_pca = pca.fit_transform(data_scaled)
df = pd.DataFrame(data_pca)
print(df.head(10))

出力は以下の通りとなる。
PCA(n_components=3)で主成分を3つに削減するように指定したため、3列のデータセットに変換された。

          0         1         2
0  3.316751  1.443463 -0.165739
1  2.209465 -0.333393 -2.026457
2  2.516740  1.031151  0.982819
3  3.757066  2.756372 -0.176192
4  1.008908  0.869831  2.026688
5  3.050254  2.122401 -0.629396
6  2.449090  1.174850 -0.977095
7  2.059437  1.608963  0.146282
8  2.510874  0.918071 -1.770969
9  2.753628  0.789438 -0.984247

主成分分析による次元削減はこんな感じ。
今までの学習もあってか、そんなに難しくはない。

ということで次に進んでみる。

寄与率について

コードの内容を見ていると寄与率という言葉が出現する。
寄与率とは簡単に言うと、各主成分が全体の中でどれだけの変動の割合を占めるかを示すもので、値が大きいほど相対的に説明力が高い主成分であることを表す。

Act26.py
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import pandas as pd
import matplotlib.pyplot as plt

# データセットの読み込み
wine = load_wine()
data = wine.data  # 特徴量(13次元)
target = wine.target  # クラスラベル

# 特徴量の標準化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# PCAの適用
pca = PCA(n_components=3)  # 主成分を3つに削減
data_pca = pca.fit_transform(data_scaled)

# 主成分の寄与率
explained_variance = pca.explained_variance_ratio_
print("各主成分の寄与率:", explained_variance)
print("累積寄与率:", explained_variance.cumsum())

出力は以下の通り。

各主成分の寄与率: [0.36198848 0.1920749  0.11123631]
累積寄与率: [0.36198848 0.55406338 0.66529969]
  • 各主成分の寄与率
    配列の左(0番目)から第一主成分、第二主成分、第三主成分となる。
    第一主成分とは、PCA前のデータの情報を最も多く説明する成分

  • 累積寄与率
    各主成分の寄与率を累積していったもの。

つまり、13個の特徴量を3次元に次元削減した場合、3つの主成分で元のデータの66.5\%を説明することが出来ているという意味になる。

ちなみに主成分を5つにすると以下の通りとなり、全体の80\%のデータを説明出来ていることになる。

各主成分の寄与率: [0.36198848 0.1920749  0.11123631 0.0706903  0.06563294]
累積寄与率: [0.36198848 0.55406338 0.66529969 0.73598999 0.80162293]

一旦コードについての説明は以上。
matplotlibについては主成分分析と大きく関わるわけではないため割愛。

分析・予測

元のデータセットとPCAで次元削減した後のデータを使ってロジスティック回帰分析を行おうと思う。

以下のようなコードを作ってみた。
やっていることは単純で、元のデータ、3次元まで削減したデータ、5次元まで削減したデータを使ってロジスティック回帰分析を行っている。

Act26-2.py
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np

# データセットの読み込み
wine = load_wine()
data = wine.data  # 特徴量(13次元)
target = wine.target  # クラスラベル

# 特徴量の標準化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# PCAの適用(3次元)
pca_3 = PCA(n_components=3)  # 主成分を3つに削減
data_pca_3 = pca_3.fit_transform(data_scaled)

# PCAの適用(5次元)
pca_5 = PCA(n_components=5)  # 主成分を5つに削減
data_pca_5 = pca_5.fit_transform(data_scaled)

# 元のデータで分析・予測
print("元のデータ")
model = LogisticRegression()
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=2)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
report = classification_report(y_test, y_pred)
print(report)

# PCAで3次元まで削減したデータで分析・予測
print("3次元に削減したデータ")
model_3 = LogisticRegression()
X_train_3, X_test_3, y_train_3, y_test_3 = train_test_split(data_pca_3, target, test_size=0.2, random_state=2)
model_3.fit(X_train_3, y_train_3)
y_pred_3 = model_3.predict(X_test_3)
report_3 = classification_report(y_test_3, y_pred_3)
print(report_3)

# PCAで5次元まで削減したデータで分析・予測
print("5次元に削減したデータ")
model_5 = LogisticRegression()
X_train_5, X_test_5, y_train_5, y_test_5 = train_test_split(data_pca_5, target, test_size=0.2, random_state=2)
model_5.fit(X_train_5, y_train_5)
y_pred_5 = model_5.predict(X_test_5)
report_5 = classification_report(y_test_5, y_pred_5)
print(report_5)

出力は以下の通り。

元のデータ
/home/onishi/.pyenv/versions/3.12.7/lib/python3.12/site-packages/sklearn/linear_model/_logistic.py:469: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
              precision    recall  f1-score   support

           0       0.94      0.89      0.91        18
           1       0.73      0.89      0.80         9
           2       1.00      0.89      0.94         9

    accuracy                           0.89        36
   macro avg       0.89      0.89      0.89        36
weighted avg       0.90      0.89      0.89        36

3次元に削減したデータ
              precision    recall  f1-score   support

           0       1.00      0.83      0.91        18
           1       0.75      1.00      0.86         9
           2       1.00      1.00      1.00         9

    accuracy                           0.92        36
   macro avg       0.92      0.94      0.92        36
weighted avg       0.94      0.92      0.92        36

5次元に削減したデータ
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        18
           1       1.00      1.00      1.00         9
           2       1.00      1.00      1.00         9

    accuracy                           1.00        36
   macro avg       1.00      1.00      1.00        36
weighted avg       1.00      1.00      1.00        36

気になる点は3つ。

  • 元データの時によく分からないログが出力されている。
    こちらは最適化アルゴリズムの最大反復回数に引っかかった際に出るメッセージ。
    デフォルトでは100回になっているため、それ以上に試行が必要だということ。

    ちなみに、最適化アルゴリズムとは、損失関数で求めた予測した値と実際の値の誤差を最小にするためにパラメータを調整することだったね。

  • 3次元に削減したデータでは、最大反復回数のログが出力されていない。
    特徴量が減ったからか試行回数が減り、デフォルトの100回でも問題なく学習が出来ている。

  • 5次元に削減したデータの場合は正解率が100\%になっている。
    これは驚いた。特徴量が多い方が情報がたくさんあるため、正解率が上がるのかなーと思っていたが、実際は必要な情報だけあれば正解率が上がるってことかな?(一概には言えないと思うけど…。)

この結果を得て、主成分分析による次元削減が如何に大事なのかということが分かった気がする。

さいごに

主成分分析、いい内容だったね。
特に最後の正解率が100\%になったところなんて結構感動した。
学習の時間は短くなり、予測の精度も上がるなんて知ったからには、積極的に次元削減していきたくなる。

次回から強化学習について学ぶ。
正直ここからが本番みたいな感じがするから、気を抜かずにしっかりと学んでいこう!
ただ、追求しすぎて挫折しないようにだけは気を付けたい。

あー、めっちゃ楽しみ!
ではまた!

Discussion