用金庸、古龍群俠名稱訓練 LSTM,會生成多麼奇葩的名字?
Magicly:之前翻譯了一篇介紹RNN的文章,一直沒看到作者寫新的介紹LSTM的blog,於是我又找了其他資料學習。本文先介紹一下LSTM,然後用LSTM在金庸、古龍的人名上做了訓練,可以生成新的武俠名字,如果有興趣的,還可以多搜集點人名,用於給小孩兒取名呢,哈哈,justforfun,大家玩得開心…
RNN回顧
RNN的出現是為了解決狀態記憶的問題,解決方法很簡單,每一個時間點t的隱藏狀態h(t)不再簡單地依賴於數據,還依賴於前一個時間節點t-1的隱藏狀態h(t-1)。可以看出這是一種遞歸定義(所以循環神經網路又叫遞歸神經網路Recursive Neural Network),h(t-1)又依賴於h(t-2),h(t-2)依賴於h(t-3)…所以h(t)依賴於之前每一個時間點的輸入,也就是說h(t)記住了之前所有的輸入。
上圖如果按時間展開,就可以看出RNN其實也就是普通神經網路在時間上的堆疊。
RNN問題:Long-Term Dependencies
一切似乎很完美,但是如果h(t)依賴於h(t - 1000),依賴路徑特別長,會導致計算梯度的時候出現梯度消失的問題,訓練時間很長根本沒法實際使用。下面是一個依賴路徑很長的例子:
1 我老家【成都】的。。。【此處省去500字】。。。我們那裡經常吃【火鍋】。。。
LSTM
Long Short Term Memory神經網路,也就是LSTM,由 Hochreiter & Schmidhuber於1997年發表。它的出現就是為了解決Long-Term Dependencies的問題,很來出現了很多改進版本,目前應用在相當多的領域(包括機器翻譯、對話機器人、語音識別、Image Caption等)。
標準的RNN里,重複的模塊里只是一個很簡單的結構,如下圖:
LSTM也是類似的鏈表結構,不過它的內部構造要複雜得多:
上圖中的圖標含義如下:
LSTM的核心思想是cell state(類似於hidden state,有LSTM變種把cell state和hidden state合并了, 比如GRU)和三種門:輸入門、忘記門、輸出門。
cell state每次作為輸入傳遞到下一個時間點,經過一些線性變化後繼續傳往再下一個時間點(我還沒看過原始論文,不知道為啥有了hidden state後還要cell state,好在確實有改良版將兩者合并了,所以暫時不去深究了)。
門的概念來自於電路設計(我沒學過,就不敢賣弄了)。LSTM里,門控制通過門之後信息能留下多少。如下圖,sigmoid層輸出[0, 1]的值,決定多少數據可以穿過門, 0表示誰都過不了,1表示全部通過。
下面我們來看看每個「門」到底在幹什麼。
首先我們要決定之前的cell state需要保留多少。 它根據h(t-1)和x(t)計算出一個[0, 1]的數,決定cell state保留多少,0表示全部丟棄,1表示全部保留。為什麼要丟棄呢,不是保留得越多越好么?假設LSTM在生成文章,裡面有小明和小紅,小明在看電視,小紅在廚房做飯。如果當前的主語是小明, ok,那LSTM應該輸出看電視相關的,比如找遙控器啊, 換台啊,如果主語已經切換到小紅了, 那麼接下來最好暫時把電視機忘掉,而輸出洗菜、醬油、電飯煲等。
第二步就是決定輸入多大程度上影響cell state。這個地方由兩部分構成, 一個用sigmoid函數計算出有多少數據留下,一個用tanh函數計算出一個候選C(t)。 這個地方就好比是主語從小明切換到小紅了, 電視機就應該切換到廚房。
然後我們把留下來的(t-1時刻的)cell state和新增加的合并起來,就得到了t時刻的cell state。
最後我們把cell state經過tanh壓縮到[-1, 1],然後輸送給輸出門([0, 1]決定輸出多少東西)。
現在也出了很多LSTM的變種, 有興趣的可以看這裡。另外,LSTM只是為了解決RNN的long-term dependencies,也有人從另外的角度來解決的,比如Clockwork RNNs by Koutnik, et al. (2014).
show me the code!
我用的Andrej Karpathy大神的代碼, 做了些小改動。這個代碼的好處是不依賴於任何深度學習框架,只需要有numpy就可以馬上run起來!
"""
Minimal character-level Vanilla RNN model. Written by Andrej Karpathy (@karpathy)
BSD License
"""
import numpy as np
# data I/O
data = open( input.txt , r ).read() # should be simple plain text file
all_names = set(data.split("
"))
chars = list(set(data))
data_size, vocab_size = len(data), len(chars)
print( data has %d characters, %d unique. % (data_size, vocab_size))
char_to_ix =
ix_to_char =
# print(char_to_ix, ix_to_char)
# hyperparameters
hidden_size = 100 # size of hidden layer of neurons
seq_length = 25 # number of steps to unroll the RNN for
learning_rate = 1e-1
# model parameters
Wxh = np.random.randn(hidden_size, vocab_size) * 0.01 # input to hidden
Whh = np.random.randn(hidden_size, hidden_size) * 0.01 # hidden to hidden
Why = np.random.randn(vocab_size, hidden_size) * 0.01 # hidden to output
bh = np.zeros((hidden_size, 1)) # hidden bias
by = np.zeros((vocab_size, 1)) # output bias
def lossFun(inputs, targets, hprev):
"""
inputs,targets are both list of integers.
hprev is Hx1 array of initial hidden state
returns the loss, gradients on model parameters, and last hidden state
"""
xs, hs, ys, ps = {}, {}, {}, {}
hs[-1] = np.copy(hprev)
loss = 0
# forward pass
for t in range(len(inputs)):
xs[t] = np.zeros((vocab_size, 1)) # encode in 1-of-k representation
xs[t][inputs[t]] = 1
hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh,
hs[t - 1]) + bh) # hidden state
# unnormalized log probabilities for next chars
ys[t] = np.dot(Why, hs[t]) + by
# probabilities for next chars
ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t]))
loss += -np.log(ps[t][targets[t], 0]) # softmax (cross-entropy loss)
# backward pass: compute gradients going backwards
dWxh, dWhh, dWhy = np.zeros_like(
Wxh), np.zeros_like(Whh), np.zeros_like(Why)
dbh, dby = np.zeros_like(bh), np.zeros_like(by)
dhnext = np.zeros_like(hs[0])
for t in reversed(range(len(inputs))):
dy = np.copy(ps[t])
# backprop into y. see
# http://cs231n.github.io/neural-networks-case-study/#grad if confused
# here
dy[targets[t]] -= 1
dWhy += np.dot(dy, hs[t].T)
dby += dy
dh = np.dot(Why.T, dy) + dhnext # backprop into h
dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity
dbh += dhraw
dWxh += np.dot(dhraw, xs[t].T)
dWhh += np.dot(dhraw, hs[t - 1].T)
dhnext = np.dot(Whh.T, dhraw)
for dparam in [dWxh, dWhh, dWhy, dbh, dby]:
# clip to mitigate exploding gradients
np.clip(dparam, -5, 5, out=dparam)
return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs) - 1]
def sample(h, seed_ix, n):
"""
sample a sequence of integers from the model
h is memory state, seed_ix is seed letter for first time step
"""
x = np.zeros((vocab_size, 1))
x[seed_ix] = 1
ixes = []
for t in range(n):
h = np.tanh(np.dot(Wxh, x) + np.dot(Whh, h) + bh)
y = np.dot(Why, h) + by
p = np.exp(y) / np.sum(np.exp(y))
ix = np.random.choice(range(vocab_size), p=p.ravel())
x = np.zeros((vocab_size, 1))
x[ix] = 1
ixes.append(ix)
return ixes
n, p = 0, 0
mWxh, mWhh, mWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why)
mbh, mby = np.zeros_like(bh), np.zeros_like(by) # memory variables for Adagrad
smooth_loss = -np.log(1.0 / vocab_size) * seq_length # loss at iteration 0
while True:
# prepare inputs (we re sweeping from left to right in steps seq_length
# long)
if p + seq_length + 1 >= len(data) or n == 0:
hprev = np.zeros((hidden_size, 1)) # reset RNN memory
p = 0 # go from start of data
inputs = [char_to_ix[ch] for ch in data[p:p + seq_length]]
targets = [char_to_ix[ch] for ch in data[p + 1:p + seq_length + 1]]
# sample from the model now and then
if n % 100 == 0:
sample_ix = sample(hprev, inputs[0], 200)
txt = .join(ix_to_char[ix] for ix in sample_ix)
print( ----
%s
---- % (txt, ))
# forward seq_length characters through the net and fetch gradient
loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev)
smooth_loss = smooth_loss * 0.999 + loss * 0.001
if n % 100 == 0:
print( iter %d, loss: %f % (n, smooth_loss)) # print progress
# perform parameter update with Adagrad
for param, dparam, mem in zip([Wxh, Whh, Why, bh, by],
[dWxh, dWhh, dWhy, dbh, dby],
[mWxh, mWhh, mWhy, mbh, mby]):
mem += dparam * dparam
param += -learning_rate * dparam /
np.sqrt(mem + 1e-8) # adagrad update
p += seq_length # move data pointer
n += 1 # iteration counter
if ((smooth_loss < 10) or (n >= 20000)):
sample_ix = sample(hprev, inputs[0], 2000)
txt = .join(ix_to_char[ix] for ix in sample_ix)
predicted_names = set(txt.split("
"))
new_names = predicted_names - all_names
print(new_names)
print( predicted names len: %d, new_names len: %d.
% (len(predicted_names), len(new_names)))
break
viewrawmin-char-rnn.pyhosted with byGitHub
然後從網上找了金庸小說的人名,做了些預處理,每行一個名字,保存到input.txt里,運行代碼就可以了。古龍的沒有找到比較全的名字, 只有這份武功排行榜,只有100多人。
下面是根據兩份名單訓練的結果,已經將完全一致的名字(比如段譽)去除了,所以下面的都是LSTM「新創作發明」的名字哈。來, 大家猜猜哪一個結果是金庸的, 哪一個是古龍的呢?
{ 姜曾鐵 , 袁南蘭", 石萬奉 , 郭萬嗔 , 蔡家 , 程伯芷", 汪鐵志 , 陳衣", 薛鐵 , 哈赤蔡師 , 殷飛虹", 鍾小硯 , 鳳一刀 , 寶蘭", 齊飛虹", 無若之 , 王老英", 鍾 , 鍾百勝 , 師 , 李沅震 , 曹蘭", 趙一刀 , 鍾靈四 , 宗家妹 , 崔樹勝 , 桑飛西", 上官公希轟", 劉之餘人童懷道 , 周雲鶴", 天", 鳳", 西靈素 , 大智虎師 , 阮徒忠 , 王兆能", 袁錚衣商寶鶴", 常伯鳳", 苗人大 , 倪不鳳", 蔡鐵 , 無伯志 , 鳳一弼", 曹鵲 , 黃賓", 曾鐵文 , 姬胡峰 , 李何豹", 上官鐵 , 童靈同 , 古若之 , 慕官景岳 , 崔百真 , 陳官 , 陳鍾 , 倪調峰", 妹沅刀 , 徐雙英", 任通督", 上官鐵褚容", 大劍太", 胡陽", 生 , 南仁鄭 , 南調 , 石雙震 , 海鐵山", 殷鶴真 , 司魚督", 德小 , 若四 , 武通濤 , 田青農 , 常塵英", 常不志 , 倪不濤 , 歐陽", 大提督", 胡玉堂 , 陳寶鶴", 南仁通四蔣赫侯 }
{ 邀三 , 熊貓開 , 鷹星 , 陸開 , 花", 薛玉羅平", 南宮主 , 南宮九 , 孫夫人 , 荊董滅", 鐵不愁 , 裴獨", 瑋劍 , 人", 陸小龍王紫無牙 , 連千里 , 仲先生 , 俞白", 方大 , 葉雷一魂 , 獨孤上紅", 葉憐花", 雷大歸 , 恕飛 , 白雙發", 邀一郎", 東樓", 鐵中十一點紅 , 鳳星真 , 無魏柳老鳳三 , 蕭貓兒", 東郭先鳳", 日孫 , 地先生 , 孟摘星 , 江小小鳳", 花雙樓 , 李佩 , 仇珏", 白壞剎", 燕悲情 , 姬悲雁 , 東郭大 , 謝曉陸鳳", 碧玉伯", 司實三 , 陸浪", 趙布雁 , 荊孤藍 , 憐燕南天", 蕭憐靜 , 龍布雁 , 東郭魚 , 司東郭金天", 薛嘯天 , 熊寶玉 , 無莫靜 , 柳羅李", 東官小魚", 漸飛 , 陸地魚 , 阿吹王 , 高傲", 蕭十三 , 龍童", 玉羅趙", 謝郎唐傲", 鐵夜帝 , 江小鳳", 孫玉玉夜 , 仇仲忍 , 蕭地孫 , 鐵莫棠 , 柴星夫", 展夫人 , 碧玉 , 老無魚", 鐵鐵花", 獨", 薛月宮九 , 老郭和尚 , 東郭大路陸上龍關飛 , 司藏", 李千 , 孫白人 , 南雙平", 王瑋", 姬原情 , 東郭大路孫玉 , 白玉羅生 , 高兒", 東珏天", 蕭王尚 , 九 , 鳳三靜 , 和空摘星 , 關吹雪", 上官官小鳳", 仇上官金飛 , 陸上龍嘯天 , 司空星魂 , 邀衣人 , 主", 李尋歡天 , 東情 , 玉夫隨", 趙小鳳", 東郭滅", 邀祟厚 , 司空星 }
感興趣的還可以用古代詩人、詞人等的名字來做訓練,大家機器好或者有時間的可以多訓練下,訓練得越多越準確。
總結
RNN由於具有記憶功能,在NLP、Speech、Computer Vision等諸多領域都展示了強大的力量。實際上,RNN是圖靈等價的。
1 If training vanilla neural nets is optimization over functions, training recurrent nets is optimization over programs.
LSTM是一種目前相當常用和實用的RNN演算法,主要解決了RNN的long-term dependencies問題。另外RNN也一直在產生新的研究,比如Attention機制。有空再介紹咯。。。
Refers
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
http://karpathy.github.io/2015/05/21/rnn-effectiveness/
https://gist.github.com/karpathy/d4dee566867f8291f086
https://deeplearning4j.org/lstm.html
※TensorFlow在工程項目中的應用 視頻+文字轉錄(下)
※TensorFlow在工程項目中的應用 公開課視頻+文字轉錄(上)
※賈揚清 F8 現場演講:從手機到大型伺服器集群,Caffe2 如何適用於不同設備和場景?
※人臉識別+手機推送,從此不再害怕老闆背後偷襲!
TAG:唯物 |
※RNN-生成古詩
※使用 GAN 網路生成名人照片
※TFBOYS和偶像練習生同齡,都是十八歲,為什麼偶像練習生成熟很多?
※LSGAN:最小二乘生成對抗網路
※Chatito-使用簡單的DSL為AI聊天機器人、NLP任務、命名實體識別或文本分類模型生成數據集
※EXO團魂最中心,Sohu隊長7年練習生成就EXO的靈魂
※五五開穢土轉生成功?盧本偉成功報名CSGO的DH大師賽!
※ACM MM最佳論文全文:通過多對抗訓練,從圖像生成詩歌
※結合通用和專用NMT的優勢,CMU為NMT引入「語境參數生成器」
※遊民App4.8更新!生成你的PSN名片 誰是白金大神?
※評價端到端生成式聊天系統,哈工大提出新型數據集 LSDSCC
※英偉達MIT黑科技:用AI生成模型快速構建虛擬世界
※TreeGAN:為序列生成任務構建有句法意識的GAN
※學界 | TreeGAN:為序列生成任務構建有句法意識的GAN
※文本挖掘:LDA文檔主題生成模型
※見過火系的暴鯉龍嗎?這個項目利用CycleGAN生成不同屬性神奇寶貝
※php 生成 RSS 類
※用英偉達StyleGAN生成老婆吧,他生成了一百多隻明日香
※谷歌AutoML創造者Quoc Le:未來最好的人工智慧模型將由機器生成
※UnifyID的研究人員使用神經網路生成風格化圖像擾亂API識別