跳至主要內容

02|无需任何机器学习,如何利用大语言模型做情感分析?

AI悦创原创ChatGPTChatGPT大约 37 分钟...约 11227 字

你好,我是悦创。

上一讲我们看到了,大型语言模型的接口其实非常简单。像 OpenAI 就只提供了 Complete 和 Embedding 两个接口,其中,Complete 可以让模型根据你的输入进行自动续写,Embedding 可以将你输入的文本转化成向量。

Question 1

上面的向量代表的是什么意思?

在自然语言处理(NLP)中,文本向量化是一种常见的处理方式。这是将文本转化为计算机可以理解和处理的数值表示形式的过程。

这里的"向量"其实是代表了文本的一种数字化表示。在诸如深度学习这样的机器学习模型中,我们不能直接使用原始的文本数据。因此,我们需要将文本转换成一种机器可以理解的形式,通常这种形式就是向量。这种向量通常会在高维空间中表示,每一个维度都包含了一些有关原始文本的信息。

Embedding 接口所做的,就是把输入的文本转化为这样的向量。这种转化的方式通常根据模型的训练方式有所不同,但通常它会捕捉到文本中的一些关键信息,如语义、语境等。例如,在word2vec这样的模型中,"king"和"queen"这样的词会被映射到向量空间中相近的位置,因为它们在语义上有很强的关联。

简而言之,Embedding 接口就是把输入的文本转化为机器可以理解和处理的向量,这样的向量会包含一些有关原始文本的关键信息。


举个例子让你理解更清楚:

我来举一个简化的例子帮助你理解。

假设我们有一个非常简单的模型,它只知道三个词:'cat', 'dog', 'car'。这个模型会把每个词都表示成一个三维的向量。

在这个简化的模型中,我们可以把每个词都想象成一个三维空间中的一个点。例如,我们可以把 'cat' 表示为向量 (1, 0, 0)'dog' 表示为向量 (0, 1, 0),而 'car' 表示为向量 (0, 0, 1) 。在这个例子中,向量的每一维都对应一个词。当一个词出现时,我们就在对应的维度上标 1,否则标 0。这就是所谓的 one-hot 编码。

当然,真实的词向量模型要比这个复杂得多。在真实的模型中,每个词都会被表示为一个高维空间中的一个点,而不只是三维。而且,这些向量通常不会只有 0 和 1,而是一些实数值。比如,使用 Word2Vec 模型,'cat' 可能会被表示为一个 300 维的向量,如 (0.13, -0.25, 0.07, ..., 0.34)'dog' 可能会被表示为另一个 300 维的向量,如 (-0.07, 0.18, -0.09, ..., -0.29)

最重要的是,在真实的词向量模型中,词与词之间的关系会被捕捉到。例如,在模型中,'cat''dog' 可能会被映射到向量空间中相近的位置,因为它们都是动物。而 'cat''car' 可能会被映射到相对较远的位置,因为它们的含义并不相近。

这就是向量在自然语言处理中的用途。它们允许我们把词语和文本转化为数值表示,从而可以被机器学习模型处理,同时还可以捕捉到语言中的一些结构和关系。

不过到这里,你的疑问可能就来了。不是说现在的大语言模型很厉害吗?传统的自然语言处理问题都可以通过大模型解决。可是用这么简单的两个 API,能够完成原来需要通过各种 NLP 技术解决的问题吗?比如情感分析、文本分类、文章聚类、摘要撰写、搜索,这一系列问题怎么通过这两个接口解决呢?

别急,在接下来的几讲里,我会告诉你,怎么利用大语言模型提供的这两个简单的 API 来解决传统的自然语言处理问题。这一讲我们就先从一个最常见的自然语言处理问题——“情感分析”开始,来看看我们怎么把大语言模型用起来。

1. 传统的二分类方法:朴素贝叶斯与逻辑回归

“情感分析”问题,是指我们根据一段文字,去判断它的态度是正面的还是负面的。

在传统的互联网产品里,经常会被用来分析用户对产品、服务的评价。比如大众点评里面,你对餐馆的评论,在京东买个东西,你对商品的评论,都会被平台拿去分析,给商家或者餐馆的评分做参考。也有些品牌,会专门抓取社交网络里用户对自己产品的评价,来进行情感分析,判断消费者对自己的产品评价是正面还是负面的,并且会根据这些评价来改进自己的产品。

对于“情感分析”类型的问题,传统的解决方案就是把它当成是一个分类问题,也就是先拿一部分评论数据,人工标注一下这些评论是正面还是负面的。

  • 如果有个用户说“这家餐馆真好吃”,那么就标注成“正面情感”。
  • 如果有个用户说“这个手机质量不好”,那么就把对应的评论标注成负面的。

我们把标注好的数据,喂给一个机器学习模型,训练出一组参数。然后把剩下的没有人工标注过的数据也拿给训练好的模型计算一下。模型就会给你一个分数或者概率,告诉你这一段评论的感情是正面的,还是负面的。

我是想知道:喂给一个机器学习模型,训练出一组参数。这个参数是什么参数?太抽象了,我想要具体的讲解示例

让我们以一种最常用的机器学习模型--线性回归为例,来讲解这个过程。

线性回归模型的基本形式为 y = wx + b,其中:

  • y 是我们要预测的目标值,这可能是一个连续的数值(如房价预测)或一个类别标签(如正面或负面的情感)。

  • x 是我们的输入特征,这可能是一个数值(如房屋面积)或一个高维向量(如词嵌入)。

  • wb 是我们的模型参数,它们决定了模型如何从输入特征 x 预测出目标值 yw 被称为权重,b 被称为偏置。它们都是模型需要在训练过程中学习的。

对于一个给定的训练数据集,我们的目标就是找到一组 wb,使得模型对训练数据的预测误差最小。这个过程被称为模型训练,而找到的最优的 wb 就是我们训练出来的参数。

对于一个新的未标注的输入 x(即新的评论),我们就可以用这组训练好的参数 wb 来计算 y(即情感倾向的预测值)。

对于更复杂的模型,如神经网络,虽然模型的形式更复杂,但是基本的思想是相同的:模型内部有很多参数,它们在训练过程中被优化,以最小化模型在训练数据上的预测误差。

这就是机器学习模型训练参数的基本过程。


问题1

y 是目标结果的话,那么如果判断用户是否是好评,是不是就是:好评或者差评就是 y。而其中的 x 是不是就是好评或者差评对应的评价内容,也就是评价内容转换成的向量

在情感分析的问题中,你所说的 y 通常是我们希望预测的目标结果,也就是评价的情感倾向。例如,对于一个二分类的情况(好评或差评),y 可能就被定义为好评是 1,差评是 0。对于多分类情况,y 可能表示多个不同的情感等级。

你所说的 x,确实就是对应的评价内容。但是,因为机器学习模型通常不能直接处理文本数据,所以我们通常需要先将文本数据转换为机器可以理解的数值形式,这种转换过程通常被称为特征工程。在处理文本数据时,最常用的特征工程方法是词嵌入(word embedding),它可以将每个词转换为一个高维的向量。然后,我们可以用这些向量作为 x 输入到模型中。

然后,机器学习模型(例如线性回归或神经网络)会尝试找到一种方式,使得根据输入的特征 x(即词嵌入向量)能够尽可能准确地预测出目标 y(即情感倾向)。在训练过程中,模型会不断调整它的参数 wb,以使得预测误差最小。

问题2

在模型训练中主要调整 w 和 b 的值,是不是其实就是在不停的计算二元一次方程?还是说其他计算方法?

这个理解部分正确。在训练过程中,我们的确在不断调整 w(权重)和 b(偏置)的值。但实际的过程比计算二元一次方程要复杂一些。

首先,尽管线性回归模型的表达式看起来像是一个二元一次方程,但在实际问题中,我们通常有许多输入特征(对应于不同的 x 值),因此 w 实际上是一个向量,而不是一个单一的数值。也就是说,我们实际上在解决一个多元线性回归问题。

其次,模型的训练过程通常使用一种叫做梯度下降的方法来寻找最优的 wb。梯度下降是一种迭代优化算法,用于尝试找到一个函数的局部最小值。在每次迭代中,我们计算模型预测值与真实值之间的误差(通常被称为损失函数),然后根据这个误差来更新 wb 的值。具体来说,我们计算损失函数对 wb 的梯度(即导数),然后将 wb 沿着梯度的反方向更新一小步,以使得损失函数的值降低。然后,我们反复进行这个过程,直到找到一组使得损失函数值最小的 wb

所以说,模型的训练过程并不仅仅是解决一个二元一次方程,而是一个通过梯度下降等优化算法来最小化预测误差的过程。

可以用来做情感分析的模型有很多,这些算法背后都是基于某一个数学模型。比如,很多教科书里,就会教你用朴素贝叶斯算法来进行垃圾邮件分类。朴素贝叶斯的模型,就是简单地统计每个单词和好评差评之间的条件概率。一般来说,如果一个词语在差评里出现的概率比好评里高得多,那这个词语所在的评论,就更有可能是一个差评。

其实,也就是我们经常用来骂人的话,被分为脏话。

例子🌰

在机器学习中,文本分类或情感分析经常用到词语的出现频率来预测一个文本(比如评论)的类别。举例来说,如果一个词语在差评中出现的概率比在好评中高得多,那么这个词语所在的评论就更有可能是一个差评。具体来看以下几个例子:

  1. 中文骂人和赞美:如果一个评论中出现了 "差劲"、"恶心"、"糟糕" 等贬义词,那么这个评论可能是差评。相反,如果评论中出现了 "优秀"、"棒极了"、"赞" 等褒义词,那么这个评论可能是好评。

  2. 英文骂人和赞美:在英文评论中,如果出现了 "terrible"、"horrible"、"worst" 等贬义词,那么这个评论可能是差评。相反,如果评论中出现了 "excellent"、"great"、"fantastic" 等褒义词,那么这个评论可能是好评。

  3. 自由发挥:让我们看一下电影评论。如果评论中出现了 "boring"、"predictable"、"unoriginal" 等词,那么这可能是对电影的负面评价。相反,如果评论中出现了 "captivating"、"innovative"、"riveting" 等词,那么这可能是对电影的正面评价。

这些都是基于词语频率的简单预测,实际的情感分析任务通常会更复杂,因为它需要理解上下文,处理否定词,甚至理解讽刺和其他复杂的语言现象。但是,词语的出现频率可以提供一个很好的起点,并且在许多情况下可以提供有用的信息。

下面是一个简单的表格,列出了上述贬义词和褒义词的英文以及其对应的中文意思:

英文中文情感倾向
terrible糟糕的负面
horrible可怕的负面
worst最差的负面
boring无聊的负面
predictable可预测的负面
unoriginal非原创的负面
excellent优秀的正面
great很棒的正面
fantastic极好的正面
captivating吸引人的正面
innovative创新的正面
riveting扣人心弦的正面

请注意,这些翻译可能并不完全精确,因为词语的确切含义通常取决于上下文。在实际使用时,可能需要考虑词语在特定上下文中的含义。

(P(cx))=P(xc)P©P(x) \Large (P(c∣x))=\frac{P(x|c)P©}{P(x)}

P(cX)P(x1c)×P(x2c)××P(xnc)×P© \large P(c∣X)\propto P(x_1∣c) \times P(x_2∣c) \times \cdot\cdot\cdot \times P(x_n∣c)\times P©

假设我们有一个训练集包含 4 封邮件,其中 2 封是垃圾邮件,2 封是非垃圾邮件。训练集里的邮件包含这些单词。

table
序号邮件类型出现的单词
邮件1垃圾邮件buy, money, offer, secret
邮件2垃圾邮件buy, secret, sell, money
邮件3普通邮件offer, book, sell
邮件4普通邮件book, study, exam

然后来了一封新邮件,里面的单词是:buy、money、sell。

通过这些单词出现的概率,我们很容易就可以预先算出这封邮件是垃圾邮件还是普通邮件。

  • P(buy垃圾)=2÷2=1P(buy∣\text{垃圾})=2÷2=1
  • P(money垃圾)=2÷2=1P(money∣\text{垃圾})=2÷2=1
  • P(sell垃圾)=1÷2=0.5P(sell∣\text{垃圾})=1÷2=0.5
  • P(buy普通)=0÷2=0P(buy∣\text{普通})=0÷2=0
  • P(money普通)=0÷2=0P(money∣\text{普通})=0÷2=0
  • P(sell普通)=1div2=0.5P(sell∣\text{普通})=1div2=0.5
解析概率计算

你给出的这个例子其实是朴素贝叶斯分类器在文本分类(垃圾邮件与普通邮件)中的一个应用。对于给定的单词,我们需要计算在给定邮件类型(垃圾邮件或普通邮件)下,出现这个单词的概率。

例如,"P(buy垃圾)P(buy∣\text{垃圾})"代表的是"在垃圾邮件中出现单词 'buy' 的概率"。在你给出的例子中,有2封垃圾邮件,且两封垃圾邮件中都出现了单词 'buy'。因此,"P(buy垃圾)P(buy∣\text{垃圾})" 的计算公式为:

出现 'buy' 的垃圾邮件数 ÷ 总的垃圾邮件数 = 2 ÷ 2 = 1

同理,"P(money垃圾)P(money∣\text{垃圾})"、"P(sell垃圾)P(sell∣\text{垃圾})"、"P(buy普通)P(buy∣\text{普通})"、"P(money普通)P(money∣\text{普通})"、"P(sell普通)P(sell∣\text{普通})" 的计算过程也是一样的。

需要注意的是,这是一个简化的模型,实际情况下在进行朴素贝叶斯分类时,还需要考虑诸如平滑处理(防止概率为 0 的情况)、词频、文档长度等因素,使得模型能够更好地适应各种数据情况。

然后我们把这封邮件里所有词语的条件概率用全概率公式乘起来,就得到了这封邮件是垃圾邮件还有普通邮件的概率。

P(垃圾X)P(buy垃圾)×P(money垃圾)×P(sell垃圾)×P(垃圾)=1×1×0.5×0.5=0.25P(\text{垃圾}∣X)∝P(buy∣\text{垃圾})×P(money∣\text{垃圾})×P(sell∣\text{垃圾})×P(\text{垃圾})=1×1×0.5×0.5=0.25

P(普通X)P(buy普通)×P(money普通)×P(sell普通)×P(普通)=0×0×0.5×0.5=0P(\text{普通}∣X)∝P(buy∣\text{普通})×P(money∣\text{普通})×P(sell∣\text{普通})×P(\text{普通})=0×0×0.5×0.5=0

全概率公式推导

首先,我们要清楚我们在尝试计算什么。我们想知道给定一封邮件的内容(这里简化为几个关键词),这封邮件是垃圾邮件的概率是多少。用符号表示就是我们想要计算 P(垃圾X)P(\text{垃圾} | X),其中 X 是邮件的内容。

这个问题可以使用贝叶斯定理来解决。贝叶斯定理的公式是:

P(AB)=P(BA)×P(A)P(B)P(A|B) = \frac{P(B|A) \times P(A)}{P(B)}

我们将 "A" 理解为 "邮件是垃圾邮件","B" 理解为 "邮件的内容是 X"。根据贝叶斯定理,我们可以得到:

P(垃圾X)=P(X垃圾)×P(垃圾)P(X)P(\text{垃圾} | X) = \frac{P(X | \text{垃圾}) \times P(\text{垃圾})}{P(X)}

这里,P(X垃圾)P(X | \text{垃圾}) 是给定邮件是垃圾邮件的情况下,邮件内容是 X 的概率;P(垃圾)P(\text{垃圾}) 是任意邮件是垃圾邮件的概率(先验概率);P(X)P(X) 是任意邮件的内容是 X 的概率。

然而,如果 X 包含多个单词(例如 "buy", "money", "sell"),P(X垃圾)P(X | \text{垃圾}) 就变得很难计算了,因为我们要考虑所有这些单词同时出现的概率。这时,我们就要使用朴素贝叶斯分类器的"朴素"假设,即假设所有单词的出现都是独立的。也就是说,一封邮件是垃圾邮件,与邮件中每个单词是否出现是无关的。

根据独立性,我们有:

P(X垃圾)=P(buy,money,sell垃圾)=P(buy垃圾)×P(money垃圾)×P(sell垃圾)P(X | \text{垃圾}) = P(buy, money, sell | \text{垃圾}) = P(buy | \text{垃圾}) \times P(money | \text{垃圾}) \times P(sell | \text{垃圾})

因此,我们可以将 P(垃圾X)P(\text{垃圾} | X) 展开为:

P(垃圾X)=P(buy垃圾)×P(money垃圾)×P(sell垃圾)×P(垃圾)P(X)P(\text{垃圾} | X) = \frac{P(buy | \text{垃圾}) \times P(money | \text{垃圾}) \times P(sell | \text{垃圾}) \times P(\text{垃圾})}{P(X)}

通常情况下,我们并不真正需要计算 P(X)P(X),因为我们只是要比较 P(垃圾X)P(\text{垃圾} | X)P(普通X)P(\text{普通} | X) 的大小。由于 P(X)P(X) 对于所有的类别都是相同的,我们可以忽略它,而只比较分子部分。因此,我们可以将上式简化为:

P(垃圾X)P(buy垃圾)×P(money垃圾)×P(sell垃圾)×P(垃圾)P(\text{垃圾} | X) \propto P(buy | \text{垃圾}) \times P(money | \text{垃圾}) \times P(sell | \text{垃圾}) \times P(\text{垃圾})

同样的,我们也可以计算 P(普通X)P(\text{普通} | X)。然后比较这两个概率,将邮件分类到概率更大的类别中。

在这里,我们发现 P(垃圾X)>P(普通X)P(\text{垃圾}∣X)>P(\text{普通}∣X),而且 P(普通X)P(\text{普通}∣X) 其实等于 0。那如果用朴素贝叶斯算法,我们就会认为这封邮件 100% 是垃圾邮件。如果你觉得自己数学不太好,这个例子没有看明白也没有关系,因为我们接下来的 AI 应用开发都不需要预先掌握这些数学知识,所以不要有心理负担。

类似的,像逻辑回归、随机森林等机器学习算法都可以拿来做分类。你在网上,特别是 Kaggle 这个机器学习比赛的网站里,可以搜索到很多其他人使用这些传统方法来设计情感分析的解决方案。这些方案都以 Jupyter Notebook 的形式出现,我在这里放个链接open in new window,你有兴趣的话也可以去研究一下。

2. 传统方法的挑战:特征工程与模型调参

但这些传统的机器学习算法,想要取得好的效果,还是颇有门槛的。除了要知道有哪些算法可以用,还有两方面的工作非常依赖经验。

2.1 特征工程

第一个是特征工程。对于很多自然语言问题,如果我们只是拿一段话里面是否出现了特定的词语来计算概率,不一定是最合适的。比如“这家餐馆太糟糕了,一点都不好吃”和 “这家餐馆太好吃了,一点都不糟糕”这样两句话,从意思上是完全相反的。但是里面出现的词语其实是相同的。在传统的自然语言处理中,我们会通过一些特征工程的方法来解决这个问题。

比如,我们不只是采用单个词语出现的概率,还增加前后两个或者三个相连词语的组合,也就是通过所谓的 2-Gram(Bigram 双字节词组)和 3-Gram(Trigram 三字节词组)也来计算概率。在上面这个例子里,第一句差评,就会有“太”和“糟糕”组合在一起的“太糟糕”,以及“不”和“好吃”组合在一起的“不好吃”。而后面一句里就有“太好吃”和“不糟糕”两个组合。有了这样的 2-Gram 的组合,我们判断用户好评差评的判断能力就比光用单个词语是否出现要好多了。

这样的特征工程的方式有很多,比如 去除停用词 ,也就是“的地得”这样的词语,去掉过于低频的词语,比如一些偶尔出现的专有名词。或者对于有些词语特征采用 TF-IDF(词频 - 逆文档频率)这样的统计特征,还有在英语里面对不同时态的单词统一换成现在时。

不同的特征工程方式,在不同的问题上效果不一样,比如我们做情感分析,可能就需要保留标点符号,因为像“”这样的符号往往蕴含着强烈的情感特征。但是,这些种种细微的技巧,让我们在想要解决一个简单的情感分析问题时,也需要撰写大量文本处理的代码,还要了解针对当前特定场景的技巧,这非常依赖工程师的经验。

2.2 机器学习相关经验

第二个就是你需要有相对丰富的机器学习经验。除了通过特征工程设计更多的特征之外,我们还需要了解很多机器学习领域里常用的知识和技巧。比如,我们需要将数据集切分成训练(Training)、验证(Validation)、测试(Test)三组数据,然后通过 AUC 或者混淆矩阵(Confusion Matrix)来衡量效果。如果数据量不够多,为了训练效果的稳定性,可能需要采用 K-Fold 的方式来进行训练。

如果你没有接触过机器学习,看到这里,可能已经看懵了。没关系,上面的大部分知识你未来可能都不需要了解了,因为我们有了大语言模型,可以通过它提供的 Completion 和 Embedding 这两个 API,用不到 10 行代码就能完成情感分析,并且能获得非常好的效果。

3. 大语言模型:20 行代码的情感分析解决方案

通过大语言模型来进行情感分析,最简单的方式就是利用它提供的 Embedding 这个 API。这个 API 可以把任何你指定的一段文本,变成一个大语言模型下的向量,也就是用一组固定长度的参数来代表任何一段文本。

我们需要提前计算“好评”和“差评”这两个字的 Embedding。而对于任何一段文本评论,我们也都可以通过 API 拿到它的 Embedding。那么,我们把这段文本的 Embedding 和“好评”以及“差评”通过余弦距离(Cosine Similarity)计算出它的相似度。「弧度类似,其实代表这两个数据类似」

然后我们拿这个 Embedding 和“好评”之间的相似度,去减去和“差评”之间的相似度,就会得到一个分数。如果这个分数大于 0,那么说明我们的评论和“好评”的距离更近,我们就可以判断它为好评。如果这个分数小于 0,那么就是离差评更近,我们就可以判断它为差评。

文本与好评、差评的相似度。接着使用:好评相似度 - 差评相似度,得到差值判断大小。

相似度高 - 相似度低 > 0 好评

相似度高 - 相似度低 < 0 差评

下面我们就用这个方法分析一下两条在京东上购买了 iPhone 用户的评论。

这个使用大模型的方法一共有 20 行代码,我们看看它能否帮助我们快速对这两条评论进行情感分析。

import openai
import os
from openai.embeddings_utils import cosine_similarity, get_embedding

# 获取访问 open ai 的密钥
openai.api_key = os.getenv("OPENAI_API_KEY")
# 选择使用最小的 ada 模型
EMBEDDING_MODEL = "text-embedding-ada-002"

# 获取"好评"和"差评"的
positive_review = get_embedding("好评")
negative_review = get_embedding("差评")

positive_example = get_embedding("买的银色版真的很好看,一天就到了,晚上就开始拿起来完系统很丝滑流畅,做工扎实,手感细腻,很精致哦苹果一如既往的好品质")
negative_example = get_embedding("降价厉害,保价不合理,不推荐")

def get_score(sample_embedding):
  return cosine_similarity(sample_embedding, positive_review) - cosine_similarity(sample_embedding, negative_review)

positive_score = get_score(positive_example)
negative_score = get_score(negative_example)

print("好评例子的评分 : %f" % (positive_score))
print("差评例子的评分 : %f" % (negative_score))

输出结果:

好评例子的评分 : 0.070963
差评例子的评分 : -0.081472

正如我们所料,京东上的好评通过 Embedding 相似度计算得到的分数是大于 0 的,京东上面的差评,这个分数是小于 0 的。

这样的方法,是不是特别简单?我们再拿刚才的例子试一下,看看这个方法是不是对所有词语都管用,只是出现的位置不同但含义截然相反的评论,能得到什么样的结果。

good_restraurant = get_embedding("这家餐馆太好吃了,一点都不糟糕")
bad_restraurant = get_embedding("这家餐馆太糟糕了,一点都不好吃")

good_score = get_score(good_restraurant)
bad_score = get_score(bad_restraurant)
print("好评餐馆的评分 : %f" % (good_score))
print("差评餐馆的评分 : %f" % (bad_score))

输出结果:

好评餐馆的评分 : 0.062719
差评餐馆的评分 : -0.074591

可以看到,虽然两句话分别是“太好吃”“不糟糕”和“太糟糕”“不好吃”,其实词语都一样,但是大语言模型一样能够帮助我们判断出来他们的含义是不同的,一个更接近好评,一个更接近差评。

4. 更大的数据集上的真实案例

在这里,我们只举了几个例子,看起来效果还不错。这会不会只是我们运气好呢?我们再来拿一个真实的数据集验证一下,利用这种方法进行情感分析的准确率能够到多少。

下面这段代码,是来自 OpenAI Cookbook 里面的一个例子。它是用同样的方法,来判断亚马逊提供的用户对一些食物的评价,这个评价数据里面,不只有用户给出的评论内容,还有用户给这些食物打了几颗星。这些几颗星的信息,正好可以拿来验证我们这个方法有多准。对于用户打出 1~2 星的,我们认为是差评,对于 4~5 星的,我们认为是好评。

我们可以通过 Pandas,将这个 CSV 数据读取到内存里面。为了避免重新调用 OpenAI 的 API 浪费钱,这个数据集里,已经将获取到的 Embedding 信息保存下来了,不需要再重新计算。

import pandas as pd
import numpy as np

from sklearn.metrics import classification_report

datafile_path = "data/fine_food_reviews_with_embeddings_1k.csv"

df = pd.read_csv(datafile_path)
df["embedding"] = df.embedding.apply(eval).apply(np.array)

# 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"})

每一条评论都用我们上面的方法,和一个预先设定好的好评和差评的文本去做对比,然后看它离哪个近一些。这里的好评和差评,我们写得稍微长了一点,分别是 “An Amazon review with a negative sentiment.” 和 “An Amazon review with a positive sentiment.”。

在计算完结果之后,我们利用 Scikit-learn 这个机器学习的库,将我们的预测值和实际用户打出的星数做个对比,然后输出对比结果。需要的代码,也就不到 20 行。

from sklearn.metrics import PrecisionRecallDisplay

def evaluate_embeddings_approach(
    labels = ['negative', 'positive'], 
    model = EMBEDDING_MODEL,
):
    label_embeddings = [get_embedding(label, engine=model) for label in labels]

    def label_score(review_embedding, label_embeddings):
        return cosine_similarity(review_embedding, label_embeddings[1]) - cosine_similarity(review_embedding, label_embeddings[0])

    probas = df["embedding"].apply(lambda x: label_score(x, label_embeddings))
    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(labels=['An Amazon review with a negative sentiment.', 'An Amazon review with a positive sentiment.'])

输出结果:

              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

在结果里面可以看到,我们这个简单方法判定的好评差评的精度,也就是 precision 在 negative 和 positive 里,分别是 0.98 和 0.96,也就是在 95% 以上。

而召回率,也就是图里的 recall,在差评里稍微欠缺一点,只有 73%,这说明还是有不少差评被误判为了好评。不过在好评里,召回率则是 100%,也就是 100% 的好评都被模型找到了。这样综合考虑下来的整体准确率,高达 96%。而要达到这么好的效果,我们不需要进行任何机器学习训练,只需要几行代码调用一下大模型的接口,计算一下几个向量的相似度就好了。

6. 小结

这一讲,我们利用不同文本在大语言模型里 Embedding 之间的距离,来进行情感分析。这种使用大语言模型的技巧,一般被称做零样本分类(Zero-Shot Classification)。

所谓零样本分类,也就是我们不需要任何新的样本来训练机器学习的模型,就能进行分类。我们认为,之前经过预训练的大语言模型里面,已经蕴含了情感分析的知识。我们只需要简单利用大语言模型里面知道的“好评”和“差评”的概念信息,就能判断出它从未见过的评论到底是好评还是差评。

这个方法,在一些经典的数据集上,轻易就达到了 95% 以上的准确度。同时,也让一些原本需要机器学习研发经验才能完成的任务变得更加容易,从而大大降低了门槛。

如果你所在的公司今天想要做一个文本分类的应用,通过 OpenAI 的 API 用几分钟时间就能得到想要的结果。

7. 课后练习

利用大模型来解决问题,不是一个理论问题,而是一个实战问题。请你尝试将这个方法运用在别的数据集上,看看是否也会有很好的效果?比如,你可以试着用 Kaggle 提供的亚马逊耳机类商品的评论数据,看看结果是怎样的?这个数据集比较大,你可以挑几条数据试一下。因为 OpenIA 会对你的调用限速,免费的 Token 数量也比较有限。对于限速情况下的大批量数据的处理,我们会在第 04 讲讲解。

数据集链接:https://www.kaggle.com/datasets/shitalkat/amazonearphonesreviewsopen in new window

8. 评论

  1. 大模型是怎么知道好评和差评是代表什么意思的呢?

    作者回复: 它是根据海量的预训练数据在高维空间里给他们两个找了个位置,我们只是我们拿到的评论离哪个位置近。 大模型的语义理解能力,就没法用一两句话说清了。如果真的想要知道,还是要深入理解深度学习,可以去看看李沐老师的论文解读系列。https://www.bilibili.com/video/BV1AF411b7xQ/open in new window

  2. 跑情感分析的部分,可能需要额外安装这些包

    conda install -c conda-forge matplotlib
    conda install -c conda-forge plotly   
    conda install -c anaconda scikit-learn
    
  3. “我们把这段文本的 Embedding 和“好评”以及“差评”通过余弦距离(Cosine Similarity)计算出它的相似度”,不太懂

    作者回复: 不理解什么是向量的余弦距离,可以问问ChatGPT: 什么是向量的余弦距离? 向量的余弦距离是用于度量两个向量之间的相似性的一种方法。它衡量的是两个向量之间的夹角的余弦值。 具体来说,假设有两个n维向量a和b,其余弦距离为cos(θ),其中θ是a和b之间的夹角,计算公式如下: cos(θ) = (a·b) / (||a|| ||b||) 其中,a·b表示向量a和向量b的点积,||a||和||b||分别表示向量a和向量b的范数。 余弦距离的取值范围在[-1, 1]之间,当两个向量之间的夹角越小时,余弦距离越接近1,表示两个向量越相似;当两个向量之间的夹角越大时,余弦距离越接近-1,表示两个向量越不相似。当夹角为90度时,余弦距离为0,表示两个向量完全不相关。

  4. 文本向量 positive 和 negative 标签是 openai 训练标注好的,那还有其他什么标签吗?只有这两种吗?

    作者回复: 这不是预先训练好的标签。而是模型根据文本,计算出来的向量,我们只是利用了向量的相似度而已。 你可以用任意的单词,或者句子来做这样的功能,比如 beautiful 和 ugly,polite 和 rude 都可以试一试

  5. 知乎上张俊林对GPT对NLP的影响做了很好解读,我这个小白听了也能有很深的理解。 他把NLP解决的问题分为中间任务和最终任务,先阐述中间任务其实是人类没有找到更好办法处理自然语言时想出来的辅助工具,GPT的出现一瞬间让这些脚手架都没了用处。 再进一步,他又把最终任务分为自然语言理解和自然语言生成任务,进一步分析GPT对这两类任务的影响。 很简洁优美的讲清楚了GPT对NLP的冲击,知乎地址我放下面,感兴趣的可以自己看。 https://zhuanlan.zhihu.com/p/597586623open in new window

  6. 请教老师几个问题:

    Q1:01课中的Jupyter Labs 开发环境是python的开发环境吗?(对python不了解,0基础,小白)

    Q2:01课中的Jupyter Labs 开发环境搭建,复杂吗?如果复杂的话,能否麻烦老师写一个手册一类的文档?最好详细一点。(一直觉得搭环境很麻烦;尤其对不熟悉的python,容易出错,容易有挫折感。)。 环境搭不好,后面的例子无法实践。

    Q3:Cookbook例子,为什么用Scikit估计结果,应该都用openai来完成啊。例子中用Scikit,那相当于“openai + Scikit”结合来判断出最后的星标了,openai这么强大,还需要用Scikit吗?

    Q4:chatGPT搜索或生成ppt的能力如何? 比如我要做一个软件的产品介绍性质的ppt,用来给客户介绍该软件。以matlab为例,我托国外同事这样搜“matlab 产品介绍 ppt”,并没有搜到合适的ppt,后来只好在百度上搜索了。请问,是我们的使用方法不对?还是说chatGPT没有这方面的能力?

    作者回复: Q1: 是的,一个交互式可以看到输出结果,图片等各种功能的开发环境 Q2: 可以直接使用在线的Colab,就不需要自己搭建了,可以看一下第一讲。搭建的复杂程度每个人感觉不一样。 Q3:并不是所有的问题都适合用OpenAI的API来完成。我们需要考虑成本,OpenAI的Embedding接口很便宜,但是Completion接口就会贵一些(即使是gpt-3.5-turbo),也要考虑效果,有标注数据的情况下,利用标注会比只用大模型效果更好 Q4:制作PPT,我们可以期待一下微软最新的发布会里介绍的 Office Copilot,你只要给它合适的资料,可以自动生成PPT。你可以试一下 beta.tome.app 是一个创业工作做的通过AI创建PPT的产品。

  7. 代码

    import pandas as pd 
    import numpy as np 
    
    from sklearn.metrics import classification_report , PrecisionRecallDisplay
    from openai.embeddings_utils import cosine_similarity , get_embedding
    
    # 选择用于情感分析的模型
    EMBEDDING_MODEL = "text-embedding-ada-002"
    # 设置数据集文件路径
    datafile_path = "data/AllProductReviews.csv"
    # 读取 csv 文件 只取前60行
    df = pd.read_csv(datafile_path,nrows=60)
    # 新增一列 embedding 用于表示获取每行的ReviewBody向量值
    df["embedding"] = df.ReviewBody.apply(lambda x:get_embedding(x,engine=EMBEDDING_MODEL))
    
    # 除去星级为3的评论来进行划分好评还是差评两个标签
    df = df[df.ReviewStar != 3]
    df["sentiment"] = df.ReviewStar.replace({1: "negative", 2: "negative", 4: "positive", 5: "positive"})
    # 函数 evaluate_embeddings_approach 默认会有两个参数 label 和 model 都有对应的默认值
    def evaluate_embeddings_approach(
        labels = ['negative', 'positive'],
        model = EMBEDDING_MODEL,
    ):
        # 计算每个 label 的向量值
        label_embeddings = [get_embedding(label, engine=model) for label in labels]
        # 辅助函数 用于计算好评和review_embedding与差评和review_embedding向量余弦值差值
        def label_score(review_embedding, label_embeddings):
            return cosine_similarity(review_embedding, label_embeddings[1]) - cosine_similarity(review_embedding, label_embeddings[0])
        # 这里更新下 embedding 列 更新成对应的 分数
        probas = df["embedding"].apply(lambda x: label_score(x, label_embeddings))
        # 预测值
        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()
    
  8. negative_example = get_embedding("随意降价,不予价保,服务态度差")

    差评例子的评分 : -0.081472

    negative_example = get_embedding("随意降价,不予价保,服务态度差,但是后续换了个客服,服务态度好多了")

    差评例子的评分 : -0.048267

    negative_example = get_embedding("随意降价,不予价保,服务态度差,但是后续换了个客服,服务态度好多了。经过沟通解决了所有问题,好评!")

    差评例子的评分 : -0.009858

    通过例子看到,第三句话很明显我已经是好评了,但是模型还是给了差评的结论。是不是排在前面的句子权重更大啊?

    作者回复: 不能简单根据内容排在前面后面来判定。 这样的例子本身的确人来判断也很难明确定义算好还算坏,从分数来看也比另外两个更接近0.

  9. 老师,请问一下“更大的数据集上的真实案例”这个案例的数据集在哪里?读文章不知道数据集长什么样子,看代码有点没有理解到。

    作者回复: 我们就是拿了Amazon的Food review的一千条数据呀,原始数据在 https://www.kaggle.com/snap/amazon-fine-food-reviewsopen in new window 可以找到 对应的一千条数据,可以在 https://github.com/xuwenhao/geektime-ai-course/tree/main/data,你可以下载下来直接看。open in new window

  10. 没有提供 亚马逊食物评价的连接罢,最后面提供的是 耳机的评价

    作者回复: 耳机评价是作业 食物评价的数据,可以参看notebook所在代码库的README https://github.com/xuwenhao/geektime-ai-courseopen in new window 链接: https://pan.baidu.com/s/1Cl0eFNLOkQqquf9ls0trEwopen in new window 提取码: jvr4

欢迎关注我公众号:AI悦创,有更多更好玩的等你发现!

公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度