pytorch NLP自然语言处理入门一:文本表示

开始编辑:2024/2/16;最后编辑2024/2/16
教程出自:https://learn.microsoft.com/en-sg/training/modules/intro-natural-language-processing-pytorch/
第二部分:https://blog.csdn.net/qq_33345365/article/details/136142152

本博客旨在探讨处理自然语言文本的不同神经网络架构。近年来,自然语言处理(Natural Language Processing,NLP)经历了快速发展,主要是由于语言模型能够在大型文本语料库上使用无监督训练时更快地准确“理解”人类语言。例如,使用GPT-3或BERT等预训练文本模型生成句子,简化了许多NLP任务,并显著提高了性能。

本博客将重点关注在PyTorch中将NLP表示为张量的基本方面,以及经典的NLP架构,如使用词袋(BoW)、词嵌入、循环神经网络和生成网络。

以下是一些常见的自然语言处理 (NLP) 任务:

  • 文本分类Text Classification: 用于将文本片段归类为预定义类的其中之一。例如,电子邮件垃圾邮件检测、新闻分类、将支持请求分配到某个类别等等。
  • 意图分类Intent Classification: 是文本分类的一个特定案例,用于将对话 AI 系统中的输入话语映射到代表该短语实际含义或用户意图的意图之一。
  • 情感分析Sentiment Analysis: 是一个回归任务,用于理解给定文本片段的负面程度。我们可以将数据集中的文本从最负面 (-1) 标记到最正面 (+1),并训练一个模型输出文本的 “积极性” 数量。
  • 命名实体识别 (Named Entity Recognition, NER): 是从文本中提取实体的任务,例如日期、地址、人名等。NER 通常与意图分类一起用于对话系统中,从用户的话语中提取参数。
  • 关键字提取keyword extraction: 类似的任务,用于找到文本中最有意义的词,然后可以将其用作标签。
  • 文本摘要Text Summarization: 提取文本中最有意义的部分,为用户提供包含大部分含义的压缩版本。
  • 问答Question/Answer: 从文本片段中提取答案的任务。该模型将文本片段和问题作为输入,需要在文本中找到包含答案的确切位置。例如,文本 “John 是一个 22 岁的学生,喜欢使用 Microsoft Learn”,问题 “John 多大了” 应该提供答案 “22”。

本博客将主要关注**文本分类**任务。使用新闻标题中的文本来分类它们属于四个类别中的哪一个:World, Sports, Business, Sci/Tech。此外还介绍生成模型,它可以自我生成类似人类的文本序列。

huggingface transformers的auto class分类,包含了目前大部分机器学习分类,详见https://blog.csdn.net/qq_33345365/article/details/136126773?spm=1001.2014.3001.5501

学习目标:

  • 理解文本在自然语言处理任务中的处理方式
  • 学习使用循环神经网络 (Recurrent Neural Networks, RNNs) 和生成网络
  • 掌握构建文本分类模型的方法

先修知识:

  • 基础 Python 知识
  • 基本使用 Jupyter notebook 的经验
  • 机器学习基础理解

Representing text as Tensors


文本表示

如果想用神经网络解决自然语言处理(NLP)任务,则需要一些方法将文本表示为张量。计算机已经将文本字符表示为数字,这些数字使用ASCII或UTF-8等编码映射到屏幕上的字体。程序员知道每个字母代表什么,以及所有的字符如何组合成一个句子的单词。然而,计算机本身并没有这样的理解,神经网络必须在训练过程中学习其含义。

因此,在表示文本时,可以使用不同的方法:

  • 字符级表示Character-level representation:将每个字符视为数字。假设文本语料库中有C个不同的字符,那么单词Hello将由5×C张量表示。在单热编码(one-hot encoding)中,每个字母对应一列张量。
  • 单词级表示Word-level representation,创建文本序列或句子中所有单词的词汇表vocabulary,然后使用one-hot编码表示每个单词。这种方法在某种程度上更好,因为每个字母本身没有太多的含义,因此通过使用更高层次的语义概念——单词——简化了神经网络的任务。然而,给定一个大的字典大小,则需要处理高维稀疏张量。例如,如果词汇表有1万个不同的单词。那么每个单词的编码长度为10000,因此是高维。

token: 为了统一这些方法,我们通常将原子文本片段称为一个token。在某些情况下,token可以是字母,在其他情况下,可以是单词或单词的一部分。

例如,我们可以选择将“indivisible”标记为“ in-divis-ible”,其中‘-’符号表示该标记是前一个单词的延续。这将允许"divis"始终由一个token表示,对应于一个核心含义。

序列化tokenization:将文本转换为标记序列的过程。

向量化vectorization:为了可以将标记输入神经网络,为序列化的每个标记分配一个数字的过程称为vectorization,通常通过构建token词汇表来完成。

版本一览,如果无法运行,请参考如下版本与类进行安装:

pip install -r https://raw.githubusercontent.com/MicrosoftDocs/pytorchfundamentals/main/nlp-pytorch/requirements.txt
# 如果打不开,那么将下面内容复制到新文件“requirements.txt”,然后pip install -r 新文件.txt即可
gensim==3.8.3
huggingface==0.0.1
matplotlib
nltk==3.5
numpy==1.18.5
opencv-python==4.5.1.48
Pillow==7.1.2
scikit-learn
scipy
torch==1.8.1
torchaudio==0.8.1
torchinfo==0.0.8
torchtext==0.9.1
torchvision==0.9.1
transformers==4.3.3

文本分类任务Text classification task

在本模块中,我们将从基于AG_NEWS样本数据集的简单文本分类任务开始,即将新闻标题分为四类:World, Sports, Business and Sci/Tech。这个数据集是由PyTorch的torchtext模块构建的。

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

在这里,’ train_dataset ‘和’ test_dataset '包含分别返回标签(类的数量)和文本对的迭代器,例如:

a = next(train_dataset)
print(a)
# 输出为:
# (3, "Wall St. Bears Claw Back Into the Black (Reuters) Reuters - Short-sellers, Wall Street's dwindling\\band of ultra-cynics, are seeing green again.")

打印数据集中的前5个新标题:

for i,x in zip(range(5),train_dataset):
    # x[0]存放标题种类,可能是0,1,2,3,x[1]是标题
    print(f"**{classes[x[0]]}** -> {x[1]}\n")  
    
#输出为:
# **Sci/Tech** -> Carlyle Looks Toward Commercial Aerospace (Reuters) Reuters - Private investment firm Carlyle Group,\which has a reputation for making well-timed and occasionally\controversial plays in the defense industry, has quietly placed\its bets on another part of the market.
# **Sci/Tech** -> Oil and Economy Cloud Stocks' Outlook (Reuters) Reuters - Soaring crude prices plus worries\about the economy and the outlook for earnings are expected to\hang over the stock market next week during the depth of the\summer doldrums.
# **Sci/Tech** -> Iraq Halts Oil Exports from Main Southern Pipeline (Reuters) Reuters - Authorities have halted oil export\flows from the main pipeline in southern Iraq after\intelligence showed a rebel militia could strike\infrastructure, an oil official said on Saturday.
# **Sci/Tech** -> Oil prices soar to all-time record, posing new menace to US economy (AFP) AFP - Tearaway world oil prices, toppling records and straining wallets, present a new economic menace barely three months before the US presidential elections.
# **Sci/Tech** -> Stocks End Up, But Near Year Lows (Reuters) Reuters - Stocks ended slightly higher on Friday\but stayed near lows for the year as oil prices surged past  #36;46\a barrel, offsetting a positive outlook from computer maker\Dell Inc. (DELL.O)

因为数据集是迭代器,如果我们想多次使用数据,我们需要将其转换为列表list:

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

序列化与向量化

现在我们需要将文本转换为可以表示为张量的数字,并将其输入神经网络。第一步是序列化:将文本转换为标记。如果我们使用word-level表示,每个词将由它自己的token表示。我们将使用torchtext模块的内置标记器:

tokenizer = torchtext.data.utils.get_tokenizer('basic_english')

PyTorch的tokenizer被用来拆分前两篇新闻文章中的单词和空格。本例使用basic_english作为tokenizer来理解语言结构。这将返回文本和字符的字符串列表。

basic_english分词器是一种简单分词器,它将文本分割成空格分隔的单词。它将所有单词转换为小写,并删除所有标点符号。

first_sentence = train_dataset[0][1]
second_sentence = train_dataset[1][1]

f_tokens = tokenizer(first_sentence)  # 使用tokenizer进行序列化
s_tokens = tokenizer(second_sentence)

print(f'\nfirst token list:\n{f_tokens}')  # 标题被序列化后的结果
print(f'\nsecond token list:\n{s_tokens}')

# 输出
# first token list:
# ['wall', 'st', '.', 'bears', 'claw', 'back', 'into', 'the', 'black', '(', 'reuters', ')', 'reuters', '-', 'short-sellers', ',', 'wall', 'street', "'", 's', 'dwindling\\band', 'of', 'ultra-cynics', ',', 'are', 'seeing', 'green', 'again', '.']

# second token list:
# ['carlyle', 'looks', 'toward', 'commercial', 'aerospace', '(', 'reuters', ')', 'reuters', '-', 'private', 'investment', 'firm', 'carlyle', 'group', ',', '\\which', 'has', 'a', 'reputation', 'for', 'making', 'well-timed', 'and', 'occasionally\\controversial', 'plays', 'in', 'the', 'defense', 'industry', ',', 'has', 'quietly', 'placed\\its', 'bets', 'on', 'another', 'part', 'of', 'the', 'market', '.']

接下来,要将文本转换为数字,需要构建包含所有标记的词汇表。首先使用Counter对象构建字典,然后创建一个Vocab对象来帮助我们处理向量化:

counter = collections.Counter()  # 初始化字典
for (label, line) in train_dataset:
    counter.update(tokenizer(line))  # 构建字典
vocab = torchtext.vocab.Vocab(counter, min_freq=1)  # 用来处理向量化

为了查看每个单词如何映射到词汇表,需要遍历列表中的每个单词以查找它在vocab中的索引号。每个单词或字符都显示其相应的索引。例如,单词“the”在两个句子中都出现了几次,它在词汇表中的唯一索引是数字3。

此处使用了python的列表推导式,可以参考https://blog.csdn.net/qq_33345365/article/details/136102620?spm=1001.2014.3001.5501了解相关知识

word_lookup = [list((vocab[w], w)) for w in f_tokens]
print(f'\nIndex lockup in 1st sentence:\n{word_lookup}')

word_lookup = [list((vocab[w], w)) for w in s_tokens]
print(f'\nIndex lockup in 2nd sentence:\n{word_lookup}')

# 输出
# Index lockup in 1st sentence:
# [[432, 'wall'], [426, 'st'], [2, '.'], [1606, 'bears'], [14839, 'claw'], [114, 'back'], [67, 'into'], [3, 'the'], [849, 'black'], [14, '('], [28, 'reuters'], [15, ')'], [28, 'reuters'], [16, '-'], [50726, 'short-sellers'], [4, ','], [432, 'wall'], [375, 'street'], [17, "'"], [10, 's'], [67508, 'dwindling\\band'], [7, 'of'], [52259, 'ultra-cynics'], [4, ','], [43, 'are'], [4010, 'seeing'], [784, 'green'], [326, 'again'], [2, '.']]
# Index lockup in 2nd sentence:
# [[15875, 'carlyle'], [1073, 'looks'], [855, 'toward'], [1311, 'commercial'], [4251, 'aerospace'], [14, '('], [28, 'reuters'], [15, ')'], [28, 'reuters'], [16, '-'], [930, 'private'], [798, 'investment'], [321, 'firm'], [15875, 'carlyle'], [99, 'group'], [4, ','], [27658, '\\which'], [29, 'has'], [6, 'a'], [4460, 'reputation'], [12, 'for'], [565, 'making'], [52791, 'well-timed'], [9, 'and'], [80618, 'occasionally\\controversial'], [2126, 'plays'], [8, 'in'], [3, 'the'], [526, 'defense'], [242, 'industry'], [4, ','], [29, 'has'], [3891, 'quietly'], [82815, 'placed\\its'], [6575, 'bets'], [11, 'on'], [207, 'another'], [360, 'part'], [7, 'of'], [3, 'the'], [127, 'market'], [2, '.']]

使用词汇表,可以很容易地将标记字符串编码为一组数字。以第一篇新闻文章为例:

vocab_size = len(vocab)
print(f"Vocab size if {vocab_size}")

def encode(x):
    return [vocab.stoi[s] for s in tokenizer(x)]  # 列表推导式

vec = encode(first_sentence)
print(vec)

# 输出
# Vocab size if 95812
# [432, 426, 2, 1606, 14839, 114, 67, 3, 849, 14, 28, 15, 28, 16, 50726, 4, 432, 375, 17, 10, 67508, 7, 52259, 4, 43, 4010, 784, 326, 2]

在这个代码中,torchtext的vocab.stoi字典允许我们将字符串表示转换为数字(stoi代表“从字符串到整数”)。要将文本从数字表示转换回文本,我们可以使用词汇表。它的字典执行反向查找:

def decode(x):
    return [vocab.itos[i] for i in x]
  
print(decode(vec))
# ['wall', 'st', '.', 'bears', 'claw', 'back', 'into', 'the', 'black', '(', 'reuters', ')', 'reuters', '-', 'short-sellers', ',', 'wall', 'street', "'", 's', 'dwindling\\band', 'of', 'ultra-cynics', ',', 'are', 'seeing', 'green', 'again', '.']

BiGrams, TriGrams and N-Grams

单词标记化的一个限制是有些单词是多单词表达的一部分,例如,单词“hot dog”与其他上下文中的单词 “hot” 和 “dog”具有完全不同的含义。如果总是用相同的向量来表示单词“hot”和“dog”,就会混淆模型。为了解决这个问题,有时在文档分类中使用N-gram表示,其中每个单词、双单词或三单词的频率是训练分类器的有用特征。

  • 例如,在bigram表示中,除了原始单词外,我们将把所有的单词对(word pairs)添加到词汇表中。

为了获得n-gram表示,可以使用ngrams_iterator函数,该函数将token序列转换为n-gram序列。在下面的代码中,我们将从我们的新闻数据集中构建bigram词汇表:

from torchtext.data.utils import ngrams_iterator

bi_counter = collections.Counter()
for (label, line) in train_dataset:
    bi_counter.update(ngrams_iterator(tokenizer(line),ngrams=2))
bi_vocab = torchtext.vocab.Vocab(bi_counter, min_freq=2)

print(f"Bigram vocab size = {len(bi_vocab)}")
# 输出为:
# Bigram vocab size = 481971 for min_freq=2
# Bigram vocab size = 294491 for min_freq=3
def encode(x):
    return [bi_vocab.stoi[s] for s in tokenizer(x)]

print(encode(first_sentence))
# 输出为:
# [572, 564, 2, 2326, 49106, 150, 88, 3, 1143, 14, 32, 15, 32, 16, 443749, 4, 572, 499, 17, 10, 0, 7, 468770, 4, 52, 7019, 1050, 442, 2]

N-gram方法的主要缺点是词汇表的大小会以极快的速度增长。这里为Vocab构造函数指定min_freq标志,以避免那些只在文本中出现一次的标记。还可以进一步增加min_freq,因为不经常出现的单词/短语通常对分类的准确性影响很小。

尝试将set min_freq参数设置为更大的值,观察词汇表长度的变化。

在实践中,n-gram词汇表的大小仍然太高,无法将单词表示为单热向量,因此我们需要将这种表示与一些降维技术结合起来,例如嵌入,我们将在后面的单元中讨论。

将文本输入神经网络

本博客讲述了如何用数字来表示每个单词。现在,为了建立文本分类模型,需要将整句(或整篇新闻文章)输入神经网络。这里的问题是每篇文章/句子都有不同的长度;但是全连接或卷积神经网络只能处理固定的输入大小。有两种方法可以解决这个问题:

  • 把一个句子分解成固定长度的向量。下一部分将描述词袋Bag-of-Words和TF-IDF表示如何做到这一点。
  • 设计能够处理可变长度序列的特殊神经网络架构。之后将描述如何实现用于序列建模的循环神经网络(RNN)。

整体代码如下所示:

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']

a = next(train_dataset)
print(a)

for i, x in zip(range(5), train_dataset):
    print(f"**{classes[x[0]]}** -> {x[1]}")

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

tokenizer = torchtext.data.utils.get_tokenizer('basic_english')

first_sentence = train_dataset[0][1]
second_sentence = train_dataset[1][1]

f_tokens = tokenizer(first_sentence)
s_tokens = tokenizer(second_sentence)

print(f'\nfirst token list:\n{f_tokens}')
print(f'\nsecond token list:\n{s_tokens}')

counter = collections.Counter()  # 初始化字典
for (label, line) in train_dataset:
    counter.update(tokenizer(line))  # 构建字典
vocab = torchtext.vocab.Vocab(counter, min_freq=1)  # 用来处理向量化

word_lookup = [list((vocab[w], w)) for w in f_tokens]
print(f'\nIndex lockup in 1st sentence:\n{word_lookup}')

word_lookup = [list((vocab[w], w)) for w in s_tokens]
print(f'\nIndex lockup in 2nd sentence:\n{word_lookup}')

vocab_size = len(vocab)
print(f"Vocab size if {vocab_size}")


def encode(x):
    return [vocab.stoi[s] for s in tokenizer(x)]


vec = encode(first_sentence)
print(vec)


def decode(x):
    return [vocab.itos[i] for i in x]


print(decode(vec))

from torchtext.data.utils import ngrams_iterator

bi_counter = collections.Counter()
for (label, line) in train_dataset:
    bi_counter.update(ngrams_iterator(tokenizer(line), ngrams=2))
bi_vocab = torchtext.vocab.Vocab(bi_counter, min_freq=3)

print(f"Bigram vocab size = {len(bi_vocab)}")


def encode(x):
    return [bi_vocab.stoi[s] for s in tokenizer(x)]


print(encode(first_sentence))

  • 30
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

whyte王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值