機械学習のPython実現ガイド

【概要】機械学習の用語(49項目)とPython実装を示す。用語は、基本概念、教師あり学習手法、教師なし学習手法(クラスタリング)、次元削減・特徴抽出手法、行列分解手法、評価指標、実装のための技術、実装結果の検証・評価に分類し、対応するPython関数を示す。実装例として、ロジスティック回帰、SVM、k-NN、決定木・ランダムフォレスト、k-means、階層的クラスタリング、DBSCAN、PCA、t-SNE、因子分析、線形判別分析(LDA)、特異値分解(SVD)、QR分解の13のプログラムを提供する。各実装には演習を付す。

【本資料の前提と対象範囲】

【サイト内のPython関連主要ページ】

【外部リソース】

機械学習の用語

基本概念

教師あり学習手法

教師なし学習手法(クラスタリング)

次元削減・特徴抽出手法

行列分解手法

評価指標

実装のための技術

実装結果の検証・評価

Pythonでの実現

教師あり学習手法(6項目)
用語対応関数・クラス・属性
ロジスティック回帰

主要:LogisticRegression (scikit-learn) - クラス

関連:fit, predict - メソッド

サポートベクターマシン(SVM)

主要:SVC (scikit-learn) - クラス(分類用)

関連:SVR (scikit-learn) - クラス(回帰用)

k近傍法(k-NN)

主要:KNeighborsClassifier (scikit-learn) - クラス(分類用)

関連:KNeighborsRegressor (scikit-learn) - クラス(回帰用)

決定木

主要:DecisionTreeClassifier (scikit-learn) - クラス(分類用)

関連:DecisionTreeRegressor (scikit-learn) - クラス(回帰用)

ランダムフォレスト

主要:RandomForestClassifier (scikit-learn) - クラス(分類用)

関連:RandomForestRegressor (scikit-learn) - クラス(回帰用)

属性:feature_importances_ - 特徴量重要度

線形判別分析(LDA)

主要:LinearDiscriminantAnalysis (scikit-learn) - クラス

属性:coef_ - 各クラスの分類用係数

属性:explained_variance_ratio_ - 各判別関数の寄与率

教師なし学習手法(クラスタリング)(3項目)
用語対応関数・クラス・属性
k-meansクラスタリング

主要:KMeans (scikit-learn) - クラス

属性:inertia_ - クラスタ内分散

階層的クラスタリング

主要:AgglomerativeClustering (scikit-learn) - クラス(凝集型)

関連:linkage (scipy.cluster.hierarchy) - リンケージ行列作成用関数

DBSCAN

主要:DBSCAN (scikit-learn) - クラス

次元削減・特徴抽出手法(3項目)
用語対応関数・クラス・属性
PCA(主成分分析)

主要:PCA (scikit-learn) - クラス

属性:explained_variance_ratio_ - 寄与率

属性:explained_variance_ - 固有値

属性:components_ - 主成分(固有ベクトル)

因子分析

主要:FactorAnalysis (scikit-learn) - クラス

属性:components_ - 因子負荷量(形状は (因子数, 変数数))

t-SNE

主要:TSNE (scikit-learn) - クラス

属性:kl_divergence_ - 最終的なKLダイバージェンス

行列分解手法(2項目)
用語対応関数・クラス・属性
特異値分解(SVD)

主要:np.linalg.svd (numpy) - 関数

関連:TruncatedSVD (scikit-learn) - クラス(疎行列対応。疎行列とは要素の大部分が0である行列)

QR分解

主要:np.linalg.qr (numpy) - 関数

評価指標(12項目)
用語対応関数・クラス・属性
精度(Accuracy)

主要:accuracy_score (scikit-learn) - 関数

関連:classification_report (scikit-learn) - 複数指標を一括出力

適合率(Precision)

主要:precision_score (scikit-learn) - 関数

関連:classification_report (scikit-learn) - 複数指標を一括出力

再現率(Recall)

主要:recall_score (scikit-learn) - 関数

関連:classification_report (scikit-learn) - 複数指標を一括出力

F1スコア

主要:f1_score (scikit-learn) - 関数

関連:classification_report (scikit-learn) - 複数指標を一括出力

コフェネティック相関係数

主要:cophenet (scipy.cluster.hierarchy) - 関数

KL-divergence

主要:TSNE.kl_divergence_ (scikit-learn) - 属性

関連:entropy (scipy.stats) - 関数(2つの確率分布を引数に指定)

寄与率

主要:PCA.explained_variance_ratio_ (scikit-learn) - 属性

累積寄与率

主要:cumsum (numpy) - 関数(PCA.explained_variance_ratio_ に適用)

固有値

主要:PCA.explained_variance_ (scikit-learn) - 属性

主成分負荷量

主要:PCA.components_explained_variance_ から算出(scikit-learn)

共通性

主要:FactorAnalysis.components_ (scikit-learn) - 属性(因子負荷量の二乗和で算出)

判別係数

主要:LinearDiscriminantAnalysis.coef_ (scikit-learn) - 属性

実装のための技術(8項目)
用語対応関数・クラス・属性
標準化

主要:StandardScaler (scikit-learn) - クラス

関連:fit_transform - メソッド

次元削減

主要:PCA (scikit-learn) - クラス

関連:TSNE (scikit-learn) - クラス

k分割交差検証

主要:cross_val_score (scikit-learn) - 関数

関連:KFold (scikit-learn) - クラス(分割方法の指定)

グリッドサーチ

主要:GridSearchCV (scikit-learn) - クラス

関連:RandomizedSearchCV (scikit-learn) - クラス(ランダム探索用)

データの可視化

主要:scatter (matplotlib.pyplot) - 関数(散布図)

関連:plot (matplotlib.pyplot) - 関数(折れ線グラフ)

関連:heatmap (seaborn) - 関数(ヒートマップ)

エルボー法

主要:KMeans.inertia_ (scikit-learn) - 属性

関連:plot (matplotlib.pyplot) - プロット用関数

デンドログラム

主要:dendrogram (scipy.cluster.hierarchy) - 関数

関連:linkage (scipy.cluster.hierarchy) - リンケージ行列作成用関数

最尤推定

主要:LogisticRegression (scikit-learn) - クラス(内部で使用)

関連:FactorAnalysis (scikit-learn) - クラス(内部で使用)

実装結果の検証・評価(4項目)
用語対応関数・クラス・属性
汎化性能

主要:cross_val_score (scikit-learn) - 関数

ハイパーパラメータ

主要:GridSearchCV (scikit-learn) - クラス(最適化用)

関連:RandomizedSearchCV (scikit-learn) - クラス(ランダム探索用)

混同行列(Confusion Matrix)

主要:confusion_matrix (scikit-learn) - 関数

関連:classification_report (scikit-learn) - 派生指標を一括出力

関連:ConfusionMatrixDisplay (scikit-learn) - クラス(可視化用)

シルエット係数(Silhouette Coefficient)

主要:silhouette_score (scikit-learn) - 関数(全体スコア)

関連:silhouette_samples (scikit-learn) - 関数(サンプルごとの係数)

データ準備(前提となる処理)
用語対応関数・クラス・属性
データ分割

主要:train_test_split (scikit-learn) - 関数

データフレーム操作

主要:DataFrame (pandas) - クラス

補足:classification_report (scikit-learn) は、各クラスの適合率(Precision)、再現率(Recall)、F1スコア、サンプル数(support)と、全体の精度(Accuracy)を一括出力する関数である。個別の指標を計算する場合は各関数(accuracy_score など)を使用し、まとめて確認する場合は classification_report を使用する。

演習

以下の演習では、Irisデータセット(4特徴量、3クラス、150サンプルから構成される分類用の標準データセット)を使用する。距離や内積を用いるアルゴリズム(SVM、k-NN、PCA、t-SNE、DBSCAN、因子分析、LDA)では標準化を行う。決定木およびランダムフォレストはスケールに依存しないため標準化を行わない。各演習で共通する処理(データ読み込み、データ分割、標準化、classification_report による評価、random_state=42 の指定)は同一の記述に揃えている。

演習1.ロジスティック回帰

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, confusion_matrix

# Irisデータセットの読み込み
iris = load_iris()
X = iris.data
y = iris.target

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# モデルの構築と学習
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)

# テストデータでの予測と性能評価
y_pred = model.predict(X_test)
print(classification_report(y_test, y_pred))

# 混同行列の表示
print("Confusion Matrix:")
print(confusion_matrix(y_test, y_pred))

# 5分割交差検証による性能評価
cv_scores = cross_val_score(model, X, y, cv=5)
print(f"Cross-validation scores: {cv_scores}")
print(f"Average CV score: {cv_scores.mean():.3f} (+/- {cv_scores.std() * 2:.3f})")

線形モデルとシグモイド関数を組み合わせた分類手法である。最尤推定により重みパラメータを最適化し、テストデータで予測する。classification_report で精度、適合率、再現率、F1スコアを算出する。混同行列で予測結果を真陽性、偽陽性、真陰性、偽陰性に分類して表示する。k分割交差検証で汎化性能を評価する。出力の「+/-」以降は標準偏差の2倍であり、スコアのばらつきを示す。scikit-learn 1.8以降、LogisticRegressionpenalty および n_jobs 引数は非推奨であり将来削除される予定である。本例はこれらを指定していないため警告なく動作する。正則化の種類を指定する場合は、penalty ではなく l1_ratio(L2なら0.0、L1なら1.0)を用いる。

手順:上記コードを実行し、classification_report、混同行列、交差検証スコアの3つの出力を確認する。

ヒント:交差検証は cv=5 で5分割を指定している。

考察ポイント:混同行列の非対角成分(誤分類数)と、交差検証スコアのばらつき(標準偏差の2倍)から、どのクラスが誤分類されやすいかを読み取る。

演習2.SVM

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# モデルの構築
svm = SVC(kernel='rbf', random_state=42)

# パラメータグリッド
param_grid = {
    'C': [0.1, 1, 10],
    'gamma': ['scale', 'auto', 0.1, 1],
}

# グリッドサーチ
grid_search = GridSearchCV(svm, param_grid, cv=5, scoring='accuracy', n_jobs=-1)

# モデルの学習
grid_search.fit(X_train_scaled, y_train)

# 最適パラメータと評価
print(f"Best parameters: {grid_search.best_params_}")
y_pred = grid_search.predict(X_test_scaled)
print(classification_report(y_test, y_pred))

マージン最大化により最適な分離超平面を探索する手法である。RBFカーネル(Radial Basis Function:データ間の類似度をガウス関数で測定するカーネル関数)を使用し、カーネルトリックで非線形分類を実現する。グリッドサーチでハイパーパラメータCとgammaの全組み合わせを探索し、k分割交差検証で最適値を決定する。Cは誤分類に対するペナルティの強さ、gammaはカーネルの影響範囲を制御するパラメータである。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、出力された Best parameters(最適なCとgamma)を確認する。

ヒント:param_grid のCとgammaの候補値を変更すると、探索される組み合わせが変わる。

考察ポイント:選ばれた最適パラメータが候補値の端(最小値や最大値)にある場合、候補範囲を広げる必要があることを読み取る。

演習3.k-NN

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# k値の範囲でモデル評価
k_range = range(1, 31)
k_scores = []
for k in k_range:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_train_scaled, y_train, cv=5)
    k_scores.append(scores.mean())

# 最適なkの選択
best_k = k_range[np.argmax(k_scores)]
print(f"Best k: {best_k}")

# 最適モデルの構築と評価
knn = KNeighborsClassifier(n_neighbors=best_k)
knn.fit(X_train_scaled, y_train)
y_pred = knn.predict(X_test_scaled)
print(classification_report(y_test, y_pred))

訓練データからk個の最近傍点を距離計算で特定し、予測するインスタンスベース学習手法である。k値を1から30の範囲で変化させ、k分割交差検証で最適値を選択する。kが小さいとノイズに敏感になり、kが大きいと決定境界が不明瞭になる。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、出力された Best k と分類性能を確認する。

ヒント:k_range で評価するk値の範囲を指定している。

考察ポイント:選ばれたkの値が小さい場合と大きい場合で、決定境界の性質がどう変わるかを考える。

演習4.決定木・ランダムフォレスト

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 決定木による学習と予測
dt = DecisionTreeClassifier(random_state=42)
dt.fit(X_train, y_train)
dt_pred = dt.predict(X_test)
print("Decision Tree:")
print(classification_report(y_test, dt_pred))

# ランダムフォレストによる学習と予測
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
rf_pred = rf.predict(X_test)
print("\nRandom Forest:")
print(classification_report(y_test, rf_pred))

# 特徴量重要度の算出と表示
feature_importance = pd.DataFrame({
    'feature': iris.feature_names,
    'importance': rf.feature_importances_
})
print("\nFeature importance:")
print(feature_importance.sort_values('importance', ascending=False))

決定木はデータを再帰的に分割して判断ルールを生成する手法である。ランダムフォレストは複数の決定木を組み合わせたアンサンブル学習手法であり、複数の決定木の予測を統合することで単一の決定木より過学習を抑制する。両手法でモデルを構築し、classification_report で性能を比較する。特徴量重要度で各特徴量の予測への寄与を評価する。これらの手法はスケールに依存しないため、標準化は行わない。

手順:上記コードを実行し、決定木とランダムフォレストの分類性能、および特徴量重要度を確認する。

ヒント:n_estimators はランダムフォレストを構成する決定木の本数である。

考察ポイント:2手法の性能差と、重要度が高い特徴量がどれかを読み取る。

演習5.k-means

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# エルボー法とシルエット分析のための値を計算
inertias = []
silhouette_scores = []
K = range(2, 10)
for k in K:
    kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
    kmeans.fit(X_scaled)
    inertias.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(X_scaled, kmeans.labels_))

# エルボー法のプロット(最適クラスタ数の参考にする)
plt.figure(figsize=(8, 6))
plt.plot(K, inertias, 'bo-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Inertia')
plt.title('Elbow Method')
plt.grid(True)
plt.show()

# シルエットスコアが最大となるクラスタ数を選択
best_k = K[np.argmax(silhouette_scores)]

# 最適なクラスタ数でモデルを再学習
kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10)
clusters = kmeans.fit_predict(X_scaled)

# 結果の出力
print(f"Best number of clusters: {best_k}")
print(f"Silhouette score: {silhouette_score(X_scaled, clusters):.3f}")

# クラスタリング結果の基本統計量
cluster_stats = pd.DataFrame(X)
cluster_stats['Cluster'] = clusters
print("\nCluster statistics:")
print(cluster_stats.groupby('Cluster').mean())

データ点の割り当てと中心の再計算を収束まで反復する教師なし学習手法である。本例ではエルボー法は可視化による参考とし、最適クラスタ数の決定にはシルエット係数を用いる。エルボー法はクラスタ内分散の減少が緩やかになる転換点を、シルエット係数は同一クラスタ内の凝集度と他クラスタとの分離度を示す。random_state を固定して再現性を確保する(k-meansは初期値に依存するため、固定により同じ初期化が再現される)。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、エルボー法のプロットと、シルエット係数で選ばれた best_k を確認する。

ヒント:K = range(2, 10) が評価するクラスタ数の範囲である。

考察ポイント:エルボー法のプロットで分散の減少が緩やかになる点と、シルエット係数が選んだ値が一致するかを比較する。

演習6.階層的クラスタリング

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.cluster import AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from scipy.cluster.hierarchy import dendrogram, linkage, cophenet
from scipy.spatial.distance import pdist

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 階層的クラスタリングの実行
hierarchical = AgglomerativeClustering(n_clusters=3)
clusters = hierarchical.fit_predict(X_scaled)

# 結果の表示
print("階層的クラスタリング結果:")
print("各クラスタのサンプル数:", np.bincount(clusters))

# クラスタごとの基本統計量
cluster_stats = pd.DataFrame(X)
cluster_stats['Cluster'] = clusters
print("\nクラスタごとの平均値:")
print(cluster_stats.groupby('Cluster').mean())

# デンドログラムの作成
linkage_matrix = linkage(X_scaled, method='ward')
plt.figure(figsize=(10, 7))
dendrogram(linkage_matrix)
plt.title('Hierarchical Clustering Dendrogram')
plt.xlabel('Sample index')
plt.ylabel('Distance')
plt.show()

# コフェネティック相関係数の計算
c, coph_dists = cophenet(linkage_matrix, pdist(X_scaled))
print(f"\nCophenetic correlation coefficient: {c:.3f}")

ボトムアップ(凝集型)で個別データ点から開始して近接クラスタを段階的に統合する手法である。ウォード法(クラスタ統合時の分散増加を最小化する手法)で距離を計算する。デンドログラムで階層構造を樹形図として可視化する。コフェネティック相関係数で元の距離とデンドログラム上の距離の相関を測定する。値が1に近いほどデンドログラムが元のデータ構造を保持している。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、デンドログラムとコフェネティック相関係数を確認する。

ヒント:method='ward' がクラスタ統合の基準(ウォード法)である。

考察ポイント:デンドログラムをどの高さで切ると3クラスタになるか、コフェネティック相関係数が高いかを読み取る。

演習7.DBSCAN

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# DBSCANによるクラスタリング
dbscan = DBSCAN(eps=0.5, min_samples=5)
clusters = dbscan.fit_predict(X_scaled)

# クラスタ評価(クラスタラベル -1 はノイズ点を示す)
n_clusters = len(set(clusters)) - (1 if -1 in clusters else 0)
n_noise = list(clusters).count(-1)
print(f"Number of clusters: {n_clusters}")
print(f"Number of noise points: {n_noise}")

# シルエットスコアの計算(ノイズ点を除外。クラスタが2つ以上ある場合のみ)
mask = clusters != -1
if len(set(clusters[mask])) >= 2:
    silhouette_avg = silhouette_score(X_scaled[mask], clusters[mask])
    print(f"Silhouette score: {silhouette_avg:.3f}")
else:
    print("有効なクラスタが2つ未満のため、シルエット係数は計算できません。")

密度ベースのクラスタリング手法である。データ点を、近傍に一定数以上のデータ点を持つコアポイント、境界ポイント、ノイズポイントの3種類に分類する。epsは近傍の半径、min_samplesはコアポイント判定に必要な近傍点数である。クラスタ数の事前指定が不要で、外れ値を自動識別できる。結果はepsとmin_samplesに依存し、値によってはクラスタが1つに集約されたり全点がノイズになることがある。シルエット係数の計算ではノイズ点を除外する。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、クラスタ数、ノイズ点数、シルエット係数を確認する。

ヒント:epsmin_samples がDBSCANの2つの主要パラメータである。

考察ポイント:epsを変えるとクラスタ数とノイズ点数がどう変化するかを読み取る。

演習8.PCA

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# PCAの実行
pca = PCA()
X_pca = pca.fit_transform(X_scaled)

# 寄与率、累積寄与率、固有値の表示
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance_ratio = np.cumsum(explained_variance_ratio)
eigenvalues = pca.explained_variance_

print("Explained variance ratio:", explained_variance_ratio)
print("Cumulative explained variance ratio:", cumulative_variance_ratio)
print("Eigenvalues:", eigenvalues)

# 主成分の方向(固有ベクトル)の表示
print("\n主成分の方向(固有ベクトル):")
for i, components in enumerate(pca.components_, 1):
    print(f"\n第{i}主成分:")
    for j, component in enumerate(components):
        print(f"  {iris.feature_names[j]}: {component:.4f}")

# 可視化
plt.figure(figsize=(12, 4))

# スクリープロット(主成分番号に対する寄与率のプロット)
plt.subplot(131)
plt.plot(range(1, len(explained_variance_ratio) + 1), explained_variance_ratio, 'bo-')
plt.xlabel('Principal Component')
plt.ylabel('Explained Variance Ratio')
plt.title('Scree Plot')
plt.grid(True)

# 累積寄与率のプロット
plt.subplot(132)
plt.plot(range(1, len(cumulative_variance_ratio) + 1), cumulative_variance_ratio, 'ro-')
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance Ratio')
plt.title('Cumulative Variance')
plt.grid(True)

# 第1・第2主成分でのデータ分布
plt.subplot(133)
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis')
plt.xlabel('First Principal Component')
plt.ylabel('Second Principal Component')
plt.title('Data Distribution in PC Space')
plt.colorbar(scatter)

plt.tight_layout()
plt.show()

データの分散が最大となる軸を算出して次元削減を行う統計的手法である。寄与率で各主成分が全体の分散を説明する割合を算出する。累積寄与率を基準に保持する主成分数を選択する。スクリープロットで主成分の重要度を可視化する。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、寄与率・累積寄与率・固有値の数値と、3つのプロットを確認する。

ヒント:累積寄与率は寄与率を順に足し合わせた値である。

考察ポイント:第2主成分までで累積寄与率がどの程度に達するか、その2次元で3クラスが分離して見えるかを読み取る。

演習9.t-SNE

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.manifold import TSNE
from sklearn.preprocessing import StandardScaler

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# t-SNEの実行
tsne = TSNE(n_components=2, perplexity=30, random_state=42)
X_tsne = tsne.fit_transform(X_scaled)

# 最終的なKLダイバージェンス(高次元と低次元の確率分布の差異)
print(f"KL divergence: {tsne.kl_divergence_:.4f}")

# 結果の可視化
plt.figure(figsize=(10, 6))
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='viridis')
plt.colorbar(scatter)
plt.title('t-SNE visualization of Iris dataset')
plt.xlabel('First t-SNE component')
plt.ylabel('Second t-SNE component')
plt.show()

確率分布の一致度を最適化する非線形次元削減手法である。近傍点間の関係(局所構造)を保持しながら、クラスタ構造の可視化に適した低次元表現を生成する。KL-divergenceで高次元空間と低次元空間の確率分布の一致度を評価し、学習後の kl_divergence_ 属性で最終値を確認できる。perplexityは各データ点の近傍点数の目安を制御するパラメータである(t-SNE原論文では5から50の範囲が推奨されている)。random_state を固定しないと実行ごとに結果が異なる。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、2次元の散布図と KL divergence の値を確認する。

ヒント:perplexity は近傍点数の目安を制御する。

考察ポイント:t-SNEの散布図とPCAの散布図(演習8)で、クラスタの分離の見え方を比較する。

演習10.因子分析

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.decomposition import FactorAnalysis
from sklearn.preprocessing import StandardScaler

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# 標準化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 因子分析の実行(rotation='varimax' で解釈可能な負荷量を得る)
fa = FactorAnalysis(n_components=2, rotation='varimax', random_state=42)
X_fa = fa.fit_transform(X_scaled)

# 因子負荷量の表示(components_ は形状 (因子数, 変数数))
print("因子負荷量:")
for i, feature_name in enumerate(iris.feature_names):
    print(f"\n{feature_name}:")
    for j, loading in enumerate(fa.components_[:, i], 1):
        print(f"  因子{j}: {loading:.4f}")

# 共通性の計算(因子方向に負荷量の二乗和をとる)
communalities = np.sum(fa.components_**2, axis=0)
print("\n共通性:")
for feature_name, comm in zip(iris.feature_names, communalities):
    print(f"{feature_name}: {comm:.4f}")

# 因子得点間の相関の計算
factor_corr = np.corrcoef(X_fa.T)
print("\n因子得点間の相関:")
print(factor_corr)

# 可視化
plt.figure(figsize=(12, 4))

# 因子負荷量のプロット
plt.subplot(131)
plt.scatter(fa.components_[0], fa.components_[1])
for i, feature_name in enumerate(iris.feature_names):
    plt.annotate(feature_name, (fa.components_[0, i], fa.components_[1, i]))
plt.xlabel('第1因子')
plt.ylabel('第2因子')
plt.title('因子負荷量プロット')
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='--', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='--', alpha=0.3)

# 因子得点の分布
plt.subplot(132)
scatter = plt.scatter(X_fa[:, 0], X_fa[:, 1], c=y, cmap='viridis', alpha=0.6)
plt.xlabel('第1因子得点')
plt.ylabel('第2因子得点')
plt.title('因子得点の分布')
plt.colorbar(scatter)

# 共通性の棒グラフ
plt.subplot(133)
plt.bar(range(len(communalities)), communalities)
plt.xticks(range(len(iris.feature_names)), iris.feature_names, rotation=45, ha='right')
plt.ylabel('共通性')
plt.title('各変数の共通性')
plt.grid(True, axis='y')

plt.tight_layout()
plt.show()

観測変数の背後にある潜在的な共通因子を抽出する統計的手法である。因子負荷量(各観測変数と各潜在因子との関係を表す係数)で各観測変数と潜在因子との関係を評価する。scikit-learnでは components_ 属性(形状は (因子数, 変数数))から因子負荷量が得られ、行が因子、列が変数に対応する。本例では rotation='varimax' を指定して解釈しやすい負荷量を得る。共通性は各変数について因子方向に負荷量の二乗和をとって算出する。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、因子負荷量、共通性、3つのプロットを確認する。

ヒント:n_components=2 が抽出する因子数である。

考察ポイント:因子負荷量プロットで各変数がどの因子に強く関連するか、共通性が低い変数がどれかを読み取る。

演習11.線形判別分析(LDA)

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report

# Irisデータセットの読み込み
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# LDAの実行
lda = LinearDiscriminantAnalysis()
lda.fit(X_train_scaled, y_train)
X_lda = lda.transform(X_train_scaled)

# 分類用係数の表示(coef_ は形状 (クラス数, 特徴量数))
print("分類用係数:")
for i, coef in enumerate(lda.coef_, 1):
    print(f"\nクラス{i}:")
    for feature_name, c in zip(iris.feature_names, coef):
        print(f"  {feature_name}: {c:.4f}")

# 分類性能の評価
y_pred = lda.predict(X_test_scaled)
print("\n分類性能:")
print(f"正答率: {lda.score(X_test_scaled, y_test):.4f}")
print("\n分類レポート:")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

# 各判別関数の寄与率
print("\n各判別関数の寄与率:")
explained_ratios = lda.explained_variance_ratio_
for i, ratio in enumerate(explained_ratios, 1):
    print(f"判別関数{i}: {ratio:.4f}")

# 可視化
plt.figure(figsize=(12, 4))

# 第1・第2判別関数でのデータ分布
plt.subplot(131)
scatter = plt.scatter(X_lda[:, 0], X_lda[:, 1], c=y_train, cmap='viridis')
plt.xlabel('第1判別関数')
plt.ylabel('第2判別関数')
plt.title('判別空間でのデータ分布')
plt.colorbar(scatter, ticks=[0, 1, 2], label='Class')

# 判別関数の寄与率
plt.subplot(132)
plt.bar(range(1, len(explained_ratios) + 1), explained_ratios)
plt.xlabel('判別関数')
plt.ylabel('寄与率')
plt.title('判別関数の寄与率')
plt.grid(True, axis='y')

# 第1判別関数のヒストグラム
plt.subplot(133)
for class_label in np.unique(y_train):
    plt.hist(X_lda[y_train == class_label, 0], alpha=0.5,
             label=iris.target_names[class_label], bins=20)
plt.xlabel('第1判別関数')
plt.ylabel('頻度')
plt.title('第1判別関数によるクラス分離')
plt.legend()
plt.grid(True, axis='y')

plt.tight_layout()
plt.show()

分類と次元削減の両機能を持つ教師あり学習手法である。クラス間分散を最大化し、クラス内分散を最小化する軸を求める。coef_(形状 (クラス数, 特徴量数))は各クラスを分離する分類用の重みであり、kクラスではk-1本得られる判別軸とは異なる。判別軸そのものは transform() が返すk-1次元の判別空間に対応する。分類性能を評価し、判別空間でのデータ分布を可視化する。ヒストグラムでクラスが分離されているほど判別関数が有効である。距離計算を用いるため標準化を行う。

手順:上記コードを実行し、分類性能、判別関数の寄与率、判別空間の散布図を確認する。

ヒント:transform() が返す判別空間は、3クラスではk-1=2次元になる。

考察ポイント:第1判別関数だけでどの程度クラスが分離されているか、ヒストグラムの重なりから読み取る。

演習12.特異値分解(SVD)とQR分解

import numpy as np
import matplotlib.pyplot as plt

# サンプルデータの生成
np.random.seed(42)
n_rows, n_cols = 100, 50

# ランク3の行列を生成(ランクとは行列の線形独立な行または列の最大数)
U_true = np.random.normal(0, 1, (n_rows, 3))
V_true = np.random.normal(0, 1, (n_cols, 3))
singular_values_true = np.array([10, 5, 2])
X = U_true @ np.diag(singular_values_true) @ V_true.T

# ノイズの追加
X_noisy = X + np.random.normal(0, 0.1, (n_rows, n_cols))

# SVDの実行
U, s, Vt = np.linalg.svd(X_noisy, full_matrices=False)

# 特異値の表示
print("特異値(上位10個):")
for i, value in enumerate(s[:10], 1):
    print(f"σ_{i}: {value:.4f}")

# 累積寄与率の計算(特異値の二乗は分散に比例するため、二乗の比で算出する)
cumulative_variance = np.cumsum(s**2) / np.sum(s**2)
print(f"\n上位3成分の累積寄与率: {cumulative_variance[2]:.4f}")

# 低ランク近似の誤差を計算
errors = []
ranks = range(1, min(n_rows, n_cols) + 1)
for r in ranks:
    X_approx = U[:, :r] @ np.diag(s[:r]) @ Vt[:r, :]
    errors.append(np.linalg.norm(X_noisy - X_approx, 'fro'))

# 可視化
plt.figure(figsize=(15, 4))

# 特異値のプロット
plt.subplot(141)
plt.plot(range(1, len(s) + 1), s, 'o-')
plt.yscale('log')
plt.xlabel('成分番号')
plt.ylabel('特異値')
plt.title('特異値のスクリープロット')
plt.grid(True)

# 累積寄与率
plt.subplot(142)
plt.plot(range(1, len(s) + 1), cumulative_variance, 'o-')
plt.xlabel('成分数')
plt.ylabel('累積寄与率')
plt.title('累積寄与率')
plt.grid(True)

# 近似誤差
plt.subplot(143)
plt.plot(ranks[:20], errors[:20], 'o-')
plt.xlabel('使用する特異値の数')
plt.ylabel('近似誤差')
plt.title('低ランク近似の誤差')
plt.grid(True)

# オリジナルデータの可視化
plt.subplot(144)
plt.imshow(X_noisy[:20, :20], cmap='coolwarm', aspect='auto')
plt.title('オリジナルデータ(部分)')
plt.colorbar()

plt.tight_layout()
plt.show()

# 低ランク近似の比較
plt.figure(figsize=(15, 4))
ranks_demo = [1, 3, 10]
for i, r in enumerate(ranks_demo):
    X_approx = U[:, :r] @ np.diag(s[:r]) @ Vt[:r, :]
    plt.subplot(1, len(ranks_demo), i+1)
    plt.imshow(X_approx[:20, :20], cmap='coolwarm', aspect='auto')
    plt.title(f'ランク{r}での近似')
    plt.colorbar()

plt.tight_layout()
plt.show()

# QR分解
np.random.seed(42)
n_rows, n_cols = 5, 3
A = np.random.randn(n_rows, n_cols)

# QR分解の実行
Q, R = np.linalg.qr(A)

# 結果の表示
print("元の行列 A:")
print(A)
print("\nQ行列(直交行列):")
print(Q)
print("\nR行列(上三角行列):")
print(R)

# 検証
print("\n検証結果:")
print("Q^T Q(単位行列に近いことを確認):")
print(np.round(Q.T @ Q, decimals=10))
print("\nQR(元の行列Aに近いことを確認):")
reconstruction_error = np.linalg.norm(A - Q @ R)
print(f"再構成誤差: {reconstruction_error:.10f}")

# QR分解の応用:最小二乗法による線形回帰
print("\n\nQR分解の応用:最小二乗法による線形回帰")

# サンプルデータの生成
n_samples = 100
X_data = np.random.uniform(0, 10, n_samples)
y_data = 2 * X_data + 1 + np.random.normal(0, 1, n_samples)

# 設計行列の作成(各列が回帰式の説明変数と定数項に対応する行列)
A_reg = np.vstack([X_data, np.ones(n_samples)]).T

# QR分解を使用した最小二乗法
Q_reg, R_reg = np.linalg.qr(A_reg)
beta = np.linalg.solve(R_reg, Q_reg.T @ y_data)

print(f"\n推定された傾き: {beta[0]:.4f}")
print(f"推定された切片: {beta[1]:.4f}")

# 結果の可視化
plt.figure(figsize=(8, 6))
plt.scatter(X_data, y_data, alpha=0.5, label='データ点')
X_sort = np.sort(X_data)
plt.plot(X_sort, beta[0] * X_sort + beta[1], 'r-', linewidth=2, label='QR分解による回帰直線')
plt.plot(X_sort, 2 * X_sort + 1, 'g--', linewidth=2, label='真の関係')
plt.xlabel('X')
plt.ylabel('y')
plt.title('QR分解を用いた線形回帰')
plt.legend()
plt.grid(True)
plt.show()

特異値分解(SVD)は行列を3つの行列(U、Σ、VT)の積に分解する手法である。特異値で各成分の重要度を評価する。特異値の二乗は各成分が説明する分散に比例するため、累積寄与率は特異値の二乗の比として算出する(PCAの固有値に対応する)。低ランク近似で小さい特異値を無視してデータを圧縮する。近似誤差はフロベニウスノルム(行列の全要素の二乗和の平方根)で測定する。QR分解は行列を直交行列Qと上三角行列Rの積に分解する手法である。直交行列QはQTQ = Iを満たす。QTQが単位行列に近いこと、QRの積が元の行列を再構成できることを検証する。応用として最小二乗法による線形回帰を実装し、QR分解で回帰係数を求める。QR分解を用いることで、正規方程式を直接解く方法より数値的に安定した計算ができる(正規方程式はATAの条件数が大きい場合に数値誤差が拡大しやすいため、QR分解による方法が用いられる)。

手順:上記コードを実行し、SVDでは特異値・累積寄与率・低ランク近似の比較画像を、QR分解ではQTQが単位行列に近いことと推定された傾き・切片を確認する。

ヒント:SVDのデータはランク3で、QR分解の回帰データは傾き2・切片1で生成している。

考察ポイント:SVDでは何個の特異値で元のデータ構造を保持できるかを近似誤差のグラフから読み取り、QR分解では推定された傾きと切片が真の値(傾き2、切片1)にどれだけ近いかを読み取る。