学習データの分割と特徴量の前処理
訓練データへの一致と未使用データへの予測を比較し、尺度とデータ漏洩の影響を調べます。
説明
教師あり学習は入力の特徴量と正解の組から予測規則を求めます。分類はクラス、回帰は数値を予測します。評価用データを学習から分離するのは、既に見た答えへの一致と、新しい対象への予測性能を分けるためです。
少数の固定データで、最も近い訓練点を使う分類と、直線による回帰を行います。二つの特徴量の単位を変え、訓練点へ完全一致する曲線も比較します。最後に、評価データを前処理へ混ぜた場合の変化を確認します。
1入力とラベルを分ける
二つの特徴量を応答時間ミリ秒と転送量バイトとします。ラベルA・Bは分類対象の二クラスです。
import math距離計算; train = [((0,1000),'A'),((10,0),'B')]訓練点とクラス; query = (1,0)予測対象; train,query入力の確認未知の点(1,0)をどちらに分類するかを調べます。座標の数値が大きいほど、その特徴が重要だと決めたわけではありません。単位が違うことを先に確認します。
2元の単位で距離を求める
ユークリッド距離で最も近い訓練点のクラスを選びます。
distances = [(math.dist(query,point),label) for point,label in train]訓練点までの距離; distances,min(distances)[1]距離一覧と最近傍クラスAまで約1000、Bまで9なのでBを選びます。転送量の1000バイト差が、応答時間の9ミリ秒差より数値上大きく効いています。
3訓練データの範囲で尺度をそろえる
訓練点の範囲に合わせ、応答時間を10、転送量を1000で割ります。予測対象にも同じ変換を使います。
scale = lambda point: (point[0]/10,point[1]/1000)訓練範囲から決めた変換; scaled_distances = [(math.dist(scale(query),scale(point)),label) for point,label in train]同じ尺度で距離を計算; scaled_distances,min(scaled_distances)[1]変換後の距離とクラス尺度をそろえるとAまで約1.005、Bまで0.9で、今回はまだBです。変換したから必ずクラスが変わるのではなく、各成分が距離へ寄与する割合が変わります。
4尺度で判定が変わる点
応答時間をさらにA寄りの0.1ミリ秒にした点を調べます。元の尺度と、先ほどの尺度を比較します。
query2 = (0.1,100)別の予測点; raw_label = min((math.dist(query2,point),label) for point,label in train)[1]元の尺度の最近傍; scaled_label = min((math.dist(scale(query2),scale(point)),label) for point,label in train)[1]尺度をそろえた最近傍; raw_label,scaled_label判定の比較元の尺度ではB、変換後ではAです。距離に基づく分類では尺度の設計が予測規則の一部になります。変換は訓練側で決め、評価側を見て都合よく変更しません。
5数値を予測する回帰
入力xと出力yの訓練点を(0,0)、(1,1)、(2,2)とします。比較する二つの予測式は直線xと曲線x+10x(x−1)(x−2)です。
training = [(0,0),(1,1),(2,2)]回帰の訓練点; linear = lambda x: x直線モデル; wavy = lambda x: x+10*x*(x-1)*(x-2)訓練点に一致する曲線; [(x,y,linear(x),wavy(x)) for x,y in training]正解と二つの予測両方とも訓練点で完全に一致します。曲線の追加項は訓練点0・1・2で0になるためです。訓練誤差0だけでは、どちらが新しい入力に適するか決まりません。
6未使用の点で誤差を測る
評価点(0.5,0.5)、(1.5,1.5)はモデルを決めるときに使わない点です。二乗誤差の平均を計算します。
test = [(0.5,0.5),(1.5,1.5)]未使用の評価点; mse = lambda model,data: sum((model(x)-y)**2 for x,y in data)/len(data)平均二乗誤差; [(x,y,linear(x),wavy(x)) for x,y in test]各点の予測, mse(linear,test),mse(wavy,test)評価誤差直線の評価誤差0に対し、曲線は14.0625です。訓練点へ合わせる自由度を増やすと、間の値で大きく外れる例です。実データの関係が常に直線だという主張ではありません。
7前処理に評価データを混ぜる影響
訓練値0・1・2の平均と、評価値100まで混ぜた平均を比べます。中心化は平均を引く前処理です。
train_values = [0,1,2]; held_out = [100]訓練と評価を分離; train_mean = sum(train_values)/len(train_values)訓練だけの平均; mixed_mean = sum(train_values+held_out)/len(train_values+held_out)評価を混ぜた平均; train_mean,mixed_mean,[x-train_mean for x in train_values],[x-mixed_mean for x in train_values]平均と中心化した値訓練平均1が混合集計では25.75となり、訓練値の変換自体が評価データに依存します。前処理の統計も訓練データだけで求めることが、評価対象の情報を漏らさないために必要です。
8同じ対象の重複を点検する
別行でも同じ対象由来なら、ランダムに行を分けただけでは独立な評価にならない場合があります。対象IDの重複を確認します。
train_ids = {'host-a','host-b'}; test_ids = {'host-b','host-c'}訓練と評価の対象ID; sorted(train_ids & test_ids)重複する対象host-bが両方にあります。新しいホストへの性能を測る目的ならホスト単位で分離します。将来時刻への予測なら時系列に沿った分割など、利用時の条件に合う評価設計が必要です。
まとめ
分類と回帰で出力の種類は違っても、未使用データでの評価が必要です。
特徴量の尺度と前処理は訓練側で決め、評価側へ同じ変換を適用します。
訓練誤差0や行の分割だけでは、過学習やデータ漏洩を防げません。
操作履歴と解説を振り返れます。
Python 3
import math; train = [((0,1000),'A'),((10,0),'B')]; query = (1,0); train,query([((0, 1000), 'A'), ((10, 0), 'B')], (1, 0))
distances = [(math.dist(query,point),label) for point,label in train]; distances,min(distances)[1]([(1000.000499999875, 'A'), (9.0, 'B')], 'B')
scale = lambda point: (point[0]/10,point[1]/1000); scaled_distances = [(math.dist(scale(query),scale(point)),label) for point,label in train]; scaled_distances,min(scaled_distances)[1]([(1.004987562112089, 'A'), (0.9, 'B')], 'B')
query2 = (0.1,100); raw_label = min((math.dist(query2,point),label) for point,label in train)[1]; scaled_label = min((math.dist(scale(query2),scale(point)),label) for point,label in train)[1]; raw_label,scaled_label('B', 'A')training = [(0,0),(1,1),(2,2)]; linear = lambda x: x; wavy = lambda x: x+10*x*(x-1)*(x-2); [(x,y,linear(x),wavy(x)) for x,y in training][(0, 0, 0, 0), (1, 1, 1, 1), (2, 2, 2, 2)]
test = [(0.5,0.5),(1.5,1.5)]; mse = lambda model,data: sum((model(x)-y)**2 for x,y in data)/len(data); [(x,y,linear(x),wavy(x)) for x,y in test], mse(linear,test),mse(wavy,test)([(0.5, 0.5, 0.5, 4.25), (1.5, 1.5, 1.5, -2.25)], 0.0, 14.0625)
train_values = [0,1,2]; held_out = [100]; train_mean = sum(train_values)/len(train_values); mixed_mean = sum(train_values+held_out)/len(train_values+held_out); train_mean,mixed_mean,[x-train_mean for x in train_values],[x-mixed_mean for x in train_values](1.0, 25.75, [-1.0, 0.0, 1.0], [-25.75, -24.75, -23.75])
train_ids = {'host-a','host-b'}; test_ids = {'host-b','host-c'}; sorted(train_ids & test_ids)['host-b']
演習完了