Act 17. Pythonでロジスティック回帰を試す
はじめに
Act 01. AIで外国為替を自動売買するまでの道のりをベースに学習を進めて行く。
前回はロジスティック回帰の概要について学習した。
なので今回は実際にpythonでロジスティック回帰分析をしてみようと思う。
データセットが必要になるため、pythonで提供されているアヤメという花?のデータセットを使用する。
ロジスティック回帰分析
結論
まずは結論から。とにかくコードが見たいんじゃ!という人はこれを見て。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 予測
y_pred = model.predict(X_test)
# 精度の評価
# 正解率(Accuracy)
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.2f}')
たったこれだけでモデルを作って予測するところまで出来てしまう…。便利だ。
以降でそれぞれ何をしているのか、しっかりと書いていこうと思う。
データセット
まずは今回使うデータセットのインポートを行う。
このデータセットは、いくつかの花の特徴が説明変数として存在し、目標変数は0: Setosa
・1: Versicolor
・2: Virginica
の3種類となっている。
from sklearn.datasets import load_iris
iris = load_iris()
少し脱線
目標変数って2つじゃないの…?
前回学習したときは、クラス1とクラス2が何たらで、シグモイド関数を通して得られた確率が0.5以上だとクラス1(肯定の分類)0.5未満だとクラス2(否定の分類)みたいな説明だったじゃん!
実際はそんなに甘くないらしい。ChatGPTさんに裏切られた気分だ…。
ロジスティック回帰モデルはもともと2クラス分類(二項分類)用に設計されているため、3クラス以上に分類する場合は特別な方法を使うらしい。
その内の一つが一対多(One-vs-Rest)方式というもの。
pythonのsklearn
だと3クラス以上の場合、自動的に「一対多(One-vs-Rest)」方式が採用されるらしいのでこれだけ説明しておく。
一対多というくらいなので以下のように分析していく。
- クラス 0(Setosa) vs 他のクラス
- クラス 1(Versicolor) vs 他のクラス
- クラス 2(Virginica) vs 他のクラス
そしてそれぞれの組み合わせで分析を行うと以下のような結果になる(例)。
- クラス 0(Setosa) vs 他のクラス: クラス 0 である確率 = 0.3
- クラス 1(Versicolor) vs 他のクラス: クラス 1 である確率 = 0.6
- クラス 2(Virginica) vs 他のクラス: クラス 2 である確率 = 0.4
この時、60%で一番可能性の高いクラス1が選ばれるようになっている。
では本題に戻る。
データセットの確認
データセットの情報について確認しておく。
from sklearn.datasets import load_iris
# データの読み込み
iris = load_iris()
print(iris.keys()) # データセットのキーを出力
実行結果は以下の通り。
dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])
target_names
が目標変数名、feature_names
が設定変数名っぽいので出力してみる。
from sklearn.datasets import load_iris
# データの読み込み
iris = load_iris()
print(iris.target_names) # 目標変数の名前
print(iris.feature_names) # 設定変数の名前
実行結果は以下の通り。
['setosa' 'versicolor' 'virginica']
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
まずはtarget_names
から。
-
setosa
: セトサという種類 -
versicolor
: バージカラーという種類 -
virginica
: バージニカという種類
書いてて思ったけどこっちの情報はどうでもよかったか…。
次にfeature_names
について。
-
sepal length (cm)
: がく片の長さ -
sepal width (cm)
: がく片の幅 -
petal length (cm)
: 花弁の長さ -
petal width (cm)
: 花弁の幅
よく分からんが、花の部位の幅と長さがデータとしてあるっぽい。
次にデータ数を見てみる。
from sklearn.datasets import load_iris
# データの読み込み
iris = load_iris()
X = iris.data # 説明変数
y = iris.target # 目的変数
print(len(X))
実行結果は以下の通り。
どうやら150個のデータが存在するらしい。少ないね…。
150
モデル
モデルの構築
モデルの構築は簡単。
ほぼ線形回帰と同じで、ロジスティック回帰の場合はLogisticRegression
クラスを使用する。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
違うところとしてmax_iter=200
と引数を指定している。
これは最大反復回数(イテレーション数) を指定しているらしい。
前回学習したが、ロジスティック回帰は最終的に回帰分析を行う。
最適なパラメータを見つけるために「最適化アルゴリズム」(通常は勾配降下法など)を用いているらしいのだが、この時に最大で何回の反復が可能か指定している。
200より少ない数で最適なパラメータを見つけた場合はそこで終了する。
必要以上に多くのイテレーションを行うと無駄な計算が増え、少なすぎると収束せず最適解に到達できない。
勾配降下法についてはAct. 18 勾配降下法の復習で詳しく説明しているので、わからない人はそっちを参照して欲しい。
ここまででモデルが完成した。
あっという間だねー!
予測と精度の評価
ここで最初の結論に記載したコードになる。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 予測
y_pred = model.predict(X_test)
# 精度の評価
# 正解率(Accuracy)
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.2f}')
実行結果は以下の通り。
Accuracy: 1.00
これは正解率を示していて、1.00ということは正解率が100%ということ。驚異的な結果だ!
花の分析は簡単なのかな?
モデルの評価
線形回帰の時にはMSE(平均二乗誤差)
やR²(決定係数)
でモデルの評価を行った。
MSEは誤差を二乗していき最終的な平均値を求める。MSEが小さいと評価の高いモデルになる。
R²はモデルがどれだけ説明出来ているか、つまりどれだけ予測出来ているかを評価する指標。
基本的に0~1の間に収まり、1に近いほど良いモデルということになる。
公式は以下の通りだった。
うんうん。何となく覚えてる。
ということで今回はロジスティック回帰の評価指標についていくつか紹介しようと思う。
正解率(Accuracy)
正解率(Accuracy)はモデルが正しく予測できたサンプルの割合を指す。
今回の場合は、Accuracy: 1.00
なので、正解率は100%となっている。
正解率(Accuracy)は、分類モデルが全体のデータに対してどれだけ正しく予測できたかを示す指標です。具体的な例と数字を用いて説明します。
1. 正解率(Accuracy)の定義
正解率は、次の式で計算される
- TP(True Positive): 実際が正例で、モデルも正例と予測した数
- TN(True Negative): 実際が負例で、モデルも負例と予測した数
- FP(False Positive): 実際は負例だが、モデルが正例と予測した数
- FN(False Negative): 実際は正例だが、モデルが負例と予測した数
なんか難しく感じるかもしれないが、結局以下のようなイメージだと思う。
2. 具体例
例えば、ある二値分類問題で以下のような予測結果が得られたとする。
- 実際に「正例」であるデータ数:100
- 実際に「負例」であるデータ数:100
正例とは前回の記事の例でいうと平均寿命より長生きした人(クラス1)で、負例とは平均寿命より長生きできなかった人(クラス2)になる。
モデルの予測結果が以下の通りになったとする。
- TP(正例を正例と予測した数):80
- TN(負例を負例と予測した数):90
- FP(負例を正例と予測した数):10
- FN(正例を負例と予測した数):20
3. 正解率の計算
このケースにおける正解率は次のように計算される。
正解率は 0.85(85%)となり、この場合、全体のデータに対して85%を正しく分類できていることになる。
4. 正解率(Accuracy)を使う際の注意点
正解率はわかりやすい指標だが、クラス不均衡がある場合には注意が必要。
例えば、データの95%が正例、5%が負例という場合、正例ばかりを予測しても95%の正解率になるが、これは実際の性能を反映していないことになる。
このため、正解率と合わせて、精度(Precision)、再現率(Recall)、F1スコアなどの指標も確認するのが望ましいらしい。
なるほど。
5. コード
コードに関しては何度も記載しているもの。
念のため再度載せておく。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 予測
y_pred = model.predict(X_test)
# 精度の評価
# 正解率(Accuracy)
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.2f}')
混同行列(Confusion Matrix)
混同行列(Confusion Matrix)は、モデルがどのように予測を行ったかを、クラスごとに確認することが出来る表になる。
実際の値と予測値に基づいて、正解・誤答の分布を視覚的に把握することが出来る。
1. 混同行列の構成
混同行列は以下のように構成される。
予測: Positive | 予測: Negative | |
---|---|---|
実際: Positive | TP | FN |
実際: Negative | FP | TN |
- TP(True Positive): 実際が正例で、モデルも正例と予測
- TN(True Negative): 実際が負例で、モデルも負例と予測
- FP(False Positive): 実際は負例だが、モデルが正例と予測
- FN(False Negative): 実際は正例だが、モデルが負例と予測
2. 具体例での混同行列
たとえば、二値分類問題で以下のような予測結果が得られたとする。
- 実際に「正例」であるデータ数:100
- 実際に「負例」であるデータ数:100
モデルの予測結果は以下の通り。
- TP(正例を正例と予測した数):80
- TN(負例を負例と予測した数):90
- FP(負例を正例と予測した数):10
- FN(正例を負例と予測した数):20
この予測結果を混同行列として整理すると、以下のようになる。
予測: Positive | 予測: Negative | |
---|---|---|
実際: Positive | 80 | 20 |
実際: Negative | 10 | 90 |
この表を見れば、どのような誤分類が多いか(例えば、False Negative が多いなど)を直感的に把握することが可能となる。
3. コード
コードにすると以下のようになる。
confusion_matrix
を使用する。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
import pandas as pd
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 予測
y_pred = model.predict(X_test)
# 混同行列(Confusion Matrix)
cm = confusion_matrix(y_test, y_pred)
df = pd.DataFrame(cm, index=iris.target_names, columns=iris.target_names)
print(df)
出力は以下の通りとなる。
setosa versicolor virginica
setosa 19 0 0
versicolor 0 13 0
virginica 0 0 13
4. 出力内容の解説
confusion_matrix
関数を使った以下の結果について解説する。
setosa versicolor virginica
setosa 19 0 0
versicolor 0 13 0
virginica 0 0 13
表にすると以下の通り。
今回はクラスが3つ存在するため、TP/TN/FP/FNの表を作成する場合は3つの表が完成する。
setosa(予測した値) | versicolor(予測した値) | virginica(予測した値) | |
---|---|---|---|
setosa(実際の値) | 19 | 0 | 0 |
versicolor(実際の値) | 0 | 13 | 0 |
virginica(実際の値) | 0 | 0 | 13 |
今回は誤分類がないためあまり説明することもない…。
適合率・再現率・F1スコア
適合率(Precision)、再現率(Recall)、F1スコア(F1_score)のそれぞれについて、具体的な数値を用いて説明する。
なぜ3つ同時なのかは後で分かるので一旦気にしないで。
まず、次のような分類結果があるとする。
200人に対して、平均寿命より長生きするか否かを分類した表。
実際のクラス | 予測: 長生きする | 予測: 長生きしない |
---|---|---|
平均寿命より長生きする | TP = 80 | FN = 20 |
平均寿命より長生きしない | FP = 10 | TN = 90 |
- TP(True Positive): 実際に正例で、モデルも正例と予測した数(80)
- TN(True Negative): 実際に負例で、モデルも負例と予測した数(90)
- FP(False Positive): 実際は負例だが、モデルが正例と誤って予測した数(10)
- FN(False Negative): 実際は正例だが、モデルが負例と誤って予測した数(20)
この結果を使って、適合率、再現率、F1スコアを計算する。
1. 適合率(Precision)
適合率は、モデルが「正例」と予測したデータのうち、実際に正例であった割合を示す。
つまり、長生きすると予測したデータの内、実際に長生きしたデータの割合ということ。
具体的な計算
解釈
- この場合、適合率は 0.89(89%)で、モデルが正例と予測したもののうち、89%が実際に正例であったことを意味する。
- 適合率が高いと、モデルが正例と予測した場合、誤っている可能性が低いことが示される。
2. 再現率(Recall)
再現率は、実際に正例であるデータのうち、モデルが正しく「正例」と予測できた割合を示す。
つまり、実際に長生きした人に対して長生きしたと予測した割合。
具体的な計算
解釈
- 再現率は 0.8(80%)で、実際の正例データの80%をモデルが正しく「正例」として予測できたことを意味する。
- 再現率が高いと、正例を見逃すことが少なく、正例をしっかり検出できるモデルであることを示す。
3. F1スコア(F1 Score)
F1スコアは、適合率と再現率の調和平均で、バランスの取れた評価指標として使用される。計算式は以下の通り。
具体的な計算
解釈
- F1スコアは 0.84(84%)で、適合率と再現率のバランスを取った評価指標。
- 特にデータにクラスの不均衡がある場合、精度だけではモデルの性能を評価しきれないため、F1スコアを確認することで適合率と再現率のバランスを総合的に判断することが可能。
4. まとめ
指標 | 計算式 | 値 | 解釈 |
---|---|---|---|
適合率 | 0.89 | 正例と予測したものの89%が正解 | |
再現率 | 0.8 | 実際の正例の80%を正しく予測 | |
F1スコア | 0.84 | 適合率と再現率のバランスを取った指標 |
5. コード
正解率(Accuracy)と適合率、再現率、F1スコアの全てを一気に評価する関数がある。
classification_report
だ。
これを使ったコードは以下の通り。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
# データの読み込み
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.3, random_state=42)
# モデルの作成
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 予測
y_pred = model.predict(X_test)
# 精度・適合率・再現率・F1スコア
report = classification_report(y_test, y_pred, target_names=iris.target_names)
print('Classification Report:\n', report)
出力は以下の通りとなる。
今回は完璧に予測出来ているため、すべてが1.00つまり100%となっている…。
Classification Report:
precision recall f1-score support
setosa 1.00 1.00 1.00 19
versicolor 1.00 1.00 1.00 13
virginica 1.00 1.00 1.00 13
accuracy 1.00 45
macro avg 1.00 1.00 1.00 45
weighted avg 1.00 1.00 1.00 45
さいごに
ロジスティック回帰についてpythonで実装してみた。
線形回帰をしっかり学んだからロジスティック回帰も何となくわかりやすかった。
線形回帰とロジスティック回帰の両方で出てきたもので、勾配降下法というものがあった。
一度学習している内容だがおさらいとして次回の記事にしようかなと考えている。
もしかしたら記事にしないかもしれないけどね!
ではまた
Discussion