9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

多数決アンサンブル分類器sklearn.ensemble.Votingclassifier()の変数やメソッドまとめ

Posted at

#作業環境
Jupyter Notebook(6.1.4)を用いて作業を進めました。
各versionはpandas(1.1.3), scikit-learn(0.23.2)です。

#多数決アンサンブル分類器Votingclassifier()とは

いくつかの分類器を使って1つのメタ分類器を作る方法をアンサンブル法といいます。
学習方法や予測値の出し方に工夫を与えたブースティングやスタッキング、バギングなどが有名ですが、ここでは単純に、個々の分類器がそれぞれ全データに対して学習をして、その結果を多数決で決める単純なアンサンブル分類器を考えていきます。
下は単純多数決アンサンブルのイメージ図です。

img1.jpg

このような単純多数決はsklearn.ensembleライブラリの中のVotingClassifierクラスで実装することができます。下のリンクは公式ガイドです。

この公式ガイドを見ながら実際に実装を行ってみました。(2021.8.19現在の公式ガイドのsklearnのversionは0.24.2になっています)

#VotingClassifier()の各変数の意味

公式ガイドを見ながら解釈したVotingClassifier()の変数は以下の6つです。

変数名 default 意味・詳細
estimators 必須 各分類器を与える変数。[('est1', LogisticRegression()), …]のように分類器の名前と分類器のタプルをリストにして渡す。
voting hard hardの場合、各分類器の予測値の多数決をとって予測値を返す。softの場合、各分類器のクラス所属確率の平均から最も所属確率の高いクラスを予測値として返す。
weights None([1, 1, …, 1]) 予測値を与える際、各分類器のクラスラベルまたは所属確率に重みを与える。
n_jobs None(1) 分類器のfitを並行処理する数を指定する。-1で全てのプロセッサを指定できる。
flatten_transform True voting='soft'を指定しtransformメソッドを使う際に影響する。Trueを指定すると所属確率をデータのインデックスごとにリストにして返す。Falseを指定すると所属確率を各分類器ごとにリストにして返す。
verbose False Trueの場合には実行時間を表示する。

実際に実装しながら動作を確認してみたいと思います。

#データと分類器の準備
今回はscikit-learndatasetsの中のアヤメのデータを使って確認していきます。簡単のため2値問題にしたいので、2つのクラスのみ取り出してクラスラベルを数値化し、訓練データとテストデータに分割後、特徴量の標準化を行っておきます。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder

iris = datasets.load_iris()
X, y = iris.data[50:, [1,2]], iris.target[50:]
le = LabelEncoder()
y = le.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=1, stratify=y)
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)

また、アンサンブルする分類器を用意します。今回は以下の3つにしてみます。

  • ロジスティック回帰
  • ランダムフォレスト
  • 最近傍法
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier

est1 = LogisticRegression(penalty='l2', C=0.01, random_state=1)
est2 = RandomForestClassifier(n_estimators=10, random_state=1)
est3 = KNeighborsClassifier(n_neighbors=1)

#変数を変えながら動作を確認する

予測精度に影響する変数はestimators, voting, weightsの3つなので、まずはここを少し変えてテストデータでの正解率を求めてみます。使用したメソッドは次の2つです。

メソッド 意味
.fit(X, y) X, yを教師データとして学習を行う
.score(X, y) yを正解データとして、Xから予測される値の正解率を求める
from sklearn.ensemble import VotingClassifier
#1. クラスラベルから多数決で予測(voting='hard')
vc1 = VotingClassifier(estimators=[('lr', est1), ('rf', est2), ('knn', est3)], voting='hard')
#2. クラス所属確率から予測(voting='soft')
vc2 = VotingClassifier(estimators=[('lr', est1), ('rf', est2), ('knn', est3)], voting='soft', flatten_transform=True)
#3. クラス所属確率から予測(voting='soft'), 重みを設定(ロジスティック回帰を重視する)
vc3 = VotingClassifier(estimators=[('lr', est1), ('rf', est2), ('knn', est3)], voting='soft', flatten_transform=True, weights=[100, 1, 1])

#各分類器の正解率の計算
name_list = ['Logistic', 'RandomForest', 'KNN', 'voting=hard', 'voting=soft', 'voiting=soft, weights=[20, 1, 1]']
est_list = [est1, est2, est3, vc1, vc2, vc3]
for est, name in zip(est_list, name_list):
    est.fit(X_train_std, y_train)
    print('ACC. : %.2f [%s]' % (est.score(X_test_std, y_test), name))
#--->
#ACC. : 0.84 [Logistic]
#ACC. : 0.90 [RandomForest]
#ACC. : 0.86 [KNN]
#ACC. : 0.90 [voting=hard]
#ACC. : 0.88 [voting=soft]
#ACC. : 0.86 [voiting=soft, weights=[20, 1, 1]]

正解率について少し差があることがわかります。次の3つのメソッドを使用して予測値や各学習器の様子を確認してもう少し違いをみてみます。

メソッド 意味
.predict(X) 学習済みモデルについて、Xに対する予測値をNumpy配列で返す
.transform(X) 各分類器のXの各行に対するクラスラベルや所属確率をNumpy配列にして返す(flatten_transformによって配列の形が異なる)
.predict_proba(X) voting='soft'を指定している場合、Xの各行に対するクラス所属確率の全分類器の平均値をNumpy配列にして返す(voting='hard'のときは使用不可)
#X_trainのindex=8のデータについて各アンサンブル分類器の予測結果を出力する
print('vc1 predict : ', vc1.predict(X_train_std)[8])
print('vc2 predict : ', vc2.predict(X_train_std)[8])
print('vc3 predict : ', vc3.predict(X_train_std)[8])
#--->
#vc1 predict :  1
#vc2 predict :  1
#vc3 predict :  0

#各分類器の予測結果の確認
#transformの結果は
#[est1の予測結果, est2の予測結果, est3の予測結果]
#の順になっている
print(vc1.transform(X_train_std)[8])
#--->
#[0 1 1]

#transformの結果は
#[est1で0に所属する確率, est1で1に所属する確率, est2で0に所属する確率, est2で1に所属する確率, est3で0に所属する確率, est3で1に所属する確率]
#の順になっている
print(vc2.transform(X_train_std)[8])
print(vc2.predict_proba(X_train_std)[8])
#--->
#[0.52310858 0.47689142 0.2        0.8        0.         1.        ]
#[0.24103619 0.75896381]

print(vc3.transform(X_train_std)[8])
print(vc3.predict_proba(X_train_std)[8])
#--->
#[0.52310858 0.47689142 0.2        0.8        0.         1.        ]
#[0.51481234 0.48518766]

まず予測結果について、vc3のみがほか2つと違った予測をしていることがわかります。予測の過程を見てみると、特に所属確率について、ランダムフォレストや最近傍法は片方のクラスに偏った所属確率を持つことがわかります。voting='soft'の場合はこれらの所属確率の平均をとるので、predict_probaの結果をみてわかる通り、重みをつけなければ予測はランダムフォレストと最近傍法の結果にほとんど支配されてしまうことがわかります。
各分類器で返されるpredict_probaの値があまり確率論的に意味を持たない分類器を使用する場合にはvoting='hard'を指定する方がアンサンブルの効果が高いように思います。

transformメソッドの結果について、変数の中でflatten_transform=Falseとすることで出力の形を変えることができます。また、verbose=Trueとすることで各分類器の学習にかかった時間が表示されます。

vc2_2 = VotingClassifier(estimators=[('lr', est1), ('rf', est2), ('knn', est3)], voting='soft', flatten_transform=False, verbose=True)
vc2_2.fit(X_train_std, y_train)
print(vc2_2.transform(X_train_std)[0, 8])
print(vc2_2.transform(X_train_std)[1, 8])
print(vc2_2.transform(X_train_std)[2, 8])
#---> 
#[Voting] ....................... (1 of 3) Processing lr, total=   0.0s
#[Voting] ....................... (2 of 3) Processing rf, total=   0.0s
#[Voting] ...................... (3 of 3) Processing knn, total=   0.0s
#[0.52310858, 0.47689142]
#[0.2       , 0.8       ]
#[0.        , 1.        ]

また、各分類器のハイパーパラメータについて、次のメソッドを使うことでアンサンブル分類器から直接確認・変更することができます。

メソッド 意味
.get_params() 現在の各分類器のハイパーパラメータを返す
set_params(name__parameter=new_value) 指定した名前の分類器のハイパーパラメータを新しい値に変更する
vc1.get_params()
#--->
#{'estimators': [('lr', LogisticRegression(C=0.01, random_state=1)),
#  ('rf', RandomForestClassifier(n_estimators=10, random_state=1)),
#  ('knn', KNeighborsClassifier(n_neighbors=1))],
# 'flatten_transform': True,
# 'n_jobs': None,
# 'verbose': False,
# 'voting': 'hard',
# 'weights': None,
# 'lr': LogisticRegression(C=0.01, random_state=1),
# 'rf': RandomForestClassifier(n_estimators=10, random_state=1),
# 'knn': KNeighborsClassifier(n_neighbors=1),
# 'lr__C': 0.01,
# 'lr__class_weight': None,
# 'lr__dual': False,
# 'lr__fit_intercept': True,
# 'lr__intercept_scaling': 1,
# 'lr__l1_ratio': None,
# 'lr__max_iter': 100,
# 'lr__multi_class': 'auto',
# 'lr__n_jobs': None,
# 'lr__penalty': 'l2',
# 'lr__random_state': 1,
# 'lr__solver': 'lbfgs',
# 'lr__tol': 0.0001,
# 'lr__verbose': 0,
# 'lr__warm_start': False,
# 'rf__bootstrap': True,
# 'rf__ccp_alpha': 0.0,
# 'rf__class_weight': None,
# 'rf__criterion': 'gini',
# :
# 'knn__p': 2,
# 'knn__weights': 'uniform'}

vc1.set_params(lr__C=0.0001)
#--->
#VotingClassifier(estimators=[('lr',
#                              LogisticRegression(C=0.0001, random_state=1)),
#                             ('rf',
#                              RandomForestClassifier(n_estimators=10,
#                                                     random_state=1)),
#                             ('knn', KNeighborsClassifier(n_neighbors=1))])
9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?