はじめに
前回の続きで言語処理100本ノック解いていきたいと思います。
すこし時間が空いてしまいましたね。すいません。
今回は第9章後半です。
9章は非常に盛りだくさんなので3部に分けて、そのうちの真ん中です。
前回はRNN(Recurrent Neural Network)と呼ばれる再帰的構造をもったモデルを使用していきましたが、今回はその多層化や、CNNの実装を行っていきます。
第9章: RNNとCNN
深層学習フレームワークを用い,再帰型ニューラルネットワーク(RNN)や畳み込みニューラルネットワーク(CNN)を実装します.
85. 双方向RNN・多層化
順方向と逆方向のRNNの両方を用いて入力テキストをエンコードし,モデルを学習せよ.
さらに,双方向RNNを多層化して実験せよ
前回の最後に作ったモデルを少し変えるだけで実装できます。
双方向RNN化はnn.LSTMの引数にbidirectional=Trueを加えるだけです。
多層化はNUM_LAYERSを増やしておきます。
class RNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, hidden_size, output_size, num_layers=3, emb_weights=None): super().__init__() if emb_weights != None: self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx) else: self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx) self.rnn = nn.LSTM(emb_size, hidden_size, num_layers, batch_first=True, bidirectional=True) self.fc = nn.Linear(hidden_size * 2, output_size) def forward(self, x, h0=None): x = self.emb(x) x, h = self.rnn(x, h0) x = x[:, -1, :] logits = self.fc(x) return logits # パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 2 # 辞書のID数 + unknown + パディングID EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) + 1 OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 NUM_LAYERS = 3 # モデルの定義 net = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, HIDDEN_SIZE, OUTPUT_SIZE, NUM_LAYERS, weights) net.train() # 損失関数の定義 criterion = nn.CrossEntropyLoss() # 最適化手法の定義 optimizer = torch.optim.SGD(net.parameters(), lr=0.1, momentum=0.9) num_epochs = 30 train_loss, train_acc, valid_loss, valid_acc = train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs) fig, ax = plt.subplots(1,2, figsize=(10, 5)) epochs = np.arange(num_epochs) ax[0].plot(epochs, train_loss, label='train') ax[0].plot(epochs, valid_loss, label='valid') ax[0].set_title('loss') ax[0].set_xlabel('epoch') ax[0].set_ylabel('loss') ax[1].plot(epochs, train_acc, label='train') ax[1].plot(epochs, valid_acc, label='valid') ax[1].set_title('acc') ax[1].set_xlabel('epoch') ax[1].set_ylabel('acc') ax[0].legend(loc='best') ax[1].legend(loc='best') plt.tight_layout() plt.savefig('fig85.png') plt.show() acc_train = calc_acc(net, train_dataloader) acc_valid = calc_acc(net, valid_dataloader) acc_test = calc_acc(net, test_dataloader) print('学習データの正解率: {:.4f}'.format(acc_train)) print('検証データの正解率: {:.4f}'.format(acc_valid)) print('テストデータの正解率: {:.4f}'.format(acc_test))
出力は以下。
NVIDIA GeForce GTX 1660 使用デバイス: cuda:0 Epoch 1 / 30 (train) Loss: 1.1720, Acc: 0.4120, (val) Loss: 1.1644, Acc: 0.4213 Epoch 2 / 30 (train) Loss: 1.1644, Acc: 0.4150, (val) Loss: 1.1647, Acc: 0.4213 Epoch 3 / 30 (train) Loss: 1.1616, Acc: 0.4311, (val) Loss: 1.1409, Acc: 0.4783 Epoch 4 / 30 (train) Loss: 1.0805, Acc: 0.5624, (val) Loss: 1.0210, Acc: 0.5967 Epoch 5 / 30 (train) Loss: 1.0180, Acc: 0.6192, (val) Loss: 0.9276, Acc: 0.6732 ... Epoch 26 / 30 (train) Loss: 0.3511, Acc: 0.8568, (val) Loss: 0.7825, Acc: 0.7264 Epoch 27 / 30 (train) Loss: 0.3434, Acc: 0.8648, (val) Loss: 0.7137, Acc: 0.7361 Epoch 28 / 30 (train) Loss: 0.3260, Acc: 0.8679, (val) Loss: 0.7650, Acc: 0.7676 Epoch 29 / 30 (train) Loss: 0.2948, Acc: 0.8824, (val) Loss: 0.7262, Acc: 0.7646 Epoch 30 / 30 (train) Loss: 0.2766, Acc: 0.8909, (val) Loss: 0.7340, Acc: 0.7586 学習データの正解率: 0.9007 検証データの正解率: 0.7586 テストデータの正解率: 0.7759
なんか悪くなっちゃいましたね、ハイパーパラメータがあんまりよくないのでしょうか…
86. 畳み込みニューラルネットワーク (CNN)
問題文長いので割愛しますが、畳み込みニューラルネットワーク(CNN: Convolutional Neural Network)を用い,単語列からカテゴリを予測するモデルを実装せよ。
というものです。
問題文に沿って畳み込み層とmaxプーリング層を実装し、モデルを作成していきます。
from torch.nn import functional as F class CNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, kernel_heights, stride, padding, emb_weights=None): super().__init__() if emb_weights != None: # 指定があれば埋め込み層の重みをemb_weightsで初期化 self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx) else: self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx) self.conv = nn.Conv2d(1, out_channels, (kernel_heights, emb_size), stride, (padding, 0)) self.drop = nn.Dropout(0.4) self.fc = nn.Linear(out_channels, output_size) def forward(self, x): emb = self.emb(x).unsqueeze(1) conv = self.conv(emb) act = F.relu(conv.squeeze(3)) max_pool = F.max_pool1d(act, act.size()[2]) logits = self.fc(self.drop(max_pool.squeeze(2))) return logits # パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 2 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) + 1 OUTPUT_SIZE = 4 OUT_CHANNELS = 100 KERNEL_HEIGHTS = 3 STRIDE = 1 PADDING = 1 # モデルの定義 model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights) x = torch.tensor([tokenizer(sample)], dtype=torch.int64) print(x) print(x.size()) print(nn.Softmax(dim=-1)(model(x)))
出力は以下。
tensor([[ 68, 76, 782, 1974, 21, 5054, 5055, 34, 1602, 0]]) torch.Size([1, 10]) tensor([[0.2539, 0.2323, 0.2989, 0.2149]], grad_fn=<SoftmaxBackward0>)
とりあえずうまく出力まではできてそうです。
87. 確率的勾配降下法によるCNNの学習
確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,問題86で構築したモデルを学習せよ.訓練データ上の損失と正解率,評価データ上の損失と正解率を表示しながらモデルを学習し,適当な基準(例えば10エポックなど)で終了させよ.
学習させていきましょう
# モデルの定義 net = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights) net.train() # 損失関数の定義 criterion = nn.CrossEntropyLoss() # 最適化手法の定義 optimizer = torch.optim.SGD(net.parameters(), lr=0.1, momentum=0.9) num_epochs = 30 train_loss, train_acc, valid_loss, valid_acc = train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs) fig, ax = plt.subplots(1,2, figsize=(10, 5)) epochs = np.arange(num_epochs) ax[0].plot(epochs, train_loss, label='train') ax[0].plot(epochs, valid_loss, label='valid') ax[0].set_title('loss') ax[0].set_xlabel('epoch') ax[0].set_ylabel('loss') ax[1].plot(epochs, train_acc, label='train') ax[1].plot(epochs, valid_acc, label='valid') ax[1].set_title('acc') ax[1].set_xlabel('epoch') ax[1].set_ylabel('acc') ax[0].legend(loc='best') ax[1].legend(loc='best') plt.tight_layout() plt.savefig('fig87.png') plt.show() acc_train = calc_acc(net, train_dataloader) acc_valid = calc_acc(net, valid_dataloader) acc_test = calc_acc(net, test_dataloader) print('学習データの正解率: {:.4f}'.format(acc_train)) print('検証データの正解率: {:.4f}'.format(acc_valid)) print('テストデータの正解率: {:.4f}'.format(acc_test))
出力は以下。
NVIDIA GeForce GTX 1660 使用デバイス: cuda:0 Epoch 1 / 30 (train) Loss: 0.9632, Acc: 0.6180, (val) Loss: 0.7480, Acc: 0.7369 Epoch 2 / 30 (train) Loss: 0.6651, Acc: 0.7585, (val) Loss: 0.5898, Acc: 0.7864 Epoch 3 / 30 (train) Loss: 0.5002, Acc: 0.8221, (val) Loss: 0.5116, Acc: 0.8178 Epoch 4 / 30 (train) Loss: 0.3920, Acc: 0.8614, (val) Loss: 0.5248, Acc: 0.8186 Epoch 5 / 30 (train) Loss: 0.3057, Acc: 0.8890, (val) Loss: 0.5322, Acc: 0.8223 ... Epoch 26 / 30 (train) Loss: 0.0686, Acc: 0.9784, (val) Loss: 0.6891, Acc: 0.8366 Epoch 27 / 30 (train) Loss: 0.0654, Acc: 0.9783, (val) Loss: 0.6809, Acc: 0.8471 Epoch 28 / 30 (train) Loss: 0.0614, Acc: 0.9777, (val) Loss: 0.6953, Acc: 0.8448 Epoch 29 / 30 (train) Loss: 0.0616, Acc: 0.9790, (val) Loss: 0.7041, Acc: 0.8351 Epoch 30 / 30 (train) Loss: 0.0558, Acc: 0.9820, (val) Loss: 0.6912, Acc: 0.8388 学習データの正解率: 0.9989 検証データの正解率: 0.8388 テストデータの正解率: 0.8643
RNNよりCNNのほうが精度がよさそうです。
ただ、8章でやった畳み込みなしのNNには精度勝てそうにないですね。。。謎です。
88. パラメータチューニング
問題85や問題87のコードを改変し,ニューラルネットワークの形状やハイパーパラメータを調整しながら,高性能なカテゴリ分類器を構築せよ.
後半の最後にいままでのモデルのハイパーパラメータを調整して精度上がるかやってみます。
CNNのほうが精度よさそうだったのでそっちを変えてみることにしましょう。
optimizerをAdamにかえて、畳み込みフィルターのサイズも調整しました。
class CNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, kernel_heights, stride, padding, emb_weights=None): super().__init__() if emb_weights != None: # 指定があれば埋め込み層の重みをemb_weightsで初期化 self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx) else: self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx) self.conv = nn.Conv2d(1, out_channels, (kernel_heights, emb_size), stride, (padding, 0)) self.drop = nn.Dropout(0.4) self.fc = nn.Linear(out_channels, output_size) def forward(self, x): emb = self.emb(x).unsqueeze(1) conv = self.conv(emb) act = F.relu(conv.squeeze(3)) max_pool = F.max_pool1d(act, act.size()[2]) logits = self.fc(self.drop(max_pool.squeeze(2))) return logits # パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 2 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) + 1 OUTPUT_SIZE = 4 OUT_CHANNELS = 500 KERNEL_HEIGHTS = 2 STRIDE = 1 PADDING = 1 # モデルの定義 net = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights) net.train() # 損失関数の定義 criterion = nn.CrossEntropyLoss() # 最適化手法の定義 optimizer = torch.optim.Adam(net.parameters(), lr=0.0005) num_epochs = 30 train_loss, train_acc, valid_loss, valid_acc = train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs) fig, ax = plt.subplots(1,2, figsize=(10, 5)) epochs = np.arange(num_epochs) ax[0].plot(epochs, train_loss, label='train') ax[0].plot(epochs, valid_loss, label='valid') ax[0].set_title('loss') ax[0].set_xlabel('epoch') ax[0].set_ylabel('loss') ax[1].plot(epochs, train_acc, label='train') ax[1].plot(epochs, valid_acc, label='valid') ax[1].set_title('acc') ax[1].set_xlabel('epoch') ax[1].set_ylabel('acc') ax[0].legend(loc='best') ax[1].legend(loc='best') plt.tight_layout() plt.savefig('fig88.png') plt.show() acc_train = calc_acc(net, train_dataloader) acc_valid = calc_acc(net, valid_dataloader) acc_test = calc_acc(net, test_dataloader) print('学習データの正解率: {:.4f}'.format(acc_train)) print('検証データの正解率: {:.4f}'.format(acc_valid)) print('テストデータの正解率: {:.4f}'.format(acc_test))
結果は以下の通り。
NVIDIA GeForce GTX 1660 使用デバイス: cuda:0 Epoch 1 / 30 (train) Loss: 1.0482, Acc: 0.5971, (val) Loss: 0.8920, Acc: 0.6897 Epoch 2 / 30 (train) Loss: 0.7826, Acc: 0.7262, (val) Loss: 0.7146, Acc: 0.7496 Epoch 3 / 30 (train) Loss: 0.6073, Acc: 0.7900, (val) Loss: 0.6166, Acc: 0.7856 Epoch 4 / 30 (train) Loss: 0.4830, Acc: 0.8420, (val) Loss: 0.5560, Acc: 0.8111 ... Epoch 27 / 30 (train) Loss: 0.0211, Acc: 0.9978, (val) Loss: 0.4855, Acc: 0.8651 Epoch 28 / 30 (train) Loss: 0.0207, Acc: 0.9969, (val) Loss: 0.4754, Acc: 0.8621 Epoch 29 / 30 (train) Loss: 0.0172, Acc: 0.9983, (val) Loss: 0.4814, Acc: 0.8643 Epoch 30 / 30 (train) Loss: 0.0163, Acc: 0.9980, (val) Loss: 0.4926, Acc: 0.8651 学習データの正解率: 0.9993 検証データの正解率: 0.8651 テストデータの正解率: 0.8793
ちょびっとだけ改善しましたかね??むずかしいです。
おわりに
時系列データの予測に強いといわれるRNNやCNNの実装をおこないました。
一通りやってみましたが、8章でやった結果を上回ることはできませんでしたね。