はじめに
機械学習モデルを構築する際、適切なデータの前処理と評価は非常に重要です。今回は二値分類問題の代表的な手法であるロジスティック回帰モデルを、乳がんの診断データセットで実装する例を解説します。
ロジスティック回帰は、説明変数と目的変数の関係を表す数理モデルです。線形回帰と同様に、説明変数と目的変数の線形の結合を計算しますが、出力値はシグモイド関数を通して0から1の値に変換されます。つまり、出力は目的変数が特定のクラスに属する確率として解釈できます。
この確率的解釈により、ロジスティック回帰は二値分類や多クラス分類の問題に適用できます。一方で、説明変数と目的変数の関係が線形であると仮定しているため、高い非線形性がある場合は適切な性能が得られない可能性があります。
そこで、今回はロジスティック回帰をベースとしながら、機械学習モデル構築のプロセス全体を追っていきます。データの前処理、モデル学習、予測と評価の一連の流れを確認し、モデルの性能向上につなげるポイントを理解することが目的です。
実装手順
1. ライブラリのインポート
機械学習モデルを構築するには、様々なライブラリやモジュールが必要になります。ここでは以下のライブラリをインポートしています。
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn import datasets
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
Pythonコード解説
matplotlib.colors
とmatplotlib.pyplot
はデータの可視化に使用します。ListedColormap
はカスタムの色マップを作成するためのクラスです。numpy
は数値計算に使用する基本的なライブラリです。データを配列の形で扱うことができます。pandas
はデータ処理に使用します。DataFrameという2次元の表形式のデータ構造を提供しています。sklearn
はPythonで機械学習モデルを構築するためのライブラリです。ロジスティック回帰をはじめ、様々なアルゴリズムが実装されています。datasets
モジュールからサンプルデータをロードできますmetrics
モジュールから評価指標を計算するための関数が提供されていますmodel_selection
モジュールから訓練データとテストデータへの分割ができますlinear_model
モジュールからロジスティック回帰モデルをインポートしていますpreprocessing
モジュールから特徴量のスケーリングなどの前処理手法が提供されています
2. データセットのインポート
ここではsklearn.datasets
から乳がんデータセットをロードしています。
dataset = datasets.load_breast_cancer()
print('columns:', dataset.feature_names)
print('ラベルの種類:', np.unique(dataset.target))
Pythonコード解説
datasets.load_breast_cancer()
は乳がんの診断データをまとめたデータセットを返します。このデータセットは説明変数が30個、目的変数が2値(0: 悪性、1: 良性)からなる二値分類問題です。
.data
には30個の説明変数の値が格納されています。.target
には目的変数の正解ラベル(0 or 1)が格納されています。.feature_names
には説明変数の名前が格納されています。.target_names
には目的変数の名前が格納されています。
出力
columns: ['mean radius' 'mean texture' 'mean perimeter' 'mean area'
'mean smoothness' 'mean compactness' 'mean concavity'
'mean concave points' 'mean symmetry' 'mean fractal dimension'
'radius error' 'texture error' 'perimeter error' 'area error'
'smoothness error' 'compactness error' 'concavity error'
'concave points error' 'symmetry error' 'fractal dimension error'
'worst radius' 'worst texture' 'worst perimeter' 'worst area'
'worst smoothness' 'worst compactness' 'worst concavity'
'worst concave points' 'worst symmetry' 'worst fractal dimension']
ラベルの種類: [0 1]
次に、訓練データとモデルを構築する前に、このデータセットについてさらに理解を深める必要があります。そのための前処理の手順について3.以降で説明していきます。
3. データセットの確認
データセットの中身を確認し、理解を深めることは、モデル構築の前提となる重要な作業です。ここではpandasのDataFrameを使ってデータを視覚的に確認できる形に変換しています。
pd.set_option('display.max_columns', None)
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
df['target'] = dataset.target
df.head()
Pythonコード解説
pd.set_option('display.max_columns', None)
- pandasの表示オプションを変更し、出力時にすべての列を表示するようにしています。
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
- DataFrameを作成し、説明変数(
dataset.data
)を割り当てています。列ラベルにはdataset.feature_names
を使用しています。
- DataFrameを作成し、説明変数(
df['target'] = dataset.target
- 目的変数(
dataset.target
)を新しい列として追加しています。
- 目的変数(
df.head()
- 作成したDataFrameの先頭5行を表示することで、データの中身を確認できます。
出力:

このようにDataFrameに変換することで、データをよりわかりやすい形で扱えるようになります。目視によるデータ確認は、モデル構築の前提条件を満たしているか、データに不備がないかを検証する上で重要です。
4. 相関関係の確認
説明変数と目的変数の間の相関関係を確認することで、モデルの性能向上が見込めます。相関が高い説明変数を選択することにより、過学習を防ぎ、一般化性能を高めることができます。
corr_matrix = df.corr()
sorted_columns = corr_matrix.abs().sort_values('target', ascending=False).index
sorted_corr_matrix = corr_matrix.loc[sorted_columns, sorted_columns]
print(sorted_corr_matrix)
Pythonコード解説
corr_matrix = df.corr()
- DataFrameの
.corr()
メソッドで相関行列を計算しています。
- DataFrameの
sorted_columns = corr_matrix.abs().sort_values('target', ascending=False).index
- 目的変数
'target'
との絶対値の相関が高い順に説明変数を並び替えています。
- 目的変数
sorted_corr_matrix = corr_matrix.loc[sorted_columns, sorted_columns]
- 並び替えた列順で相関行列を作り直しています。
print(sorted_corr_matrix)
- 並び替えた相関行列を出力しています。
出力:

これにより、目的変数との相関が高い説明変数がわかります。後の手順で、この情報を使って重要な説明変数を選択することができます。
相関の高い変数を選択することで、モデルの性能が向上する可能性があります。また、相関の低い変数を除外することで、モデルの単純化やデータの次元削減にもつながります。相関分析は重要な前処理ステップの一つと言えます。
5. 説明変数と目的変数に分割
前の手順で確認した相関関係に基づき、重要な説明変数を選択します。そして、説明変数(X)と目的変数(y)に分離させます。
select_features = ['worst perimeter', 'worst concave points']
X = df.loc[:, select_features].values
y = df.loc[:, 'target'].values
Pythonコード解説
select_features = ['worst perimeter', 'worst concave points']
- 4.の相関分析で相関が高かった2つの説明変数を選択しています。
X = df.loc[:, select_features].values
- DataFrameの特定の列(選択した説明変数)を抽出し、NumPyの配列に変換しています。
y = df.loc[:, 'target'].values
- 同様に目的変数の値を配列に変換しています。
説明変数Xと目的変数yに分離することで、後の学習ステップでそれぞれを入力値と正解値として扱えるようになります。
相関が低い変数は除外しましたが、さらなる変数選択の手法としては、変数間の多重共線性をチェックしたり、L1正則化による自動的な変数選択を行ったりすることもできます。
6. 訓練データとテストデータに分割
次に、説明変数Xと目的変数yのデータを訓練データとテストデータに分割します。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
Pythonコード解説
sklearn.model_selection.train_test_split
関数を使って分割しています。
test_size=0.3
で、全体の30%をテストデータとして割り当てています。random_state=0
で乱数シードを固定しています。再現性を確保するために設定します。stratify=y
を指定することで、目的変数yの値の割合が訓練/テストデータで維持されます。
訓練データは実際にモデルを学習させるためのデータセット、テストデータはモデルの汎化性能を評価するためのデータセットとなります。
テストデータは学習には使わず、最後の評価のみに使うことが重要です。そうしないと、テストデータの情報も含んだモデルになってしまい、過学習を起こす可能性があります。
分割する際には、目的変数の値の割合が偏らないよう気をつける必要があります。上記のstratifyパラメータはその観点から重要です。
通常、テストデータの割合は20~30%程度が一般的です。訓練データが少なすぎるとモデルが適切に学習できなくなる可能性があります。
7. 特徴量のスケーリング
説明変数の値が異なる単位やスケールになっている場合、モデルの性能が低下する可能性があります。そこで、標準化(標準偏差1、平均0に変換)などのスケーリング手法を用いて、説明変数の値を似たような範囲に揃えます。
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)
Pythonコード解説
sc = StandardScaler()
- scikit-learnの
StandardScaler
クラスのインスタンスを作成します。
- scikit-learnの
sc.fit(X_train)
- 訓練データ
X_train
から、平均と標準偏差を計算します。
- 訓練データ
X_train_std = sc.transform(X_train)
- 計算した平均と標準偏差を使って、訓練データを標準化します。
X_test_std = sc.transform(X_test)
- 同じ平均と標準偏差を使って、テストデータも標準化します。
訓練データとテストデータを別々に標準化してはいけません。そうすると、異なる基準でスケーリングされた値を比較することになり、モデルの性能が低下してしまいます。
スケーリングによって値が似た範囲に納まることで、コスト関数の等高線がよりスムーズになり、勾配降下法の収束が速くなります。これにより、モデルの収束性と安定性が向上します。
一方で、標準化以外のスケーリング手法としては、最小最大スケーリング、ロバストスケーリングなどがあります。データの特性に応じて、適切な手法を選ぶ必要があります。
8. 訓練データによるモデルの学習
スケーリング済みの訓練データを使って、ロジスティック回帰モデルを学習させます。
classifier = LogisticRegression(C=100, random_state=1, solver='lbfgs', multi_class='ovr')
classifier.fit(X_train_std, y_train)
Pythonコード解説
classifier = LogisticRegression(C=60, random_state=1, solver='lbfgs', multi_class='ovr')
LogisticRegression
クラスのインスタンスを作成します。C
はL2正則化の強さを調節するパラメータです。大きい値を設定すると正則化は弱くなります。random_state
は乱数シードを設定します。solver
は最適化手法を指定します。'lbfgs'
は小規模な問題に適しています。multi_class
は多クラス分類の手法を指定します。'ovr'
はOne-vs-Restと呼ばれる手法です。
classifier.fit(X_train_std, y_train)
- 訓練データ
X_train_std
と目的変数y_train
を使ってモデルを学習させます。
- 訓練データ
ロジスティック回帰はコスト関数の最小化によって、重み係数(パラメータ)が学習されます。具体的には最尤推定法を用いて、訓練データから最適な係数を求めています。
上記のハイパーパラメータの設定は問題に応じて調整する必要があります。特にCの値は大きすぎると過学習、小さすぎると過小適合を起こす可能性があるため、注意が必要です。
ハイパーパラメータのチューニングには、グリッドサーチやランダムサーチなどの最適化手法を使うことが一般的です。
モデルの学習はfit
メソッドで行われていますが、ロジスティック回帰のような単純なモデルであれば、学習自体は高速に行えます。
9. 新しいデータセットで予測
学習済みのモデルを使って、新しいデータポイントに対する予測を行うことができます。
new_data = [[-1, 1]]
print(f'予測したクラス: {classifier.predict(sc.transform(new_data))[0]}')
Pythonコード解説
new_data = [[-1, 1]]
- 新しい説明変数の値を配列で用意しています。この例では2つの説明変数があるので、サイズ2の配列になっています。
classifier.predict(sc.transform(new_data))
predict
メソッドで予測を行います。引数には新しい説明変数の値を入力する必要があります。sc.transform(new_data)
で、訓練データと同じ基準でスケーリングを行っています。
[0]
predict
メソッドは配列を返すので、インデックス[0]
を指定して1つの値を取り出しています。
出力:
予測したクラス:0 # 0:悪性
出力される予測値は、クラスラベル(この例では0か1)になります。予測値をさらに確率値で得たい場合は、predict_proba
メソッドを使用します。
新規データでの予測は、学習済みモデルの汎化性能を確かめる上で重要です。単に訓練データでの予測精度が高くても、未知のデータに対する予測精度が低ければ実用的ではありません。そのため、次の手順で未知のテストデータに対する予測精度を評価します。
10. テストデータで予測
学習済みのモデルをテストデータで評価します。テストデータはあらかじめ分離しておいた未知のデータセットです。
y_pred = classifier.predict(X_test_std)
Pythonコード解説
classifier.predict(X_test_std)
predict
メソッドにテストデータX_test_std
を入力して予測を行います。
y_pred = ...
- 予測結果を
y_pred
という変数に格納しています。
- 予測結果を
テストデータに対する予測値y_pred
が得られたので、次の手順で実際の正解値y_test
との比較を行い、モデルの性能を評価します。
予測値と実際の正解を比較するために、テストデータは学習には使わず最後の評価のみに利用するのが一般的です。学習に使ってしまうと、過学習が起こり本来の汎化性能が測れなくなってしまうためです。
そのため、学習の際はテストデータとは分離した「訓練データ」のみを使い、学習済みモデルの最終評価にのみ「テストデータ」を使うというのが通例になっています。
11. モデルの性能評価
テストデータに対する予測値と実際の正解値を比較することで、モデルの性能を評価します。
print(f'正分類のデータ点: {(y_test == y_pred).sum()}個/{len(y_test)}個' )
print(f'Accuracy(Test): {accuracy_score(y_test, y_pred):.3f}')
Pythonコード解説
(y_test == y_pred).sum()
- 予測値
y_pred
と正解値y_test
を比較し、一致した数をカウントしています。 ==
は要素ごとの比較を行い、TrueまたはFalseの配列を返します。sum()
でTrueの個数、つまり正解の個数を計算しています。
- 予測値
len(y_test)
- テストデータ全体の数を取得しています。
accuracy_score(y_test, y_pred)
- scikit-learnの
accuracy_score
関数で、正解率(Accuracy)を計算しています。
- scikit-learnの
出力:
正分類のデータ点: 155個/171個
Accuracy(Test): 0.906
出力される値から、テストデータ全体の何個が正しく分類できたか、また正解率がどの程度であるかがわかります。
正解率は二値分類や多クラス分類など、様々な分類問題で広く使われる評価指標です。また、その他にも適合率(Precision)、再現率(Recall)、F1スコアなど、問題に応じて様々な評価指標があります。
高い正解率を出せたとしても、過学習の可能性があります。そこで、次の手順で決定境界をプロットし、モデルの汎化性能を視覚的に確認します。
12. 性能評価の可視化
訓練データとテストデータに対する決定境界を可視化することで、モデルの挙動を詳しく確認できます。
※可視化部分のPythonコードは必須でないため理解できなくても良い。
def calculate_bounds(X1, X2):
X1_min, X1_max = X1.min() - (X1.max()-X1.min())/20, X1.max() + (X1.max()-X1.min())/20
X2_min, X2_max = X2.min() - (X2.max()-X2.min())/20, X2.max() + (X2.max()-X2.min())/20
return X1_min, X1_max, X2_min, X2_max
Pythonコード解説
この関数は、説明変数X1とX2の値の範囲を計算し、少し余裕を持たせた最小値と最大値を返します。具体的には以下の処理を行っています。
X1.min()
とX1.max()
で、X1の最小値と最大値を取得します。(X1.max()-X1.min())/20
で、X1の値の範囲の1/20を計算します。X1.min() - (X1.max()-X1.min())/20
とX1.max() + (X1.max()-X1.min())/20
で、最小値と最大値からそれぞれ範囲の1/20を引いた値と足した値を計算します。これにより、元の範囲よりも少し広い範囲が得られます。- 同様の処理をX2についても行い、X2の最小値と最大値を計算しています。
- 計算した最小値と最大値を返します。
この余裕を持たせることで、グラフの端までデータ点や決定境界を描画できるようになります。端までプロットされないと、一部の領域が切り捨てられてしまう可能性があります。
def plot_data(ax, X_set, y_set, X1, X2, Z, colors, kind, classifier):
cmap = ListedColormap(colors[:len(np.unique(y_set))])
ax.contourf(X1, X2, Z, alpha=0.3, cmap=cmap)
for idx, feature in enumerate(np.unique(y_set)):
ax.scatter(x=X_set[y_set == feature, 0],
y=X_set[y_set == feature, 1],
alpha=0.5,
color=colors[idx],
marker='o',
label=dataset.target_names[feature],
edgecolor='black')
ax.set_xlabel(select_features[0])
ax.set_ylabel(select_features[1])
ax.set_title(f'{type(classifier).__name__} ({kind})')
ax.legend(loc='best')
Pythonコード解説
この関数は、実際のプロットを行う部分です。引数として以下が渡されます。
ax
: matplotlib.pylotのAxesオブジェクトX_set
: 説明変数の値y_set
: 目的変数の値X1
,X2
: グリッドポイントの値Z
: グリッドポイントでの予測値colors
: プロットの色kind
: ‘Training set’または’Test set’の文字列classifier
: 使用した分類器オブジェクト
具体的な処理の内容は以下の通りです。
cmap = ListedColormap(...)
- 目的変数の値に対応した色を設定するためのカラーマップを作成
ax.contourf(X1, X2, Z, alpha=0.3, cmap=cmap)
- グリッドポイント上の予測値
Z
を、等高線で描画。alphaで透明度を設定。
- グリッドポイント上の予測値
for
ループ- 目的変数の値ごとにデータ点をプロット
ax.scatter
でデータ点を散布図で描画- 色、マーカー、ラベル、エッジ色を設定
ax.set_xlabel
,ax.set_ylabel
- 使用した説明変数の名前を軸ラベルとして設定
ax.set_title
- グラフのタイトルを設定(分類器の名前と’Training/Test set’)
ax.legend
- 凡例を適切な位置に自動的に配置
このようにしてグリッド上の等高線と、実際のデータ点をプロットしています。
def plot_decision_regions(X_train_std, X_test_std, y_train, y_test, classifier):
colors = ('red', 'blue')
X_train_set, y_train_set = sc.inverse_transform(X_train_std), y_train
X_test_set, y_test_set = sc.inverse_transform(X_test_std), y_test
X_combined = np.vstack((X_train_set, X_test_set))
X1_min, X1_max, X2_min, X2_max = calculate_bounds(X_combined[:, 0], X_combined[:, 1])
X1, X2 = np.meshgrid(np.arange(X1_min, X1_max, step=(X1_max - X1_min) / 1000),
np.arange(X2_min, X2_max, step=(X2_max - X2_min) / 1000))
Z = classifier.predict(sc.transform(np.array([X1.ravel(), X2.ravel()]).T))
Z = Z.reshape(X1.shape)
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
plot_data(ax[0], X_train_set, y_train_set, X1, X2, Z, colors, "Training set", classifier)
plot_data(ax[1], X_test_set, y_test_set, X1, X2, Z, colors, "Test set", classifier)
plt.tight_layout()
plt.show()
plot_decision_regions(X_train_std, X_test_std, y_train, y_test, classifier)
Pythonコード解説
この関数が実際に決定境界のプロットを行う部分です。具体的な処理は以下の通りです。
- 訓練データとテストデータを元の値に戻す (
sc.inverse_transform
) - 訓練データとテストデータを結合し(
np.vstack
)、calculate_bounds
関数で描画範囲を計算 np.meshgrid
とnp.arange
を使って、計算した範囲内でグリッドポイントを生成- グリッドポイントごとで分類器の
predict
メソッドを呼び出して予測を行い、その結果をZ
に格納 subplots
で2つのグラフ領域を確保plot_data
関数を呼び出して、訓練データとテストデータをそれぞれプロットtight_layout
でグラフ間の間隔を調整plt.show
でグラフを表示- グリッドポイントごとに予測を行い、その結果を等高線で可視化しています
グリッドポイントごとに予測を行い、その結果を等高線で可視化しています
出力:

可視化することで、以下のことが確認できます。
- 訓練データに対する決定境界の適切さ
- テストデータに対する汎化性能
- 境界付近でのモデルの挙動
- 分類が難しいデータ点の有無
単に正解率が高いだけでなく、実際にどのようなデータ点で誤分類が起きているのかを確認できます。場合によっては、データの特性に合わないモデルを使っている可能性があります。
また、この可視化では2つの説明変数しか使っていませんが、高次元の場合はさらに詳細な可視化が必要になります。代表的な手法として、t-SNEなどの次元削減手法と組み合わせて、高次元データを2次元や3次元にマッピングして可視化することがあります。
決定境界を可視化することで、モデルの解釈性が高まり、より適切なモデル選択やデータ前処理を行えるようになります。機械学習の性能向上には重要な工程と言えます。
まとめ
この記事では、scikit-learnを使ったロジスティック回帰モデルの実装例とその解説を行いました。
データの前処理を適切に行うことで、過学習や過小適合を防ぎ、高い汎化性能を持つモデルを構築できます。一方で、単に正解率が高いだけでなく、可視化によるモデルの挙動確認も重要です。機械学習ではモデルの解釈性を高めることが、さらなる性能向上につながります。
ロジスティック回帰はシンプルでありながら強力な手法です。この手法を理解することは、より高度な機械学習アルゴリズムを学ぶ上での足がかりとなるでしょう。ロジスティック回帰をマスターした上で、ニューラルネットワークなど他の手法にもチャレンジしてみることをおすすめします。
本コンテンツへの意見や質問