自然語言處理(NLP)有很多有趣的應用,文本生成就是其中一個有趣的應用。
當一個機器學習模型工作在諸如循環神經網絡、LSTM-RNN、GRU等序列模型上時,它們可以生成輸入文本的下一個序列。
PyTorch提供了一組功能強大的工具和庫,這些工具和庫為這些基於NLP的任務增添了動力。它不僅需要較少的預處理量,而且加快了訓練過程。
在本文中,我們將在PyTorch中訓練幾種語言的循環神經網絡(RNN)。訓練成功後,RNN模型將預測屬於以輸入字母開頭的語言的名稱。
PyTorch實現
這個實現是在Google Colab中完成的,其中的數據集是從Google驅動器獲取的。所以,首先,我們將用Colab Notebook安裝Google驅動器。
from google.colab import drivedrive.mount('/content/gdrive')現在,我們將導入所有必需的庫。
from __future__ import unicode_literals, print_function, divisionfrom io import openimport globimport osimport unicodedataimport stringimport torchimport torch.nn as nnimport randomimport timeimport mathimport matplotlib.pyplot as pltimport matplotlib.ticker as ticker下面的代碼片段將讀取數據集。
all_let = string.ascii_letters + " .,;'-"n_let = len(all_let) + 1def getFiles(path): return glob.glob(path)# Unicode字符串到ASCIIdef 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('\n') 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] = linesn_ctg = len(all_ctg)在下一步中,我們將定義module類來生成名稱。該模塊將是一個循環神經網絡。
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 tensordef 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 tensordef 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.0005def 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)
下一步,該模型將訓練10000個epoch。
epochs = 100000print_every = 5000plot_every = 500all_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("\nKorean:-")sample_names('Korean', 'CMRS')print("\nRussian:-")sample_names('Russian', 'AJLN')print("\nVietnamese:-")sample_names('Vietnamese', 'LMT')
因此,正如我們在上面看到的,我們的模型已經生成了屬於語言類別的名稱,並從輸入字母開始。
參考文獻:
Trung Tran, 「Text Generation with Pytorch」.「NLP from scratch: Generating names with a character level RNN」, PyTorch Tutorial.Francesca Paulin, 「Character-Level LSTM in PyTorch」, Kaggle.