NNLM——预测下一个单词

一、原理篇 

      NNLM(Neural Network Language Model,神经网络语言模型)是一种通过神经网络进行语言建模的技术,通常用于预测序列中的下一个词。

        NNLM的核心思想是使用词嵌入(word embedding)将词转换为低维向量,并通过神经网络学习语言中的词序关系。

        NNLM的基本结构包括以下几个部分:

  • 输入层:输入一个固定长度的词窗口,例如 n 个词的上下文(前  n - 1 个词)作为输入。
  • 嵌入层:将每个输入词映射到一个低维空间,得到词向量。这一层的权重矩阵通常表示为词嵌入矩阵。
  • 隐藏层:一个或多个隐藏层可以捕获词之间的关系,一般是全连接层。
  • 输出层:用于预测下一个词的概率分布,通常使用softmax函数。

        假设一个句子为 w_1, w_2, \dots, w_T,我们希望通过上下文词 w_{t-n+1}, \dots, w_{t-1} 来预测下一个词 w_t。其目标是最大化预测正确的概率,即:

P(w_t | w_{t-1}, w_{t-2}, \dots, w_{t-n+1})

1. 词嵌入查找

        每个词 w都有一个唯一的索引。我们用嵌入矩阵 C 来将这些词映射到低维向量空间:

x_i = C(w_{t-i})

        其中x_i 表示第 i 个词的嵌入向量。对于所有 n−1 个词的上下文,我们得到词嵌入向量的集合:

\mathbf{x} = \left[ C(w_{t-n+1}), C(w_{t-n+2}), \dots, C(w_{t-1}) \right]

2. 嵌入向量拼接

        将所有词嵌入向量拼接成一个大的向量:

x = \left[ x_{t-n+1}, x_{t-n+2}, \dots, x_{t-1} \right]

        这个拼接向量 x 包含了上下文中的信息。

3. 隐藏层计算

        将拼接后的向量 x 输入到隐藏层,隐藏层的权重矩阵为 W,偏置向量为 b,激活函数为 f(例如tanh)。隐藏层的输出表示为:

h = f(W \cdot \mathbf{x} + b)

        在图中,“most computation here”指的就是这个计算过程。

4. 输出层与Softmax

        隐藏层的输出 h 通过输出层进行预测。输出层使用softmax函数来计算词汇表中每个词的概率分布:

P(w_t = i | w_{t-1}, w_{t-2}, \dots, w_{t-n+1}) = \frac{\exp(U_i \cdot h + c_i)}{\sum_{j=1}^{V} \exp(U_j \cdot h + c_j)}

其中:

  • U_i 是输出层对应词 i 的权重向量,
  • c_i​ 是词 i 的偏置,
  • V 是词汇表的大小。

        最终输出层会给出一个词汇表大小的概率分布,用于预测下一个词的概率。

5. 损失函数

        模型通过最大化训练数据的似然来进行优化。通常使用交叉熵损失来最小化预测的词分布和真实标签之间的差距:

L = - \sum_{t=1}^{T} \log P(w_t | w_{t-1}, w_{t-2}, \dots, w_{t-n+1})

        通过最小化该损失函数,可以优化模型参数,使得模型能够更好地预测下一个词。

二、代码篇

# 1.导入必要的库
import torch
import torch.nn as nn
import torch.optim as optimizer
import torch.utils.data as Data

# 2.数据准备
sentences = ["I like milk",
             "I love hot-pot",
             "I hate coffee",
             "I want sing",
             "I am sleep",
             "I go home",
             "Love you forever"]

word_list = " ".join(sentences).split()     # 获取个句子单词
word_list = list(set(word_list))            # 获取单词列表

word_dict = {w: i for i, w in enumerate(word_list)}     # 单词-位置索引字典
number_dict = {i: w for i, w in enumerate(word_list)}   # 位置-单词索引字典

vocab_size = len(word_list)         # 词汇表大小

# 3.X-生成输入和输出数据
def make_data():
    input_data = []
    output_data = []

    for sen in sentences:
        word = sen.split()
        input_temp = [word_dict[n] for n in word[:-1]]
        output_temp = word_dict[word[-1]]

        input_data.append(input_temp)
        output_data.append(output_temp)

    return input_data, output_data


input_data, output_data = make_data()
input_data, output_data = torch.LongTensor(input_data), torch.LongTensor(output_data)   # 数据转换:将 input_data 和 output_data 转换为 LongTensor,以便用于模型训练。
dataset = Data.TensorDataset(input_data, output_data)       # 建数据集:Data.TensorDataset 将输入和输出配对为数据集
loader = Data.DataLoader(dataset, 4, True)                  # 数据加载器:DataLoader,使用批量大小为 2,随机打乱样本


# 4.初始化参数
m = 2
n_step = 2
n_hidden = 10


# 5.模型定义
class NNLM(nn.Module):
    def __init__(self):
        super(NNLM, self).__init__()
        self.C = nn.Embedding(vocab_size, m)
        self.H = nn.Linear(n_step * m, n_hidden, bias=False)
        self.d = nn.Parameter(torch.ones(n_hidden))
        self.U = nn.Linear(n_hidden, vocab_size, bias=False)
        self.W = nn.Linear(n_step * m, vocab_size, bias=False)
        self.b = nn.Parameter(torch.ones(vocab_size))

    def forward(self, X):
        X = self.C(X)               # X = [batch_size, n_step, m]
        X = X.view(-1, n_step * m)  # 展平 X = [batch_size, n_step * m]
        hidden_output = torch.tanh(self.d + self.H(X))
        output = self.b + self.W(X) + self.U(hidden_output)
        return output

# 6.定义训练过程
model = NNLM()
optim = optimizer.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()

# 7.模型训练
for epoch in range(5000):
    for batch_x, batch_y in loader:
        pred = model(batch_x)
        loss = criterion(pred, batch_y)
        if (epoch + 1) % 1000 ==0:
            print(epoch+1, loss.item())
        optim.zero_grad()
        loss.backward()
        optim.step()

# 8.模型测试
pred = model(input_data).max(1, keepdim=True)[1]
print([number_dict[idx.item()] for idx in pred.squeeze()])

代码简单解释:

        1.最开始导入一些必要的库

       

        2.首先需要准备一些数据,用于模型训练并测试

word_list = " ".join(sentences).split()

        ①对于文本数据,肯定要进行一个分词操作,先使用" ".join(sentences).split()来切分每一个句子的每个单词,这时候获得的word_list列表就是:

['I', 'like', 'milk', 'I', 'love', 'hot-pot', 'I', 'hate', 'coffee', 'I', 'want', 'sing', 'I', 'am', 'sleep', 'I', 'go', 'home', 'Love', 'you', 'forever']

        ②但是这里得到的单词可能会有重复的情况,我们需要得到不重复的单词列表,为后面的创建词典提供方便。

word_list = list(set(word_list))  

  set (word_list) :将 word_list 转换为集合(set),自动去除列表中的重复元素。

  list(set (word_list) ):再将集合转换回列表,这样可以保持原数据类型一致(即 word_list 仍然是一个列表)。

['milk', 'coffee', 'sing', 'hot-pot', 'home', 'you', 'am', 'I', 'sleep', 'love', 'want', 'hate', 'go', 'forever', 'like', 'Love']

        ③然后就是构造词典:单词到位置的索引字典、位置到单词的索引字典。

word_dict = {w: i for i, w in enumerate(word_list)}     # 单词-位置索引字典
number_dict = {i: w for i, w in enumerate(word_list)}   # 位置-单词索引字典

word_dict: {'sleep': 0, 'go': 1, 'home': 2, 'milk': 3, 'hate': 4, 'Love': 5, 'love': 6, 'am': 7, 'want': 8, 'sing': 9, 'forever': 10, 'hot-pot': 11, 'I': 12, 'like': 13, 'you': 14, 'coffee': 15}

number_dict:{0: 'sleep', 1: 'go', 2: 'home', 3: 'milk', 4: 'hate', 5: 'Love', 6: 'love', 7: 'am', 8: 'want', 9: 'sing', 10: 'forever', 11: 'hot-pot', 12: 'I', 13: 'like', 14: 'you', 15: 'coffee'}

        这里使用enumerate是因为 enumerate 函数可以方便地同时获取列表元素的索引对应的值。也就是我们想要的字典。

        ④然后就是获得词汇表大小,在模型搭建中需要用到。

        3.有了初始数据,我们需要构建出数据X,也就是输入数据和目标输出数据。

def make_data():
    input_data = []
    output_data = []

    for sen in sentences:
        word = sen.split()
        input_temp = [word_dict[n] for n in word[:-1]]
        output_temp = word_dict[word[-1]]

        input_data.append(input_temp)
        output_data.append(output_temp)

    return input_data, output_data

        ①先构建空的输入数据input_data和输出数据output_data。

        ②将每个句子的前 n-1个单词的位置添加到输入数据input_data中,第 n 个单词的位置添加到输入数据output_data中,得到每个输入 x 和输出 y 在词汇表中的顺序:

input_data:[[12, 13], [12, 6], [12, 4], [12, 8], [12, 7], [12, 1], [5, 14]]

output_data:[3, 11, 15, 9, 0, 2, 10]

        这是个二维的矩阵,行元素代表一个句子中用于训练输入/测试输出的单词在词汇表中的位置索引;列元素是不同的句子

        每个句子都是3个单词,前2个作为前文信息作为输入,第3个作为预测输出,我们前面给的一共是7个句子。

        4.初始化参数

        这里的m指的是维度,也就是一个单词要嵌入到多少维度,由于这里的数据量比较小,每个句子也只有3个单词,所以这给出的维度选个很低的2。

        n_step=2,指的是用两个单词来预测下一个目标单词。

        n_hidden=10,指的是隐藏层的数量。

        5.前面的数据已经初步定义好了,这里就要搭建NNLM模型了。

class NNLM(nn.Module):
    def __init__(self):
        super(NNLM, self).__init__()
        self.C = nn.Embedding(vocab_size, m)
        self.H = nn.Linear(n_step * m, n_hidden, bias=False)
        self.d = nn.Parameter(torch.ones(n_hidden))
        self.U = nn.Linear(n_hidden, vocab_size, bias=False)
        self.W = nn.Linear(n_step * m, vocab_size, bias=False)
        self.b = nn.Parameter(torch.ones(vocab_size))

    def forward(self, X):
        X = self.C(X)               # X = [batch_size, n_step, m]
        X = X.view(-1, n_step * m)  # 展平 X = [batch_size, n_step * m]
        hidden_output = torch.tanh(self.d + self.H(X))
        output = self.b + self.W(X) + self.U(hidden_output)
        return output

         ①def _init_(self):定义各层和参数

  • self.C:词嵌入层,将输入词转换为词向量。

  vocab_size 定义词汇表的大小,m 是词嵌入的维度,表示每个词将被嵌入成 m 维的向量。

  • self.H:线性层,将展平后的输入映射到隐藏层。

       n_step * m 是展平后的输入大小,n_hidden 是隐藏层的维度,用来控制隐藏层输出的特征数量。

  • self.d:偏置向量,用于隐藏层的输出。

  n_hidden 是偏置项的维度,与隐藏层输出匹配,用于提升隐藏层的表达能力。

  • self.U:线性层,将隐藏层输出映射到词汇表空间。

  n_hidden 是隐藏层输出的大小,vocab_size 是词汇表大小,用于将隐藏层的特征映射到每个词的预测空间。

  • self.W:线性层,从输入直接映射到词汇表空间。

  n_step * m 是展平后的输入大小,vocab_size 是词汇表大小,用于将输入直接映射到词汇表的预测空间。

  • self.b:偏置向量,用于最终输出层的分数调整。

  vocab_size 是词汇表的大小,用作最终输出层的偏置。

        这里分别用了Embedding、Linear和Parameter:

  • Embedding:嵌入层,用于将离散的词汇索引(如单词的整数表示)映射到连续的稠密向量空间。
  • Linear:全连接层(线性层),用于将输入的特征通过线性变换映射到输出空间。
  • Parameter:可学习的参数。

      ②def forward(self,X):定义神经网络在前向传播过程中的计算步骤

  • X = self.C(X)
    首先通过嵌入层将输入的词索引(X)转换为词向量表示,这个时候得到是三维度的:[batch_size, n_step, m]
  • X = X.view(-1, n_step * m)
    然后将 X 从三维张量展平为二维张量 [batch_size, n_step * m],方便输入到全连接层 self.H。
  • hidden_output = torch.tanh(self.d + self.H(X))
    接着,利用公式h=tanh(W_hX+d)计算得到隐藏层输出。
  • output = self.b + self.W(X) + self.U(hidden_output)
    最后,利用公式output=b+WX+Uh得到最终的输出。

        6.定义训练过程

        这里初始化model,并且设置优化器为Adam,并且使用了交叉熵损失。

        7.模型训练

for epoch in range(5000):
    for batch_x, batch_y in loader:
        pred = model(batch_x)
        loss = criterion(pred, batch_y)
        if (epoch + 1) % 1000 ==0:
            print(epoch+1, loss.item())
        optim.zero_grad()
        loss.backward()
        optim.step()

        这里从数据加载器中加载数据,将当前批次的输入数据batch_x传入模型中得到预测结果,同时计算预测值与真实值的损失loss,每1000个epoch打印损失。然后梯度清零、反向传播计算梯度、更新模型参数。

        8.模型测试

pred = model(input_data).max(1, keepdim=True)[1]
  • model(input_data):将输入数据传递给模型,获取每个类别的得分(logits)。
  • max(1, keepdim=True)[1]:

          max(1):对每个样本找出最大得分的类别索引。
          keepdim=True:保持输出维度不变。
          [1]:提取每个样本的最大值索引(预测类别)。

print([number_dict[idx.item()] for idx in pred.squeeze()])
  • pred.squeeze():移除维度为 1 的维度,得到一维张量。
  • [idx.item() for idx in pred.squeeze()]:将每个索引转换为整数。
  • number_dict[idx.item()]:通过索引查找可读标签。
  • print([...]):打印出模型预测的类别标签。

输出:

1000 0.05966342240571976
1000 0.034198883920907974
2000 0.005526650696992874
2000 0.009151813574135303
3000 0.0021409429609775543
3000 0.0015856553800404072
4000 0.0006656644982285798
4000 0.0005017295479774475
5000 0.00018937562708742917
5000 0.00020660058362409472
['milk', 'hot-pot', 'coffee', 'sing', 'sleep', 'home', 'forever']

参考

Neural Network Language Model PyTorch实现_哔哩哔哩_bilibili

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

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

相关文章

移植 AWTK 到 纯血鸿蒙 (HarmonyOS NEXT) 系统 (4) - 平台适配

在移植 AWTK 到 HarmonyOS NEXT 系统之前,我们需要先完成平台适配,比如文件、多线程(线程和同步)、时间、动态库和资源管理。 1. 文件 HarmonyOS NEXT 支持标准的 POSIX 文件操作接口,我们可以直接使用下面的代码&am…

TON 区块链开发的深入概述#TON链开发#DAPP开发#交易平台#NFT#Gamefi链游

区块链开发领域发展迅速,各种平台为开发人员提供不同的生态系统。其中一个更有趣且越来越相关的区块链是TON(开放网络)区块链。TON 区块链最初由 Telegram 构思,旨在提供快速、安全且可扩展的去中心化应用程序 (dApp)。凭借其独特…

【机器学习】27. 马尔科夫链和隐马模型HMM

马尔科夫链和隐马模型HMM 1. Markov chain2. 计算3. Hidden Markov Model4. 两个假设5. 问题1:evaluation6. Forward 算法7. 问题2:Decoding8. Viterbi算法9. 问题3:Learning10. 期望最大化算法Expectation Maximization 1. Markov chain 马…

向量和矩阵的范数

一般,实数的绝对值来表示“实数”的大小;复数的模来表示复数的大小。这在实际应用中,带来了非常大的便利。 对于一个平面向量 a a a ,当其在直角坐标系中的分量分别为 x 0 x_0 x0​和 y 0 y_0 y0​时,我们常用 x 0 2 y 0 2 \sq…

树莓派开发相关知识七 -串口数码管

1、概述 一个普通的数码管实际上为71个LED灯。 上图可知,A-G加上DP点8个LED,通过不同的亮暗来显示出所需的数字。 如果同时要控制多个数码管,则需要的GPIO未免太多。 我们选择控制4个数码管,通过串行转并行的方式实现控制。 所…

基于IMX6ULL的电子产品量产工具

参考博客: https://blog.csdn.net/m0_63168877/article/details/138545059一、设计思路 软件框架及目录 二、显示系统 2.1显示管理器框架 2.2DispOpr 结构体 在disp_manager.h这一层抽象出显示结构体 在底层显示模块分配、设置这个结构体,并且向本层…

React 中使用 Redux Toolkit 状态管理

在现代 React 应用程序中,状态管理是一个至关重要的部分。使用 Redux Toolkit 可以简化 Redux 的配置和管理。本文将通过三个文件的示例,详细讲解如何使用 Redux Toolkit 创建和管理一个简单的计数器状态,并通过类比源 store 和根 store 的概…

3、liunx系统网络配置

一、liunx网络配置 Linux服务器网卡默认配置文件在/etc/sysconfig/network-scripts/下,命名的名称一般为:ifcfg-eth0 ifcfg-eth1 ,eth0表示第一块网卡,eth1表示第二块网卡,依次类推,例如DELL R720标配有4块千兆网卡&am…

【零售和消费品&存货】快递包裹条形码与二维码识别系统源码&数据集全套:改进yolo11-RFCBAMConv

改进yolo11-RVB等200全套创新点大全:快递包裹条形码与二维码识别系统源码&数据集全套 1.图片效果展示 项目来源 人工智能促进会 2024.11.01 注意:由于项目一直在更新迭代,上面“1.图片效果展示”和“2.视频效果展示”展示的系统…

牛客网最新Java高频面试题汇总(2024最新含答案)

作为一名优秀的程序员,技术面试都是不可避免的一个环节,一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识。 如果你参加过一些大厂面试,肯定会遇到一些这样的问题: 1、看你项目都用的框架,熟悉…

电科金仓(人大金仓)更新授权文件(致命错误: XX000: License file expired.)

问题:电科金仓(人大金仓)数据库链接异常,重启失败,查看日志如下: 致命错误: XX000: License file expired. 位置: PostmasterMain, postmaster.c:725 解决方法: 一、下载授权文件 根据安装版本在官网下载授权文件(电科金仓-成为世界卓越的数据库产品与服务提供商)…

Hadoop期末复习(完整版)

前言(全部为语雀导出,个人所写,仅用于学习!!!!) 复习之前我们要有目的性,明确考什么,不考什么。 对于hadoop来说,首先理论方面是跑不掉的&#x…

河北冠益荣信科技公司洞庭变电站工程低压备自投装置的应用

摘 要:随着电力需求增长,供电可靠性变得至关重要,许多系统已有多回路供电。备用电源自动投入装置能提升供电可靠性,它能在主电源故障时迅速切换到备用电源。本文介绍的AM5-DB低压备自投装置,为洞庭变电站提供多种供电方…

Spring Boot:打造动态定时任务,开启灵活调度之旅

一、描述 在 Spring Boot 中设置动态定时任务是一种非常实用的功能,可以根据实际需求在运行时动态地调整定时任务的执行时间、频率等参数。以下是对 Spring Boot 设置动态定时任务的简单介绍: 1、传统定时任务的局限性 在传统的 Spring Boot 定时任务…

Lua 从基础入门到精通(非常详细)

目录 什么是 Lua? Lua 环境安装 Lua基本语法 注释 数据类型 nil(空) Boolean number(数字) string(字符串) function(函数) userdata thread table&#xff…

PostgreSQL 到 PostgreSQL 数据迁移同步

简述 PostgreSQL 是一个历史悠久且广泛使用的数据库,不仅具备标准的关系型数据库能力,还具有相当不错的复杂 SQL 执行能力。用户常常会将 PostgreSQL 应用于在线事务型业务,以及部分数据分析工作,所以 PostgreSQL 到 PostgreSQL …

GESP4级考试语法知识(捕捉异常)

参考程序代码&#xff1a; #include <iostream> using namespace std;double divide(double a, double b) {if (b 0) {throw "Division by zero error"; // 抛出异常}return a / b; }int main() {double num1, num2;cout << "Enter two numbers:…

新老项目不同node版本,使用nvm控制node版本切换(mac、window)

window系统电脑的链接&#xff1a;https://blog.csdn.net/qq_40269801/article/details/136450961 以下是mac版本的操作方式&#xff1a; 1、打开终端 克隆 NVM 仓库&#xff1a; git clone https://github.com/nvm-sh/nvm.git ~/.nvm 2、运行安装脚本&#xff1a; cd ~/.n…

HTTP与HTTPS协议

HTTP协议&#xff0c;即超文本传输协议&#xff08;HyperText Transfer Protocol&#xff09;&#xff0c;是互联网中一种用于在Web浏览器与Web服务器之间传输数据的应用层协议。它的核心理念是提供一种简单、灵活的方式来请求和响应信息&#xff0c;是现代万维网的基础。 1. 什…

R语言机器学习与临床预测模型79--机器学习总览

R小盐准备介绍R语言机器学习与预测模型的学习笔记 你想要的R语言学习资料都在这里&#xff0c; 快来收藏关注【科研私家菜】 01 机器学习分类 机器学习模型主要分为有监督、无监督和强化学习方法。 监督学习 监督学习是教师向学生提供关于他们在考试中是否表现良好的反馈。其中…