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

目录

什么是语言模型?

语言模型的核心目的

一.量化文本的合理性

[二.支持下游 NLP 任务](#二.支持下游 NLP 任务)

[三. 语义和上下文依赖](#三. 语义和上下文依赖)

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

[核心概念:n-gram 模型](#核心概念:n-gram 模型)

[1. 一元语法(Unigram)](#1. 一元语法(Unigram))

[2. 二元语法(Bigram)](#2. 二元语法(Bigram))

[3. 三元语法(Trigram)](#3. 三元语法(Trigram))

[n-gram 模型的共性问题与扩展](#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)

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

  • 概率计算 : 对于句子,其概率为所有词元概率的乘积:

其中, 是词在语料库中出现的频率(即 / 总词数)。

  • 示例 : 句子 "猫吃鱼" 的概率 = P(猫) P(吃) P(鱼)。

  • 优缺点

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

2. 二元语法(Bigram)

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

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

其中,条件概率 近似为两个词同时出现的频率(即)。

  • 示例 : 句子 "猫吃鱼" 的概率 = P(猫) P(吃|猫) P(鱼|吃)。

  • 优缺点

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

3. 三元语法(Trigram)

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

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

其中,

  • 示例 : 句子 "猫喜欢吃鱼" 的概率 = P(猫) P(喜欢|猫) P(吃|猫, 喜欢) P(鱼|喜欢, 吃)。

  • 优缺点

    • 优点:比二元语法更贴近实际语言规律,能捕捉更丰富的局部上下文(例如 "喜欢吃" 后面更可能接 "鱼" 而非 "石头")。
    • 缺点:
      • 对数据量需求大,容易出现 "数据稀疏" 问题(很多三元组合在语料库中从未出现,导致概率为 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元语法

完整代码

python 复制代码
"""
文件名: 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>固定在索引0
        self.idx_to_token = ['<unk>'] + reserved_tokens
        self.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  # 新索引为当前长度-1

    def __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]  # 索引列表

    @property
    def unk(self):
        """未知词元的索引(固定为0)"""
        return 0

    @property
    def token_freqs(self):
        """返回词元频率列表"""
        return self._token_freqs


def 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列表为1D
    return 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越大,频率下降越快,符合短距离依赖)

实验结果

词频图

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

相关推荐
JNU freshman12 分钟前
计算机视觉 之 数字图像处理基础(一)
人工智能·计算机视觉
SHIPKING39328 分钟前
【python】基于pygame实现动态粒子爱心
开发语言·python·pygame
鹧鸪云光伏31 分钟前
鹧鸪云重构光伏发电量预测的精度标准
人工智能·无人机·光伏·光伏设计·光伏模拟
九章云极AladdinEdu32 分钟前
摩尔线程MUSA架构深度调优指南:从CUDA到MUSA的显存访问模式重构原则
人工智能·pytorch·深度学习·机器学习·语言模型·tensorflow·gpu算力
IT信息技术学习圈40 分钟前
AI交互中的礼貌用语:“谢谢“的效用与代价分析
人工智能·交互
kk_stoper2 小时前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源
java1234_小锋2 小时前
【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 架构搭建
python·自然语言处理·flask
机器之心3 小时前
马斯克Grok这个二次元「小姐姐」,攻陷了整个互联网
人工智能
szxinmai主板定制专家3 小时前
基于光栅传感器+FPGA+ARM的测量控制解决方案
arm开发·人工智能·嵌入式硬件·fpga开发
Guheyunyi3 小时前
电气安全监测系统:筑牢电气安全防线
大数据·运维·网络·人工智能·安全·架构