機械学習_基礎と課題

特徴抽出

次元削減の一種で、分類に必要な特徴量を抽出することが可能であり、特徴はベクトルとして表現される.

特徴選択

学習に使用される説明変数を選択することを特徴選択という.

フィルタ法

 相関係数で重要が高い特徴量を選択する.

ラッパー法

 すべての説明変数を使って線形回帰を学習し、各変数の重要度を算出して、重要度が小さい説明変数を除いて、線形回帰を繰り返して学習して特徴量を選択する.

埋め込み法

 線形回帰の損失関数にL1ノルム正則項を入れたアルゴリズムで学習して特徴量を選択する.

性能指標

誤差関数

  • 二乗和誤差関数: L = -\frac{1}{2}\sum_{x=1}^{N} (p(x) - q(x))^2 

 その関数の微分 \frac{\partial L}{\partial y_k}  = {y_k}-{t_k}

 その関数の微分 \frac{\partial L}{\partial y_k} = - \frac{t_k}{y_k}

活性化関数

  • Sigmoid関数

 sigmoid(x) = \frac{1}{1 + \exp(-x)}

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

その関数の微分
 f(x)' = (1 - f(x)) \cdot f(x)

class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx
  • Softmax関数


Softmax関数は、入力ベクトルの要素間の差にのみ依存する関数である.
オーバーフロー対策のため、 softmax(z) = softmax(z- max z_i)
その関数の微分

  \frac{\partial L}{\partial z_k}  = y_k ( \frac{\partial L}{\partial y_k} - \sum \frac{\partial L}{\partial y_i} y_i )

class Softmax:
     def __init__(self):
          self.params, self.grads = [], []
          self.out = None

     def forward(self, x):
          self.out = softmax(x)
          return self.out

     def backward(self, out):
         dx = self.out*out 
         sumdx = np.sum(dx, axis=1, keepdim=True)
         dx -= self.out*sumdx
         return dx
    
     def softmax(x):
         if x.ndim == 2:
             x = x - x.max(axis=1, keepdims=True)
             x = np.exp(x)
             x /= x.sum(axis=1, keepdims=True)
         elif x.dim == 1:
             x = x - np.max(x)
             x= np.exp(x) / np.sum(np.exp(x))
         return x

混同行列

混同行列 予測Positive 予測Negative
正解_Positive TP個 FN個
正解_Negative FP個 TN個

Precision

Positiveと予測したもので実際Positiveであった割合.
Precision= TP/(TP+FP) 

Recall

実際Positiveであるもの中で、どれぐらいPositiveが予測されるかという割合.
Recall = TP/(TP+FN)

F-measure

(2*Recall*Precision)/ (Recall + Precision)
物体検知アルゴリズムにおいて「任意のPredictied BBに検出されないGround-Truth BB」は、False Negativeである.

線形回帰

ロジスティック回帰

バッチ学習

①ミニバッチ学習との違い

  • バッチ学習では、パラメータ初期値を決定的にもしくは確率的に定めた後、同じデータを繰り返しモデルに投入し学習させる.-> 目的関数はわからない.
  • ミニバッチ学習では、エポック毎にミニバッチ内のデータを変更するため、イテレーションのたびに異なる目的関数を最適化する.
  • >目的関数がイテレーションのたびに変わる.局所的な最小解にトラップされた場合でも、次のイテレーションで異なる目的関数の最適化のため、局所的最小解から抜け出せる可能性がある.

そのため、ミニバッチ学習は局所的最小解を必ず脱するとは限らないが、バッチ学習は大局的最小解に辿り着いくは限らない。
ミニバッチ学習やバッチ学習では、重みの更新が安定するため、比較的に大きな学習係数を設定できる.
→バッチサイズが小さいミニバッチ学習よりも比較的短い時間で学習を収束させる.

過学習

  • 決定木のモデルが過学習の場合、木の深さを浅くする.
  • 未学習の場合、層数を増やす.

(注意:特徴量を標準化しても、過学習に改善しないからね)

ハイパーパラメータの探索

パラメータの最適化を行う前にハイパーパラメータを決めなければならない変数である.(NNの層数や正則化のパラメータ)

  • グリッドサーチ:ハイパラ毎に候補点設定、そのすべての組み合わせを探索する.
  • ランダムサーチ:グリッドサーチより少ない探索回数でよい性能を示す点を見つかる.
  • ベイズ最適化:

予測モデル(ガウス過程回帰)を構築し、獲得関数を用いて次の探索点を選択する.
なるべく少ない回数でより良い解を見つけたい場合に向いている手法で、ガウス過程回帰はベイズ推論手法であるため、ベイズ最適化と呼ばれる.
実験条件の探索として使われる.

パラメータの最適化

分かりやすい動画:GitHub - Jaewan-Yun/optimizer-visualization: Visualize Tensorflow's optimizers.

線形回帰のパラメータ最適化が凸最適化問題と考えられる場合、適切な条件で勾配法を用いれば、目的関数が最小になるパラメータを見つかること.(目的関数がパラメータによって微分可能であること)

確率的勾配降下法(SGD)

特徴:無作為に選びだされたデータ を用いて、目的関数が最小となるパラメータを勾配法によって探すことをSGDという.
課題:SGDの非効率な探索経路による.それは勾配の方向が本来の最小値ではない方向を指しているため.(それを解決するため、Momentumの導入)
パラメータθの更新式:  {\mathbf{w}  \gets  \mathbf{w} - \eta \frac{\partial L}{\partial \mathbf{w}}}
サンプルコード:

param[key] -= self.lr * grad[key] #lr: 学習率     

例題:

X Y
-1.0 0.0
0 0.5
2.0 1.5

二乗和誤差: E = \sum_i (y_i - (ax_i + b))^2

1回目更新量:
 -\eta \frac {\partial L} {\partial a} =0.1(y_i-x_i)x_i = 0.1\times(-1.0) = -0.1
 -\eta \frac {\partial L} {\partial b} =0.1(y_i-x_i)     = 0.1\times(1.0) = 0.1

(a, b) = (1.0 , 0 )  #初期値
lr = 0.1        # 学習率 learning rate
(x, y) = (-1.0, 0.0)    
dL_da = lambda a, b: a * x * x + b * x - x * y
dL_db = lambda a, b: b + a * x - y
# (a, b)を1回更新する更新量を(da, db)とする
da = -lr * dL_da(a, b)
db = -lr * dL_db(a, b)
print(da, db)# 答えを表示

2回目更新量:
 -\eta \frac {\partial L} {\partial a} =0.1(y_i-(0.9x_i+0.1))x_i = 0.1\times(0.0) = 0.0
 -\eta \frac {\partial L} {\partial b} =0.1(y_i-(0.9x_i+0.1))     = 0.1\times(0.4) = 0.01

(a, b) = (-0.1 , 0.1 )  #初期値
lr = 0.1    # 学習率 learning rate
(x, y) = (0.0, 0.5)
# 問題文における偏微分の定義で n = 1 とする
dL_da = lambda a, b: a * x * x + b * x - x * y
dL_db = lambda a, b: b + a * x - y
# (a, b)を1回更新する更新量を(da, db)とする
da = -lr * dL_da(a, b)
db = -lr * dL_db(a, b)
print(da, db)# 答えを表示

Momentum

特徴:大域的な最適解を解く.学習率の値が重要である小さすぎると学習時間が長い、大きいと発散する.
大きく学習して、どんとん小さく学習する(学習係数を徐々に下げていく).

パラメータθの更新式:
  {\mathbf{v}  \gets  \alpha \mathbf{v} - \eta \frac{\partial L}{\partial \mathbf{w}}}
  {\mathbf{w}  \gets  \mathbf{w}  + \mathbf{v}}
or
  \theta_{t+1}  \gets  \theta_{t}-\eta \nabla_{\theta_t}L(\theta_t)+\alpha(\theta_t-\theta_{t-1})
慣性項は直前のパラメータの更新量に比例する項であるため、 \alpha(\theta_t-\theta_{t-1})

サンプルコード:

self.v[key] = self.momentum[key] * self.v[key] - self.lr * grads[key] #lr: 学習率
param[key] += self.v[key]     

モメンタムありのとき2回目のパラメータの更新量
 -\eta \frac {\partial L} {\partial a} + \alpha(\theta_t - \theta_{t-1})= 0.0+0.9\times(-0.1) = -0.09
 -\eta \frac {\partial L} {\partial a} + \alpha(\theta_t - \theta_{t-1})= 0.04+0.9\times(0.1) = 0.13
サンプルコード:

(a, b) = (-0.1 * 0.9, 0.1 * 0.9)  #「前回の更新量」×「慣性項のパラメータ」
lr = 0.1    # 学習率 learning rate
(x, y) = (0.0, 0.5)
# 問題文における偏微分の定義で n = 1 とする
dL_da = lambda a, b: a * x * x + b * x - x * y
dL_db = lambda a, b: b + a * x - y
# (a, b)を1回更新する更新量を(da, db)とする
da = -lr * dL_da(a, b) + a
db = -lr * dL_db(a, b) + b

Nesterov

パラメータθの更新式: 
 v_{t+1} =  \alpha v_t - \eta \frac{\partial L}{\partial(\eta_t + \alpha v_t)}
 \theta_{t+1} = \theta_t + \alpha v_{t+1}
すなわち  \Theta_{t+1} = \Theta_t + \alpha^2 v_t - (1+ \alpha)\eta\frac{\partial L}{\partial\Theta_t}
サンプルコード

self.v[key] *= self.momentum
self.v[key]-=self.lr * grads[key]
param[key] += self.momentum* self.momentum* self.v[key]
param[key] -= (1+self.momentum) * self.lr * grads[key]    

AdaGrad

特徴:AdaGradは各パラメータの要素ごとに学習率を調整して学習する.各要素の学習率を決めるため変数hとする.
   勾配の絶対値の大きい要素に更新量を小さくし、勾配の絶対値の小さい要素に更新量を大きくすることで、学習率を調整する.
課題:AdaGradの更新量が0に近づく、パラメータは更新しない.(それを解決するため、RMSpropが開発された)

パラメータθの更新式:
 h \gets h +  (\frac {\partial L} {\partial \mathbf{w}})^{2}
  \mathbf{w}  \gets  \mathbf{w} -  \eta \frac{1}{\sqrt{h}} \frac{\partial L}{\partial \mathbf{w}}

サンプルコード:

for key in params.keys():
    self.h[key]   += grads[key] * grads[key]
    params[key]  -= self.lr * (1/np.sqrt(self.h[key]+1e-7)) * grads[key]  

AdaDelta

ニュートン法を勾配降下法で近似することでロバストな学習率
単位合わせた

RMSProp

特徴:過去のすべての勾配を均一に加算しない.過去の勾配を徐々に忘れて、新しい勾配情報を大きく反映されるように加算する(指数移動平均

  • AdaGradに加えて、時間が経過するほど更新幅が小さくなるようにdecayを用いて学習率が計算される.
  • grads[key]の要素が大きい場合、パラメータ更新幅が小さくなる.
  • grads[key]が0に近い場合、更新回数が多くなるほど、パラメータ更新幅は大きくなる.
  • self.decay_rateは0から1の値を取る。

パラメータθの更新式:
  \mathbf{h}  \gets  \mathbf{h} * decay
  \mathbf{h}  \gets  \mathbf{h}  +  (1-decay) \frac{\partial L}{\partial \mathbf{w}} \frac{\partial L}{\partial \mathbf{w}}
  \mathbf{w}  \gets  \mathbf{w}  -   \eta \frac{1}{\sqrt{h}} \frac{\partial L}{\partial \mathbf{w}}

サンプルコード

self.h[key] *= self.decay_rate
self.h[key] += (1-self.decay_rate)*grads[key]*grads[key]
params[key] -= self.lr * (1/np.sqrt(self.h[key]+1e-7)) * grads[key] 

Adam

特徴:
パラメータθの更新式:
  m_{t+1} = \rho_{1} m_{t} + (1 - \rho_{1}) \frac{\partial L}{\partial \mathbf{w}}
  v_{t+1} = \rho_{2} v_{t} + (1 - \rho_{2}) \frac{\partial L}{\partial \mathbf{w}}  \frac{\partial L}{\partial \mathbf{w}}
  \mathbf{w}_{t+1} = \mathbf{w}_{t} - \eta \frac{\hat{m}}{\sqrt{\hat{v}} + \epsilon}

サンプルコード

self.m[key] = self.rho1 * self.m[key] + (1-self.rho1)* grads[key]
self.v[key] = self.rho2 * self.v[key] + (1-self.rho2)* grads[key]*grads[key]
params[key] -= self.lr * m/ (np.sqrt(v) +self.epsilon) 

検証集合

ホールドアウト法

k-分割交差検証

正則化(Regularization)

過剰適合を抑えることを目的にパラメータに何らかの制約を課すことを正則化という.

L2 Ridge

L2ノルムのWeight decayは  1/2 λ w^2で、損失関数に加算する.
λ:正規化の強さをコントロールするハイパラ.

  • 大きくすると、大きな重みを取ることに強いペナルティを与える.
  • パラメータが原点から離れることに対して、ペナルティを与える.
  • 大きくすると、学習が失敗するようになった
  • 小さくすると過学習が防げず、いずれの場合も学習がうまくいかなかった.
  • ハイパーパラメータを大きな値に設定すると、すべての重みが限りなく0に近づく
  • 「いくつかの回帰係数は0に近づくか、完全に0にならない」

L2ノルムは、||param||^2なのでその勾配が誤差の勾配に加えられる.
→2 * paramであるが、係数2は正則化の係数に吸収されても変わらないのでgrad +=rate +param と定義する.

L1 Lasso

大きくすると、学習が失敗するようになった. 

  • 小さくすると過学習が防げず、いずれの場合も学習がうまくいかなかった.
  • 「いくつかの回帰係数は完全に0となる」

L1ノルムは、|param|なのでその勾配が誤差の勾配に加えられる.
→x = np.sign(param)
grad += rate *x
と定義する.signは符号関数である.


Early Stop

訓練時、エポック毎に検証誤差を算出し、検証誤差が前ステップの検証誤差より小さくならなかったら、1カウントとする.
検証誤差が最も小さくなる時点やその近辺で訓練を打ち切る.

Dropout(2014年)

  • 訓練時、ユニットをランダムに消去しながらパラメータの最適化を行う.ユニットを消去する割合は定数として与える.
  • Dropoutの予測結果を複数のネットワークのアンサンブル平均と解釈する.各ネットワークはパラメータを共有する.
  • 各ユニットは確率pで存在していて、pを大きくすると、ランダムに消去されるユニットが少なくなる. p=1- dropout_ratio
  • 訓練後、ユニットがすべて存在している.完全なネットワークによって予測する.各ユニットの出力には、確率Pをかける.

サンプルコード

class Dropout:
    """
    http://arxiv.org/abs/1207.0580
    """
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask

#予測時にすべてのユニットを採用して、すべてのパラメータにユニットを消去しなかった割合を一律にかける.
 
def backward(self, dout):
return dout*self.mask
#逆伝播では順伝播に消去したユニットは勾配を伝えず、消化しなかったユニットは勾配をそのまま入力層側へ伝える.
|

荷重減衰(Weight decay)

重みパラメータの値が小さくなるように学習を行うことを目的とする.重みの値を小さくすることで、過学習が起きにくい.

正則化(Normalization)

訓練前に特徴量を特定のスケールに調整しておくことを正則化という

  • DNNで正則化を用いるとき、正則化係数の値は層によってことなってもOK
  • 正則化の対象とするパラメータにはバイアスを含むべきではない.

Batch Normalization(2015年)

重みの初期値を適切に設定すれば、各層のActivation分布は適度な広がりを持ち、学習がスムーズに行える。
>> Activationの分布を調整するのはBatch Normalizationである.

バッチ正則化を行う部分を1つの層として考える。バッチ正規化層のあるノードへの入力をhとすると、バッチ正規化層では、以下の式で正則化する
 h' = (h- \mu)/\sigma
訓練時、バッチ正規化層への入力を用いて計算する.テスト時、バッチ正規化層への入力を用いて計算するのではなく、訓練時に計算しておいた移動平均値を使う.
また、バッチ正則化では、モデルの表現力を維持するために、正則化さらたh'を、パラメータのγとβを用いて、γh'+βに置き換える

サンプルコード

def forward(self, x, train_flg=True):
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)
                        
        if train_flg:
            mu = x.mean(axis=0) # 平均
            xc = x - mu # xをセンタリング
            var = np.mean(xc**2, axis=0) # 分散
            std = np.sqrt(var + 10e-7) # スケーリング
            xn = xc / std
            
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu # 平均値の加重平均
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var #分散値の加重平均
        else:
            xc = x - self.running_mean
            xn = xc / ((np.sqrt(self.running_var + 10e-7)))
            
        out = self.gamma * xn + self.beta 
        
        return out
    # 逆伝播メソッドの定義
    def backward(self, dout):
        
        # 微分の計算
        dbeta = dout.sum(axis=0) # 調整後の平均
        dgamma = np.sum(self.xn * dout, axis=0) # 調整後の標準偏差
        dxn = self.gamma * dout # 正規化後のデータ
        dxc = dxn / self.std # 偏差
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0) # 標準偏差
        dvar = 0.5 * dstd / self.std # 分散
        dxc += (2.0 / self.batch_size) * self.xc * dvar # 偏差
        dmu = np.sum(dxc, axis=0) # 平均
        dx = dxc - dmu / self.batch_size # 入力データ
        
        # インスタンス変数に保存
        self.dgamma = dgamma
        self.dbeta = dbeta
        
        return dx

<メリット>
1. 学習を速く進行させる(学習係数を大きくする)
2. 初期値に依存しない
3. 過学習を抑制する(Dropoutを減らす)

ミニバッチごとに正則化を行う.データの分布が平均0で分散が1になるように正規化


<問題点>
1. バッチ方向に平均と標準偏差を計算するため、データ数が1である.
2. オンライン学習の場合には適用する意味がない
3. ミニバッチごとのデータ数が極めて少ない場合に適していなくて、収束性が悪い.

Layer Normalization(2016年)

平均と標準偏差同じ層内のすべてのユニットを対象として計算するため、ミニバッチ内の個々のデータごとに、正規化に用いる平均と標準偏差が異なる.

Instance Normalization(2016年)

スタイル転送タスクにおいてInstance Normを用いることで、出力画像の質を高められるスタイル転送にこのInstance Normalization を導入した理由は、
スタイル転送の出力を観察すると、合成画像のコントトラストはコンテント画像(構造を担保するContent image)のコントラストに依存しないように見られたため.