起名字這個技術活,終於用Pytorch找到解決辦法了!

本文由集智小仙女發表在集智AI學園公眾號上,歡迎關注集智AI學園微信公眾號:swarmAI。

上節課我們使用深度學習技術創造了一個會寫嘻哈說唱詞的 AI,這節課我們使用類似的方法,再創建一個 AI國際起名大師

AI 起名大師專起外國名,你只需告訴他你想要個哪國的名字,AI 起名大師分分鐘給你想出一大堆名字出來!

下面都是是用 AI 起名大師生成的名字,有了它你還會為了起一個好的外國名而發愁嗎

RussiannRovakov Uantov ShavakovnnGermannGerren Ereng RoshernnSpanishnSalla Parer Allann

資源:

集智 AI 學園公眾號回復「AI起名大師」,獲取本節課的 Jupyter Notebook 文檔!

準備數據

做深度學習的第一步是把數據準備好。像之前一樣,我們先準備數據。

這次的數據是18個文本文件,每個文件以「國家名字」命名,其中存儲了這個國家的不同人名。

在讀取這些數據前,為了簡化神經網路的輸入參數規模,我們把各國各語言人名都轉化成用26個英文字母來表示,下面就是轉換的方法。

import globnimport unicodedatanimport stringnn# all_letters 即課支持列印的字元+標點符號nall_letters = string.ascii_letters + " .,;-"n# Plus EOS markernn_letters = len(all_letters) + 1nEOS = n_letters - 1nndef unicode_to_ascii(s):n return .join(n c for c in unicodedata.normalize(NFD, s)n if unicodedata.category(c) != Mnn and c in all_lettersn )nnprint(unicode_to_ascii("ONéàl"))nnnONealn

其中 all_letters 包含了我們數據集中所有可能出現的字元,也就是「字元表」。n_letters 是字元表的長度,在本例中長度為59。EOS 的索引號為58,它在字元表中沒有對應的字元,僅代表結束。

讀取數據

準備好處理數據的方法,下面就可以放心的讀取數據了。

我們建立一個列表 all_categories 用於存儲所有的國家名字。

建立一個字典 category_lines,以讀取的國名作為字典的索引,國名下存儲對應國別的名字。

# 按行讀取出文件中的名字,並返回包含所有名字的列表ndef read_lines(filename):n lines = open(filename).read().strip().split(n)n return [unicode_to_ascii(line) for line in lines]nnn# category_lines是一個字典n# 其中索引是國家名字,內容是從文件讀取出的這個國家的所有名字ncategory_lines = {}n# all_categories是一個列表n# 其中包含了所有的國家名字nall_categories = []n# 循環所有文件nfor filename in glob.glob(../data/names/*.txt):n # 從文件名中切割出國家名字n category = filename.split(/)[-1].split(.)[0]n # 將國家名字添加到列表中n all_categories.append(category)n # 讀取對應國別文件中所有的名字n lines = read_lines(filename)n # 將所有名字存儲在字典中對應的國別下n category_lines[category] = linesnn# 共有的國別數nn_categories = len(all_categories)nnprint(# categories: , n_categories, all_categories)nprint()nprint(# names: , category_lines[Russian][:10])nn# categories: 18 [Arabic, Chinese, Czech, Dutch, English, French, German, Greek, Irish, Italian, Japanese, Korean, Polish, Portuguese, Russian, Scottish, Spanish, Vietnamese]nn# names: [Ababko, Abaev, Abagyan, Abaidulin, Abaidullin, Abaimoff, Abaimov, Abakeliya, Abakovsky, Abakshin]n

現在我們的數據準備好了,可以搭建神經網路了!

搭建神經網路

這次使用的 RNN 神經網路整體結構上與之前相似,細節上增加了一部分內容。

在輸入層增加了 category,即名字的國別。這個 category 是以獨熱編碼向量(one-hot vector)的方式輸入的,再啰嗦一遍就是,作為長度為18的向量,代表自己國別的位置為1, 其它位置都為0。

我們搭建的神經網路的目的是生成「名字」,而「名字」就是一序列的字元。所以神經網路的輸出(output)代表的是字元表中的每個字元,能成為「下一個字元」的概率,概率最大的那個字元,即作為「下一個字元」。

另外,這次還加入了「第二層」神經網路,即 o2o 層,以增強神經網路的預測性能。在 o2o 中還包括一層「dropout」,它會將輸入「softmax」之前的數據隨機置0(在本例中為隨機置0.1)。「dropout」常被用來緩解「過擬合(overfitting)」的問題,因為它可以增加「混亂(chaos)」並提高採樣的多樣性。

建立神經網路的代碼比較簡單,並且網路的各個部分都在上圖中標識出來了。

import torchnimport torch.nn as nnnfrom torch.autograd import Variablennclass RNN(nn.Module):n def __init__(self, input_size, hidden_size, output_size):n super(RNN, self).__init__()n self.input_size = input_sizen self.hidden_size = hidden_sizen self.output_size = output_sizenn self.i2h = nn.Linear(n_categories + input_size + hidden_size, hidden_size)n self.i2o = nn.Linear(n_categories + input_size + hidden_size, output_size)n self.o2o = nn.Linear(hidden_size + output_size, output_size)n self.softmax = nn.LogSoftmax()nn def forward(self, category, input, hidden):n input_combined = torch.cat((category, input, hidden), 1)n hidden = self.i2h(input_combined)n output = self.i2o(input_combined)n output_combined = torch.cat((hidden, output), 1)n output = self.o2o(output_combined)n return output, hiddennn def init_hidden(self):n return Variable(torch.zeros(1, self.hidden_size))n

準備訓練

首先建立一個可以隨機選擇數據對 (category, line) 的方法,以方便訓練時調用。

import randomnndef random_training_pair():n # 隨機選擇一個國別名n category = random.choice(all_categories)n # 讀取這個國別名下的所有人名n line = random.choice(category_lines[category])n return category, linen

對於訓練過程中的每一步,或者說對於訓練數據中每個名字的每個字元來說,神經網路的輸入是 (category, current letter, hidden state),輸出是 (next letter, next hidden state)。所以在每 批次 的訓練中,我們都需要「一個國別」、「對應國別的一批名字」、「以及要預測的下一個字元」。

與上節課一樣,神經網路還是依據「當前的字元」預測「下一個字元」。比如對於「Kasparov」這個名字,創建的數據對是 ("K", "a"), ("a", "s"), ("s", "p"), ("p", "a"), ("a", "r"), ("r", "o"), ("o", "v"), ("v", "EOS")。

# 將名字所屬的國家名轉化為「獨熱向量」ndef make_category_input(category):n li = all_categories.index(category)n tensor = torch.zeros(1, n_categories)n tensor[0][li] = 1n return Variable(tensor)nn# 將一個名字轉化成矩陣Tensorn# 矩陣的每行為名字中每個字元的獨熱編碼向量ndef make_chars_input(chars):n tensor = torch.zeros(len(chars), n_letters)n # 遍歷每個名字中的每個字元n for ci in range(len(chars)):n char = chars[ci]n # 獨熱編碼n tensor[ci][all_letters.find(char)] = 1n # 增加一個維度n tensor = tensor.view(-1, 1, n_letters)n return Variable(tensor)nn# 將「目標」,也就是「下一個字元」轉化為Tensorn# 注意這裡最後以 EOS 作為結束標誌ndef make_target(line):n # 從第2個字元開始,取出每個字元的索引n letter_indexes = [all_letters.find(line[li]) for li in range(1, len(line))]n # 在最後加上 EOS 的索引n letter_indexes.append(n_letters - 1) # EOSn # 轉化成 LongTensorn tensor = torch.LongTensor(letter_indexes)n return Variable(tensor)n

同樣為了訓練時方便使用,我們建立一個 random_training_set 函數,以隨機選擇出數據集 (category, line) 並轉化成訓練需要的 Tensor: (category, input, target)。

def random_training_set():n # 隨機選擇數據集n category, line = random_training_pair()n # 轉化成對應 Tensorn category_input = make_category_input(category)n line_input = make_chars_input(line)n line_target = make_target(line)n return category_input, line_input, line_targetn

開始訓練!

與之前處理得分類問題不同,在分類問題中只有最後的輸出被使用。而在當前的 生成 名字的任務中,神經網路在每一步都會做預測,所以我們需要在每一步計算損失值。

PyTorch 非常易用,它允許我們只是簡單的把每一步計算的損失加起來,並在最後進行反向傳播。

def train(category_tensor, input_line_tensor, target_line_tensor):n hidden = rnn.init_hidden()n optimizer.zero_grad()n loss = 0nn for i in range(input_line_tensor.size()[0]):n output, hidden = rnn(category_tensor, input_line_tensor[i], hidden)n loss += criterion(output, target_line_tensor[i])nn loss.backward()n optimizer.step()nn return output, loss.data[0] / input_line_tensor.size()[0]n

我們定義 time_since 函數,它可以列印出訓練持續的時間。

import timenimport mathnndef time_since(t):n now = time.time()n s = now - tn m = math.floor(s / 60)n s -= m * 60n return %dm %ds % (m, s)n

訓練的過程與我們前幾節課一樣,就是調用訓練函數,再等上幾分鐘就好啦吼吼!

通過 plot_every 控制列印日誌的頻率,通過 all_losses 控制記錄繪圖數據的頻率,已經都是老套路啦!

n_epochs = 100000nprint_every = 5000nplot_every = 500nall_losses = []nloss_avg = 0 # Zero every plot_every epochs to keep a running averagenlearning_rate = 0.0005nnrnn = RNN(n_letters, 128, n_letters)noptimizer = torch.optim.Adam(rnn.parameters(), lr=learning_rate)ncriterion = nn.CrossEntropyLoss()nnstart = time.time()nnfor epoch in range(1, n_epochs + 1):n output, loss = train(*random_training_set())n loss_avg += lossnn if epoch % print_every == 0:n print(%s (%d %d%%) %.4f % (time_since(start), epoch, epoch / n_epochs * 100, loss))nn if epoch % plot_every == 0:n all_losses.append(loss_avg / plot_every)n loss_avg = 0n

0m 20s (5000 5%) 1.7640n0m 43s (10000 10%) 2.0930n1m 9s (15000 15%) 2.0813n1m 32s (20000 20%) 2.3847n1m 53s (25000 25%) 2.3773n2m 16s (30000 30%) 1.0473n2m 38s (35000 35%) 2.4157n2m 59s (40000 40%) 1.4604n3m 21s (45000 45%) 3.6522n3m 43s (50000 50%) 1.4112n4m 5s (55000 55%) 1.9039n4m 24s (60000 60%) 2.0797n4m 44s (65000 65%) 2.6220n5m 3s (70000 70%) 1.7693n5m 22s (75000 75%) 1.4734n5m 41s (80000 80%) 1.1522n6m 1s (85000 85%) 2.6431n6m 22s (90000 90%) 1.7703n6m 41s (95000 95%) 1.9069n7m 0s (100000 100%) 2.2563n

繪製觀察損失曲線

讓我們將訓練過程中記錄的損失繪製成一條曲線,觀察下神經網路學習的效果。

import matplotlib.pyplot as pltnimport matplotlib.ticker as tickern%matplotlib inlinennplt.figure()nplt.plot(all_losses)nn[<matplotlib.lines.Line2D at 0x114e8ec50>]n

正如所有的優秀的神經網路一樣^_^,損失值逐步降低並穩定在一個範圍中。

測試使用神經網路

既然神經網路訓練好了,那也就是說,我們餵給它第一個字元,他就能生成第二個字元,餵給它第二個字元,它就會生成第三個,這樣一直持續下去,直至生成 EOS 才結束。

那下面我們編寫 generate_one 函數以方便的使用神經網路生成我們想要的名字字元串,在這個函數里我們定義以下內容:

  • 建立輸入國別,開始字元,初始隱藏層狀態的 Tensor
  • 創建 output_str 變數,創建時其中只包含「開始字元」
  • 定義生成名字的長度最大不超過 max_length
    • 將當前字元傳入神經網路
    • 在輸出中選出預測的概率最大的下一個字元,同時取出當前的隱藏層狀態
    • 如果字元是 EOS,則生成結束
    • 如果是常規字元,則加入到 output_str 中並繼續下一個流程
  • 返回最終生成的名字字元串

max_length = 20n

# 通過指定國別名 categoryn# 以及開始字元 start_charn# 還有混亂度 temperature 來生成一個名字ndef generate_one(category, start_char=A, temperature=0.5):n category_input = make_category_input(category)n chars_input = make_chars_input(start_char)n hidden = rnn.init_hidden()nn output_str = start_charnn for i in range(max_length):n output, hidden = rnn(category_input, chars_input[0], hidden)nn # 這裡是將輸出轉化為一個多項式分布n output_dist = output.data.view(-1).div(temperature).exp()n # 從而可以根據混亂度 temperature 來選擇下一個字元n # 混亂度低,則趨向於選擇網路預測最大概率的那個字元n # 混亂度高,則趨向於隨機選擇字元n top_i = torch.multinomial(output_dist, 1)[0]nn # 生成字元是 EOS,則生成結束n if top_i == EOS:n breakn else:n char = all_letters[top_i]n output_str += charn chars_input = make_chars_input(char)nn return output_strnn# 再定義一個函數,方便每次生成多個名字ndef generate(category, start_chars=ABC):n for start_char in start_chars:n print(generate_one(category, start_char))n

generate(Russian, RUS)n

RainenUrlamovnSakhganynngenerate(German, GER)nnGrosnEchernRobernngenerate(Spanish, SPA)nnSalvannParenAlenn

看起來還不錯哈!不過這個模型還是有點簡單,我們以後還可以對它進行改進,發明出更多的玩法!

更多玩法和改進

本文中給出的是通過指定「國家名」生成「人名」,我們還可以通過改變訓練數據集,搞出其它的玩法,比如:

  • 通過小說 生成 小說中人物名字
  • 通過演講 生成 演講辭彙
  • 通過國家 生成 城市名

如果要考慮到改進:

  • 可以嘗試增加神經網路的規模,來提升預測的效果
  • 嘗試把中文字元加進來,讓神經網路可以生成中文的名字!
  • 如果要使用中文,注意要控制輸入層的規模,可以試試嵌入

如果您有任何關於Pytorch方面的問題,歡迎進【集智—清華】火炬群與大家交流探討,添加集智小助手微信swarmaAI,備註(pytorch交流群),小助手會拉你入群。

關注集智AI學園公眾號

獲取更多更有趣的AI教程吧!

搜索微信公眾號:swarmAI

集智AI學園QQ群:426390994

學園網站: campus.swarma.org

weixin.qq.com/r/FzpGXp3 (二維碼自動識別)


推薦閱讀:

從事深度學習等人工智慧研究在未來的職業發展前景如何?
什麼是稀疏特徵(Sparse Features)?
有了AWS,不需要自己配GPUs?做深度學習。?
圖森科技的演算法實習生和地平線的人工智慧演算法實習生如何選擇?

TAG:PyTorch | 深度学习DeepLearning | 人工智能 |