1.9-改进的CBOW模型的实现

文章目录

  • 0引言
  • 1 CBOW模型的重构
    • 1.1模型初始化
    • 1.2模型的前向计算
    • 1.3模型的反向传播
  • 2总结

0引言

  1. 前面讲述了对word2vec高速化的改进:
    1. 改进输入侧的计算,变成Embedding,即从权重矩阵中选取特定的行;
    2. 改进输出侧的计算,包含两点
      1. 改进输出侧矩阵乘法,改为Embedding_dot层,Embedding部分其实与输入侧一样;dot部分就是将中间层的结果与Embedding部分的结果做内积得到一个值;
      2. 化多分类为二分类,将softmax改进为sigmoid,并引入负采样方法;损失函数依然使用交叉熵损失,只不过是二分类的;
  2. 接下来,将这两块的改进应用到CBOW模型上,重新构建CBOW模型以及学习代码。

1 CBOW模型的重构

代码位于:improved_CBOW/CBOW.py;代码文件链接:https://1drv.ms/u/s!AvF6gzVaw0cNjqNRnWXdF3J6J0scCA?e=3mfDlx;

1.1模型初始化

  1. 截止模型初始化,程序入口的代码如下:

    if __name__ == "__main__":text = "you say goodbye and I say hello."# 构建单词与编号之间的映射并将句子向量化corpus, word_to_id, id_to_word = preprocess(text)# contexts是一个维度为[6,2]的numpy数组contexts = np.array([[0, 2], [1, 3], [2, 4], [3, 1], [4, 5], [1, 6]])  # (6,2)target = np.array([1, 2, 3, 4, 1, 5])  # (6,)vocab_size = len(word_to_id)hidden_size = 3window_size = 1CBOW_model = CBOW(vocab_size, hidden_size, window_size, corpus)
    
  2. 改进之后CBOW模型的初始化代码如下:

    class CBOW:def __init__(self, vocab_size, hidden_size, window_size, corpus):V, H = vocab_size, hidden_size# 初始化权重W_in = 0.01 * np.random.randn(V, H).astype('f') # (7,3)# 因为W_out这里将使用embedding层,计算时需要转置,# 所以这里索性初始化就直接是转置后的W_out = 0.01 * np.random.randn(V, H).astype('f') # (7,3)# 生成层self.in_layers = []for i in range(2 * window_size):layer = Embedding(W_in)  # 使用Embedding层self.in_layers.append(layer)self.ns_loss = NegativeSamplingLoss(W_out, corpus, power=0.75, sample_size=3)# 将所有的权重和梯度整理到列表中layers = self.in_layers + [self.ns_loss]self.params, self.grads = [], []for layer in layers:self.params += layer.paramsself.grads += layer.grads# 将单词的分布式表示设置为成员变量self.word_vecs = W_in
    
  3. 关于初始化的代码,做如下解释:

    1. 因为W_out这里将使用Embedding层,前面的笔记中说过,计算时需要转置,所以这里索性初始化就直接是转置后的;因此从代码上来看,输入侧的权重和输出侧的权重维度相同,在学习的过程中分别去优化;
    2. 和之前一样,根据上下文窗口的大小,生成相应数量的输入层;只是这里改进之后,创建的是相应数量的Embedding层
    3. 根据负采样的sample_size,为每个负例创建相应的sigmoid层以及交叉熵损失计算层;为正例创建一个sigmoid层以及交叉熵损失计算层
  4. 再来看一下初始化的结果:

    1. 如下图:创建的CBOW_model包含输入层in_layers、输出侧的Embedding_dot层ns_loss.embed_dot_layers、sigmoid&交叉熵损失计算层ns_loss.loss_layers

      在这里插入图片描述

    2. 每一个Embedding_dot层都包含一个Embedding层,其中的参数维度都是(vocab_size,hidden_size);如下图所示:

      在这里插入图片描述

    3. 经过整理,所有的参数和梯度都被整理到一块,如下图所示;前两个是输入侧的两个权重矩阵的参数(因为上下文窗口大小为1),后面四个是输出侧一个正例和三个负例的权重矩阵的参数;梯度跟参数对应,这里就不列了;

      在这里插入图片描述

1.2模型的前向计算

  1. 为了进行前向计算,程序入口增加的代码如下:

    loss = CBOW_model.forward(contexts, target) # contexts:(6,2);target:(6,)
    
  2. 前向计算的代码如下:

    def forward(self, contexts, target):'''@param contexts: 目标词的上下文;(batch_size, 2*window_size);e.g. (6,2)@param target: 目标词;(batch_size,);e.g. (6,)'''h = 0for i, layer in enumerate(self.in_layers):h += layer.forward(contexts[:, i]) # h:(6,3)h *= 1 / len(self.in_layers) # 对h进行平均;window_size不一定是1,所以取决于self.in_layersloss = self.ns_loss.forward(h, target)return loss
    
  3. 关于输入侧的计算:

    1. 每次计算一个mini-batch的上下文的某一个单词的前向计算结果,因此每次传入的是contexts[:, i],维度是(6,);这是一个mini-batch的单词ID,forward方法会从该layer的权重矩阵中抽取对应的行,返回的结果就是(6,3)h
    2. 由于我们只改变了输入侧的计算方法,输入侧的计算结果仍然像之前一样,求平均;因此需要对所有输入层的中间结果求平均得到总的h
  4. 接着,在self.ns_loss.forward中,首先进行负例采样;根据传入的target,为其中每一个样本抽取sample_size个负例样本对应的单词ID,得到negative_sample,维度为(batch_size,sample_size)

  5. 接着,在self.ns_loss.forward中,进行正例的前向计算;将这一个mini-batch的正例从输出侧的权重矩阵中抽取对应的行,并于对应的中间结果做内积,得到这个mini-batch的得分,维度为(batch_size,);例如(6,);然后将这个得分和真实标签一起送入sigmoid&损失计算层,计算交叉熵损失得到损失值;这个损失值是一个标量,是一个mini-batch损失的平均值;

  6. 接着,在self.ns_loss.forward中,进行负例的前向计算;计算过程与正例一样;但因为每个样本的有sample_size个负例,因此一次同时处理一个mini-batch的某一个负例;然后将所有负例的损失累加的正例的损失中,作为最终的前向计算的损失值;

  7. 输出以及损失侧的计算步骤较多,这里再贴出来loss = self.ns_loss.forward(h, target)的具体过程:

    def forward(self, h, target):'''@param h: 中间层的结果,维度为(batch_size,hidden_dim); e.g. (6,3)@param target: 正确解标签;维度为(batch_size,); e.g. (6,)'''batch_size = target.shape[0]# 获取self.sample_size个负例解标签negative_sample = self.sampler.get_negative_sample(target) # (batch_size,sample_size); e.g. (6,3)# 正例的正向传播score = self.embed_dot_layers[0].forward(h, target) # (batch_size,) e.g. (6,)correct_label = np.ones(batch_size, dtype=np.int32) # 正例的真实标签自然是1;维度为(batch_size,) e.g. (6,)loss = self.loss_layers[0].forward(score, correct_label) # 损失标量# 负例的正向传播negative_label = np.zeros(batch_size, dtype=np.int32) # 负例的真实标签自然是0;维度为(batch_size,) e.g. (6,)for i in range(self.sample_size):# 对一个mini-batch的每一个负例样本,依次计算损失并累加到正例的损失上去negative_target = negative_sample[:, i] # (batch_size,) e.g. (6,)score = self.embed_dot_layers[1 + i].forward(h, negative_target) # (batch_size,)loss += self.loss_layers[1 + i].forward(score, negative_label)return loss
    

1.3模型的反向传播

  1. 为了进行反向传播,程序入口增加的代码如下:

    CBOW_model.backward()
    
  2. 先进行输出侧的反向传播:

    1. 输出侧由一个正例+sample_size个负例组成,根据计算图,它们求得的输出层的输入侧的梯度需要进行累加;

    2. 因此遍历所有的loss_layersembed_dot_layers,然后先进行loss_layer的反向传播,再进行embed_dot_layer的反向传播;

      1. 因为前向计算时每个损失是累加起来作为最终损失的,因此反向传播时传到每个损失里面的dout=1;于是就根据之前推导的结果,sigmoid+交叉熵损失的梯度是y-t,计算传递至loss_layer输入侧的梯度;
      2. 然后计算embed_dot_layer的反向传播;计算对dtarget_w的梯度以更新这个Embedding_dot层的权重参数;计算dh以将梯度传递至下游;过程在前面的笔记中讲解过;
    3. 由于过程较多,因此这里贴出来代码供查看;另外注释也更新了;

      # 输出侧损失反向传播入口
      dout = self.ns_loss.backward(dout)
      # 输出侧损失反向传播入口对应的反向传播函数
      def backward(self, dout=1):dh = 0# 中间层结果h到输出侧是进入了多个分支,因此反向传播时梯度需要累加for l0, l1 in zip(self.loss_layers, self.embed_dot_layers):# 依次对正例和每个负例所在的网络结构进行反向传播dscore = l0.backward(dout) # 损失函数(sigmoid和交叉熵损失)的反向传播,即y-t的结果;维度为(batch_size,); e.g. (6,)dh += l1.backward(dscore) # Embedding_dot的反向传播,包含保存各自权重矩阵对应的行的梯度return dh# sigmoid函数的反向传播
      def backward(self, dout=1):'''本质上是对sigmoid函数的输入求梯度'''batch_size = self.t.shape[0]dx = (self.y - self.t) * dout / batch_size # 这里将梯度平均了;维度为(batch_size,); e.g. (6,)return dx# Embedding_dot层的反向传播
      def backward(self,dout):'''@param dout: 上游损失函数的梯度;形状为(batch_size,);e.g. (6,)'''h,target_w=self.cachedout=dout.reshape(dout.shape[0],1) # 这里是为了保证dout的形状与h的形状一致;形状为(batch_size,1);e.g. (6,1)dtarget_w=dout*h # 对应元素相乘;dout:[batch_size,1];h:[batch_size,hid_dim];所以会进行广播;形状为(batch_size,hidden_dim);e.g. (6,3)self.embed.backward(dtarget_w) # 把梯度更新到权重矩阵的梯度矩阵的对应行;先前在执行self.embed.forward(idx)时已经保存了使用的idxdh=dout*target_w # 对应元素相乘;会进行广播;形状为(batch_size,hidden_dim);e.g. (6,3)return dh
      
  3. 然后是中间层的梯度,因为前向计算时,是对window_size个输入层的输出结果平均了,才得到的h;所以执行如下语句计算中间层的梯度;

    dout *= 1 / len(self.in_layers)
    
  4. 最后,计算window_size个输入层的梯度,即Embedding层;由于只是从输入侧权重矩阵中选取了特定行,因此梯度的传播仅仅是将上游传递来的梯度值放到对应的梯度矩阵中;代码如下:

    for layer in self.in_layers:layer.backward(dout)
    

2总结

  1. 几点注意
    1. 关于这里使用的batch_size的含义:个人理解,这里的批处理大小并不是指通常意义的样本数(or句子数),CBOW模型每次的输入就是目标词的上下文单词;一个目标词对应的上下文单词构成mini-batch里面的一条数据;
    2. 改进之前,输入和输出都是使用的独热编码,改进之后,不再使用;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/44340.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】【排序算法】----- 归并排序

由于最近要考试,好久没有发博客了,非常抱歉大家对我的支持。之后我会不断更新博客,继续创作出高质量的文章,希望能帮到大家! 文章目录 一、归并排序基本思想二、递归实现三、非递归实现四、效率分析 一、归并排序基本…

KDP数据分析实战:从0到1完成数据实时采集处理到可视化

智领云自主研发的开源轻量级Kubernetes数据平台,即Kubernetes Data Platform (简称KDP),能够为用户提供在Kubernetes上的一站式云原生数据集成与开发平台。在最新的v1.1.0版本中,用户可借助 KDP 平台上开箱即用的 Airflow、AirByte、Flink、K…

关于原型和原型链的学习和实践

在前端面试中,原型和原型链始终是一个避不开的问题,今天就弄明白! 原型和原型链 对象的创建方式工厂模式构造函数模式原型模式 原型和原型链实践 对象的创建方式 原型和原型链都是关于对象的内容,先来看一下JavaScript中对象的构建方式。 工…

代码随想录(day3)有序数组的平方

暴力求解法: 注意:需要确定范围,比如nums.sort()是在for循环之外,根据函数的功能来确定 return返回的是nums,而不是nums[i]因为返回的是整个数组 class Solution(object):def sortedSquares(self, nums):for i in r…

人话学Python-基础篇-数字计算

一:数字类型 对于最常见的数据类型,数字在Python中分为三类: 整型(int) 表示的是整数类型的所有数字,包括正整数,负整数和0。和C语言不同的是,Python中的int型没有范围的限制,理论上可以从无限小的整数取到…

react基础语法,模板语法,ui渲染,jsx,useState状态管理

创建一个react应用 这里使用create-react-app的脚手架构建项目(结构简洁,基于webpack-cli), npx create-react-app [项目名称] 使用其他脚手架构建项目可以参考:react框架,使用vite和nextjs构建react项目…

数学建模国赛入门指南

文章目录 认识数学建模及国赛认识数学建模什么是数学建模?数学建模比赛 国赛参赛规则、评奖原则如何评省、国奖评奖规则如何才能获奖 国赛赛题分类及选题技巧国赛赛题特点赛题分类 国赛历年题型及优秀论文数学建模分工技巧数模必备软件数模资料文献数据收集资料收集…

白蛇插画:成都亚恒丰创教育科技有限公司

白蛇插画:古韵今风,情深意长 在浩瀚的艺术长河中,插画作为一种独特的艺术形式,以其生动形象的画面、丰富多彩的色彩和深邃悠远的意境,成都亚恒丰创教育科技有限公司深受人们喜爱。而“白蛇插画”,作为融合…

bug - while parsing file included at

bug 如下 找到这个对应文件tb_top.sv的对应行,发现是一个 include "inc_tb_tests_xxx.sv" 问题点:头文件,重复定义,那么 解决方法- 在被include的文件首尾加入 ifndef MY_TRANSACTION__SV define MY_TRANSACTION__SV …

GenAI 技术堆栈架构师指南 - 十种工具

这篇文章于 2024 年 6 月 3 日首次出现在 The New Stack 上。 我之前写过关于现代数据湖参考架构的文章,解决了每个企业面临的挑战——更多的数据、老化的Hadoop工具(特别是HDFS)以及对RESTful API(S3)和性能的更大需求…

前端--第一个前端程序

第一个前端程序 第一步: 使用记事本,编写代码 在你的一个磁盘里面创建一个文件夹,名为前端,然后在里面新建一个记事本,在里面写如下代码,注意一定要使用英文,然后把后缀名称改为.html。 第二…

你明白C++中的多态吗?(暑假提升-多态专题)

内不欺己,外不欺人。———孔子 有趣的多态 1、前言2、概念3、多态定义与产生条件4、多态的重要组成成员-(虚函数)5、虚函数的重写(覆盖)6、辅助关键字override与final(了解即可)7、重载,重定义(隐藏),重写(覆盖)8、抽象类9、多态的原理9、1、…

PHP老照片修复文字识别图像去雾一键抠图微信小程序源码

🔍解锁复古魅力,微信小程序黑科技大揭秘!老照片修复&更多神奇功能等你来试! 📸 【老照片修复,时光倒流的美颜术】 你是否珍藏着一堆泛黄的老照片,却因岁月侵蚀而模糊不清?现在…

实验02 黑盒测试(组合测试、场景法)

1. 组合测试用例设计技术 指出等价类划分法和边界值分析法通常假设输入变量相互独立,但实际情况中变量间可能存在关联。全面测试:覆盖所有输入变量的所有可能组合,测试用例数量随输入变量的增加而指数增长。 全面测试需要对所有输入的各个取…

2008年上半年软件设计师【上午题】真题及答案

文章目录 2008年上半年软件设计师上午题--真题2008年上半年软件设计师上午题--答案 2008年上半年软件设计师上午题–真题 2008年上半年软件设计师上午题–答案

按模版批量生成定制合同

提出问题 一个仪器设备采购公司,商品合同采购需要按模版生成的固定的文件,模板是固定的,只是每次需要替换信息,然后打印出来寄给客户。 传统方法 如果手工来做这个事情,准备好数据之后,需要从Excel表格中…

高效应对网络攻击,威胁检测响应(XDR)平台如何提升企业应急响应能力

在数字化时代,企业面临的网络攻击威胁持续增加,如恶意软件、勒索软件、钓鱼攻击、DDoS攻击等。这些威胁不仅危及企业数据安全、系统稳定,还损害了品牌形象和市场信任。随着云计算、大数据、物联网的广泛应用,企业网络攻击面扩大&a…

MAVLink代码生成-C#

一. 准备Windows下安装环境 Python 3.3 – 官网链接下载Python future模块 –pip3 install future TkInter (GUI 工具). – python for Windows自带,无需下载环境变量PYTHONPATH必须包含mavlink存储库的目录路径。 –set PYTHONPATH你的mavlink源码路径 源码下载在…

【昆工主办|7月昆明】第三届绿色建筑、土木工程与智慧城市国际会议(GBCESC 2024)

随着全球城市化进程的加速,绿色建筑、土木工程与智慧城市等议题逐渐成为了行业内外关注的焦点。在这一背景下,第三届绿色建筑、土木工程与智慧城市国际会议(GBCESC 2024)的召开,无疑将为相关领域的研究者、学者及从业者…

原理和组成

能力要素:(1)人员要素:“正确选人”。(2)过程要素:“正确做事”。(3)技术要素:“高效做事”。(4)资源要素:“保障做事”。…