☰
用自定义数据集重跑 AI-For-Beginners 文本表示 Notebook:从 BoW 到 TF-IDF 的完整实战指南
2026/10/2 2:20:12 网站建设 项目流程
  • 教程
  • 人工智能
  • 机器学习
  • 深度学习

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

12 Weeks, 24 Lessons, AI for All!

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

本指南对应 AI-For-Beginners 课程第 13 课(Text Representation)的作业任务:基于课程附带的 PyTorch 或 TensorFlow 文本表示 Notebook,更换为你自己的数据集(如 Kaggle 上的 UFO 目击记录)重新运行,并改写 Notebook 突出你的独立发现。读完本文,你将掌握文本分类任务中从分词、构建词表,到 Bag-of-Words、N-gram 与 TF-IDF 三种向量化表示的完整实现路径,以及如何把官方 Notebook 改造为一份可复用、可署名、可复现的个人实验记录。

作业任务解读:这不是"跑通就完事"的练习

作业原文(assignment.md)的核心要求有三条,任何一条都不应省略:

  1. 重跑官方 Notebook:使用本课附带的 TextRepresentationPyTorch.ipynb 或 TextRepresentationTF.ipynb,用你自己的数据集替代原数据集执行一遍完整流程;
  2. 署名与数据来源:若数据集来自 Kaggle 等平台,必须保留恰当的 attribution(数据提供者、许可说明);
  3. 改写而非照抄:对 Notebook 进行重构,加入你自己的观察、结论与实验对比,并鼓励尝试有冲击力的创新数据集,例如 NUFORC 提供的 UFO 目击记录数据集——这类文本天然带有"时间、地点、形状描述、持续时长"等结构化噪声,非常考验向量化表示的区分能力。

要完成这个作业,首先需要理解 Notebook 背后的课程知识体系。本课是 NLP 专题的第一课,目标是在 AG News 新闻分类 这类任务上建立"把文本变成张量"的核心能力:整条链路由 分词(Tokenization)→ 词表(Vocabulary)→ 向量化(BoW / N-gram / TF-IDF)→ 分类器训练 组成。

预备知识:文本如何被表示为张量

在把文本送入神经网络之前,必须回答一个基础问题:文本如何变成数值张量?课程 README 给出了两条经典路径:

  • 字符级表示:把每个字符映射为一个数字(类似 ASCII/UTF-8 的编码思想)。假设语料中有 C 种不同字符,单词Hello会被表示为 5×C 的张量,每个字母对应 one-hot 编码中的一列。缺点是单个字符本身几乎不含语义信息。
  • 词级表示:先建立包含语料中所有词的词表(vocabulary),再用 one-hot 编码表示每个词。词的语义密度远高于字符,能显著简化神经网络的学习任务;但代价是词表规模庞大,产生高维稀疏张量。

无论选择哪种表示,第一步都是把文本切分成令牌(token)序列——一个 token 可以是一个字符、一个词,甚至半个词——然后通过词表把 token 映射为整数索引。

从分词到词表:Notebook 中的具体实现

以 TextRepresentationPyTorch.ipynb 为例,官方实现分两步走:

第一步:分词。使用 torchtext 自带的basic_englishtokenizer,把句子切成 token 列表:

tokenizer = torchtext.data.utils.get_tokenizer('basic_english') tokenizer('He said: hello') # 输出: ['he', 'said', 'hello']

第二步:构建词表。遍历训练集,用collections.Counter统计每个 token 的出现频次,再交给torchtext.vocab.vocab生成词表:

counter = collections.Counter() for (label, line) in train_dataset: counter.update(tokenizer(line)) vocab = torchtext.vocab.vocab(counter, min_freq=1)

词表建好后,通过vocab.get_stoi()拿到"token → 索引"的映射字典,即可把任意文本编码为一串整数:

stoi = vocab.get_stoi() 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]

在官方 AG News 数据集上,仅min_freq=1就产生了95810大小的词表——这是后面所有"维度爆炸"问题的起点,也是作业中值得记录的第一组观察数据。

Bag-of-Words:最经典的定长向量表示

BoW 的核心思想极其朴素:忽略词序,只统计每个词在文档中出现的次数。你可以把它理解为所有词的 one-hot 向量之和。为什么这能用于分类?因为词频往往暗示主题——新闻里出现president、country更可能是政治类,出现collider、discovered更可能是科技类(见 README)。

Notebook 给出了两种实现路径。一是 sklearn 的CountVectorizer,适合快速验证:

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)

二是基于自建词表的 PyTorch 版本,把编码后的索引逐个累加到定长向量上:

vocab_size = len(vocab) 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 原文中的 Note):词表通常很大,可以只保留最高频的前 N 个词。试着调低vocab_size再跑分类器,观察准确率的变化——预期会有一定下降,但不会断崖式下跌,这是用精度换性能的经典取舍。这条实验值得原样写进你的作业里。

在 BoW 之上训练一个线性分类器

拿到定长 BoW 向量后,训练分类器就变得很简单。官方 Notebook 通过collate_fn把原始数据批处理为 BoW 张量:

from torch.utils.data import DataLoader def bowify(b): return ( torch.LongTensor([t[0]-1 for t in b]), torch.stack([to_bow(t[1]) for t in b]) ) 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)

分类器本身只是"线性层 + LogSoftmax":

net = torch.nn.Sequential( torch.nn.Linear(vocab_size, 4), # 输入=词表大小,输出=类别数(4) torch.nn.LogSoftmax(dim=1) )

训练循环使用标准 PyTorch 流程(optimizer.zero_grad()→ 前向 →NLLLoss→backward→optimizer.step()),并支持用epoch_size限制训练量、用report_freq控制进度打印。官方在 15000 条样本上训练即可观察到准确率从 80.2%(3200 条)爬升到 85.8%(12800 条)左右——这组中间指标恰好是作业中"展示自己发现"的优秀模板:记录不同训练量下的准确率曲线。

N-gram:解决"多词短语"难题

BoW 的明显缺陷是忽略上下文:hot dog(热狗)与hot、dog分开出现时的含义完全不同,而 BoW 始终把它们当作相同向量。N-gram 的思路是把相邻词的组合也加入词表,例如 bigram 会把I like,like to,to go,go fishing作为独立 token(见 README)。

sklearn 中通过ngram_range=(1, 2)一行实现:

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

PyTorch Notebook 则用 torchtext 的ngrams_iterator统计 bigram 词表。它的代价在作业里值得被重点记录:官方 AG News 训练集上的 bigram 词表长度高达 1308842——比 unigram 词表膨胀了十几倍。Notebook 给出的缓解建议是:把min_freq调高,只保留出现次数足够多的 n-gram,可显著压缩维度;真正彻底的解法是引入下一课要讲的Embedding(词嵌入)做降维。

TF-IDF:给词频加上"稀有度"权重

BoW 对每个词一视同仁,但a、in这类高频虚词对分类几乎毫无帮助。TF-IDF(term frequency–inverse document frequency)就是为此设计的加权方案。记词 $i$ 在文档 $j$ 中的权重为 $w_{ij}$:

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

其中 $tf_{ij}$ 是词 $i$ 在文档 $j$ 中的出现次数(即 BoW 值),$N$ 是语料文档总数,$df_i$ 是包含词 $i$ 的文档数。直观理解:词在单篇文档中出现越多权重越高,但它在越多文档里出现则权重被压低。极端情况下,一个词出现在全部文档中($df_i = N$),则 $w_{ij} = 0$,被完全忽略——这正是and、is等虚词的命运(见 README 与 Notebook 的 TF-IDF 章节)。

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() # 输出的是浮点权重而非整数计数

注意输出的已经不是 0/1 或整数频次,而是浮点权重向量——这正是"用频率权重区分词的重要程度"这一思想的直接体现。

使用自己的数据集重跑:五个实操步骤

综合作业要求与 Notebook 结构,推荐按以下步骤改造:

步骤 1:选数据集并处理署名。优先选择文本分类友好的数据集。作业点名的 NUFORC UFO 目击记录数据集就是好选择:每条记录含目击地点、形状描述、事件描述等自由文本,分类目标可以是"目击形状"或"事件类别"。若数据来自 Kaggle,请在 Notebook 开头(或项目 README)显式注明数据集名称、来源与许可信息。

步骤 2:替换数据加载段。官方 Notebook 依赖torchtext.datasets.AG_NEWS这一行加载数据。替换为读取你自己的数据文件(如 CSV),并把数据规范成(label, text)的二元组序列——这是后续所有代码唯一的接口约定:

import pandas as pd df = pd.read_csv('your_dataset.csv') train_dataset = list(zip(df['label'], df['text'])) test_dataset = list(zip(df['label_test'], df['text_test'])) # 注意保持 (label, text) 顺序,与 AG_NEWS 返回结构一致

如果类别数不是 4,记得同步修改classes列表和torch.nn.Linear(vocab_size, 4)中的输出维度。

步骤 3:重建词表与编码。分词器和collections.Counter统计逻辑完全复用,无需改动;但请重点实验min_freq与vocab_size裁剪对模型效果的影响,并把对比结果写进你的发现。

步骤 4:依次重跑三种表示并对比。对同一个数据集分别用 BoW、Bigram、TF-IDF 训练分类器,记录各自的最佳准确率、词表大小与训练耗时。这就是作业要求的"underlines your own findings"最直接的素材来源。

步骤 5:改写 Notebook 结构。在每节末尾追加"Observation"型 Markdown 单元格,记录:数据分布(类别是否均衡)、词表规模与裁剪策略、三种表示的性能对比、失败案例分析(如 UFO 描述中地名/时间词被当成分类特征)。这会让你的 Notebook 从"复现"升级为"研究"。

双框架对照:TensorFlow 版本的差异点

如果你选择 TextRepresentationTF.ipynb,整体流程相同,但 API 形态不同,替换数据集时需注意:

  • 分词与建表合二为一:TF 版本用 Keras 的TextVectorization层一步完成分词与词表构建,adapt()方法扫描全部文本后即可建立词表;
  • 三种表示靠output_mode切换:默认(multi-hot/embedding 输入)、output_mode='count'(等价 BoW)、output_mode='tf-idf'(等价 TF-IDF);
  • bigram 通过ngrams参数启用:构造TextVectorization时传入 n-gram 参数即可,官方 Note 同样提醒 bigram 词表会膨胀到 130 万 token 以上,必须设置合理的max_tokens上限;
  • 模型与训练:使用sparse_categorical_crossentropy损失 +adam优化器 +acc指标编译,配合嵌入层或池化层组合成分类网络。

课程为两个框架分别提供了依赖清单:requirements-tf.txt(注意实际路径为 requirements-tf.txt)与 requirements-pytorch.txt,重跑前可参考安装。

结语:清楚知道这些方法的边界

本课所有方法(BoW、N-gram、TF-IDF)的共同边界在于:它们能给词加权,却无法表达词义与词序。正如语言学家 J. R. Firth 在 1935 年所言:"一个词的完整意义永远是上下文相关的,脱离上下文研究意义是不严肃的。" 课程后半段将通过语言建模与 Embedding 来捕获上下文信息(见 README 结论部分)。因此,你的作业如果能明确写出"哪种数据集下 TF-IDF 明显优于 BoW、哪种情况下三者都失效",就已经精准踩到了这门课的进阶门坎。

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

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

12 Weeks, 24 Lessons, AI for All!

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

相关推荐

上一篇:lo 并发工具详解:用 parallel.Times 并行生成切片数据的 Go 泛型实践
下一篇:Kubo v0.26 版本解析:废弃命令清理、可选 Pin 名称与 OTLP 追踪迁移

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

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

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

立即咨询