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

2024-06-20 18:20

本文主要是介绍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:# 跳过单词本身continuecs.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] = 1elif 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] = 1return 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.paramsself.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.5self.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-2t = 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的和得到概率;

这篇关于1.4自然语言的分布式表示-word2vec实操的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1078901

相关文章

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

集中式版本控制与分布式版本控制——Git 学习笔记01

什么是版本控制 如果你用 Microsoft Word 写过东西,那你八成会有这样的经历: 想删除一段文字,又怕将来这段文字有用,怎么办呢?有一个办法,先把当前文件“另存为”一个文件,然后继续改,改到某个程度,再“另存为”一个文件。就这样改着、存着……最后你的 Word 文档变成了这样: 过了几天,你想找回被删除的文字,但是已经记不清保存在哪个文件了,只能挨个去找。真麻烦,眼睛都花了。看

开源分布式数据库中间件

转自:https://www.csdn.net/article/2015-07-16/2825228 MyCat:开源分布式数据库中间件 为什么需要MyCat? 虽然云计算时代,传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。 MyCat的目标就是:低成本地将现有的单机数据库和应用平滑迁移到“云”端

8. 自然语言处理中的深度学习:从词向量到BERT

引言 深度学习在自然语言处理(NLP)领域的应用极大地推动了语言理解和生成技术的发展。通过从词向量到预训练模型(如BERT)的演进,NLP技术在机器翻译、情感分析、问答系统等任务中取得了显著成果。本篇博文将探讨深度学习在NLP中的核心技术,包括词向量、序列模型(如RNN、LSTM),以及BERT等预训练模型的崛起及其实际应用。 1. 词向量的生成与应用 词向量(Word Embedding)

Level3 — PART 3 — 自然语言处理与文本分析

目录 自然语言处理概要 分词与词性标注 N-Gram 分词 分词及词性标注的难点 法则式分词法 全切分 FMM和BMM Bi-direction MM 优缺点 统计式分词法 N-Gram概率模型 HMM概率模型 词性标注(Part-of-Speech Tagging) HMM 文本挖掘概要 信息检索(Information Retrieval) 全文扫描 关键词

laravel框架实现redis分布式集群原理

在app/config/database.php中配置如下: 'redis' => array('cluster' => true,'default' => array('host' => '172.21.107.247','port' => 6379,),'redis1' => array('host' => '172.21.107.248','port' => 6379,),) 其中cl

基于MySQL实现的分布式锁

概述 在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等)。 但是到了分布式系统的时代,这种

基于Python的自然语言处理系列(1):Word2Vec

在自然语言处理(NLP)领域,Word2Vec是一种广泛使用的词向量表示方法。它通过将词汇映射到连续的向量空间中,使得计算机可以更好地理解和处理文本数据。本系列的第一篇文章将详细介绍Word2Vec模型的原理、实现方法及应用场景。 1. Word2Vec 原理         Word2Vec模型由Google的Tomas Mikolov等人在2013年提出,主要有两种训练方式

Kafka 分布式消息系统详细介绍

Kafka 分布式消息系统 一、Kafka 概述1.1 Kafka 定义1.2 Kafka 设计目标1.3 Kafka 特点 二、Kafka 架构设计2.1 基本架构2.2 Topic 和 Partition2.3 消费者和消费者组2.4 Replica 副本 三、Kafka 分布式集群搭建3.1 下载解压3.1.1 上传解压 3.2 修改 Kafka 配置文件3.2.1 修改zookeep

从计组中从重温C中浮点数表示及C程序翻译过程

目录 移码​编辑  传统浮点表示格式 浮点数的存储(ieee 754)->修炼内功 例子:   ​编辑 浮点数取的过程   C程序翻译过程 移码  传统浮点表示格式 浮点数的存储(ieee 754)->修炼内功 根据国际标准IEEE(电⽓和电⼦⼯程协会)  32位 例子:    64位    IEEE754对有效数字M和