numpy ndarray の行方向正規化

行列の行(列)方向の正規化は機械学習アルゴリズムでよく使う操作である。

実装方法はいろいろあると思うが、私が良く使うやり方は以下。以下は行方向について。

numpy だけでやる方法

import numpy as np

A = np.random.rand(N, N).astype('float64')

# 行列の行方向の L1 ノルムをとる。ord=2 とすると L2 ノルム。
row_norm = np.linalg.norm(A, ord=1, axis=1)

# の各行を除算して正規化する
A = A / row_norm[:, np.newaxis]

最後の除算は row_norm の shape(3,) に対して長さ1の次元を加えて (3,1) にしている。 これで行列Aに対してブロードキャストできるようになる。

scikit-learn を使う方法

こっちのほうが簡潔。

from sklearn.preprocessing import normalize

A = normalize(A, norm='l1', axis=1, copy=True)

キーワード引数 norm は、'l1' で L1 ノルム、l2 で L2ノルムとなる。 copy引数 は True で新たにメモリ確保する。デフォルト True なので必要なければ False にしたほうが速い。

APIリファレンス sklearn.preprocessing.normalize — scikit-learn 0.20.2 documentation

速度比較

numpy.linalg.norm と sklearn.preprocessing.normalize の copy 有り無しの3つで比較した。

行列サイズは、100 x 100 〜 5000 x 5000 を 100 刻みで5回ずつやった結果がこちら。 横軸が行列の一辺のサイズ、縦軸が実行時間。Python の timeit で計測した。行列 A のアロケートは実行時間に含んでいない。

予想どおり sklearn の copy=True (緑)が最も遅くて、中間表現がある numpy(赤)が次に遅くて、 最も速かったのは sklearn の copy=False(青)。 まぁそうですよねという感じ。

float64 で 4000x4000 のときに急激に悪化するのはどっかのキャッシュかなんかの最大値がこのへんにあたってるのだろうか…

ちなみにマシンスペックはMBP2017で、i7 (2.5 GHz), RAM 16GB, l1, l2, l3 キャッシュは 32KB, 256KB, 4MB。

あと、行列のサイズが小さいときのコピー有り sklearn の挙動が特徴的。

とりあえず、中間表現不要でとくにこだわりなければ sklearn の copy=False でやればよいということはわかった。

以上です。