PythonでベクトルをL2正規化(normalization)する方法一覧

Pythonを使ってベクトルをL2正規化（normalization）する方法が色々あるのでまとめます。
※L2正則化（regularization）= Ridgeではありません。

# L2正規化とは
ベクトル$x = (x_1,x_2,...,x_n)$に対応するL2正規化は以下のように定式化されます。

```math
x_{L2\,norm} = \frac{x}{||x||_2}
```

ここで、$||x||_2$は、以下の式で求まる$x$のL2ノルムです。

```math
||x||_2 = \sqrt{(\sum_i x_i^2)} = \sqrt{x_1^2 + x_2^2 + ... + x_n^2}
```

ベクトル$x$をL2正規化すると、長さが1のベクトルになります。

２次元空間で考えた場合、この操作は任意の２次元ベクトルを原点を中心とした半径1の円上へのベクトルに変換していると考えることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245628/0920eaf1-40df-6a66-1ada-52d0fbbc9b64.png)

３次元の場合は原点を中心とした半径1の球面上へのベクトルに変換します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245628/7b29802f-4a94-082b-d968-143048f0a7d0.png)

４次元以上はイメージが難しいですが、同じように原点を中心とする半径1の超球面上へのベクトルに変換すると考えれば良いと思います。

# pythonでの計算方法
上記のようなL2正規化ですが、pythonでは色々と計算方法があるようなので以下にまとめます。
*他にもあると思うので、見つけたら追記します。*

## 直接計算する
定義に従って計算すれば良いです。

```python
# 定義に従って計算
x = np.array([1,2,3,4,5])
x_l2_norm = sum(x**2)**0.5
x_l2_normalized = x / x_l2_norm
# 検算
np.linalg.norm(x_l2_normalized,ord=2)
# 1.0
```

## [`numpy.linalg.norm`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.norm.html)
`numpy.linalg.norm`を使って計算することも可能です。
こいつはベクトル`x`のL2ノルムを返すので、L2ノルムを求めた後に`x`を割ってあげる必要があります。

```python
# numpy.linalg.normを使う
x = np.array([1,2,3,4,5])
x_l2_norm = np.linalg.norm(x,ord=2)
x_l2_normalized = x / x_l2_norm
# 検算
np.linalg.norm(x_l2_normalized,ord=2)
# 1.0
```

## [`sklearn.preprocessing.normalize`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.normalize.html)
みんな大好きscikit-learnでも計算可能です。
２次元の場合は`axis`で軸の指定します。`axis=0`で列方向、`axis=1`(デフォルト)で行方向に正規化を行います。

```python
# sklearn.preprocessing.normalizeを使う
from sklearn.preprocessing import normalize
x = [[1,2,3,4,5],]
x_l2_normalized = normalize(x,norm='l2')
x_2d = [[1,2,3,4,5],[6,7,8,9,10]]
x_2d_l2_normalized = normalize(x_2d,norm='l2')
x_2d_l2_normalized2 = normalize(x_2d,norm='l2',axis=0)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[1,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[:,4],ord=2))
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 1.0
# 0.9999999999999999
```

## [`keras.backend.l2_normalize`](https://keras.io/backend/)
kerasにもあります。backendはtensorflowです。
(`tf.keras.backend.l2_normalize`というものもありました)

```python
# keras.backend.l2_normalizeを使う
from keras import backend as K
x = [1.,2.,3.,4.,5.]
x_2d = [[1.,2.,3.,4.,5.],[6.,7.,8.,9.,10.]]
x_l2_normalized = K.l2_normalize(x,axis=0)
x_2d_l2_normalized = K.l2_normalize(x_2d,axis=0)
x_2d_l2_normalized2 = K.l2_normalize(x_2d,axis=1)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    x_l2_normalized = sess.run(x_l2_normalized)
    x_2d_l2_normalized = sess.run(x_2d_l2_normalized)
    x_2d_l2_normalized2 = sess.run(x_2d_l2_normalized2)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,4],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[1,:],ord=2))
#1.0000001
#1.0
#0.99999994
#1.0
#0.99999994
#1.0
#1.0000001
#1.0
```

## [`tf.math.l2_normalize`](https://www.tensorflow.org/api_docs/python/tf/math/l2_normalize)

*※tensorflow 1.11までは`tf.nn.l2_normalize`として提供されていたようです。*

```python
import tensorflow as tf

x = tf.Variable([1,2,3,4,5], dtype=tf.float32)
x_2d = tf.Variable([[1,2,3,4,5],[6,7,8,9,10]], dtype=tf.float32)
x_l2_normalized = tf.math.l2_normalize(x,axis=0)
x_2d_l2_normalized = tf.math.l2_normalize(x_2d,axis=0)
x_2d_l2_normalized2 = tf.math.l2_normalize(x_2d,axis=1)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    x_l2_normalized = sess.run(x_l2_normalized)
    x_2d_l2_normalized = sess.run(x_2d_l2_normalized)
    x_2d_l2_normalized2 = sess.run(x_2d_l2_normalized2)
# 検算
print(np.linalg.norm(x_l2_normalized,ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,0],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,1],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,2],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,3],ord=2))
print(np.linalg.norm(x_2d_l2_normalized[:,4],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[0,:],ord=2))
print(np.linalg.norm(x_2d_l2_normalized2[1,:],ord=2))
#1.0000001
#1.0
#0.99999994
#1.0
#0.99999994
#1.0
#1.0000001
#1.0
```

# おわりに
いろいろありますねー。用途に合わせて使い分けていきましょー（？）
（pythonは変数のスコープどうなってんすか。。。）