【言語処理100本ノック 2020】第9章: RNNとCNN
自然言語処理の問題集として有名な言語処理100本ノックの2020年版の解答例です。 この記事では、以下の第1章から第10章のうち、「第9章: RNNとCNN」を解いてみた結果をまとめています。
- 第1章: 準備運動
- 第2章: UNIXコマンド
- 第3章: 正規表現
- 第4章: 形態素解析
- 第5章: 係り受け解析
- 第6章: 機械学習
- 第7章: 単語ベクトル
- 第8章: ニューラルネット
- 第9章: RNNとCNN
- 第10章: 機械翻訳
環境設定
コードの実行はGoogle Colaboratoryで行います。 以降の解答の実行結果をすべて含むノートブックは、以下のリンクから直接参照することができます。
第9章: RNN, CNN
80. ID番号への変換
問題51で構築した学習データ中の単語にユニークなID番号を付与したい.学習データ中で最も頻出する単語に,2番目に頻出する単語に,……といった方法で,学習データ中で2回以上出現する単語にID番号を付与せよ.そして,与えられた単語列に対して,ID番号の列を返す関数を実装せよ.ただし,出現頻度が2回未満の単語のID番号はすべてとせよ.
まずは、指定のデータをダウンロード後、データフレームとして読込みます。そして、学習データ、検証データ、評価データに分割し、保存します。 ここまでは、第6章の問題50とまったく同じ処理のため、そちらで作成したデータを読み込んでも問題ありません。
# データのダウンロード !wget https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip !unzip NewsAggregatorDataset.zip
# 読込時のエラー回避のためダブルクォーテーションをシングルクォーテーションに置換 !sed -e 's/"/'\''/g' ./newsCorpora.csv > ./newsCorpora_re.csv
import pandas as pd from sklearn.model_selection import train_test_split # データの読込 df = pd.read_csv('./newsCorpora_re.csv', header=None, sep='\t', names=['ID', 'TITLE', 'URL', 'PUBLISHER', 'CATEGORY', 'STORY', 'HOSTNAME', 'TIMESTAMP']) # データの抽出 df = df.loc[df['PUBLISHER'].isin(['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail']), ['TITLE', 'CATEGORY']] # データの分割 train, valid_test = train_test_split(df, test_size=0.2, shuffle=True, random_state=123, stratify=df['CATEGORY']) valid, test = train_test_split(valid_test, test_size=0.5, shuffle=True, random_state=123, stratify=valid_test['CATEGORY']) # 事例数の確認 print('【学習データ】') print(train['CATEGORY'].value_counts()) print('【検証データ】') print(valid['CATEGORY'].value_counts()) print('【評価データ】') print(test['CATEGORY'].value_counts())
--- 出力 --- 【学習データ】 b 4501 e 4235 t 1220 m 728 Name: CATEGORY, dtype: int64 【検証データ】 b 563 e 529 t 153 m 91 Name: CATEGORY, dtype: int64 【評価データ】 b 563 e 530 t 152 m 91 Name: CATEGORY, dtype: int64
続いて、単語の辞書を作成します。 学習データの単語をカウントし、2回以上登場するものをキーとして頻度順位(ID)を登録していきます。
from collections import defaultdict import string # 単語の頻度集計 d = defaultdict(int) table = str.maketrans(string.punctuation, ' '*len(string.punctuation)) # 記号をスペースに置換するテーブル for text in train['TITLE']: for word in text.translate(table).split(): d[word] += 1 d = sorted(d.items(), key=lambda x:x[1], reverse=True) # 単語ID辞書の作成 word2id = {word: i + 1 for i, (word, cnt) in enumerate(d) if cnt > 1} # 出現頻度が2回以上の単語を登録 print(f'ID数: {len(set(word2id.values()))}\n') print('頻度上位20語') for key in list(word2id)[:20]: print(f'{key}: {word2id[key]}')
--- 出力 --- ID数: 9405 頻度上位20語 to: 1 s: 2 in: 3 on: 4 UPDATE: 5 as: 6 US: 7 for: 8 The: 9 of: 10 1: 11 To: 12 2: 13 the: 14 and: 15 In: 16 Of: 17 a: 18 at: 19 A: 20
最後に、辞書を用いて与えられた単語列をID番号の列に変換する関数を定義します。このとき、問題文の指示に従い、辞書にない単語には0
を返すようにします。
def tokenizer(text, word2id=word2id, unk=0): """ 入力テキストをスペースで分割しID列に変換(辞書になければunkで指定した数字を設定)""" table = str.maketrans(string.punctuation, ' '*len(string.punctuation)) return [word2id.get(word, unk) for word in text.translate(table).split()]
2つ目の文で確認します。
# 確認 text = train.iloc[1, train.columns.get_loc('TITLE')] print(f'テキスト: {text}') print(f'ID列: {tokenizer(text)}')
--- 出力 --- テキスト: Amazon Plans to Fight FTC Over Mobile-App Purchases ID列: [169, 539, 1, 683, 1237, 82, 279, 1898, 4199]
81. RNNによる予測
ID番号で表現された単語列がある.ただし,は単語列の長さ,は単語のID番号のone-hot表記である(は単語の総数である).再帰型ニューラルネットワーク(RNN: Recurrent Neural Network)を用い,単語列からカテゴリを予測するモデルとして,次式を実装せよ.
ただし,は単語埋め込み(単語のone-hot表記から単語ベクトルに変換する関数),は時刻の隠れ状態ベクトル,は入力と前時刻の隠れ状態から次状態を計算するRNNユニット,は隠れ状態ベクトルからカテゴリを予測するための行列,はバイアス項である(はそれぞれ,単語埋め込みの次元数,隠れ状態ベクトルの次元数,ラベル数である).RNNユニットには様々な構成が考えられるが,典型例として次式が挙げられる.
ただし,はRNNユニットのパラメータ,は活性化関数(例えばやReLUなど)である.
なお,この問題ではパラメータの学習を行わず,ランダムに初期化されたパラメータでを計算するだけでよい.次元数などのハイパーパラメータは,など,適当な値に設定せよ(以降の問題でも同様である).
解答に入る前に、ニューラルネットを用いた自然言語処理、特にテキスト分類における処理の流れを整理しておきます。 ニューラルネットを用いたテキスト分類は、主に以下の4つの工程からなります。
- 文をトークン(例えば単語)の列に分割
- それぞれのトークンをベクトルに変換
- トークンベクトルを文ベクトルとして1つに集約
- 文ベクトルを入力としてラベルを分類
それぞれの工程について、いろいろな方法が考えられますが、例えば第8章では、
- 文をトークン(例えば単語)の列に分割 ⇒ スペースで分割
- それぞれのトークンをベクトルに変換 ⇒ 事前学習済みWord2Vecで変換
- トークンベクトルを文ベクトルとして1つに集約 ⇒ トークンベクトルを平均
- 文ベクトルを入力としてラベルを分類 ⇒ 全結合層で分類
の流れを実装し、No.4のパラメータを学習していました(日本語文書を対象とする場合は、No.1で第4章の形態素解析が必要となります)。
それに対し、本章では、
- 文をトークン(例えば単語)の列に分割 ⇒ スペースで分割
- それぞれのトークンをベクトルに変換 ⇒ 埋め込み層で変換
- トークンベクトルを文ベクトルとして1つに集約 ⇒ RNNまたはCNNで集約
- 文ベクトルを入力としてラベルを分類 ⇒ 全結合層で分類
となり、No.2~4を繋げたネットワークのパラメータを学習していきます。 なお、本章の問題のように、便宜的に分割したトークンを対応するIDに変換しておくことも多いですが、工程としてはNo.1に含まれます。
それでは、早速本問のネットワークを実装します。
埋め込み層にはnn.Embedding
を使います。この層は、単語IDを与えるとone-hotベクトルに変換した後、指定したサイズ(emb_size
)のベクトルに変換します。
続くRNN部分は、全結合層を再帰的に通す処理で実現できますが、nn.RNN
を用いることでシンプルに書くことができます。
最後に全結合層を繋げれば完成です。
import torch from torch import nn class RNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size): super().__init__() self.hidden_size = hidden_size self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx) self.rnn = nn.RNN(emb_size, hidden_size, nonlinearity='tanh', batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): self.batch_size = x.size()[0] hidden = self.init_hidden(x.device) # h0のゼロベクトルを作成 emb = self.emb(x) # emb.size() = (batch_size, seq_len, emb_size) out, hidden = self.rnn(emb, hidden) # out.size() = (batch_size, seq_len, hidden_size) out = self.fc(out[:, -1, :]) # out.size() = (batch_size, output_size) return out def init_hidden(self, device): hidden = torch.zeros(1, self.batch_size, self.hidden_size, device=device) return hidden
次に、前章と同様にDataset
を作成するクラスを定義します。
今回は、テキストとラベルを受け取り、テキストを指定したtokenizer
でID化した後、それぞれをTensor型で出力する機能を持たせます。
from torch.utils.data import Dataset class CreateDataset(Dataset): def __init__(self, X, y, tokenizer): self.X = X self.y = y self.tokenizer = tokenizer def __len__(self): # len(Dataset)で返す値を指定 return len(self.y) def __getitem__(self, index): # Dataset[index]で返す値を指定 text = self.X[index] inputs = self.tokenizer(text) return { 'inputs': torch.tensor(inputs, dtype=torch.int64), 'labels': torch.tensor(self.y[index], dtype=torch.int64) }
上記を用いてDataset
を作成します。tokenizer
には、前問で定義した関数を指定します。
# ラベルベクトルの作成 category_dict = {'b': 0, 't': 1, 'e':2, 'm':3} y_train = train['CATEGORY'].map(lambda x: category_dict[x]).values y_valid = valid['CATEGORY'].map(lambda x: category_dict[x]).values y_test = test['CATEGORY'].map(lambda x: category_dict[x]).values # Datasetの作成 dataset_train = CreateDataset(train['TITLE'], y_train, tokenizer) dataset_valid = CreateDataset(valid['TITLE'], y_valid, tokenizer) dataset_test = CreateDataset(test['TITLE'], y_test, tokenizer) print(f'len(Dataset)の出力: {len(dataset_train)}') print('Dataset[index]の出力:') for var in dataset_train[1]: print(f' {var}: {dataset_train[1][var]}')
--- 出力 --- len(Dataset)の出力: 10684 Dataset[index]の出力: inputs: tensor([ 169, 539, 1, 683, 1237, 82, 279, 1898, 4199]) labels: 1
本問では学習しないため、Dataset
からinputs
をモデルに与え、Softmax後にそのまま出力を確認します。
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 # 辞書のID数 + パディングID EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 # モデルの定義 model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE) # 先頭10件の予測値取得 for i in range(10): X = dataset_train[i]['inputs'] print(torch.softmax(model(X.unsqueeze(0)), dim=-1))
--- 出力 --- tensor([[0.2667, 0.2074, 0.2974, 0.2285]], grad_fn=<SoftmaxBackward>) tensor([[0.1660, 0.3465, 0.2154, 0.2720]], grad_fn=<SoftmaxBackward>) tensor([[0.2133, 0.2987, 0.3097, 0.1783]], grad_fn=<SoftmaxBackward>) tensor([[0.2512, 0.4107, 0.1825, 0.1556]], grad_fn=<SoftmaxBackward>) tensor([[0.2784, 0.1307, 0.3715, 0.2194]], grad_fn=<SoftmaxBackward>) tensor([[0.2625, 0.1569, 0.2339, 0.3466]], grad_fn=<SoftmaxBackward>) tensor([[0.1331, 0.5129, 0.2220, 0.1319]], grad_fn=<SoftmaxBackward>) tensor([[0.2404, 0.1314, 0.2023, 0.4260]], grad_fn=<SoftmaxBackward>) tensor([[0.1162, 0.4576, 0.2588, 0.1674]], grad_fn=<SoftmaxBackward>) tensor([[0.4685, 0.1414, 0.2633, 0.1268]], grad_fn=<SoftmaxBackward>)
82. 確率的勾配降下法による学習
確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,問題81で構築したモデルを学習せよ.訓練データ上の損失と正解率,評価データ上の損失と正解率を表示しながらモデルを学習し,適当な基準(例えば10エポックなど)で終了させよ.
こちらも前章同様に、学習のための一連の処理をtrain_model
関数として定義します。
from torch.utils.data import DataLoader import time from torch import optim def calculate_loss_and_accuracy(model, dataset, device=None, criterion=None): """損失・正解率を計算""" dataloader = DataLoader(dataset, batch_size=1, shuffle=False) loss = 0.0 total = 0 correct = 0 with torch.no_grad(): for data in dataloader: # デバイスの指定 inputs = data['inputs'].to(device) labels = data['labels'].to(device) # 順伝播 outputs = model(inputs) # 損失計算 if criterion != None: loss += criterion(outputs, labels).item() # 正解率計算 pred = torch.argmax(outputs, dim=-1) total += len(inputs) correct += (pred == labels).sum().item() return loss / len(dataset), correct / total def train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, num_epochs, collate_fn=None, device=None): """モデルの学習を実行し、損失・正解率のログを返す""" # デバイスの指定 model.to(device) # dataloaderの作成 dataloader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, collate_fn=collate_fn) dataloader_valid = DataLoader(dataset_valid, batch_size=1, shuffle=False) # スケジューラの設定 scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, num_epochs, eta_min=1e-5, last_epoch=-1) # 学習 log_train = [] log_valid = [] for epoch in range(num_epochs): # 開始時刻の記録 s_time = time.time() # 訓練モードに設定 model.train() for data in dataloader_train: # 勾配をゼロで初期化 optimizer.zero_grad() # 順伝播 + 誤差逆伝播 + 重み更新 inputs = data['inputs'].to(device) labels = data['labels'].to(device) outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # 評価モードに設定 model.eval() # 損失と正解率の算出 loss_train, acc_train = calculate_loss_and_accuracy(model, dataset_train, device, criterion=criterion) loss_valid, acc_valid = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion) log_train.append([loss_train, acc_train]) log_valid.append([loss_valid, acc_valid]) # チェックポイントの保存 torch.save({'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict()}, f'checkpoint{epoch + 1}.pt') # 終了時刻の記録 e_time = time.time() # ログを出力 print(f'epoch: {epoch + 1}, loss_train: {loss_train:.4f}, accuracy_train: {acc_train:.4f}, loss_valid: {loss_valid:.4f}, accuracy_valid: {acc_valid:.4f}, {(e_time - s_time):.4f}sec') # 検証データの損失が3エポック連続で低下しなかった場合は学習終了 if epoch > 2 and log_valid[epoch - 3][0] <= log_valid[epoch - 2][0] <= log_valid[epoch - 1][0] <= log_valid[epoch][0]: break # スケジューラを1ステップ進める scheduler.step() return {'train': log_train, 'valid': log_valid}
さらに、ログを可視化するための関数も定義しておきます。
import numpy as np from matplotlib import pyplot as plt def visualize_logs(log): fig, ax = plt.subplots(1, 2, figsize=(15, 5)) ax[0].plot(np.array(log['train']).T[0], label='train') ax[0].plot(np.array(log['valid']).T[0], label='valid') ax[0].set_xlabel('epoch') ax[0].set_ylabel('loss') ax[0].legend() ax[1].plot(np.array(log['train']).T[1], label='train') ax[1].plot(np.array(log['valid']).T[1], label='valid') ax[1].set_xlabel('epoch') ax[1].set_ylabel('accuracy') ax[1].legend() plt.show()
パラメータを設定し、モデルを学習します。
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 LEARNING_RATE = 1e-3 BATCH_SIZE = 1 NUM_EPOCHS = 10 # モデルの定義 model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS)
--- 出力 --- epoch: 1, loss_train: 1.0954, accuracy_train: 0.5356, loss_valid: 1.1334, accuracy_valid: 0.5015, 86.4033sec epoch: 2, loss_train: 1.0040, accuracy_train: 0.6019, loss_valid: 1.0770, accuracy_valid: 0.5516, 85.2816sec epoch: 3, loss_train: 0.8813, accuracy_train: 0.6689, loss_valid: 0.9793, accuracy_valid: 0.6287, 78.9026sec epoch: 4, loss_train: 0.7384, accuracy_train: 0.7364, loss_valid: 0.8498, accuracy_valid: 0.7058, 78.4496sec epoch: 5, loss_train: 0.6427, accuracy_train: 0.7696, loss_valid: 0.7878, accuracy_valid: 0.7253, 83.4453sec epoch: 6, loss_train: 0.5730, accuracy_train: 0.7942, loss_valid: 0.7378, accuracy_valid: 0.7470, 79.6968sec epoch: 7, loss_train: 0.5221, accuracy_train: 0.8064, loss_valid: 0.7058, accuracy_valid: 0.7530, 79.7377sec epoch: 8, loss_train: 0.4924, accuracy_train: 0.8173, loss_valid: 0.7017, accuracy_valid: 0.7605, 78.2168sec epoch: 9, loss_train: 0.4800, accuracy_train: 0.8234, loss_valid: 0.7014, accuracy_valid: 0.7575, 77.8689sec epoch: 10, loss_train: 0.4706, accuracy_train: 0.8253, loss_valid: 0.6889, accuracy_valid: 0.7650, 79.4202sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train) _, acc_test = calculate_loss_and_accuracy(model, dataset_test) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.825 正解率(評価データ):0.773
83. ミニバッチ化・GPU上での学習
問題82のコードを改変し,事例ごとに損失・勾配を計算して学習を行えるようにせよ(の値は適当に選べ).また,GPU上で学習を実行せよ.
現在は文ごとに系列長が異なりますが、ミニバッチとしてまとめるには系列長を揃える必要があります。
そこで、複数の文の最大系列長に合わせてパディングする機能を持つPadsequence
クラスを新たに定義します。これをDataloader
の引数collate_fn
に与えることで、ミニバッチを取り出すごとに系列長を揃える処理を実現することができます。
class Padsequence(): """Dataloaderからミニバッチを取り出すごとに最大系列長でパディング""" def __init__(self, padding_idx): self.padding_idx = padding_idx def __call__(self, batch): sorted_batch = sorted(batch, key=lambda x: x['inputs'].shape[0], reverse=True) sequences = [x['inputs'] for x in sorted_batch] sequences_padded = torch.nn.utils.rnn.pad_sequence(sequences, batch_first=True, padding_value=self.padding_idx) labels = torch.LongTensor([x['labels'] for x in sorted_batch]) return {'inputs': sequences_padded, 'labels': labels}
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 LEARNING_RATE = 5e-2 BATCH_SIZE = 32 NUM_EPOCHS = 10 # モデルの定義 model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) # デバイスの指定 device = torch.device('cuda') # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)
--- 出力 --- epoch: 1, loss_train: 1.2605, accuracy_train: 0.3890, loss_valid: 1.2479, accuracy_valid: 0.4162, 12.1096sec epoch: 2, loss_train: 1.2492, accuracy_train: 0.4246, loss_valid: 1.2541, accuracy_valid: 0.4424, 12.0607sec epoch: 3, loss_train: 1.2034, accuracy_train: 0.4795, loss_valid: 1.2220, accuracy_valid: 0.4686, 11.8881sec epoch: 4, loss_train: 1.1325, accuracy_train: 0.5392, loss_valid: 1.1542, accuracy_valid: 0.5210, 12.2269sec epoch: 5, loss_train: 1.0543, accuracy_train: 0.6214, loss_valid: 1.0623, accuracy_valid: 0.6175, 11.8767sec epoch: 6, loss_train: 1.0381, accuracy_train: 0.6316, loss_valid: 1.0556, accuracy_valid: 0.6145, 11.9757sec epoch: 7, loss_train: 1.0546, accuracy_train: 0.6165, loss_valid: 1.0806, accuracy_valid: 0.5913, 12.0352sec epoch: 8, loss_train: 0.9924, accuracy_train: 0.6689, loss_valid: 1.0150, accuracy_valid: 0.6587, 11.9090sec epoch: 9, loss_train: 1.0123, accuracy_train: 0.6517, loss_valid: 1.0482, accuracy_valid: 0.6310, 12.0953sec epoch: 10, loss_train: 1.0036, accuracy_train: 0.6623, loss_valid: 1.0319, accuracy_valid: 0.6504, 11.9331sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train, device) _, acc_test = calculate_loss_and_accuracy(model, dataset_test, device) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.662 正解率(評価データ):0.649
84. 単語ベクトルの導入
事前学習済みの単語ベクトル(例えば,Google Newsデータセット(約1,000億単語)での学習済み単語ベクトル)で単語埋め込みを初期化し,学習せよ.
前章と同様に事前学習済み単語ベクトルをダウンロードします。
# 学習済み単語ベクトルのダウンロード import gdown url = 'https://drive.google.com/uc?id=0B7XkCwpI5KDYNlNUTTlSS21pQmM' output = 'GoogleNews-vectors-negative300.bin.gz' gdown.download(url, output, quiet=False)
事前学習済み単語ベクトルをモデルに利用する場合、その単語をすべて利用する方法(辞書を置き換える方法)と、手元のデータの辞書はそのまま利用し、それらの単語ベクトルの初期値としてのみ利用する方法があります。 今回は後者の方法を採用し、すでに作成している辞書に対応する単語ベクトルを抽出します。
from gensim.models import KeyedVectors # 学習済みモデルのロード model = KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300.bin.gz', binary=True) # 学習済み単語ベクトルの取得 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 weights = np.zeros((VOCAB_SIZE, EMB_SIZE)) words_in_pretrained = 0 for i, word in enumerate(word2id.keys()): try: weights[i] = model[word] words_in_pretrained += 1 except KeyError: weights[i] = np.random.normal(scale=0.4, size=(EMB_SIZE,)) weights = torch.from_numpy(weights.astype((np.float32))) print(f'学習済みベクトル利用単語数: {words_in_pretrained} / {VOCAB_SIZE}') print(weights.size())
--- 出力 --- 学習済みベクトル利用単語数: 9174 / 9406 torch.Size([9406, 300])
ネットワークの埋め込み層に初期値を設定できるように変更します。 また、次の問題用に双方向化、多層化のための設定も追加しておきます。
class RNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size, num_layers, emb_weights=None, bidirectional=False): super().__init__() self.hidden_size = hidden_size self.num_layers = num_layers self.num_directions = bidirectional + 1 # 単方向:1、双方向:2 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.rnn = nn.RNN(emb_size, hidden_size, num_layers, nonlinearity='tanh', bidirectional=bidirectional, batch_first=True) self.fc = nn.Linear(hidden_size * self.num_directions, output_size) def forward(self, x): self.batch_size = x.size()[0] hidden = self.init_hidden(x.device) # h0のゼロベクトルを作成 emb = self.emb(x) # emb.size() = (batch_size, seq_len, emb_size) out, hidden = self.rnn(emb, hidden) # out.size() = (batch_size, seq_len, hidden_size * num_directions) out = self.fc(out[:, -1, :]) # out.size() = (batch_size, output_size) return out def init_hidden(self, device): hidden = torch.zeros(self.num_layers * self.num_directions, self.batch_size, self.hidden_size, device=device) return hidden
埋め込み層の初期値を指定して学習します。
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 NUM_LAYERS = 1 LEARNING_RATE = 5e-2 BATCH_SIZE = 32 NUM_EPOCHS = 10 # モデルの定義 model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) # デバイスの指定 device = torch.device('cuda') # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)
--- 出力 --- epoch: 1, loss_train: 1.1655, accuracy_train: 0.4270, loss_valid: 1.1839, accuracy_valid: 0.4244, 9.7483sec epoch: 2, loss_train: 1.1555, accuracy_train: 0.4635, loss_valid: 1.1404, accuracy_valid: 0.4865, 9.7553sec epoch: 3, loss_train: 1.0189, accuracy_train: 0.6263, loss_valid: 1.0551, accuracy_valid: 0.6085, 10.0445sec epoch: 4, loss_train: 1.0377, accuracy_train: 0.6221, loss_valid: 1.0947, accuracy_valid: 0.5951, 10.1138sec epoch: 5, loss_train: 1.0392, accuracy_train: 0.6082, loss_valid: 1.0776, accuracy_valid: 0.5921, 9.8540sec epoch: 6, loss_train: 1.0447, accuracy_train: 0.6087, loss_valid: 1.1020, accuracy_valid: 0.5793, 9.8598sec epoch: 7, loss_train: 0.9999, accuracy_train: 0.6270, loss_valid: 1.0519, accuracy_valid: 0.6108, 9.7565sec epoch: 8, loss_train: 0.9539, accuracy_train: 0.6557, loss_valid: 1.0092, accuracy_valid: 0.6385, 9.7457sec epoch: 9, loss_train: 0.9287, accuracy_train: 0.6674, loss_valid: 0.9806, accuracy_valid: 0.6430, 9.6464sec epoch: 10, loss_train: 0.9456, accuracy_train: 0.6593, loss_valid: 1.0029, accuracy_valid: 0.6377, 9.6835sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train, device) _, acc_test = calculate_loss_and_accuracy(model, dataset_test, device) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.659 正解率(評価データ):0.645
85. 双方向RNN・多層化
順方向と逆方向のRNNの両方を用いて入力テキストをエンコードし,モデルを学習せよ.
ただし,はそれぞれ,順方向および逆方向のRNNで求めた時刻の隠れ状態ベクトル,は入力と次時刻の隠れ状態から前状態を計算するRNNユニット,は隠れ状態ベクトルからカテゴリを予測するための行列,はバイアス項である.また,]はベクトルとの連結を表す。
さらに,双方向RNNを多層化して実験せよ.
双方向を指定する引数であるbidirectional
をTrue
とし、またNUM_LAYERS
を2
に設定して学習を実行します。
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 HIDDEN_SIZE = 50 NUM_LAYERS = 2 LEARNING_RATE = 5e-2 BATCH_SIZE = 32 NUM_EPOCHS = 10 # モデルの定義 model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights, bidirectional=True) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) # デバイスの指定 device = torch.device('cuda') # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)
--- 出力 --- epoch: 1, loss_train: 1.1731, accuracy_train: 0.4307, loss_valid: 1.1915, accuracy_valid: 0.4274, 19.3181sec epoch: 2, loss_train: 1.0395, accuracy_train: 0.6116, loss_valid: 1.0555, accuracy_valid: 0.5996, 18.8118sec epoch: 3, loss_train: 1.0529, accuracy_train: 0.5899, loss_valid: 1.0832, accuracy_valid: 0.5696, 18.9088sec epoch: 4, loss_train: 0.9831, accuracy_train: 0.6351, loss_valid: 1.0144, accuracy_valid: 0.6235, 18.8913sec epoch: 5, loss_train: 1.0622, accuracy_train: 0.5797, loss_valid: 1.1142, accuracy_valid: 0.5487, 19.0636sec epoch: 6, loss_train: 1.0463, accuracy_train: 0.5741, loss_valid: 1.0972, accuracy_valid: 0.5367, 19.0612sec epoch: 7, loss_train: 1.0056, accuracy_train: 0.6102, loss_valid: 1.0485, accuracy_valid: 0.5898, 19.0420sec epoch: 8, loss_train: 0.9724, accuracy_train: 0.6294, loss_valid: 1.0278, accuracy_valid: 0.6093, 19.3077sec epoch: 9, loss_train: 0.9469, accuracy_train: 0.6371, loss_valid: 0.9943, accuracy_valid: 0.6160, 19.2803sec epoch: 10, loss_train: 0.9343, accuracy_train: 0.6451, loss_valid: 0.9867, accuracy_valid: 0.6235, 19.0755sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train, device) _, acc_test = calculate_loss_and_accuracy(model, dataset_test, device) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.645 正解率(評価データ):0.634
86. 畳み込みニューラルネットワーク (CNN)
ID番号で表現された単語列がある.ただし,は単語列の長さ,は単語のID番号のone-hot表記である(は単語の総数である).畳み込みニューラルネットワーク(CNN: Convolutional Neural Network)を用い,単語列からカテゴリを予測するモデルを実装せよ.
ただし,畳み込みニューラルネットワークの構成は以下の通りとする.
- 単語埋め込みの次元数:
- 畳み込みのフィルターのサイズ: 3 トークン
- 畳み込みのストライド: 1 トークン
- 畳み込みのパディング: あり
- 畳み込み演算後の各時刻のベクトルの次元数:
- 畳み込み演算後に最大値プーリング(max pooling)を適用し,入力文を次元の隠れベクトルで表現 すなわち,時刻の特徴ベクトルは次式で表される.
ただし,はCNNのパラメータ,は活性化関数(例えばやReLUなど),]はベクトルの連結である.なお,行列の列数がになるのは,3個のトークンの単語埋め込みを連結したものに対して,線形変換を行うためである. 最大値プーリングでは,特徴ベクトルの次元毎に全時刻における最大値を取り,入力文書の特徴ベクトルを求める.]でベクトルの番目の次元の値を表すことにすると,最大値プーリングは次式で表される.
最後に,入力文書の特徴ベクトルに行列とバイアス項による線形変換とソフトマックス関数を適用し,カテゴリを予測する.
なお,この問題ではモデルの学習を行わず,ランダムに初期化された重み行列でを計算するだけでよい.
指定のネットワークを実装します。
埋め込み層に続き、nn.Conv2d
で畳み込みを計算します。max_pool
で系列長方向に最大値を取得しており、この部分で文単位にベクトルが集約されています。
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.3) self.fc = nn.Linear(out_channels, output_size) def forward(self, x): # x.size() = (batch_size, seq_len) emb = self.emb(x).unsqueeze(1) # emb.size() = (batch_size, 1, seq_len, emb_size) conv = self.conv(emb) # conv.size() = (batch_size, out_channels, seq_len, 1) act = F.relu(conv.squeeze(3)) # act.size() = (batch_size, out_channels, seq_len) max_pool = F.max_pool1d(act, act.size()[2]) # max_pool.size() = (batch_size, out_channels, 1) -> seq_len方向に最大値を取得 out = self.fc(self.drop(max_pool.squeeze(2))) # out.size() = (batch_size, output_size) return out
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) 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) # 先頭10件の予測値取得 for i in range(10): X = dataset_train[i]['inputs'] print(torch.softmax(model(X.unsqueeze(0)), dim=-1))
--- 出力 --- tensor([[0.2607, 0.2267, 0.2121, 0.3006]], grad_fn=<SoftmaxBackward>) tensor([[0.2349, 0.2660, 0.2462, 0.2529]], grad_fn=<SoftmaxBackward>) tensor([[0.2305, 0.2649, 0.2099, 0.2948]], grad_fn=<SoftmaxBackward>) tensor([[0.2569, 0.2409, 0.2418, 0.2604]], grad_fn=<SoftmaxBackward>) tensor([[0.2610, 0.2149, 0.2355, 0.2886]], grad_fn=<SoftmaxBackward>) tensor([[0.2627, 0.2363, 0.2388, 0.2622]], grad_fn=<SoftmaxBackward>) tensor([[0.2694, 0.2434, 0.2224, 0.2648]], grad_fn=<SoftmaxBackward>) tensor([[0.2423, 0.2465, 0.2365, 0.2747]], grad_fn=<SoftmaxBackward>) tensor([[0.2591, 0.2695, 0.2468, 0.2246]], grad_fn=<SoftmaxBackward>) tensor([[0.2794, 0.2465, 0.2234, 0.2507]], grad_fn=<SoftmaxBackward>)
87. 確率的勾配降下法によるCNNの学習
確率的勾配降下法(SGD: Stochastic Gradient Descent)を用いて,問題86で構築したモデルを学習せよ.訓練データ上の損失と正解率,評価データ上の損失と正解率を表示しながらモデルを学習し,適当な基準(例えば10エポックなど)で終了させよ.
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = 300 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 OUT_CHANNELS = 100 KERNEL_HEIGHTS = 3 STRIDE = 1 PADDING = 1 LEARNING_RATE = 5e-2 BATCH_SIZE = 64 NUM_EPOCHS = 10 # モデルの定義 model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) # デバイスの指定 device = torch.device('cuda') # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)
--- 出力 --- epoch: 1, loss_train: 1.0671, accuracy_train: 0.5543, loss_valid: 1.0744, accuracy_valid: 0.5726, 12.9214sec epoch: 2, loss_train: 0.9891, accuracy_train: 0.6594, loss_valid: 1.0148, accuracy_valid: 0.6452, 12.6483sec epoch: 3, loss_train: 0.9098, accuracy_train: 0.6928, loss_valid: 0.9470, accuracy_valid: 0.6729, 12.7305sec epoch: 4, loss_train: 0.8481, accuracy_train: 0.7139, loss_valid: 0.8956, accuracy_valid: 0.7028, 12.7967sec epoch: 5, loss_train: 0.8055, accuracy_train: 0.7250, loss_valid: 0.8634, accuracy_valid: 0.7096, 12.6543sec epoch: 6, loss_train: 0.7728, accuracy_train: 0.7361, loss_valid: 0.8425, accuracy_valid: 0.7141, 12.7423sec epoch: 7, loss_train: 0.7527, accuracy_train: 0.7396, loss_valid: 0.8307, accuracy_valid: 0.7216, 12.6718sec epoch: 8, loss_train: 0.7403, accuracy_train: 0.7432, loss_valid: 0.8227, accuracy_valid: 0.7246, 12.5854sec epoch: 9, loss_train: 0.7346, accuracy_train: 0.7447, loss_valid: 0.8177, accuracy_valid: 0.7216, 12.4846sec epoch: 10, loss_train: 0.7331, accuracy_train: 0.7448, loss_valid: 0.8167, accuracy_valid: 0.7231, 12.7443sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train, device) _, acc_test = calculate_loss_and_accuracy(model, dataset_test, device) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.745 正解率(評価データ):0.719
88. パラメータチューニング
問題85や問題87のコードを改変し,ニューラルネットワークの形状やハイパーパラメータを調整しながら,高性能なカテゴリ分類器を構築せよ.
今回はConvolutional Neural Networks for Sentence Classificationで提案されたTextCNNをシンプルにしたネットワークを試してみます。 前問までのCNNでは幅が3のフィルターのみを学習していましたが、このネットワークでは2、3、4の3種類の幅のフィルターを利用します。
from torch.nn import functional as F class textCNN(nn.Module): def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, conv_params, drop_rate, 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.convs = nn.ModuleList([nn.Conv2d(1, out_channels, (kernel_height, emb_size), padding=(padding, 0)) for kernel_height, padding in conv_params]) self.drop = nn.Dropout(drop_rate) self.fc = nn.Linear(len(conv_params) * out_channels, output_size) def forward(self, x): # x.size() = (batch_size, seq_len) emb = self.emb(x).unsqueeze(1) # emb.size() = (batch_size, 1, seq_len, emb_size) conv = [F.relu(conv(emb)).squeeze(3) for i, conv in enumerate(self.convs)] # conv[i].size() = (batch_size, out_channels, seq_len + padding * 2 - kernel_height + 1) max_pool = [F.max_pool1d(i, i.size(2)) for i in conv] # max_pool[i].size() = (batch_size, out_channels, 1) -> seq_len方向に最大値を取得 max_pool_cat = torch.cat(max_pool, 1) # max_pool_cat.size() = (batch_size, len(conv_params) * out_channels, 1) -> フィルター別の結果を結合 out = self.fc(self.drop(max_pool_cat.squeeze(2))) # out.size() = (batch_size, output_size) return out
また、パラメータのチューニングには第6章と同様にoptunaを使います。
!pip install optuna
import optuna def objective(trial): # チューニング対象パラメータのセット emb_size = int(trial.suggest_discrete_uniform('emb_size', 100, 400, 100)) out_channels = int(trial.suggest_discrete_uniform('out_channels', 50, 200, 50)) drop_rate = trial.suggest_discrete_uniform('drop_rate', 0.0, 0.5, 0.1) learning_rate = trial.suggest_loguniform('learning_rate', 5e-4, 5e-2) momentum = trial.suggest_discrete_uniform('momentum', 0.5, 0.9, 0.1) batch_size = int(trial.suggest_discrete_uniform('batch_size', 16, 128, 16)) # 固定パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 CONV_PARAMS = [[2, 0], [3, 1], [4, 2]] NUM_EPOCHS = 30 # モデルの定義 model = textCNN(VOCAB_SIZE, emb_size, PADDING_IDX, OUTPUT_SIZE, out_channels, CONV_PARAMS, drop_rate, emb_weights=weights) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum) # デバイスの指定 device = torch.cuda.set_device(0) # モデルの学習 log = train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device) # 損失の算出 loss_valid, _ = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion) return loss_valid
パラメータ探索を実行します。
# 最適化 study = optuna.create_study() study.optimize(objective, timeout=7200) # 結果の表示 print('Best trial:') trial = study.best_trial print(' Value: {:.3f}'.format(trial.value)) print(' Params: ') for key, value in trial.params.items(): print(' {}: {}'.format(key, value))
--- 出力 --- Best trial: Value: 0.469 Params: emb_size: 300.0 out_channels: 100.0 drop_rate: 0.4 learning_rate: 0.013345934577557608 momentum: 0.8 batch_size: 32.0
探索したパラメータでモデルを学習します。
# パラメータの設定 VOCAB_SIZE = len(set(word2id.values())) + 1 EMB_SIZE = int(trial.params['emb_size']) PADDING_IDX = len(set(word2id.values())) OUTPUT_SIZE = 4 OUT_CHANNELS = int(trial.params['out_channels']) CONV_PARAMS = [[2, 0], [3, 1], [4, 2]] DROP_RATE = trial.params['drop_rate'] LEARNING_RATE = trial.params['learning_rate'] BATCH_SIZE = int(trial.params['batch_size']) NUM_EPOCHS = 30 # モデルの定義 model = textCNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, CONV_PARAMS, DROP_RATE, emb_weights=weights) print(model) # 損失関数の定義 criterion = nn.CrossEntropyLoss() # オプティマイザの定義 optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE, momentum=0.9) # デバイスの指定 device = torch.cuda.set_device(0) # モデルの学習 log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)
--- 出力 --- textCNN( (emb): Embedding(9406, 300, padding_idx=9405) (convs): ModuleList( (0): Conv2d(1, 100, kernel_size=(2, 300), stride=(1, 1)) (1): Conv2d(1, 100, kernel_size=(3, 300), stride=(1, 1), padding=(1, 0)) (2): Conv2d(1, 100, kernel_size=(4, 300), stride=(1, 1), padding=(2, 0)) ) (drop): Dropout(p=0.4, inplace=False) (fc): Linear(in_features=300, out_features=4, bias=True) ) epoch: 1, loss_train: 0.7908, accuracy_train: 0.7239, loss_valid: 0.8660, accuracy_valid: 0.6901, 12.2279sec epoch: 2, loss_train: 0.5800, accuracy_train: 0.7944, loss_valid: 0.7384, accuracy_valid: 0.7485, 12.1637sec epoch: 3, loss_train: 0.3951, accuracy_train: 0.8738, loss_valid: 0.6189, accuracy_valid: 0.7919, 12.1612sec epoch: 4, loss_train: 0.2713, accuracy_train: 0.9217, loss_valid: 0.5499, accuracy_valid: 0.8136, 12.1877sec epoch: 5, loss_train: 0.1913, accuracy_train: 0.9593, loss_valid: 0.5176, accuracy_valid: 0.8293, 12.1722sec epoch: 6, loss_train: 0.1322, accuracy_train: 0.9749, loss_valid: 0.5042, accuracy_valid: 0.8234, 12.4483sec epoch: 7, loss_train: 0.1033, accuracy_train: 0.9807, loss_valid: 0.4922, accuracy_valid: 0.8323, 12.1556sec epoch: 8, loss_train: 0.0723, accuracy_train: 0.9943, loss_valid: 0.4900, accuracy_valid: 0.8308, 12.0309sec epoch: 9, loss_train: 0.0537, accuracy_train: 0.9966, loss_valid: 0.4903, accuracy_valid: 0.8346, 11.9471sec epoch: 10, loss_train: 0.0414, accuracy_train: 0.9966, loss_valid: 0.4801, accuracy_valid: 0.8421, 11.9275sec epoch: 11, loss_train: 0.0366, accuracy_train: 0.9978, loss_valid: 0.4943, accuracy_valid: 0.8406, 11.9691sec epoch: 12, loss_train: 0.0292, accuracy_train: 0.9983, loss_valid: 0.4839, accuracy_valid: 0.8436, 11.9665sec epoch: 13, loss_train: 0.0271, accuracy_train: 0.9982, loss_valid: 0.5042, accuracy_valid: 0.8421, 11.9634sec epoch: 14, loss_train: 0.0222, accuracy_train: 0.9986, loss_valid: 0.4912, accuracy_valid: 0.8458, 11.9298sec epoch: 15, loss_train: 0.0194, accuracy_train: 0.9988, loss_valid: 0.4925, accuracy_valid: 0.8436, 11.9375sec epoch: 16, loss_train: 0.0176, accuracy_train: 0.9988, loss_valid: 0.5074, accuracy_valid: 0.8451, 11.9333sec epoch: 17, loss_train: 0.0163, accuracy_train: 0.9991, loss_valid: 0.5124, accuracy_valid: 0.8436, 11.9137sec
# ログの可視化 visualize_logs(log) # 正解率の算出 _, acc_train = calculate_loss_and_accuracy(model, dataset_train, device) _, acc_test = calculate_loss_and_accuracy(model, dataset_test, device) print(f'正解率(学習データ):{acc_train:.3f}') print(f'正解率(評価データ):{acc_test:.3f}')
--- 出力 --- 正解率(学習データ):0.999 正解率(評価データ):0.851
89. 事前学習済み言語モデルからの転移学習
事前学習済み言語モデル(例えばBERTなど)を出発点として,ニュース記事見出しをカテゴリに分類するモデルを構築せよ.
【PyTorch】BERTを用いた文書分類入門として別の記事に切り出しています。 ここでは、正解率の結果のみ転記します。
正解率(学習データ):0.993 正解率(評価データ):0.948
理解を深めるためのオススメ教材
全100問の解答はこちら