機械学習の分野などで、データの前処理にスケールを揃える正規化(normalize)をすることがあります。

正規化という言葉自体は様々な分野で使われているため、意味が混乱してしまいますが、ここでは数量に関する正規化(数学的意味合いにおける正規化も含む)を紹介します。

数量を扱った正規化自体には主に2つのやり方があり、それぞれ用途に応じたものを使い分けましょう。

データの正規化

データの正規化は、異なる種類のデータを1つの尺度にまとめるために行う正規化です。

例えば、学校の試験で英語が80点、数学が60点だったとします。このとき、点数だけを見れば、英語の方が成績が良さそうですが、クラス内の平均点が英語が78点、数学が30点で標準偏差が共に10であったら、英語の偏差値はわずかに50.2、数学の偏差値が80.0ということになります。

これを見れば数学の方が試験の成績としては良かった、と言うことができます。

このように、一つの尺度として考えることができればなんらかの数値を測りやすくなることがあります。偏差値もデータの正規化の1つの手段です。

データ分析の分野で使いやすいように考えられた正規化の方法が2つあるので、どのような方法かを紹介し、NumPyでの実装の仕方を紹介します。

z-score normalization (標準化)

z-scoreを求める手法は日本語では標準化(standardization)とよく表現される処理です。これは、元のデータの平均を0、標準偏差が1のものへと変換する正規化法です。

外れ値のあるデータに対してはそれを無視するようなデータへ変換できるため、外れ値に対して効果的な手法だと言えます。 この処理を行う前提として、元のデータの分布が正規分布になっていることがあります。このことについて詳細は以下のサイトを参考にしてみてください。

数量の正規化:方法の違いは何を意味するか?

z-scoreの求め方は以下の通りです。

x^i_{zscore} = \frac{x^i-\sigma}{\mu} \\ \sigma : 平均,\ \ \mu : 標準偏差 \\

これをNumPyで実装すると、以下のようになります。

import numpy as np
def zscore(x, axis = None):
    xmean = x.mean(axis=axis, keepdims=True)
    xstd  = np.std(x, axis=axis, keepdims=True)
    zscore = (x-xmean)/xstd
    return zscore

この関数は、多次元配列にも対応できるよう、axisを指定できるようにしてあります。axisに何も指定しないと、配列全体の要素の平均と標準偏差を算出して、全ての要素に対して同様の処理を施すことになります。

axisを指定すれば指定された軸(axis)方向のみで平均、標準偏差を出して計算してくれます。利用用途としては、バッチ全体で正規化するか、1サンプル毎に正規化するかを指定することができます。

こんな感じの使い方を想定しています。

In [66]: a = np.random.randint(10, size=(2,5))

In [67]: a # 乱数を10個生成
Out[67]:
array([[3, 1, 0, 3, 5],
       [4, 8, 1, 2, 5]])

In [68]: zscore(a)
Out[68]:
array([[-0.08804509, -0.968496  , -1.40872145, -0.08804509,  0.79240582],
       [ 0.35218036,  2.11308218, -0.968496  , -0.52827054,  0.79240582]])

In [71]: zscore(a, axis=1)
Out[71]:
array([[ 0.3441236 , -0.80295507, -1.3764944 ,  0.3441236 ,  1.49120227],
       [ 0.        ,  1.63299316, -1.22474487, -0.81649658,  0.40824829]])

In [72]: b = zscore(a, axis=1)

In [73]: b.sum(axis=1)
Out[73]: array([  2.22044605e-16,   5.55111512e-17])

In [74]: b.std(axis=1)
Out[74]: array([ 1.,  1.])

min-max normalization

データの中における最大値と最小値を使って正規化する方法です。

x^i_{minmax} = \frac{x^i-min}{max-min} \\ min: xの最小値、\ \ max:xの最大値 \\

この処理をすることで、データは最大値が1,最小値が0のデータとなります。あらかじめ最大値と最小値の範囲が限られている場合には有効な手法です。(逆に、外れ値が存在すると他の値の差がほとんど無くなってしまう可能性があります)

では、これもNumPyで実装していきます。

import numpy as np
def min_max(x, axis=None):
    min = x.min(axis=axis, keepdims=True)
    max = x.max(axis=axis, keepdims=True)
    result = (x-min)/(max-min)
    return result

次のように使用します。

In [87]: b = np.random.randint(10, size=(2,5))

In [88]: b
Out[88]:
array([[0, 1, 5, 8, 9],
       [9, 7, 1, 0, 5]])

In [89]: c = min_max(b)

In [90]: c
Out[90]:
array([[ 0.        ,  0.11111111,  0.55555556,  0.88888889,  1.        ],
       [ 1.        ,  0.77777778,  0.11111111,  0.        ,  0.55555556]])

In [91]: d = min_max(b, axis=1)

In [92]: d
Out[92]:
array([[ 0.        ,  0.11111111,  0.55555556,  0.88888889,  1.        ],
       [ 1.        ,  0.77777778,  0.11111111,  0.        ,  0.55555556]])

ベクトルなどの正規化

次は大きさを1にする正規化を紹介します。ベクトルの大きさをノルムといいますが、これを各要素に対して割っていくことでノルムを1にするものです。つまり、ノルムを求める手法さえわかれば簡単に実装できます。

NumPyにはノルムを求める便利な関数が実装されています。

np.linalg.norm関数です。

この関数は、引数さえ指定すれば、行列におけるノルムもいくつか求めることができます。今回は、要素の二乗和の平方根をノルムとして扱っていきます。

import numpy as np
def normalize(v, axis=-1, order=2):
    l2 = np.linalg.norm(v, ord = order, axis=axis, keepdims=True)
    l2[l2==0] = 1
    return v/l2

もし、3次元配列で2次元配列ごとに正規化したい場合は、axis=(1,2)のように正規化したい2次元配列の軸(axis)番号を2つ指定します。

実際のコードで使ってみましょう。

In [17]: a = np.array([1,2,3,2,1])

In [18]: b = normalize(a)

In [19]: b
Out[19]: array([ 0.22941573,  0.45883147,  0.6882472 ,  0.45883147,  0.22941573])

In [20]: (b*b).sum()
Out[20]: 0.99999999999999967

In [21]: c = np.random.randint(10, size=(3,4))

In [22]: c
Out[22]:
array([[7, 9, 9, 6],
       [4, 3, 4, 2],
       [8, 2, 2, 1]])


In [24]: d = normalize(c, axis=None) # 全ての要素において正規化

In [25]: d
Out[25]:
array([[ 0.3821709 ,  0.49136258,  0.49136258,  0.32757506],
       [ 0.21838337,  0.16378753,  0.21838337,  0.10919169],
       [ 0.43676674,  0.10919169,  0.10919169,  0.05459584]])

In [26]: (d*d).sum()
Out[26]: 1.0879576993800013

In [27]: e = normalize(c, axis=1)

In [28]: e
Out[28]:
array([[ 0.44539933,  0.57265629,  0.57265629,  0.38177086],
       [ 0.59628479,  0.4472136 ,  0.59628479,  0.2981424 ],
       [ 0.93632918,  0.23408229,  0.23408229,  0.11704115]])

In [29]: f = np.random.randint(10, size=(2,3,4))

In [30]: normalize(f, axis=(1,2))
Out[30]:
array([[[ 0.36412521,  0.41614309,  0.10403577,  0.26008943],
        [ 0.26008943,  0.46816098,  0.26008943,  0.10403577],
        [ 0.41614309,  0.10403577,  0.20807155,  0.41614309]],

       [[ 0.4176141 ,  0.4176141 ,  0.        ,  0.        ],
        [ 0.31321057,  0.26100881,  0.4176141 ,  0.        ],
        [ 0.46981586,  0.15660529,  0.46981586,  0.15660529]]])

こんな感じに使うことができます。

参考