カテゴリー
【シェルスクリプトで機械学習】Awkで機械学習で使える高速データ処理〜ガウス分布ノイズの生成方法
※ 当ページには【広告/PR】を含む場合があります。
2020/12/08
2022/09/30
近年では深層学習を用いたAIの発展を背景に、学習の決めてとなる訓練データを膨大かつ高速で処理しなければならないというニーズの高まりを感じます。
特にAwkはテキスト処理に特化した一種のスクリプト言語で、c言語のような記述でスクリプトが組めて、その処理もとても高速であり、昨今のAI熱を受けて再評価に兆しがあるようです。
ということで今回はAwkを中心にして人工知能の訓練などの下処理にも使えるテクニックを意識しながら、ガウス分布ノイズのデータセット生成の方法を解説します。
はじめに
線型回帰解析などで実験データがない場合や、自作の関数で何点かサンプルする場合に、通常はpythonならnumpyを使って
np.arange
ただ、かなり演算の実装が難しい特殊関数などはそれ相応の数値計算ライブラリを使わないと歯が立たないのは仕方ないことです。
ですが、大したことのない基礎的な数学関数だけしか使わないのに、
numpy
scipy
そんなことを思ってしまった人は、一度立ち止まってこう考えるべきかもしれません。
「...それはAwkで十分ではないのか?」
もしその答えがYesだったなら、shellを立ち上げてawkを打つだけで既に必要な開発環境を手に入れていることになっているでしょう。
まさにインストールレス。
ほとんどの環境で、即時使うことが可能です。
さて、以降の内容ではAwkを使ってシェルスクリプトでもダミー実験データを生成する方法をやってみます。
ちなみに、今回の内容の類似記事として
そちらの記事はawkだけでなくsedやsort、wgetなどの他のコマンドもバランス良く要所要所で使っています。
Awkで使える基礎的な数学関数
Awkが使える組み込みの関数は決して多くありません。
ただ三角関数やランダム関数が使えるので、発想次第でほとんどの陽関数なら自作できると思います。
乱数を使った簡単なサイコロの目を出すだけのプログラム例を以下に挙げておきます。
$ awk -v seed="${RANDOM}" '
function roll(n) { return 1 + int(rand() * n) }
BEGIN{
srand(seed);
print(roll(6))
}
'
#実行する度に違う目が出る
5
なお、このプログラムではroll関数内でrand関数を使っており、短期間には(内部時刻から参照された)同一のシード値が設定されます。
なので、何度実行しても同一の返り値が得られるようにも思えますが、rollを呼び出す直前に
srand(seed)
等間隔の配列を作成する
numpyでいうところの
np.arange
$ awk '
#start(下限)とstop(上限)の範囲をstep間隔に刻んだ配列を返す
function arange(start, stop, step) {
count = 0;
x = start;
while (x <= stop) {
varr[count] = x;
count++;
x = x + step;
}
}
BEGIN {
#例として0から6までを0.5間隔で刻んだ配列を計算
arange(0, 6, 0.5);
arr_len = length(varr);
for (i = 0; i < arr_len; i++) {
print i, varr[i];
}
}
'
#👇実行後
0 0
1 0.5
2 1
3 1.5
4 2
5 2.5
6 3
7 3.5
8 4
9 4.5
10 5
11 5.5
12 6
正規分布関数
ガウス分布とは以下のような関数を指します。
Eq. (1)
また確率変数$$x$$(1次元)が正規分布は$$N(\mu ,\sigma^2)$$と表現します。
さて、これをAwkで関数を定義し、計算できるように実装してみます。
$ awk '
#start(下限)とstop(上限)の範囲をstep間隔に刻んだ配列を返す
function gauss(val, mu, sigma) {
PI = 3.14159265359; #👈円周率は適当な精度で...
a_part = 1.0 / sqrt(2.0 * PI * sigma^2);
b_part = -1.0 * (val - mu)^2 / (2 * sigma^2);
return a_part * exp(b_part)
}
BEGIN {
print gauss(0, 0, 1)
}
'
#👇実行後
0.398942
(一点しか確認してませんが...)良さげな値が得られていますので、検証はひとまずおいておいて次の内容に進みます。
正規分布に基づく乱数(正規乱数)の生成
正規分布に基づく乱数、つまりはガウス分布ノイズ(=ホワイトノイズ)を作成するためには、単なる乱数から少し捻りを加えないといけません。
有名どころのアルゴリズムで、
とても簡潔なアルゴリズムですので、Awkでも難なく実装できると思います。
$ awk -v seed="${RANDOM}" '
function box_muller(mu, sigma, arr_len_) {
PI = 3.14159265359; #👈円周率は適当な精度で...
count = 0;
while(count < arr_len_) {
x1 = rand();
x2 = rand();
#👇cosかsinかどちらかを選択
tmp_rand_val = sqrt(-2 * log(x1)) * cos(2 * PI * x2);
# tmp_rand_val = sqrt(-2 * log(x1)) * sin(2 * PI * x2);
gauss_arr[count] = mu + sigma*tmp_rand_val;
count++;
}
}
BEGIN{
#👇シード値をリセット(Awkの場合には重要)
srand(seed);
#標準正規分布N(0,1)に従う乱数10点の配列を計算
box_muller(0, 1, 10);
arr_len = length(gauss_arr);
for (i = 0; i < arr_len; i++) {
print i, gauss_arr[i];
}
}
'
#実行
0 1.28016
1 -0.435245
2 -0.962477
3 -1.56287
4 0.710585
5 -0.512278
6 0.4468
7 -1.43556
8 0.0096908
9 1.02674
なおボックス=ミュラー法は標準正規分布$$N(0, 1)$$に従う乱数です。
今回では、線形変換により平均$$\mu$$、分散$$\sigma^2$$の正規分布$$N(\mu, \sigma^2)$$の分布に従う乱数を発生させるように拡張しています。
話を本題に戻すと、このコードが生成するを正規乱数は、ガウス分布ノイズと等価です。
言い換えるとノイズのとる値が正規分布に従えばガウス分布ノイズであることを確かめることができます。
ガウス分布ノイズの検証
あらかた材料は揃いましたので、最後に標準正規分布(理論値)と、正規乱数で100万回分サンプルした度数分布を正規化して重ねてみます。
まず$$N(0,1)$$分布の理論値を以下のスクリプトで生成します。
$ awk -v inp_mu=0.0 -v inp_sigma=1.0 '
function arange(start, stop, step) {
count = 0;
x = start;
while (x <= stop) {
varr[count] = x;
count++;
x = x + step;
}
}
function gauss(val, mu, sigma) {
PI = 3.14159265359;
a_part = 1.0 / sqrt(2.0 * PI * sigma^2);
b_part = -1.0 * (val - mu)^2 / (2 * sigma^2);
return a_part * exp(b_part)
}
BEGIN {
OFS=","
arange(-4, 4, 0.1);
arr_len = length(varr);
for (i = 0; i < arr_len; i++) {
print i, varr[i], gauss(varr[i], inp_mu, inp_sigma);
}
}
' | awk -F "," '
BEGIN {
OFS=",";
count = 0;
}
{
xarr[count] = $2;
yarr[count] = $3;
if(ymax<$3) ymax=$3;
count++;
}
END {
xarr_len = length(xarr);
for (i = 0; i < xarr_len; i++) {
print i, xarr[i], yarr[i] / ymax
}
}
' > norm_dist_strict.csv
さらに、$$N(0,1)$$の正規乱数を100万回繰り返して得られた度数分布のデータセットは以下のスクリプトで生成します。
$ awk -v seed="${RANDOM}" -v iter=1000000 -v inp_mu=0.0 -v inp_sigma=1.0 '
function box_muller(mu, sigma, arr_len_) {
PI = 3.14159265359;
count = 0;
while(count < arr_len_) {
x1 = rand();
x2 = rand();
tmp_rand_val = sqrt(-2 * log(x1)) * cos(2 * PI * x2);
gauss_arr[count] = mu + sigma * tmp_rand_val;
count++;
}
}
BEGIN{
srand(seed); #シード値をリセット
box_muller(inp_mu, inp_sigma, iter);
arr_len = length(gauss_arr);
for (i = 0; i < arr_len; i++) {
print gauss_arr[i];
}
}
' | awk -v binsize=0.2 '
BEGIN {
OFS=","
}
{
if(binsize <= 0) exit
if($1 < 0) {
frequency[int($1 / binsize) - 1] ++;
} else {
frequency[int($1 / binsize)] ++;
}
}
END {
for(i in frequency) {
print (i + 0.5) * binsize, frequency[i] | "sort -n";
}
}
' | awk -F "," '
BEGIN {
OFS=",";
count = 0;
}
{
xarr[count] = $1;
yarr[count] = $2;
if(ymax<$2) ymax=$2;
count++;
}
END {
xarr_len = length(xarr);
for (i = 0; i < xarr_len; i++) {
print i, xarr[i], yarr[i] / ymax
}
}
' > norm_dist_exp.csv
以上で、生成された理論式のデータセット(norm

関数形だけみるとほぼ一致しており、正規乱数から得られた値がガウス分布ノイズであることが確認できました。
まとめ
正規分布は機械学習においてもいろんな場面で顔を出す重要な関数の一つです。
数学的な理解を深めつつ、計算のアルゴリズムも確実に身につけておきたい内容だと思います。
今回の内容は見ての通りで、Awkを駆使することで正規分布のみならず色々な確率密度関数に基づいたデータセットが生成することも可能です。
特に機械学習のプログラムは様々な知識が入り乱れる複合テーマです。
データの下処理周りはシェルスクリプトにお任せし、解析はtensorflowなどの機械学習用ライブラリで、描画・視覚化はchart.jsなどのグラフィックユーティリティで、各作業を区分けして行うとそれぞれのブログラムの棲み分けができてプロジェクトの管理がしやすくなると思います。
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー