ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI-For-Beginners 文本表示实战:将文本转为张量的字符级编码、N-gram、BoW 与 TF-IDF 全解析

AI-For-Beginners 文本表示实战:将文本转为张量的字符级编码、N-gram、BoW 与 TF-IDF 全解析 教程人工智能机器学习深度学习【免费下载链接】AI-For-Beginners12 Weeks, 24 Lessons, AI for All!项目地址https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners点击查看免费下载本文是 AI-For-Beginners 课程「自然语言处理NLP」第 13 课的实战解析。围绕该课配套文档 translations/bn/lessons/5-NLP/13-TextRep/README.md 与英文原版 lessons/5-NLP/13-TextRep/README.md 的核心脉络结合仓库内 PyTorch 笔记与 TensorFlow 笔记 的源码级实现系统讲解「如何把自然语言文本表示为神经网络可消费的张量」。读完本文你将掌握分词Tokenization与词表Vocabulary构建流程、字符级与词级表示的区别、N-gram 的原理与代价、Bag-of-WordsBoW与 TF-IDF 的公式推导与工程实现并能直接用 PyTorch 或 TensorFlow/Keras 训练出一个基于文本表示的新闻分类器。任务背景以 AG News 文本分类为入口本课关注的核心任务是文本分类Text Classification。文本分类是 NLP 中最典型的分类问题比如把电子邮件分为垃圾邮件/正常邮件把文章分为体育、商业、政治等类别在构建聊天机器人时我们还需要做意图分类intent classification判断用户想表达什么这时往往要面对非常多的类别。课程的配套实验统一使用AG News数据集。该数据集中包含如下类型的新闻条目以科学/技术类新闻为例类别CategorySci/Tech科学/技术标题TitleKy. Company Wins Grant to Study Peptides (AP)正文BodyAP - A company founded by a chemistry researcher at the University of Louisville won a grant to develop...我们的目标就是根据新闻的标题与正文文本把这条新闻自动归类到预定义的类别中。在 PyTorch 笔记 中AG News 被划分为 World世界、Sports体育、Business商业、Sci/Tech科学/技术四个类别在 TensorFlow 笔记 中通过tensorflow_datasets加载的ag_news_subset同样包含 120,000 条训练样本与 7,600 条测试样本。从这两份笔记可以看到使用简单的词袋特征加单层线性分类器即可在四分类任务上取得 84%88% 的准确率——这正是「文本表示」这一基础环节的威力。为什么文本必须表示为张量如果想要用神经网络解决 NLP 任务就必须找到一种把文本表示为**张量Tensor**的方法。计算机本身早已用编码如 ASCII、UTF-8把文本字符映射为数字进而映射为屏幕上显示的字体但我们人类能理解每个字母「代表什么」以及字符如何组合成单词、单词如何组成句子计算机自身并不具备这种理解神经网络的语义理解能力必须在训练过程中通过学习获得。为此文本表示存在两条基本路线字符级表示Character-level Representation把每个字符当作一个数字。假设语料库中有C个不同的字符那么单词Hello将被表示为一个 5×C的张量每个字符对应 one-hot 编码中的一个张量列。字符级表示的好处是词表极小只需覆盖所有字符但字符本身携带的语义信息太少模型需要自己从字符拼凑出词义。词级表示Word-level Representation先为文本中的所有词建立词表Vocabulary再用 one-hot 编码表示每个词。因为单词是更高层级的语义概念这种方式能显著简化神经网络的学习任务但词表往往很大导致我们不得不面对高维稀疏张量high-dimensional sparse tensors带来的存储与计算压力。无论采用哪种表示处理流程的第一步都是把文本转换为token令牌序列——一个 token 可以是一个字符、一个单词甚至是一个单词的一部分子词。接着利用词表把 token 映射为一个数字这个数字经过 one-hot 编码后就可以送入神经网络。从 PyTorch 笔记 的实现看这一「分词 → 建词表 → 编码」流水线正是文本表示的标准三步import torch import torchtext import os import collections os.makedirs(./data, exist_okTrue) train_dataset, test_dataset torchtext.datasets.AG_NEWS(root./data) classes [World, Sports, Business, Sci/Tech] # 1. 分词basic_english 分词器 tokenizer torchtext.data.utils.get_tokenizer(basic_english) tokenizer(He said: hello) # [he, said, hello] # 2. 建词表统计全量训练文本中所有 token 的出现次数 counter collections.Counter() for (label, line) in train_dataset: counter.update(tokenizer(line)) vocab torchtext.vocab.vocab(counter, min_freq1) # 3. 编码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 全量训练集上构建的词表规模约为95,810个 token笔记中的运行输出为Vocab size is 95810。而 TensorFlow 笔记 使用 Keras 的TextVectorization层完成等价工作其词表前 10 位依次为[, [UNK], the, to, a, in, of, and, on, for]——注意[UNK]是「未登录词」占位符是空串占位符词表总是从高频停用词开始排列。N-gram让模型看见局部上下文在自然语言中单词的精确含义只有在上下文中才能确定。例如neural network神经网络与fishing network渔网中的network含义完全不同。一种朴素的解决方案是以词对word pairs为单位建立模型把词对视为独立的词表 token。于是句子I like to go fishing将被表示成如下 token 序列I like→like to→to go→go fishing这种做法的代价十分明显词表规模会急剧膨胀而且go fishing与go shopping虽然是不同的 token却共享同一个动词go二者之间没有任何语义相似性可言。在某些场景下还可以考虑三个词的组合——tri-gram三元组因此这一系列方法被统称为N-gram。此外在字符级表示上使用 N-gram 同样是合理的此时 N-gram 大致对应不同的音节syllabi。N-gram 的初衷是捕捉「多词表达」比如hot dog与单独出现的hot、dog含义完全不同如果始终用同一向量表示这两个词会干扰模型判断。在文档分类方法中单词、双词、三词的出现频率都是训练分类器时很有用的特征。在 TensorFlow 笔记 中可以看到若为 AG News 构建二元组bigram词表规模会膨胀到超过 130 万个 tokenPyTorch 笔记的运行输出为Bigram vocabulary length 1308842。正因为如此N-gram 必须与降维技术如后续单元要讲的嵌入 embeddings配合使用或者通过调高建词表时的min_freq参数过滤掉低频 N-gram以显著压缩维度。Bag-of-Words词袋最简单实用的定长向量处理文本分类这类任务时我们需要用一个定长的向量表示文本作为最终稠密分类器dense classifier的输入。最直接的做法是把所有单词的表示合并起来——比如直接相加把所有单词的 one-hot 编码逐位相加就得到一个频率向量frequency vector每个维度上的数值表示对应单词在文本中出现了多少次。这种文本表示被称为词袋模型Bag of Words简称 BoWBoW 本质上记录了「哪些词出现了、各出现了多少次」这确实能很好地提示文本在讲什么主题政治新闻更可能出现president、country这样的词科学出版物则更可能出现collider、discovered等词。因此在很多场景下词频是文本内容的好指标。用 scikit-learn 的CountVectorizer可以一行代码生成 BoW 向量来自 PyTorch 笔记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]], dtypeint64)结果中第 0 维hot 出现 1 次、第 1 维dog 出现 1 次、第 3 维dogs 出现 2 次等非零位置恰好对应新句子里各词的频次。下图直观展示了 BoW 向量在内存中的形态——每个词绑定一个向量下标向量元素是该词在文档中的出现次数在 PyTorch 中可以基于前面构建的词表手写一个to_bow函数先对每个 token 查表得到索引再把索引对应位置累加 1最终得到一个长度为词表大小的torch.zeros向量vocab_size len(vocab) def to_bow(text, bow_vocab_sizevocab_size): res torch.zeros(bow_vocab_size, dtypetorch.float32) for i in encode(text): if i bow_vocab_size: res[i] 1 return res print(to_bow(train_dataset[0][1])) # tensor([2., 1., 2., ..., 0., 0., 0.])TensorFlow 版本则利用tf.one_hot与tf.reduce_sum实现同样逻辑把TextVectorization输出的索引序列扩展成 one-hot 矩阵再按行求和def to_bow(text): return tf.reduce_sum(tf.one_hot(vectorizer(text), vocab_size), axis0)BoW 的致命缺陷在于and、is这类常见词几乎出现在所有文本中且频率最高会掩盖真正重要的关键词。解决思路是把「整个文档集合中该词的出现情况」纳入考量——这正是下一节 TF-IDF 的核心思想。TF-IDF用逆文档频率压制高频噪音词TF-IDF 全称term frequency–inverse document frequency词频–逆文档频率。它是 BoW 的一种变体不再用 0/1 标记单词是否出现而是使用与词在整个语料库中出现频率相关的浮点权重。更正式地说词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的文档数量。权重 $w_{ij}$ 随词在单篇文档中出现的次数增加而增大同时被「包含该词的文档数」抵消——这正好校正了「有些词天生比别的词更常见」的问题。极端情况下如果某个词出现在集合中的每一篇文档里则 $df_i N$此时 $w_{ij} 0$该词被完全忽略。用 scikit-learn 可以方便地得到 TF-IDF 向量化结果注意这里与 BoW 示例共用同一个corpusfrom 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() # array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664, # 0.43381609, 0. , 0. , 0. , 0. , ...]])可以看到高频/常见词被赋予较低权重如 hot、dogs 对应 0.43而更能区分文本的词此处dog likes之类的二元组特征获得更高权重0.66。而在 Keras 中只需把TextVectorization层的output_mode参数从count切换为tf-idf即可让向量化器自动计算 TF-IDF 频率详见 TensorFlow 笔记 的对应小节。实战一用 PyTorch 训练 BoW 新闻分类器PyTorch 笔记 给出了一条完整的「BoW 单层线性分类器」训练链路。核心步骤如下。把数据集转换为 BoW 表示。将bowify函数作为collate_fn传给 PyTorch 标准的DataLoader即可在取 batch 时把每条文本实时转成词袋向量from torch.utils.data import DataLoader import numpy as np # collate 函数接收 batch_size 条 (label, text) 元组 # 返回整个 minibatch 的 (标签张量, 特征张量) 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]) ) train_loader DataLoader(train_dataset, batch_size16, collate_fnbowify, shuffleTrue) test_loader DataLoader(test_dataset, batch_size16, collate_fnbowify, shuffleTrue)定义分类器网络。一个仅含单层线性层的小网络输入维度等于vocab_size输出维度等于类别数 4。因为是分类任务最后的激活函数用LogSoftmax损失函数用NLLLoss负对数似然损失net torch.nn.Sequential( torch.nn.Linear(vocab_size, 4), torch.nn.LogSoftmax(dim1) )训练循环。笔记提供了标准的训练函数train_epoch使用 Adam 优化器学习率默认 0.01支持通过epoch_size限制训练样本量数据集很大教学场景只跑部分数据并通过report_freq控制训练过程中准确率的打印频率def train_epoch(net, dataloader, lr0.01, optimizerNone, loss_fntorch.nn.NLLLoss(), epoch_sizeNone, report_freq200): optimizer optimizer or torch.optim.Adam(net.parameters(), lrlr) net.train() total_loss, acc, count, i 0, 0, 0, 0 for labels, features in dataloader: optimizer.zero_grad() out net(features) loss loss_fn(out, labels) loss.backward() optimizer.step() total_loss loss _, predicted torch.max(out, 1) acc (predicted labels).sum() count len(labels) i 1 if i % report_freq 0: print(f{count}: acc{acc.item()/count}) if epoch_size and count epoch_size: break return total_loss.item()/count, acc.item()/count train_epoch(net, train_loader, epoch_size15000)笔记中的实际运行输出显示仅训练约 15,000 条样本不到一个 epoch训练集准确率就从 80.3%3,200 条稳步爬升到 85.8%12,800 条最终达到约86.2%——对于四分类任务而言这已经是个不错的基线结果。实用技巧来自笔记原文提示上面用全局vocab_size指定了词表默认大小。由于词表通常很大可以只保留最频繁的词来限制词表规模。试着调低vocab_size再运行代码观察准确率变化准确率会有一定下降但不会很剧烈——这是用少量精度损失换取更高训练性能的常见权衡。实战二用 TensorFlow/Keras 构建端到端分类器TensorFlow 笔记 走的是另一条技术栈但思路完全同构。要点如下。加载数据并向量化。用tfds.load(ag_news_subset)得到训练集120,000 条与测试集7,600 条。随后实例化 Keras 的TextVectorization预处理层通过max_tokens限制词表上限再用adapt方法扫描文本构建词表。笔记只用了前 500 条样本做adapt以加快执行——代价是全集中的部分词会落入[UNK]而被忽略这会让最终准确率略有损失但不会显著vocab_size 50000 vectorizer keras.layers.experimental.preprocessing.TextVectorization(max_tokensvocab_size) vectorizer.adapt(ds_train.take(500).map(lambda x: x[title] x[description])) vocab vectorizer.get_vocabulary() vocab_size len(vocab) # 实际词表长度 5335 print(vocab[:10]) # [, [UNK], the, to, a, in, of, and, on, for] vectorizer(I love to play with my words) # tf.Tensor: shape(7,), dtypeint64, numpyarray([112, 3695, 3, 304, 11, 1041, 1])训练 BoW 分类器。先用map把数据集每条样本转成(BoW向量, 标签)并batch再定义一个单层Dense(4, activationsoftmax)网络损失函数用sparse_categorical_crossentropybatch_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, activationsoftmax, input_shape(vocab_size,)) ]) model.compile(losssparse_categorical_crossentropy, optimizeradam, metrics[acc]) model.fit(ds_train_bow, validation_datads_test_bow) # 运行输出loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697把向量化器并入网络端到端训练。因为TextVectorization本身也是 Keras 层可以直接把它接到Input之后让「分词-向量化-分类」成为一个整体网络省去map预处理步骤。这样得到的模型包含text_vectorization → tf.one_hot → tf.reduce_sum → Dense四层可训练参数仅21,344个验证准确率达到约87.4%val_acc: 0.8736。更省事的方式内置计数/权重模式。新版 TensorFlow 支持直接让向量化器输出 BoW 计数或 TF-IDF 权重无需手写one_hot reduce_sum# 自动计算 BoW 向量 model keras.models.Sequential([ keras.layers.experimental.preprocessing.TextVectorization( max_tokensvocab_size, output_modecount), keras.layers.Dense(4, input_shape(vocab_size,), activationsoftmax) ]) # 运行输出loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772 # 自动计算 TF-IDF 权重 model keras.models.Sequential([ keras.layers.experimental.preprocessing.TextVectorization( max_tokensvocab_size, output_modetf-idf), keras.layers.Dense(4, input_shape(vocab_size,), activationsoftmax) ]) # 运行输出loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849从笔记的运行结果可以直观对比同样是单层线性分类器BoW 计数的验证准确率约 87.7%而 TF-IDF 提升到约88.5%且验证损失从 0.4168 明显下降到 0.3432——说明「频率加权」确实让特征更有区分度。同时注意由于这里有 4 个类别准确率超过 80% 就已经是不错的成绩笔记原文结论。词表规模的取舍与 N-gram 的内存瓶颈综合两份笔记可以总结出文本表示工程中的几条关键权衡词表越大表示越完整但张量越稀疏。AG News 全量词表约 95,810 词PyTorch 侧而 Keras 侧仅用 500 条样本构建、限制max_tokens50000时实际词表只有 5,335 词训练更快但会丢弃低频词。N-gram 会指数级膨胀词表。二元组词表长度达到1,308,842PyTorch 笔记输出或「超过 130 万」TF 笔记直接用同样的代码训练分类器会非常浪费内存。两个笔记都给出了同一建议只有保留出现次数超过阈值min_freq调高的 N-gram才能显著降低维度而正规做法是引入**嵌入embeddings**做降维——这正是下一单元第 14 课 Word Embeddings要解决的问题。在 Keras 中为TextVectorization传入ngrams参数即可生成 N-gram 词表但笔记提醒要同时用合理上限截断 bigram token 数。BoW 与 TF-IDF 都无法表达词序与语义。BoW 把文档看作词的「无序集合」丢掉语序TF-IDF 只是对词频做了加权。著名语言学家 J. R. Firth 在 1935 年就指出「一个词的完整含义永远是上下文相关的脱离上下文的含义研究不值得认真对待The complete meaning of a word is always contextual, and no study of meaning apart from context can be taken seriously.。」要捕获上下文信息就需要语言建模Language Modeling与嵌入等技术——这正是本课程后续单元RNN、Transformer 等的核心主题。练习、挑战与课后作业配套练习笔记跟着 PyTorch 版 TextRepresentationPyTorch.ipynb 或 TensorFlow 版 TextRepresentationTF.ipynb 亲手跑一遍文本表示与分类流程两份笔记内容互相对照能更透彻地理解两套框架的等价实现。课后挑战课程鼓励用 BoW 和不同数据模型多做练习例如参考 Kaggle 上的 word2vec-nlp-tutorial 竞赛其第一部分就是面向初学者的词袋方法体会词袋特征在真实 NLP 竞赛中的用法。作业Assignment详见 lesson 13 作业说明。作业要求基于本课笔记PyTorch 或 TensorFlow 版本换用你自己的数据集可来自 Kaggle注意标注来源重新运行重写笔记以突出自己的发现课程还特别推荐了 NUFORC 的 UFO 目击数据集这类「意想不到」的数据集来挑战模型的泛化能力。课前/课后测验本课在原版文档中配有课前测验与课后测验各 10 余道题用于在学习前后自查对文本表示、词袋、TF-IDF 等概念的掌握程度。复习与自学可借助微软 Learn 平台上的自然语言处理入门模块练习文本嵌入text embeddings与词袋技术如需在本地安装运行本课程 NLP 部分所需的全部依赖可按 lessons/5-NLP/README.md 中的说明分别执行pip install -r requirements-pytorch.txtPyTorch 栈或pip install -r requirements-tf.txtTensorFlow 栈。小结本课围绕「把文本变成张量」这一 NLP 管线最前端的问题给出了一套完整且可落地的技术图谱字符级表示 → 词级表示 → N-gram → BoW → TF-IDF。字符级与词级表示解决「怎么编码」N-gram 尝试引入局部上下文BoW 把变长文本压缩成定长频率向量TF-IDF 进一步用逆文档频率压制噪音词。仓库中的两份笔记分别以 PyTorch 与 TensorFlow 实现了从「分词建表」到「训练分类器」的全流程并给出可复现的准确率基线约 84%88%。理解了这一课你就掌握了传统统计文本表示的全部核心手段也自然能理解为什么后续课程要引入嵌入与语言模型——因为词义永远在语境之中。赞分享教程人工智能机器学习深度学习【免费下载链接】AI-For-Beginners12 Weeks, 24 Lessons, AI for All!项目地址https://gitcode.com/GitHub_Trending/ai/AI-For-Beginners点击查看免费下载相关推荐AI-For-Beginners 课程详解将文本表示为张量——从字符编码、N-Gram 到 BoW 与 TF-IDF 的完整实战AI For Beginners 课程详解将文本表示为张量——从字符编码、N Gram 到 BoW 与 TF IDF 的完整实战 本文是微软 AI For B教程人工智能机器学习深度学习AI-For-Beginners 课程笔记将文本表示为张量 —— 从字符编码到 BoW 与 TF/IDF 的 NLP 入门实战AI For Beginners 课程笔记将文本表示为张量 —— 从字符编码到 BoW 与 TF/IDF 的 NLP 入门实战 本文基于 AI For Beg教程人工智能机器学习深度学习AI-For-Beginners 文本表示实战指南用自定义数据集重跑 BoW、N-Gram 与 TF-IDF 笔记本AI For Beginners 文本表示实战指南用自定义数据集重跑 BoW、N Gram 与 TF IDF 笔记本 本指南面向《AI For Beginne教程人工智能机器学习深度学习创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表