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.rhkb.cn/news/373658.html

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

相关文章

100359.统计X和Y频数相等的子矩阵数量

1.题目描述 给你一个二维字符矩阵 grid,其中 grid[i][j] 可能是 X、Y 或 .,返回满足以下条件的子矩阵数量: 包含 grid[0][0]X 和 Y 的频数相等。至少包含一个 X。 示例 1: 输入: grid [["X","Y",…

《UDS协议从入门到精通》系列——图解0x84:安全数据传输

《UDS协议从入门到精通》系列——图解0x84:安全数据传输 一、简介二、数据包格式2.1 服务请求格式2.2 服务响应格式2.2.1 肯定响应2.2.2 否定响应 Tip📌:本文描述中但凡涉及到其他UDS服务的,均提供专栏内文章链接跳转方式以便快速…

如何利用AI自动生成绘画?5款AI绘画的六大神器!

以下是五款专业级别的AI绘画工具,它们能够帮助用户迅速生成高质量的AI艺术作品: 1.AI先行者: 这是一款流行的 AI 绘画平台,它利用深度学习技术将你的照片或图像转换成艺术风格的绘画作品。你可以在线使用上上传图片并选择喜欢的艺…

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

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

解锁短视频运营新高度:视频号矩阵源码,定时自动发布,畅享高效管理

在数字时代浪潮下,短视频已然成为信息传播的重要渠道。对于内容创作者和企业来说,如何高效地管理和运营短视频账号,实现内容的定时自动发布,成为了一个亟待解决的问题。今天,我们将为您揭秘一款短视频运营的新利器——…

蓝卓创始人褚健:工业软件是数字化转型的灵魂和核心驱动力

如果把“工业3.0”简单理解为就是“自动化”,“工业4.0”理解为是“智能化”,那么“智能化”的实现一定要有软件。如同今天的移动互联网,是因为有大量的APP,所以让人们进入了智能时代。映射到工业、制造业领域,就是要依…

第4章 课程发布:模块需求分析,课程预览(模板引擎 静态页面),课程审核,课程发布(分布式事务,页面静态化:熔断降级),课程搜索(es索引)

1 模块需求分析 1.1 模块介绍 课程信息编辑完毕即可发布课程,发布课程相当于一个确认操作,课程发布后学习者在网站可以搜索到课程,然后查看课程的详细信息,进一步选课、支付、在线学习。 下边是课程编辑与发布的整体流程&#…

PHP全域旅游景区导览系统源码小程序

🌍【探索无界,畅游无忧】全域旅游景区导览系统小程序全攻略 📱【一键启动,智能导览在手】 告别纸质地图的繁琐,迎接全域旅游景区导览系统小程序的便捷时代!只需轻轻一点,手机瞬间变身私人导游…

C++ 编译体系入门指北

前言 之从入坑C之后,项目中的编译构建就经常跟CMake打交道,但对它缺乏系统的了解,遇到问题又陷入盲人摸象。对C的编译体系是如何发展的,为什么要用CMake,它的运作原理是如何的比较感兴趣,所以就想系统学习…

CentOS7 安装 git 命令

通过yum源install下载的git版本比较低,不推荐此方式安装。 官网下载最新版git源码:Git 1. 解压安装包 tar -xzvf git-2.45.2.tar.gz 2. 安装相关依赖 yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel gcc perl-ExtUtils…

第三方商城对接重构(HF202407)

文章目录 项目背景一、模块范围二、问题方案1. 商品模块2. 订单模块3. 售后4. 发票5. 结算单 经验总结 项目背景 作为供应商入围第三方商城成功,然后运营了一段时间,第三方通知要重构, 需要重新对接打通接口完成系统对接,能贯穿整…

【QT中实现摄像头播放、以及视频录制】

学习分享 1、效果图2、camerathread.h3、camerathread.cpp4、mainwindow.h5、mainwindow.cpp6、main.cpp 1、效果图 2、camerathread.h #ifndef CAMERATHREAD_H #define CAMERATHREAD_H#include <QObject> #include <QThread> #include <QDebug> #include &…

Mybatis的优缺点及适用场景?

目录 一、什么是Mybatis&#xff1f; 二、Mybatis框架的特点 三、Mybatis框架的优点&#xff1f; 四、MyBatis 框架的缺点&#xff1f; 五、MyBatis 框架适用场合&#xff1f; 六、代码示例 1. 配置文件 mybatis-config.xml 2. 映射文件 UserMapper.xml 3. Java 代码…

coco_eval 使用

参考 coco eval 解析 COCO目标检测比赛中的模型评价指标介绍&#xff01; coco 的评估函数对应的是 pycocotools 中的 cocoeval.py 文件。 从整体上来看&#xff0c;整个 COCOeval 类的框架如图&#xff1a; 基础的用法为 # The usage for CocoEval is as follows: cocoGt…

深入解析视频美颜SDK:开发直播平台主播专用的美颜工具教学

本篇文章&#xff0c;笔者将深入解析视频美颜SDK的原理与应用&#xff0c;帮助开发者打造适用于直播平台的专业美颜工具。 一、视频美颜SDK的基础原理 视频美颜SDK其核心技术包括人脸检测、面部特征点识别、图像增强和特效应用等。 二、视频美颜SDK的开发流程 环境搭建 首先…

Redis+Caffeine 实现两级缓存

RedisCaffeine 实现两级缓存 背景 ​ 事情的开始是这样的&#xff0c;前段时间接了个需求&#xff0c;给公司的商城官网提供一个查询预计送达时间的接口。接口很简单&#xff0c;根据请求传的城市仓库发货时间查询快递的预计送达时间。因为商城下单就会调用这个接口&#xff…

人工智能建立在对象存储上的真正原因

tl;dr: 在这篇文章中&#xff0c;我们将探讨 AI 工作负载依赖高性能对象存储的四个技术原因。 1. 对非结构化数据没有限制 在当前的机器学习范式中&#xff0c;性能和能力与计算成比例&#xff0c;计算实际上是数据集大小和模型大小的代理&#xff08;神经语言模型的缩放定律&a…

74HC165芯片验证

目录 0x01 74HC165芯片介绍0x02 编程实现 0x01 74HC165芯片介绍 74HC165的引脚定义如下&#xff0c;长这个样子 ABCDEFGH是它的八个输入引脚&#xff0c;例如你可以将它连接按键&#xff0c;让它来读取8个按键值。也可以将他级联其它的74165&#xff0c;无需增加单片机GPIO引…

Msfvenom制作自己的专属Shell

Msfvenom制作自己的专属Shell 如何通过Msfvenom来生成用户自己的专属Shell?有时候我们上传Shell到目标主机后&#xff0c;不仅我们自己可以连接&#xff0c;其他用户也可以连接&#xff0c;有时候会导致我们丢失该Shell&#xff0c;甚至该shell被用户发现并查杀。 实验环境 …

HTTP 概况

Web的应用层协议是超文本传输协议(HyperTextTransferProtocol&#xff0c;HTTP)&#xff0c;它是 Web的核心。HTTP由两个程序实现:一个客户程序和一个服务器程序。客户程序和服务器程序运行在不同的端系统中&#xff0c;通过交换HTTP报文进行会话。HTTP定义了这些报文的结构以及…