- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
本指南面向 AI-For-Beginners 课程 NLP 章节的13-TextRep单元,围绕其课后任务(Assignment: Notebooks)展开:任务要求学习者基于本课附带的 TextRepresentationPyTorch.ipynb 或 TextRepresentationTF.ipynb,换成自己的数据集重新运行,改写 Notebook 以突出自己的发现,并尝试一些可能带来意外洞察的新颖数据集。读完本文,你将掌握文本表示(Token 化、词表构建、Bag-of-Words、N-Gram、TF-IDF)在 PyTorch 与 TensorFlow 两条技术路线下的完整实现,并能独立完成"换数据集重跑 + 改造记录发现"的完整实战闭环。
1. 任务解读:这一课后作业到底要求什么
原作业(lessons/5-NLP/13-TextRep/assignment.md)的要求非常明确,可以拆解为四个动作:
- 用本课附带的两份 Notebook 之一(PyTorch 版或 TensorFlow 版)作为起点;
- 用自己的数据集重新运行——数据集可以来自 Kaggle 等平台,但必须正确标注来源(proper attribution);
- 改写 Notebook,把重点落在展示你个人的观察与结论上,而不是简单复现原示例;
- 尝试创新数据集,例如 NUFORC 的 UFO 目击事件记录数据集,这类"非典型"文本往往能暴露常规新闻分类管线之外的盲点。
换句话说,这个作业不是"照着跑一遍"的体力活,而是要求你把本课讲义中讲到的文本表示技术栈(字符级/词级表示、词袋、N-Gram、TF-IDF)真正迁移到新数据上,并用实验结果说话。
课程讲义背景可参考 13-TextRep 单元主页:本单元聚焦文本分类(text classification),默认数据集是 AG News 新闻标题四分类(World / Sports / Business / Sci/Tech)。作业正是要求你脱离这个默认数据集,检验同一套方法在新数据上的泛化表现。
2. 动手前的准备:环境与依赖
2.1 安装依赖
NLP 章节提供了两份依赖清单,按你选择的框架安装其一即可:
- PyTorch 路线:lessons/5-NLP/requirements-pytorch.txt,包含
torch、torchtext、torchvision、scikit-learn、nltk、gensim等; - TensorFlow 路线:lessons/5-NLP/requirements-tf.txt,包含
TensorFlow、TensorFlow_datasets、TensorFlow_text、scikit-learn等。
# PyTorch 版 pip install -r lessons/5-NLP/requirements-pytorch.txt # 或 TensorFlow 版 pip install -r lessons/5-NLP/requirements-tf.txt2.2 GPU 注意事项
lessons/5-NLP/README.md 明确给出了本章节的 GPU 使用建议:
- 训练较大模型时建议使用 GPU 环境以减少等待;
- 若遇显存不足,优先减小 minibatch 大小;
- 旧版 TensorFlow 在同一 Python 内核中训练多个模型时可能不释放显存,可显式开启按需增长:
physical_devices = tf.config.list_physical_devices('GPU') if len(physical_devices)>0: tf.config.experimental.set_memory_growth(physical_devices[0], True)这一段代码同样出现在 TextRepresentationTF.ipynb 的开头,属于官方推荐的标准配置。
3. 原 Notebook 的技术管线:换数据前先吃透骨架
无论选 PyTorch 还是 TensorFlow 版本,两份 Notebook 都遵循同一套六段式管线。先把它吃透,换数据时才不会盲目。
3.1 数据加载与格式认知
PyTorch 版使用torchtext内置的AG_NEWS数据集,返回(label, text)二元组:
import torch, torchtext, os, collections os.makedirs('./data', exist_ok=True) train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data') classes = ['World', 'Sports', 'Business', 'Sci/Tech']注意:torchtext数据集对象是迭代器,想要重复使用必须先转成 list(Notebook 中专门强调了这一点):
train_dataset, test_dataset = torchtext.datasets.AG_NEWS(root='./data') train_dataset = list(train_dataset) test_dataset = list(test_dataset)TensorFlow 版使用 TensorFlow Datasets 的ag_news_subset:
import tensorflow as tf from tensorflow import keras import tensorflow_datasets as tfds dataset = tfds.load('ag_news_subset') ds_train = dataset['train'] # 120000 条 ds_test = dataset['test'] # 7600 条TF 版的样本是字典结构(title、description、label),打印前 5 条可以看到3 (Sci/Tech)、1 (Sports)等标签。
换数据的关键启示:这一步决定了你要做多少适配工作——如果自定义数据集的结构与上述两种格式不一致(例如是 CSV、JSON,字段名不同,标签不是整数),就需要额外写一个解析器把它转成(label, text)或字典结构。
3.2 Token 化与词表构建
文本要喂给神经网络,必须先从字符序列变成数字。两步走:
- 用tokenizer把文本切成 token;
- 用vocabulary(词表)把 token 映射为整数下标。
PyTorch 版:
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) stoi = vocab.get_stoi() # token -> index def encode(x): return [stoi[s] for s in tokenizer(x)]在这个实现里,AG News 词表大小约为 95810(Notebook 实际打印为Vocab size if 95810)。
TensorFlow 版用TextVectorization层一步完成:
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']))注意 TF 版的两处工程细节:
- 词表大小上限:AG News 原始词表超过 10 万词,但低频词模型学不到东西,因此用
max_tokens截断到 5 万; - 只取 500 条子集做
adapt:为了加快运行,Notebook 刻意只用部分数据建词表,代价是部分词会落入[UNK],Notebook 明确指出"用全量数据跑adapt会提高最终准确率,但提升并不显著"。
这给了换数据时的两条实用经验:词表要截断、建词表可以只抽样。
3.3 Bag-of-Words(词袋)表示
词袋的核心思想:词序不重要,词的(出现)频率才是内容信号。一份文档的 BoW 向量 = 词表中每个词在该文档中出现的次数;也可以理解为"所有单词 one-hot 向量之和"。
先用 scikit-learn 快速体验(TextRepresentationPyTorch.ipynb 与 TF 版都包含这段):
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]])在 PyTorch 中手工实现 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 resTensorFlow 版则用tf.one_hot+tf.reduce_sum实现同样的逻辑:
def to_bow(text): return tf.reduce_sum(tf.one_hot(vectorizer(text), vocab_size), axis=0)更现代的做法是直接让TextVectorization输出计数向量:
keras.layers.experimental.preprocessing.TextVectorization( max_tokens=vocab_size, output_mode='count')值得注意的差异:TF 版 Notebook 特意对比了 scikit-learn 与 Keras 两种 BoW 结果不一致的原因——前者词表来自当前示例语料,后者词表来自整个 AG News 数据集,向量维度完全不同。这说明BoW 向量高度依赖词表来源,换数据集时词表必须重建。
3.4 在 BoW 上训练分类器
PyTorch 版:把bowify作为collate_fn传给标准DataLoader,即可把 batch 里的每条文本实时转成 BoW 向量:
from torch.utils.data import DataLoader def bowify(b): return ( torch.LongTensor([t[0]-1 for t in b]), # 标签(AG News 标签从 1 开始,故减 1) 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))训练循环使用标准 PyTorch 写法(Adam 优化器 + NLLLoss + 按report_freq频率打印累计准确率),Notebook 用epoch_size=15000只训练了不到一个 epoch,训练侧准确率即可到 86% 左右。
TensorFlow 版:用map把数据集批量转成 BoW,再定义一个单线性层模型:
batch_size = 128 ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']), x['label'])).batch(batch_size) ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']), x['label'])).batch(batch_size) model = keras.models.Sequential([ keras.layers.Dense(4, activation='softmax', input_shape=(vocab_size,)) ]) model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['acc']) model.fit(ds_train_bow, validation_data=ds_test_bow)TF 版运行结果中,BoW + 单层 Dense 在验证集上达到约 87% 的准确率。Notebook 评价:"四分类问题达到 80% 以上即算不错的结果"。
TF 版还展示了一个进阶技巧:因为TextVectorization本身也是 Keras 层,可以把它和分类头一起放进同一个keras.Model,端到端训练,省掉map预转换:
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'])3.5 N-Gram:为多词表达建模
BoW 的局限在于:像hot dog这类多词表达,拆成hot和dog两个独立词会丢失语义。解法是把相邻词对(bigram)甚至三词组合(trigram)也纳入词表:
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\b\w+\b', min_df=1)PyTorch 版用torchtext的ngrams_iterator构建 bigram 词表:
counter = collections.Counter() for (label, line) in train_dataset: l = tokenizer(line) counter.update(torchtext.data.utils.ngrams_iterator(l, ngrams=2)) bi_vocab = torchtext.vocab.vocab(counter, min_freq=1)实验数据点:AG News 的 bigram 词表长度高达1308842(约 131 万)——这揭示了 N-Gram 路线的核心痛点:词表爆炸。两份 Notebook 都给出了实操建议:
- 用
min_freq过滤掉出现次数过少的 n-gram,显著降低维度; - N-Gram 表示必须配合后续课程讲到的嵌入(embeddings)降维才有实用价值;
- 在 TF 版中可给
TextVectorization传ngrams参数,并用max_tokens限制 bigram 数量。
3.6 TF-IDF:压制高频泛词
BoW 对每个词一视同仁,导致a、in这类几乎出现在所有文档里的词占满权重。**TF-IDF(词频-逆文档频率)**用浮点权重替代 0/1 计数,词 $i$ 在文档 $j$ 中的权重定义为:
$$w_{ij} = tf_{ij}\times\log\left({N \over df_i}\right)$$
其中 $tf_{ij}$ 是词 $i$ 在文档 $j$ 中出现的次数(即 BoW 值),$N$ 是文档总数,$df_i$ 是包含词 $i$ 的文档数。直观理解:一个词出现在所有文档里时 $df_i=N$,权重归零,被完全忽略。
scikit-learn 一行搞定:
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(ngram_range=(1, 2))Keras 版直接改output_mode='tf-idf':
keras.layers.experimental.preprocessing.TextVectorization( max_tokens=vocab_size, output_mode='tf-idf')TF 版给出了可对比的实测结果:在相同配置下,BoW(output_mode='count')验证准确率约 87.7%,TF-IDF 提升到约88.5%——这正是换数据时可以复现的关键对照实验。
3.7 理论边界
无论是 BoW 还是 TF-IDF,都只刻画了"频率",无法表达语义与顺序。Notebook 结尾引用了语言学家 J. R. Firth 1935 年的名言:"一个词的完整意义永远是语境化的,脱离语境研究意义是不可取的。" 这正是后续语言建模、嵌入章节要解决的问题。
4. 换用自定义数据集:分步改造指南
这是作业的核心动作。下面给出从"拿到新数据"到"跑通改造版 Notebook"的完整路径。
4.1 选数据与合规
- 来源标注:作业明确要求数据来自 Kaggle 等平台时必须正确标注来源(attribution),在 Notebook 的 markdown 单元格里写明数据集名称、作者与下载地址;
- 推荐方向:作业点名 NUFORC 的 UFO 目击记录(sightings)数据集。这类数据集的特点恰恰是"反常规"——文本是目击者的非结构化描述,长短差异极大、拼写随意、大量地名与日期、类别维度与新闻四分类完全不同,能暴露出文本表示管线的真实短板;
- 自备数据:任何"文本 + 标签"结构的 CSV/JSON 都可以,例如邮件垃圾分类、商品评论情感分类、客服工单意图分类。
4.2 数据格式适配(PyTorch 路线)
把自定义数据集整理成(label, text)二元组列表,替换掉torchtext.datasets.AG_NEWS(...)这一行:
import csv rows = [] with open('my_dataset.csv', encoding='utf-8') as f: reader = csv.reader(f) next(reader) # 跳过表头 for label, text in reader: rows.append((int(label), text)) # 划分训练/测试 split = int(len(rows) * 0.8) train_dataset, test_dataset = rows[:split], rows[split:]后续tokenizer、vocab、encode、to_bow、bowify、DataLoader全部原样复用——这段管线对数据格式不敏感,是标准化的。
4.3 数据格式适配(TensorFlow 路线)
把tfds.load('ag_news_subset')换成从文本构建tf.data.Dataset:
import tensorflow as tf def load_csv(path): ds = tf.data.experimental.CsvDataset( path, [tf.int32, tf.string], header=True ) return ds.map(lambda label, text: {'label': label, 'text': text}) ds_train = load_csv('train.csv').shuffle(10000) ds_test = load_csv('test.csv')然后把原 Notebook 中的字段访问从x['title']+' '+x['description']改为x['text'],其余TextVectorization、to_bow、model.fit逻辑不变。
4.4 词表与向量的必改项
换数据后以下参数几乎必然需要调整,这是最容易踩坑的地方:
| 参数 | 原默认值(AG News) | 换数据后的处理策略 |
|---|---|---|
max_tokens/vocab_size | 5 万(TF)/ 约 9.6 万(PT) | 先打印新词表长度,再设一个合理截断值 |
min_freq | 1 | 文本量大可提高到 2~3,压制噪声 token |
ngrams | 1 | 若数据集多词表达多,可试(1,2)并观察词表膨胀 |
output_mode | 无 | 对比'count'(BoW)与'tf-idf'的准确率差 |
| 标签编码 | 4 类整数 | 新分类任务的类别数要同步改Dense/Linear输出维度 |
PyTorch 版 Notebook 给出了一个非常值得换数据时复现的对照实验:降低vocab_size(只用最高频词)会带来轻微准确率下降,但显著提升性能——你可以在新数据集上画出"词表大小 vs 准确率"的曲线。
5. 改写 Notebook:把"发现"变成文章的骨架
作业要求"rewrite the notebook to underline your own findings"。建议按以下结构重构 markdown 单元格:
- 数据卡片:数据集名称、来源、规模、类别分布(用
collections.Counter统计标签); - 观察 1:词表画像——打印新数据集的词表大小、top 10 高频词,与 AG News 对比。UFO 数据集大概率会出现大量地名、日期、数字 token;
- 观察 2:BoW 分类基线——记录 BoW 准确率,并指出哪些高频词其实是噪声;
- 观察 3:N-Gram 实验——bigram 词表膨胀到什么程度?
min_freq提上去后维度降了多少? - 观察 4:TF-IDF 增益——对比
output_mode='count'与'tf-idf'的准确率差异,能否复现约 1 个百分点的提升; - 观察 5:失败案例——从测试集挑 2~3 条分类错误的样本,分析 BoW/TF-IDF 为什么搞不定它们(例如否定句、罕见表达、词序敏感句子),自然引出"需要语义建模"的结论。
每一节实验都要给出数字与代码输出,这正是"highlight your own findings"的含义。
6. 作业自检清单
完成改造后,对照以下清单确认交付质量:
- 数据来源已在 Notebook 中明确标注(attribution);
- Notebook 可在新数据上端到端跑通(从加载到评估);
- 词表、类别数、
max_tokens等参数已按新数据调整; - 至少完成 BoW 与 TF-IDF 的准确率对比;
- 记录了 N-Gram 词表膨胀与
min_freq截断的效果; - 有 2~3 条对失败案例的分析,指出了频率式表示的局限;
- 没有简单照抄原 Notebook 结论,而是用自己的实验数据说话。
完成这组实验,你不仅完成了 assignment.md 的要求,也彻底掌握了文本表示这层地基——它是后续 14-Embeddings(嵌入降维)、15-LanguageModeling(语言建模)等课程继续深入的直接前提。
- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
相关推荐
用自定义数据集重跑 AI-For-Beginners 文本表示 Notebook:从 BoW 到 TF-IDF 的完整实战指南
用自定义数据集重跑 AI For Beginners 文本表示 Notebook:从 BoW 到 TF IDF 的完整实战指南 本指南对应 AI For Beg
教程人工智能机器学习深度学习SuperAgent 单元测试代码覆盖率: Istanbul 与 Codecov 集成
SuperAgent 单元测试代码覆盖率: Istanbul 与 Codecov 集成 引言:为什么代码覆盖率至关重要 在现代软件开发中,单元测试是保证代码质量
教程人工智能机器学习深度学习AI-For-Beginners 文本表示实战:用 PyTorch/TensorFlow 笔记本复跑自定义数据集,完成 UFO 目击文本分类
AI For Beginners 文本表示实战:用 PyTorch/TensorFlow 笔记本复跑自定义数据集,完成 UFO 目击文本分类 本文对应课程 13
教程人工智能机器学习深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考