利用
注意: 一部のmethodやattributeはまだ実装されていないため、ドキュメント記載通りに動作しない場合があります。
Wide Learningは、科学的発見プロセスをシミュレートするAI技術です。
科学における「発見」プロセスでは、次のステップを繰り返す。
- 仮説を考える
- 仮説の妥当性を検証するための観察と実験からのデータの使用
- 仮説が正しくない場合は、新しい仮説を検討します (1に戻る) 。
この手順を繰り返した後、生き残った仮説は科学の新しい理論である「発見」とみなされる。
ユーザーは、クラスWideLearnerClientの使い方を学ぶだけでWide Learningを試すことができます。
WideLearnerClient は scikit-learn の classifier インターフェースを実装していますので、ユーザーは上記のパイプラインを意識することなく、いつも通り fit() で学習を、 predict() で予測を実行できます。
>>>from sklearn.datasets import load_iris
>>>X, y = load_iris(return_X_y=True, as_frame=True)
>>>test = [49, 99, 149]
>>>X_train, X_test = X.drop(test), X.iloc[test]
>>>y_train, y_test = y.drop(test), y.iloc[test]
>>>y_test.values
array([0, 1, 2])
>>>from widelearning.api import WideLearnerClient
>>>wl = WideLearnerClient(random_state=0)
>>>wl.fit(X_train, y_train)
>>>WideLearnerClient(random_state=0)
>>>wl.predict(X_test)
array([0, 1, 2])
>>>wl.score(X_test, y_test)
1.0
fit(X, y) や predict(X) のパラメータ X として(単純な numpy array ではなく)カラム名情報を持ったpandas.DataFrameを与えることにより、ナレッジチャンク(KC)をわかりやすい名前で表示することができます。 KCの一覧表を得るためには、fit() の後に chunkdata() を実行してください。PlotlyまたはSeabornを使ってKCを可視化するplot_chunks()も用意されています。
WideLearnerClientは内部で次のパイプラインを実行します。
WidePreprocessorによる前処理ChunkyMinerまたはCopulaMinerによるKC列挙- python-glmnetの
LogitNetまたはscikit-learnのLogisticRegressionCVによる重み最適化
以降では上記の流れに沿って、WideLearnerClientクラスが持つパラメータを説明していきます。
1.前処理
WideLearnerClientへの入力データセットは、数値特徴量を数値型の列に、カテゴリ特徴量を文字列型の列にそれぞれ格納する必要があります。前処理の役割は、このデータを後段のパイプライン(KC列挙)で扱えるように、0から1の範囲の数値型の列に変換することです。前処理の設定はWideLearnerClientの第1引数preprocで指定できます。
列ごとに個別の前処理方法を指定するには、その列の名前をキーとし、その前処理方法の名前を値とする辞書オブジェクトをpreprocに渡します。前処理方法の選択肢については、この後の節で説明します。
aを数値特徴量の前処理方法、bをカテゴリ特徴量の前処理方法とした場合、preproc=(a, b)と指定することで、全ての数値特徴量にaが、全てのカテゴリ特徴量にbが適用されます1。デフォルトはpreproc=('qscale', 'onehot')です。preproc=(a, 'onehot')はpreproc=aと省略して記述することもでき、同様に、preproc=('qscale', b)はpreproc=bと省略して記述することも可能です。
[1]タプル中の順序は重要ではなく、preproc=(b, a)と指定しても同様の結果が得られます。さらに、同じタイプの特徴量に対して複数の前処理を適用させることも可能で、例えばpreproc=('onehot', 'onecold', 'cut5', 'qcut4')のように指定することも許容されています。
1.1 数値特徴量を対象とする前処理
数値特徴量の前処理には大きく分けて「スケーリング」と「2値化」があります。一般に、数値と正解ラベルの間に「大きいほどラベル1の比率が高い」や「小さいほどラベル1の比率が高い」のような単調性があるときはスケーリングが適しており、より複雑な関係を抽出するには2値化が適しているでしょう。
スケーリング
数値特徴量を [0, 1] の範囲に正規化します。値が大きいほど強く現れる性質と値が小さいほど強く現れる性質の両方を Wide Learning が発見できるように、元の特徴量に対して正の相関を持つ(最小値が0に、最大値が1に写像される)正規化特徴量と負の相関を持つ(最小値が1に、最大値が0に写像される)正規化特徴量の2つを生成します。
スケーリングのオプションには、MinMaxScaler (clip=True) を使って線形変換する 'scale' と、QuantileTransformerを使って一様分布に変換する'qscale'があります。元の値そのものを分類モデルに反映させるには'scale'が適しており、外れ値の影響を軽減するには'qscale'が適しています。
>>>X_train.columns
Index(['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)',
'petal width (cm)'],
dtype='object')
>>>wl = WideLearnerClient('scale').fit(X_train, y_train)
>>>wl.features_
array(['sepal length (cm)↗', 'sepal length (cm)↘', 'sepal width (cm)↗',
'sepal width (cm)↘', 'petal length (cm)↗', 'petal length (cm)↘',
'petal width (cm)↗', 'petal width (cm)↘'], dtype=object)
>>>wl = WideLearnerClient(preproc='qscale').fit(X_train, y_train)
>>>wl.features_
array(['sepal length (cm)⇧', 'sepal length (cm)⇩', 'sepal width (cm)⇧',
'sepal width (cm)⇩', 'petal length (cm)⇧', 'petal length (cm)⇩',
'petal width (cm)⇧', 'petal width (cm)⇩'], dtype=object)
2値化
指定された離散化方法と整数 k ≥ 2 に従って数値を k 個の区間に離散化した後、各区間への所属を「cut」または「bin」の方法でコード化した{0, 1} の2値ベクトルを生成します。「cut」は数値が各分割点未満かそれ以上かをそれぞれ示す最大 (k–1)×2 個の2値特徴量を生成し、「bin」は数値が各区間に属しているかどうかをone-hot表現する最大 k 個の2値特徴量を生成します2。 Wide Learningでは、2値特徴量の組み合わせで様々な区間を表現できる「cut」がよく使われます。
[2]学習サンプルの存在しない区間は隣接する区間に併合されますので、最終的な区間の数は k より少なくなることがあります。
「cutk」('cut2', 'cut3', …) と「bink」('bin2', 'bin3', …) は、列の最小値から最大値までの範囲を等間隔な k 個の区間に分割します3。「qcutk」('qcut2', 'qcut3', …) と「qbink」('qbin2', 'qbin3', …) は、列の値を学習サンプルがほぼ同数ずつ含まれる k 個の区間に分割します。「cutk」と「qcutk」の関係('bink' と 'qbink' の関係)は'scale' と 'qscale'の関係に似ています。元の値そのものに注目したい特徴量には「cutk」や「bink」が適しており、値の大きさの順位に注目したい特徴量には「qcutk」や「qbink」が適しています。
[3]分割点は学習サンプルの値を跨がない範囲で切りの良い値に補正されますので、最終的な区間は正確に等間隔とは限りません。
「ecutk」('ecut2', 'ecut3', …) と「ebink」('ebin2', 'ebin3', …) は、平均情報量(エントロピー)の計算に基づいて各区間と正解ラベルの相関が高くなるように分割点を決定する、教師ありの離散化方法です。他の特徴量との相互作用が考慮されていないことに注意が必要ですが、分類モデルの精度向上に役立つことが期待できます。
>>>wl = WideLearnerClient(
... preproc={
... 'sepal length (cm)': 'cut3',
... 'sepal width (cm)': 'qcut3',
... 'petal length (cm)': 'ecut3',
... 'petal width (cm)': 'ebin3',
... }
...).fit(X_train, y_train)
>>>wl.features_
array(['sepal length (cm)<5.5', 'sepal length (cm)≥5.5',
'sepal length (cm)<6.7', 'sepal length (cm)≥6.7',
'sepal width (cm)<2.9', 'sepal width (cm)≥2.9',
'sepal width (cm)<3.2', 'sepal width (cm)≥3.2',
'petal length (cm)<2.0', 'petal length (cm)≥2.0',
'petal length (cm)<4.8', 'petal length (cm)≥4.8',
'petal width (cm)<0.8', '0.8≤petal width (cm)<1.8',
'petal width (cm)≥1.8'], dtype=object)
1.2 カテゴリ特徴量を対象とする前処理
カテゴリ特徴量に対する前処理では、値がどのカテゴリであるかをone-hot表現した 2値ベクトルを生成する'onehot'が標準的でしょう。この方法はシンプルで解釈しやすく、パイプライン後段のKC列挙や重み最適化における計算コストを抑制する効果があります。
しかし、カテゴリ数の多い特徴量に対して 'onehot' を適用すると、該当するサンプル数の少ない(すなわちサポートの小さい)項目ばかりが生成され、Wide Learning での良い分析ができないことがあります。それを手作業の特徴量エンジニアリングで解消する方法は、グループ化によりカテゴリ数を減らすことです。より良い分析を行うためには、元のカテゴリを複数の視点でグループ化した複数のカテゴリ特徴量を作ると良いでしょう。そうすれば、カテゴリグループの有用な組み合わせは Wide Learning が自動的に見つけ出します。
もう一つの前処理 'onecold' には、KC列挙の計算コスト増加を代償として、そのようなカテゴリのグループ化作業を自動化する効果があります。 'onecold' は 'onehot' の0と1を反転させた2値ベクトルを生成します4。すなわち、一つ一つの2値特徴量は特定のカテゴリでないことを示し、その組み合わせによってカテゴリ値の任意の部分集合を表現できるようになります。
[4]3種類以上の値が出現しない特徴量は 'onehot' と同じように処理されます。
>>>import random
>>>import pandas as pd
>>>pd.set_option('display.width', 999)
>>>pd.set_option('display.max_colwidth', 999)
>>>pd.set_option('display.max_columns', 999)
>>>X = pd.DataFrame(
... {
... 'A': random.choices(['a0', 'a1', 'a2'], k=100), # categorical
... 'B': random.choices(['b0', 'b1'], k=100), # categorical
... 'C': random.choices([0, 1, 2], k=100), # numeric
... 'D': random.choices(['0', '1', '2'], k=100), # categorical
... }
...)
>>>X.dtypes
A object
B object
C int64
D object
dtype: object
>>>y = [0] * 50 + [1] * 50
>>>wl = WideLearnerClient().fit(X, y)
>>>wl.features_
array(["A='a0'", "A='a1'", "A='a2'", "B='b0'", "B='b1'", 'C⇧', 'C⇩',
"D='0'", "D='1'", "D='2'"], dtype=object)
>>>wl = WideLearnerClient(preproc=('cut3', 'onecold')).fit(X, y)
>>>wl.features_
array(["A≠'a0'", "A≠'a1'", "A≠'a2'", "B='b0'", "B='b1'", 'C<1', 'C≥1',
'C<2', 'C≥2', "D≠'0'", "D≠'1'", "D≠'2'"], dtype=object)
2. KC列挙
このステップでは、前処理で作られた正規化特徴量(アイテム)を組み合わせて作ることのできるアイテム集合の中からナレッジチャンク(KC)を列挙します。ラベル C0 と C1 への2分類問題では、目的クラス「C0」と「C1」に対するKC列挙が実行されます。 3分類以上の問題では、各ラベル C について「C」と「C 以外(¬C)」の2通りの目的クラスに対するKC列挙が実行されます。
KCは、次の条件を満たすアイテム集合です。
-
明示的に指定された
max_lenなどの制約条件を満たす。 -
目的クラスに対するヒット率(信頼度)が自身の部分集合よりも高い。
-
以上を満たすものの中で、目的クラスとの相互情報量が上から k 位以内。
目的クラスごとのKC数を chunk_limit で近似的に5制限することができます。シンプルなモデルを得る目的で、これを小さい値に設定することができます。大きい値に設定することで予測モデルの精度を高められる可能性がありますが、大きすぎると後段の重み最適化ステップの処理能力を越えてしまうことに注意してください。
[5]高速化のため厳密なトップk計算を省略しています。
>>>from sklearn.datasets import load_iris
>>>X, y = load_iris(return_X_y=True, as_frame=True)
# using solver=None to skip the optimization step
>>>wl = WideLearnerClient('cut7', max_len=None, chunk_limit=1, solver=None).fit(X, y)
>>>wl.n_chunks(all=True)
13
>>>wl.chunkdata(all=True)
weight len npos nneg supp conf chi2 nmi
label chunk
0 petal width (cm)<0.8 1.0 1 50 0 1.00 1.000000 150.000000 1.000000
petal length (cm)<2.0 1.0 1 50 0 1.00 1.000000 150.000000 1.000000
¬0 petal width (cm)≥0.8 -1.0 1 100 0 1.00 1.000000 150.000000 1.000000
petal length (cm)≥2.0 -1.0 1 100 0 1.00 1.000000 150.000000 1.000000
petal length (cm)≥1.8 ∧ petal width (cm)≥0.5 -1.0 2 100 0 1.00 1.000000 150.000000 1.000000
1 petal length (cm)<5.3 ∧ petal width (cm)≥0.8 ∧ petal width (cm)<1.9 1.0 2 50 8 1.00 0.862069 118.965517 0.756287
petal length (cm)≥2.0 ∧ petal length (cm)<5.3 ∧ petal width (cm)<1.9 1.0 2 50 8 1.00 0.862069 118.965517 0.756287
petal length (cm)≥1.8 ∧ petal length (cm)<5.3 ∧ petal width (cm)≥0.5 ∧ petal width (cm)<1.9 1.0 2 50 8 1.00 0.862069 118.965517 0.756287
sepal length (cm)≥4.9 ∧ sepal width (cm)<3.8 ∧ petal length (cm)≥1.8 ∧ petal length (cm)<5.3 ∧ petal width (cm)<1.9 1.0 4 50 8 1.00 0.862069 118.965517 0.756287
¬1 petal width (cm)<0.8 -1.0 1 50 0 0.50 1.000000 37.500000 0.274018
petal length (cm)<2.0 -1.0 1 50 0 0.50 1.000000 37.500000 0.274018
2 petal length (cm)≥4.4 ∧ petal width (cm)≥1.5 1.0 2 49 14 0.98 0.777778 96.551724 0.593289
¬2 petal length (cm)<5.3 ∧ petal width (cm)<1.9 -1.0 2 100 8 1.00 0.925926 116.666667 0.701315
>>>wl = WideLearnerClient('cut7', max_len=None, chunk_limit=100, solver=None).fit(X, y)
>>>wl.n_chunks(all=True)
548
制約条件を設定するパラメータには、max_len の他に min_npos、max_neg、 min_supp、min_conf、min_chi2、min_nmiなどがあります。詳しくはAPIリファレンスWideLearnerClientをご覧ください。
3. 重み最適化
WideLearnerClientの分類モデルは、KCの値6を入力とした一つまたは複数の線形関数で構成されます。重み最適化はそれらの線形関数における係数(重み)と切片7を決めるステップです。
[6]KCの値は、それを構成するアイテム値の積です。例えばアイテム集合 {A, B} で構成されるKCの値は、A = 0.5 かつ B = 0.8 のサンプルでは 0.5 × 0.8 = 0.4 となります。全てが2値アイテムの場合、これは論理積と同じです。例えば2値アイテムの集合 {C, D} で構成されるKCの値は、C = 1 かつ D = 1 のサンプルのみで 1 となります。空集合KC {} の値は常に 1 です。
[7]chunkdata() や plot_chunks() において、切片は空集合KC{} に対する重みとして表示されます。この目的で追加された空集合KCは前節の列挙条件を満たしていないことに注意してください。
パラメータfit_interceptをFalseに設定すると、切片を0に固定することができます。
l1_ratio はElastic-NetにおけるL1正則化とL2正則化の混合比を指定するパラメータです。これは0から1までの値を取り、0(L2正則化のみ)ならRidge、1(L1正則化のみ)ならLassoと同じです。したがって1に近付くほど、相関の高いKC群から一つを残して他の重みを0にする効果が強くなります。
>>>from sklearn.datasets import load_iris
>>>from sklearn.model_selection import train_test_split
>>>X, y = load_iris(return_X_y=True, as_frame=True)
>>>y_0 = y == 0
>>>X_train, X_test, y_train, y_test = train_test_split(
... X, y_0, test_size=0.33, stratify=y_0, random_state=0
...)
# Lasso with intercept
>>>wl = WideLearnerClient(
... 'scale', fit_intercept=True, l1_ratio=1, random_state=1
...).fit(X_train, y_train)
>>>wl.score(X_test, y_test)
1.0
>>>wl.chunkdata()
weight len npos nneg supp conf chi2 nmi
label chunk
True petal length (cm)↘ × petal width (cm)↘ 23.015085 2 28.439972 9.099576 0.861817 0.757600 49.701686 0.414764
sepal width (cm)↗ × petal length (cm)↘ 4.604287 2 18.795904 7.536723 0.569573 0.713788 23.812898 0.181879
False -14.374686 0 67.000000 33.000000 1.000000 0.670000 0.000000 0.000000
# Lasso without intercept
>>>wl = WideLearnerClient(
... 'scale', fit_intercept=False, l1_ratio=1, random_state=1
...).fit(X_train, y_train)
>>>wl.score(X_test, y_test)
1.0
>>>wl.chunkdata()
weight len npos nneg supp conf chi2 nmi
label chunk
True petal length (cm)↘ × petal width (cm)↘ 13.001155 2 28.439972 9.099576 0.861817 0.757600 49.701686 0.414764
False petal width (cm)↗ -1.867848 1 44.041667 2.166667 0.657338 0.953111 31.140801 0.283293
sepal width (cm)↘ -6.425330 1 42.833333 12.583333 0.639303 0.772932 5.956385 0.047081
petal length (cm)↗ -10.316972 1 44.457627 2.576271 0.663547 0.945225 30.422906 0.272887
# Elestic-Net without intercept
>>>wl = WideLearnerClient(
... 'scale', fit_intercept=False, l1_ratio=0.5, random_state=1
...).fit(X_train, y_train)
>>>wl.score(X_test, y_test)
1.0
>>>wl.chunkdata()
weight len npos nneg supp conf chi2 nmi
label chunk
True petal length (cm)↘ × petal width (cm)↘ 3.141001 2 28.439972 9.099576 0.861817 0.757600 49.701686 0.414764
sepal length (cm)↘ × petal length (cm)↘ × petal width (cm)↘ 3.005075 3 22.800912 5.027521 0.690937 0.819339 41.759278 0.329134
sepal width (cm)↗ × petal length (cm)↘ × petal width (cm)↘ 1.562555 3 17.523717 2.768597 0.531022 0.863564 32.780539 0.253875
sepal width (cm)↗ × petal length (cm)↘ 1.160689 2 18.795904 7.536723 0.569573 0.713788 23.812898 0.181879
sepal length (cm)↘ × petal length (cm)↘ 1.121336 2 24.354905 11.407807 0.738027 0.681014 31.024387 0.246821
sepal length (cm)↘ × sepal width (cm)↗ × petal length (cm)↘ × petal width (cm)↘ 1.110712 4 13.657534 1.377991 0.413865 0.908351 26.771670 0.208692
sepal width (cm)↗ × petal width (cm)↘ 1.107409 2 19.024306 7.493056 0.576494 0.717428 24.498501 0.187346
sepal length (cm)↘ × petal width (cm)↘ 1.047620 2 24.698232 11.502525 0.748431 0.682257 31.844430 0.254197
sepal length (cm)↘ × sepal width (cm)↗ × petal length (cm)↘ 0.865064 3 14.625535 3.440699 0.443198 0.809551 22.934289 0.173656
sepal length (cm)↘ × sepal width (cm)↗ × petal width (cm)↘ 0.835394 3 14.818550 3.396991 0.449047 0.813511 23.550275 0.178560
petal length (cm)↘ 0.509627 1 30.423729 22.542373 0.921931 0.574400 30.422906 0.272887
petal width (cm)↘ 0.358236 1 30.833333 22.958333 0.934343 0.573199 31.140801 0.283293
sepal length (cm)↘ × sepal width (cm)↗ 0.185292 2 15.877525 9.618687 0.481137 0.622741 13.263995 0.100278
sepal width (cm)↗ 0.099418 1 20.416667 24.166667 0.618687 0.457944 5.956385 0.047081
False sepal length (cm)↗ × sepal width (cm)↘ × petal width (cm)↗ -0.106405 3 15.789247 0.137574 0.235660 0.991362 8.848554 0.100453
sepal length (cm)↗ × sepal width (cm)↘ × petal length (cm)↗ -0.141141 3 16.110448 0.168464 0.240454 0.989651 8.985762 0.101108
sepal length (cm)↗ × petal width (cm)↗ -0.466383 2 26.241162 0.470960 0.391659 0.982369 16.085091 0.168928
sepal length (cm)↗ × petal length (cm)↗ -0.524704 2 26.562404 0.537237 0.396454 0.980176 16.175589 0.168501
sepal width (cm)↘ × petal length (cm)↗ × petal width (cm)↗ -0.903704 3 18.693385 0.062735 0.279006 0.996655 11.141477 0.128453
sepal length (cm)↗ × sepal width (cm)↘ -1.060094 2 23.148990 2.066919 0.345507 0.918031 9.381863 0.087559
sepal length (cm)↗ -1.138718 1 37.696970 6.606061 0.562641 0.850889 11.771678 0.098219
petal length (cm)↗ × petal width (cm)↗ -1.537311 2 30.598870 0.182910 0.456700 0.994058 21.121739 0.227261
sepal width (cm)↘ × petal width (cm)↗ -3.110567 2 27.368056 0.774306 0.408478 0.972486 16.207266 0.164727
sepal width (cm)↘ × petal length (cm)↗ -3.227391 2 27.827684 0.955508 0.415339 0.966803 16.102934 0.161124
sepal width (cm)↘ -3.787949 1 42.833333 12.583333 0.639303 0.772932 5.956385 0.047081
petal width (cm)↗ -4.165566 1 44.041667 2.166667 0.657338 0.953111 31.140801 0.283293
petal length (cm)↗ -4.331671 1 44.457627 2.576271 0.663547 0.945225 30.422906 0.272887
正則化の強さ λ は交差検証により自動的に最適化されます。交差検証の分割数や分割方法はパラメータcvで、交差検証の評価スコアはcv_scoreで指定できます。
学習サンプルにおける正解ラベルの比率に大きな偏りがあり、それを補正した最適化を実行したいときはclass_weightを設定してください。各ラベルをキーとして重みを値とする辞書オブジェクトを与えると、一つの学習サンプルが最適化計算に与える影響の大きさをラベルごとに設定することができます。文字列'balanced'を与えると、どのクラスも学習サンプルの重み合計が同じになるように、ラベルごとの重みが割り振られます。
最適化の結果には非決定性があります。結果の再現性が要求される場合には、random_stateで乱数シードを固定してください。