機械学習(1) 超球面上にランダムに点を打つ

まずは、二値分類のプログラムを書こうと思います。

そのために、ランダムに超平面を作ります。しかし、超平面をランダムに作ろうとすれば、法線ベクトルをランダムに作る必要があります。すなわち、これは超球面上にランダムに点を打つことになります。

最も簡単な方法は、xi ∊ [-1, 1]を生成し、超球面上に原点方向へ射影することです。しかし、これはダメです。二次元で考えても、x軸方向より(1, 1)方向からに射影される点のほうが多いです。

正確で簡単な方法は、同様に超立方体上に点を打ち、それがその超立方体に入る超球内なら超球面上に射影する方法です。しかし、これは次元が高くなると非現実的になります。

wikipedia:超球の体積

これによると、n次元で超立法体に占める超球の割合は、

 \frac{(\frac{\pi}{4})^{\frac{n}{2}}}{\Gamma(\frac{n}{2}+1)}

小さな次元で割合を具体的に計算すると、

  2     3     4     5     6     7     8     9
0.079 0.524 0.308 0.164 0.081 0.037 0.016 0.006

このように、次元が高くなると急速に小さくなります。

検索してみると、次のような方法が知られていることがわかりました。

wikipedia:超球面

各座標成分を正規分布でランダムに出して、それを球面上に射影すればよいと。なるほど。

正規分布に従う乱数にはBox-Mullerを使う。

wikipedia:乱数列

これで、1000次元でも高速に超球面上にランダムに点を打つことができますね。

from math import sqrt, log, cos, pi
import random

def Box_Muller():
    r1 = random.random()
    r2 = random.random()
    return sqrt(-2.0 * log(r1)) * cos(2.0 * pi * r2)

def random_hyper_sphere(D):
    xs = [ Box_Muller() for _ in range(D) ]
    r = sqrt(sum(x * x for x in xs))
    return [ x / r for x in xs ]

D = 6
N = 20
format = "%10.6f" * D
for k in range(N):
    xs = random_hyper_sphere(D)
    print format % tuple(xs)