循环神经网络 rnn
卷积网络(cnn)是用来处理空间信息(一张图像),循环神经网络(rnn)则用来处理序列信息(一句话,一个视频的图像帧,温度的变化序列)
rnn引入了状态变量存储序列信息和当前输入,从而预测下一个输入,以此不断循环,预测整段序列信息。
序列模型
对序列模型进行建模,考虑到成千上万的生活中的例子,比如许多观众对该电影的评分,比如一次大地震后的数次余震,一段新闻标题,一段股票价格序列,还有一长段人们在社交媒体上的对话。
综上,可以引入时间步t的概念,时间序列t1, t2, t3, t4…., 还有X(t1), X(t2), X(t3) X(t4) ….., 对于电影评分来说,X(t)就是在t时间步该电影的评分,新闻标题则, X(t)就是在t时间步的单词,股价则是t时间步股票的价格。整个建模的公式就是
P(xt| xt-1, … xt1)-xt, 预测在已经发生了xt1到xt-1的时间步后,下一个xt的概率是多少。以地震为例子,比如某地发生了8级大地震,记xt0=8, 后面发生了数次小级别的余震分别是3, 4, 3, 5,记xt1=3, xt2=4, xt3=3, xt4=5。所以需要计算 P(1 | xt4=5,xt3=3,xt2=4,xt1=3,xt0=8),也就是在这些地震发生后,下一次余震是1级的概率。以此类推,算出2,3,4,5,6,7,8,9,10级的余震的概率,然后取概率最高者作为xt5。
在这种模型里会遇到一个问题,这个问题在于序列越来越长的时候,
1 计算量将会变得不可接受
2 同时对现实时间的模拟也会失真。
比如,某地遇地震等级的序列在现实中,其实大部分时间步都为0,【0,0,0,0,0,0,0,0,0,无数的0….. , 8, 3 4 5 ,4, 2, 3, 1, 1, 1】
两种策略:
1 自回归(autogressive),对过去序列取一个有效的时间步长度,比如在上述例子中,摒弃掉所有前面的0。
2 隐式变量自回归(latent autogressive) 使用一个变量来对过去的序列进行总结,比如上述例子中,使用sum,对之前的无数的0序列,都叠加到这个sum中,最终还是0。
对于1类策略,落地到数学工具则是马尔可夫模型。
举个例子,预测地震等级,P(xt=2|xt-1=3, xt-2=4 … xt0=8) 约等于 P(xt=2| xt-1=3),当然也可以约等于 P(xt=2| xt-1=3, xt-2=4),前者是1阶马尔可夫,后者是2阶马尔可夫。
这样形成一个马尔科夫链,从而很容易计算出 P(xt=2|xt-1=3, xt-2=4 … xt0=8)
针对马尔可夫链的思考还有因果关系,也是就是时间步总是向前的,很显然未来不能影响过去,可以针对现有的历史数据,预测下一个时间步出现的数据,但是不可逆。
但是我个人对这个因果关系是持有反对立场,因为在考古学,过去是对自然界是已知的,但是对人类是未知的,比如发生在数万年前的地球温度的变化,也可以通过逆向的马尔可夫链来预测。
demo:
暂无
对于2类策略,先不表。
序列模型中的明珠,就是文字序列问题。
这一章节主要介绍了一些,读取数据集的手段:
1 读取文字序列
demo中读取了一本小说,主要是一行一行字符串序列读取的。
2 tokenize 词元化
还有tokenize函数,这是用来对字符串序列进行分割,成一个一个单词(word-level,后面bpe的byte-level才是好的,先不说)。
3
tokenizer了之后,是词表的构建,对于一个单词分配出现频次记作索引值,低频词会被移除,映射为【unk】,并且补充【pad】,【bos】,【eos】作为填充词元,开始词元,结束词元。
遍历语料,构建词表。
语言模型和数据集合
语言模型是自然语言处理的关键。而语言模型的建模和序列模型的建模一致,都是计算下一个词汇出现的概率
P(xt| xt-1, … xt1)-xt,
比如一个序列: deep learning is fun
P(deep) ?
P(learning) = n(deep, learning)/n(deep)
p(is) = n(deap, learning, is)/n(deep, learning)
这里就提到了 拉普拉斯平滑,有点不理解,因为拉普拉斯平滑依旧不适合应对多个n元词出现的情况。
齐普夫定律支配着单词的分布,这个分布不仅适用于一元语法,还适用于其他n元语法。
这里我是有疑问,为什么齐普夫高估了尾部单词的频率
对于zipf’law 指导了几个事实:
1 n元词受zip定律支配
2 n元词组数目不大
3 有可以使用深度学习的希望
读取长序列的主要方式是随机采样和顺序分区。
在迭代过程中,后者可以保证来自两个相邻的小批量中的子序列在原始序列上也是相邻的
关于顺序分区和随机采样有demo
所以如何使用深度学习的方法来研究字符串序列模型,是最主要的方法。
而可落地的方式就是之前讲到的隐式变量法
P( x(t) | x(t-1), x(t-2), … x(t1) ) 约等于 P ( x(t) | h(t-1))
问题在于,h(t-1) 是个必须包含【x(t-1), x(t-2), … x(t1) 】所有信息的隐变量。
而为了避免大量的计算,需要转变成一个一个时间步去计算
h(t)=f(h(t-1), x(t))
为什么设置为这样的函数,因为h(t)需要有过去所序列的信息,同时也包含 x(t)的信息,kind of like 斐波那契函数。
so, 变成了这样的格式后。下一步要思考的就是h(t-1) 与 x(t) 如何结合起来。
先考虑特例情况:
h(1)是只包含了x(1)信息的隐变量。
h(1) = f(x(1)), 这种情况比就是最普遍最简单的,多层感知机么?但是多层感知机,是肯定有output的。
h(1) = x(1)*w(xh) + b(h), ps:h(1)的维度是n x h
o(1)= activation(h1)*w(ho) + b(o)
那么考虑一下h(2), 其实参考多层感知机的思想,就是给自变量加个权重和bias
h(2) = f(h(1), x(2)) = h(1)*w(hh) + x(2)*w(xh) + b(h) ps: h(1)是nxh,所以h(1)*w(hh)是nxh, x(2)是nxd,x(2)*w(xh)是nxh,两个相加依旧是nxh, 然后加上bias
o(2) = activation(h2)*w(ho) + b(o)
x(2)是哪里来的呢?这种情况下,只有从o(1)来。
所以这个最终总结下来的公式是:
x(t) 是nxd, n批量大小,d为特征
H(t), H(t-1)是nxh,n是批量大小,h是隐藏数目
W(ho)是hxo,h隐藏数目,o是输出维度,一般是词表大小,映射都某一个词
H(t) =activation( x(t)*w(dh) + H(t-1)*w(hh) + b(h) )
O(t) = H(t)*W(ho) + b(o)
代码测试
“””
X, W_xh = torch.normal(0, 1, (3, 1)), torch.normal(0, 1, (1, 4))
H, W_hh = torch.normal(0, 1, (3, 4)), torch.normal(0, 1, (4, 4))
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)
但是应该可以使用拼接再相乘
torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))
“””
这里的公式里的两次矩阵相乘,可以转化成一次相乘,但是这样的转化效率会不会变高,则不知道了。
perplexity,当有了这样的一个模型,我们预测三组数据
And don’t be sad and don’t cry
And don’t be sad and don’t eat apple and banna
And don’t be sad and don’t sdsdsadawda
很显然需要一个数值来衡量模型的准确性,从人类的角度,第一句话肯定是最对的。
第二句话说明模型可以合成单词了,但是没有把握含义,第三句话说明模型欠拟合。
想探索perplexity,有点难。我还是记住就好了。
当perplexity为1的时候是最好的,
当perplexity为1的时候是最差的,
当perplexity为词表唯一词的数目是基线。
所以接下来就是实践,也就是write from scratch, 有两个章节和很多代码,just skip
从0实现和简洁实现rnn
当训练的时候,其实是有两个主要的问题,梯度爆炸和梯度消失。
但是这个问题,这个章节,在未晚上上述的情况下,很难进行学习。
也就是通过实践反向传播这一章节