Kaggleに取り組んでいた時、大量のデータセットが与えられた。
一般的に、ニューラルネットに入力するデータは標準化している方がいいと言われているが、データが大量にあってメモリに乗り切らず、sklearn.preprocessing.StandardScalerが使えない。
一度に全ての平均値を計算するのではなく、メモリに乗る量のデータを入力し、逐次的に計算できるような(オンライン処理できるような)アルゴリズムがないか調べた所、Welfordのアルゴリズムというのがあった。
平均値の計算式を以下のように式変形する。
\begin{eqnarray}
\overline{x}_{n} &=& \frac{1}{n}\sum ^{n}_{i=1}x_{i} \\
\\
&=& \frac{(n-1)\overline{x}_{n-1}+{x}_{n}}{n} \\
\\
&=& \overline{x}_{n-1} + \frac{{x}_{n} – \overline{x}_{n-1}}{n}
\end{eqnarray}
新しいデータを加えて平均値を更新するためには、1つ前の平均値と新しい値さえあれば計算できることがわかる。これはO(1)の計算量で更新できそう。
同様に式変形をすれば、逐次更新に使う新しい値が1つでも複数個でも可能になる。
Pythonで書くと以下の通りシンプルになる。
mean += (x - mean)/k
標準偏差は分散の平方根で求められるので、分散を逐次更新で求める方法を考える。
分散は以下の計算式で求められる。2項目の平均値は上述の式で求められるので、1項目の二乗の平均値がわかればいい。
\begin{eqnarray}
{σ\;}^{ 2} = \overline{x^2} \;\; – \;\;\overline{x}^{\;\;2}
\end{eqnarray}
以上の考察をもとに、バッチ単位でデータを読み込み、逐次更新で平均値と標準偏差を求めていく。
今回は3つの特徴量の平均と分散を求める例を示す。
num_feature = 3
mean = np.zeros(num_feature, dtype=np.float64)
sum_of_square = np.zeros(num_feature, dtype=np.float64)
count = 0
for i in range(num_batch):
batch = read_batch(...)
count += len(batch)
new_val = batch_to_ndarray(...)
mean += np.sum(new_val - mean, axis=0) / count
sum_of_square += np.sum(new_val ** 2, axis=0)
var = (sum_of_square / count) - mean ** 2
std = np.sqrt(var)
print(mean)
print(std)
fp32で収まらない値になるかもしれないので、fp64で値を保存する配列を初期化。
countでデータの個数を保存しつつ、上述の数式に従って値を更新していく。
read_batch()でメモリに乗る分だけデータを読み込み、batch_to_ndarray()でnumpy形式の配列に変更する。
ここは各自の関数を書く。返り値の次元数は(batch_size, num_feature)になればOK。
numpyで処理すればforループを無くせるので高速。TensorFlowで書き換えればGPUでの処理も可能。
結果はnp.saveなりpd.DataFrameなりにして保存し忘れないように注意。
これで、メモリの乗り切らない大量のデータから平均値と標準偏差を求めることができ、ニューラルネットに入力するデータの前処理を行うことができた。
コメント