<>前言

Github:代码下载 <https://github.com/FlameCharmander/MachineLearning>
由于RNN具有记忆功能,之前文章有介绍RNN来实现二进制相加,并取得了比较好的效果。那这次本文使用RNN来进行古诗生成。

<>数据集

数据集就是我们的古诗了,每行都是一首古诗,并且以格式"题目:古诗"。

首先需要创建一个词典,词典可以是每个字的词频高的前6000字作为词典,然后用one-hot来表示词向量。
def getVocab(filename='poetry.txt'): with open(filename, encoding='utf-8') as
f: lines = f.readlines() #读取每一行 wordFreqDict = dict()
#将每个词都进行词频计算,取词频高的前多少词用来做词典 for line in lines: #遍历每一行 tokens =
dict(nltk.FreqDist(list(line))) #分词,并且计算词频 wordFreqDict =
dict(Counter(wordFreqDict) + Counter(tokens)) #把每个词的词频相加 wordFreqTuple =
sorted(wordFreqDict.items(), key = lambda x: x[1], reverse=True) #按词频排序,逆排序 fw
= open('vocab.txt','w', encoding='utf-8') #将词典的每个词(这里是每个字作为一个词)写入到文件 vocab =
wordFreqTuple[:6000] #词典 for word in vocab: fw.write(word[0] + '\n')
这个函数就是按词频的前6000个字做成词典,并写入到txt文件中,保存。
def loadData(filename = 'poetry.txt'): vocab = readVocab() #获取词典,词典是以['词',
'典']这样存放的,类似这样 word2Index = {word: index for index, word in enumerate(vocab)}
#将词典映射成{词:0, 典:1},类似这样 fr = open(filename, 'r', encoding='utf-8') #读取文件,准备数据集
lines = fr.readlines() #所有行 dataSet = list() #数据集 labels = list() #标签 for line
in lines: poetry = line.split(":")[1].rstrip() #去除标题 X = [word2Index.get(word,
0) for word in list(poetry)] #把对应的文字转成索引,形成向量 y = X.copy() X.insert(0, 1)
#1代表是词典里的START,代表开始 dataSet.append(X) y.append(2) #2代表是词典里的END labels.append(y)
return dataSet, labels

这里读取每首古诗的诗,就是去掉标题的部分。比如"临洛水:春蒐驰骏骨,总辔俯长河。霞处流萦锦,风前漾卷罗。水花翻照树,堤兰倒插波。岂必汾阴曲,秋云发棹歌。",那只要"春蒐驰骏骨,总辔俯长河。霞处流萦锦,风前漾卷罗。水花翻照树,堤兰倒插波。岂必汾阴曲,秋云发棹歌。",并且转成索引列表,形成词向量[],并且添加首位标志,那么这里可能会产生一个问题,这里的标签是从何而来?其实这里标签也是古诗,不过是词向量加上一个末尾标志。所以最终的数据集和标签集可能长这样。因为我们希望’春’能够通过RNN预测’蒐’,‘蒐’预测’驰’。
至此,有了数据集和标签,就可以来训练RNN了。

<>算法实现
def __init__(self): self.wordDim = 6000 wordDim = self.wordDim self.hiddenDim
= 100 hiddenDim = self.hiddenDim self.Wih = np.random.uniform(-np.sqrt(1. /
wordDim), np.sqrt(1. / wordDim), (hiddenDim, wordDim)) #输入层到隐含层的权重矩阵(100, 6000)
self.Whh = np.random.uniform(-np.sqrt(1. / self.hiddenDim), np.sqrt(1. /
self.hiddenDim), (hiddenDim, hiddenDim)) #隐含层到隐含层的权重矩阵(100, 100) self.Why =
np.random.uniform(-np.sqrt(1. / self.hiddenDim), np.sqrt(1. / self.hiddenDim),
(wordDim, hiddenDim)) #隐含层到输出层的权重矩阵(10, 1)
最开始先初始化词典大小(维度),一集隐含层大小。

<>前向传播

这里先给出前向传播的公式:
zth=Wihx+Whhat−1hath=tanh⁡(zth)ztk=Whyathatk=softmax(ztk)E=−∑t=1Nytlog⁡(atk)
\begin{array}{l} z_t^h = {W_{ih}}x + {W_{hh}}a_{{\rm{t - 1}}}^h\\ a_t^h = \tanh
\left( {z_t^h} \right)\\ z_t^k = {W_{hy}}a_t^h\\ a_t^k = {\mathop{\rm
softmax}\nolimits} \left( {z_t^k} \right)\\ E =- \sum\limits_{t = 1}^N
{{y_t}\log \left( {a_t^k} \right)} \end{array}zth​=Wih​x+Whh​at−1h​ath​=tanh(zth
​)ztk​=Why​ath​atk​=softmax(ztk​)E=−t=1∑N​yt​log(atk​)​
相比上一节RNN来做二进制相加的公式来看,可以看到之前的2个sigmoid函数被改成tanh函数和softmax函数了。
同时损失函数改成了交叉熵。
def forward(self, data): #前向传播,原则上传入一个数据样本和标签 T = len(data) output =
np.zeros((T, self.wordDim, 1)) #输出 hidden = np.zeros((T+1, self.hiddenDim, 1))
#隐层状态 for t in range(T): #时间循环 X = np.zeros((self.wordDim, 1)) #构建(6000,1)的向量
X[data[t]][0] = 1 #将对应的值置为1,形成词向量 Zh = np.dot(self.Wih, X) + np.dot(self.Whh,
hidden[t-1]) #(100, 1) ah = np.tanh(Zh) #(100, 1),隐层值 hidden[t] = ah Zk =
np.dot(self.Why, ah) #(6000,1) ak = self.softmax(Zk) #(6000, 1),输出值 output[t] =
ak #把index写进去 return hidden, output
<>代码不难懂,跟公式是对应的。
反向传播

反向传播的公式:
δtk=∂E∂ak∂ak∂zk={i=j,aj−1i≠j,ajδTh=∂E∂aTk∂aTk∂zTk∂zTk∂aTh∂aTh∂zTh=(Why)TδTkδth=
∂E∂atk∂atk∂ztk∂ztk∂ath∂ath∂zth+∂E∂at+1k∂at+1k∂zt+1k∂zt+1k∂ath∂ath∂zth=((Whh)Tδt+
1h+(Why)Tδtk)∂ath∂zth=((Whh)Tδt+1h+(Why)Tδtk)×(1−tanh⁡(ath))Wih=Wih−ηδth(xt)TWhh
=Whh−ηδth(at−1h)TWhy=Why−ηδtk(ath)T\begin{array}{l} \delta _t^k =
\frac{{\partial E}}{{\partial {a^k}}}\frac{{\partial {a^k}}}{{\partial {z^k}}}
= \left\{ \begin{array}{l} i = j,{a_j} - 1\\ i \ne j,{a_j} \end{array}
\right.\\ \delta _T^h = \frac{{\partial E}}{{\partial a_T^k}}\frac{{\partial
a_T^k}}{{\partial z_T^k}}\frac{{\partial z_T^k}}{{\partial
a_{\rm{T}}^h}}\frac{{\partial a_T^h}}{{\partial z_{\rm{T}}^h}} = {\left(
{{W_{hy}}} \right)^T}\delta _T^k\\ \delta _t^h = \frac{{\partial E}}{{\partial
a_t^k}}\frac{{\partial a_t^k}}{{\partial z_t^k}}\frac{{\partial
z_t^k}}{{\partial a_t^h}}\frac{{\partial a_t^h}}{{\partial z_t^h}} +
\frac{{\partial E}}{{\partial a_{t + 1}^k}}\frac{{\partial a_{t +
1}^k}}{{\partial z_{t + 1}^k}}\frac{{\partial z_{t + 1}^k}}{{\partial
a_t^h}}\frac{{\partial a_t^h}}{{\partial z_t^h}}\\ {\rm{ }} = \left( {{{\left(
{{W_{hh}}} \right)}^T}\delta _{t + 1}^h + {{\left( {{W_{hy}}} \right)}^T}\delta
_t^k} \right)\frac{{\partial a_t^h}}{{\partial z_t^h}}\\ {\rm{ }} = \left(
{{{\left( {{W_{hh}}} \right)}^T}\delta _{t + 1}^h + {{\left( {{W_{hy}}}
\right)}^T}\delta _t^k} \right) \times \left( {1 - \tanh \left( {a_t^h}
\right)} \right)\\ {W_{ih}} = {W_{ih}} - \eta \delta _t^h{\left( {{x_t}}
\right)^T}\\ {W_{hh}} = {W_{hh}} - \eta \delta _t^h{\left( {a_{t - 1}^h}
\right)^T}\\ {W_{hy}} = {W_{hy}} - \eta \delta _t^k{\left( {a_t^h} \right)^T}
\end{array}δtk​=∂ak∂E​∂zk∂ak​={i=j,aj​−1i̸​=j,aj​​δTh​=∂aTk​∂E​∂zTk​∂aTk​​∂aTh​∂
zTk​​∂zTh​∂aTh​​=(Why​)TδTk​δth​=∂atk​∂E​∂ztk​∂atk​​∂ath​∂ztk​​∂zth​∂ath​​+∂at+1
k​∂E​∂zt+1k​∂at+1k​​∂ath​∂zt+1k​​∂zth​∂ath​​=((Whh​)Tδt+1h​+(Why​)Tδtk​)∂zth​∂at
h​​=((Whh​)Tδt+1h​+(Why​)Tδtk​)×(1−tanh(ath​))Wih​=Wih​−ηδth​(xt​)TWhh​=Whh​−ηδt
h​(at−1h​)TWhy​=Why​−ηδtk​(ath​)T​
这里主要是softmax求导可能会有问题,这里我推荐一个文章,写得很好。softmax求导
<https://www.jianshu.com/p/ffa51250ba2e>
def backPropagation(self, data, label, alpha = 0.002): #反向传播 hidden, output =
self.forward(data) #(N, 6000) T = len(output) #时间长度=词向量的长度 deltaHPre =
np.zeros((self.hiddenDim, 1)) #前一时刻的隐含层偏导 WihUpdata = np.zeros(self.Wih.shape)
#权重更新值 WhhUpdata = np.zeros(self.Whh.shape) WhyUpdata =
np.zeros(self.Why.shape) for t in range(T-1, -1, -1): X =
np.zeros((self.wordDim, 1)) # (6000,1) X[data[t]][0] = 1 #构建出词向量
output[t][label[t]][0] -= 1 #求导后,输出结点的误差跟output只差在i=j时需要把值减去1 deltaK =
output[t].copy() #输出结点的误差 deltaH = np.multiply(np.add(np.dot(self.Whh.T,
deltaHPre),np.dot(self.Why.T, deltaK)), (1 - (hidden[t] ** 2))) #隐含层结点误差
deltaHPre=deltaH.copy() WihUpdata += np.dot(deltaH, X.T) WhhUpdata +=
np.dot(deltaH, hidden[t-1].T) WhyUpdata += np.dot(deltaK, hidden[t].T) self.Wih
-= alpha * WihUpdata self.Whh -= alpha * WhhUpdata self.Why -= alpha * WhyUpdata
最后就是训练了。
def train(self,dataSet, labels): #训练 N = len(dataSet) for i in range(N): if
(i % 100 == 0 and i >= 100): self.calcEAll(dataSet[i-100:i], labels[i-100:i])
self.backPropagation(dataSet[i], labels[i]) pass
到这里,RNN的核心部分就是如此,这时可以利用RNN来进行文本生成了。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信