大家好,在自然语言处理(NLP)的世界里,我们面临的首要问题就是如何让冰冷的机器理解充满温度的人类语言。机器本质上只认识数字,因此,将文本转换成它们能够处理的数字形式——即文本向量化文本张量表示——是所有NLP任务的基石。今天,我们将从最基础的One-Hot编码讲起,逐步深入到Word2Vec、FastText,最后探讨如何在深度学习框架中使用Embedding层,带你彻底搞懂文本向量化的核心技术。


本文摘要

本文将详细讲解以下几种核心的文本张量表示方法,带你一步步揭开文本向量化的神秘面紗:

  1. One-Hot 编码:最直观的词向量表示法及其致命缺陷。
  2. Word2Vec 模型:开启语义向量时代的神器,详解其CBOW与Skip-gram两种模式。
  3. FastText 模型:Word2Vec的升级版,如何通过子词信息解决OOV(未登录词)问题。
  4. WordEmbedding 层:深度学习模型中词向量的“标准配置”,如何使用并进行可视化。

一. 为什么需要文本张量表示?

核心意义:将文本,特别是构成文本的词汇,转换为计算机能够进行数学运算的数值向量。这是让机器学习模型能够识别、理解并处理自然语言的绝对前提。我们后续的所有模型训练,无论是文本分类、情感分析还是机器翻译,都是建立在这些数字向量之上的。


二. One-Hot 编码:简单但“语义鸿沟”巨大

1. 定义

One-Hot,又称独热编码,是最简单、最直观的词向量表示方法。

核心思想

  1. 构建一个包含所有不重复词的词汇表
  2. 为词汇表中的每一个词分配一个向量。这个向量的维度等于词汇表的总大小。
  3. 向量中只有一个位置是1(代表当前词在词汇表中的索引),其余所有位置都是0

举例:假设我们的词汇表是 {"周杰伦", "陈奕迅", "王力宏"}

  • “周杰伦” 的One-Hot编码可能是 [1, 0, 0]
  • “陈奕迅” 的One-Hot编码可能是 [0, 1, 0]
  • “王力宏” 的One-Hot编码可能是 [0, 0, 1]

2. 缺点

尽管实现简单,但One-Hot编码存在两个致命的缺点:

  1. 割裂词义关系(主要):在One-Hot向量中,任意两个不同词的向量都是正交的。这意味着从数学上来看,"周杰伦""陈奕迅"(都是歌手,语义相近)之间的关系,与"周杰伦""苹果"(毫无关系)之间的关系是完全等价的。它无法表达出词与词之间的任何语义相似性。
  2. 维度爆炸与稀疏性:当词汇表非常庞大时(例如,中文常用词汇就数以万计),每个词的向量维度会变得极高,并且其中只有一个1,其余全是0。这造成了巨大的内存浪费和计算效率低下。
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 导入用于对象保存与加载的joblib
import joblib

def get_one_hot():
    # 准确语料库
    vocabs = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
    # 实例化Tokenizer
    my_tokenizer = Tokenizer()
    my_tokenizer.fit_on_texts(vocabs)
    # 打印word_index, index_word
    print(my_tokenizer.word_index)
    # print(my_tokenizer.index_word)
    # 对每个vocab进行one-hot编码的实现
    for vocab in vocabs:
        zero_list = [0] * len(vocabs)
        idx = my_tokenizer.word_index[vocab] - 1
        zero_list[idx] = 1
        print(f'当前{vocab}的one-hot编码是{zero_list}')

    # 保存tokenizer,方便下次使用
    mypath = './mytokenizer'
    joblib.dump(my_tokenizer, mypath)
    print('模型保存成功')


def use_one_hot():
    # 加载训练好的tokenizer
    mypath = './mytokenizer'
    my_tokenizer = joblib.load(mypath)
    print(f'my_tokenizer--》{my_tokenizer.word_index}')
    token = '李宗盛'
    zero_list = [0] * len(my_tokenizer.word_index)
    idx = my_tokenizer.word_index[token] - 1
    zero_list[idx] = 1
    print(f'当前{token}的one-hot编码是{zero_list}')

if __name__ == '__main__':
    # get_one_hot()
    use_one_hot()

三. Word2Vec模型:让词向量拥有“灵魂”

为了克服One-Hot的缺陷,研究人员提出了Word2Vec。它是一种通过无监督训练,将词映射到低维、稠密、连续的向量空间中的模型。这些生成的向量(即词向量或Word Embedding)能够神奇地捕捉到词与词之间的语义关系。

Word2Vec的核心假设是“一个词的含义可以由其上下文推断出来”。它包含两种主要的训练方式:

1. CBOW (Continuous Bag-of-Words)

  • 任务:利用上下文(周围的词)来预测中心词。
  • 过程:将目标词周围窗口内的多个上下文词向量进行整合(如求和或平均),然后通过一个简单的神经网络来预测出中心词。

2. Skip-gram

  • 任务:利用中心词来预测其上下文。
  • 过程:输入一个中心词,让模型去预测其窗口范围内的多个上下文词汇。
  • 特点:对于大型语料库和不常见的低频词,Skip-gram的效果通常比CBOW更好。

训练完成后,我们真正需要的东西,其实是神经网络隐藏层的权重矩阵。这个矩阵的每一行,就对应着词汇表中一个词的稠密语义向量

3. FastText:更精细的Word2Vec

FastText是Facebook开源的一个词向量训练工具,可以看作是Word2Vec的优化升级版。

核心创新:FastText引入了子词(subword)的概念。它将每个词看作是由字符n-gram(n个连续字符的组合)组成的集合。

  • 例如,对于单词apple,FastText不仅学习apple本身的向量,还会学习其子词,如 <ap, app, ppl, ple, le> 等的向量。最终,apple的词向量是它所有子词向量的叠加。

巨大优势

  1. 解决未登录词(OOV)问题:即使训练时没见过某个词(比如apples),FastText也能通过其内部的字符n-gram(如apples)来“估算”出一个合理的词向量。Word2Vec则无能为力。
  2. 更好地捕捉形态学信息:对于eat, eating, eaten这类有相似词根的词,因为它们共享很多子词,所以它们的词向量在空间上会非常接近,这完全符合语言学直觉。
import fasttext

# 训练词向量模型
def dm_fasttext_01():
    # 直接开始训练:以非监督的方式进行
    model = fasttext.train_unsupervised('./data/ai20aa')
    # 保存模型
    model.save_model('./data/ai20_fil9.bin')

# 获取某个词的词向量和检验模型效果
def dm_fasttext_02():
    # 加载模型
    model = fasttext.load_model('./data/ai20_fil9.bin')
    # 直接获取某个词的向量
    # results = model.get_word_vector("the")
    # print(type(results))
    # print(results.shape)
    # print(results)
    # 检验模型的效果
    results = model.get_nearest_neighbors("dog")
    print(f'dog的临近词-->{results}')

# 训练词向量模型:修改参数
def dm_fasttext_03():
    # 直接开始训练:以非监督的方式进行
    model = fasttext.train_unsupervised('./data/ai20aa',"cbow", dim=100, lr=0.1, epoch=1)
    # 保存模型
    model.save_model('./data/ai20_fil9_new.bin')

if __name__ == '__main__':
    # dm_fasttext_01()
    # dm_fasttext_02()
    dm_fasttext_03()

四. WordEmbedding层:深度学习模型的标配

在PyTorch、TensorFlow等深度学习框架中,Embedding层是处理序列化文本数据的标准模块。

定义Embedding层本质上是一个可学习的查询表(Lookup Table)。它内部维护着一个尺寸为 [词汇表大小, 词向量维度] 的权重矩阵。当你输入一个或一批词的整数索引时,它会高效地返回这些索引对应的向量。

1. 两种使用方式

  1. 从头训练:随机初始化Embedding层的权重,然后在下游任务(如文本分类)的训练过程中,通过反向传播不断学习和优化这些词向量,使其最适合当前任务。
  2. 加载预训练向量:使用Word2Vec或FastText等工具预先训练好的词向量来初始化Embedding层的权重。这是一种非常强大的迁移学习技巧,可以让模型在一个很好的起点上开始训练。

2. 可视化

通过TensorBoard等可视化工具,我们可以将Embedding层中的高维词向量通过降维算法(如t-SNE或PCA)投影到2D或3D空间中。这可以帮助我们直观地观察词向量的学习效果。在一个训练良好的模型中,语义相近的词(如“国王”和“女王”,或“北京”和“上海”)在投影空间中的位置会彼此靠近,形成簇群。这为我们理解模型内部的工作机制提供了一个非常直观的窗口。

import torch
from keras.preprocessing.text import Tokenizer
from torch.utils.tensorboard import SummaryWriter
import jieba
import torch.nn as nn

# 实验:nn.Embedding层词向量可视化分析
# 1 对句子分词 word_list
# 2 对句子word2id求my_token_list,对句子文本数值化sentence2id
# 3 创建nn.Embedding层,查看每个token的词向量数据
# 4 创建SummaryWriter对象, 可视化词向量
#   词向量矩阵embd.weight.data 和 词向量单词列表my_token_list添加到SummaryWriter对象中
#   summarywriter.add_embedding(embd.weight.data, my_token_list)
# 5 通过tensorboard观察词向量相似性
# 6 也可通过程序,从nn.Embedding层中根据idx拿词向量

def dm_embeding_show():

    # 1 对句子分词 word_list
    sentence1 = '传智教育是一家上市公司,旗下有黑马程序员品牌。我是在黑马这里学习人工智能'
    sentence2 = "我爱自然语言处理"
    sentences = [sentence1, sentence2]

    word_list = [] # list()
    for s in sentences:
        word_list.append(jieba.lcut(s))
    # print(f'word_list--》{word_list}')

    # 2 对每次词进行词表映射
    my_tokenizer = Tokenizer()
    my_tokenizer.fit_on_texts(word_list)
    print(f'my_tokenizer.word_index-->{my_tokenizer.word_index}')
    # print(f'my_tokenizer.index_word-->{my_tokenizer.index_word}')

     #获取所有词汇(去重)
    my_token_list = my_tokenizer.index_word.values()
    print(f'my_token_list-->{my_token_list}')
    print(f'my_token_list的长度-->{len(my_token_list)}')

    # 将句子的单词用数字进行表示
    seq2id = my_tokenizer.texts_to_sequences(word_list)
    # print(f'seq2id--》{seq2id}')

    # 3 创建Embedding层
    embed = nn.Embedding(num_embeddings=len(my_token_list), embedding_dim=8)
    # print(f'embed-->{embed.weight}')
    print(f'embed-->{embed.weight.data}')
    # print(f'embed-->{embed.weight.data.shape}')

    # 4 可视化展示
    # my_summary = SummaryWriter()
    # my_summary.add_embedding(embed.weight.data, my_token_list)
    # my_summary.close()

    # 5 取出每个单词对应的向量表示
    for idx in range(len(my_tokenizer.index_word)):
        temp_vector = embed(torch.tensor(idx))
        print(f'temp_vector--》{temp_vector}')
        word = my_tokenizer.index_word[idx+1]
        print(f'当前单词--》--<{word}>--的词向量是--》{temp_vector.detach().numpy()}')


if __name__ == '__main__':
    dm_embeding_show()

五. 总结

让我们用一个表格来清晰地对比这几种方法:

方法 核心思想 优点 缺点
One-Hot 每个词对应一个高维向量,其中一位是1,其余为0。 简单、直观。 维度灾难、内存占用大、无法表达词间语义关系。
Word2Vec 用低维、稠密的向量表示词,通过上下文预测来学习语义。 向量包含语义信息、维度低、效率高。 无法处理未登录词(OOV)。
FastText 在Word2Vec基础上引入n-gram子词信息。 继承Word2Vec优点,且能很好地处理OOV问题,对词形态变化鲁棒。 模型文件比Word2Vec大,训练稍慢。
Embedding层 深度学习模型中的一个可训练层(查询表)。 灵活性高,可从头学习或加载预训练向量进行微调,与下游任务深度耦合。 本身不是一种训练方法,而是一个模块。

从One-Hot的“形同陌路”,到Word2Vec的“物以类聚”,再到Embedding层的“量体裁衣”,文本向量化技术的发展,本质上就是不断追求更精准、更高效地捕捉和表达词汇语义信息的过程。掌握了这些方法,你就已经迈出了NLP世界中最坚实的一步!

六. 参考资料

黑马程序员AI大模型NLP自然语言处理全套视频教程,从传统序列模型到基于Transformer的预训练模型,构建完整NLP知识体系与项目实战


感谢阅读!如果这篇文章对你有帮助,欢迎点赞、收藏并关注我,我们下期再见!

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐