从零实现深度学习框架——前馈网络语言模型
引言
本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。
要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不使用外部完备的框架前提下,实现我们想要的模型。本系列文章的宗旨就是通过这样的过程,让大家切实掌握深度学习底层实现,而不是仅做一个调包侠。
我们前面学习过n-gram语言模型,但是n-gram模型中的n不能太大,这限制了它的应用。本文我们学习如何基于前馈神经网络来构建语言模型。
前馈网络语言模型
我们知道语言模型是基于前面的上下文单词来预测下一个单词。
前馈网络语言模型1的架构图如上图所示。分为四层:
- 输入层(Input Layer)
- 投影层(Projection Layer)
- 隐藏层(Hidden Layer)
- 输出层(Output Layer)
输入是每个单词的独热编码。类似N-gram,神经概率语言模型也有一个窗口。根据投影层(嵌入层)得到窗口大小的嵌入向量。然后拼接这些嵌入向量,经过一个隐藏层,激活函数是tanh\text{tanh}tanh。然后经过softmax\text{softmax}softmax得到给定上下文窗口下预测下一个单词的概率分布。
我们具体来看一看。
前向推理
和Word2vec一样,也有时间步的概念。在每个时间步,假设词典大小为∣V∣|V|∣V∣,给定窗口大小。我们使用长度为∣V∣|V|∣V∣的独热编码表示NNN个之前的单词。
假设N=3N=3N=3,我们这里就有三个独热编码。
如上图所示,在时间步ttt,都有3个独热编码,大小为∣V∣×3|V| \times 3∣V∣×3,分别乘上嵌入矩阵E∈Rd×∣V∣E \in \Bbb R^{d \times |V|}E∈Rd×∣V∣。得到3个大小为d×1d \times 1d×1的嵌入向量。
假设某个独热编码只有索引为5处的元素为1,那么乘上嵌入矩阵后,相当于选择了嵌入矩阵的第5列,这种操作我们已经在word2vec中见过了。
然后我们把这三个嵌入向量拼接起来得到e∈R3d×1e \in \Bbb R ^{3d \times 1}e∈R3d×1,这就是投影层的结果。然后嵌入向量eee乘以一个权重矩阵W∈Rdh×3dW \in \Bbb R^{d_h \times 3d}W∈Rdh×3d,dhd_hdh为隐藏层大小,得到隐藏状态h∈Rdh×1h \in \Bbb R^{d_h \times 1}h∈Rdh×1。然后经过tanh\tanhtanh激活函数,维度不变。最后乘以矩阵U∈R∣V∣×dhU \in \Bbb R^{|V| \times d_h}U∈R∣V∣×dh,得到维度为∣V∣×1|V| \times 1∣V∣×1的向量,经过softmax\text{softmax}softmax得到了整个词典内所有单词的概率分布。
总结一下,上面例子中使用的算法如下:
- 从嵌入矩阵EEE中选择三个嵌入向量:给定三个之前的单词,查看索引,得到3个独热编码向量,然后乘上矩阵EEE。假设wt−3w_{t-3}wt−3代表索引35处单词
for
的独热编码,乘上矩阵EEE,得到d×1d \times 1d×1的嵌入向量。即索引iii处的独热编码经过Exi=eiEx_i=e_iExi=ei。然后拼接3个单词嵌入,得到3d×13d \times 13d×1的嵌入eee。 - 乘上WWW:我们再乘上WWW(可能加上对应偏置),经过激活函数(tanh\tanhtanh或ReLU\text{ReLU}ReLU)得到隐藏向量hhh。
- 乘上UUU:hhh接着乘上UUU,将其维度扩展成和词典大小一致。
- 应用softmax\text{softmax}softmax:在经过softmax\text{softmax}softmax,得到了词典中每个单词作为下一个单词的概率P(wt=i∣wt−1,wt−2,wt−3)P(w_t=i|w_{t-1},w_{t-2},w_{t-3})P(wt=i∣wt−1,wt−2,wt−3)。
其对应的公式为:
e=[Ext−3;Ext−2;Ext−1]h=σ(We+b)z=Uhy^=softmax(z)(1)\begin{aligned} e &= [E_{x_{t-3}}; E_{x_{t-2}};E_{x_{t-1}}] \\ h &= σ(We+b) \\ z &= Uh \\ \hat y &= \text{softmax}(z) \end{aligned} \tag 1 ehzy^=[Ext−3;Ext−2;Ext−1]=σ(We+b)=Uh=softmax(z)(1)
我们用;;;来表示拼接。我们已经知道了前向推理的过程,那么如何定义损失函数呢?
损失函数
显然,这是一个多分类问题。一般使用交叉熵损失函数。
首先,当我们有多类时,我们需要将yyy和y^\hat yy^都表示为向量。假设我们处理硬分类,即只有一个正确类别。真实标签yyy就是一个个数为KKK的独热编码,如果真实类别为ccc,那么只有yc=1y_c=1yc=1。而我们的分类器会产生同样KKK个元素的估计向量y^\hat yy^,每个元素y^k\hat y_ky^k代表估计概率p(yk=1∣x)p(y_k = 1|x)p(yk=1∣x)。
对于单个样本的损失函数就是KKK个输出类别概率的负对数之和,通过类别对应的真实概率yky_kyk加权:
LCE(y^,y)=−∑k=1Kyklogy^k(2)L_{CE}(\hat y, y) = - \sum^K_{k=1} y_k \log \hat y_k \tag 2 LCE(y^,y)=−k=1∑Kyklogy^k(2)
我们可以进一步地简化该公式。我们先使用1{}\Bbb{1}\{\}1{}重写,如果括号内的条件为真那么返回111,否则返回000。那么可以得到:
LCE(y^,y)=−∑k=1K1{yk=1}logy^k(3)L_{CE}(\hat y,y) = -\sum_{k=1}^K \Bbb{1}\{y_k=1\} \log \hat y_k \tag 3 LCE(y^,y)=−k=1∑K1{yk=1}logy^k(3)
只有真实类别yk=1y_k=1yk=1的时候才不为000。换言之,交叉熵损失就是简单的负对数正确类别的输出概率,我们称为负对数似然损失:
LCE(y^,y)=−logy^cc是正确类别(4)L_{CE}(\hat y,y) = -\log \hat y_c \quad \text{$c$是正确类别} \tag 4 LCE(y^,y)=−logy^cc是正确类别(4)
通过softmax函数展开,并有KKK个类别时:
LCE(y^,y)=−logexp(zc)∑j=1Kexp(zj)c是正确类别(5)L_{CE}(\hat y,y) = -\log \frac{\text{exp}(z_c)}{\sum_{j=1}^K \exp(z_j)} \quad \text{$c$是正确类别} \tag 5 LCE(y^,y)=−log∑j=1Kexp(zj)exp(zc)c是正确类别(5)
下面来看如何训练前馈网络语言模型。
训练
我们需要的参数θ=E,W,U,b\theta=E,W,U,bθ=E,W,U,b。
训练的目的是得到嵌入矩阵EEE,我们就可以像使用word2vec中的嵌入矩阵一样,使用该矩阵EEE来获取每个单词的嵌入向量。
还是上面的例子,给定上下文for all the
来预测下一个单词fish
的概率。
如上小节所介绍的,我们使用交叉熵(负对数似然)损失:
LCE(y^,y)=−logy^cc是正确类别(6)L_{CE}(\hat y,y) = -\log \hat y_c \quad \text{$c$是正确类别} \tag 6 LCE(y^,y)=−logy^cc是正确类别(6)
对于语言模型来说,类别就是词典中的所有单词,所以y^c\hat y_cy^c意味着模型分配给正确单词wtw_twt的概率:
LCE=−logp(wt∣wt−1,⋯,wt−n+1)(7)L_{CE} = -\log p(w_t|w_{t-1},\cdots,w_{t-n+1}) \tag 7 LCE=−logp(wt∣wt−1,⋯,wt−n+1)(7)
我们希望模型分配给正确单词的概率越高越好,通过最小化上面的损失来更新参数θ\thetaθ。
代码实现
数据集
数据还是西游记数据集。
数据集下载 → 提取码:nap4
class NGramDataset(Dataset):def __init__(self, corpus, vocab, window_size=4):self.data = []self.bos = vocab[BOS_TOKEN]self.eos = vocab[EOS_TOKEN]for sentence in tqdm(corpus, desc="Dataset Construction"):# 插入句首句尾符号sentence = [self.bos] + sentence + [self.eos]if len(sentence) < window_size:continuefor i in range(window_size, len(sentence)):# 模型输入:长为context_size的上文context = sentence[i - window_size:i]# 模型输出:当前词target = sentence[i]self.data.append((context, target))self.data = np.asarray(self.data)def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):# 从独立样本集合中构建batch输入输出inputs = Tensor([ex[0] for ex in examples])targets = Tensor([ex[1] for ex in examples])return inputs, targets
模型
创建前馈网络语言模型类2,参数主要包括词嵌入层、词嵌入到隐藏层、隐藏层到输出层。
class FeedForwardNNLM(nn.Module):def __init__(self, vocab_size, embedding_dim, window_size, hidden_dim):# 单词嵌入E : 输入层 -> 嵌入层self.embeddings = nn.Embedding(vocab_size, embedding_dim)# 词嵌入层 -> 隐藏层self.e2h = nn.Linear(window_size * embedding_dim, hidden_dim)# 隐藏层 -> 输出层self.h2o = nn.Linear(hidden_dim, vocab_size)self.activate = F.reludef forward(self, inputs) -> Tensor:embeds = self.embeddings(inputs).reshape((inputs.shape[0], -1))hidden = self.activate(self.e2h(embeds))output = self.h2o(hidden)log_probs = F.log_softmax(output, axis=1)return log_probs
训练
if __name__ == '__main__':embedding_dim = 64window_size = 2hidden_dim = 128batch_size = 1024num_epoch = 10min_freq = 3 # 保留单词最少出现的次数# 读取文本数据,构建FFNNLM训练数据集(n-grams)corpus, vocab = load_corpus('../../data/xiyouji.txt', min_freq)dataset = NGramDataset(corpus, vocab, window_size)data_loader = DataLoader(dataset,batch_size=batch_size,collate_fn=dataset.collate_fn,shuffle=True)# 负对数似然损失函数nll_loss = NLLLoss()# 构建FFNNLM,并加载至devicedevice = cuda.get_device("cuda:0" if cuda.is_available() else "cpu")model = FeedForwardNNLM(len(vocab), embedding_dim, window_size, hidden_dim)model.to(device)optimizer = SGD(model.parameters(), lr=0.001)total_losses = []for epoch in range(num_epoch):total_loss = 0for batch in tqdm(data_loader, desc=f"Training Epoch{epoch}"):inputs, targets = [x.to(device) for x in batch]optimizer.zero_grad()log_probs = model(inputs)loss = nll_loss(log_probs, targets)loss.backward()optimizer.step()total_loss += loss.item()print(f"Loss:{total_loss:.2f}")total_losses.append(total_loss)# 保存词向量(model.embeddings)save_pretrained(vocab, model.embeddings.weight.data, "ffnnlm.vec")
完整代码
https://github.com/nlp-greyfoss/metagrad
References
A Neural Probabilistic Language Model ↩︎
自然语言处理:基于预训练模型的方法 ↩︎
从零实现深度学习框架——前馈网络语言模型相关推荐
- python学习框架图-从零搭建深度学习框架(二)用Python实现计算图和自动微分
我们在上一篇文章<从零搭建深度学习框架(一)用NumPy实现GAN>中用Python+NumPy实现了一个简单的GAN模型,并大致设想了一下深度学习框架需要实现的主要功能.其中,不确定性最 ...
- 从零实现深度学习框架——GloVe从理论到实战
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导.
- 从零实现深度学习框架——Seq2Seq从理论到实战【实战】
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导.
- 从零实现深度学习框架——RNN从理论到实战【理论】
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导.
- 从零实现深度学习框架——深入浅出Word2vec(下)
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导. 要深入理解深度学 ...
- 从零实现深度学习框架——从共现矩阵到点互信息
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导.
- 从零实现深度学习框架——LSTM从理论到实战【理论】
引言 本着"凡我不能创造的,我就不能理解"的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导.
- 深度学习框架不能“包治百病”,开发者如何选出最适合自己的?
随着深度学习关注度和势头上升,深度学习被越来越多的企业和组织的生产实践结合起来.这时,无论是对于深度学习相关专业的初学者,还是已经在企业和组织中从事工业场景应用和研发的开发者来说,选择一个适合自己,适 ...
- 初学者如何选出最适合自己深度学习框架?
无论是对于深度学习相关专业的初学者,还是已经在企业和组织中从事工业场景应用和研发的开发者来说,选择一个适合自己,适合业务场景需求的深度学习框架显得尤为重要.下边对现有深度框架做一个全面的阐述及解答. ...
- 【人工智能】【深度学习】初学者如何选出最适合自己深度学习框架?
无论是对于深度学习相关专业的初学者,还是已经在企业和组织中从事工业场景应用和研发的开发者来说,选择一个适合自己,适合业务场景需求的深度学习框架显得尤为重要.下边对现有深度框架做一个全面的阐述及解答. ...
最新文章
- UVA 1515 - Pool construction(最小割)
- Nvidia 安装相关文件下载地址
- c语言 typedef的用法
- android 最新消息滚动,Android 滚动操作Scroller类详解
- 排序算法之---堆排序(很重要的一个结构,新手入门必备)
- 考研复习——时间安排小结
- (软件工程复习核心重点)第四章总体设计-第一节:总体设计基本概念和设计过程
- centos yum 安装mysql
- little定理的证明,网络的时延模型,little定理的例子
- Redis的缓存数据过期策略,内存淘汰机制
- win10系统镜像下载及在VMware虚拟机上创建虚拟机
- 计算机网络第七版谢希仁课后答案第三章(部分答案)
- C语言入门——适合练手的密码本项目
- 容安馆札记 606-610则 笺疏
- java程序员要学什么?
- 人生若只如初见,当时只道是寻常
- 第四章 线程切换与调度——操作系统的发动机
- Kindle电子书的资源汇总
- python基于PHP+MySQL的志愿者管理系统
- EU.org免费域名申请教程
热门文章
- Java十二平均律判断
- 电脑主机 外置usb蓝牙适配器 连接后声音断断续续
- 2018 年计算机语言排行榜,TIOBE:2018年11月编程语言排行榜
- python编写字典库_用Python生成MySql数据字典
- 代码大全(第2版)_2021【公式大全3.0版】【(数一)第371页】【(数二)第283页】【(数三)第324页】【有关矩阵秩的重要结论】6)~...
- FZOJ P2109 【卡德加的兔子】
- 20155313 杨瀚 《网络对抗技术》实验五 MSF基础应用
- HDU - Shaolin(STL)
- 如何抓取图片php,PHP网络爬虫之图片抓取
- 1)华为手机使用电脑批量管理联系人 - 2)华为云空间联系人同步到手机 - 3)华为手机导入联系人列表