当前位置: 首页 > news >正文

一文读懂循环神经网络(RNN)—语言模型+n元语法(1)

目录

什么是语言模型?

语言模型的核心目的

        一.量化文本的合理性

        二.支持下游 NLP 任务

   三. 语义和上下文依赖

一元语法、二元语法和三元语法详解

核心概念:n-gram 模型

1. 一元语法(Unigram)

2. 二元语法(Bigram)

3. 三元语法(Trigram)

n-gram 模型的共性问题与扩展

总结

停用词

1.停用词的特点

2.常见的停用词类型(以中英文为例)

3.为什么要去除停用词?

马尔可夫模型与n元语法

完整代码

实验结果

词频图

一元/二元/三元语法的词频分布对比


什么是语言模型?

语言模型(Language Model, LM)是自然语言处理(NLP)中的核心技术,它的本质是对语言规律的数学建模—— 通过学习文本数据中的模式,预测 “一段文本序列出现的概率”,或在给定前文的情况下预测 “下一个词 / 字符出现的概率”。

简单来说,语言模型的核心能力是判断 “一句话是否通顺”,以及预测 “接下来会说什么”。例如:

  • 对于句子 “我想喝____”,语言模型能预测 “水”“咖啡”“茶” 等词的概率(其中 “水” 的概率通常最高);
  • 对于句子 “天空是____色的”,模型会给 “蓝” 赋予远高于 “绿”“紫” 的概率。

语言模型的核心目的

语言模型的核心目的是捕捉语言的统计规律和语义逻辑,从而实现对自然语言的理解与生成。具体可拆解为以下几个目标:

        一.量化文本的合理性

   通过计算文本序列的概率,判断其是否符合人类语言习惯。例如,“猫在追老鼠” 的概率远高于 “老鼠在追猫”(在无特殊语境下),语言模型能通过概率差异体现这种合理性。

        二.支持下游 NLP 任务

作为基础组件,语言模型为其他任务提供 “语言知识”:

  • 机器翻译:预测 “目标语言句子” 与 “源语言句子” 的匹配概率;
  • 文本生成:按概率生成通顺的句子(如写诗、写代码、聊天机器人回复);
  • 语音识别:从语音转写的多个候选文本中,选择概率最高的合理结果;

拼写纠错:对输入的错误文本,预测最可能的正确形式(如 “我去公圆”→“我去公园”)。

   三. 语义和上下文依赖
  • “苹果很好吃” 中的 “苹果” 指水果;
  • “苹果发布了新手机” 中的 “苹果” 指公司。
    语言模型通过上下文建模,能区分这两种含义。
  1. 高级语言模型(如 Transformer、BERT、GPT 系列)能捕捉词与词之间的上下文关系,理解歧义、多义词在不同语境下的含义。例如:

  2. 实现无监督 / 半监督学习
    语言模型可以仅通过海量文本(无需人工标注)学习语言规律,降低对标注数据的依赖。例如,GPT 系列通过 “预测下一个词” 的无监督任务,就能在对话、写作等任务中表现出强大能力。

一元语法、二元语法和三元语法详解

一元语法(Unigram)、二元语法(Bigram)和三元语法(Trigram)是基于n-gram 模型的基础概念,用于描述文本中词元(token)之间的序列关系。它们通过假设 “一个词的出现仅与前 n-1 个词相关”,简化了语言的概率建模过程,是早期语言模型的核心技术。

核心概念:n-gram 模型

n-gram 模型的核心思想是:将文本序列拆分为连续的 n 个词元组成的片段(n-gram),并通过统计这些片段的出现频率来计算句子的概率。 例如,对于句子 “我喜欢自然语言处理”,其 n-gram 片段为:

  • 一元语法(1-gram):["我", "喜欢", "自然", "语言", "处理"]
  • 二元语法(2-gram):["我 喜欢", "喜欢 自然", "自然 语言", "语言 处理"]
  • 三元语法(3-gram):["我 喜欢 自然", "喜欢 自然 语言", "自然 语言 处理"]

1. 一元语法(Unigram)

  • 定义:仅考虑单个词元的概率,忽略词与词之间的依赖关系,假设每个词的出现是独立的。

  • 概率计算: 对于句子S = [w_1, w_2, ..., w_n],其概率为所有词元概率的乘积:

                P(S) = P(w_1) \times P(w_2) \times ... \times P(w_n)

        其中,P(w_i) 是词w_i在语料库中出现的频率(即\text{count}(w_i) / 总词数)。

  • 示例: 句子 “猫吃鱼” 的概率 = P(猫) \times P(吃) \timesP(鱼)。

  • 优缺点

    • 优点:计算简单,数据需求量小,泛化能力强(很少出现未见过的词)。
    • 缺点:完全忽略上下文关系,合理性差(例如 “猫吃鱼” 和 “鱼吃猫” 的概率相同)。

2. 二元语法(Bigram)

  • 定义:假设一个词的出现仅依赖于前一个词,即考虑两个连续词元的概率。

  • 概率计算: 句子 S 的概率通过条件概率链表示: 

        P(S) = P(w_1) \times P(w_2|w_1) \times P(w_3|w_2) \times ... \times P(w_n|w_{n-1})

        其中,条件概率P(w_i|w_{i-1}) 近似为两个词同时出现的频率(即\text{count}(w_{i-1}, w_i) / \text{count}(w_{i-1}))。

  • 示例: 句子 “猫吃鱼” 的概率 = P(猫) \times P(吃|猫) \times P(鱼|吃)。

  • 优缺点

    • 优点:考虑了相邻词的依赖关系,比一元语法更合理(例如 “猫吃鱼” 的概率远高于 “鱼吃猫”)。
    • 缺点:仅依赖前一个词,长距离上下文(如 “猫喜欢吃鱼” 中 “喜欢” 对 “鱼” 的影响)被忽略;可能出现未见过的二元组合(如罕见短语)。

3. 三元语法(Trigram)

  • 定义:假设一个词的出现依赖于前两个词,即考虑三个连续词元的概率。

  • 概率计算: 句子 S 的概率为:

        P(S) = P(w_1) \times P(w_2|w_1) \times P(w_3|w_1, w_2) \times ... \times P(w_n|w_{n-2}, w_{n-1})

        其中,P(w_i|w_{i-2}, w_{i-1}) \approx \text{count}(w_{i-2}, w_{i-1}, w_i) / \text{count}(w_{i-2}, w_{i-1})

  • 示例: 句子 “猫喜欢吃鱼” 的概率 = P(猫) \times P(喜欢|猫) \times P(吃|猫, 喜欢) \timesP(鱼|喜欢, 吃)。

  • 优缺点

    • 优点:比二元语法更贴近实际语言规律,能捕捉更丰富的局部上下文(例如 “喜欢吃” 后面更可能接 “鱼” 而非 “石头”)。
    • 缺点:
      • 对数据量需求大,容易出现 “数据稀疏” 问题(很多三元组合在语料库中从未出现,导致概率为 0)。
      • 计算复杂度高于一元 / 二元语法,存储成本更高(需要记录大量三元组合)。

n-gram 模型的共性问题与扩展

  1. 数据稀疏性: n 越大,需要的训练数据越多,否则会出现大量未见过的 n-gram(称为 “未登录词问题”)。例如,三元语法比二元语法更容易遇到 “count=0” 的情况。

    • 解决方法:通过 “平滑技术”(如拉普拉斯平滑)给未见过的 n-gram 赋予一个极小的概率。
  2. n 的选择

    • n 越小:计算越高效,泛化能力越强,但忽略的上下文越多。
    • n 越大:捕捉的上下文越丰富,但数据需求和计算成本越高,且容易过拟合(依赖罕见组合)。 实际应用中,n 通常取 2(Bigram)或 3(Trigram),极少超过 5。

总结

模型核心假设优点缺点
一元语法词独立出现简单、泛化强忽略上下文,合理性差
二元语法依赖前一个词捕捉相邻依赖,较合理忽略长距离上下文
三元语法依赖前两个词捕捉局部上下文,更合理数据稀疏,计算成本高

停用词

停用词(Stop Words) 指的是在文本中频繁出现,但通常对文本的核心语义贡献较小的词语。这些词语由于使用过于普遍,往往被认为在文本分析、情感识别、主题提取等任务中 “信息量较低”,因此会被提前过滤掉,以简化处理流程并提升模型效率。

1.停用词的特点

  1. 高频性:在语言中出现频率极高,比如英语中的 “the”“and”“is”,中文中的 “的”“是”“在” 等。
  2. 语义弱化:本身没有明确的实义,多为辅助性词汇(如介词、连词、助词、代词等),单独出现时难以表达具体含义。
  3. 通用性:在不同主题、不同领域的文本中均大量存在,不具备区分文本特征的能力。

2.常见的停用词类型(以中英文为例)

语言停用词类型示例
英语冠词、介词、连词、代词等the, a, an, in, on, and, or, he, she
中文助词、连词、介词、代词等的、地、得、在、和、与、他、她、它

3.为什么要去除停用词?

  1. 减少数据量:停用词通常占文本总词数的 30%-50%,过滤后可大幅降低数据规模,提升模型训练和推理速度。
  2. 聚焦核心信息:过滤掉冗余词汇后,剩余词语更能反映文本的核心主题(如 “机器学习”“自然语言处理” 等实义词),帮助模型更精准地捕捉语义。
  3. 降低噪声干扰:高频且无实义的停用词可能会干扰模型对关键特征的学习(例如,在文本分类任务中,“的” 出现次数再多也无法区分 “科技” 和 “体育” 主题)。

马尔可夫模型与n元语法

完整代码

"""
文件名: 8.3 语言模型和数据集
作者: 墨尘
日期: 2025/7/14
项目名: dl_env
备注: 实现语言模型的基础数据处理流程,包括文本读取、词元化、词表构建,并分析一元/二元/三元语法的频率分布
"""
import random
import torch
import collections  # 用于统计词频
import re  # 用于文本清洗
from d2l import torch as d2l  # 提供数据下载、绘图等工具
# 手动显示图像相关库
import matplotlib.pyplot as plt  # 绘图库
import matplotlib.text as text  # 用于修改文本绘制(解决符号显示问题)# -------------------------- 核心解决方案:解决文本显示问题 --------------------------
def replace_minus(s):"""解决Matplotlib中Unicode减号(U+2212)显示异常的问题参数:s: 待处理的字符串或其他类型对象返回:处理后的字符串(替换减号)或原始对象(非字符串类型)"""if isinstance(s, str):  # 仅处理字符串return s.replace('\u2212', '-')  # 替换特殊减号为普通减号return s  # 非字符串直接返回# 重写matplotlib的Text类的set_text方法,全局修复减号显示
original_set_text = text.Text.set_text  # 保存原始方法
def new_set_text(self, s):s = replace_minus(s)  # 处理减号return original_set_text(self, s)  # 调用原始方法设置文本
text.Text.set_text = new_set_text  # 应用重写后的方法# -------------------------- 字体配置(确保中文和数学符号正常显示)--------------------------
plt.rcParams["font.family"] = ["SimHei"]  # 设置中文字体(支持中文显示)
plt.rcParams["text.usetex"] = True  # 使用LaTeX渲染文本(提升数学符号美观度)
plt.rcParams["axes.unicode_minus"] = True  # 确保负号正确显示(避免方块)
plt.rcParams["mathtext.fontset"] = "cm"  # 数学符号使用Computer Modern字体
d2l.plt.rcParams.update(plt.rcParams)  # 让d2l库的绘图工具继承配置# -------------------------- 关键修复:提前注册数据集信息 --------------------------
# 注册《时间机器》数据集到d2l的DATA_HUB(必须在read_time_machine函数前)
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',  # 数据集下载地址'090b5e7e70c295757f55df93cb0a180b9691891a'  # 哈希校验值(确保文件完整)
)# -------------------------- 1. 读取数据集 --------------------------
def read_time_machine():  # @save"""读取《时间机器》文本数据集并清洗步骤:1. 下载并打开文本文件2. 清洗文本:保留字母,其他字符替换为空格,转小写,去首尾空格返回:清洗后的文本行列表(非空行)"""with open(d2l.download('time_machine'), 'r') as f:  # 下载并读取文件lines = f.readlines()  # 按行读取# 正则清洗:只保留A-Za-z,其他替换为空格,再转小写并去首尾空格return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]# -------------------------- 2. 词元化(Tokenization) --------------------------
def tokenize(lines, token='word'):  # @save"""将文本行分割为词元(单词或字符)参数:lines: 清洗后的文本行列表(如["the time machine", ...])token: 词元类型,'word'按单词分割,'char'按字符分割返回:词元列表的列表(每行对应一个词元列表)"""if token == 'word':return [line.split() for line in lines]  # 按空格分割为单词elif token == 'char':return [list(line) for line in lines]  # 按字符分割else:print('错误:未知词元类型:' + token)# -------------------------- 3. 词表(Vocabulary) --------------------------
class Vocab:  #@save"""文本词表:映射词元到整数索引,支持词元与索引的双向转换属性:idx_to_token: 索引→词元的列表(如[<unk>, 'a', 'b', ...])token_to_idx: 词元→索引的字典(如{'<unk>':0, 'a':1, ...})_token_freqs: 词元频率列表(按频率降序)"""def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):"""初始化词表参数:tokens: 词元列表(可嵌套,如[["a","b"], ["c"]])min_freq: 最小词频阈值,低于此值的词元不加入词表reserved_tokens: 预留特殊词元(如['<pad>', '<bos>'])"""if tokens is None:tokens = []if reserved_tokens is None:reserved_tokens = []# 统计词频并按频率降序排序counter = count_corpus(tokens)  # 展平词元列表并计数self._token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True)# 初始化词表:未知词元<unk>固定在索引0self.idx_to_token = ['<unk>'] + reserved_tokensself.token_to_idx = {token: idx for idx, token in enumerate(self.idx_to_token)}# 加入高频词元(过滤低频词)for token, freq in self._token_freqs:if freq < min_freq:break  # 因已排序,后续词元频率更低,直接停止if token not in self.token_to_idx:  # 避免重复加入预留词元self.idx_to_token.append(token)self.token_to_idx[token] = len(self.idx_to_token) - 1  # 新索引为当前长度-1def __len__(self):"""返回词表大小(词元总数)"""return len(self.idx_to_token)def __getitem__(self, tokens):"""词元→索引转换(支持单个词元或列表)参数:tokens: 单个词元(如"a")或词元列表(如["a","b"])返回:对应的索引(或列表),未知词元返回<unk>的索引(0)"""if not isinstance(tokens, (list, tuple)):return self.token_to_idx.get(tokens, self.unk)  # 单个词元return [self.__getitem__(token) for token in tokens]  # 词元列表def to_tokens(self, indices):"""索引→词元转换(支持单个索引或列表)参数:indices: 单个索引(如1)或索引列表(如[1,2])返回:对应的词元(或列表)"""if not isinstance(indices, (list, tuple)):return self.idx_to_token[indices]  # 单个索引return [self.idx_to_token[index] for index in indices]  # 索引列表@propertydef unk(self):"""未知词元的索引(固定为0)"""return 0@propertydef token_freqs(self):"""返回词元频率列表"""return self._token_freqsdef count_corpus(tokens):  #@save"""统计词元频率(展平嵌套列表)参数:tokens: 1D或2D词元列表(如["a","b"]或[["a","b"], ["c"]])返回:collections.Counter: 词元频率计数器"""if len(tokens) == 0 or isinstance(tokens[0], list):tokens = [token for line in tokens for token in line]  # 展平2D列表为1Dreturn collections.Counter(tokens)  # 计数每个词元的出现次数# -------------------------- 4. 整合预处理流程 --------------------------
def load_corpus_time_machine(max_tokens=-1):  #@save"""加载《时间机器》数据集,返回字符级语料库和词表参数:max_tokens: 最大词元数,-1表示使用全部返回:corpus: 词元索引序列(1D列表)vocab: 字符级词表"""lines = read_time_machine()  # 读取清洗后的文本tokens = tokenize(lines, 'char')  # 按字符分割词元vocab = Vocab(tokens)  # 构建字符级词表# 展平所有词元为索引序列corpus = [vocab[token] for line in tokens for token in line]if max_tokens > 0:corpus = corpus[:max_tokens]  # 截断到最大长度return corpus, vocab# -------------------------- 5. 测试代码:分析n-gram频率 --------------------------
if __name__ == '__main__':# 步骤1:读取并查看原始文本lines = read_time_machine()print(f'# 文本总行数: {len(lines)}')  # 输出清洗后的总行数(如3221)print("第0行文本:", lines[0])  # 输出:'the time machine by h g wells'print("第10行文本:", lines[10])  # 输出:'twinkled and his usually pale face was flushed and animated'# 步骤2:分析一元语法(unigram)的高频词tokens = tokenize(read_time_machine())  # 单词级词元化corpus = [token for line in tokens for token in line]  # 展平为1D词元列表vocab = Vocab(corpus)  # 基于单词构建词表print("\n前10个高频单词(一元语法):", vocab.token_freqs[:10])  # 如[('the', 2261), ('of', 1267), ...]# 步骤3:绘制一元语法的词频分布(对数坐标)freqs = [freq for token, freq in vocab.token_freqs]  # 提取所有词元的频率d2l.plot(freqs, xlabel='token: x',  # x轴:词元(按频率排序)ylabel='frequency: n(x)',  # y轴:频率xscale='log', yscale='log'  # 双对数坐标(符合齐夫定律))plt.show(block=True)  # 显示图像(词频随排名下降,符合幂律分布)# 步骤4:分析二元语法(bigram)的高频词对# 生成连续词对(如"the time"→("the", "time"))bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]bigram_vocab = Vocab(bigram_tokens)  # 基于词对构建词表print("\n前10个高频词对(二元语法):", bigram_vocab.token_freqs[:10])  # 如[('of', 'the'), 130), ...]# 步骤5:分析三元语法(trigram)的高频词 triples# 生成连续三个词(如"the time machine"→("the", "time", "machine"))trigram_tokens = [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:])]trigram_vocab = Vocab(trigram_tokens)  # 基于三词组构建词表print("\n前10个高频三词组(三元语法):", trigram_vocab.token_freqs[:10])  # 如[('in', 'the', 'year'), 20), ...]# 步骤6:对比一元/二元/三元语法的词频分布bigram_freqs = [freq for token, freq in bigram_vocab.token_freqs]  # 二元频率trigram_freqs = [freq for token, freq in trigram_vocab.token_freqs]  # 三元频率d2l.plot([freqs, bigram_freqs, trigram_freqs],  # 三条频率曲线xlabel='token: x', ylabel='frequency: n(x)', xscale='log', yscale='log',legend=['unigram', 'bigram', 'trigram']  # 图例)plt.show(block=True)  # 显示图像(n越大,频率下降越快,符合短距离依赖)

实验结果

词频图

一元/二元/三元语法的词频分布对比

http://www.lryc.cn/news/587538.html

相关文章:

  • Knife4j快速入门
  • 基于微信小程序的财务管理系统的设计与实现;账本管理系统的设计与实现
  • 云手机常见问题解析:解决延迟、掉线等困扰
  • Lovable - AI 驱动的全栈应用开发平台
  • 4G模块 A7670发送英文短信到手机
  • django parler 使用教程
  • Foundry 私钥管理指南:方法与安全最佳实践
  • es的自定义词典和停用词
  • aspnetcore Mvc配置选项中的ModelMetadataDetailsProviders
  • 幻想读 通过多版本并发控制(MVCC)和间隙锁(Gap Lock)的组合也能防止幻读具体说下
  • 基于R语言的极值统计学及其在相关领域中的实践技术应用
  • Linux RDMA Maillist patchsets (Jul. 7 - Jul. 13, 2025)
  • 【LeetCode240.搜索二维矩阵Ⅱ】以及变式
  • 传统机器学习在信用卡交易预测中的卓越表现:从R²=-0.0075到1.0000的华丽转身
  • 【Hadoop科普篇】大数据怎么处理?Hadoop是什么?跟HDFS, Spark, Flink, Hive, Hbase是什么关系?
  • React Three Fiber 实现 3D 模型视图切换、显隐边框、显隐坐标轴
  • JavaScript 性能优化实战:深入性能瓶颈,精炼优化技巧与最佳实践
  • 如何彻底解决PLM/ERP/MES等系统访问速度慢问题?
  • ThinkPHP 8 在 Apache 下启用伪静态
  • .NET 9 GUID v7 vs v4:时间有序性如何颠覆数据库索引性能
  • 【python实用小脚本-139】Python 在线图片批量下载器:requests+PIL 一键保存网络图像
  • Docker 拉取镜像并离线迁移至云桌面指南(以Redis为例)
  • 2025 春秋杯夏季个人挑战赛 Web
  • Spark SQL 之 UT
  • 实战:如何创建 AWS RDS 数据库
  • Android 16k jni修改
  • 构建高效事件驱动架构:AWS S3与SQS集成实践指南
  • 如何连接 AWS RDS 数据库实例
  • AWS RDS PostgreSQL可观测性最佳实践
  • 2025最新android面试指南