異種混合学習を活用するsklearn-fabの使用方法¶

本ノートブックでは、異種混合学習の性質と特長を活かすためのsklearn-fabの使用方法を紹介します。

異種混合学習では、作成されるモデルが内部生成のランダム値に依存するため、
モデル作成を複数回実行し、最良なモデルを選択することがよくあります。
また、異種混合学習の大きな特長の一つとして、モデルの解釈性の高さが挙げられます。
そのため、最良なモデルの選択には、評価指標以外にモデルの門木や予測式の内容なども重要となります。

それらの性質と特長を踏まえた使用方法として、以下の3点について説明します。

  • 複数のパラメーターでランダムリスタートし、複数モデルの作成を実施する
    2. 複数モデルの作成をご覧ください

  • 解釈性を考慮した複数モデルの評価・比較を実施する
    3. 複数モデルの評価をご覧ください

  • 可視化した門木を、より使いやすく加工する
    4. 可視化した門木の加工をご覧ください

1. データ準備¶

まず、学習およびテストに使用するデータを確認します。
本ノートブックでは、ワインの品種分類を示すデータを使用します。
データの詳細については、以下をご覧ください(本ハンズオンでは、説明変数が標準化済みのデータを使用します)。
Wine recognition dataset

In [ ]:
import pandas as pd

data = pd.read_csv('./data/train_data.csv')
data.head()

上記のデータについて、

  • カテゴリ変数が存在しない
  • 説明変数の標準化が未実施

という理由により、データ標準化のクラスStandardScalerを使用し、以下で標準化を実施します。

In [ ]:
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X = pd.DataFrame(scaler.fit_transform(data.iloc[:, 1:]), columns=data.iloc[:, 1:].columns.values)
y = data.iloc[:, 0]

2. 複数モデルの作成¶

この章では、複数のパラメーターでランダムリスタートし、複数モデルの作成を行います。
estimatorに与えるパラメーターとして、以下の組み合わせを試します。

In [ ]:
from sklearn.model_selection import ParameterGrid

estimator_param_grid = {'random_seed': [0, 1], 'tree_depth': [4, 5],
                        'shrink_threshold': ['1.0%', '2.0%']}
estimator_params = list(ParameterGrid(estimator_param_grid))
estimator_params

上記のパラメーターに加え、より多くのモデルを作成して評価するために、
学習およびテストに使用するデータを、ホールドアウトによって2通りに分割します。
データを学習用、テスト用に分割するため、scikit-learnのKFoldを使用します。

In [ ]:
from sklearn.model_selection import KFold

kf = KFold(n_splits=2, shuffle=True, random_state=1)

KFoldによりデータを分割しつつ、estimatorに与えるパラメーターを複数変化させて学習を実行します。
評価指標として、ワインの品種を正しく分類できたことを示す、正解率(accuracy)を採用します。
estimatorは、多値分類を実行するために「SklearnFABBernGateSoftmaxClassifier」を使用します。

※scikit-learnには、複数モデルを作成して評価するGridSearchCVという機能がありますが、
 ベストモデル(評価指標が一番高いモデル)のみしか保存されないため、cross_validate()関数を使用します。

In [ ]:
from sklearn.model_selection import cross_validate
from sklearn_fab import SklearnFABBernGateSoftmaxClassifier

estimator_score_dict = {}

for i, param in enumerate(estimator_params):
    cl = SklearnFABBernGateSoftmaxClassifier(**param)
    cv_results = cross_validate(cl, X, y, cv=kf, return_estimator=True)

    for j in range(kf.get_n_splits()):
        result_as_dict = {'estimator': cv_results['estimator'][j],
                          'accuracy': cv_results['test_score'][j]}
        estimator_score_dict['fold{}_param{}_estimator'.format(j, i)] = result_as_dict

3. 複数モデルの評価¶

この章では、前章で作成した複数モデルの評価・比較を行います。
まずは、各モデルの正解率を確認します。

In [ ]:
for k, v in estimator_score_dict.items():
    print('{}: {}'.format(k, v['accuracy']))

これらの全てについて、門木や予測式を確認するのは大きな手間がかかってしまいます。
そこで、正解率が高い上位3つのモデルのみを選択します。

In [ ]:
scores = sorted(estimator_score_dict.items(), key=lambda x: x[1]['accuracy'], reverse=True)
good_score_estimators = {name: score for _, (name, score) in zip(range(3), scores)}
good_score_estimators

選択されたモデルを比較するため、それぞれの門木と予測式情報を可視化し、門関数と予測式の詳細を確認します。
estimatorに与えたパラメーターについては、今回指定したものに絞って出力します。

In [ ]:
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display, Image
from sklearn_fab.utils import export_gate_tree_dot
%matplotlib inline


# 門木を可視化する関数を定義
def visualize_gate_tree(estimator):
    return display(Image(export_gate_tree_dot(estimator).create_png()))


# 予測式を可視化する関数を定義
def visualize_prediction_formulas(estimator):
    columns = np.append(estimator.feature_names_, ['bias'])
    multicl_indices = ['component #{}, class[{}]'.format(comp_id, cls_name)
                       for comp_id in estimator.comp_ids_
                       for cls_name in estimator.classes_]
    prediction_formulas = [np.append(comp.weights[:, cls_idx], comp.bias[cls_idx])
                           for comp in estimator.comps_
                           for cls_idx in range(estimator.n_classes_)]
    pf_df = pd.DataFrame(prediction_formulas, columns=columns, index=multicl_indices)
    relevant_feature_indices = pf_df.sum(axis=0) != 0
    pf_df = pf_df.T[relevant_feature_indices]

    return pf_df.plot(kind='barh', figsize=(10, 5), stacked=True, xlim=[-15, 10])


# グラフのプロット時におけるWarningを非表示
np.seterr(invalid='ignore')

# 今回、指定したパラメーターのみ抽出
specified_params = estimator_param_grid.keys()

for i, (k, v) in enumerate(good_score_estimators.items()):
    est_instance = v['estimator']
    full_params = est_instance.get_params()
    params_of_estimator = {key: full_params[key] for key in specified_params}
    print(k + ': score = {}\n'.format(v['accuracy']) + str(params_of_estimator))
    visualize_gate_tree(est_instance)
    visualize_prediction_formulas(est_instance)
    plt.show()

以下を判断基準として、fold0_param1_estimatorをベストモデルとして選択します。

  • 正解率(精度)が一番高い
    他の正解率0.955に対し、0.9775と正解率が高くなっています。
  • 採用された門関数が妥当
    アルコール度数を意味するalcoholが、門関数として使用されています。
    品種によりアルコール度数が異なるのは自然であるため、妥当だといえます。
  • 予測式の係数、biasが妥当
    色彩強度(color_intensity)やフラボノイド値(flavanoids)等、品種分類として使用される類の属性が使用されています。
    また、それらの係数とbiasが同様の値であり、適度に学習がされている(アンダーフィットではない)ことがわかります。
In [ ]:
best_estimator = estimator_score_dict['fold0_param1_estimator']['estimator']
best_estimator

上記に示したように、異種混合学習ではランダムリスタートして複数モデルを作成し、
精度のみならず門木と予測式が妥当で理解できるものかを確認して、モデル選択を行います。
sklearn-fabでも、それらの性質や特長を活かして、分析が実行できることがわかりました。

4. 可視化した門木の加工¶

異種混合学習で学習したモデルの解釈性を更に高めるという目的で、可視化した門木を見やすく加工することがあります。
加工とは、具体的に以下のようなことを示します。

  • 門関数を日本語化・逆標準化する
  • 予測式毎に背景色を変える

本バージョンでは、門木がdotフォーマットで出力されるため、簡単に編集することができます。

In [ ]:
dot = export_gate_tree_dot(best_estimator)
Image(dot.create_png())

上記の図が、dotフォーマットでどのように表現されるかを確認します。

In [ ]:
print(dot.to_string())

一例として、以下を実行します。

  • 門関数「alcohol < -3.775e-01」を逆標準化・日本語化します。
  • 予測式11の背景を青色にします。
In [ ]:
# labelの数値を逆標準化する関数を定義
def destandardize(estimator, node_id, scaler):
    gate = estimator.tree_.get_gate_functions()[node_id]
    array = np.zeros((len(estimator.feature_names_)))
    array[gate.feature_id] = gate.threshold
    destandardized_array = scaler.inverse_transform(array)
    return destandardized_array[gate.feature_id]


# 門関数を逆標準化・日本語化にする
node_id = 0
gate_node, = dot.get_node(str(node_id))
destandardized_threshold = round(destandardize(best_estimator, node_id, scaler), 3)
label = 'アルコール濃度が{}%未満'.format(destandardized_threshold)
gate_node.set('label', label)

# 予測式の背景を青色にする
comp_node, = dot.get_node('24')
comp_node.set('fillcolor', '#4169e1')
Image(dot.create_png())

上記のように、門木の内容が変更されました。
可視化APIである「export_gate_tree_dot」は、.dot形式のファイルを出力する機能もあるため、
.dotファイルを直接編集することも可能です。