1.9-改进的CBOW模型的实现

news2024/9/21 16:43:34

文章目录

  • 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 = 3
        window_size = 1
        CBOW_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.params
                self.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 = 0
        for 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_layers
        loss = 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.cache
          dout=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)时已经保存了使用的idx
          dh=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.coloradmin.cn/o/1916236.html

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

相关文章

最新的数据防泄密方案来袭!

沙箱技术作为一种先进的数据安全解决方案,在数据防泄密领域发挥着日益重要的作用。它通过构建一个隔离的虚拟环境,使得应用程序在该环境中运行,从而隔离了应用程序对系统资源的直接访问,有效防止了数据泄露的风险。 一、沙箱技术在…

[C++] 轻熟类和对象

类的定义 格式规范 class为定义类的关键字,后有类名,类的主体存于{}中;类定义结束时后面的分号不能省略;类体的内容成为类的成员,类中的变量成为成员变量,函数成为方法或成员函数;C兼容C语言的…

[spring] Spring MVC - security(上)

[spring] Spring MVC - security(上) 这部分的内容基本上和 [spring] rest api security 是重合的,主要就是添加 验证(authentication)和授权(authorization)这两个功能 即: 用户…

SpringBoot的老年慢性病药物管理系统-计算机毕业设计源码70568

目录 摘要 Abstract 第一章 绪论 1.1 选题背景及意义 1.2 国内外研究现状 1.3 研究方法 第二章 相关技术介绍 2.1 MySQL简介 2.2 Java编程语言 2.3 B/S模式 2.4 springboot框架 第三章 老年慢性病药物管理系统 系统分析 3.1 系统目标 3.2 系统可行性分析 3.2.1 技…

STM32中断学习记录

文章目录 NVICNVIC是什么NVIC寄存器NVIC 结构体NVIC 相关固件库函数 如何定义优先级中断编程外部中断 EXTIEXIT 外部中断/事件控制器EXIT的使用EXTI内部寄存器分析GPIO触发中断例程为什么中断后要清除中断标志位 SysTick的使用SysTick分析 NVIC NVIC是什么 待补充.........NVI…

ChatTTS的爆火是必然,它正在重新定义我们与机器对话的方式

当AI技术与语音合成相遇,开源技术众多,为什么 ChatTTS 能够一夜爆火?你有听说过能说情感真切文字的 AI 吗? 前言 想象一下,你只需输入一句话,AI就能念得声情并茂,不仅支持中英文混读&#xff0…

自注意力简介

在注意力机制中,每个查询都会关注所有的键值对并生成一个注意力输出。如果查询q,键k和值v都来自于同一组输入,那么这个注意力就被称为是自注意力(self-attention)。自注意力这部分理论,我觉得台大李宏毅老师…

一位互联网公司项目经理繁忙的一天

早晨:准备与计划 7:00 AM - 起床与准备 项目经理起床后,快速洗漱并享用早餐。之后花几分钟查看手机上的邮件和消息,确保没有紧急事务需要立即处理。 7:30 AM - 通勤时间 前往公司。在通勤途中,通过手机或平板电脑查看当天的会议…

硅谷甄选运营平台-vue3组件通信方式

vue3组件通信方式 vue2组件通信方式: props:可以实现父子组件、子父组件、甚至兄弟组件通信自定义事件:可以实现子父组件通信全局事件总线$bus:可以实现任意组件通信pubsub:发布订阅模式实现任意组件通信vuex:集中式状态管理容器,实现任意组件通信ref:父…

简单了解下安全测试!

一、基本概念 安全测试是在软件产品开发基本完成时,验证产品是否符合安全需求定义和产品质量标准的过程。它主要检查系统对非法侵入渗透的防范能力,旨在通过全面的脆弱性安全测试,发现系统未知的安全隐患并提出相关建议,确保系统…

星申刹车盘平衡机:精准与高效兼备

星申动双工位刹车盘动平衡机是一款具备测量和切削两个工位的高精度设备。该机器由平衡测量单元、内部搬运系统、切削校正模块及电气控制部分组成,专为满足自动化生产需求设计。其主要功能特点包括: 1. 实现全自动刹车盘平衡测试,精度高达30gm…

vue3 ts 报错:无法找到模块“../views/index/Home.vue”的声明文件

解决办法: env.d.ts 新增代码片段: declare module "*.vue" {import type { DefineComponent } from "vue";// eslint-disable-next-line typescript-eslint/no-explicit-any, typescript-eslint/ban-typesconst component: Define…

ExtruOnt——为工业 4.0 系统描述制造机械类型的本体

概述 论文地址 :https://arxiv.org/abs/2401.11848 原文地址:https://ai-scholar.tech/articles/ontology/ExtruOnt 在工业 4.0 应用场景中,以机器可解释代码提供的、语义丰富的制造机械描述可以得到有效利用。然而,目前显然还缺…

使用nvm安装node包后,安装vue提示“vue不是内部或外部命令,也不是可运行的程序或批处理命令”

前言 使用 npm 安装了 vue-cli 后,输入 "vue -V" 查询vue版本命令提示: “vue不是内部或外部命令,也不是可运行的程序或批处理命令”。 解决方法 第一步:首先,查看 C 盘下有没有 npm 文件夹。 目录类似于&#xff1…

六、数据可视化—Echars(爬虫及数据可视化)

六、数据可视化—Echars(爬虫及数据可视化) Echarts应用 Echarts Echarts官网,很多图表等都是我们可以 https://echarts.apache.org/zh/index.html 是百度自己做的图表,后来用的人越来越多,捐给了orange组织&#xf…

网络渗透CTF实践:获取靶机Web Developer 文件/root/flag.txt中flag

实验目的:通过对目标靶机的渗透过程,了解CTF竞赛模式,理解CTF涵盖的知识范围,如MISC、PPC、WEB等,通过实践,加强团队协作能力,掌握初步CTF实战能力及信息收集能力。熟悉网络扫描、探测HTTP web服…

交易员需要克服的十大心理问题

撰文:Koroush AK 编译:Chris,Techub News 本文来源香港Web3媒体:Techub News 一个交易者在交易上所犯下的最大的错误可能更多来自于心态的失衡而并非技术上的失误,类似的情况已经发生在了无数交易者身上。作为交易者…

如何压缩pdf文件大小,怎么压缩pdf文件大小

在数字化时代,pdf文件因其稳定的格式和跨平台兼容性,成为了工作与学习中不可或缺的一部分。然而,随着pdf文件内容的丰富,pdf文件的体积也随之增大,给传输和存储带来了不少挑战。本文将深入探讨如何高效压缩pdf文件大小…

C++入门 模仿mysql控制台输出表格

一、 说明 控制台输出表格&#xff0c;自适应宽度 二、 源码 #include <iostream> #include <map> #include <string> #include <vector>using namespace std;void printTable(vector<vector<string>> *pTableData) {int row pTableDa…

前端八股文 箭头函数和普通函数的区别

箭头函数是匿名函数&#xff0c;不能作为构造函数&#xff0c;不能使用new箭头函数不绑定 arguments &#xff0c;取而代之用 rest 参数...解决箭头函数不绑定 this &#xff0c;会捕获其所在的上下文的this值&#xff0c;作为自己的this值箭头函数通过 call() 或 apply() 方法…