今回は、データを「正規化する」と「標準化する」という紛らわしい言葉を整理したうえで、超有名なアヤメ(iris)のデータをネタにそれで精度がどう変化するのかをNeural Network Consoleで試してみるのをテーマとしてやります。
標準化と正規化って何が違うのか
自分がまさにそうでしたが「標準化」と「正規化」と言う言葉に混乱している人を良く見かけます。
混乱する原因は、同じ単語が違う意味で使われがちだからです。
例えば、「ベクトル」にも「特徴軸」にも「確率分布」にも、それぞれに「正規化」が存在し、意味が違います。
機械学習だけにフォーカスをあてみても、状況はあまり変わりません。
scikit-learnにおける標準化と正規化
例えば、pythonの機械学習モジュールであるscikit-learnには、StandardScaler と MinMaxScaler がそれぞれ 標準化(standardization) と 正規化(normalization) のモジュールだという説明があります。
ここでは標準化は「平均が0、標準偏差が1になるようにデータを加工(スケーリング)」し、正規化は「最低が0、最高が1になるようにデータを加工(スケーリング)する」モジュールを指してます。
ニューラルネットワークにおける正規化
ところが、ニューラルネットワークのレイヤーには「バッチノーマライゼーション(Batch Normalization)」という手法があり、これも「平均が0、分散が1になるようにデータを加工」するのですが、標準化とは言いません。
正規化(normalization)とか正則化という言葉で説明されてます。
どうやら。
標準化と呼ばれる時はこうだ。
正規化と呼ばれる時ならこうだ。
そんな風に無理やり一般化した正解があるとは考えないほうが良さげです。
すべて正規化ということで良いのではないか
結局。
すべては「正規化」ということで良いみたいです。
Wikiから引用すると。
正規化(せいきか、英: normalization)とは、データ等々を一定のルール(規則)に基づいて変形し、利用しやすくすること。別の言い方をするならば、正規形でないものを正規形(比較・演算などの操作のために望ましい性質を持った一定の形)に変形することをいう。
これだけです。
後は、データの分布や状態や何をしたいかによって、適用する一定のルールが違うというだけみたいなのですね。
例えば、正規分布に従うデータの場合は「平均0、分散1」の標準正規分布に従うように変形することで単位や平均が異なるデータでも比較しやすくなる(偏差値なんかの考え方・・と同じ)=学習効率があがる可能性がある・・とか。
一様分布に従うようなデータの場合は、最小0から最大1になるように加工した方が良い結果がでる可能性があるとか。
そんな感じで良さげです。
アヤメ(iris)のデータの正規化をやってみます
アヤメのデータの概要については、前回もふれているので、繰り返しはしません。
今回は正規化なので、データがどんな分布なのか?がポイントになります。
特徴変数は4つあります。
- sepal length :萼片(がくへん)の長さ
- sepal width: 萼片(がくへん)の幅
- petal length:花びらの長さ
- petal width:花びらの幅
最初は視覚的な分布状況の確認
まず、ヒストグラムで視覚的に確認すると。
見た感じは。
sepal (がくへん)は、length、widthともなんとなく正規分布っぽい。
petal (花びら)は両方とも・・ぽくない。
そういう印象です。
統計的手法(正規性検定)を用いた確認
ここはちゃんと先人の知恵を借りて確認します。
SHAPIRO-WILKの正規性検定(シャピロ・ウィルク検定)を行います。
データが正規分布に従っているという仮説に対する検定です。
計算でP値を求めて、それが0.05より大きければ棄却されない(=正規分布である)ですし、0.05以下なら棄却される(=正規分布ではない)と判断します。
上記4つの変数について、検定してみます。
import numpy as np import pandas as pd from sklearn.datasets import load_iris import scipy.stats as stats ''' アヤメ(iris)のデータをpandasのDataFrameに変換する。 scipy.statsのSHAPIRO-WILKの正規性検定で正規分布か否かを検定する ''' class MyDataFrame(): def get_iris(self): iris = load_iris() return pd.DataFrame(data= np.c_[iris['data'], iris['target']],columns=iris['feature_names'] + ['target']) mdf = MyDataFrame() mpd = mdf.get_iris() a = stats.shapiro(mpd['sepal length (cm)'].values) print(a) b = stats.shapiro(mpd['sepal width (cm)'].values) print(b) c = stats.shapiro(mpd['petal length (cm)'].values) print(c) d = stats.shapiro(mpd['petal width (cm)'].values) print(d)
結果は以下の通りです。
sepal length (cm) : (0.9760897755622864, 0.01017984002828598)
sepal width (cm) : (0.9849170446395874, 0.10113201290369034)
petal length (cm) : (0.8762688040733337, 7.412849778454245e-10)
petal width (cm) : (0.9018339514732361, 1.6802413682626138e-08)
結果は(統計量W、p値)の順です。
チェックするのは後ろの方(p値)です。
判断の仕方は
です。
見る限り二番目の「sepal width (cm)」のみが正規分布で、残り3つは0.05以下のため正規分布ではないということになります。
結果をふまえて正規化手法をとりあえず決定する
でも。
一番目の「sepal length (cm)」は、棄却されたとは言え、約0.01で近いので、まあ、今回は大目に見て(笑:普通はありえないですが)、2番目の「sepal width (cm)」と同じ扱いで考えてみます。
つまり、正規化の方針は以下のようにしてみます。
sepal (がくへん)の2つは「StandardScaler」で正規化し、
petal (花びら)の2つは「MinMaxScaler」で正規化します。
方針を踏まえて正規化&CSVデータを吐き出すpythonソース
正規化して、元データと置き換えてCSVに吐き出す処理です。
import numpy as np import pandas as pd from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler,MinMaxScaler ''' アヤメ(iris)のデータをpandasのDataFrameに変換する。 sklearnのStandardScalerとMinMaxScalerを使って正規化し、結果をDataFrameに書き戻してCSVに保存する ''' class MyDataFrame(): def get_iris(self): iris = load_iris() return pd.DataFrame(data= np.c_[iris['data'], iris['target']],columns=iris['feature_names'] + ['target']) def norm(norm,ar): ar1 = ar.reshape(ar.shape[0],-1) ar2 = norm.fit_transform(ar1) return ar2.flatten() mdf = MyDataFrame() mpd = mdf.get_iris() std = StandardScaler() minmax = MinMaxScaler(feature_range=(-1, 1)) a = norm(std,mpd['sepal length (cm)'].values) b = norm(std,mpd['sepal width (cm)'].values) c = norm(minmax,mpd['petal length (cm)'].values) d = norm(minmax,mpd['petal width (cm)'].values) cnt = 0 for index,row in mpd.iterrows(): mpd.at[index,'sepal length (cm)'] = a[cnt] mpd.at[index,'sepal width (cm)'] = b[cnt] mpd.at[index,'petal length (cm)'] = c[cnt] mpd.at[index,'petal width (cm)'] = d[cnt] print(row[0],row[1],row[2],row[3]) cnt += 1 mpd.to_csv('iris_norm.csv')
補足します。
正規化処理自体は「sklearn.preprocessing」の「StandardScaler(平均0分散1で正規化)」と「MinMaxScaler(最小値から最大値の間の値に正規化)」を使います。
MinMaxScalerはデフォルトだと0から1の範囲で正規化するのですが、平均0分散1の正規化の値が-1から1の範囲になるので、「MinMaxScaler(feature_range=(-1, 1))」で範囲をあわせてます。
正規化は「fit_transform()」メソッドなのですが、引数が2次元配列前提になってます。
ですが、DataFrameからvaluesで抽出したままだと1次元配列です。
なので、一旦「ar.reshape(ar.shape[0],-1)」で2次元配列に変換して、「fit_transform()」で処理して、結果を「ar2.flatten()」で1次元に戻すという、若干面倒くさいことをやってます。
あとは、ループの中でDataFrameの各値を正規化後の値に置き換えて、置き換え済の結果をCSVに吐き出すわけです。
Neural Network Consoleで学習・評価をやってみる
例によって「Neural Network Console」で学習評価をしてみます。
CSV出力しただけだと、左端に連番が出力されていたり、ヘダーの名称が「sepal length (cm)」とかになっています。
るので、EXCELで開き、余分な列である左端の列(連番)を消して、ヘダーの名称を「x__1」のようにNeural Network Consoleのルールに沿って修正する必要があります。
あとは、全体で150件のデータを適当に割り振って、トレーニング用とテスト用のCSVにわけて、それぞれ「iris_train_norm.csv」「iris_test_norm.csv」とでも名前をつけておきます。
trainが0.7:testが0.3くらいの比率に普通はするのですが、今回は条件を厳しくするために、0.5:0.5で75件ずつにバッサリわけてみました。
この辺のCSVデータの加工は手作業です。
データも小さいし、難しい加工でもないので・・。
ネットワークモデル
さて。
学習用のネットワークモデルはこんな感じ。
inputは以下のようなCSVで、xの列が4つなので「4」です。
内容のデータは、正規化後の数値に置き換わってます。
このCSVをDataSetに登録して学習を実行します。
学習結果と評価結果
正規化前と同じMaxEpoch数 100だと、学習が完了しても、Accuracyは92%くらいしかいきません。
Best Validationも100ですし、明らかに学習不足です。
それから増やして試行しましたが、自分の環境では810がベストでした。
学習曲線と評価結果です。
Accuracyは98.66%でした。
単純比較すると、正規化前が約97%だったので改善はしてます。
劇的な改善はないけど、若干効果は感じられた
もちろん。
学習データとテストデータを入れ替えながら試行してみると、96%から100%までの振れ幅がありました。
だから、単純に正規化したから結果が良くなったなどとは言えません。
でも。
正規化することで、Epoch数を増やしても元データよりも、過学習になりづらくなったのは事実です。
アヤメのデータのような小さく既に整っているデータなので大した効果は感じられなかったのですが、精度をあげる方法のひとつであることは間違いなさそうですねえ。
ではでは。