☰
AI-For-Beginners 课程详解:如何将文本表示为张量 —— 词袋、N-gram 与 TF/IDF 实战指南
2026/10/9 2:05:22 网站建设 项目流程
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

本文是 AI-For-Beginners 课程「自然语言处理(NLP)」模块 第 13 课 的技术详解。课程以文本分类为核心任务(以 AG News 新闻数据集为例,将新闻归入 World / Sports / Business / Sci-Tech 四个类别),完整讲解从「文本 → 字符/词 → 数字 → 张量」的表示链路,覆盖字符级与词级表示、N-gram、Bag-of-Words(BoW)与 TF-IDF四种经典方法,并给出 PyTorch 与 TensorFlow 两套可运行代码。读完本文,你将掌握如何把任意 NLP 任务中的原始文本转换为神经网络可消费的张量输入,并能独立构建一个 BoW/TF-IDF 文本分类器。

1 背景:为什么 NLP 任务需要「文本表示」

计算机无法直接理解自然语言。虽然计算机内部以 ASCII / UTF-8 等编码把字符映射为数字(这些数字对应屏幕上的字形),但正如课程 README 指出的:人类知道每个字母"代表"什么、字符如何组成词语,而计算机自身不具备这种理解,神经网络的"意义"完全要在训练过程中学习。因此,我们需要的不是字形编号,而是一种语义友好的张量表示。

以字符编码为例,H对应 ASCII 码 100、二进制 01100100,但这种表示对神经网络毫无语义信息:

图片来源:课程 13-TextRep/README.md

与图像、表格数据不同,文本的显著特征是变长序列。本节 NLP 模块的 5-NLP/README.md 专门指出:图像输入尺寸预先已知,而文本长度不固定;同时文本中的模式远比图像复杂——例如否定词与被否定对象之间可以隔着任意多的词(I do not like oranges与I do not like those big colorful tasty oranges语义相同但词距不同),这促使后续课程引入 RNN、Transformer 等新架构。而在那之前,第一步永远是:把文本变成张量。

课程聚焦的起点任务是文本分类:给定一段新闻文本(标题 + 正文),判断它属于哪个类别。本节使用 AG News 数据集,典型样本如下:

  • 类别:Sci/Tech
  • 标题:Ky. Company Wins Grant to Study Peptides (AP)
  • 正文:AP - A company founded by a chemistry researcher at the University of Louisville won a grant to develop...

2 两种基础表示:字符级与词级

无论采用何种表示,流程都是:先将文本切分为token序列(一个 token 可以是字符、词、甚至是词的一部分),再把 token 映射为数字(通常借助词汇表 vocabulary),最后以 one-hot 编码送入神经网络。课程给出了两种基础粒度:

2.1 字符级表示(Character-level)

把每个字符当作一个数字处理。假设语料中共有C个不同字符,那么单词Hello会被表示为5×C 的张量:每个字母对应 one-hot 编码下张量的一列(该字符位置为 1,其余为 0)。

2.2 词级表示(Word-level)

对全部文本构建一个词汇表(vocabulary),然后用 one-hot 编码表示每个词。课程认为这种方案"某种程度上更好":单个字母本身几乎没有含义,而采用更高层的语义概念——词——能为神经网络简化任务。代价是字典规模大,需要处理高维稀疏张量。

这两种表示正是后续 BoW / TF-IDF / 嵌入方法的基础:one-hot 是"一次一词"的表示,而 Bag-of-Words 就是对 one-hot 向量做聚合。

3 N-gram:让表示捕获局部上下文

自然语言中,词的精确含义只能在上下文中确定。课程给出的经典例子:neural network(神经网络)与fishing network(渔网)含义完全不同。一种把上下文纳入表示的方法是基于词对构建模型——把相邻词对当作独立的词汇表 token。

句子I like to go fishing将表示为 token 序列:

I like, like to, to go, go fishing

这种方法的明显缺陷有两个:

  1. 字典规模急剧膨胀;若扩展为三元组(tri-gram)乃至 n 元组,即所谓n-gram,规模增长更快;
  2. 语义隔离:go fishing与go shopping虽然共享同一个动词go,却被编码为完全不同的 token,二者没有任何语义相似性。

此外课程指出,n-gram 也适用于字符级表示——此时 n-gram 大致对应不同的音节组合(syllabi),这对形态丰富的语言(如芬兰语、土耳其语)尤其有意义。

4 Bag-of-Words(词袋):固定长度向量表示

做文本分类时,我们最终需要一个固定长度的向量作为稠密分类器的输入。最简单的方法是把文本中所有词的表示聚合起来——例如直接相加。将每个词的 one-hot 向量相加,就得到频率向量:每一维记录对应词在文本中出现的次数。这就是Bag-of-Words(BoW)。

图注:BoW 本质上是文本中所有词 one-hot 向量的累加。图片来自课程 13-TextRep/README.md。

BoW 的价值在于:哪些词出现、出现多少次,本身就是内容的强指示器。政治新闻很可能包含president、country,科学论文则会出现collider、discovered等词——因此词频在多数场景下是内容的好信号。

但 BoW 有一个致命问题:and、is这类高频停用词几乎出现在所有文本中,拥有最高的频率,把真正重要的词掩盖了。解决思路是考虑"该词在整个文档集合中的出现频率"来降低其权重——这正是下一节 TF-IDF 的核心思想。

下图展示了词袋向量在内存中的实际形态:词汇表中每个词对应唯一位置,向量元素为该词在文档中的出现次数:

4.1 用 sklearn 快速构造 BoW

两个配套 notebook(TextRepresentationPyTorch.ipynb 与 TextRepresentationTF.ipynb)都演示了用 Scikit-Learn 构造 BoW:

from sklearn.feature_extraction.text import CountVectorizer vectorizer = CountVectorizer() corpus = [ 'I like hot dogs.', 'The dog ran fast.', 'Its hot outside.', ] vectorizer.fit_transform(corpus) vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray() # 输出形如:array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)

5 TF-IDF:用逆文档频率给词重新加权

TF-IDF(term frequency–inverse document frequency,词频–逆文档频率)是 BoW 的变体:不再用 0/1 或原始计数,而是用与语料中词出现频率相关的浮点权重。形式上,词i在文档j中的权重定义为:

$$ w_{ij} = tf_{ij}\times\log({N\over df_i}) $$

其中:

  • $tf_{ij}$:词i在文档j中的出现次数,即前文 BoW 的值;
  • $N$:文档集合中的文档总数;
  • $df_i$:整个集合中包含词i的文档数。

直观理解:$w_{ij}$ 随词在单篇文档中出现次数增加而增大,同时被"包含该词的文档数"抵消——如果一个词出现在每一篇文档中($df_i=N$),则 $w_{ij}=0$,该词被完全忽略。这样,a、in这类无处不在的词权重趋近于零,而专业术语获得高权重。

sklearn 实现同样一行即可:

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(ngram_range=(1, 2)) vectorizer.fit_transform(corpus) vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()

课程 notebook 特别指出:TF-IDF 虽为不同词提供了频率权重,但仍无法表示词的语义与顺序。真正捕获语义要等后续课程引入词嵌入(embeddings)与语言模型。

6 实战一:用 PyTorch 从零构建 BoW 文本分类器

TextRepresentationPyTorch.ipynb 给出了完整的端到端流程。任务设定:基于AG_NEWS数据集,把新闻标题分类到 4 个类别之一。

6.1 加载数据集

AG_NEWS 内置于torchtext模块,可直接加载:

import torch import torchtext import os import collections os.makedirs('./data', exist_ok=True) train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data') classes = ['World', 'Sports', 'Business', 'Sci/Tech']

数据集是迭代器,返回(类别编号, 文本)二元组;若需多次使用,应先转为 list:

train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data') train_dataset = list(train_dataset) test_dataset = list(test_dataset)

6.2 Tokenization 与词汇表构建

词级表示需要两步:分词(tokenizer)+构建词汇表(vocabulary)。使用 torchtext 内置的basic_english分词器与Counter统计词频:

tokenizer = torchtext.data.utils.get_tokenizer('basic_english') tokenizer('He said: hello') # ['he', 'said', 'hello'] counter = collections.Counter() for (label, line) in train_dataset: counter.update(tokenizer(line)) vocab = torchtext.vocab.vocab(counter, min_freq=1)

利用词汇表把 token 编码为数字。课程代码中 AG News 全量词汇表约95810词:

vocab_size = len(vocab) print(f"Vocab size if {vocab_size}") stoi = vocab.get_stoi() # dict: token -> index def encode(x): return [stoi[s] for s in tokenizer(x)] encode('I love to play with my words') # 例如 [599, 3279, 97, 1220, 329, 225, 7368]

6.3 实现 BoW 向量

在编码基础上累加计数即可:

def to_bow(text, bow_vocab_size=vocab_size): res = torch.zeros(bow_vocab_size, dtype=torch.float32) for i in encode(text): if i < bow_vocab_size: res[i] += 1 return res

调参提示(notebook 原文强调):vocab_size默认取全量词汇表,但可以人为调小以只保留最高频词。你会发现精度略有下降但并不剧烈,换来的是更高性能——这是控制高维稀疏表示规模的第一种手段。

6.4 训练单层 BoW 分类器

通过collate_fn参数把原始样本批处理为 BoW 张量,再定义一个单线性层 + LogSoftmax 的分类网络:

from torch.utils.data import DataLoader import numpy as np def bowify(b): return ( torch.LongTensor([t[0]-1 for t in b]), # 标签(AG News 编号从 1 开始,减 1 对齐到 0..3) torch.stack([to_bow(t[1]) for t in b]), # BoW 特征 ) train_loader = DataLoader(train_dataset, batch_size=16, collate_fn=bowify, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=16, collate_fn=bowify, shuffle=True) net = torch.nn.Sequential(torch.nn.Linear(vocab_size, 4), torch.nn.LogSoftmax(dim=1))

课程提供了标准训练循环(NLLLoss + Adam,可通过epoch_size限制训练量、report_freq控制进度打印)。在 notebook 中仅训练约 15000 个样本后,训练准确率已快速逼近86%(输出示例:12800: acc=0.85765625)。对 4 分类任务,这已是不错的基线。

7 实战二:用 TensorFlow/Keras 实现 BoW、自动计数与 TF-IDF

TextRepresentationTF.ipynb 以 TensorFlow Datasets 的ag_news_subset为数据源(训练集 120000 条、测试集 7600 条),全程围绕 KerasTextVectorization层展开。

7.1 环境准备与数据加载

import tensorflow as tf from tensorflow import keras import tensorflow_datasets as tfds # 按需增长 GPU 显存(多模型训练时避免显存溢出) physical_devices = tf.config.list_physical_devices('GPU') if len(physical_devices) > 0: tf.config.experimental.set_memory_growth(physical_devices[0], True) dataset = tfds.load('ag_news_subset') ds_train = dataset['train'] ds_test = dataset['test']

关于 GPU 显存的说明来自本节 5-NLP/README.md:训练大模型时若遇到显存不足,优先缩小 minibatch 大小;旧版 TensorFlow 在同一内核中连续训练多个模型可能不释放显存,上述set_memory_growth配置可缓解。

7.2 用 TextVectorization 分词并限定词汇表

课程强调:AG News 全量词汇超过10 万,而稀有的词模型几乎学不到东西,因此限制词汇表规模很有必要。TextVectorization通过max_tokens参数一步完成分词 + 建表:

vocab_size = 50000 vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size) vectorizer.adapt(ds_train.take(500).map(lambda x: x['title'] + ' ' + x['description']))

注意(notebook 原文):这里只用 500 条子集建表以加快执行;代价是有部分语料词未进词汇表、训练时被忽略。用全量数据adapt可小幅提升最终精度。

查看词汇表与编码结果:

vocab = vectorizer.get_vocabulary() print(vocab[:10]) # ['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for'] print(f"Length of vocabulary: {len(vocab)}") vectorizer('I love to play with my words') # <tf.Tensor shape=(7,), dtype=int64, ...>

注意前两个位置是保留位:''(padding/空位)与[UNK](未知词)——这是 Keras 向量化层与 torchtext 词汇表的一个实现差异。

7.3 两种 BoW 构造方式

方式一:手动累加 one-hot。把每个词编号转成 one-hot 再沿序列维求和:

def to_bow(text): return tf.reduce_sum(tf.one_hot(vectorizer(text), vocab_size), axis=0) to_bow('My dog likes hot dogs on a hot day.').numpy()

理解差异(notebook 原文提示):这里得到的向量长度等于基于整个 AG News 数据集的词汇表规模,而前面 sklearn 示例的词汇表是当场从样例文本构建的,所以两者向量维度与结果不同。

方式二:output_mode='count'自动计数。最新版 TensorFlow 允许向量化层直接输出计数向量,模型定义与训练显著简化:

model = keras.models.Sequential([ keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size, output_mode='count'), keras.layers.Dense(4, input_shape=(vocab_size,), activation='softmax') ]) print("Training vectorizer") model.layers[0].adapt(ds_train.take(500).map(extract_text)) model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc']) model.fit(ds_train.map(tupelize).batch(batch_size), validation_data=ds_test.map(tupelize).batch(batch_size))

7.4 把向量化层并入网络端到端训练

因为TextVectorization本身也是 Keras 层,可以把它放进网络内部,让原始字符串直接进网络:

def extract_text(x): return x['title'] + ' ' + x['description'] def tupelize(x): return (extract_text(x), x['label']) inp = keras.Input(shape=(1,), dtype=tf.string) x = vectorizer(inp) x = tf.reduce_sum(tf.one_hot(x, vocab_size), axis=1) out = keras.layers.Dense(4, activation='softmax')(x) model = keras.models.Model(inp, out) model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc']) model.fit(ds_train.map(tupelize).batch(batch_size), validation_data=ds_test.map(tupelize).batch(batch_size))

模型摘要显示各层参数量:text_vectorization(0 参数)、tf.one_hot(0 参数)、tf.math.reduce_sum(0 参数),唯一可训练的是输出层dense_2(21344 参数 = 5335 × 4 + 4)。端到端训练后验证准确率约87.4%,与手动map向量化的版本(约 87.0%)相当,但管线更简洁。

7.5 Keras 下自动计算 TF-IDF

把output_mode换成'tf-idf',即可让向量化层自动完成 TF-IDF 加权:

model = keras.models.Sequential([ keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size, output_mode='tf-idf'), keras.layers.Dense(4, input_shape=(vocab_size,), activation='softmax') ]) print("Training vectorizer") model.layers[0].adapt(ds_train.take(500).map(extract_text)) model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc']) model.fit(ds_train.map(tupelize).batch(batch_size), validation_data=ds_test.map(tupelize).batch(batch_size))

课程 notebook 的运行结果显示:手动 BoW 验证准确率约 86.97%,output_mode='count'约 87.72%,而TF-IDF 达到约 88.49%——可见去掉停用词干扰后分类效果进一步改善。

8 Bigram 与 N-gram 实践:为何需要嵌入降维

前文提到"hot dog"的含义与单独的hot、dog完全不同,若二者始终用同一向量会误导模型。解决方案是在词汇表中加入词对(bigram)。sklearn 中一行切换:

bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\b\w+\b', min_df=1) bigram_vectorizer.fit_transform(corpus) print("Vocabulary:\n", bigram_vectorizer.vocabulary_) bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()

输出词汇表会同时包含单词('hot'、'dog')与词对('hot dogs'、'dog ran'等),向量的某些维度来自词对计数。

但代价极其可观:课程在 AG News 上用torchtext.data.utils.ngrams_iterator构建 bigram 词汇表时,长度高达1,308,842(超过 130 万);TF 版本同样指出 bigram 词汇超过 130 万 token。如此高维的稀疏向量直接训练分类器是内存低效的。两个 notebook 都给出两条出路:

  1. 调高min_freq:只保留出现足够多次的 n-gram,显著降维。PyTorch 版本注释提示:"把min_freq设为更高值,观察词汇表长度变化";TF 版本则建议给 bigram 也设定合理的max_tokens上限。
  2. 引入嵌入(embeddings):这是下一课(14-Embeddings)的主题——用低维稠密向量代替高维稀疏 one-hot,从根本上解决维度爆炸。

9 课后实践:用自己的数据集重跑 notebook

课程为 13-TextRep 配套了独立 assignment.md:使用本节两个 notebook(PyTorch 或 TensorFlow 任一版本),换成你自己的数据集重新运行。建议:

  • 数据集可从 Kaggle 获取并注明出处(attribution);
  • 尝试有创意的数据集,notebook 原文举例了 NUFORC 的UFO 目击报告数据集——这类非常规语料往往能带来意想不到的发现;
  • 改写 notebook,突出你自己的发现(例如词频分布、类别不平衡、n-gram 阈值对精度的敏感度)。

依赖安装参考本节 5-NLP/README.md:PyTorch 路线执行pip install -r requirements-pytorch.txt,TensorFlow 路线执行pip install -r requirements-tf.txt(对应文件位于 5-NLP/requirements-pytorch.txt 与 5-NLP/requirements-tf.txt)。

10 小结与下一步

表示方法维度是否考虑上下文主要缺点一句话适用场景
字符级 one-hot字符数 × 序列长否无语义、维度高需处理拼写/形态变体的底层任务
词级 one-hot词汇表大小否高维稀疏理论铺垫,实际很少直接用
N-gram词汇表(含组合)部分(局部)维度爆炸、词对语义隔离配合降维或少量局部上下文
BoW词汇表大小否停用词掩盖关键词简单文本分类基线
TF-IDF词汇表大小否仍无语义与顺序文本检索、分类的经典特征

正如课程结尾引用的语言学家 J. R. Firth(1935)所言:"一个词的完整意义总是由语境决定的,脱离语境研究意义是不可取的。"BoW、N-gram 与 TF-IDF 都只能为词附加频率权重,却无法表达含义与顺序。因此,下一步的学习路径是清晰的:

  1. 14-Embeddings:用稠密向量表示词义,解决稀疏高维问题;
  2. 15-LanguageModeling 与 16-RNN:从文本中捕获上下文与顺序信息;
  3. 直至 18-Transformers 等现代架构——它们正是"语境即意义"这一思想在工程上的完整落地。

本节全部资源:课程讲义 13-TextRep/README.md、PyTorch notebook TextRepresentationPyTorch.ipynb、TensorFlow notebook TextRepresentationTF.ipynb,以及配套作业 assignment.md。

  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

【免费下载链接】AI-For-Beginners

12 Weeks, 24 Lessons, AI for All!

项目地址:https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners
点击查看免费下载

相关推荐

上一篇:如何在Windows上实现高效屏幕标注?ppInk免费开源标注工具终极指南
下一篇:HMCL启动器:5分钟掌握Minecraft跨平台游戏管理终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询