赞
踩
前面两讲,体验了 OpenAI 通过 API 提供的 GPT-3.5 系列模型的两个核心接口。一个是获取一段文本的 Embedding 向量,另一个则是根据提示语,直接生成一段补全的文本内容。我们用这两种方法,都可以实现零样本(zero-shot)或者少样本下的情感分析任务。不过,你可能会提出这样两个疑问。
1. Embedding 不就是把文本变成向量吗?我也学过一些自然语言处理,直接用个开源模型,比如 Word2Vec、Bert 之类的就好了呀,何必要去调用 OpenAI 的 API 呢?
2. 我们在这个情感分析里做了很多投机取巧的工作。一方面,我们把 3 分这种相对中性的评价分数排除掉了;另一方面,我们把 1 分 2 分和 4 分 5 分分别合并在了一起,把一个原本需要判断 5 个分类的问题简化了。那如果我们想要准确地预测多个分类,也会这么简单吗?
那么,这一讲我们就先来回答第一个问题。我们还是拿代码和数据来说话,就拿常见的开源模型来试一试,看看能否通过零样本学习的方式来取得比较好的效果。第二个问题,我们下一讲再来探讨,看看能不能利用 Embedding 进一步通过一些机器学习的算法,来更好地处理情感分析问题。
给出一段文本,OpenAI 就能返回给你一个 Embedding 向量,这是因为它的背后是 GPT-3 这个超大规模的预训练模型(Pre-trained Model)。事实上,GPT 的英文全称翻译过来就是“生成式预训练 Transformer(Generative Pre-trained Transformer)”。
所谓预训练模型,就是虽然我们没有看过你想要解决的问题,比如这里我们在情感分析里看到的用户评论和评分。但是,我可以拿很多我能找到的文本,比如网页文章、维基百科里的文章,各种书籍的电子版等等,作为理解文本内容的一个学习资料。
我们不需要对这些数据进行人工标注,只根据这些文本前后的内容,来习得文本之间内在的关联。比如,网上的资料里,会有很多“小猫很可爱”、“小狗很可爱”这样的文本。小猫和小狗后面都会跟着“很可爱”,那么我们就会知道小猫和小狗应该是相似的词,都是宠物。同时,一般我们对于它们的情感也是正面的。这些隐含的内在信息,在我们做情感分析的时候,就带来了少量用户评论和评分数据里缺少的“常识”,这些“常识”也有助于我们更好地预测。
比如,文本里有“白日依山尽”,那么模型就知道后面应该跟“黄河入海流”。文本前面是“今天天气真”,后面跟着的大概率是“不错”,小概率是“糟糕”。这些文本关系,最后以一堆参数的形式体现出来。对于你输入的文本,它可以根据这些参数计算出一个向量,然后根据这个向量,来推算这个文本后面的内容。
可以这样来理解:用来训练的语料文本越丰富,模型中可以放的参数越多,那模型能够学到的关系也就越多。类似的情况在文本里出现得越多,那么将来模型猜得也就越准。
预训练模型在自然语言处理领域并不是 OpenAI 的专利。早在 2013 年,就有一篇叫做 Word2Vec 的经典论文谈到过。它能够通过预训练,根据同一个句子里一个单词前后出现的单词,来得到每个单词的向量。而在 2018 年,Google 关于 BERT 的论文发表之后,整个业界也都会使用 BERT 这样的预训练模型,把一段文本变成向量用来解决自己的自然语言处理任务。在 GPT-3 论文发表之前,大家普遍的结论是,BERT 作为预训练的模型效果也是优于 GPT 的。
今天我们就拿两个开源的预训练模型,来看看直接用它们对文本进行向量化,是不是也能取得和 OpenAI 的 API 一样好的效果。
第一个是来自 Facebook 的 Fasttext,它继承了 Word2Vec 的思路,能够把一个个单词表示成向量。第二个是来自 Google 的 T5,T5 的全称是 Text-to-Text Transfer Trasnformer,是适合做迁移学习的一个模型。所谓迁移学习,也就是它推理出来向量的结果,常常被拿来再进行机器学习,去解决其他自然语言处理问题。通常很多新发表的论文,会把 T5 作为预训练模型进行微调和训练,或者把它当作 Benchmark 来对比、评估。
我们先来试一下 Fasttext,在实际运行代码之前,我们需要先安装 Fasttext 和 Gensim 这两个 Python 包。
在下面列出了通过 pip 安装对应 Python 包的代码,如果你使用的是 PIP 或者其他的 Python 包管理工具,你就换成对应的命令就好了。因为这一讲加载的 AI 模型需要的内存比较大,为了快速运行也需要对应的 GPU 资源,推荐你可以使用 Google Colab 来实验对应的代码。
- %pip install gensim
- %pip install fasttext
然后,我们要把 Fasttext 对应的模型下载到本地。因为这些开源库和对应的论文都是 Facebook 和 Google 这样的海外公司发布的,效果自然是在英语上比较好,所以我们就下载对应的英语模型,名字叫做 “cc.en.300.bin”。同样的,对应模型的下载链接,也放在这里了。
下载之后解压,然后把文件放在和 Notebook 相同的目录下,方便我们接下来运行代码。
这里我们拿来测试效果的数据集还是和第 02 讲一样,用的是 2.5w 条亚马逊食物评论的数据。
注:各种语言的 Fasttext 模型。
代码的逻辑也不复杂,我们先利用 Gensim 这个库,把 Facebook 预训练好的模型加载进来。然后,我们定义一个获取文本向量的函数。因为 Fasttext 学到的是单词的向量,而不是句子的向量。同时,因为我们想要测试一下零样本学习的效果,不能再根据拿到的评论数据进一步训练模型了。所以我们把一句话里每个单词的向量,加在一起平均一下,把得到的向量作为整段评论的向量。这个方法也是当年常用的一种将一句话变成向量的办法。我们把这个操作定义成了 get_fasttext_vector 这个函数,供后面的程序使用。
- import gensim
- import numpy as np
- # Load the FastText pre-trained model
- model = gensim.models.fasttext.load_facebook_model('cc.en.300.bin')
-
- def get_fasttext_vector(line):
- vec = np.zeros(300) # Initialize an empty 300-dimensional vector
- for word in line.split():
- vec += model.wv[word]
- vec /= len(line.split()) # Take the average over all words in the line
- return vec
而对应的零样本学习,我们还是和第 02 讲一样,将需要进行情感判断的评论分别与 “An Amazon review with a positive sentiment.” 以及 “An Amazon review with a negative sentiment.” 这两句话进行向量计算,算出它们之间的余弦距离。
离前一个近,我们就认为是正面情感,离后一个近就是负面情感。
- positive_text = """Wanted to save some to bring to my Chicago family but my North Carolina family ate all 4 boxes before I could pack. These are excellent...could serve to anyone"""
- negative_text = """First, these should be called Mac - Coconut bars, as Coconut is the #2 ingredient and Mango is #3. Second, lots of people don't like coconut. I happen to be allergic to it. Word to Amazon that if you want happy customers to make things like this more prominent. Thanks."""
-
- positive_example_in_fasttext = get_fasttext_vector(positive_text)
- negative_example_in_fasttext = get_fasttext_vector(negative_text)
-
- positive_review_in_fasttext = get_fasttext_vector("An Amazon review with a positive sentiment.")
- negative_review_in_fasttext = get_fasttext_vector('An Amazon review with a negative sentiment.')
-
- import numpy as np
-
- def get_fasttext_score(sample_embedding):
- return cosine_similarity(sample_embedding, positive_review_in_fasttext) - cosine_similarity(sample_embedding, negative_review_in_fasttext)
-
- positive_score = get_fasttext_score(positive_example_in_fasttext)
- negative_score = get_fasttext_score(negative_example_in_fasttext)
-
- print("Fasttext好评例子的评分 : %f" % (positive_score))
- print("Fasttext差评例子的评分 : %f" % (negative_score))
输出结果:
- Fasttext好评例子的评分 : -0.000544
- Fasttext差评例子的评分 : 0.000369
我们从亚马逊食物评论的数据集里,选取了一个用户打 5 分的正面例子和一个用户打 1 分的例子试了一下。结果非常不幸,通过这个零样本学习的方式,这两个例子,程序都判断错了。
不过,仔细想一下,这样的结果也正常。因为这里的整句向量就是把所有单词的向量平均了一下。这意味着,可能会出现我们之前说过的单词相同顺序不同的问题。
“not good, really bad” 和 “not bad, really good”,在这个情况下,意思完全不同,但是向量完全相同。更何况,我们拿来做对比的正面情感和负面情感的两句话,只差了 positive/negative 这样一个单词。不考虑单词的顺序,而只考虑出现了哪些单词,并且不同单词之间还平均一下。这种策略要是真的有很好的效果,你反而要担心是不是哪里有 Bug。
Fasttext 出师不利,毕竟 Word2Vec 已经是 10 年前的技术了,可以理解。那么,我们来看看和 GPT 一样使用了现在最流行的 Transformer 结构的 T5 模型效果怎么样。
T5 模型的全称是 Text-to-Text Transfer Transformer,翻译成中文就是“文本到文本的迁移 Transformer”,也就是说,这个模型就是为了方便预训练之后拿去“迁移”到别的任务上而创造出来的。当时发表的时候,它就在各种数据集的评测上高居榜首。
T5 最大的模型也有 110 亿个参数,也是基于 Transformer,虽然比起 GPT-3 的 1750 亿小了不少,但是对硬件的性能要求也不低。所以,我们先测试一下 T5-Small 这个小模型看看效果。
同样的,在实际运行代码之前,我们也需要安装对应的 Python 包。这里我们分别安装了 SentencePiece 和 PyTorch。在安装 PyTorch 的时候,我一并安装了 Torchvision,后面课程会用到。
- conda install transformers -c conda-forge
- conda install pytorch torchvision -c pytorch
- conda install sentencepiece
代码也不复杂,我们先加载预训练好的 T5 模型的分词器(Tokenizer),还有对应的模型。然后,我们定义了一个 get_t5_vector 函数,它会接收一段你的文本输入,然后用分词器来分词把结果变成一个序列,然后让模型的编码器部分对其进行编码。编码后的结果,仍然是分词后的一个词一个向量,我们还是把这些向量平均一下,作为整段文本的向量。
不过要注意,虽然同样是平均,但是和前面 Fasttext 不一样的是,这里每个词的向量,随着位置以及前后词的不同,编码出来的结果是不一样的。所以这个平均值里,仍然包含了顺序带来的语义信息。
这段代码执行的过程可能会有点慢。因为第一次加载模型的时候,Transformer 库会把模型下载到本地并缓存起来,整个下载过程会花一些时间。
- from transformers import T5Tokenizer, T5Model
- import torch
-
- # load the T5 tokenizer and model
- tokenizer = T5Tokenizer.from_pretrained('t5-small', model_max_length=512)
- model = T5Model.from_pretrained('t5-small')
-
- # set the model to evaluation mode
- model.eval()
-
- # encode the input sentence
- def get_t5_vector(line):
- input_ids = tokenizer.encode(line, return_tensors='pt', max_length=512, truncation=True)
- # generate the vector representation
- with torch.no_grad():
- outputs = model.encoder(input_ids=input_ids)
- vector = outputs.last_hidden_state.mean(dim=1)
- return vector[0]
有了模型和通过模型获取的向量数据,我们就可以再试一试前面的零样本学习的方式,来看看效果怎么样了。我们简单地把之前获取向量和计算向量的函数调用,都换成新的 get_t5_vector,运行一下就能看到结果了。
- positive_review_in_t5 = get_t5_vector("An Amazon review with a positive sentiment.")
- negative_review_in_t5 = get_t5_vector('An Amazon review with a negative sentiment.')
-
- def test_t5():
- positive_example_in_t5 = get_t5_vector(positive_text)
- negative_example_in_t5 = get_t5_vector(negative_text)
-
- def get_t5_score(sample_embedding):
- return cosine_similarity(sample_embedding, positive_review_in_t5) - cosine_similarity(sample_embedding, negative_review_in_t5)
-
- positive_score = get_t5_score(positive_example_in_t5)
- negative_score = get_t5_score(negative_example_in_t5)
-
- print("T5好评例子的评分 : %f" % (positive_score))
- print("T5差评例子的评分 : %f" % (negative_score))
-
- test_t5()
输出结果:
- T5好评例子的评分 : -0.010294
- T5差评例子的评分 : -0.008990
不幸的是,结果还是不太好,两个例子都被判断成了负面情绪,而且好评的分数还更低一点。不过别着急,会不会是我们用的模型太小了呢?毕竟 T5 论文里霸占各个排行榜的是 110 亿个参数的大模型,我们这里用的是 T5-Small 这个同样架构下的小模型,参数数量只有 6000 万个。
110 亿个参数要花太多时间了,我们不妨把模型放大一下,试试有 2.2 亿个参数的 T5-Base 这个模型?试用起来也很简单,我们就直接把上面模型的名字从 T5-small 改成 T5-base 就好了,其他代码不需要动,重新运行一遍。
- tokenizer = T5Tokenizer.from_pretrained('t5-base', model_max_length=512)
- model = T5Model.from_pretrained('t5-base')
-
- # set the model to evaluation mode
- model.eval()
-
- # encode the input sentence
- def get_t5_vector(line):
- input_ids = tokenizer.encode(line, return_tensors='pt', max_length=512, truncation=True)
- # generate the vector representation
- with torch.no_grad():
- outputs = model.encoder(input_ids=input_ids)
- vector = outputs.last_hidden_state.mean(dim=1)
- return vector[0]
-
- positive_review_in_t5 = get_t5_vector("An Amazon review with a positive sentiment.")
- negative_review_in_t5 = get_t5_vector('An Amazon review with a negative sentiment.')
-
- test_t5()
输出结果:
- T5好评例子的评分 : 0.010347
- T5差评例子的评分 : -0.023935
这一次,结果似乎是我们想要的了,好评被判定为正面情感,而差评被判定为负面情感。不过,也许我们只是运气好,在这一两个例子上有效果呢?所以,接下来让我们把整个数据集里,1 分 2 分的差评和 4 分 5 分的好评都拿出来看一看。在 OpenAI 的 API 拿到的 Embedding 里,它的准确率能够达到 95% 以上,我们看看用这个有 2.2 亿个参数的 T5-base 模型能有什么样的结果。
对应的代码也不复杂,基本上和第 02 讲里 OpenAI 给到的 Embedding 代码是类似的。无非是通过 pandas,根据评论的 Text 字段,全部计算一遍 T5 下的 Embedding,然后存到 DataFrame 的 t5_embedding 里去。
同样的,我们还是要通过 T5 的模型,来获得 “An Amazon review with a positive sentiment.” 以及 “An Amazon review with a negative sentiment.” 这两句话的 Embedding。然后,我们用刚刚计算的用户评论的 Embedding 和这两句话计算一下余弦距离,来判断这些评论是正面还是负面的。
最后,通过 Scikit-learn 的分类报告的类库把评估的报告结果打印出来。
- import pandas as pd
- from sklearn.metrics import classification_report
-
- datafile_path = "data/fine_food_reviews_with_embeddings_1k.csv"
-
- df = pd.read_csv(datafile_path)
-
- df["t5_embedding"] = df.Text.apply(get_t5_vector)
- # convert 5-star rating to binary sentiment
- df = df[df.Score != 3]
- df["sentiment"] = df.Score.replace({1: "negative", 2: "negative", 4: "positive", 5: "positive"})
-
- from sklearn.metrics import PrecisionRecallDisplay
- def evaluate_embeddings_approach():
- def label_score(review_embedding):
- return cosine_similarity(review_embedding, positive_review_in_t5) - cosine_similarity(review_embedding, negative_review_in_t5)
-
- probas = df["t5_embedding"].apply(lambda x: label_score(x))
- preds = probas.apply(lambda x: 'positive' if x>0 else 'negative')
-
- report = classification_report(df.sentiment, preds)
- print(report)
-
- display = PrecisionRecallDisplay.from_predictions(df.sentiment, probas, pos_label='positive')
- _ = display.ax_.set_title("2-class Precision-Recall curve")
-
- evaluate_embeddings_approach()
输出结果:
- precision recall f1-score support
- negative 0.60 0.90 0.72 136
- positive 0.98 0.90 0.94 789
- accuracy 0.90 925
- macro avg 0.79 0.90 0.83 925
- weighted avg 0.93 0.90 0.91 925
结果显示,使用 T5 的效果也还可以,考虑所有样本的准确率也能达到 90%。但是,在比较困难的差评的判断里,它的表现要比直接用 OpenAI 给到的 Embedding 要差很多,整体的精度只有 60%。我们去看整体模型的准确率的话,OpenAI 的 Embedding 能够到达 96%,还是比这里的 90% 要好上一些的。
- precision recall f1-score support
- negative 0.98 0.73 0.84 136
- positive 0.96 1.00 0.98 789
- accuracy 0.96 925
- macro avg 0.97 0.86 0.91 925
- weighted avg 0.96 0.96 0.96 925
这里重新贴一下使用 OpenAI 的 Embedding 取得的效果,可以做个对比。
当然,这个分数也还不错,也能作为一个合格的情感分析分类器的基准线了。毕竟,我们这里采用的是零样本分类的方法,没有对需要分类的数据做任何训练,使用的完全是预训练模型给出来的向量,直接根据距离做的判断。所以,看起来大一点的预训练模型的确有用,能够取得更好的效果。而且,当你因为成本或者网络延时的问题,不方便使用 OpenAI 的 API 的时候,如果只是要获取文本的 Embedding 向量,使用 T5 这样的开源模型其实效果也还不错。
最后,我们来复习一下这一讲的内容。
这一讲我们一起使用 Fasttext、T5-small 和 T5-base 这三个预训练模型,做了零样本分类测试。在和之前相同的食物评论的数据集上,使用只学习了单词向量表示的 Fasttext,效果很糟糕。当我们换用同样基于 Transformer 的 T5 模型的时候,T5-small 这个 6000 万参数的小模型其实效果也不好。但是当我们用上 2.2 亿参数的 T5-base 模型的时候,结果还可以。不过,还是远远比不上直接使用 OpenAI 的 API 的效果。可见,模型的大小,即使是对情感分析这样简单的问题,也能产生明显的差距。
1. 我们在尝试使用 T5-base 这个模型之后,下了个判断认为大一点的模型效果更好。不过,其实我们并没有在整个数据集上使用 T5-small 这个模型做评测,你能试着修改一下代码,用 T5-small 测试一下整个数据集吗?测试下来的效果又是怎样的呢?
2. 我们使用 Fasttext 的时候,把所有的单词向量平均一下,用来做情感分析效果很糟糕。那么什么样的分类问题,可以使用这样的方式呢?给你一个小提示,你觉得什么样的文本分类,只关心出现的单词是什么,而不关心它们的顺序?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。