深層学習Day2
Section2 学習率最適化手法
ニューラルネットワークの学習において、ハイパーパラメータの一つが学習率。これが大きいとパラメータが発散して学習できず、逆に小さいと最適解までたどり着くのに時間がかかりすぎてしまったり、局所最適解にはまってしまったりする。
そこで、
- 初期の学習率を大きく設定して、徐々に小さくする
- パラメータごとに学習率を可変にする
など、学習率最適化手法を利用して学習率を最適化する。
ここでいう「初期の学習率」や学習率を可変にした際の「減衰率」なども、結局はハイパーパラメータになってしまうため、実際にモデルを作成する際には実験を繰り返す必要がある。
momentum
誤差をパラメータで微分したものと学習率の積を減算した後、現在の重みに前回の重みを減算した値と慣性の積を加算する。
Adagrad
誤差をパラメータで微分したものと、再定義した学習率の積を減算する。
RMSprop
誤差をパラメータで微分したものと、再定義した学習率の積を減算する。
Adagradとは異なり、微分するパラメータ部分に新たな変数を導入している。
Adam
momentumの、過去の勾配の指数関数的減衰平均
RMSpropの、過去の勾配の2乗の指数関数的減衰平均
<確認テスト>
momentum・Adagrad・RMSpropの特徴をそれぞれ簡潔に説明せよ
⇒
momentumは、局所最適解にはならず、大域的最適解になる。谷間についてから最適値にいくまでの時間が早い。
Adagradは、勾配の緩やかな斜面に対して最適値に近づける。学習率が徐々に小さくなるので、鞍点問題を引き起こすことがあった。
RMSpropは、局所最適解にはならず、大域的最適解となる。ハイパーパラメータの調整が必要な場合が少ない。
実装演習
前Sectionと同じく、mnistデータセットの分類モデルを実装。
まずは比較対象としてSGDによる学習を実装。
# データの読み込み
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True, one_hot_label=True)
print("データ読み込み完了")
use_batchnorm = False
network = MultiLayerNet(input_size=784, hidden_size_list=[40, 20], output_size=10, activation='sigmoid', weight_init_std=0.01,
#use_batchnorm=use_batchnorm
)
iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
train_loss_list = []
accuracies_train = []
accuracies_test = []
plot_interval=10
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
# 勾配
grad = network.gradient(x_batch, d_batch)
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
network.params[key] -= learning_rate * grad[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
if (i + 1) % plot_interval == 0:
accr_test = network.accuracy(x_test, d_test)
accuracies_test.append(accr_test)
accr_train = network.accuracy(x_batch, d_batch)
accuracies_train.append(accr_train)
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
# グラフの表示
plt.show()
続いて、momentum。
変数momentum
を導入し、勾配の計算式を以下のように変える。
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
v = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
v[key] = np.zeros_like(network.params[key])
v[key] = momentum * v[key] - learning_rate * grad[key]
network.params[key] += v[key]
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
学習が進むようになった。
続いて、Adagrad。
勾配の計算式を以下のように変える。
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
h = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
h[key] = np.full_like(network.params[key], 1e-4)
else:
h[key] += np.square(grad[key])
network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key]))
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
こちらもうまく学習が進んでいる。
続いて、RMSprop。
変数decay_rate
を導入し、勾配の計算式を以下に変更。
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
h = {}
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
h[key] = np.zeros_like(network.params[key])
h[key] *= decay_rate
h[key] += (1 - decay_rate) * np.square(grad[key])
network.params[key] -= learning_rate * grad[key] / (np.sqrt(h[key]) + 1e-7)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
momentumやAdagradよりもうまく学習が進んでいるように見える。
続いて、Adam。
2つの変数beta1
とbeta2
を導入し、勾配の計算式を以下に変更。
# 勾配
grad = network.gradient(x_batch, d_batch)
if i == 0:
m = {}
v = {}
learning_rate_t = learning_rate * np.sqrt(1.0 - beta2 ** (i + 1)) / (1.0 - beta1 ** (i + 1))
for key in ('W1', 'W2', 'W3', 'b1', 'b2', 'b3'):
if i == 0:
m[key] = np.zeros_like(network.params[key])
v[key] = np.zeros_like(network.params[key])
m[key] += (1 - beta1) * (grad[key] - m[key])
v[key] += (1 - beta2) * (grad[key] ** 2 - v[key])
network.params[key] -= learning_rate_t * m[key] / (np.sqrt(v[key]) + 1e-7)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
こちらもRMSpropと同様、うまく学習が進んでいるように見える。
Section3 過学習
そもそも過学習とは、特定の訓練サンプルに対して特化して学習してしまい、汎化性能が下がってしまうことを指す。グラフにすると、テスト誤差と訓練誤差とで学習曲線が剥離してしまう。
原因としては、基本的にネットワークの自由度が高いこと。
具体的には、
- パラメータの数が多い。
- パラメータの値が適切ではない。
- ノードが多い
ことが挙げられる。このとき、
正則化
これらの過学習を抑制する方法として有効な方法が正則化。
これは以下の2種類。
- L1/L2正則化
- ドロップアウト
<確認テスト>
機械学習で使われる線形モデル(線形回帰,主成分分析...etc)の正則化は、モデルの重みを制限することで可能となる。前述の線形モデルの正則化手法の中にリッジ回帰という手法があり、その特徴として正しいものを選択しなさい。
⇒a
L1/L2正則化は、損失関数に実数×重みの大きさのn乗
の正則化項を加えることで、重みの大きさが大きくなることを抑制する手法。
ここでいう実数
はハイパーパラメータ。大きいほど正則化項の影響が大きくなる。
確認テスト
下図について、L1正則化を表しているグラフはどちらか答えよ。
⇒右図
ドロップアウト
過学習を抑制するためによく使われる手法。
過学習が起きる原因の一つが、ノードの数が多いこと。
これに対して、学習の際にランダムにノードを削除して学習させることで、データ量を変化させずに異なるモデルを学習させていると解釈できる。
学習のイテレーションごとに異なるノードのパラメータを更新しているため、実質的に毎回異なるモデルを用いて計算しているとみなすことができ、アンサンブル学習のイメージで学習が行える。
実装演習
これまでと同じMNISTデータセットの分類。
過学習を抑制するため、データ数を減らして実験してみる。
用いる最適化手法はSGD。
(x_train, d_train), (x_test, d_test) = load_mnist(normalize=True)
print("データ読み込み完了")
# 過学習を再現するために、学習データを削減
x_train = x_train[:300]
d_train = d_train[:300]
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)
optimizer = optimizer.SGD(learning_rate=0.02)
iters_num = 1000
train_size = x_train.shape[0]
batch_size = 100
train_loss_list = []
accuracies_train = []
accuracies_test = []
plot_interval=10
for i in range(iters_num):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
d_batch = d_train[batch_mask]
grad = network.gradient(x_batch, d_batch)
optimizer.update(network.params, grad)
loss = network.loss(x_batch, d_batch)
train_loss_list.append(loss)
if (i+1) % plot_interval == 0:
accr_train = network.accuracy(x_train, d_train)
accr_test = network.accuracy(x_test, d_test)
accuracies_train.append(accr_train)
accuracies_test.append(accr_test)
lists = range(0, iters_num, plot_interval)
plt.plot(lists, accuracies_train, label="training set")
plt.plot(lists, accuracies_test, label="test set")
plt.legend(loc="lower right")
plt.title("accuracy")
plt.xlabel("count")
plt.ylabel("accuracy")
plt.ylim(-0.1, 1.1)
# グラフの表示
plt.show()
Section4 畳み込みニューラルネットワークの概念
畳み込みニューラルネットワーク(以下、CNN)はディープラーニングを行う際によく用いられるモデルの種類の一つ。特に画像処理分野において用いられることが多い。
CNNは全結合型のニューラルネットワークとは異なり、「畳み込み層」「プーリング層」を持つことが大きな特徴。
この「畳み込み層」「プーリング層」を交互に重ねて学習していくことで、画像など2次元・3次元のデータに対して"隣接セルとの関係"を特徴量としてうまく取り出すことが可能。
畳み込み層
畳み込み層では、縦・横・奥行の3次元データに対して、フィルターをずらしながら掛け合わせて特徴マップを作り、次の層へと伝播させていく。
画像の場合は、3次元は縦・横・色(通常はRGBで3チャネル)に該当する。
畳み込み層の基本計算は上記の通りだが、これ以外にもいくつか処理がある。
- バイアス:畳み込み演算後に、すべてのセルに足し合わせる値
- パディング:特徴マップを大きくしたいとき、元の入力画像の周囲を0で埋め合わせて、見かけ上大きいサイズの画像にする。
- ストライド:フィルタをずらす幅。上記の例では1。
- チャンネル:フィルタの数。これを増やすと、次の層での奥行の次元が増える
プーリング層
縦・横方向の空間を小さくするような演算。
入力値に対して、一部の対象領域の最大値や平均値を取って、それを出力値としてマッピングする。
確認テスト
サイズ6×6の丹生慮k画像を、サイズ2×2のフィルタで畳み込んだ時の出力画像のサイズを答えよ。
なお、ストライドとパディングは1とする。
⇒7×7
実装演習
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
# N: number, C: channel, H: height, W: width
N, C, H, W = input_data.shape
# 切り捨て除算
out_h = (H + 2 * pad - filter_h)//stride + 1
out_w = (W + 2 * pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride * out_h
for x in range(filter_w):
x_max = x + stride * out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3) # (N, C, filter_h, filter_w, out_h, out_w) -> (N, filter_w, out_h, out_w, C, filter_h)
col = col.reshape(N * out_h * out_w, -1)
return col
# im2colの処理確認
input_data = np.random.rand(2, 1, 4, 4)*100//1 # number, channel, height, widthを表す
print('========== input_data ===========\n', input_data)
print('==============================')
filter_h = 3
filter_w = 3
stride = 1
pad = 0
col = im2col(input_data, filter_h=filter_h, filter_w=filter_w, stride=stride, pad=pad)
print('============= col ==============\n', col)
print('==============================')
結果は以下。
========== input_data ===========
[[[[52. 86. 15. 4.]
[64. 77. 13. 64.]
[38. 75. 93. 43.]
[75. 51. 62. 75.]]]
[[[69. 68. 8. 39.]
[24. 55. 21. 23.]
[16. 40. 90. 99.]
[66. 23. 93. 91.]]]]
==============================
============= col ==============
[[52. 86. 15. 64. 77. 13. 38. 75. 93.]
[86. 15. 4. 77. 13. 64. 75. 93. 43.]
[64. 77. 13. 38. 75. 93. 75. 51. 62.]
[77. 13. 64. 75. 93. 43. 51. 62. 75.]
[69. 68. 8. 24. 55. 21. 16. 40. 90.]
[68. 8. 39. 55. 21. 23. 40. 90. 99.]
[24. 55. 21. 16. 40. 90. 66. 23. 93.]
[55. 21. 23. 40. 90. 99. 23. 93. 91.]]
==============================
Section5 最新のCNN
AlexNet
2012年にHinton教授らのチームが提案した物体認識のためのモデル。このAlexNetが物体認識タスクにおいて、初めて深層学習・CNNのアイデアを取り入れたモデルになる。
それまで物体認識タスクではSVMなどを用いていたが、この年のコンペにおいてAlexNetが他のモデルと精度で大きく差をつけたことが話題になった。
モデルの最後の方にある全結合層の部分にドロップアウトを用いている。
現在ではほとんど使われていない。
U-net
生物医学の分野で、セマンティックセグメンテーションのために作られたモデル。全てCNNの構造のみで構成されている。
U-netはエンコーダとデコーダによって構成されている。エンコーダでは、入力画像を何度か畳み込み、特徴を抽出していく(ダウンサンプリング)。デコーダでは逆畳み込みによって特徴マップを大きくしていく。(アップサンプリング)。この際、ダウンサンプリングの際の元サイズの特徴量をそのまま加える(画像の灰色矢印の部分)ことで、物体の位置情報などをうまく伝播させることができるようにしている。
U-netはこの構造を起点として派生したモデルが多数提案されている。
DCGAN
画像生成のためのモデルとして提案されたGANに、CNNの構造を持ち込んだモデル。
基本的に、GeneratorとDiscriminatorが競うように学習させるのは元のGANと同じ。
Generatorでは、ランダムな数値のベクトルを元に、畳み込み層(アップサンプリング)で画像を生成する。ここではプーリング層は使用されていない。(画像はGeneratorの部分)
Discriminatorでは、物体認識のCNNと同じようにダウンサンプリングしながら特徴マップを生成するが、普通のプーリングではなく別の畳み込み処理を行っている。
GANは基本的にニューラルネットワークの中では学習が不安定であるため、活性化関数なども工夫されている。
現在では、このDCGANからも様々な派生モデルが提案されている。
実装演習
Pytorchを利用したCNN構造の実装(MNISTデータセットの分類を想定)
import torch.nn as nn
import torch.nn.functional as F
class CNN_classify(nn.Module):
def __init__(self,num_class):
super(CNN_classify, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3) # 28x28x32 -> 26x26x32
self.conv2 = nn.Conv2d(32, 64, 3) # 26x26x64 -> 24x24x64
self.pool = nn.MaxPool2d(2, 2) # 24x24x64 -> 12x12x64
self.fc1 = nn.Linear(12 * 12 * 64, 128)
self.bn1 = nn.BatchNorm1d(128)
self.fc2 = nn.Linear(128, num_class)
def init_weights(self):
initrange = 1
self.conv1.weight.data.uniform_(-initrange, initrange)
self.conv2.weight.data.uniform_(-initrange, initrange)
nn.init.kaiming_normal_(self.fc1.weight, mode='fan_out', nonlinearity='relu')
self.fc1.bias.data.zero_()
self.bn1.weight.data.uniform_(-initrange, initrange)
self.bn1.bias.data.zero_()
nn.init.kaiming_normal_(self.fc2.weight, mode='fan_out', nonlinearity='relu')
self.fc2.bias.data.zero_()
def forward(self, x, bacth_size):
x = F.relu(self.conv1(x))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 12 * 12 * 64)
x = self.fc1(x)
x = self.bn1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.relu(x)
return x