自然言語処理100本ノック2020を解く(8章-後半)
73.確率的勾配降下法による学習
確率的勾配降下法による学習を行なっていきます.
まずはニューラルネットワークのモデルを定義します.
コード
class MyNet(nn.Module): def __init__(self, input_size): super(MyNet, self).__init__() self.fc1 = torch.nn.Linear(input_size, 4) def forward(self, x): x = self.fc1(x) return x
本問題では,とりあえず入力→出力のレイヤのみとします.
forward()は,推論処理をする部分です.順方向伝搬とも呼びます.
コード
mynet = MyNet(dim) optimizer = optim.SGD(mynet.parameters(), lr = 0.01, momentum = 0.9) num_epochs = 100 save_path = '../data/params.tar'
入力は,単語ベクトルの次元と同じにします.次に,pytorchからSGDを持ってきてインスタンスを作ります.
確率的勾配法や学習係数,モメンタムについてここで説明するとN番煎じな感じになるので,割愛します.
プロフェッショナルシリーズの深層学習が数式が載っていてわかりやすいです.
コード
import time import matplotlib.pyplot as plt %matplotlib inline class Model(): def __init__(self, model): self.model = model def set_params(self, num_epochs, num_batches, optimizer, criterion): # エポック数,バッチサイズ,最適化アルゴリズム,損失関数の初期化 self.num_epochs = num_epochs self.num_batches = num_batches self.optimizer = optimizer self.criterion = criterion # epoch毎の損失と精度を保持するリスト self.losses = np.zeros(num_epochs) self.accs = np.zeros(num_epochs) def fit(self, x_train, y_train): num_data = x_train.shape[0] for epoch in range(self.num_epochs): # epoch毎の損失と精度 running_loss = 0.0 accuracy = 0.0 # 開始時間 t = time.time() # ミニバッチ学習のために訓練データのインデックスをランダムに並べ替える index = np.random.permutation(num_data) for i in range(0, num_data, self.num_batches): # NNへの入力と正解ラベル in_, label = torch.tensor(x_train[index[i : i + self.num_batches if i + self.num_batches < num_data else num_data]]), \ torch.tensor(y_train[index[i : i + self.num_batches if i + self.num_batches < num_data else num_data]]).long() # 最適化関数の初期化 self.optimizer.zero_grad() # 予測とロスの計算 out_ = self.model(in_.float()) loss = self.criterion(out_, label) _, idx = torch.max(out_, 1) # バッチ数のラベルが出てくるので,いくつ正解しているかカウント cnt = 0 for i in range(idx.shape[0]): if idx[i] == label[i]: cnt += 1 accuracy += cnt / idx.shape[0] # パラメーターの更新 loss.backward() optimizer.step() running_loss += loss.data # 各種データの出力・保存 num_loop = num_data / self.num_batches accuracy /= num_loop loss = running_loss / num_loop print('[ epoch:{0}, time:{3} ] acc: {1:.4f} loss:{2:.4f}'.format(epoch + 1, accuracy, loss, time.time() - t)) self.accs[epoch] = accuracy self.losses[epoch] = loss # 状況の保存 torch.save({ 'epoch': epoch, 'model_state_dict': self.model.state_dict(), 'optimizer_state_dict': self.optimizer.state_dict(), 'loss': loss, 'accuracy': accuracy, }, save_path) def predict(self, x): out_ = self.model(x) return out_ def evaluate(self, x, y): p = self.predict(x) _, idx = torch.max(p, 1) cnt = 0 for i in range(idx.shape[0]): if idx[i] == y[i]: cnt += 1 accuracy = cnt / idx.shape[0] return accuracy def visualize(self): x = np.arange(len(self.accs)) fig = plt.figure() ax = fig.add_subplot(211) ax.plot(x, self.accs, color = 'red', linewidth = 2, label = 'accuracy') ax2 = fig.add_subplot(212) ax2.plot(x, self.losses, color = 'green', linewidth = 2, label = 'loss') ax.legend() ax2.legend() plt.plot() model = Model(mynet) model.set_params(num_epochs, 1, optimizer, criterion) model.fit(x_feature, y_train)
実行結果
[ epoch:1, time:2.592939853668213 ] acc: 0.8776 loss:0.3732 [ epoch:2, time:2.466198205947876 ] acc: 0.9112 loss:0.2633 [ epoch:3, time:2.4603400230407715 ] acc: 0.9181 loss:0.2404 ... [ epoch:98, time:2.58657169342041 ] acc: 0.9427 loss:0.1618 [ epoch:99, time:2.535313129425049 ] acc: 0.9415 loss:0.1610 [ epoch:100, time:2.559777021408081 ] acc: 0.9419 loss:0.1612
細かい説明などは問題に合わせてしていきます.
エポックは100で学習しています.
ロスが減っていくのが確認できます.
74.正解率の計測
学習データと評価データに対する予測をし,その正解率を求めます.
Modelクラスのevaluateメソッドを使います.
コード
# Modelクラス内 def evaluate(self, x, y): p = self.predict(x) _, idx = torch.max(p, 1) cnt = 0 for i in range(idx.shape[0]): if idx[i] == y[i]: cnt += 1 accuracy = cnt / idx.shape[0] return accuracy print(model.evaluate(x_feature.float(), y_train)) print(model.evaluate(torch.tensor(vec2sum(x_valid, dim)).float(), y_valid))
実行結果
0.9435002296738632 0.9237132352941176
75.損失と正解率のプロット
この問題では,Modelクラス内のvisualizeメソッドを使います.
def visualize(self): x = np.arange(len(self.accs)) fig = plt.figure() ax = fig.add_subplot(211) ax.plot(x, self.accs, color = 'red', linewidth = 2, label = 'accuracy') ax2 = fig.add_subplot(212) ax2.plot(x, self.losses, color = 'green', linewidth = 2, label = 'loss') ax.legend() ax2.legend() plt.plot() model.visualize()
実行結果
76.チェックポイント
73のコードの#状況の保存 の部分でチェックポイントを記録しています.
77.ミニバッチ化
いくつかの事例毎にパラメータを更新するようにします.
73のコードでは,num_batchesでバッチサイズを指定しています.
毎エポックでインデックスをランダムにしたものを作成し,まとめて入力とします.
コード
model2 = Model(mynet)
model2.set_params(num_epochs, 4, optimizer, criterion)
model2.fit(x_feature, y_train)
実行結果
[ epoch:1, time:0.7158238887786865 ] acc: 0.8294 loss:0.5254 [ epoch:2, time:0.6863267421722412 ] acc: 0.8870 loss:0.3467 [ epoch:3, time:0.6839478015899658 ] acc: 0.9010 loss:0.3032 ... [ epoch:98, time:0.7376530170440674 ] acc: 0.9402 loss:0.1720 [ epoch:99, time:0.723405122756958 ] acc: 0.9386 loss:0.1723 [ epoch:100, time:0.7342729568481445 ] acc: 0.9388 loss:0.1723
バッチサイズを4にしたところ,1エポックにかかる時間が大体1/4になりました.
78.GPU上での学習
割愛します.
79.多層ニューラルネットワーク
ニューラルネットワークをカスタマイズします.
コード
class MyNet2(nn.Module): def __init__(self, input_size, h_size, output_size): super(MyNet2, self).__init__() self.fc1 = torch.nn.Linear(input_size, h_size) self.fc2 = torch.nn.Linear(h_size, h_size / 2) self.fc3 = torch.nn.Linear(h_size / 2, output_size) def forward(self, x): x = self.fc1(x) x = func.relu(x) x = self.fc2(x) x = func.relu(x) x = self.fc3(x) return x mynet = MyNet2(dim, 100, 4) optimizer = optim.SGD(mynet.parameters(), lr = 0.01, momentum = 0.9) criterion = torch.nn.CrossEntropyLoss() save_path = '../data/params.tar' model = Model(mynet) model.set_params(num_epochs, 2, optimizer, criterion) model.fit(x_feature, y_train)
実行結果
[ epoch:1, time:2.569727897644043 ] acc: 0.8461 loss:0.4161 [ epoch:2, time:2.516397714614868 ] acc: 0.9080 loss:0.2681 [ epoch:3, time:2.531559944152832 ] acc: 0.9192 loss:0.2329 ... [ epoch:98, time:3.5405173301696777 ] acc: 0.9985 loss:0.0020 [ epoch:99, time:3.155651092529297 ] acc: 0.9987 loss:0.0020 [ epoch:100, time:3.258673906326294 ] acc: 0.9985 loss:0.0020
中間層を追加しました.
精度も見てみます.
コード
print(model.evaluate(x_feature.float(), y_train)) print(model.evaluate(torch.tensor(vec2sum(x_valid, dim)).float(), y_valid)) model.visualize()
0.9989664676159853 0.9365808823529411
過適合してますね...
とりあえず,以上とします.
何かございましたらコメントください...
全コード:
github.com