• MXNet对含隐藏状态的循环神经网络(RNN)的实现


            循环神经网络(RNN)跟前面介绍的卷积神经网络区别很大,卷积神经网络主要是处理空间信息,每层提取不同的特征,而RNN是处理时序信息的,时序信息就是说将一段文字或者声音等看作是一段离散的时间序列,按照时间进行输出的一种模型,本节主要介绍如何处理自然语言(Natural Language Processing NLP)。
            比如一段语音的识别,“老板,来包福贵”,那可能会输出“老板,来包富贵”,因为fugui读音一样,这个时候就需要语言模型去判断两者的概率,输出大的即可,而语言模型如下:
    把一段长度为T的文本里的词依次表示为W1,W2,W3,...,WT,那么在离散的时间序列中,Wt(1<=t<=T)看作在时间步t的输出或标签,该语言模型计算该序列的概率是P(W1,W2,W3,...,WT),计算公式是:
    P(W1,W2,W3,...,WT)=∏P(Wt|W1,...,Wt-1),也就是该词出现的概率连乘前面每个词出现的条件概率。比如4个词文本序列的概率:P(W1,W2,W3,W4)=P(W1)*P(W2|W1)*P(W3|W1,W2)*P(W4|W1,W2,W3),那么这个词的概率可以通过该词的词频与训练数据集的总词数之比来计算。而条件概率比如P(W3|W1,W2)可以通过P(W1,W2,W3)这3个词的相邻频率与P(W1,W2)这2个相邻频率之比来获得。

    1.n元语法

    如果序列很长,计算和存储这些概率的复杂度会呈现指数级的增加,这里使用n元语法通过马尔科夫假设来简化语言模型计算,也就是说一个词的出现只跟前面n个词有关(n阶马尔科夫链),当n分别是1、2、3时,我们将其分别称作一元语法、二元语法、三元语法,比如长度是4的序列W1,W2,W3,W4在一元、二元、三元语法中的概率分别是:

    P(W1,W2,W3,W4)=P(W1)P(W2)P(W3)P(W4)
    P(W1,W2,W3,W4)=P(W1)P(W2|W1)P(W3|W2)P(W4|W3)
    P(W1,W2,W3,W4)=P(W1)P(W2|W1)P(W3|W1,W2)P(W4|W2,W3)

    当n较小时,n元语法往往并不准确,比如一元语法:“你走先”和“你先走”概率是一样的;然而当n较大时,n元语法又需要计算并存储大量的词频和多词相邻频率。于是平衡这两种方式的策略的RNN就出现了。

    2.循环神经网络

    我们可以先来看一张图,个人比较喜欢画图来直观表示流程和公式的展示。

    通过图我们可以看到,中间层是隐藏状态(隐藏变量),它的作用其实就是用来存储之前时间步的信息的,也就是可以将历史信息保留并传播下去,每个时间步都使用了上一时间步的隐藏状态,而这样的计算是循环的,所以叫做循环神经网络(Recurrent Neural Network)。
    图中的公式还可以换一种等价的公式,就是将Xt与Ht-1连结后的矩阵乘以Wxh与Whh连结后的矩阵,举例验证下:

    1. X,W_xh=nd.random.normal(shape=(3,1)),nd.random.normal(shape=(1,4))
    2. H,W_hh=nd.random.normal(shape=(3,4)),nd.random.normal(shape=(4,4))
    3. print(nd.dot(X,W_xh) + nd.dot(H,W_hh))
    4. print(nd.dot(nd.concat(X,H,dim=1),nd.concat(W_xh,W_hh,dim=0)))
    5. '''
    6. [[ 0.03172791 -0.40466753 -0.5573117 0.39783114]
    7. [ 3.3352664 2.2760866 1.135608 0.32824945]
    8. [ 4.352936 -1.3085628 -1.8609544 4.0762043 ]]
    9. [[ 0.03172791 -0.40466756 -0.5573117 0.39783114]
    10. [ 3.335266 2.2760866 1.1356078 0.32824934]
    11. [ 4.352936 -1.3085628 -1.8609543 4.0762043 ]]
    12. '''

    2.1语言模型数据集

    我们使用周杰伦歌词数据集来创作歌词jaychou_lyrics.txt.zip,下载之后是7z,解压之后是zip可以不解压成txt文档,直接通过zipfile模块来解压缩,下面下载的是解压后就是txt,那就直接读取即可,两种读取方式如下:

    1. import zipfile
    2. with zipfile.ZipFile('data/jaychou_lyrics.txt.zip') as zin:
    3. with zin.open('jaychou_lyrics.txt') as f:
    4. corpus_chars=f.read().decode('utf-8')
    5. with open('data/jaychou_lyrics.txt','rb') as f:
    6. corpus_chars=f.read().decode('utf-8')

    然后需要对这些歌词做一本类似我们查词的“新华字典”,也就是对每个词建立一个对应的索引

    1. #建立字符索引
    2. #歌词总计有63282个(包括空格)去重之后有2582个字符
    3. corpus_chars=corpus_chars[:10000]#截取10000个字符来训练
    4. idx_to_char=list(set(corpus_chars))#去重之后是1027个,如:['桑', '拜', '拖', '心', '碎', '背', '的', '防', '白', '仔',...]
    5. char_to_idx=dict([(c,i) for i,c in enumerate(idx_to_char)])
    6. #print(char_to_idx['要'],char_to_idx)#349,{'桑': 0, '拜': 1, '拖': 2, '心': 3, '碎': 4, '背': 5, '的': 6, '防': 7,...}
    7. #训练数据集字符与索引的转换
    8. corpus_indices=[char_to_idx[c] for c in corpus_chars]
    9. print(corpus_indices[:10])
    10. print('字符:',''.join([idx_to_char[i] for i in corpus_indices])[:10])
    11. '''
    12. [789, 349, 390, 599, 18, 319, 413, 789, 349, 539]
    13. 字符: 想要有直升机 想要和
    14. '''

    一本字典就做好了,我们在训练的时候,会每次随机读取小批量的样本和标签,此处的标签和以往不一样,因为是时序的数据,预测下一个字符,那么样本的标签序列就是这些字符分别在训练集中的下一个字符。另外需要注意的是,这个set每次去重之后的顺序是变化的
    我们有两种采样方式:

    2.2随机采样

    每个样本都是在原始序列上面任意截取的一段序列,所以相邻的两个随机小批量在原始序列上的位置不一定相毗邻,我们也无法用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态,训练模型时每次随机采样前都需要重新初始化隐藏状态。

    1. #随机采样
    2. #batch_size:每个小批量的样本数,num_steps:每个样本的时间步数
    3. def data_iter_random(corpus_indices,batch_size,num_steps,ctx=None):
    4. num_examples=(len(corpus_indices)-1)//num_steps
    5. print(num_examples)
    6. epoch_size=num_examples//batch_size
    7. print(epoch_size)
    8. example_indices=list(range(num_examples))
    9. print(example_indices)
    10. random.shuffle(example_indices)
    11. #返回从pos开始的长为num_steps的序列
    12. def _data(pos):
    13. return corpus_indices[pos:pos+num_steps]
    14. for i in range(epoch_size):
    15. i=i*batch_size
    16. batch_indices=example_indices[i:i+batch_size]
    17. X=[_data(j*num_steps) for j in batch_indices]
    18. Y=[_data(j*num_steps+1) for j in batch_indices]
    19. yield nd.array(X,ctx),nd.array(Y,ctx)
    20. seq=list(range(50))
    21. for X,Y in data_iter_random(seq,batch_size=3,num_steps=4):
    22. print("输入",X,"\n标签",Y)
    23. '''
    24. 输入
    25. [[ 4. 5. 6. 7.]
    26. [44. 45. 46. 47.]
    27. [ 8. 9. 10. 11.]]
    28. 标签
    29. [[ 5. 6. 7. 8.]
    30. [45. 46. 47. 48.]
    31. [ 9. 10. 11. 12.]]
    32. 输入
    33. [[40. 41. 42. 43.]
    34. [28. 29. 30. 31.]
    35. [16. 17. 18. 19.]]
    36. 标签
    37. [[41. 42. 43. 44.]
    38. [29. 30. 31. 32.]
    39. [17. 18. 19. 20.]]
    40. 输入
    41. [[32. 33. 34. 35.]
    42. [36. 37. 38. 39.]
    43. [24. 25. 26. 27.]]
    44. 标签
    45. [[33. 34. 35. 36.]
    46. [37. 38. 39. 40.]
    47. [25. 26. 27. 28.]]
    48. 输入
    49. [[12. 13. 14. 15.]
    50. [ 0. 1. 2. 3.]
    51. [20. 21. 22. 23.]]
    52. 标签
    53. [[13. 14. 15. 16.]
    54. [ 1. 2. 3. 4.]
    55. [21. 22. 23. 24.]]
    56. '''

    可以看出两个小批量之间顺序是打乱的,没有相毗邻。

    2.3相邻采样

    相邻采样可以让相邻的两个随机小批量在原序列中相毗邻,所以我们可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态, 从而使下一个小批量的输出也取决于当前小批量的输入,如此循环下去。这对实现循环神经网络造成两个影响:1、在训练模型时,只需要在每个迭代周期开始时初始化隐藏状态。2、 当多个相邻小批量通过传递隐藏状态串联起来时,模型参数的梯度计算将依赖所有的序列,所以在同一迭代周期中,随着迭代次数的增加,梯度计算开销会越来越大。

    1. def data_iter_consecutive(corpus_indices,batch_size,num_steps,ctx=None):
    2. corpus_indices=nd.array(corpus_indices,ctx=ctx)
    3. data_len=len(corpus_indices)
    4. batch_len=data_len//batch_size
    5. indices=corpus_indices[0:batch_size*batch_len].reshape((batch_size,batch_len))
    6. epoch_size=(batch_len-1)//num_steps
    7. for i in range(epoch_size):
    8. i=i*num_steps
    9. X=indices[:,i:i+num_steps]
    10. Y=indices[:,i+1:i+num_steps+1]
    11. yield X,Y
    12. seq=list(range(50))
    13. for X,Y in data_iter_consecutive(seq,batch_size=3,num_steps=4):
    14. print("输入",X,"\n标签",Y)
    15. '''
    16. 输入
    17. [[ 0. 1. 2. 3.]
    18. [16. 17. 18. 19.]
    19. [32. 33. 34. 35.]]
    20. 标签
    21. [[ 1. 2. 3. 4.]
    22. [17. 18. 19. 20.]
    23. [33. 34. 35. 36.]]
    24. 输入
    25. [[ 4. 5. 6. 7.]
    26. [20. 21. 22. 23.]
    27. [36. 37. 38. 39.]]
    28. 标签
    29. [[ 5. 6. 7. 8.]
    30. [21. 22. 23. 24.]
    31. [37. 38. 39. 40.]]
    32. 输入
    33. [[ 8. 9. 10. 11.]
    34. [24. 25. 26. 27.]
    35. [40. 41. 42. 43.]]
    36. 标签
    37. [[ 9. 10. 11. 12.]
    38. [25. 26. 27. 28.]
    39. [41. 42. 43. 44.]]
    40. '''

    3.构造循环神经网络

    为了将词表示成向量输入到神经网络,一个简单办法就是使用独热编码(one-hot)向量,除了这个词对应索引i所在的元素设置为1,其余全部为0,我们直接看示例:

    1. nd.one_hot(nd.array([1,3,5]),10)
    2. '''
    3. [[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
    4. [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    5. [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]]
    6. '''
    7. nd.one_hot(nd.array([[1,2,4],[2,4,7]]),10)
    8. '''
    9. [[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
    10. [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
    11. [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]]
    12. [[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
    13. [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
    14. [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]]
    15. '''
    16. print(nd.one_hot(nd.array(nd.array([[1,2,4],[2,4,7]]).T),10))
    17. print(nd.array([[1,2,4],[2,4,7]]).T)
    18. '''
    19. [[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
    20. [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]]
    21. [[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
    22. [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]]
    23. [[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
    24. [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]]
    25. [[1. 2.]
    26. [2. 4.]
    27. [4. 7.]]
    28. '''
    29. 针对循环神经网络做的一个独热编码函数(自带有):
    30. def to_onehot(X,size):
    31. return [nd.one_hot(x,size) for x in X.T]
    32. X=nd.arange(10).reshape((2,5))
    33. print(X)
    34. inputs=to_onehot(X,10)#d2l.to_onehot(X,10)
    35. print(inputs)
    36. '''
    37. [[0. 1. 2. 3. 4.]
    38. [5. 6. 7. 8. 9.]]
    39. [
    40. [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
    41. [0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]]
    42. ,
    43. [[0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
    44. [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]
    45. ,
    46. [[0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
    47. [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]]
    48. ,
    49. [[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    50. [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]]
    51. ,
    52. [[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
    53. [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
    54. ]
    55. '''

    3.1建立模型

    现在开始构造神经网络,以周杰伦专辑歌词数据集训练模型来创作歌词。

    1. import d2lzh as d2l
    2. from mxnet import autograd,gluon,init,nd
    3. from mxnet.gluon import data as gdata,loss as gloss,nn,rnn
    4. import random
    5. import zipfile
    6. #FileNotFoundError: [Errno 2] No such file or directory: '../data/jaychou_lyrics.txt.zip'
    7. #调用自带的需要注意路径,本人为了图简单,直接将data目录拷贝到上一级目录里
    8. corpus_indices,char_to_idx,idx_to_char,vocab_size=d2l.load_data_jay_lyrics()
    9. #初始化模型参数
    10. #ctx=d2l.try_gpu()
    11. ctx=None
    12. num_inputs,num_hiddens,num_outputs=vocab_size,256,vocab_size
    13. def get_params():
    14. def _one(shape):
    15. return nd.random.normal(scale=0.01,shape=shape,ctx=ctx)
    16. #隐藏层参数
    17. W_xh=_one((num_inputs,num_hiddens))
    18. W_hh=_one((num_hiddens,num_hiddens))
    19. b_h=nd.zeros(num_hiddens,ctx=ctx)
    20. #输出参数
    21. W_hq=_one((num_hiddens,num_outputs))
    22. b_q=nd.zeros(num_outputs,ctx=ctx)
    23. #附上梯度
    24. params=[W_xh,W_hh,b_h,W_hq,b_q]
    25. for p in params:
    26. p.attach_grad()
    27. return params
    28. #定义模型
    29. #返回初始化的隐藏状态,使用元组便于处理隐藏状态含有多个NDArray的情况
    30. def init_rnn_state(batch_size,num_hiddens,ctx):
    31. return (nd.zeros(shape=(batch_size,num_hiddens),ctx=ctx),)
    32. #计算隐藏状态与输出
    33. def rnn(inputs,state,params):
    34. W_xh,W_hh,b_h,W_hq,b_q=params
    35. H,=state#state是元组,加逗号转成列表,可以简单看做去括号
    36. outputs=[]
    37. for X in inputs:
    38. H=nd.tanh(nd.dot(X,W_xh)+nd.dot(H,W_hh)+b_h)
    39. Y=nd.dot(H,W_hq)+b_q
    40. outputs.append(Y)
    41. return outputs,(H,)
    42. #观察输出和隐藏状态的形状
    43. X=nd.arange(10).reshape((2,5))
    44. state=init_rnn_state(X.shape[0],num_hiddens,ctx)#(2,256)
    45. #inputs=d2l.to_onehot(X.as_in_context(ctx),vocab_size)
    46. inputs=d2l.to_onehot(X,vocab_size)
    47. params=get_params()
    48. outputs,state_new=rnn(inputs,state,params)
    49. print(len(outputs),state_new[0].shape,outputs[0].shape)#5 (2, 256) (2, 1027)
    50. #预测函数,d2lzh已有
    51. def predict_rnn(prefix,num_chars,rnn,params,init_rnn_state,num_hiddens,vocab_size,ctx,idx_to_char,char_to_idx):
    52. state=init_rnn_state(1,num_hiddens,ctx)
    53. output=[char_to_idx[prefix[0]]]
    54. for t in range(num_chars+len(prefix)-1):
    55. X=d2l.to_onehot(nd.array([output[-1]],ctx=ctx),vocab_size)#上一时间步的输出作为当前时间步的输入
    56. Y,state=rnn(X,state,params)#计算输出与更新隐藏状态
    57. if t<len(prefix)-1:
    58. output.append(char_to_idx[prefix[t+1]])
    59. else:
    60. output.append(int(Y[0].argmax(axis=1).asscalar()))
    61. return ''.join([idx_to_char[i] for i in output])
    62. predict_rnn('分开',10,rnn,params,init_rnn_state,num_hiddens,vocab_size,ctx,idx_to_char,char_to_idx)
    63. '分开拿到作中处社晰宣节办'

    3.2裁剪梯度与困惑度

    在循环神经网络中,容易出现梯度衰减或梯度爆炸,这就造成了模型的不稳定,要么失控要么没法学习下去,为了预防这种情况,我们使用裁剪梯度。假设我们把所有模型参数梯度的元素拼接成一个向量g,并设置裁剪梯度的阈值为θ,裁剪后的梯度:g←min(1,θ / ||g||) * g,||g||是L2范数,也就是说梯度g的范数大于了阈值,就做裁剪,避免爆炸
    代码如下(d2lzh包已有):

    1. def grad_clipping(params,theta,ctx):
    2. norm=nd.array([0],ctx)
    3. for param in params:
    4. norm+=(param.grad**2).sum()
    5. norm=norm.sqrt().asscalar()
    6. if norm>theta:
    7. for param in params:
    8. param.grad[:]*=theta/norm

    在前面介绍的卷积神经网络使用的是损失函数或精度来评价模型,这里我们使用困惑度(perplexity)来评价语言模型的好坏,困惑度是对交叉熵损失函数做指数运算后得到的值。

    1、最佳情况,模型总是把标签类别的概率预测为1,此时困惑度为1
    2、最坏情况,模型总是把标签类别的概率预测为0,此时困惑度为正无穷
    3、基线情况,模型总是预测所有类别的概率都一样,此时困惑度就是类别的个数

    那么对于一个有效模型的困惑必须是小于基线情况才行,也就是困惑度小于类别个数(词典大小)现在通过裁剪梯度以及困惑度来训练与评估模型

    1. #训练并通过困惑度来评估,d2lzh包已有
    2. def train_and_predict_rnn(rnn,get_params,init_rnn_state,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,
    3. is_random_iter,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes):
    4. if is_random_iter:
    5. data_iter_fn=d2l.data_iter_random
    6. else:
    7. data_iter_fn=d2l.data_iter_consecutive
    8. params=get_params()
    9. loss=gloss.SoftmaxCrossEntropyLoss()
    10. for epoch in range(num_epochs):
    11. #相邻采样,epoch开始时初始化隐藏状态
    12. if not is_random_iter:
    13. state=init_rnn_state(batch_size,num_hiddens,ctx)
    14. l_sum,n,start=0.0,0,time.time()
    15. data_iter=data_iter_fn(corpus_indices,batch_size,num_steps,ctx)
    16. for X,Y in data_iter:
    17. #随机采样需每个小批量更新前初始化隐藏状态
    18. if is_random_iter:
    19. state=init_rnn_state(batch_size,num_hiddens,ctx)
    20. #相邻采样就将隐藏状态从计算图中分离
    21. else:
    22. for s in state:
    23. s.detach()
    24. with autograd.record():
    25. inputs=d2l.to_onehot(X,vocab_size)
    26. outputs,state=rnn(inputs,state,params)
    27. outputs=nd.concat(*outputs,dim=0)
    28. y=Y.T.reshape((-1,))
    29. l=loss(outputs,y).mean()#交叉熵损失计算平均分类误差
    30. l.backward()
    31. #裁剪梯度(迭代模型参数之前)
    32. grad_clipping(params,clipping_theta,ctx)
    33. d2l.sgd(params,lr,1)
    34. l_sum+=l.asscalar()*y.size
    35. n+=y.size
    36. if(epoch+1)%pred_period==0:
    37. print('epoch %d,perplexity %f,time %.2f sec' % (epoch+1,math.exp(l_sum/n),time.time()-start))
    38. for prefix in prefixes:
    39. print(predict_rnn(prefix,pred_len,rnn,params,init_rnn_state,num_hiddens,vocab_size,ctx,idx_to_char,char_to_idx))
    40. num_epochs,num_steps,batch_size,lr,clipping_theta=250,35,32,1e2,1e-2
    41. pred_period,pred_len,prefixes=50,50,['分开','不分开']
    42. train_and_predict_rnn(rnn,get_params,init_rnn_state,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,
    43. True,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)
    44. '''
    45. epoch 50,perplexity 69.879726,time 1.67 sec
    46. 分开 我想要你想你 你知我不 你是了 一子两 我想要你 你着我 别子我 别你的让我疯狂的可爱女人 坏坏的
    47. 不分开 我爱你这你的 我想要这 你着我 别子我 别你我 我不要你想你 你知你不 你着了双 你的让我 你的
    48. epoch 100,perplexity 10.730620,time 1.41 sec
    49. 分开 一颗两双 在谁耿中 你一定梦 你一定梦 你一定梦 你一定梦 你一定梦 你一定梦 你一定梦 你一定梦
    50. 不分开吗 单天在双 在小村外的溪边 默默等的 娘果我的见头 有话是你 全要我不见 你不定 在我的脚快 银
    51. epoch 150,perplexity 3.033938,time 1.39 sec
    52. 分开 一直用停留都的母斑鸠 印地安老斑鸠 腿短毛不多 除非是人鸦抢了它能活 脑袋瓜有一点秀逗 猎物 夕吸
    53. 不分开吗 我想你爸 你打的事 就人梦向 我有定受 你在的梦 你的梦空 我想揍你 经再的从快极银 我古 伊原
    54. epoch 200,perplexity 1.610590,time 1.39 sec
    55. 分开球默会这样的在淡 能不斜过去圈的誓言 一切又重演 祭司 神殿 征战 弓箭 是谁的从前 喜欢在人潮中你
    56. 不分开扫 然后将过去 慢慢温习 让我爱上你 那场悲剧 是你完美演出的一场戏 宁愿心碎哭泣 再狠狠忘记 你爱
    57. epoch 250,perplexity 1.335831,time 1.43 sec
    58. 分开球默会记等 在原上的是不 然无过 一直两 我想就这样牵着你的手不放开 爱可不可以永简单纯没有悲哀 我
    59. 不分开吗 然后将过去 慢慢温习 让我爱上你 那场悲剧 是你完美演出的一场戏 宁愿心碎哭泣 再狠狠忘记 你爱
    60. '''

    如果不裁剪梯度,大家可以试试看有什么结果,测试将会出现溢出错误:
    OverflowError: math range error
    超出了数值范围,问题出在了指数函数math.exp的l_sum/n值上,大于了709。
    然后将is_random_iter设置为False,大家可以看下相邻采样的结果。

    4.简洁实现

    1. corpus_indices,char_to_idx,idx_to_char,vocab_size=d2l.load_data_jay_lyrics()
    2. #定义模型
    3. num_hiddens,batch_size,num_steps=256,2,35
    4. rnn_layer=rnn.RNN(num_hiddens,3)
    5. rnn_layer.initialize()
    6. state=rnn_layer.begin_state(batch_size=batch_size)
    7. print(state[0].shape)#(3, 2, 256)(隐藏层个数,批量大小,隐藏单元个数)
    8. X=nd.random.uniform(shape=(num_steps,batch_size,vocab_size))
    9. Y,state_new=rnn_layer(X,state)
    10. print(Y.shape,len(state_new),state_new[0].shape)#(35, 2, 256) 1 (3, 2, 256)
    11. #循环神经网络模块,在d2lzh中已有
    12. class RNNModel(nn.Block):
    13. def __init__(self,rnn_layer,vocab_size,**kwargs):
    14. super(RNNModel,self).__init__(**kwargs)
    15. self.rnn=rnn_layer
    16. self.vocab_size=vocab_size
    17. self.dense=nn.Dense(vocab_size)
    18. def forward(self,inputs,state):
    19. X=nd.one_hot(inputs.T,self.vocab_size)
    20. Y,state=self.rnn(X,state)
    21. #output=self.dense(Y.reshape((-1,Y.shape[-1])))
    22. output=self.dense(Y)
    23. return output,state
    24. def begin_state(self,*args,**kwargs):
    25. return self.rnn.begin_state(*args,**kwargs)
    26. #预测函数,在d2lzh中已有
    27. def predict_rnn_gluon(prefix,num_chars,model,vocab_size,ctx,idx_to_char,char_to_idx):
    28. state=model.begin_state(batch_size=1,ctx=ctx)
    29. output=[char_to_idx[prefix[0]]]
    30. for t in range(num_chars+len(prefix)-1):
    31. X=nd.array([output[-1]],ctx=ctx).reshape((1,1))
    32. (Y,state)=model(X,state)
    33. if t<len(prefix)-1:
    34. output.append(char_to_idx[prefix[t+1]])
    35. else:
    36. output.append(int(Y.argmax(axis=1).asscalar()))
    37. return ''.join([idx_to_char[i] for i in output])
    38. ctx=None
    39. #ctx=d2l.try_gpu()
    40. model=RNNModel(rnn_layer,vocab_size)
    41. model.initialize(force_reinit=True,ctx=ctx)
    42. print(predict_rnn_gluon('分开',10,model,vocab_size,ctx,idx_to_char,char_to_idx))
    43. '''
    44. 分开录器哀豆器耍逗种勉刚
    45. '''
    1. #训练模型,在d2lzh中已有
    2. def train_and_predict_rnn_gluon(model,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,
    3. num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes):
    4. loss=gloss.SoftmaxCrossEntropyLoss()
    5. model.initialize(ctx=ctx,force_reinit=True,init=init.Normal(0.01))
    6. trainer=gluon.Trainer(model.collect_params(),'sgd',{'learning_rate':lr,'momentum':0,'wd':0})
    7. for epoch in range(num_epochs):
    8. l_sum,n,start=0.0,0,time.time()
    9. data_iter=d2l.data_iter_consecutive(corpus_indices,batch_size,num_steps,ctx)
    10. state=model.begin_state(batch_size=batch_size,ctx=ctx)
    11. for X,Y in data_iter:
    12. for s in state:
    13. s.detach()
    14. with autograd.record():
    15. (output,state)=model(X,state)
    16. y=Y.T.reshape((-1,))
    17. l=loss(output,y).mean()
    18. l.backward()
    19. #梯度裁剪
    20. params=[p.data() for p in model.collect_params().values()]
    21. d2l.grad_clipping(params,clipping_theta,ctx)
    22. trainer.step(1)
    23. l_sum += l.asscalar()*y.size
    24. n+=y.size
    25. if (epoch+1)%pred_period==0:
    26. print('epoch %d,perplexity %f,time %.2f sec ' % (epoch+1,math.exp(l_sum/n),time.time()-start))
    27. for prefix in prefixes:
    28. print(predict_rnn_gluon(prefix,pred_len,model,vocab_size,ctx,idx_to_char,char_to_idx))
    29. num_epochs,batch_size,lr,clipping_theta=250,32,1e2,1e-2
    30. pred_period,pred_len,prefixes=50,50,['分开','不分开']
    31. train_and_predict_rnn_gluon(model,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,
    32. num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)

    MXNetError: [12:56:29] c:\projects\mxnet-distro-win\mxnet-build\3rdparty\dmlc-core\include\dmlc\./any.h:286: Check failed: type_ != nullptr The any container is empty requested=class mxnet::Imperative::AGInfo

    如果出现上述错误,那就是内存不够,然后换成GPU计算即可。

    1. '''
    2. epoch 50,perplexity 139.032945,time 0.11 sec
    3. 分开 想坏
    4. 不分开 我不要 想坏
    5. epoch 100,perplexity 23.330386,time 0.11 sec
    6. 分开著我我我的你界最不了的你人相思寄红豆那场悲剧 我将我这辈子注定一个人演戏默语不著我想上你没牵着你的手
    7. 不分开让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏
    8. epoch 150,perplexity 5.121261,time 0.11 sec
    9. 分开熬的淡淡 干切抢篮多人除驳的砖墙我铺到榉斯坦堡著我像能和远远句么找也等不直 一果我遇见你是一场悲剧
    10. 不分开让我感狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏
    11. epoch 200,perplexity 1.931705,time 0.12 sec
    12. 分开 你分啊被远球 的那望开怎么小 就怎么每天祈祷了雕愿 看远女决 不能承受每已无处 旧每开的 偷一出痛
    13. 不分开让我感狂的可爱女人 坏坏的让你疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让你疯狂的可爱女人 坏
    14. epoch 250,perplexity 1.320265,time 0.11 sec
    15. 分开书有会 随水已潮起个见 瞎透了我 说你句如果说的离望力够临变沼泽 灰狼啃食的牛肉暴力默回开 我我它让
    16. 不分开让我感动的可爱女人想成漂泊心伤透单在每人风口不友 一透水我 全你会呵落当 快使用双截棍 哼哼哈兮 如
    17. '''
  • 相关阅读:
    URL because the SSL module is not available
    tar解压到指定文件夹 2208281732
    会声会影色彩校正在哪里 会声会影色彩素材栏在哪 会声会影中文免费版下载
    大数据学习笔记1.2 ——安装配置CentOS(二)
    dpi是什么意思
    检测域名是否支持http2.0协议脚本
    Matlab实现SUSAN角点检测
    zookeeper集群搭建步骤
    elasticsearch集群部署-实操
    C# 搭建一个简单的WebApi项目23.10.10
  • 原文地址:https://blog.csdn.net/weixin_41896770/article/details/126231680