0
雷鋒網(wǎng)按:本文轉(zhuǎn)載自 Magicly 博客,獲作者授權(quán)。閱讀原文請見:http://magicly.me/2017/04/07/rnn-lstm-generate-name/?utm_source=tuicool&utm_medium=referral。
Magicly:之前翻譯了一篇介紹RNN的文章,一直沒看到作者寫新的介紹LSTM的blog,于是我又找了其他資料學習。本文先介紹一下LSTM,然后用LSTM在金庸、古龍的人名上做了訓練,可以生成新的武俠名字,如果有興趣的,還可以多搜集點人名,用于給小孩兒取名呢,哈哈,justforfun,大家玩得開心…
RNN的出現(xiàn)是為了解決狀態(tài)記憶的問題,解決方法很簡單,每一個時間點t的隱藏狀態(tài)h(t)不再簡單地依賴于數(shù)據(jù),還依賴于前一個時間節(jié)點t-1的隱藏狀態(tài)h(t-1)??梢钥闯鲞@是一種遞歸定義(所以循環(huán)神經(jīng)網(wǎng)絡又叫遞歸神經(jīng)網(wǎng)絡Recursive Neural Network),h(t-1)又依賴于h(t-2),h(t-2)依賴于h(t-3)…所以h(t)依賴于之前每一個時間點的輸入,也就是說h(t)記住了之前所有的輸入。
上圖如果按時間展開,就可以看出RNN其實也就是普通神經(jīng)網(wǎng)絡在時間上的堆疊。
一切似乎很完美,但是如果h(t)依賴于h(t - 1000),依賴路徑特別長,會導致計算梯度的時候出現(xiàn)梯度消失的問題,訓練時間很長根本沒法實際使用。下面是一個依賴路徑很長的例子:
1 我老家【成都】的。。。【此處省去500字】。。。我們那里經(jīng)常吃【火鍋】。。。
Long Short Term Memory神經(jīng)網(wǎng)絡,也就是LSTM,由 Hochreiter & Schmidhuber于1997年發(fā)表。它的出現(xiàn)就是為了解決Long-Term Dependencies的問題,很來出現(xiàn)了很多改進版本,目前應用在相當多的領(lǐng)域(包括機器翻譯、對話機器人、語音識別、Image Caption等)。
標準的RNN里,重復的模塊里只是一個很簡單的結(jié)構(gòu),如下圖:
LSTM也是類似的鏈表結(jié)構(gòu),不過它的內(nèi)部構(gòu)造要復雜得多:
上圖中的圖標含義如下:
LSTM的核心思想是cell state(類似于hidden state,有LSTM變種把cell state和hidden state合并了, 比如GRU)和三種門:輸入門、忘記門、輸出門。
cell state每次作為輸入傳遞到下一個時間點,經(jīng)過一些線性變化后繼續(xù)傳往再下一個時間點(我還沒看過原始論文,不知道為啥有了hidden state后還要cell state,好在確實有改良版將兩者合并了,所以暫時不去深究了)。
門的概念來自于電路設計(我沒學過,就不敢賣弄了)。LSTM里,門控制通過門之后信息能留下多少。如下圖,sigmoid層輸出[0, 1]的值,決定多少數(shù)據(jù)可以穿過門, 0表示誰都過不了,1表示全部通過。
下面我們來看看每個“門”到底在干什么。
首先我們要決定之前的cell state需要保留多少。 它根據(jù)h(t-1)和x(t)計算出一個[0, 1]的數(shù),決定cell state保留多少,0表示全部丟棄,1表示全部保留。為什么要丟棄呢,不是保留得越多越好么?假設LSTM在生成文章,里面有小明和小紅,小明在看電視,小紅在廚房做飯。如果當前的主語是小明, ok,那LSTM應該輸出看電視相關(guān)的,比如找遙控器啊, 換臺啊,如果主語已經(jīng)切換到小紅了, 那么接下來最好暫時把電視機忘掉,而輸出洗菜、醬油、電飯煲等。
第二步就是決定輸入多大程度上影響cell state。這個地方由兩部分構(gòu)成, 一個用sigmoid函數(shù)計算出有多少數(shù)據(jù)留下,一個用tanh函數(shù)計算出一個候選C(t)。 這個地方就好比是主語從小明切換到小紅了, 電視機就應該切換到廚房。
然后我們把留下來的(t-1時刻的)cell state和新增加的合并起來,就得到了t時刻的cell state。
最后我們把cell state經(jīng)過tanh壓縮到[-1, 1],然后輸送給輸出門([0, 1]決定輸出多少東西)。
現(xiàn)在也出了很多LSTM的變種, 有興趣的可以看這里。另外,LSTM只是為了解決RNN的long-term dependencies,也有人從另外的角度來解決的,比如Clockwork RNNs by Koutnik, et al. (2014).
我用的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("\n"))
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 = {ch: i for i, ch in enumerate(chars)}
ix_to_char = {i: ch for i, ch in enumerate(chars)}
# 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('----\n %s \n----' % (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("\n"))
new_names = predicted_names - all_names
print(new_names)
print('predicted names len: %d, new_names len: %d.\n' % (len(predicted_names), len(new_names)))
break
view rawmin-char-rnn.py hosted with ? by GitHub
然后從網(wǎng)上找了金庸小說的人名,做了些預處理,每行一個名字,保存到input.txt里,運行代碼就可以了。古龍的沒有找到比較全的名字, 只有這份武功排行榜,只有100多人。
下面是根據(jù)兩份名單訓練的結(jié)果,已經(jīng)將完全一致的名字(比如段譽)去除了,所以下面的都是LSTM“新創(chuàng)作發(fā)明”的名字哈。來, 大家猜猜哪一個結(jié)果是金庸的, 哪一個是古龍的呢?
{'姜曾鐵', '袁南蘭', '石萬奉', '郭萬嗔', '蔡家', '程伯芷', '汪鐵志', '陳衣', '薛鐵','哈赤蔡師', '殷飛虹', '鐘小硯', '鳳一刀', '寶蘭', '齊飛虹', '無若之', '王老英', '鐘','鐘百勝', '師', '李沅震', '曹蘭', '趙一刀', '鐘靈四', '宗家妹', '崔樹勝', '桑飛西','上官公希轟', '劉之余人童懷道', '周云鶴', '天', '鳳', '西靈素', '大智虎師', '阮徒忠','王兆能', '袁錚衣商寶鶴', '常伯鳳', '苗人大', '倪不鳳', '蔡鐵', '無伯志', '鳳一弼','曹鵲', '黃賓', '曾鐵文', '姬胡峰', '李何豹', '上官鐵', '童靈同', '古若之', '慕官景岳','崔百真', '陳官', '陳鐘', '倪調(diào)峰', '妹沅刀', '徐雙英', '任通督', '上官鐵褚容', '大劍太','胡陽', '生', '南仁鄭', '南調(diào)', '石雙震', '海鐵山', '殷鶴真', '司魚督', '德小','若四', '武通濤', '田青農(nóng)', '常塵英', '常不志', '倪不濤', '歐陽', '大提督', '胡玉堂','陳寶鶴', '南仁通四蔣赫侯'}
{'邀三', '熊貓開', '鷹星', '陸開', '花', '薛玉羅平', '南宮主', '南宮九', '孫夫人','荊董滅', '鐵不愁', '裴獨', '瑋劍', '人', '陸小龍王紫無牙', '連千里', '仲先生','俞白', '方大', '葉雷一魂', '獨孤上紅', '葉憐花', '雷大歸', '恕飛', '白雙發(fā)','邀一郎', '東樓', '鐵中十一點紅', '鳳星真', '無魏柳老鳳三', '蕭貓兒', '東郭先鳳','日孫', '地先生', '孟摘星', '江小小鳳', '花雙樓', '李佩', '仇玨', '白壞剎', '燕悲情','姬悲雁', '東郭大', '謝曉陸鳳', '碧玉伯', '司實三', '陸浪', '趙布雁', '荊孤藍','憐燕南天', '蕭憐靜', '龍布雁', '東郭魚', '司東郭金天', '薛嘯天', '熊寶玉', '無莫靜','柳羅李', '東官小魚', '漸飛', '陸地魚', '阿吹王', '高傲', '蕭十三', '龍童', '玉羅趙','謝郎唐傲', '鐵夜帝', '江小鳳', '孫玉玉夜', '仇仲忍', '蕭地孫', '鐵莫棠', '柴星夫','展夫人', '碧玉', '老無魚', '鐵鐵花', '獨', '薛月宮九', '老郭和尚', '東郭大路陸上龍關(guān)飛','司藏', '李千', '孫白人', '南雙平', '王瑋', '姬原情', '東郭大路孫玉', '白玉羅生', '高兒','東玨天', '蕭王尚', '九', '鳳三靜', '和空摘星', '關(guān)吹雪', '上官官小鳳', '仇上官金飛','陸上龍嘯天', '司空星魂', '邀衣人', '主', '李尋歡天', '東情', '玉夫隨', '趙小鳳', '東郭滅', '邀祟厚', '司空星'}
感興趣的還可以用古代詩人、詞人等的名字來做訓練,大家機器好或者有時間的可以多訓練下,訓練得越多越準確。
RNN由于具有記憶功能,在NLP、Speech、Computer Vision等諸多領(lǐng)域都展示了強大的力量。實際上,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也一直在產(chǎn)生新的研究,比如Attention機制。有空再介紹咯。。。
http://colah.github.io/posts/2015-08-Understanding-LSTMs/
http://karpathy.github.io/2015/05/21/rnn-effectiveness/
https://www.zhihu.com/question/29411132
https://gist.github.com/karpathy/d4dee566867f8291f086
https://deeplearning4j.org/lstm.html 雷鋒網(wǎng)雷鋒網(wǎng)
相關(guān)文章:
谷歌大腦科學家親解 LSTM:一個關(guān)于“遺忘”與“記憶”的故事
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。