颠覆语言认知的革命!神经概率语言模型如何突破人类思维边界?

server/2025/3/16 22:34:02/

颠覆语言认知的革命!神经概率语言模型如何突破人类思维边界?

一、传统模型的世纪困境:当n-gram遇上"月光族难题"

令人震惊的案例:2012年Google语音识别系统将

用户说:“我要还信用卡”
系统识别:“我要环信用开”

三大困境解析

  1. 维度灾难:当n=3时,词表大小10万 → 需要存储10^15个组合
  2. 数据稀疏:"量子计算融资"在10亿语料库中出现0次
  3. 长程依赖:无法捕捉"虽然…但是…"的超距离关联

二、神经概率语言模型:NPLM

NPLM的结构包括3个主要部分:输⼊层、隐藏层和输出层。

输⼊层将词汇映射到连续的词向量空间,隐藏层通过⾮线性激活函数学习词与词之间的复杂关系,输出层通过softmax函数产⽣下⼀个单词的概率分布。

image-20250309184522370

数学之美
P ( w t ∣ w t − 1 , . . . , w t − n + 1 ) = s o f t m a x ( C ⋅ t a n h ⁡ ( W x + b ) ) P ( w t ∣ w t − 1 , . . . , w t − n + 1 ) = softmax ( C ⋅ tanh ⁡ ( W x + b ) ) P(wt∣wt−1,...,wt−n+1)=softmax(C⋅tanh⁡(Wx+b))P(w_t|w_{t-1},...,w_{t-n+1}) = \text{softmax}(C \cdot \tanh(Wx + b)) P(wtwt1,...,wtn+1)=softmax(Ctanh(Wx+b))P(wtwt1,...,wtn+1)=softmax(Ctanh(Wx+b))
其中:

  • C C C:词向量到输出的连接矩阵
  • W W W:上下文词向量组合矩阵
  • x x x:上下文词向量拼接

2.1 NPLM的实现

image-20250309184711801

2.1.1 建立语料库
# 构建一个非常简单的数据集
sentences = ["我 喜欢 玩具", "我 爱 爸爸", "我 讨厌 挨打"] 
# 将所有句子连接在一起,用空格分隔成多个词,再将重复的词去除,构建词汇表
word_list = list(set(" ".join(sentences).split())) 
# 创建一个字典,将每个词映射到一个唯一的索引
word_to_idx = {word: idx for idx, word in enumerate(word_list)} 
# 创建一个字典,将每个索引映射到对应的词
idx_to_word = {idx: word for idx, word in enumerate(word_list)} 
voc_size = len(word_list) # 计算词汇表的大小
print(' 词汇表:', word_to_idx) # 打印词汇到索引的映射字典
print(' 词汇表大小:', voc_size) # 打印词汇表大小
 词汇表: {'爸爸': 0, '讨厌': 1, '我': 2, '玩具': 3, '爱': 4, '喜欢': 5, '挨打': 6}词汇表大小: 7
2.1.2 生成NPLM训练数据
# 构建批处理数据
import torch # 导入 PyTorch 库
import random # 导入 random 库
batch_size = 2 # 每批数据的大小
def make_batch():input_batch = []  # 定义输入批处理列表target_batch = []  # 定义目标批处理列表selected_sentences = random.sample(sentences, batch_size) # 随机选择句子for sen in selected_sentences:  # 遍历每个句子word = sen.split()  # 用空格将句子分隔成多个词# 将除最后一个词以外的所有词的索引作为输入input = [word_to_idx[n] for n in word[:-1]]  # 创建输入数据# 将最后一个词的索引作为目标target = word_to_idx[word[-1]]  # 创建目标数据input_batch.append(input)  # 将输入添加到输入批处理列表target_batch.append(target)  # 将目标添加到目标批处理列表input_batch = torch.LongTensor(input_batch) # 将输入数据转换为张量target_batch = torch.LongTensor(target_batch) # 将目标数据转换为张量return input_batch, target_batch  # 返回输入批处理和目标批处理数据
input_batch, target_batch = make_batch() # 生成批处理数据
print(" 输入批处理数据:",input_batch)  # 打印输入批处理数据
# 将输入批处理数据中的每个索引值转换为对应的原始词
input_words = []
for input_idx in input_batch:input_words.append([idx_to_word[idx.item()] for idx in input_idx])
print(" 输入批处理数据对应的原始词:",input_words)
print(" 目标批处理数据:",target_batch) # 打印目标批处理数据
# 将目标批处理数据中的每个索引值转换为对应的原始词
target_words = [idx_to_word[idx.item()] for idx in target_batch]
print(" 目标批处理数据对应的原始词:",target_words)
 输入批处理数据: tensor([[2, 1], [2, 4]])输入批处理数据对应的原始词: [['我', '讨厌'], ['我', '爱']]目标批处理数据: tensor([6, 0])目标批处理数据对应的原始词: ['挨打', '爸爸']

batch_size = 2 # 每批数据的大小

image-20250310194259986

即为整理好数据集,及其对应的结果,接下来只等构建网络和训练

2.1.3 定义NPLM
import torch.nn as nn # 导入神经网络模块
# 定义神经概率语言模型(NPLM)
class NPLM(nn.Module):def __init__(self):super(NPLM, self).__init__() self.C = nn.Embedding(voc_size, embedding_size) # 定义一个词嵌入层# 第一个线性层,其输入大小为 n_step * embedding_size,输出大小为 n_hiddenself.linear1 = nn.Linear(n_step * embedding_size, n_hidden) # 第二个线性层,其输入大小为 n_hidden,输出大小为 voc_size,即词汇表大小self.linear2 = nn.Linear(n_hidden, voc_size) def forward(self, X):  # 定义前向传播过程# 输入数据 X 张量的形状为 [batch_size, n_step]X = self.C(X)  # 将 X 通过词嵌入层,形状变为 [batch_size, n_step, embedding_size]        X = X.view(-1, n_step * embedding_size) # 形状变为 [batch_size, n_step * embedding_size]# 通过第一个线性层并应用 ReLU 激活函数hidden = torch.tanh(self.linear1(X)) # hidden 张量形状为 [batch_size, n_hidden]# 通过第二个线性层得到输出 output = self.linear2(hidden) # output 形状为 [batch_size, voc_size]return output # 返回输出结果

image-20250310194659088

进一部的说明我们结合以下代码

2.1.4 实例化NPLM
n_step = 2 # 时间步数,表示每个输入序列的长度,也就是上下文长度 
n_hidden = 2 # 隐藏层大小
embedding_size = 2 # 词嵌入大小
model = NPLM() # 创建神经概率语言模型实例
print(' NPLM 模型结构:', model) # 打印模型的结构

输入数据[[1,3], [1,6]](对应句子片段[“我 爱”, “我 讨厌”])

处理流程

  1. 嵌入层转为形状 [2,2,2] 的张量
  2. 展平为 [2,4]
  3. 第一线性层压缩到 [2,2]
  4. 第二线性层扩展到 [2,7](每个样本对应7个词的概率)

image-20250310195024069

2.1.5 训练NPLM
import torch.optim as optim # 导入优化器模块
criterion = nn.CrossEntropyLoss() # 定义损失函数为交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=0.1) # 定义优化器为 Adam,学习率为 0.1
# 训练模型
for epoch in range(5000): # 设置训练迭代次数optimizer.zero_grad() # 清除优化器的梯度input_batch, target_batch = make_batch() # 创建输入和目标批处理数据output = model(input_batch) # 将输入数据传入模型,得到输出结果loss = criterion(output, target_batch) # 计算损失值if (epoch + 1) % 1000 == 0: # 每 1000 次迭代,打印损失值print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))loss.backward() # 反向传播计算梯度optimizer.step() # 更新模型参数

是不是中一段训练的设定看着挺抽象,接下来举一个简单的栗子:

for epoch in range(5000):optimizer.zero_grad()          # 清空炒锅残留(重置梯度)input_batch, target = make_batch()  # 随机选菜谱(数据采样)output = model(input_batch)    # 学生试做菜品(前向传播)loss = criterion(output, target) # 老师试吃评分(计算损失)loss.backward()                # 分析哪里做错了(反向传播)optimizer.step()               # 根据建议改进(参数更新)
image-20250310195240344
2.1.6 用NPLM预测新词
# 进行预测
input_strs = [['我', '讨厌'], ['我', '喜欢']]  # 需要预测的输入序列
# 将输入序列转换为对应的索引
input_indices = [[word_to_idx[word] for word in seq] for seq in input_strs]
# 将输入序列的索引转换为张量
input_batch = torch.LongTensor(input_indices) 
# 对输入序列进行预测,取输出中概率最大的类别
predict = model(input_batch).data.max(1)[1]  
# 将预测结果的索引转换为对应的词
predict_strs = [idx_to_word[n.item()] for n in predict.squeeze()]  
for input_seq, pred in zip(input_strs, predict_strs):print(input_seq, '->', pred)  # 打印输入序列和预测结果
['我', '讨厌'] -> 挨打['我', '喜欢'] -> 玩具

image-20250310195543613

2.2 完整手撕

最开始的学习中容易对NPLM建立中纬度的变化中产生疑惑,接下来进行具体得分析。

image-20250310195740161

其结构可以拆分为一个三明治结构

输入词索引 → 嵌入层 → 全连接层1+激活 → 全连接层2 → 词汇概率分布

让我们用具体数值示例(假设参数如下)逐步解析:

voc_size = 7        # 词汇表大小(示例数据实际值)
n_step = 2          # 用前2个词预测第3个词
embedding_size = 2  # 词向量维度(示例设定)
n_hidden = 2        # 隐藏层维度(示例设定)

2.2.1 输入数据示例

假设输入批处理数据为:

input_batch = [[1,3], [1,6]]  # 对应句子片段:"我 爱"、"我 讨厌"
# 转换为张量后形状:[batch_size=2, n_step=2]

2.2.2 前向传播逐层分解

1. 嵌入层(Embedding Layer)
self.C = nn.Embedding(7, 2)  # 参数:7词→2维向量
X = self.C(input_batch)      # 输入:[2,2] → 输出:[2,2,2]

相当于把索引中的七个词转化为二维的向量,转化过程如下

计算过程

  • 每个词索引查表获取对应向量
# 假设嵌入矩阵(随机初始化示例):
[[0.1, 0.2],  # 索引0:'挨打'[0.3, 0.4],  # 索引1:'我'[0.5, 0.6],  # 索引2:'爸爸' [0.7, 0.8],  # 索引3:'爱'[0.9, 1.0],  # 索引4:'喜欢'[1.1, 1.2],  # 索引5:'玩具'[1.3, 1.4]   # 索引6:'讨厌'
]# 输入[[1,3], [1,6]]的嵌入结果:
[[[0.3,0.4], [0.7,0.8]],  # "我 爱"[[0.3,0.4], [1.3,1.4]]   # "我 讨厌"
]

数学本质
将离散的词索引映射为连续可训练的向量空间中的点,公式:
E = EmbeddingLookup ( X ) \mathbf{E} = \text{EmbeddingLookup}(\mathbf{X}) E=EmbeddingLookup(X)
其中 X ∈ Z b a t c h × n _ s t e p \mathbf{X} \in \mathbb{Z}^{batch×n\_step} XZbatch×n_step, E ∈ R b a t c h × n _ s t e p × e m b e d d i n g _ s i z e \mathbf{E} \in \mathbb{R}^{batch×n\_step×embedding\_size} ERbatch×n_step×embedding_size


2. 特征拼接(Flatten)
X = X.view(-1, n_step*embedding_size)  # [2,2,2] → [2,4]

具体变换

# 第一个样本:"我 爱"  
原始嵌入 → [0.3,0.4], [0.7,0.8]  
拼接后 → [0.3, 0.4, 0.7, 0.8]# 第二个样本:"我 讨厌"  
原始嵌入 → [0.3,0.4], [1.3,1.4]  
拼接后 → [0.3, 0.4, 1.3, 1.4]

技术意义
将序列的时序信息转换为空间特征,为全连接层提供固定长度的输入。


3. 第一全连接层(Feature Projection)
self.linear1 = nn.Linear(4, 2)  # 输入4维 → 输出2维
hidden = torch.tanh(self.linear1(X))  # [2,4] → [2,2]

参数示例
假设权重和偏置为(点乘):
W 1 = [ 0.1 − 0.2 0.3 0.4 − 0.5 0.6 0.7 − 0.8 ] , b 1 = [ 0.1 − 0.2 ] W_1 = \begin{bmatrix} 0.1 & -0.2 & 0.3 & 0.4 \\ -0.5 & 0.6 & 0.7 & -0.8 \end{bmatrix}, \quad b_1 = \begin{bmatrix} 0.1 \\ -0.2 \end{bmatrix} W1=[0.10.50.20.60.30.70.40.8],b1=[0.10.2]

计算演示

# 第一个样本计算:
[0.3, 0.4, 0.7, 0.8] × W1 + b1
= (0.3*0.1 + 0.4*(-0.2) + 0.7*0.3 + 0.8*0.4) + 0.1 = 0.54 → tanh(0.54)0.49  
= (0.3*(-0.5) + 0.4*0.6 + 0.7*0.7 + 0.8*(-0.8)) + (-0.2) = -0.56 → tanh(-0.56)-0.51
输出 → [0.49, -0.51]

数学表达式
h = tanh ⁡ ( X f l a t W 1 ⊤ + b 1 ) \mathbf{h} = \tanh(\mathbf{X}_{flat}W_1^\top + \mathbf{b}_1) h=tanh(XflatW1+b1)
其中 W 1 ∈ R n _ h i d d e n × ( n _ s t e p × e m b e d d i n g _ s i z e ) W_1 \in \mathbb{R}^{n\_hidden×(n\_step×embedding\_size)} W1Rn_hidden×(n_step×embedding_size)


4. 第二全连接层(Output Projection)
self.linear2 = nn.Linear(2,7)  # 2维→7分类
output = self.linear2(hidden)  # [2,2] → [2,7]

相当于将第一层的输入进行线性变换后(上下文特征的抽象提取),通过激活函数得出的结果分别再与第二层的参数进行线性变换(语义到词汇的概率映射),通过结果可以判断出哪一个词语为预测词的概率更大。

参数示例
假设:
W 2 = [ 0.2 − 0.3 0.4 0.1 − 0.5 0.6 0.7 − 0.8 − 0.9 0.2 1.0 0.3 0.1 − 0.4 ] , b 2 = [ 0.1 , 0.2 , − 0.3 , 0.4 , − 0.5 , 0.6 , − 0.7 ] W_2 = \begin{bmatrix} 0.2 & -0.3 \\ 0.4 & 0.1 \\ -0.5 & 0.6 \\ 0.7 & -0.8 \\ -0.9 & 0.2 \\ 1.0 & 0.3 \\ 0.1 & -0.4 \end{bmatrix}, \quad b_2 = [0.1, 0.2, -0.3, 0.4, -0.5, 0.6, -0.7] W2= 0.20.40.50.70.91.00.10.30.10.60.80.20.30.4 ,b2=[0.1,0.2,0.3,0.4,0.5,0.6,0.7]

计算演示

# 第一个样本隐藏值 [0.49, -0.51]
计算每个输出单元:
0.49*0.2 + (-0.51)*(-0.3) + 0.10.098 + 0.153 + 0.1 = 0.351 → 输出单元0  
0.49*0.4 + (-0.51)*0.1 + 0.20.196 - 0.051 + 0.2 = 0.345 → 输出单元1  
...以此类推所有7个单元

数学表达式
o = h W 2 ⊤ + b 2 \mathbf{o} = \mathbf{h}W_2^\top + \mathbf{b}_2 o=hW2+b2
其中 W 2 ∈ R v o c _ s i z e × n _ h i d d e n W_2 \in \mathbb{R}^{voc\_size×n\_hidden} W2Rvoc_size×n_hidden


2.2.3 输出结果解读

最终得到形状为 [batch_size, voc_size]未归一化概率(logits),例如:

output = [[0.35, 0.34, -1.2, 0.8, 2.1, -0.3, 0.5],  # 样本1:预测"喜欢"(索引4)概率最高[1.2, -0.5, 0.7, 0.3, -0.8, 0.9, 2.3]     # 样本2:预测"讨厌"(索引6)概率最高
]

要得到概率分布,需应用Softmax:

probs = torch.softmax(output, dim=1)
# probs ≈ [
#   [0.05, 0.05, 0.01, 0.10, 0.60, 0.09, 0.10], 
#   [0.15, 0.03, 0.08, 0.07, 0.02, 0.10, 0.55]
# ]

2.2.4 参数计算详解

1. 嵌入层参数

参数数 = v o c _ s i z e × e m b e d d i n g _ s i z e = 7 × 2 = 14 \text{参数数} = voc\_size × embedding\_size = 7×2 = 14 参数数=voc_size×embedding_size=7×2=14

2. 第一全连接层

参数数 = ( n _ s t e p × e m b e d d i n g _ s i z e ) × n _ h i d d e n + n _ h i d d e n = 4 × 2 + 2 = 10 \text{参数数} = (n\_step×embedding\_size) × n\_hidden + n\_hidden = 4×2 + 2 = 10 参数数=(n_step×embedding_size)×n_hidden+n_hidden=4×2+2=10

3. 第二全连接层

参数数 = n _ h i d d e n × v o c _ s i z e + v o c _ s i z e = 2 × 7 + 7 = 21 \text{参数数} = n\_hidden × voc\_size + voc\_size = 2×7 + 7 = 21 参数数=n_hidden×voc_size+voc_size=2×7+7=21

总参数:14 + 10 + 21 = 45个可训练参数


http://www.ppmy.cn/server/175542.html

相关文章

JavaScript相关面试题

以下是150道JavaScript相关面试题及详细答案: JavaScript基础 1.JavaScript是什么? JavaScript是一种直译式脚本语言,主要用于网页开发,也可用于服务器端开发(如Node.js)。它是一种动态类型、弱类型、基于原…

FPGA初级项目9——基于SPI的ADC芯片进行模数转换

FPGA初级项目9——基于SPI的ADC芯片进行模数转换 ADC芯片介绍 ADC(Analog-to-Digital Converter)芯片是一种将连续变化的模拟信号转换为离散数字信号的电子器件,广泛应用于电子系统中,是连接现实世界与数字世界的桥梁。可将电压、…

骑士74CMS_v3.34.0SE版uniapp全开源小程序怎么编译admin和member流程一篇文章说清楚

有粉丝一直问我骑士系统怎么编译后台和小程序目前骑士人才系统74CMS分标准版,创业板,专业版,其除功能不同外其配置方法完全一致有喜欢系统的也可以私信我或者找我获取 一.安装打包环境[Nodejs]这个就不用我说了吧,用不小于V20的版…

hadoop集群配置-scp的使用

1.推送。在hadoop100机器上,把文件推送到hadoop101机器上 在101和102中新建文件夹 在100中输入命令将jdk拷贝到101中 scp -r jdk1.8.0_212/ roothadoop101:/opt/module 用同样的方法拷贝hadoop scp -r hadoop-3.1.3/ roothadoop101:/opt/module 2.拉取。在hadoop1…

零信任架构实战手册-企业安全升级

🔐 开篇痛点暴击: “又被黑客钓鱼了?VPN漏洞补到心累?😫” 传统边界安全像纸糊的墙,内鬼渗透、APT攻击防不胜防! 别慌!零信任架构(Zero Trust)用「永不信任,持续验证」原则,让安全等级飙升10倍! 🚦 零信任3大核心武器(附实操步骤): 1. 🌟 身份即边界!抛…

玩转python:通俗易懂掌握高级数据结构-collections模块之ChainMap

引言 ChainMap是Python中collections模块提供的一个强大工具,用于将多个字典或映射合并为一个逻辑视图。它允许我们在不修改原始字典的情况下,对多个字典进行统一的查找和操作。ChainMap非常适合用于配置管理、多层作用域模拟等场景。本文将详细介绍Cha…

vue3实现跨页面缓存

避免频繁向后端发送请求,vue3中,可以用缓存机制,为了实现跨页面缓存,可以把缓存放到localsotrage里面 关键代码: const globalCache JSON.parse(localStorage.getItem(globalCache)) || {}; 然后加一个forceRefresh关键字, const fetchData async (forceRefresh false) …

Spring Boot集成HikariCP:原理剖析与实战指南

一、HikariCP连接池的底层实现剖析 1. 连接池核心数据结构 HikariCP的核心数据结构采用ConcurrentBag与FastList实现高性能并发管理: (1)ConcurrentBag 无锁设计:通过ThreadLocal缓存和CopyOnWriteArrayList实现高并发下的高效…