1.4自然语言的分布式表示-word2vec实操

文章目录

  • 0写在前面
  • 1数据准备
  • 2CBOW模型结构的实现
  • 3交叉熵损失函数的前向计算
    • 3.1关于cross_entropy_error的计算
    • 3.2关于softmax

0写在前面

  1. 代码都位于:nlp;
  2. 其他相关内容详见专栏:深度学习自然语言处理基础_骑着蜗牛环游深度学习世界的博客-CSDN博客;

1数据准备

  1. 输入是上下文,目标是中间的单词

  2. 因此对于下图所示的小的语料库来说,可以构建上下文以及对应的目标词:

    1. 对语料库中的所有单词都执行该操作(两端的单词 除外)

    在这里插入图片描述

  3. 接下来需要将单词转换为神经网络能够处理的输入

    1. 需要根据前面所学习的内容,从语料库构建单词-ID之间的映射【使用nlp\2-基于计数的方法.py中写好的preprocess方法】

    2. 根据构建的映射,将输入和输出从单词转换为索引;由于这里不是一个单词了,而是所有的输入输出,因此需要写一个函数来统一处理;

      def create_contexts_targets(corpus, window_size=1):
          '''
      
          :param corpus: 序列化的语料库列表
          :param window_size: 上下文大小;主要用于去除掉不同时具备上下文的开头或者末尾的单词
          :return:
          '''
          target = corpus[window_size:-window_size]  # 获取需要预测的词列表;开头和末尾是不算的,因为他们不同时具有上下文
          contexts = []
          # 从第一个有上下文的单词开始,到最后一个有上下文的单词结束
          for i in range(window_size, len(corpus) - window_size):
              cs = []  # 当前单词的上下文列表
              for t in range(-window_size, window_size + 1):
                  if t == 0:
                      # 跳过单词本身
                      continue
                  cs.append(corpus[i + t])
              contexts.append(cs)
          return np.array(contexts), np.array(target)
      

      在这里插入图片描述

    3. 然后将单词索引转换为独热编码;注意维度的变化;

      在这里插入图片描述

      def convert_one_hot(corpus, vocab_size):
          '''转换为one-hot表示
      
          :param corpus: 单词ID列表(一维或二维的NumPy数组)
          :param vocab_size: 词汇个数
          :return: one-hot表示(二维或三维的NumPy数组)
          '''
          N = corpus.shape[0]  # 获取数据的数量;即输入的个数(输出的个数)
      
          if corpus.ndim == 1:
              # 维度数量为1,即是目标词数组
              one_hot = np.zeros((N, vocab_size), dtype=np.int32)
              for idx, word_id in enumerate(corpus):
                  one_hot[idx, word_id] = 1
      
          elif corpus.ndim == 2:
              # 维度数量为2,即上下文数组
              C = corpus.shape[1]  # 窗口的大小(上下文的大小)
              one_hot = np.zeros((N, C, vocab_size), dtype=np.int32)
              for idx_0, word_ids in enumerate(corpus):
                  # word_ids是某个目标词对应的上下文ID向量
                  for idx_1, word_id in enumerate(word_ids):
                      one_hot[idx_0, idx_1, word_id] = 1
      
          return one_hot
      

2CBOW模型结构的实现

  1. 构建SimpleCBOW

    1. 初始化:初始化权重、构建两个输入层,一个输出层,一个损失计算层;并将所有的权重和梯度整理到一起;

      class SimpleCBOW():
          def __init__(self, vocab_size, hidden_size):
              '''
              :param vocab_size: 输入侧和输出侧神经元的个数
              :param hidden_size: 中间层神经元个数
              '''
              V, H = vocab_size, hidden_size
              # 乘上0.01使得初始化的权重是一些比较小的浮点数
              W_in = 0.01 * np.random.randn(V, H).astype('f')  # 维度为[V,H]
              W_out = 0.01 * np.random.randn(H, V).astype('f')  # 维度为[H,V]
              # 构建层
              self.in_layer_0 = MatMul(W_in)  # 输入层
              self.in_layer_1 = MatMul(W_in)  # 输入层
              self.out_layer = MatMul(W_out)  # 输出层
              self.loss_layer = SoftmaxWithLoss()  # 损失计算层
      
              # 将所有的权重的参数和梯度数据存在一个变量中
              layers = [self.in_layer_0, self.in_layer_1, self.out_layer]
              self.params, self.grads = [], []
              for layer in layers:
                  self.params += layer.params
                  self.grads += layer.grads
      
              # 将单词的分布式表示记录为这个模型类的成员变量;这样模型训练好之后权重被更新
              # "在 Python 中,对象和变量实际上是对内存中的值的引用。当你创建一个变量并将其设置为某个对象时,你实际上是在创建一个指向该对象的引用"
              self.wordvec = W_in
      
    2. 实现CBOW模型的前向计算(关于这里的损失计算,后面再专门讲)

      1. 这里由于contexts的维度为[6,2,7],因此不再是一个单词的上下文向量;因此传入输入层进行计算时是一次性计算了6条数据的全连接结果;因而我们可以发现,矩阵天然可以支持批量数据的处理;
      2. 也就是说,输入是[6,7],输入层的权重维度为[7,3],得到中间层是[6,3],不再是原来的[1,3]
      def forward(self, contexts, target):
          '''
      
          :param contexts: 输入
          :param target: 真实标签;[6,7]
          :return:损失值
          '''
          # 输入层到中间层
          h0 = self.in_layer_0.forward(contexts[:, 0])  # 结果是[6,3]
          h1 = self.in_layer_1.forward(contexts[:, 1])
          h = 0.5 * (h0 + h1)
          # 中间层到输出层
          score = self.out_layer.forward(h)  # 输出的维度是[6,7]
          # 计算损失
          loss = self.loss_layer.forward(score, target)  # 将得分与真实标签传入损失计算函数;score在计算损失时会被施加softmax转换为概率的
          return loss
      
    3. 梯度反向传播;计算过程为【关于反向传播的细节,需要深入去了解】:

      1. 先计算损失函数的导数,从而得到损失函数输入的梯度
      2. 然后是输出层矩阵乘法的导数
      3. 然后0.5h的导数计算;这个直接对这个y=0.5h求导,对h求导;梯度是0.5;因此根据梯度的链式传递法则,在传过来的时候是梯度需要乘上0.5
      4. 然后是两个输入层结果相加的操作,这一步的梯度是分别原样传递到各个分支;简答理解:
        1. y=x1+x2;当对x1求导时,x2是当做常数的;x2同理;
        2. 因此相加操作处的梯度就直接分别传递给两个输入层
      5. 最后,两个输入层再传播梯度【这里传递梯度的计算公式在之前有分享一个博客:【深度学习】7-矩阵乘法运算的反向传播求梯度_矩阵梯度公式-CSDN博客】
      def backward(self, dout=1):
          ds = self.loss_layer.backward(dout)
          da = self.out_layer.backward(ds)
          da = da * 0.5
          self.in_layer_0.backward(da)  # 输入层计算完最终梯度之后会将梯度保存在梯度列表里面;因此这里就不需要返回值了
          self.in_layer_1.backward(da)
      

3交叉熵损失函数的前向计算

当调用SimpleCBOW类的forward方法之后,当执行到self.loss_layer.forward语句时,将进入到SoftmaxWithLoss类的forward函数,进行损失的计算;

  1. 首先,将模型输出的得分通过softmax函数转换为了概率分布;维度保持不变,依然是[6,7]

  2. 这里的真实标签是独热编码形式,因此根据独热编码提取正确的解标签(即,每条数据要预测单词的真实ID);此时标签变成一个维度:(6,)

  3. 然后进行交叉损失的计算,并返回损失值。

    def forward(self, x, t):
        self.t = t  # 维度为[6,7];是独热编码的形式
        self.y = softmax(x)  # 首先将得分转换为概率;维度为[6,7]
    
        # 在监督标签为one-hot向量的情况下,转换为正确解标签的索引
        # 在监督标签为one-hot向量的情况下,它与模型输出的得分维度相同
        if self.t.size == self.y.size:
            self.t = self.t.argmax(axis=1)  # 从独热编码转换为标签;变成一个维度:(6,)
    
        loss = cross_entropy_error(self.y, self.t)
        return loss
    

3.1关于cross_entropy_error的计算

  1. 代码如下;接下来对其进行解释;

    def cross_entropy_error(y, t):
        '''
        :param y: 模型输出;已经转换为概率了
        :param t: 真实标签;不再是独热编码
        :return:
        '''
        if y.ndim == 1: #1-1
            # 默认不执行;这种情况是当批处理大小为1时可能进行的
            t = t.reshape(1, t.size)
            y = y.reshape(1, y.size)
        # 在监督标签为one-hot-vector的情况下,转换为正确解标签的索引
        # 当前示例下,监督标签已经在上一步转换成了真实标签索引了
        if t.size == y.size: #1-2
            t = t.argmax(axis=1)
        batch_size = y.shape[0]
        return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
    
  2. 我们这个小例子输入的数据维度是[6,7],是包含多条数据的;如果只有一条数据,则可能存在维度是1的情况;当这种情况发生时,要先增加一个维度;对标签也是如此;因此需要执行reshape(1, t.size)reshape(1, y.size)

  3. 执行#1-2时存在以下情况:

    1. 真实标签和模型输出一般是[6,7],即两个维度;那么在SimpleCBOW类的forward方法中,真实标签就会从独热编码变成真实标签ID数组,真实标签的维度就已经是一维的了;此时#1-2就不会执行了;

    2. 如果是一条数据的情况,模型输出一般还是[1,7]两个维度;真实标签数组(独热编码形式)维度可能是[1,7],也可能是(7,);如果是[1,7],则和多条数据时的情况一样;如果是(7,),则在SimpleCBOW类的forward方法中不会将标签从独热编码转换为ID索引,那么在cross_entropy_error这里,最好将#1-1改成如下的样子;然后通过执行#1-2将真实标签从独热编码转换为ID索引;

      if t.ndim == 1: #1-1
          # 默认不执行;这种情况是当批处理大小为1时可能进行的
          t = t.reshape(1, t.size)
      
      # 以下是一个维度变换的小例子
      import numpy as np
      c = np.array([1, 0, 0, 0, 0, 0, 0]).astype('f')
      print(c.size) #7
      print(c.shape) # (7,)
      print(c.reshape(1,c.size)) # c:[[1. 0. 0. 0. 0. 0. 0.]]
      
  4. 然后计算交叉熵损失

    1. 本例子要预测的单词所属类别是7(因为对于每个待预测的单词,有7种可能),因此是一个多分类问题;对于多分类问题,交叉熵损失公式为: L = − ∑ [ y i ∗ l o g ( p i ) ] L=-\sum[y_i*log(p_i)] L=[yilog(pi)];对于一次处理多条数据的情况,一般累加每个样本的值,然后求和再平均;​

    2. 如何理解这个损失:假设这里的真实标签y是独热编码的形式,模型预测如果很准确,则独热编码中元素1就拥有更大的概率;概率越大,log函数值越接近0,取相反数之后,值越接近0就相当于是值越小;我们是期望这个值越小的,因为这意味着正确解标签具有更大的概率,模型就更准确;

    3. 实际代码计算时,我们不需要完全按照公式来一步步计算;而是直接取正确解标签在模型预测输出中对应位置的概率来计算就可以了;

      1. y是二维的,[6,7];通过y[np.arange(batch_size), t]y中选择对应的行和列,即选择了每条数据真实解标签对应的概率值;不能写成y[:, t]
      2. y[np.arange(batch_size), t]的结果为(6,),其中是每个样本的正确解标签的概率值;对每个值计算对数;
      3. +1e-7防止对数里的值为0
      4. 然后对每个样本的对数值求和,并求平均,作为损失值;这个损失值应该越小越好;
    4. 这样就能把损失值计算出来了:

      在这里插入图片描述

3.2关于softmax

  1. 代码如下:

    def softmax(x):
        if x.ndim == 2:
            x = x - x.max(axis=1, keepdims=True)
            x = np.exp(x)
            x /= x.sum(axis=1, keepdims=True)
        elif x.ndim == 1:
            x = x - np.max(x)
            x = np.exp(x) / np.sum(np.exp(x))
    
        return x
    
  2. 我们的输入一般是二维的;即[6,7]

  3. 执行x - x.max可以将数值约束到负数;这样指数的值就在0、1之间;可以有效防止数值过大溢出的问题;

  4. 然后使用np.expx的每个元素计算指数;得到新的x

  5. 然后用当前x的某个值除以x的和得到概率;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习模型训练中 学习率参数 设置大小问题及设置合适值

💪 专业从事且热爱图像处理,图像处理专栏更新如下👇: 📝《图像去噪》 📝《超分辨率重建》 📝《语义分割》 📝《风格迁移》 📝《目标检测》 📝《暗光增强》 &a…

Mybatis Plus 详解 IService、BaseMapper、自动填充、分页查询功能

结构直接看目录 前言 MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效…

Linux系统ubuntu20.04 无人机PX4 开发环境搭建(失败率很低)

Linux系统ubuntu20.04 无人机PX4 开发环境搭建 PX4固件下载开发环境搭建MAVROS安装安装地面站QGC PX4固件下载 PX4的源码处于GitHub,因为众所周知的原因git clone经常失败,此处从Gitee获取PX4源码和依赖模块。 git clone https://gitee.com/voima/PX4-…

使用 Python 中的美丽汤进行网络数据解析的完整指南

Beautiful Soup 是一个广泛使用的 Python 库,在数据提取方面发挥着重要作用。它为解析 HTML 和 XML 文档提供了强大的工具,使从网页中轻松提取有价值的数据成为可能。该库简化了处理互联网上非结构化内容的复杂过程,使您可以将原始网页数据转…

【nginx】 nginx核心功能

【nginx】 nginx核心功能 1.nginx核心功能 1. 反向代理 2. 负载均衡 3. 动静分离 4. nginx的高可用2. 反向代理 正向代理: 该服务器代理的是客户端,对于服务器来说,不知道真实客户端的ip。比如: 翻墙软件。 访问国外的服务器---使用了翻墙软件----对…

2024年【R1快开门式压力容器操作】考试及R1快开门式压力容器操作考试内容

题库来源:安全生产模拟考试一点通公众号小程序 2024年R1快开门式压力容器操作考试为正在备考R1快开门式压力容器操作操作证的学员准备的理论考试专题,每个月更新的R1快开门式压力容器操作考试内容祝您顺利通过R1快开门式压力容器操作考试。 1、【多选题…

开源技术:在线教育系统源码及教育培训APP开发指南

本篇文章,小编将探讨如何利用开源技术开发在线教育系统及教育培训APP,旨在为有志于此的开发者提供全面的指导和实践建议。 一、在线教育系统的基本构架 1.1架构设计 包括前端、后端和数据库三个主要部分。 1.2前端技术 在前端开发中,HTML…

【我是产品经理_注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞 …

论文学习 Learning Robust Representations via Multi-View Information Bottleneck

Code available at https://github.com/mfederici/Multi-View-Information-Bottleneck 摘要:信息瓶颈原理为表示学习提供了一种信息论方法,通过训练编码器保留与预测标签相关的所有信息,同时最小化表示中其他多余信息的数量。然而&#xff0…

HCIA-速查-ENSP模拟器2步清空配置

需求:清空模拟器配置 清空当前图中配置 步骤1:reset saved-configuration 后输入y确认 步骤2:reboot后输入n否认再输入y确认 验证已经清空配置

QT利用QGraphicsDropShadowEffect效果及自定义按钮来实现一个炫酷键盘

1、效果 2、核心代码 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent<

阿里云 邮件系统DNS域名解析 搭配 postfix+dovecot 邮件服务器

1 创建邮箱域名A记录(一般邮箱客户端&#xff0c;增加pop,imap,stmp 3条记录) 登录阿里云控制台--云解析DNS 2 MX记录 3 SPF记录

chatgpt: linux 下用纯c 编写ui

在Linux下用纯C语言编写用户界面&#xff08;UI&#xff09;&#xff0c;通常会使用GTK或Xlib。GTK是一个更高级的库&#xff0c;提供了丰富的控件和功能&#xff0c;而Xlib则是一个更底层的库&#xff0c;提供了直接操作X Window系统的功能。 下面是一个使用GTK在Linux上创建…

R语言dplyr统计指定列里面种类个数和比例

输入数据框&#xff1a;dfuorf&#xff0c;Type列有uORF和overlpaORF两种类型 dfuorf1 <- dfuorf %>%group_by(Type) %>% summarise(Countn()) %>% mutate(percentCount/sum(Count)) %>% mutate(percent1 (paste0(round((Count/sum(Count)), 2)*100,"%&…

【因果推断python】46_估计量2

目录 连续型干预变量案例 非线性处理效果 关键思想 连续型干预变量案例 目标转换方法的另一个明显缺点是它仅适用于离散或二元处理。这是你在因果推理文献中经常看到的东西。大多数研究都是针对二元干预案例进行的&#xff0c;但您找不到很多关于连续干预的研究。这让我很困…

【深度学习】GELU激活函数是什么?

torch.nn.GELU 模块在 PyTorch 中实现了高斯误差线性单元&#xff08;GELU&#xff09;激活函数。GELU 被用于许多深度学习模型中&#xff0c;包括Transformer&#xff0c;因为它相比传统的 ReLU&#xff08;整流线性单元&#xff09;函数能够更好地近似神经元的真实激活行为。…

ARM64汇编0B - 函数调用约定

建议先看《CSAPP》的3.7节&#xff0c;讲的很细。我们这里就直接看例子来分析了。 例子 static int func(int a, int b, int c, int d, int e, int f, int g, int h, int i) {printf("%s\n", "add all");int x a b;return a b c d e f g h i; …

Faiss:选择合适的索引Index

向量相似性搜索彻底改变了搜索领域。它允许我们高效地检索从GIF到文章等各种媒体&#xff0c;即使在处理十亿级别数据集时&#xff0c;也能在亚秒级时间内提供令人印象深刻的准确性。 然而&#xff0c;这种灵活性也带来了一个问题&#xff1a;如何知道哪种索引大小最适合我们的…

2-11 基于matlab的BP-Adaboost的强分类器分类预测

基于matlab的BP-Adaboost的强分类器分类预测&#xff0c;Adaboost是一种迭代分类算法&#xff0c;其在同一训练集采用不同方法训练不同分类器&#xff08;弱分类器&#xff09;&#xff0c;并根据弱分类器的误差分配不同权重&#xff0c;然后将这些弱分类器组合成一个更强的最终…

check python checking for Python executable “python2“ in the PATH

背景&#xff1a; mac电脑升级后重新拉取老项目后安装node_module 和启动项目报错 gyp info using node-gyp3.8.0 gyp info using node14.18.0 | darwin | x64 gyp verb command rebuild [] gyp verb command clean [] gyp verb clean removing "build" directory …