blog

テキスト生成のためのリカレントニューラルネットワークのPyTorch実装

自然言語処理には多くの興味深いアプリケーションがあり、テキスト生成もその一つです。\n機械学習モデルがリカレントニューラルネットワーク、LSTM-RNN、GRUなどのシーケンスモデルで動作する場合、入...

Jan 4, 2021 · 5 min. read
シェア

自然言語処理には多くの興味深いアプリケーションがあり、テキスト生成もその一つです。

機械学習モデルがリカレントニューラルネットワーク、LSTM-RNN、GRUなどのシーケンスモデルで動作する場合、それらは入力テキストの次のシーケンスを生成することができます。

PyTorchは、これらのNLPベースのタスクにパワーを与える強力なツールとライブラリのセットを提供します。前処理が少なくて済むだけでなく、学習プロセスも高速化します。

この記事では、リカレントニューラルネットワークをPyTorchで複数の言語について学習します。学習に成功すると、RNNモデルは入力文字で始まる言語に属する名前を予測します。

PyTorch

この実装はGoogle Colabで行われ、データセットはGoogle Driveから取得されます。まず、Colab Notebookを使ってGoogle Driveをインストールします。

from google.colab import drive
drive.mount('/content/gdrive')

これで、必要なライブラリがすべてインポートされます。

from __future__ import unicode_literals, print_function, division
from io import open
import glob
import os
import unicodedata
import string
import torch
import torch.nn as nn
import random
import time
import math
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

次のコード・スニペットはデータセットを読み込みます。

all_let = string.ascii_letters + " .,;'-"
n_let = len(all_let) + 1
def getFiles(path):
 return glob.glob(path)
# Unicode文字列をASCIIに変換する
def unicodeToAscii(s):
 return ''.join(
 c for c in unicodedata.normalize('NFD', s)
 if unicodedata.category(c) != 'Mn'
 and c in all_let
 )
# ファイルを読み込み、行に分割する
def getLines(filename):
 lines = open(filename, encoding='utf-8').read().strip().split('
')
 return [unicodeToAscii(line) for line in lines]
# catを作成する_lin辞書、各カテゴリの行のリストを格納する
cat_lin = {}
all_ctg = []
for filename in getFiles('gdrive/My Drive/Dataset/data/data/names/*.txt'):
 categ = os.path.splitext(os.path.basename(filename))[0]
 all_ctg.append(category)
 lines = getLines(filename)
 cat_lin[categ] = lines
n_ctg = len(all_ctg)

次のステップでは、モジュール・クラスを定義して名前を生成します。モジュールはリカレント・ニューラル・ネットワークになります。

class NameGeneratorModule(nn.Module):
 def __init__(self, inp_size, hid_size, op_size):
 super(NameGeneratorModule, self).__init__()
 self.hid_size = hid_size
 self.i2h = nn.Linear(n_ctg + inp_size + hid_size, hid_size)
 self.i2o = nn.Linear(n_ctg + inp_size + hid_size, op_size)
 self.o2o = nn.Linear(hid_size + op_size, op_size)
 self.dropout = nn.Dropout(0.1)
 self.softmax = nn.LogSoftmax(dim=1)
 def forward(self, category, input, hidden):
 inp_comb = torch.cat((category, input, hidden), 1)
 hidden = self.i2h(inp_comb)
 output = self.i2o(inp_comb)
 op_comb = torch.cat((hidden, output), 1)
 output = self.o2o(op_comb)
 output = self.dropout(output)
 output = self.softmax(output)
 return output, hidden
 def initHidden(self):
 return torch.zeros(1, self.hid_size)

以下の関数を使用して、リストから無作為に項目を選択したり、カテゴリから無作為に行を選択したりします。

def randChoice(l):
 return l[random.randint(0, len(l) - 1)]
def randTrainPair():
 category = randChoice(all_ctg)
 line = randChoice(cat_lin[category])
 return category, line

以下の関数は、RNNモジュールと互換性のある形式にデータを変換します。

def categ_Tensor(categ):
 li = all_ctg.index(categ)
 tensor = torch.zeros(1, n_ctg)
 tensor[0][li] = 1
 return tensor
def inp_Tensor(line):
 tensor = torch.zeros(len(line), 1, n_let)
 for li in range(len(line)):
 letter = line[li]
 tensor[li][0][all_let.find(letter)] = 1
 return tensor
def tgt_Tensor(line):
 letter_indexes = [all_let.find(line[li]) for li in range(1, len(line))]
 letter_id.append(n_let - 1) # EOS
 return torch.LongTensor(letter_id)

以下の関数は、カテゴリ、入力、ターゲットテンソルを持つ確率的学習例を作成します。

# 
criterion = nn.NLLLoss()
# 
lr_rate = 0.0005
def train(category_tensor, input_line_tensor, target_line_tensor):
 target_line_tensor.unsqueeze_(-1)
 hidden = rnn.initHidden()
 rnn.zero_grad()
 loss = 0
 for i in range(input_line_tensor.size(0)):
 output, hidden = rnn(category_tensor, input_line_tensor[i], hidden)
 l = criterion(output, target_line_tensor[i])
 loss += l
 loss.backward()
 for p in rnn.parameters():
 p.data.add_(p.grad.data, alpha=-lr_rate)
 return output, loss.item() / input_line_tensor.size(0)

トレーニング中の時間を表示するには、以下の関数を定義します。

def time_taken(since):
 now = time.time()
 s = now - since
 m = math.floor(s / 60)
 s -= m * 60
 return '%dm %ds' % (m, s)

次のステップでは、RNNモデルを定義します。

model = NameGenratorModule(n_let, 128, n_let)

定義されたRNNモデルのパラメータが表示されます。

print(model)

次のステップでは、モデルを10,000エポック学習させます。

epochs = 100000
print_every = 5000
plot_every = 500
all_losses = []
total_loss = 0 # 各反復でリセットする
start = time.time()
for iter in range(1, epochs + 1):
 output, loss = train(*rand_train_exp())
 total_loss += loss
 if iter % print_every == 0:
 print('Time: %s, Epoch: (%d - Total Iterations: %d%%), Loss: %.4f' % (time_taken(start), iter, iter / epochs * 100, loss))
 if iter % plot_every == 0:
 all_losses.append(total_loss / plot_every)
 total_loss = 0

トレーニングのロスが可視化されます。

plt.figure(figsize=(7,7))
plt.title("Loss")
plt.plot(all_losses)
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.show()

最後に、アルファベットで始まる文字が与えられた言語に属する名前を生成するモデルをテストします。

max_length = 20
# カテゴリーと開始文字の例
def sample_model(category, start_letter='A'):
 with torch.no_grad(): # no need to track history in sampling
 category_tensor = categ_Tensor(category)
 input = inp_Tensor(start_letter)
 hidden = NameGenratorModule.initHidden()
 output_name = start_letter
 for i in range(max_length):
 output, hidden = NameGenratorModule(category_tensor, input[0], hidden)
 topv, topi = output.topk(1)
 topi = topi[0][0]
 if topi == n_let - 1:
 break
 else:
 letter = all_let[topi]
 output_name += letter
 input = inp_Tensor(letter)
 return output_name
# カテゴリーからの複数のサンプルと複数の開始文字
def sample_names(category, start_letters='XYZ'):
 for start_letter in start_letters:
 print(sample_model(category, start_letter))

サンプルモデルは、言語と開始文字を指定して名前を生成します。

print("Italian:-")
sample_names('Italian', 'BPRT')
print("
Korean:-")
sample_names('Korean', 'CMRS')
print("
Russian:-")
sample_names('Russian', 'AJLN')
print("
Vietnamese:-")
sample_names('Vietnamese', 'LMT')

このように、このモデルは、入力文字から始まる言語カテゴリに属する名前を生成しています。

パンチャンAIブログサイトへようこそ:

Panchuangのブログリソースラウンドアップへようこそ:

Read next

TypeScriptの関数

複数の引数を同時に操作したい場合や、引数がいくつ渡されるかわからない場合があります。このような場合、argumentsを使用すると、渡されたすべての引数にアクセスできます。すべての引数を変数に集めることができ、残りの引数は無制限のオプション引数として扱われます。何もないこともありますが、いくつあってもかまいません。 オーバーロードされた定義と関数...

Jan 4, 2021 · 2 min read