技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

マジで駄文。ボウリングのスコア計算

ボウリングのスコア計算ってどういうルールなんだろう?
と思って調べてみた。

スコアの付け方

なるほどそういうルールだったのか…興味ないから今まで知らんかった(汗

  • ストライク = 10 点+次の 2 投分
  • スペア = 10 点 + 次の 1 投分

ストライクは次の 2 投分 + 10 するということは、「ストライク → 1, 3」となったとき、10+1+3, 1, 3 という計算になるのだろう。
仮に9 フレーム目まで All Strikeで、10フレーム目で「1, 0」だったと仮定すると

9 フレーム目にフィードバックで 11, 8 フレーム目= 10 + 10(9フレーム目) + 1(10フレーム目) みたいな計算になるはず。
てことは、実質最後から計算した方が計算しやすいのかもしれない。

全フレームストライクと仮定すると、最初の 2 投まで1投目のスコアはつかず、3投目決まった時点で 30 点になる。
で、その後もストライクが続くと 1 フレームあたりマックススコア 30 が 10 フレーム続くので、 300 点マックスと。

やっぱりこれは後ろから計算した方が楽そうだ。

python にゃ配列の処理が豊富なので、色々楽にできる。

# 各フレームのスコアを列挙してみた
score = [
    [9, 1],
    [8, 2],
    [10],
    [5, 0],
    [3, 6],
    [4, 2],
    [7, 3],
    [6, 3],
    [10],
    [9, 1, 9]
]
score.reverse()
score

[[9, 1, 9], [10], [6, 3], [7, 3], [4, 2], [3, 6], [5, 0], [10], [8, 2], [9, 1]]

逆順ソートできるのでイイネ
そしたら 10 フレーム目だけ単純 sum して残りをスライスでループしたらいい

total=0
next1, next2 = 0, 0

# 最終フレームは特別扱い
total += sum(score[0])
if len(score[0]) == 3:
    next1, next2, _ = score[0]
else:
    next1, next2 = score[0]
print(f'Total: {total}')

# 他のフレームを計算
for frame in score[1:]:
    print(f'frame {frame}')
    frame_sum = sum(frame)
    if len(frame) == 1:
        print(f'strike! 10 + {next1} + {next2}')
        total += 10 + next1 + next2
        next1, next2 = 10, next1
    elif frame_sum == 10:
        print(f'spere! 10 + {next1}')
        total += 10 + next1
        next1, next2 = frame
    else:
        print(f'append {frame_sum}')
        total += frame_sum
        next1, next2 = frame
    print(f'Total: {total}')

total

結果: イインジャネ?

Total: 19
frame [10]
strike! 10 + 9 + 1
Total: 39
frame [6, 3]
append 9
Total: 48
frame [7, 3]
spere! 10 + 6
Total: 64
frame [4, 2]
append 6
Total: 70
frame [3, 6]
append 9
Total: 79
frame [5, 0]
append 5
Total: 84
frame [10]
strike! 10 + 5 + 0
Total: 99
frame [8, 2]
spere! 10 + 10
Total: 119
frame [9, 1]
spere! 10 + 8
Total: 137
137

WSL に Salesforce 開発環境を作る

「汚さず」というのはWindowsのPATHとか常駐とか増やしたくないのです。
で、どういう事かというと WSL2 に Ubuntu 突っ込んで、そこに sfdx をセットアップするという方向。

なぜセットアップかって?メインマシンに SFDX 入れてなかったからさ!

WSL2 インスコ

まずは、Windows subsystem for linux をインストールしよう。

docs.microsoft.com

やるならWSL2からだね。
当たり前だけど窓 10 限定。

  1. Powershell を管理者で起動して
    f:id:white-azalea:20210407201754p:plain
  2. Powershell脳死dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart と突っ込む。
    f:id:white-azalea:20210407201912p:plain
  3. 次のコマンドで dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart を叩く。
    f:id:white-azalea:20210407202148p:plain
  4. カーネルパッチを当てよう
    Windows Subsystem for Linux (WSL) を Windows 10 にインストールする | Microsoft Docs
  5. 最後にPowerShellwsl --set-default-version 2 を叩けばおk

因みに、最後のコマンド実行時に BIOS の仮想化が Off になってる場合は指摘されます。
BIOS の設定はマシンによるので、環境ごとにやって下さいな。

尚、ASUSIntel マザボの場合は、「詳細」「CPU」の中に 「Intel VT」があるのでこいつを有効にしよう。

Ubuntu のインストール…っても Windows ストアで「Ubuntu」って突っ込めば手に入ります。

f:id:white-azalea:20210407203444p:plain

初回起動時にアカウントとパスワードを登録する。
因みに、初回は以下をお忘れなく

$ sudo apt update
$ sudo apt upgrade -y

nodejs と sfdx インストール

なぜ nodejs からだって?sfdx単体でなんて入れたくないからだ

  1. sudo apt install nodejs npm -y でとりあえず二つをインストール。
  2. sudo npm install n -gn というコマンドをインストールする。こいつはパッケージ管理ツールみたいなもんだ。
  3. sudo n stable 安定版 nodejs いれませう
$ node --version
v14.16.1
  1. で、インストール後に apt で入れた node は消しとく。 sudo apt purge -y nodejs npm
  2. npm コマンドをアップデートしよう npm update npm
  3. sfdx をインストールして完了。 sudo npm install sfdx-cli --global
続きを読む

画像を行列で処理する前準備

CNN で特徴的な動作に畳み込みっちゅう処理がある。

画像には局所性(隣接するピクセルの影響を受ける事)があるので、それを利用して画像の特徴を強調したりできる。
やってることはフィルタを用意して、画像に畳み込み計算を行う事。

以下の例みたいに、左上からフィルタを適用して 1 セル生成。したら、右に 1 セルずらして…と繰り返す。


    \left(
        \begin{array}{cc}
            1 & 0 \\
            0 & 1 \\
        \end{array}
    \right)
    \times
    \left(
        \begin{array}{cccc}
            2 & 2 & 1 & 2 \\
            1 & 1 & 2 & 1 \\
            1 & 2 & 1 & 0 \\
            1 & 0 & 0 & 1 \\
        \end{array}
    \right)
    \longrightarrow
    \left(
        \begin{array}{ccc}
            3 & 4 & 2 \\
            3 & 2 & 2 \\
            1 & 2 & 2 \\
        \end{array}
    \right)

見ての通り画像自体は小さくなる。

因みにこのずらす間隔をストライドと呼ぶ。
ストライド幅が大きいほど画像サイズは小さくなり、特徴が見逃されやすくなる。

で、この計算するとしたときに普通の画像に対して処理するのがしんどいなんで整形するのが一般的なのだそうだ。

im2col

画像に対して畳み込みを行うには形状がめんどくさいので以下みたいに配置しなおす。
これを im2col と呼ぶっぽい。


\left(
    \begin{array}{cc}
        2 & 2 \\
        1 & 1 \\
    \end{array}
\right)
\longrightarrow
\left(
    \begin{array}{c}
        2 \\
        2 \\
        1 \\
        1 \\
    \end{array}
\right)

例えば 2x2 のフィルタで 3x2 のデータに適用するとき、まずは im2col でこんな風にしてしまう


\left(
    \begin{array}{ccc}
        2 & 2 & 3 \\
        1 & 1 & 2 \\
    \end{array}
\right)
\longrightarrow
\left(
    \begin{array}{cc}
        2 & 2 \\
        2 & 3 \\
        1 & 1 \\
        1 & 2 \\
    \end{array}
\right)

こう変換すれば、フィルタをドット積で一括計算できる。

で、実際に変換と逆変換をやってみた。

続きを読む

過学習対策あれこれ

white-azalea.hatenablog.jp

前回わざと過学習させてみたわけだが、そもそも学習の打ち切りはやたらと難しいハズだ。
あの時はデータセットが 200 位しかないし、ニューロンの数も限られていたといえる。
しかし、現実には大量のデータセットがあるだろうし、学習時間はそこそこかかるだろうと思われる。

例えば1回の学習だけで数時間かかるとする。
それを見て、じゃぁどの位で打ち切るか?2週目開始までにどれだけ待つ必要があるだろう?
要するに学習の打ち切りを判断するのはちょとどころでは難しいと予測できる。

ということで、今回はやり方を変えてみる。
まずは前回の誤差を見てみよう。

Epoch: 0 / 1000, Err_train: 1.1043634751935245, Err_test: 1.0623824378086812
Epoch: 100 / 1000, Err_train: 0.04227735559583767, Err_test: 0.13396243782658682
Epoch: 200 / 1000, Err_train: 0.018747407955545834, Err_test: 0.12865441586002588
Epoch: 300 / 1000, Err_train: 0.004991569690202153, Err_test: 0.218183645593179
Epoch: 400 / 1000, Err_train: 0.002959841289188, Err_test: 0.25887483170314346
Epoch: 500 / 1000, Err_train: 0.001493562533847086, Err_test: 0.3049230574592406
Epoch: 600 / 1000, Err_train: 0.0010474962034239849, Err_test: 0.3333645685971381
Epoch: 700 / 1000, Err_train: 0.0007757803305818819, Err_test: 0.3506614397003805
Epoch: 800 / 1000, Err_train: 0.0006128411251375161, Err_test: 0.36719589756963056
Epoch: 900 / 1000, Err_train: 0.0004995183637277335, Err_test: 0.37831813699595335

f:id:white-azalea:20210318205457p:plain

最終的に過学習のせいで 0.4 目指してエラーが広がっている。

モノは試しに RMSProp を導入してみた。
式は過去記事のものを利用。

white-azalea.hatenablog.jp

roh = 0.95

class RMSNeuron(Neuron):
    def __init__(self, n_upper, n, activation_function, differential_function):
        super().__init__(n_upper, n, activation_function, differential_function)
        self.h_w = np.zeros((n_upper, n)) + 1e-8
        self.h_b = np.zeros((n)) + 1e-8

    def update(self):
        # ここだけ更新
        self.h_w = (self.h_w * roh) + (1 - roh) * (self.grad_w * self.grad_w)
        self.h_b = (self.h_b * roh) + (1 - roh) * (self.grad_b * self.grad_b)
        self.w -= eta / np.sqrt(self.h_w) * self.grad_w
        self.b -= eta / np.sqrt(self.h_b) * self.grad_b


class Output(RMSNeuron):
    pass


class Middle(RMSNeuron):
    def backword(self, grad_y):
        delta = self.differential_function(grad_y, self.y)
        self.grad_w = self.x.T.dot(delta)
        self.grad_b = delta.sum(axis = 0)
        self.grad_x = delta.dot(self.w.T)
        return self.grad_x


# ニューロン初期化
mid_layer1 = Middle(n_in, n_mid, relu_func, relu_func_dash)
mid_layer2 = Middle(n_mid, n_mid, relu_func, relu_func_dash)
outputs = Output(n_mid, n_out, soft_max, soft_max_dash)

exec()

結果が

Epoch: 0 / 1000, Err_train: 1.1000068284857787, Err_test: 1.105102282860116
Epoch: 100 / 1000, Err_train: 0.0004723302009342129, Err_test: 0.25285731779944576
Epoch: 200 / 1000, Err_train: 2.435419747198503e-05, Err_test: 0.41570733582516095
Epoch: 300 / 1000, Err_train: 1.6498331127020632e-07, Err_test: 0.42415464268234687
Epoch: 400 / 1000, Err_train: 3.2775216891847647e-06, Err_test: 0.424167001320326
Epoch: 500 / 1000, Err_train: 1.7252434605417651e-06, Err_test: 0.42416032411453786
Epoch: 600 / 1000, Err_train: -9.588821783186093e-08, Err_test: 0.42416031798981263
Epoch: 700 / 1000, Err_train: 1.9298299962666904e-07, Err_test: 0.424160335489329
Epoch: 800 / 1000, Err_train: -2.7639660886741467e-08, Err_test: 0.42416031479869326
Epoch: 900 / 1000, Err_train: -9.780188673424984e-08, Err_test: 0.4241603145452869

f:id:white-azalea:20210318205756p:plain

ちょwwwww
過学習広がったしwwww

アカンw これ学習係数は一方通行で減らさないとダメだコレw

初期段階で一気に学習進めると、一時的に学習係数に圧がかかって、緩やかになるのだけど、それ以降で物忘れによって学習係数が跳ね上がる。 そこに過学習が入ってヒャッハーされたことが見て取れる。

続きを読む

多層ニューラルネットで過学習させてみる

実際過学習がどの程度影響するのかを検証してみる。
ニューロンのひな型は このへん で作ったものを流用。

import numpy as np

wb_width = 0.1  # 重みとバイアスの広がり方
eta = 0.01      # 学習係数

class Neuron:
    def __init__(self, n_upper, n, activation_function, differential_function):
        self.w = wb_width * np.random.randn(n_upper, n) # ランダムなのが確立勾配法
        self.b = wb_width * np.random.randn(n)
        self.grad_w = np.zeros((n_upper, n))
        self.grad_b = np.zeros((n))
        self.activation_function = activation_function
        self.differential_function = differential_function

    def update(self):
        self.w -= eta * self.grad_w
        self.b -= eta * self.grad_b
    
    def forward(self, x):
        self.x = x
        u = x.dot(self.w) + self.b
        self.y = self.activation_function(u)
        return self.y

    def backword(self, t):
        delta = self.differential_function(self.y, t)
        self.grad_w = self.x.T.dot(delta)
        self.grad_b = np.sum(delta, axis=0)
        self.grad_x = delta.dot(self.w.T)
        return self.grad_x
    
    def show(self):
        print(json.dumps(self.w.tolist()))
        print(json.dumps(self.b.tolist()))


class Output(Neuron):
    pass


class Middle(Neuron):
    def backword(self, grad_y):
        delta = self.differential_function(grad_y, self.y)
        self.grad_w = self.x.T.dot(delta)
        self.grad_b = delta.sum(axis = 0)
        self.grad_x = delta.dot(self.w.T)
        return self.grad_x

活性関数は ReLU で、出力層にはソフトマックス関数を利用。
損失関数には交差エントロピーを使う。(最適化には確率的勾配降下法

前回は2層(中間層8+出力層3)で過学習らしい話はなかったので、今回はわざと中間層ニューロンを 25 個とかにする。

def relu_func(x):
    return np.where(x <= 0, 0, x)

def relu_func_dash(y, t):
    return y * np.where(t <= 0, 0, 1)

def soft_max(u):
    return np.exp(u) / np.sum(np.exp(u), axis=1, keepdims = True)

def soft_max_dash(y, t):
    return y - t

n_in  = 4
n_mid = 25
n_out = 3
mid_layer1 = Middle(n_in, n_mid, relu_func, relu_func_dash)
mid_layer2 = Middle(n_mid, n_mid, relu_func, relu_func_dash)
outputs = Output(n_mid, n_out, soft_max, soft_max_dash)

def forward_propagation(x):
    return outputs.forward(mid_layer2.forward(mid_layer1.forward(x)))


def back_propagation(t):
    mid_layer1.backword(mid_layer2.backword(outputs.backword(t)))


def update():
    mid_layer1.update()
    mid_layer2.update()
    outputs.update()


def get_err(result, t, batch_size):
    """交差エントロピー誤差"""
    return -np.sum(t * np.log(result + 1e-7)) / batch_size
続きを読む

ニューラルネットの弱点

過学習

現在のデータセットに過度に最適化されすぎて、未知のデータに対処できなくなる問題。
これはニューラルネットに限った話でもない。

同一のデータセットを学習させ続けることでも起きるが、ニューラルネットの場合以下のケースがある。

  1. 少ないデータを学習に使い続ける
    ニューロンの学習は1回で決まるものでもないので、エポック(データセット1週分)内の確率的というか平均というか…そんな特徴を捉えていく。
    データ数が少ない場合、特徴を取り違える可能性がある。
  2. ニューロン数を増やしすぎる
    データセットにピッタリ合いすぎて、未知のデータに対応できなくなる。
    ニューロン数が増える=表現力が上がる…が上がった表現力が過剰にマッチする状態。
  3. 階層を増やしすぎる
    これも同じようなもの。

対策

  1. ハイパーパラメータ(ニューロン数や学習係数なんかの定数のこと)を最適化していく。
    まぁ要するにやりすぎになってそうならニューロン数とか減らす。
  2. 学習の打ち切り
    学習しすぎる前に処理を打ち切る。
    検証用データセットの誤差を監視して、適当なところで学習を打ち切る。
  3. 十分なデータを用意しよう。
    いや、これが地味にきついんだけどね…。
  4. データ拡張
    データの水増し。例えば画像データなら、回転や反転した画像を使ってみるとか。
  5. データ前処理
    データを 0-1 に抑える(正規化)。統計値を使って標準偏差 1 になるように調整する(標準化)
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline


def normalize(x):
    """正規化"""
    xmax = np.max(x)
    xmin = np.min(x)
    return (x - xmin) / (xmax - xmin)


def standardize(x):
    """標準化"""
    av = np.average(x)
    std = np.std(x)
    return (x - av) / std

局所最適解

局所的な最適解にハマって学習が停止し、全体最適解にたどり着けなくなる問題。

対策

  1. 最適化アルゴリズムで回避する。
    記事は このへん
  2. 正則化する。
    重みの値に制限を与える。(最大値/最小値など)
    最大値制限(※1)とか制限したり重みを減衰させる場合に(※2)などする。
  3. 重みやバイアスの初期値をランダムにしとく。

   \sum_i w_{ij}^2 < c   ...(※1)

   E_w = E + \frac{\lambda}{2} || W ||^2   ...(※2)

勾配喪失

3層以上でやってると、表層への伝播が弱くなりすぎてしまう問題。
微分を繰り返すので、それによって値が小さくなり、最終的にほとんど学習できなくなってしまう。

例えばシグモイド関数だと  f'(x) = (1-f(x))f(x) で MAX 0.25 そりゃ勾配少ないわけだ。

def sigmoid(u):
    return 1 / (1 + np.exp(-u))


def fdash(y):
    d = sigmoid(y)
    return (1 - d) * d


X = np.linspace(-5, 5)
Y = fdash(X)

plt.plot(X, Y)
plt.show()

f:id:white-azalea:20210315225425p:plain

対策

なんで、ReLU とかを使うらしい。