02. 文本表示

一、文本表示

  文本表示 是将自然语言转换为计算机能够理解的数值形式,是绝大多数自然语言处理(NLP)任务的基础步骤。

  文本表示的第一步通常是 分词 和 词表构建。

  • 分词(Tokenization):将原始文本切分为若干具有独立语义的最小单元(即 token)的过程,是所有 NLP 任务的起点。
  • 词表(Vocabulary):由于语料库构建出、包含模型可识别 token 的集合。词表中每个 token 都分配有唯一的 ID,并支持 token 与 ID 之间的双向映射。

分词与词表构建

  在后续训练或预测过程中,模型会首先对输入文本进行 分词,再通过 词表 将每个 token 映射为其对应的 ID。接着,这些 ID 会被输入 嵌入层(Embedding Layer),转换为 低纬稠密的向量 表示(即 词向量)。

  此外,在文本生成任务中,模型的输出层会针对词表中的每个 token 生成一个概率分布,表示其作为下一个词的可能性。系统通常选取具有最大概率的 ID,并通过词表查找对应的 token,从而逐步生成最终的输出文本。

获取词向量

二、分词

  不同语言由于语言结构、词边界的差异,其分词策略和算法也不尽相同。

2.1、英文分词

  按照分词粒度的大小,可分为 词级分词(Word-Level)、字符级分词(Character-Level)和 子词级分词(Subword-Level)。

2.1.1、词级分词

  词级分级 是将文本按 词语 进行切分,是最传统、最直观的分词方式。在英文中,空格 和 标点 往往是天然的分隔符。

英文词级分词

  词级分词虽然便于理解和实现,但在实际过程中容易出现 OOV(Out-Of-Vocabulary,未登录词)问题。所谓 OOV,就是 在模型使用阶段,输入文本中出现了不在预先构建词表中的词语,常见的包括网络热词、专有名词、复合词及拼写变体。由于模型无法识别这些词,通常会将其同意替换为特殊标记(如 <UNK>),从而导致语义信息的丢弃,影响模型的理解与预测能力。

2.1.2、字符级分词

  字符级分词(Character-level Tokenization)是以 单个字符 为最小单位进行分词的方法,文本中每一个字母、数字、标点甚至空格,都被视为一个独立的 token。

英文字符级分词

  在这种分词方式下,词表仅由所有可能出现的字符组成,因此词表现规模非常小,覆盖率极高,几乎不存在 OVV(Out-of-Vocabulary)问题。无论输入中出现什么样的新词或拼写变体,只要字符在词表中,就能被表示出来。

  然而,由于单个字符本身语义信息极弱,模型必须依赖更长的上下文来推断词义和结构,这显著增加了建模难度和训练成本。此外,输入序列也会变得更长,影响模型效率。

2.1.3、子词级分词

  子词级分词 是一种介于词级分词与字符分词之间的分词方法,它将词语切分为更小的单元 —— 子词(subword),例如 词根、前缀、后缀 或 常见词片段。与 词级分词 相比,子词分词可以显著缓解 OOV 问题,与 字符级分词 相比,它能更好地保留一定的语义结构。

  子词分词的基本思想是:即使一个完整的词没有出现在词表中,只要它可以被拆分为词表中存在的子词单元,就可以被模型识别和表示,从而避免整体被替换为 <UNK>。

英文子词级分词

  常见的子词分词算法包括 BPE(Byte Pair Encoding)、WordPiece 和 Unigram Language Model。

【1】BPE 字词分词算法

  在 训练阶段,首先将预料中的词汇拆分为单个字符,构建初始词表。然后迭代地统计语料中出现频率最高的相邻字符对,将其合并为单个子词单元,并加入词表。这个过程继续进行,直到词表大小达到预设上限。

  在 分词阶段,BPE 会根据构建好的词表和合并规则对新输入的文本进行处理。具体做法是将文本拆分为最小单位(如字符或字节),然后按顺序应用训练中学习到的合并规则,逐步合并,直到无法继续。最终得到的就是子词组成的分词结果。

2.2、中文分词

2.2.1、字符级分词

  字符级分词 是中文处理中最简单的一种方式,即将文本按照单个汉字进行切分,文件中的每一个汉字都被是为一个独立的 token。由于汉字本身通常据具有独立语义,因此字符级分词在中文中具备天然的可行性。相比英文中的字符分词,中文的字符分词更加语义友好。

中文字符级分词

2.2.2、词级分词

  词级分词 是将中文文本按照完整词语进行切分的传统方法,切分结果更贴近人类阅读习惯。由于中文没有空格等天然词边界,词级分词通常以来字典、规则或模型来识别词语边界。

中文词级分词

2.2.3、子词级分词

  虽然中文没有英文中的子词结构(如前缀、后缀、词根等),但子词分词算法(如 BPE)仍可以直接应用于中文。它们以汉字为基本单元,通过学习语料高频的子组合,自动构建子词词表。这种方式无需人工词典,具有较强的适应能力。

三、分词工具

  目前市面上可用于中文分词的工具种类繁多,按照实现方式大致可以分为如下两类:

  • 基于词典或模型的传统方法,主要以 “词” 为单位进行切分。常用的工具:jieba、HanLP 等。
  • 基于子词构建算法的方式(如 BPE),从数据中自动学习高频字组合,构建子词词表。常用的工具有 Hugging Face Tokenizer、SentencePiece、tiktoken 等。

  我们可以在终端中使用 pip 安装 jieba 分词库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

pip install jieba -i https://pypi.tuna.tsinghua.edu.cn/simple

  国内常用的 pip 下载源列表:

  jieba 分词器提供了多种分词模式,以使用不同的应用场景。

【1】、精确模式

  精确模式 试图将句子最精确切开,适合文本分析。分析效果如下:

jieba分词器精确模式

  精确模式 可使用 jieba.cut() 或者 jieba.lcut() 方法。前者返回一个 生成器对象,后者返回一个 列表。

def cut(
    sentence: Unknown,                                                          # 需要分词的中文文本
    cut_all: bool = False,                                                      # 是否使用全模式
    HMM: bool = True,                                                           # 是否使用隐马尔可夫模型
    use_paddle: bool = False                                                    # 是否使用PaddlePaddle深度学习框架的分词模式
) -> Generator[Unknown | str | Any, Any, None]
def lcut(...) -> list[Unknown | str | Any]
import jieba

if __name__ == "__main__":
    text = "小明毕业于北京大学计算机系"
    words = jieba.cut(text)                                                     # 精确模式

    # 小明 毕业 于 北京大学 计算机系
    print(" ".join(words))

【2】、全模式

  全模式 把句子中所有的可以成词的词语都扫描出来。分析效果如下:

jieba分词器全模式

  全模式 可使用 jieba.cut() 或者 jieba.lcut() 方法,并传入 cut_all = True 关键字参数 。前者返回一个 生成器对象,后者返回一个 列表。

import jieba

if __name__ == "__main__":
    text = "小明毕业于北京大学计算机系"
    words = jieba.cut(text, True)                                               # 全模式

    # 小 明 毕业 于 北京 北京大学 大学 计算 计算机 计算机系 算机 系
    print(" ".join(words))

【3】、搜索引擎模式分词

  在精确模式基础上,对长词进一步切分,适合用于搜索引擎分词,分词效果如下:

jieba分词器搜索引擎模式

  精确引擎模式 可使用 jieba.cut_for_search() 或者 jieba.lcut_for_search() 方法。前者返回一个 生成器对象,后者返回一个 列表。

def cut_for_search(
    sentence: Unknown,                                                          # 需要被切分的文本句子
    HMM: bool = True                                                            # 是否使用隐马尔可夫模型
) -> Generator[Unknown | str | Any, Any, None]
def lcut_for_search(...) -> list[Unknown | str | Any]
import jieba

if __name__ == "__main__":
    text = "小明毕业于北京大学计算机系"
    words = jieba.cut_for_search(text)                                          # 搜索引擎模式

    # 小明 毕业 于 北京 大学 北京大学 计算 算机 计算机 计算机系
    print(" ".join(words))

  jieba 分词器支持 用户自定义字典,以便包含 jieba 词库里没有的词,用于增强特定领域词汇的识别能力。

  自定义词典的格式为:一个词占一行,每一行分三部分:词语、词频(可省略,词频决定某个词在分词时的优先级。词频越高被优先切分出来的概率越大),词性标签(可省略,不影响分词结果),每个部分之间用 空格 隔开,顺序不可调换。

词语 词频(可省略) 词性标签(可省略)

  常用的词性标签如下:

标签 含义 标签 含义 标签 含义 标签 含义
n 普通名词 f 方位名词 s 处所名词 t 时间
nr 人名 ns 地名 nt 机构名 nw 作品名
nz 其它专名 v 普通动词 vd 动副词 vn 名动词
a 形容词 ad 副形词 an 名形词 d 副词
m 数量词 q 量词 r 代词 p 介词
c 连词 u 助词 xc 其它虚词 w 标点符号
PER 人名 LOC 地名 ORG 机构名 TIME 时间

  我们可以使用 jieba.load_userdict() 方法 加载词典文件。

def load_userdict(f: Unknown) -> None

  我们新建一个 user_dict.txt 文件,用来存放自定义的字典。

云计算
云原生
大模型 10 n
import jieba

if __name__ == "__main__":
    text = """
    随着云计算技术的普及,越来越多的企业开始采用云原生架构来部署服务,并借助大模型能力提升智能化水平,实现业务流程的自动化与智能决策。
    """

    jieba.load_userdict("assets/data/user_dict.txt")                            # 加载用户自定义词典
    words = jieba.cut(text)                                                     # 精确模式

    print(" ".join(words))

  我们可以使用 jieba.posseg.lcut() 方法 获取每个分词的词性标注。

import jieba.posseg as pseg

if __name__ == "__main__":
    text = "小明毕业于北京大学计算机系"
    data = pseg.lcut(text)                                                      # 搜索引擎模式
    print(data)

四、词表示

  在分词完成之后,文本被转换为一系列的 token(词、子词或字符)。然而,这些符号本身对计算机而言是不可计算的。因此,为了让模型能够理解和处理文本,必须将这些 token 转换为计算机可以识别和操作的数值形式,这一步就是所谓的 词表示(word representation)。

  词表示的发展经历了从 稀疏的 one-hot 编码 到 稠密的语义化词向量,再到 上下文相关的词表示。不同的词表方法在表达能力、语义建模、上下文适应性等方面存在显著差异。

4.1、one-hot 编码

  one-hot 编码将词汇表中的每个词映射为一个稀疏向量,向量的长度等于整个词表的大小。该词在对应的位置为 1,其它位置为 0。one-hot 编码虽然实现简单、直观易懂,但它无法体现词与词之间的语义关系,且随着词表规模的扩大,向量维度会迅速膨胀,导致计算效率低下。

one-hot编码

4.2、语义化词向量

  传统的 one-hot 表示虽然结构简单,但它无法反映词语之间的语义关系,也无法衡量词与词之间的相似度。为了解决这个问题,研究者提出了 Word2Vec 模型,它通过对大规模语料的学习,为每个词生成一个具有语义意义的 稠密向量 表示。这些向量能够在连续空间中表达词与词之间的关系,使得意思相近的词在空间中距离更近。

语义化词向量

  Word2Vec 是一种将 单词 转换为 词向量 的自然语言处理技术,是利用 深度学习网络 来探索单词和单词之间的语义关系,用深度学习的 网络权重参数 表示词向量,它是在 无监督的语料 上构建一个 有监督的任务。

  Word2Vec 的设计理念源自 ”分布假设“,即 一个词的含义由它周围的词决定。基于这一假设,Word2Vec 构建了一个简洁的神经网络模型,通过学习词与上下文之间的关系,自动为每个词生成一个能够反映语义特征的向量表示。Word2Vec 提供了两种典型的模型结构,用于实现词向量的学习。

【1】、CBOW(Continuous Bag-of-Words)模型

  CBOW 模型试图根据 上下文的周围单词 来 预测当前单词。它将 周围单词的词向量求和或取平均 作为 上下文的表示,然后通过一个神经网络进行预测。由于 CBOW 是对上下文的整体信息进行汇总,因此 CBOW 通常需要比 Skip-gram 更多的训练数据才能取得更好的结果。因为它将多个上下文单词的词向量求和或取平均,减少了模型的复杂度,因此 CBOW x相对于 Skip-gram 在计算上更为高效。CBOW 在训练数据较大的情况下往往表现较好,特别是在 低频词 的情况下。

CBOW模型

【2】、Skip-gram 模型

  Skip-gram 模型试图根据 当前单词 来 预测上下文的周围单词。具体来说,它将 当前单词的词向量 作为输入,然后通过一个神经网络来 预测周围单词。由于 Skip-gram 将单词的上下文信息进行了明确建模,因此 Skip-gram 相对于 CBOW 更加灵活,它可以处理更复杂的语义关系。因为它能够生成更丰富的上下文信息,因此 Skip-gram 在处理 低频词 时往往更具有优势,尤其是在大规模数据集中。相对于 CBOW,Skip-gram 的训练数独通常较慢,因为它需要对每个单词生成上下文。

Skip-gram模型

  Word2Vec 不依赖人工标注,而是直接利用大规模原始文本作为数据源,从而自动构建训练样本。由于两种模型的输入和输出都是词语,因此首先需要对原始文本进行分词,将连续文本转换为 token 序列。此外,模型无法直接处理文本符号,训练时仍需要将词语转换为 one-hot 编码,以便作为模型的输入和输出进行计算。

 词向量的获取通常有两种方式:一种是 直接使用他人公开发布大的词向量,另一种是 在特定语料上自行训练。在项目中,无论是加载已有模型还是从零训练,都可以借助 Gensim 来完成,它提供了便捷的接口来加载 Word2Vec 格式的词向量,也支持基于自有于语料训练属于自己的词向量模型。

  我们可以在终端中使用 pip 安装 gensim 库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

pip install gensim -i https://pypi.tuna.tsinghua.edu.cn/simple

【1】、加载公开的词向量

  我们可以从 GitHub 中下载公开的中文词向量: https://github.com/Embedding/Chinese-Word-Vectors 。如果我们无法访问 GitHub,我们可以下载 瓦特工具箱(Steam++官网) - Watt Toolkit 加速访问。我们还可以从 AtomGit 访问镜像仓库: https://gitcode.com/gh_mirrors/ch/Chinese-Word-Vectors 。

  在我们访问链接之后,可以查看它的 README_zh.md 文档(镜像仓库的文档:README_zh.md 文档)。该文档中介绍了这个仓库如何使用。在该文档中提供预训练中文词向量链接,我们可以点击进行下载。

下载公开词向量

  所有的预训练词向量文件均为文本格式。每一行都包括一个词和它对应的词向量。所有的值均用空格分开。每个文件的第一行记录了基本信息:第一个数值是 文件中总词数,第二个数值是 向量维度。

<词汇总数> <向量维度>
word1 value11 value12 ... value1N
word2 value21 value22 ... value2N
...

  我们可以使用 Gensim 中的 KeyedVectors.load_word2vec_format() 方法 加载词向量文件。

def load_word2vec_format(
    fname: Unknown,                                                             # 文件路径
    fvocab: Unknown | None = None,                                              # 可选的词汇文件路径
    binary: bool = False,                                                       # 是否为二进制格式
    encoding: str = 'utf8',                                                     # 编码方式
    unicode_errors: str = 'strict',                                             # Unicode错误处理方式
    limit: Unknown | None = None,                                               # 限制加载的词数
    datatype: float32 = REAL,                                                   # 数据类型
    no_header: bool = False                                                     # 是否没有标题行
) -> Unknown

  在加载完词向量文件之后,我们可以使用 model.vector_size 属性 查看词向量维度,使用 model["词"] 方式 查看某个词的向量,使用 model.similarity("词1", "词2") 方法 查看两个词向量的余弦相似度。它的公式如下:

\[similarity(w_{1}, w_{2}) = \cos(\theta) = \frac{\vec{x_{1}} . \vec{w_{2}}}{\lvert \lvert \vec{w_{1}} \rvert\rvert . \lvert \lvert \vec{x_{2}} \rvert \rvert} \]

  它的返回值介于 [-1, 1]。接近 1 表示 高度相似,语义接近,接近 0 表示 无明显相关,接近 -1 表示方向完全相反,极度不相似。

from gensim.models import KeyedVectors

if __name__ == "__main__":
    model = KeyedVectors.load_word2vec_format("assets/data/sgns.weibo.word.bz2")    # 加载词向量文件
    similarity = model.similarity("公交", "地铁")                                # 计算两个词的相似度
    print(similarity)

  我们可以使用 model.most_similar() 方法 查找与指定词最接近的其它词。

def most_similar(
    positive = None,                                                            # 作为“正面”贡献的词列表。这些词的向量相加,
    negative = None,                                                            # 作为“负面”贡献的词列表。这些词的向量会从总和中减去
    topn = 10,                                                                  # 返回的最相似词的数量
    clip_start = 0,                                                             # 结果返回的起始索引位置
    clip_end = None,                                                            # 结果返回的结束索引位置
    restrict_vocab = None,                                                      # 仅考虑词表中频率最高的前restrict_vocab个词
    indexer = None,                                                             # 一个函数或对象,用于获取词表中词的索引信息
)
from gensim.models import KeyedVectors

if __name__ == "__main__":
    model = KeyedVectors.load_word2vec_format("assets/data/sgns.weibo.word")    # 加载词向量文件
  
    # 获取最相似的词
    most_similar_words = model.most_similar(positive=["男生", "女孩"], negative=["男孩"])
  
    for word, similarity in most_similar_words:
        print(word, similarity)

【2】、训练自己的词向量

  在 Gensim 中提供了 Word2Vec API 用来 训练词向量。

class Word2Vec(
    sentences: Unknown | None = None,                                           # 可迭代的句子序列
    corpus_file: Unknown | None = None,                                         # 语料文件路径
    vector_size: int = 100,                                                     # 词向量的维度
    alpha: float = 0.025,                                                       # 初始学习率
    window: int = 5,                                                            # 上下文窗口大小,即中心词左右各 window 个词的范围
    min_count: int = 5,                                                         # 最低词频阈值。词频低于此值的词会被忽略
    max_vocab_size: Unknown | None = None,                                      # 词汇表最大词数。若词数超过此值,按词频从低到高删减。
    sample: float = 0.001,                                                      # 高频词下采样阈值
    seed: int = 1,                                                              # 随机数种子
    workers: int = 3,                                                           # 并行训练的工作线程数
    min_alpha: float = 0.0001,                                                  # 训练结束时的最小学习率
    sg: int = 0,                                                                # 训练算法开关,0: CBOW, 1: skip-gram
    hs: int = 0,                                                                # 是否使用层级 Softmax,0:不使用;1:使用。
    negative: int = 5,                                                          # 负采样个数。若 =0,不使用
    ns_exponent: float = 0.75,                                                  # 负采样分布平滑指数
    cbow_mean: int = 1,                                                         # 仅 CBOW 模式下有效。1:将上下文词向量求和取平均。0:直接求和。通常设为 1 效果更好。
    hashfxn: Unknown = hash,                                                    # 用于哈希索引词的函数
    epochs: int = 5,                                                            # 遍历语料的次数
    null_word: int = 0,                                                         # 若设为 1,模型会学习一个“空词”的向量,用于表示不在词汇表中的词(OOV)。开启后 OOV 词可通过此向量得到统一表示
    trim_rule: Unknown | None = None,                                           # 自定义词汇剪枝函数
    sorted_vocab: int = 1,                                                      # 词汇表是否按词频降序排列。通常设为 1,便于高频词索引靠前。
    batch_words: Unknown = MAX_WORDS_IN_BATCH,                                  # 每批处理的最大词数
    compute_loss: bool = False,                                                 # 是否计算并存储每个 epoch 的训练损失。
    callbacks: Unknown = (),                                                    # 回调函数列表,可在训练各阶段(epoch 开始/结束、batch 结束等)注入自定义操作
    comment: Unknown | None = None,                                             # 为模型添加一行注释,不影响训练,仅用于标记模型来源或参数信息。
    max_final_vocab: Unknown | None = None,                                     # 最终词汇表最大词数
    shrink_windows: bool = True                                                 # 是否随机缩小实际窗口大小
)

  为了解析 CSV 文件,我们可以在终端中使用 pip 安装 pandas 库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载。

pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple

  我们可以从 魔塔社区 下载 商品评论情感预测数据集: https://www.modelscope.cn/datasets/DAMO_NLP/jd/files 。

import os

import jieba
import pandas as pd

from gensim.models import Word2Vec

if __name__ == "__main__":
    df = pd.read_csv("assets/data/train.csv").dropna()                          # 加载数据,并去掉缺失值

    # 分词,获取二维的可迭代的句子序列
    sentences = [[token for token in jieba.lcut(sentence) if token.strip() != ""] for sentence in df["sentence"]]

    # 获取词向量模型
    model = Word2Vec(
        sentences,                                                              # 已分词的句子序列
        vector_size = 100,                                                      # 词向量维度维度
        window = 5,                                                             # 上下文窗口大小
        min_count = 2,                                                          # 最小词频,低于将被忽略
        sg = 1,                                                                 # 1表示skip-gram模型,0表示cbow模型
        workers = 4                                                             # 并行训练线程数
    )                                     

    if not os.path.exists("assets/data"):
        os.makedirs("assets/data")

    model.wv.save_word2vec_format("assets/data/word2vec.txt")                   # 保存词向量

4.3、上下文相关词表示

  虽然像 Word2Vec 这样的模型已经能够为词语提供具有语义的向量表示,但是它只为每个词分配一个固定的向量表示。无论该词出现在什么样的句子语境中,都不会发生改变。这种表示方式就是 静态词向量。

  然而,语言的表达极其灵活,一个词在不同上下文中可能会出现不同的含义。此时,使用同一个静态词向量去表示那个词,显然无法区别它的不同语境下的语义。例如,意思这个词在不同的语境下就有不同的含义。

  • 你这是什么意思(此处的 “意思” 表示送礼的意图或目的)
  • 没什么意思,只是意思意思(前面的 “意思” 表示没有特别的意图,后面的 “意思” 表示略表心意)
  • 你这就不够意思了(此处的 “意思” 表示不够朋友、不够交情)
  • 小意思小意思(此处的 “意思” 表示谦虚,礼物很轻微,不值得一提)
  • 你这人真有意思(此处的 “意思” 表示有趣、幽默)
  • 其实也没别的意思(此处的 “意思” 表示没有其他特别的意图)
  • 那我就不好意思了(此处的 “意思” 表示接受礼物时的客气话)
  • 是我不好意思(此处的“意思”表示道歉或表示自己的行为不够得体)

  上下文相关词表示(Contextual Word Representations)是指词语的向量表示会根据它所在的句子上下文动态变化,从而更好地捕捉其语义。一个具有代表的模型是:ELMo(全称为 Embeddings from Language Models)。该模型基于 LSTM 语言模型,使用上下文动态生成每个词的表示,每个词的向量都有其前文和后文共同决定,是第一个被广泛应用于下游任务的上下文词向量模型。

posted @ 2026-08-29 21:39  星光映梦  阅读(21)  评论(0)    收藏  举报