斯坦福-CS124-从信息到语言课程笔记-全-

斯坦福 CS124 从信息到语言课程笔记(全)

课程 P1:L1.1 - 正则表达式 📝

在本节课中,我们将要学习文本处理。正则表达式是文本处理中最基础、最核心的工具。

概述

正则表达式是一种用于指定文本字符串的形式语言。假设我们需要在文本文档中查找“woodchuck”这个词。这个词可能有多种写法,例如单数形式、复数形式(带s)、首字母大写或小写,以及这些形式的任意组合。我们需要工具来处理这类问题。

基本工具:析取

正则表达式中最简单的工具是析取。方括号 [] 在正则表达式模式中表示匹配括号内的任意一个字符。

例如,模式 [Ww]oodchuck 可以匹配 woodchuckWoodchuck。同样,[1234567890] 可以匹配任意一个数字。连续书写所有数字很繁琐,因此我们可以使用范围表示法。

  • [0-9] 表示匹配 09 之间的任意一个数字。
  • [A-Z] 表示匹配 AZ 之间的任意一个大写字母。

让我们看看具体如何工作。


这里有一个使用正则表达式工具 RegExr 进行搜索的例子。我们有一段来自苏斯博士的文本:“we looked, and we saw him step in on the mat. we looked and we saw him the cat in the hat.”

我们可以尝试析取操作:

  • 模式 [Ww] 可以匹配所有大写 W 和小写 w
  • 模式 [EM] 可以匹配所有 EM
  • 使用范围:模式 [A-Z] 匹配所有大写字母。
  • 模式 [a-z] 匹配所有小写字母。
  • 模式 [A-Za-z0-9] 匹配所有字母数字字符。请思考一下如何匹配所有字母数字字符。


我们也可以匹配一些非字母数字字符。例如,模式 [ !] 可以匹配空格和感叹号,如图所示。

否定

在析取中,我们有时需要否定,即排除某些字符集。例如,我们想匹配“非大写字母”。这可以通过在方括号内使用脱字符 ^ 来实现。

  • [^A-Z] 表示匹配任何不是大写字母 AZ 的字符。
  • [^Aa] 表示匹配既不是大写 A 也不是小写 a 的字符。
  • [^E^] 表示匹配不是 E 也不是 ^ 的字符。

注意,脱字符 ^ 紧接在开方括号 [ 之后才表示“否定”,在其他位置就只是一个普通的 ^ 字符。



让我们看看具体示例:

  • 模式 [^A-Z] 匹配所有非大写字母。
  • 模式 [^!] 匹配大多数字符(非感叹号)。
  • 模式 [^A-Za-z] 匹配所有非字母字符,如图所示,只有空格和感叹号被匹配。
  • 模式 ^ 尝试匹配脱字符本身,但文本中没有,所以没有匹配项。


管道符号析取

另一种用于更长字符串的析取方式是管道符号 |,有时也称为“或”操作符。

  • groundhog|woodchuck 表示匹配字符串 groundhogwoodchuck
  • 管道符号有时可以实现与方括号类似的功能,例如 a|b|c 等同于 [abc]

我们可以组合使用这些符号。例如,[Gg]roundhog|[Ww]oodchuck 可以匹配首字母大小写不同的“groundhog”或“woodchuck”。

在我们的例子中:

  • 模式 looked|step 可以匹配单词“looked”和“step”。
  • 模式 at|ook 可以匹配所有包含“at”或“ook”的字符串片段。

特殊字符与运算符

正则表达式中有几组非常重要的特殊字符。

  • 问号 ?:表示前一个字符是可选的。
    • 例如:colou?r 可以匹配 color(没有 u)或 colour(有 u)。
  • 克莱尼星号 *(以 Stephen Kleene 命名):匹配前一个字符的零次或多次出现。
    • 例如:O*h! 可以匹配 Oh!OOh!OOOh! 等。
  • 克莱尼加号 +:匹配前一个字符的一次或多次出现。
    • 例如:O+h! 可以匹配 Oh!OOh!OOOh! 等,但不能匹配 h!
  • 点号 .:匹配任意单个字符(除换行符外,取决于模式)。
    • 例如:beg.n 可以匹配 beginbeg3nbeg n 等。
  • 脱字符 ^(在方括号外):匹配行的开头。
    • 例如:^[A-Z] 匹配行首的大写字母。
  • 美元符号 $:匹配行的结尾。
    • 例如:[A-Z]$ 匹配行尾的大写字母。
  • 转义字符 \:由于点号 . 是特殊字符,如果要匹配字面意义上的句点,需要使用反斜杠转义。
    • 例如:\. 匹配一个真正的句点字符,而 . 匹配任意字符。

让我们看一些例子:

  • 模式 O+ 匹配一个或多个连续的 O
  • 模式 ^[A-Z] 匹配行首的大写字母。
  • 模式 [A-Z]$ 匹配行尾的大写字母(本例中没有)。
  • 模式 [!.]$ 匹配行尾的感叹号或句点。
  • 模式 \. 匹配所有句点。如果不转义,模式 . 会匹配所有字符。

综合示例:查找单词“the”

让我们通过一个具体例子来实践。假设我们有句子:“The other one there, the blithe one.” 我们想找出其中所有的单词“the”。

  1. 初步尝试:最简单的模式是 the。它能找到小写的“the”,但会漏掉大写的“The”,同时也会错误匹配“there”和“blithe”中的“the”。
  2. 解决大小写问题:使用析取 [Tt]he。现在可以匹配“The”了,但“there”和“blithe”的问题依然存在。
  3. 提高精确度:我们需要确保“the”前后没有字母字符(即它是一个独立的单词)。我们可以使用非字母字符来界定。
    • 首先,确保后面是非字母:[Tt]he[^A-Za-z]。这解决了“there”的问题(因为‘r’是字母),但“blithe”的问题还在(因为前面是字母‘l’)。
    • 然后,确保前面也是非字母:[^A-Za-z][Tt]he[^A-Za-z]。现在,我们成功匹配了所有独立的“the”单词。

这个过程中,我们处理了两种错误:

  • 假阳性(Type I 错误):匹配了不该匹配的字符串(如“there”)。通过增加模式的精确度(Precision) 来减少此类错误。
  • 假阴性(Type II 错误):漏掉了该匹配的字符串(如大写的“The”)。通过增加模式的召回率(Recall) 来减少此类错误。

在自然语言处理中,我们经常需要在这两类错误之间进行权衡和优化。


总结

本节课中我们一起学习了正则表达式的基础知识。

正则表达式在文本处理中扮演着极其重要的角色。我们看到的这些简单正则表达式序列,通常是任何文本处理任务的初级模型。对于更复杂的任务,我们将会使用更强大的机器学习分类器。但即使在那时,正则表达式也常被用作分类器的特征,对于捕捉文本中的一般化模式非常有用。

因此,在后续的学习和实践中,你会反复用到正则表达式。

课程 P10:L2.4- 加权的最小编辑距离 🧮

在本节课中,我们将要学习加权的最小编辑距离。我们将探讨为何需要为编辑操作赋予不同的权重,以及如何在算法中实现这种加权计算。通过具体的应用场景,如拼写纠错和生物学序列分析,我们将理解加权编辑距离的实际意义。


最小编辑距离可以被赋予权重。为什么我们要在计算编辑距离时加入权重呢?

考虑拼写纠错等特定应用场景。很明显,某些字母被误输入的可能性比其他字母更高。在生物学领域,由于学科本身的限制,某些类型的删除或插入操作也更为常见。

例如,在拼写领域:

上图展示了一个拼写错误的混淆矩阵。观察这个矩阵可以发现,字母 E 很容易与 AO 混淆。元音字母之间容易混淆。但字母 AB 混淆的可能性则非常低。因此,人们常犯的拼写错误具有系统性。

这种系统性不仅体现在元音字母之间,还受到键盘布局的影响。人们更可能用另一只手的同源手指或按错相邻的按键。因此,在特定领域(如拼写或生物学)的限制下,某些编辑操作会比其他的更可能发生。

为了体现这一点,我们将对算法稍作修改,引入权重。在莱文斯坦距离中,插入和删除的成本是1,替换的成本是2。在加权最小编辑距离中,我们只需在每次操作时查找一个特定的成本值。

以下是算法的核心修改:

  • 初始化:不再为每个删除操作简单地加1,而是使用每个被删除符号的实际删除成本,并将所有删除成本累加。
  • 插入操作:同样,不再为每个插入操作加1,而是使用每个插入符号的插入成本,并将它们累加。
  • 递推关系:在递推关系中,我们将加入特定的删除、插入和替换成本。即,删除特定字符、插入特定字符或进行替换操作的成本是多少。
  • 终止条件:最终,我们使用相同的终止条件。

我们将使用独立的小型查找表来记录每个符号的删除、插入和替换成本。

顺便提一下,“动态规划”这个名字从何而来?以下是理查德·贝尔曼自传中的一些引述。贝尔曼是动态规划的发明者。有趣的是,他告诉我们,他提出“动态规划”这个名字,实际上是一种公关手段,目的是让这个算法听起来更激动人心。这可能是最早为了品牌化、让算法听起来更吸引人而被命名的算法之一。

这就是我们加权最小编辑距离的算法。


本节课中,我们一起学习了加权最小编辑距离的概念。我们了解到,在实际应用中,不同的编辑操作具有不同的可能性或“成本”,因此需要为它们分配权重。通过修改标准的最小编辑距离算法,引入基于查找表的特定操作成本,我们可以更精确地计算两个序列之间的差异,从而在拼写纠错、生物信息学等领域得到更符合实际的结果。

课程 P11:L2.5 - 计算生物学中的最小编辑距离 🧬

在本节课中,我们将要学习最小编辑距离在计算生物学领域中的一些高级变体。这些变体在分析生物序列(如核苷酸或蛋白质序列)时扮演着特殊角色。我们将了解如何调整标准算法以适应生物学中的特定需求,例如处理序列重叠或寻找局部相似区域。

计算生物学中的序列比对

上一节我们介绍了最小编辑距离的基本概念,本节中我们来看看它在计算生物学中的具体应用。

在计算生物学中,我们比对的是核苷酸序列或蛋白质序列。我们的任务是处理两个字符串,并生成一个类似下图的比对结果。

这在生物学中非常重要,原因有很多。我们可以用于在基因组中寻找特定区域、发现基因功能、通过比较不同物种来研究进化关系。此外,在DNA测序和片段组装中,我们也需要寻找重叠片段并进行匹配,或者通过比较个体来寻找突变位点,发现序列间的相似与差异。

从距离到相似度

在自然语言处理中,我们通常讨论距离,目标是最小化距离,并为操作分配权重

然而,在计算生物学中,我们更常讨论相似度,目标是最大化相似度。我们关注两个事物有多相似,因此我们尝试最大化某个值,并且通常使用分数而非权重来描述。

尼德曼-翁施算法

在计算生物学中,我们刚刚看过的最小编辑距离标准算法被称为尼德曼-翁施算法

下图展示了该算法,它与我们之前看到的算法本质相同。通常,我们用 D 表示插入和删除的成本,并用一个小的 S 值表示替换的正负分值。在生物学中,通常为匹配项分配正分,为插入和删除操作分配成本(负分)。

以下是尼德曼-翁施算法的动态规划矩阵。请注意,与自然语言处理中的一般做法不同,在计算生物学中,我们通常将原点放在左上角

重叠检测变体

首先,让我们看看计算生物学中一些重要的算法变体。其中一种情况是允许在字符串的开头和结尾存在无限制的间隙

这通常发生在当我们有两小段DNA序列,并且知道其中一段的端点可能与另一段的端点重叠,但其他位置可能没有关联时。例如,这里有一条长序列,另一条也是长序列,但可能只有这一部分与那一部分重叠。我们不希望因为序列开头或结尾存在不匹配的内容而受到惩罚,因此我们希望修改算法,使其不惩罚末端的间隙

实际上,可能存在多种不同类型的此类重叠。这可能发生在进行测序并处理重叠读数时,或者当我们在一段较大的序列中寻找某个基因片段时。

用于重叠检测的动态规划算法变体,即重叠检测变体,只对标准算法进行少量修改。

以下是该算法的修改步骤。

首先,我们改变初始化条件,使得从一个长字符串开始并删除或插入所有内容时成本为零

在标准算法中,第一行和第一列的初始化公式为 -i * D-j * D。在重叠检测变体中,我们去除了这些惩罚项,因为允许比对路径从矩阵中间某个点(即重叠开始处)以零成本开始。这样,我们就不必为匹配序列开头之前的所有内容而受罚。

其次,终止条件也发生变化。我们不再从矩阵的右下角开始回溯,因为允许比对不必延伸到序列末端。相反,我们会在最后一行或最后一列中寻找分数最大值的位置,并从那里开始回溯。

局部比对问题

与尼德曼-翁施算法或用于字符串距离的标准动态规划算法类似的另一个扩展是局部比对问题

假设我们有两个字符串:X(长度为 m)和 Y(长度为 n)。我们希望找到两个子串,使得它们的相似度最大。例如,在 XY 中,我们可能希望找出相似度最高的子串,比如 CCGGG

这与我们之前看到的重叠检测变体非常相似,但不同之处在于,它不仅允许我们在开头和结尾忽略未比对序列,而且允许在任何位置忽略。也就是说,最佳比对可以完全出现在序列中间的某个区域。

为了修改尼德曼-翁施算法以允许任何类型的局部比对,新版本被称为史密斯-沃特曼算法

首先,与重叠检测变体一样,我们将初始化条件改为零,即不因起始字符串而惩罚自己。

其次,我们将进行一项关键修改:在计算每个单元格时,我们不仅会从三个来源(上方、左方、左上方)中选择最大值,还会将 0 作为一个选项加入比较。公式如下:

score[i][j] = max(0, score[i-1][j-1] + s(x_i, y_j), score[i-1][j] + D, score[i][j-1] + D)

其中,s(x_i, y_j) 是字符 x_iy_j 的匹配分数,D 是间隙惩罚(通常为负值)。

这样做的生物学意义是,当我们谈论最大化相似度时,如果某些区域差异很大导致分数变得非常负,我们可以选择从零重新开始,相当于丢弃那些完全无法比对的区域

史密斯-沃特曼算法的终止

史密斯-沃特曼算法的终止条件取决于我们的目标。

  • 如果我们只想找到最佳的局部比对,我们会在整个矩阵中寻找分数最大值的位置,并从那里开始回溯。
  • 如果我们想找到所有分数超过某个阈值 T 的局部比对,那么我们会找到所有分数大于 T 的位置,并分别从这些位置回溯。

这里可能会因为存在重叠的局部比对而变得复杂。但若只寻找最佳局部比对,则相对简单。

局部比对示例

让我们看一个局部比对的例子。假设规则是:两个符号匹配得 +1 分,任何删除、插入或替换操作得 -1 分。

现在,寻找字符串 ATC AA ATC 之间的所有局部比对。

首先,因为进行局部比对,我们将矩阵的第一行和第一列初始化为0。

填充矩阵后,我们寻找可以作为回溯起点的、具有最大值的单元格。这里我们发现两个这样的单元格。

  • 其中一个对应比对 A T C A TA T T A T。这里有四个匹配和一个不匹配,因此相似度分数为 3
  • 另一个对应比对 ATCATC。这里有三个匹配符号,分数也是 3

总结

本节课中,我们一起学习了最小编辑距离在计算生物学中的几种高级变体。

我们首先回顾了计算生物学中序列比对的基本目标,并理解了从“最小化距离”到“最大化相似度”的思维转变。接着,我们深入探讨了两种重要的算法变体:

  1. 重叠检测变体:通过修改初始化和终止条件,允许在序列开头和结尾存在无惩罚的间隙,适用于处理可能重叠的序列片段,如DNA测序读数。
  2. 局部比对问题与史密斯-沃特曼算法:通过将0纳入动态规划递推公式的最大值比较,并修改初始化,使得算法能够找出两个序列内部相似度最高的子区域,这对于在长序列中寻找保守的功能域或相似片段至关重要。

这些变体扩展了最小编辑距离的应用范围,使其成为计算生物学中序列分析不可或缺的工具。

📚 课程 P12:L3.1 - N-gram 语言模型介绍

在本节课中,我们将要学习自然语言处理中一个非常重要的主题——语言建模。我们将了解语言模型的目标、其应用场景,并重点介绍一种简单而强大的建模方法:N-gram 模型。


🎯 语言模型的目标

语言模型的目标是为一个句子或一个词序列分配一个概率。

我们为何需要为句子分配概率?这出现在各种应用中。

在机器翻译中,我们希望通过概率来区分好的翻译和坏的翻译。例如,“high winds tonight” 可能比 “large winds tonight” 是更好的翻译,因为 “high winds” 在语言中搭配得更好。

在拼写纠错中,我们看到像 “15 minues from my house” 这样的短语。这很可能是 “minutes” 的拼写错误。让我们做出这个判断的一条信息是,“15 minutes from” 这个短语比 “15 minues from” 出现的可能性大得多。

在语音识别中,像 “I saw a van” 这样的短语,比一个听起来语音相似但可能性低得多的短语 “eyes awe of an” 更可能出现。

事实证明,语言建模在摘要生成、问答系统等几乎所有自然语言处理任务中都扮演着重要角色。


🔍 如何计算句子概率

一个语言模型的目标是计算一个句子或一个词序列的概率。

给定一个词序列 W₁ 到 W_N,我们要计算它们的概率 P(W)。我们用大写 W 表示从 W₁ 到 W_N 的序列。

这与计算下一个词出现的概率任务密切相关。所以,P(W₅ | W₁, W₂, W₃, W₄) 与计算 P(W₁, W₂, W₃, W₄, W₅) 高度相关。

一个能计算这两者之一——要么是 P(W)(整个序列的联合概率),要么是给定前序词时最后一个词的条件概率——的模型,我们称之为语言模型。技术上,这告诉我们这些词组合在一起有多好,我们通常用“语法”这个词来描述,但“语言模型”(常缩写为 LM)已成为标准术语。


⛓️ 利用概率链式法则

那么,我们如何计算这个联合概率呢?例如,我们想计算短语 “its water is so transparent that” 的概率。

语言建模的直觉是,我们将依赖于概率的链式法则。让我们回顾一下条件概率的定义。

P(A | B) = P(A, B) / P(B)

我们可以将其重写为:P(A | B) * P(B) = P(A, B)。或者反过来:P(A, B) = P(A | B) * P(B)。

我们可以将其推广到更多变量。因此,整个序列 A, B, C, D 的联合概率是:

P(A, B, C, D) = P(A) * P(B | A) * P(C | A, B) * P(D | A, B, C)

这是链式法则更一般的形式:任何变量序列的联合概率,等于第一个的概率乘以第二个给定第一个的条件概率,再乘以第三个给定前两个的条件概率,依此类推,直到最后一个给定前 N-1 个的条件概率。

现在,链式法则可以应用于计算句子中词的联合概率。

假设我们有短语 “its water is so transparent”。根据链式法则,该序列的概率是:

P(its, water, is, so, transparent) = P(its) * P(water | its) * P(is | its, water) * P(so | its, water, is) * P(transparent | its, water, is, so)

更正式地说,一个词序列的联合概率是每个词给定其之前所有词的条件概率的乘积。


❓ 如何估计这些概率?

我们能否通过计数和除法来计算这些概率?我们通常通过计数和除法来计算概率。例如,要计算 “the” 在 “its water is so transparent that” 之后出现的概率,我们可以统计 “its water is so transparent that the” 出现的次数,除以 “its water is so transparent that” 出现的次数。

但我们不能这样做。原因在于,可能的句子数量太多,我们永远无法估计所有这些概率。我们无法获得足够的数据来统计所有可能的英语句子的出现次数。


🧠 马尔可夫假设

因此,我们采用一种称为马尔可夫假设的简化方法,以安德烈·马尔可夫命名。

马尔可夫假设建议,我们不再计算 P(the | its water is so transparent that),而是通过计算 P(the | that) 来估计,即只考虑序列中的最后一个词 “that”。或者,我们也可以计算 P(the | transparent that),即只考虑最后两个词。

马尔可夫假设就是:我们只关注前一个或前几个词,而不是整个上下文。

更正式地说,马尔可夫假设指出,一个词序列的概率是每个词的条件概率的乘积,而每个词的条件概率仅由其前几个词决定。

换句话说,在链式法则的乘积中,我们用一个更简单的、仅基于最后几个词的概率 P(W_i | W_{i-k} ... W_{i-1}) 来估计 P(W_i | W₁ ... W_{i-1})。


📊 N-gram 模型

以下是几种常见的 N-gram 模型:

一元模型 (Unigram Model):这是最简单的马尔可夫模型。在一元模型中,我们简单地通过各个词(一元组)概率的乘积来估计整个词序列的概率。如果我们通过随机挑选词来生成句子,结果看起来就像“词沙拉”。例如,自动生成的句子可能是:“fifth an of this”。在一元模型中,词是相互独立的。

二元模型 (Bigram Model):在二元模型中,我们以前一个词为条件。我们通过前一个词来估计一个词给定整个前缀的概率。如果使用二元模型生成随机句子,句子看起来会更像英语一些,但仍然存在问题。例如:“outside new car parking lot of the agreement reached”。

三元及更高阶模型 (Trigram and Higher-order Models):我们可以将 N-gram 模型进一步扩展到三元组(三个词)、四元组或五元组。


⚖️ N-gram 模型的局限性

总的来说,N-gram 模型显然是对语言的一种不充分的建模。

原因在于语言存在长距离依赖。例如,如果我想预测句子 “The computer which I had just put into the machine room on the fifth floor ___” 的下一个词,并且我只以前一个词 “floor” 为条件,我很难猜出是 “crashed”。但实际上,“crashed” 是句子的主要动词,而 “computer” 是主语。如果我们知道 “computer” 是主语,我们就更有可能猜出 “crashed”。

这种长距离依赖意味着,一个真正优秀的英语词预测模型必须考虑大量的长距离信息。

然而,在实践中,我们通常可以使用这些 N-gram 模型,因为局部信息,特别是当我们使用三元组和四元组时,通常已经足够约束,在大多数情况下可以解决我们的问题。


📝 总结

本节课中,我们一起学习了语言建模的基本概念。我们了解到语言模型的核心目标是为句子分配概率,并广泛应用于机器翻译、拼写纠错和语音识别等领域。为了应对数据稀疏问题,我们引入了马尔可夫假设,并在此基础上学习了一元模型、二元模型等 N-gram 模型。虽然 N-gram 模型因忽略长距离依赖而存在局限性,但其简单有效,在实践中仍被广泛使用。

📊 课程 P13:L3.2 - 计算 N-gram 概率

在本节课中,我们将学习如何估计 N-gram 语言模型中的概率。我们将从最简单的二元语法(Bigram)模型开始,通过计数和归一化的方法计算概率,并理解这些概率背后反映的语言知识和现实世界信息。


🔢 如何估计 N-gram 概率?让我们从二元语法开始

上一节我们介绍了 N-gram 模型的基本概念,本节中我们来看看如何具体计算其概率。我们使用最大似然估计(Maximum Likelihood Estimation, MLE)来计算二元语法的概率。

对于一个二元语法,单词 w_i 在给定前一个单词 w_{i-1} 情况下的条件概率,可以通过简单的计数来估计。我们统计单词 w_{i-1}w_i 一起出现的次数,然后除以单词 w_{i-1} 单独出现的总次数。

用公式表示如下:

P(w_i | w_{i-1}) = C(w_{i-1}, w_i) / C(w_{i-1})

其中,C 代表计数(Count)。这个公式的含义是:在所有出现 w_{i-1} 的情况下,w_i 紧随其后出现的比例。


📖 通过一个简单例子理解计算过程

让我们通过一个具体的例子来理解上述公式。假设我们有一个来自苏斯博士的微型语料库,包含以下三个句子:

  • <s> I am Sam </s>
  • <s> Sam I am </s>
  • <s> I do not like green eggs and ham </s>

其中,<s> 代表句子开始符号,</s> 代表句子结束符号。

现在,让我们计算一些概率。

首先,计算 P(I | <s>),即句子以“I”开头的概率。

  • 分子:<s>I 一起出现的次数(即“I”紧跟 <s> 的次数)。在语料库中,这发生了两次(第一句和第三句)。
  • 分母:<s> 出现的总次数。在三个句子中,<s> 出现了三次。

因此,P(I | <s>) = 2 / 3 ≈ 0.67

再计算另一个例子,P(Sam | am),即在单词“am”之后出现“Sam”的概率。

  • 分子:“am”和“Sam”一起出现的次数。这发生了一次(在第一句中:“I am Sam”)。
  • 分母:“am”出现的总次数。在语料库中,“am”出现了两次(第一句和第二句)。

因此,P(Sam | am) = 1 / 2 = 0.5


📊 在更大语料库中观察二元语法计数与概率

为了获得更现实的统计结果,我们来看一个更大的语料库。这个语料库来自一个关于加州伯克利餐厅的对话系统,包含近一万个句子。

以下是计算过程中的一些核心数据表。

首先,我们得到原始的二元语法计数表。这个表格展示了任意两个单词连续出现的次数。

前一个词 (w_{i-1}) 当前词 (w_i) 联合计数 C(w_{i-1}, w_i)
I I 5
I want 827
want to 680
to eat 686

(注:表格中还有很多计数为0的组合,例如“want”后从未直接跟过“want”,“Chinese”后从未直接跟过“to”。)

为了将这些计数转化为概率,我们需要用前一个单词的单字词(Unigram)计数进行归一化。以下是部分单词的单字词计数:

  • C(I) = 2533
  • C(want) = 927
  • C(to) = 608

现在,我们可以计算具体的二元语法概率了。例如:

P(to | want) = C(want, to) / C(want) = 680 / 927 ≈ 0.66

这意味着,在语料库中,当人们说出“want”这个词时,下一个词是“to”的概率非常高,约为66%。同时,那些计数为0的组合,其概率也为0。


🧮 利用二元语法概率计算句子概率

计算出所有的二元语法概率后,我们就可以估计一个完整句子的概率了。这是语言建模的最终目标。

计算方法是:将句子中每个单词基于前一个单词的条件概率相乘。

例如,计算句子 <s> I want English food </s> 的概率:

P(<s> I want English food </s>) = P(I | <s>) * P(want | I) * P(English | want) * P(food | English) * P(</s> | food)


💡 二元语法概率揭示了什么知识?

这些计算出的概率并非随机数字,它们编码了丰富的语言和世界知识。

  • 世界知识:为什么 P(Chinese | want) 通常高于 P(English | want)?这可能反映了现实世界中,中餐比英国菜更受欢迎,人们更常询问中餐。
  • 语法知识:为什么 P(to | want) 如此之高?这反映了英语语法规则:动词“want”后面通常接带“to”的不定式。
  • 结构零值:为什么 P(want | spend) 是0?因为“spend want”这种两个动词连续出现的形式在标准英语语法中是不被允许的。这是一个由语法规则导致的“结构零值”。
  • 偶然零值:为什么 P(food | to) 在这个特定语料库中是0?你完全可以想象出“to food”出现在一个句子中(例如某种特定语境)。这里的零值仅仅是因为在训练数据中从未出现过这种组合,这是一个“偶然零值”。

⚙️ 实践技巧:使用对数概率

在实际应用中,我们通常不以原始概率的形式存储和计算,而是使用它们的对数概率。这样做有两个主要原因:

  1. 防止下溢:长句子的概率是许多小于1的小数相乘,结果会是一个极其微小的数字,可能导致计算机算术下溢(Underflow)。将对数相加等价于原始概率相乘,但能有效避免数值过小的问题。
  2. 计算效率:加法运算通常比乘法运算更快。

因此,我们存储和操作的是 log(P(w_i | w_{i-1}))。计算句子概率时,我们将这些对数概率相加,而不是将原始概率相乘。


🧰 公开可用的语言建模资源

有许多公开的工具和语料库可以帮助你进行语言建模实践:

以下是几个重要的资源:

  • SRI 语言建模工具包 (SRILM):一个功能强大、广泛使用的语言模型工具包,可用于训练和评估 N-gram 模型。
  • Google N-gram 语料库:一个包含超过一万亿个词条(5-grams)和1300万个独立单词的庞大数据集,适用于各种 N-gram 应用研究。
    • 示例:在该语料库中,短语“serve as the indication”出现了72次。
  • Google Books N-gram 语料库:这个语料库允许你绘制特定单词在谷歌图书中随时间变化的出现频率曲线,支持美式英语、英式英语、中文、法语、德语等多种语言的语料。


📝 课程总结

本节课中我们一起学习了计算 N-gram 概率的核心方法。

  1. 我们掌握了使用最大似然估计计算二元语法概率的公式:P(w_i | w_{i-1}) = C(w_{i-1}, w_i) / C(w_{i-1})
  2. 我们通过一个小型例子逐步演练了概率的计算过程。
  3. 我们观察了更大语料库中的计数和概率分布,并理解了概率值为零的两种情况:结构零值偶然零值
  4. 我们学会了如何将单个二元语法概率组合起来,通过连乘计算整个句子的概率。
  5. 我们探讨了二元语法概率背后所蕴含的世界知识语法知识
  6. 我们了解了在实际应用中,使用对数概率来防止计算下溢和提高效率的重要性。
  7. 最后,我们介绍了一些公开可用的语言建模工具包和大型语料库,如 SRILM 和 Google N-gram,为后续的实践提供了资源方向。

课程 P14:L3.3 - 评估准则(困惑度等)📊

在本节课中,我们将学习如何评估语言模型的性能。我们将重点介绍两种主要的评估方法:外部评估和内部评估,并深入探讨最常用的内部评估指标——困惑度。


外部评估与内部评估

上一节我们介绍了语言模型的基本概念。本节中我们来看看如何评估一个语言模型的好坏。

一个好的语言模型,通常指能更好地区分“好”句子与“坏”句子的模型。具体来说,我们希望模型能为真实或常见的句子分配更高的概率,而为不合语法或罕见的句子分配较低的概率。

我们通常在训练集上训练模型参数,然后在未见过的测试集上评估其性能。这需要一个评估指标来衡量模型在测试集上的表现。

以下是两种主要的评估方法:

  • 外部评估:将语言模型置于实际应用任务中(如拼写检查、语音识别、机器翻译),通过比较任务完成度(如正确翻译的单词数)来评估模型优劣。这种方法也称为“体内评估”。
  • 内部评估:不依赖具体应用,直接评估语言模型本身的性能。最常用的内部评估指标是困惑度

外部评估虽然直接,但通常耗时较长。因此,在初步实验中,我们常使用困惑度作为快速评估工具,但最终仍需结合外部评估来全面判断模型性能。


困惑度的直观理解 🤔

困惑度的思想可以追溯到克劳德·香农提出的“词语预测游戏”。其核心是:一个优秀的语言模型应该能更准确地预测下一个出现的词语。

例如,给定句子“I always order pizza with cheese and”,一个好的模型可能会预测下一个词是“mushrooms”或“pepperoni”,而预测“fried rice”或另一个“and”的可能性则极低。模型预测实际出现词语的能力越强,其质量就越高。

总结来说,更好的语言模型能为测试集中的句子(即实际出现的句子序列)赋予更高的概率。困惑度正是基于这个概率计算出的一个标准化指标。


困惑度的定义与计算 🧮

困惑度是测试集概率的归一化度量,它考虑了句子的长度,使得我们可以比较不同长度的测试集。

对于一个由N个词组成的测试句子 ( W = w_1, w_2, ..., w_N ),其困惑度 ( PP(W) ) 定义为:

[
PP(W) = P(w_1 w_2 ... w_N)^{-\frac{1}{N}}
]

根据概率的链式法则,整个句子的概率可以分解为每个词在其历史上下文条件下概率的乘积。对于二元语法模型,我们可以用二元概率来近似:

[
P(w_1 w_2 ... w_N) \approx \prod_{i=1}^{N} P(w_i | w_{i-1})
]

因此,困惑度也可以表示为:

[
PP(W) = \sqrt[N]{\prod_{i=1}^{N} \frac{1}{P(w_i | w_{i-1})}}
]

从公式可以看出,最小化困惑度等价于最大化测试集的概率


困惑度的另一种解释:加权平均分支因子 🌳

困惑度还有另一种直观解释:它代表了模型在预测下一个词时,面临的“加权平均分支因子”或平均不确定性。

  • 如果下一个词有10种等可能的选择,那么困惑度就是10。
  • 在一个语音识别系统中,如果需要识别30,000个等可能的名字,那么该任务的困惑度就是30,000。
  • 考虑一个更现实的例子:一个自动电话接线系统,25%的概率听到“operator”,25%听到“sales”,25%听到“technical support”,剩下25%的概率均匀分布在30,000个名字上。计算其加权平均分支因子,得到的困惑度约为54。

这个“加权平均分支因子”的解释,与之前“归一化概率的倒数”的定义在数学上是等价的。例如,对于一个由随机数字(每个数字概率为1/10)组成的序列,无论多长,其困惑度计算出来都是10。


困惑度的实际意义与局限性 ⚠️

一般来说,困惑度越低,模型越好。因为它意味着模型对实际出现的数据有更强的预测能力,不确定性更低。

例如,在一个实验中:

  • 一元语法模型在测试集上的困惑度为962。
  • 二元语法模型的困惑度降至170。
  • 三元语法模型的困惑度更低。

这显示了高阶模型通常能获得更低的困惑度。

然而,需要注意的是,困惑度是一个内部评估指标。它只有在测试数据与训练数据分布相似时,才能较好地近似外部评估的效果。因此,它通常用于前期实验和模型调试,最终模型选择仍需结合具体任务的外部评估结果。


总结 📝

本节课我们一起学习了语言模型的评估方法。
我们介绍了外部评估(通过下游任务表现评估)和内部评估(直接评估模型本身)。
我们重点探讨了困惑度这一核心内部评估指标,理解了它的两种直观含义:归一化测试集概率的倒数加权平均分支因子
我们学习了困惑度的计算公式,并认识到更低的困惑度通常意味着更好的模型性能
最后,我们明确了困惑度的适用场景与局限性,强调最终评估应结合外部任务指标。

📚 课程 P15:L3.4 - 泛化与零概率处理

在本节课中,我们将要学习语言模型中的一个核心挑战:如何处理在训练数据中从未出现过的N元语法(即零概率事件)。我们将通过香农可视化方法理解问题,并探讨其在实际应用中的影响,特别是如何通过泛化技术来改进模型。


🔍 香农可视化方法

上一节我们介绍了最大似然估计构建N元语法模型。本节中我们来看看如何直观地理解这些模型。

香农提出了一种可视化方法,用于展示通过最大似然估计构建的实际N元语法模型。以下是该方法的步骤:

  1. 根据其概率,随机选择一个以“句子开始”标记为第一个词的二元组。
  2. 掷骰子,选择出现的任意一个二元组。假设我们首先选择了“开始 I”。
  3. 接着,以刚生成的词(例如“I”)作为开头,根据概率选择下一个词,例如“want”。
  4. 重复此过程,直到选择到“句子结束”标记。

最终,我们将这些词串联起来,生成了一个句子:I want to eat Chinese food.

香农可视化方法能揭示我们构建的N元语法模型的许多特性。


🎭 不同语料库的生成示例

以下是使用不同模型在莎士比亚作品上训练后生成的随机句子示例:

  • 一元语法句子Every enter now severally so let Hill he late speaks or.
    • 这些句子质量不高。
  • 二元语法句子Why dost stand forth thy canopy forsooth he is this palpable hit the King Henry.
    • 这些句子开始听起来像莎士比亚的风格。
  • 四元语法句子It cannot be, but so. Will you not tell me who I am?
    • 这些句子听起来非常逼真。

莎士比亚作品包含约80万个单词,词汇量约3万。在这些文本中,产生了约30万个不同的二元组类型。然而,所有可能的二元组组合数量是3万的平方,即8.44亿个。这意味着99.96%的可能二元组从未出现,它们在二元组概率表中对应的条目为零。

四元语法的情况甚至更糟。那些听起来像莎士比亚的四元语法句子,实际上就是莎士比亚的原句,因为在如此小的语料库中,特定的四元语法后通常只有一个可能的词。


📊 语料库差异与过拟合风险

如果我们观察另一个语料库,例如《华尔街日报》,情况则完全不同。

例如,以下是从《华尔街日报》语料库生成的三元语法句子:
They also point to $99.6 billion from $2004063% of the rates of interest stores as Mexico and Brazil on market conditions.

这听起来很像《华尔街日报》的风格。这两个英语语料库规模都算合理(数百万单词),但生成的莎士比亚句子和《华尔街日报》句子之间完全没有重叠

这给我们一个重要的教训:过拟合的危险。N元语法模型只有在测试语料与训练语料相似时,才能很好地预测词语。如果你在《华尔街日报》上训练,却在莎士比亚作品上测试,预测效果会很差。在实际应用中,我们希望训练出更健壮、泛化能力更好的模型。


🧩 零概率问题

接下来,我们重点讨论一种泛化问题:处理零概率事件。这里的“零”指的是在训练集中从未出现,但在测试集中出现了的词语序列。

假设在训练集中,我们看到了以下短语:
denied the allegations
denied the reports
denied the claims
denied the request

但我们从未见过 denied the offer。因此,基于最大似然估计,条件概率 P(offer | denied the) = 0

现在,在测试集中我们遇到了句子 denied the offerdenied the loan。这些序列的概率是多少?由于我们的概率是基于训练集训练的,这些序列的概率将为零。这会导致严重问题:如果我们是语音识别器,将永远无法识别这个短语;如果是机器翻译系统,将拒绝翻译成这个短语,并声称这不是正确的英语。


⚠️ 零概率的影响

具有零概率的二元组意味着我们将为测试集分配零概率。这导致我们无法计算困惑度(因为不能除以零)。因此,我们必须找到一种方法来处理这些零概率的二元组。


📝 总结

本节课中我们一起学习了:

  1. 使用香农可视化方法理解N元语法模型的生成过程。
  2. 认识到在不同语料库上训练的模型存在巨大差异,揭示了过拟合的风险
  3. 明确了语言模型中的核心挑战:零概率问题,即训练集中未出现但测试集中存在的N元语法。
  4. 理解了零概率会导致模型在测试时失效,因此必须通过泛化技术(如平滑算法,将在后续课程介绍)来分配少量概率给未见事件,以构建更健壮的模型。

📚 课程 P16:L3.5 - 加一平滑

在本节课中,我们将要学习如何处理语言模型中出现的零概率问题,并详细介绍一种基础的解决方案——加一平滑(也称为拉普拉斯平滑)。我们将从直观理解平滑的概念开始,逐步深入到其数学公式和实际应用效果。


🧠 平滑的直观理解

上一节我们讨论了N-gram模型中的零概率问题。本节中我们来看看如何通过“平滑”技术来解决它。

假设在我们的训练数据中,我们看到了以下短语:“denied the allegations”、“denied the reports”、“denied the claim”、“denied the request”。我们据此计算了概率。在“denied the”之后,总共出现了7个不同的词,我们可以得到每个词出现的概率。

但我们希望模型能够处理未在训练数据中出现的情况,例如“denied the effort”或“denied the outcome”。因此,我们需要从已观测到的事件中“窃取”一小部分概率质量,并将其分配给未来可能遇到但训练数据中未出现的事件。

以下是我们的训练数据及其最大似然计数。这些词在“denied the”之后出现过,而那些从未出现过的词计数为零。我们希望从每个已出现词的计数中拿走一点概率,放到所有其他可能的词(或某个词集合)上,从而消除零概率。


🔢 加一平滑(拉普拉斯平滑)的原理

最简单的平滑方法称为加一估计或拉普拉斯平滑。其核心思想非常简单:我们假设每个词都比实际看到的情况多出现了一次。我们只需在所有计数上加一。

如果我们的最大似然估计公式是:
P(w_i | w_{i-1}) = count(w_{i-1}, w_i) / count(w_{i-1})

那么我们的加一平滑估计公式则变为:
P_{add-1}(w_i | w_{i-1}) = [count(w_{i-1}, w_i) + 1] / [count(w_{i-1}) + V]

这里,分母需要加上词汇表大小 V。这是因为我们为每一个可能跟在 w_{i-1} 后面的词都增加了一次计数。因此,分母的增加量不是1,而是所有可能的后继词数量,即词汇表大小 V。


📊 最大似然估计回顾

我多次使用了“最大似然估计”这个术语,让我们回顾一下它的含义。

给定一个训练集,模型某个参数的最大似然估计是使该训练集在该模型下出现的可能性(似然)最大化的那个值。我们有一个训练集,最大似然估计器能让我们从训练集中学习一个模型,这个模型使得训练集本身出现的可能性最大。

这是什么意思呢?假设单词“bagel”在一个百万词的语料库中出现了400次。我问:从另一段文本中随机抽取一个词是“bagel”的概率是多少?

根据我们语料库的最大似然估计,这个概率是 400 / 1,000,000 = 0.004。这个估计对于另一个语料库可能并不准确,但我们无法预知。然而,这个估计值使得“bagel”在百万词语料中出现400次的可能性最大,而这正是它在我们的训练语料中实际发生的情况。所以,我们是在最大化训练数据的似然。

加一平滑以及任何平滑技术,都是一种非最大似然估计器,因为我们改变了训练数据中实际出现的计数,以期获得更好的泛化能力。


🍽️ 加一平滑实例:Berkeley餐厅语料库

让我们回到Berkeley餐厅项目语料库的例子。如果我们对所有计数进行加一平滑处理,就会得到拉普拉斯平滑后的二元语法计数。现在,所有原本为零的计数都变成了一,其他所有计数也都增加了一。

现在,我们可以使用前面提到的加一平滑公式,根据这些新的计数来计算二元语法概率。

于是我们得到了所有平滑后的二元语法概率。例如,P(want | i) = 0.26,而所有原本为零的概率现在都变成了一个很小的非零值,例如 0.000420.0026 等。

我们还可以根据这些平滑后的概率,“重构”出能够自然产生这些概率的虚拟计数。我们取这些概率,并反推出能得出这些概率的原始计数应该是多少,以此观察加一平滑对我们的概率估计造成了多大的改变。

以下是重构后的计数。例如,“I”后面跟“want”出现了527次,“Chinese”后面跟“food”出现了8.2次。这些都是根据平滑概率反推的虚拟计数。

让我们将其与原始计数进行比较。在原始计数中,“want”跟在“i”后面出现了608次。而在平滑后的重构计数中,这个数字只有238次,几乎减少了三分之二。同样,“Chinese food”在原始计数中出现了82次,在重构计数中只有8.2次。

这表明,加一平滑对我们的计数做出了巨大的改变,有时改变因子甚至达到10倍。为了“窃取”足够的概率质量分配给海量的零计数事件,原始计数被大幅度地修改了。


⚖️ 加一平滑的优缺点与适用场景

换句话说,加一平滑是一个非常“钝”的工具。为了将概率质量分配给数量庞大的零概率事件,它对原始计数进行了非常剧烈的调整。

因此,在实践中,我们通常不将加一平滑用于N-gram语言模型,因为我们有更好的平滑方法(我们将在后续课程中介绍)。

然而,加一平滑确实被用于其他类型的自然语言处理模型中。例如,在文本分类或类似领域中,零值问题的规模没有N-gram模型中那么巨大,加一平滑仍然是一个有效且常用的选择。


✅ 课程总结

本节课中我们一起学习了:

  1. 零概率问题:在N-gram模型中,未在训练数据中出现的事件概率为零,这会影响模型的泛化能力。
  2. 平滑的概念:通过调整概率分布,为未出现事件分配非零概率。
  3. 加一平滑(拉普拉斯平滑):一种基础的平滑方法,通过给所有计数加一来实现。
  4. 其数学公式P_{add-1}(w_i | w_{i-1}) = [count(w_{i-1}, w_i) + 1] / [count(w_{i-1}) + V]
  5. 实际效果:加一平滑会显著改变原始计数,在N-gram模型中可能过于剧烈,但在其他NLP任务(如文本分类)中仍有应用价值。

理解加一平滑是学习更高级平滑技术(如古德-图灵估计、Kneser-Ney平滑等)的重要基础。

📚 课程 P17:L3.6 - 插值、回退及大型语言模型

在本节课中,我们将要学习语言模型中的两种重要技术:插值回退。我们还将探讨如何处理大规模语料库(如网络数据)中的语言建模问题,并简要介绍一些高级语言模型概念。


🔄 插值与回退

上一节我们介绍了N-gram模型的基本概念。本节中我们来看看,当模型遇到数据稀疏或不可靠的高阶N-gram时,如何通过插值回退来改进概率估计。

插值的核心思想是,我们总是混合使用不同阶数的N-gram模型(如一元、二元、三元模型),以综合利用它们的信息。而回退则是在高阶N-gram证据不足时,才转而依赖低阶模型。在实践中,插值法通常比回退法效果更好。


📊 两种插值方法

以下是两种主要的插值方法:

1. 简单线性插值

在简单线性插值中,我们通过加权求和的方式,混合一元、二元和三元模型的概率。权重(λ)之和为1,以保证结果是一个有效的概率分布。

公式如下:
P̂(wₙ|wₙ₋₂, wₙ₋₁) = λ₁·P(wₙ) + λ₂·P(wₙ|wₙ₋₁) + λ₃·P(wₙ|wₙ₋₂, wₙ₋₁)

2. 上下文相关插值

这种方法更为复杂。我们仍然混合三种模型,但权重λ不再是固定的,而是依赖于当前的上下文(即前两个词是什么)。这允许模型根据上下文动态调整对不同阶数模型的依赖程度。


🧮 如何确定λ值?

通常,我们使用一个留存语料库(或称开发集)来设置λ值。

具体步骤如下:

  1. 使用训练数据训练出N-gram模型。
  2. 在留存数据上,寻找一组λ值,使得该留存数据的对数似然概率最大化。

❓ 处理未知词

当遇到训练集中从未出现过的词(即集外词)时,我们需要特殊处理。

以下是处理集外词的一种常用方法:

  1. 在训练前,预先定义一个固定的词表。
  2. 将训练数据中所有不在该词表中的词(通常是罕见词)替换为一个特殊的 <UNK> 标记。
  3. 像对待普通词一样,在训练数据中统计 <UNK> 的N-gram概率。
  4. 在解码(预测)时,任何未知词都被视为 <UNK>,并使用其对应的概率。

🌐 大规模N-gram的处理

面对像谷歌N-gram语料库这样的大规模数据时,我们需要高效的存储和计算策略。

以下是几种关键的技术:

  • 剪枝:仅存储高频的N-gram。例如,可以移除所有只出现一次(单例)的高阶N-gram,以节省大量空间。
  • 高效数据结构:使用字典树等结构来存储和查询N-gram。
  • 近似算法:使用近似但高效的语言模型,可能无法得到精确概率,但速度更快。
  • 存储优化:不存储完整的字符串,而是存储索引;使用霍夫曼编码压缩;对概率值进行量化,用少量比特存储。

🧹 大规模数据的平滑方法:Stupid Backoff

对于超大规模的N-gram,最流行的平滑算法之一是 “Stupid Backoff”。它之所以叫“Stupid”,是因为其规则非常简单,但在海量数据下效果很好。

算法逻辑如下:

  • 如果要计算 S(wₙ|wₙ₋₂, wₙ₋₁),首先检查三元组 (wₙ₋₂, wₙ₋₁, wₙ) 的计数。
  • 如果计数 > 0,则直接使用最大似然估计:count(wₙ₋₂, wₙ₋₁, wₙ) / count(wₙ₋₂, wₙ₋₁)
  • 如果计数为 0,则回退到二元概率,并乘以一个固定的折扣因子(如0.4):0.4 * S(wₙ|wₙ₋₁)
  • 以此类推,直至回退到一元概率。

需要注意的是,Stupid Backoff 输出的 S 值并非严格意义上的概率(其总和可能不为1),但它作为评分函数在任务中(如搜索排序)表现优异。


🚀 高级语言模型议题

除了基础的N-gram,现代研究还关注更高级的语言建模技术:

以下是几个重要的研究方向:

  • 判别式模型:不再以拟合训练数据为目标,而是直接优化下游任务(如机器翻译、语音识别)的性能来调整模型参数。
  • 基于句法的模型:利用句法分析器提供的信息,而不仅仅是连续的词序列。
  • 缓存模型:假设近期出现过的词更可能再次出现。因此,将词的概率与其在近期历史中的出现频率混合。但需注意,缓存模型在某些场景(如语音识别)中可能效果不佳。

📝 总结

本节课中我们一起学习了语言模型中的关键技术。我们了解了插值法如何综合不同阶数模型的信息,以及回退法如何在数据稀疏时提供支持。我们探讨了处理未知词和应对大规模语料的策略,特别是简单高效的 Stupid Backoff 算法。最后,我们简要介绍了判别式建模、句法模型和缓存模型等高级议题,为深入理解现代语言模型奠定了基础。

📚 课程 P18:L3.7 - Kneser-Ney 平滑

在本节课中,我们将要学习语言模型中最复杂的平滑技术之一——Kneser-Ney平滑。它不仅有优雅的数学直觉,而且在实践中(如语音识别和机器翻译)被广泛使用。我们将从绝对折扣平滑的直觉出发,逐步理解Kneser-Ney平滑的核心思想,并最终掌握其通用递归公式。


🧠 从古德-图灵到绝对折扣平滑

上一节我们介绍了古德-图灵估计。它通过折扣高频事件的计数,将节省出的概率质量分配给未见事件。观察古德-图灵折扣后的计数,你会发现一个规律:折扣后的计数与原始计数通常只相差一个固定的、较小的数值,例如原始计数减去0.75。

基于这个观察,我们可以直接采用一个固定的折扣值,而不必每次都进行复杂的古德-图灵计算。这种方法被称为绝对折扣平滑

以下是绝对折扣平滑的公式(以二元语法为例):

P_{\text{abs}}(w_i | w_{i-1}) = \frac{\max(C(w_{i-1}, w_i) - D, 0)}{C(w_{i-1})} + \lambda(w_{i-1}) P(w_i)

其中,D 是一个固定的折扣值(例如0.75),λ 是一个插值权重,用于混合折扣后的二元概率和一元概率 P(w_i)。对于计数为1或2的情况,折扣值 D 可以单独设置以更精确地建模。


🔍 一元概率的问题与Kneser-Ney的直觉

绝对折扣平滑的问题在于其使用的一元概率 P(w)。让我们通过一个经典的香农游戏来理解这个问题。

假设我们有一个句子:“I can’t see without my reading __”。下一个词很可能是“glasses”。但如果我们考虑“Francisco”这个词,它作为一元词的出现频率可能比“glasses”更高。然而,“Francisco”几乎总是跟在“San”后面出现。因此,在“reading”这个上下文之后,我们不应该信任“Francisco”的高一元概率。

这个直觉引出了Kneser-Ney平滑的核心思想:在回退模型中,我们不应该使用一个词本身的概率(它有多常见),而应该使用它的延续概率——即这个词作为一个新延续出现的可能性有多大。


📊 如何计算延续概率

延续概率衡量的是一个词出现在多少种不同的二元语法类型中,即它前面可以有多少个不同的词。

具体来说,一个词 w 的延续概率 P_{\text{cont}}(w) 定义如下:

P_{\text{cont}}(w) = \frac{|\{w_{i-1}: C(w_{i-1}, w) > 0\}|}{|\{(w_{j-1}, w_j): C(w_{j-1}, w_j) > 0\}|}
  • 分子:是前面不同词 w_{i-1} 的数量,这些词与 w 共同组成了出现过的二元组。这衡量了 w 作为新延续的多样性。
  • 分母:是整个语料库中所有出现过的二元语法类型(即不同的词对)的总数。

因此,像“Francisco”这样虽然总频次高,但只出现在少数上下文(如“San”)中的词,其延续概率会很低。


⚙️ Kneser-Ney平滑算法

结合绝对折扣的直觉和用于低阶n元语法的延续概率,我们就得到了Kneser-Ney平滑算法。

对于二元语法,其公式如下:

P_{\text{KN}}(w_i | w_{i-1}) = \frac{\max(C(w_{i-1}, w_i) - D, 0)}{C(w_{i-1})} + \lambda(w_{i-1}) P_{\text{cont}}(w_i)

其中,λ(w_{i-1}) 是一个归一化的权重,用于分配从高阶n元语法中折扣出的总概率质量。它的计算方式如下:

\lambda(w_{i-1}) = \frac{D}{C(w_{i-1})} \times |\{w: C(w_{i-1}, w) > 0\}|
  • 第一部分 D / C(w_{i-1}) 是每个计数被折扣的相对量。
  • 第二部分 |{w: C(w_{i-1}, w) > 0}| 是在当前上下文 w_{i-1} 之后出现的不同词的数量。
  • 两者相乘,得到分配给所有可能延续词的总概率质量。

🔄 通用的递归公式

以上是二元语法的特例。Kneser-Ney平滑可以递归地应用于任意阶的n元语法。其通用递归公式如下:

P_{\text{KN}}(w_i | w_{i-n+1}^{i-1}) = \frac{\max(C_{\text{KN}}(w_{i-n+1}^{i}) - D, 0)}{C_{\text{KN}}(w_{i-n+1}^{i-1})} + \lambda(w_{i-n+1}^{i-1}) P_{\text{KN}}(w_i | w_{i-n+2}^{i-1})

这里的关键区别在于计数函数 C_{\text{KN}}(·)

  • 对于最高阶的n元组(例如我们要计算概率的三元组),C_{\text{KN}} 使用原始计数
  • 当递归到低阶n元组(例如二元组和一元组)时,C_{\text{KN}} 使用我们之前定义的延续计数(即前面不同上下文的数目),而不是原始计数。

这种设计确保了在回退时,我们使用的是衡量“延续能力”的计数,而非单纯的频率,这与Kneser-Ney的核心直觉一脉相承。


📝 总结

本节课中我们一起学习了Kneser-Ney平滑。

  1. 我们从绝对折扣平滑出发,理解了固定折扣值的直觉。
  2. 我们发现了传统一元回退概率的缺陷,并引入了延续概率的核心概念——一个词作为新延续出现的可能性。
  3. 我们将两者结合,推导出了Kneser-Ney平滑的具体公式,它通过折扣高阶n元计数,并将节省的概率质量按延续概率分配给低阶模型。
  4. 最后,我们了解了Kneser-Ney平滑通用的递归形式,它通过区分最高阶原始计数和低阶延续计数,优雅地将这一思想扩展到任意阶的n元语法。

Kneser-Ney平滑因其出色的效果和优美的数学直觉,成为语言建模中最为流行和强大的平滑技术之一。

📚 课程 P19:L4.1 - 文本分类任务

在本节课中,我们将介绍文本分类这一主题,以及朴素贝叶斯算法——这是进行文本分类最重要的方法之一。


📧 文本分类应用示例

上一节我们介绍了文本分类的基本概念,本节中我们来看看一些具体的应用示例。

以下是文本分类的一些应用场景:

  • 垃圾邮件检测:例如,一封邮件中可能包含拼写错误(如“gr8t”)、通用称呼(如“undisclosed recipients”)、可疑的URL或夸张的词语(如“exciting”),这些特征组合起来可以帮助分类器判断其为垃圾邮件。
  • 作者归属:判断文本的作者是谁。一个著名的例子是判断《联邦党人文集》中匿名文章的作者是麦迪逊还是汉密尔顿,1963年研究者使用贝叶斯方法成功解决了这一问题,这也催生了我们将要讨论的朴素贝叶斯方法。
  • 性别识别:判断作者的性别。研究表明,女性作者倾向于使用更多代词,而男性作者在名词短语中使用更多限定词和事实陈述。
  • 情感分析:判断一段评论(如电影评论)的情感是积极还是消极。例如,“unbelievably disappointing”是负面评论,而“the greatest schoolball comedy ever filmed”是正面评论。这是非常重要的商业应用。
  • 主题分类:为科学文章自动分配主题类别,例如在医学数据库中判断一篇文章属于“拮抗剂”、“血液供应”、“药物疗法”还是“流行病学”等主题。

🎯 文本分类任务定义

上一节我们看了一些应用,本节我们来正式定义文本分类任务。

文本分类的任务是:为任何一段文本分配一个主题类别。输入包括:

  • 一个文档 D
  • 一个固定的类别集合 C = {c₁, c₂, ..., cⱼ}

我们的目标是:给定文档 D 和类别集合 C,预测文档所属的类别 c ∈ C


⚙️ 文本分类方法

基于规则的方法

最简单的文本分类方法是使用手写规则。例如,在垃圾邮件检测中,可以制定如下规则:

  • 如果发件人在黑名单中,则标记为垃圾邮件。
  • 如果邮件内容包含“millions of dollars”或“you have been selected”等短语,则标记为垃圾邮件。

虽然专家精心设计的规则可以达到很高的准确率,但构建和维护这些规则成本高昂。因此,手写规则通常作为系统的一部分,与机器学习方法结合使用。

监督式机器学习方法

更主流的方法是监督式机器学习。除了文档 D 和类别集合 C,我们还需要一个训练集。训练集由许多已经人工标注好类别的文档组成。

给定训练集,机器学习的目标是生成一个分类器 γ。分类器 γ 是一个函数,当输入一个新文档时,它能输出该文档的预测类别。

机器学习中有多种分类器,本节课我们将重点介绍朴素贝叶斯分类器。在本课程后续内容中,我们还会接触到逻辑回归、支持向量机(SVM)、K近邻(KNN)等其他分类器。

无论使用哪种分类器,文本分类的核心流程都是:从文档中提取特征,然后构建一个能根据这些特征判断文档类别的分类器。


📝 总结

本节课中,我们一起学习了文本分类的基本概念。我们了解了文本分类的多种应用场景,如垃圾邮件检测、作者归属、情感分析等。我们明确了文本分类的任务定义:为文档分配预定义类别。最后,我们介绍了实现文本分类的两种主要方法:基于规则的方法和监督式机器学习方法,并指出后者是更通用和强大的解决方案,其中朴素贝叶斯算法是一个重要的基础分类器。

课程 P2:L1.2 - 正则进阶(替换等操作) 🔄

在本节课中,我们将学习如何使用正则表达式进行字符串的修改操作,特别是替换功能。正则表达式在修改字符串时扮演着非常强大的角色。这种字符串替换是早期最重要的自然语言处理系统之一——1966年的聊天机器人Eliza——的关键组成部分。


替换语法

替换的语法很简单。例如,在Python中,re.sub() 命令可用于将匹配正则表达式的字符串更改为另一个替换字符串。

re.sub(pattern, repl, string)

捕获组

通常,能够引用匹配第一个模式的字符串的特定子集是很有用的。为此,我们可以使用捕获组。捕获组是一种将模式的一部分存储到寄存器中的方法,以便我们稍后可以在替换字符串中引用它。

假设我们想给所有数字(例如这里的35)加上尖括号,即我们希望将“35”替换为“<35>”。

我们可以使用圆括号将模式捕获到寄存器中,寄存器会有编号。我们可以通过编号引用它们,例如 \1 将代表寄存器一中的内容。

在替换命令的第一个字符串中,这些圆括号意味着:获取此处的正则表达式(在本例中是一个数字序列)并将其放入寄存器一(因为它是我们替换模式中的第一组圆括号)。然后,在指定要替换成什么时,\1 表示我们在第一组圆括号中匹配到的任何内容,接着我们可以添加两个尖括号,从而将“35”转换为“<35>”。


多个捕获组

在非常复杂的模式中,我们可能需要使用多个寄存器。以下是一个示例,我们可能捕获两个字符串,然后按顺序引用它们(第一个字符串,第二个字符串),接着按顺序引用它们。

我们可以匹配“the faster they ran, the faster we ran”,但无法匹配“the faster they ran, the faster we ate”,因为“ate”与 \2 不匹配,而 \2 在本例中是字符串“ran”。


非捕获组

这里存在一个问题:圆括号用于指定捕获组,但它们也是我们对术语进行分组的方式,例如用于像匹配“people”或“cats”的字符串这样的析取表达式。我们如何指定我们使用圆括号仅用于分组而非捕获呢?

我们只需在开括号后添加 ?:。这里的 ?: 意味着:将这些括号仅视为分组,而非捕获。因此,这个正则表达式不会捕获匹配“some”或“a few”的任何内容,但会捕获匹配“people”或“cats”的任何内容,并将其放入第一个寄存器 \1。所以,这将匹配“some cats”这样的短语,但不会匹配“some some”这样的短语,因为 \1 不匹配“some”,它匹配“cats”。因此,第一组括号在寄存器编号和存储方面被忽略。


前后查找断言

最后,有时我们需要预测未来,在文本中向前查看某个模式是否匹配,但我们不打算推进匹配光标,这样我们可以在模式出现时处理它。这些前后查找断言利用了 (?...) 语法,我们刚刚介绍了非捕获组。

  • (?=pattern) 运算符:如果模式出现,则为真,但它是零宽度的,意味着匹配指针不前进。
  • (?!pattern) 负向先行断言:仅当模式不匹配时才返回真,同样也是零宽度的。

负向先行断言通常在我们解析某些复杂模式但想要排除特殊情况时使用。例如,这里的最后一个模式匹配行首的任何单个单词,但该单词不以“volcano”开头。


Eliza:一个简单的聊天机器人示例

替换和捕获组在实现像Eliza这样的简单聊天机器人时非常有用。Eliza是最重要的历史性自然语言处理系统之一,由先驱人工智能研究员Joseph Weizenbaum于1966年创建。它模拟了一位罗杰斯式心理学家,一种强调镜像反馈他们所听到内容的治疗师。

Eliza是一个非常简单的程序,仅使用模式匹配来识别像“I need X”这样的短语,并将其转换为合适的输出,例如“What would it mean to you if you got X?”。这种简单技术在这个领域取得了成功,因为Eliza实际上不需要了解任何信息来模仿罗杰斯式心理治疗师。这是少数几种听众可以表现得好像对世界一无所知的对话类型之一。

以下是1966年与Eliza进行示例对话的一些片段。Eliza对人类对话的模仿非常成功。许多与Eliza互动的人开始相信它真正理解了他们和他们的问题。在非常有先见之明的早期工作中,Weizenbaum指出了将人类品质归因于人工代理的伦理问题,我们将在对话讲座中回到这个问题。

Eliza主要由一系列替换模式组成,带有一些用于决定选择哪些模式的控制逻辑,以及一些我们稍后会讨论的更高级别的对话结构。但在这里,我们可以看到捕获组的示例,用于生成我们在上一张幻灯片中看到的字符串。

例如,我们可能会捕获作者用来描述自己的形容词,然后说一些与这些特定形容词相关的内容。或者,当用户使用包含“all”或“always”等词的通用陈述时,使用简单模式来询问更多细节。


总结

在本节课中,我们一起学习了正则表达式的进阶操作,特别是字符串替换功能。我们探讨了捕获组和非捕获组的使用,了解了如何引用匹配的子字符串。我们还介绍了前后查找断言,这是一种在不消耗字符的情况下检查模式是否匹配的强大工具。最后,我们通过历史性的聊天机器人Eliza的示例,看到了这些技术在实际应用中的价值。正则表达式替换和前后查找等强大工具将在各种场景中发挥作用。稍后,当我们讨论如何构建能够进行对话交互的智能体时,我们将再次回到Eliza的例子。

📚 课程 P20:L4.2 - 朴素贝叶斯分类器

在本节课中,我们将学习朴素贝叶斯分类器。这是一种基础的文本分类方法,通过它我们可以引入文本分类中出现的许多核心问题。


🧳 词袋模型

上一节我们介绍了朴素贝叶斯分类器的基本概念,本节中我们来看看它如何表示文档。朴素贝叶斯分类器基于一个非常简单的文档表示方法,称为“词袋模型”。

以下是一个电影评论示例:

I love this movie. It's sweet, but with satirical humor.

在词袋表示法中,我们想象将这篇评论中的所有单词都扔进一个大纸袋并混合在一起。因此,我们不知道单词出现的顺序。单词“I”在袋子里,单词“love”也在袋子里,单词“this”也在。但袋子并不表示单词的顺序。

我们可以将这个词袋视为一个单词及其出现次数的列表。例如,单词“it”在这个袋子中出现了六次,单词“I”出现了五次,单词“the”出现了四次,以此类推,像“humor”、“have”或“great”这样的单词都只出现了一次。


🎯 分类器的任务

上一节我们了解了如何用词袋表示文档,本节中我们来看看分类器的目标。分类函数可以被看作一个函数 γ,它接收这个单词和计数的列表,并做出分类决策——例如“点赞”或“踩”(如果是二元分类)。或者,如果我们有更多类别,我们可以将其映射到其中一个类别。因此,我们的任务是将这个单词和计数的列表映射到某个类别。


📊 贝叶斯规则与分类

在分类任务中,我们有一个文档 D 和一个类别 C,我们的目标是为每个类别计算给定文档的条件概率 P(C|D)。我们将使用这个概率来选择最佳类别。

根据贝叶斯规则,这个概率等于:
P(C|D) = P(D|C) * P(C) / P(D)

让我们看看如何在分类器中使用它。最佳类别,即最大后验概率类别,是我们为文档分配的那个类别。它是所有类别中,使 P(C|D) 最大的那个类别。

根据贝叶斯规则,使 P(C|D) 最大的类别,同样也使 P(D|C) * P(C) / P(D) 最大。在贝叶斯分类的传统中,我们可以忽略分母 P(D),因为对于比较所有类别来说,P(D) 是一个常数,不影响排序结果。

因此,最可能的类别 C_map 是最大化以下两个概率乘积的类别:

  • P(D|C):我们称之为似然
  • P(C):我们称之为先验

🔧 计算概率与简化假设

上一节我们推导了分类决策公式,本节中我们来看看如何计算其中的概率。计算类别的先验概率 P(C) 相对简单,可以通过统计某个语料库或数据集中该类别的相对频率来完成。

然而,计算给定类别的文档似然 P(D|C) 则复杂得多。为了使其可计算,朴素贝叶斯分类器做出了两个关键的简化假设。

以下是这两个假设:

  1. 词袋假设:我们假设单词在文档中的位置无关紧要。我们只关心某个单词或特征是否出现,而不关心它出现在哪里。
  2. 条件独立性假设:我们假设给定类别时,不同特征(例如单词)的出现是相互独立的。也就是说,一个单词的出现概率不依赖于其他单词的出现。

这两个假设都是不正确的简化,但在实践中,它们极大地简化了问题,使我们能够以较高的准确率解决问题。

基于这两个假设,我们可以将给定类别下整个特征集 x1, x2, ..., xn 的联合概率表示为一系列独立概率的乘积:
P(x1, x2, ..., xn | C) = P(x1|C) * P(x2|C) * ... * P(xn|C)

因此,朴素贝叶斯分类器选择的最佳类别是最大化以下公式的类别:
C_map = argmax_C [ P(C) * ∏_i P(x_i | C) ]


📝 应用于文本分类

现在,让我们具体看看如何将朴素贝叶斯应用于文本分类。首先,我们假设查看文本文档中的所有单词位置。

对于一个有 n 个单词的文档,对于每个类别 C,我们计算:
P(C) * ∏_{i=1}^{n} P(word_i | C)

然后,我们选择计算结果最高的类别作为文档的类别。

这个算法有一个潜在问题:argmax 计算涉及大量概率的连乘。由于概率都是介于0和1之间的小数,连乘可能导致浮点数下溢。

我们的解决方案是使用对数。因为 log(a * b) = log(a) + log(b),所以我们可以将对数概率相加,而不是将概率相乘。

因此,我们最终的计算公式变为:
C_map = argmax_C [ log P(C) + ∑_{i=1}^{n} log P(word_i | C) ]

取对数不会改变类别的排序,最高概率的类别同样具有最高的对数概率。使用对数求和意味着我们创建了一个线性模型,我们是在对权重求和后取最大值。这清楚地表明朴素贝叶斯是一个线性分类器,而线性分类器是简单分类器家族中的重要成员。


🎓 总结

本节课中,我们一起学习了朴素贝叶斯文本分类器的基本原理。我们首先介绍了词袋模型作为文档的表示方法。然后,我们利用贝叶斯规则推导出分类决策公式,即选择最大化 P(C) * P(D|C) 的类别。为了实际计算,我们引入了两个关键的简化假设:词袋假设条件独立性假设。最后,我们讨论了如何通过取对数来避免数值下溢问题,并指出朴素贝叶斯本质上是一个线性分类器

课程 P21:L4.3 - 朴素贝叶斯训练学习 🧠

在本节课中,我们将要学习如何训练朴素贝叶斯模型,即如何从数据中学习模型的参数。我们将重点介绍使用最大似然估计的简单方法,并探讨其潜在问题及解决方案。


最大似然估计 📊

学习多项式朴素贝叶斯模型最简单的方法是使用最大似然估计。其核心思想是直接使用数据中的频率来计算概率。

先验概率的计算

要计算一个文档属于特定类别 ( C_j ) 的先验概率 ( P(C_j) ),我们可以统计所有文档的数量,然后计算其中属于类别 ( C_j ) 的文档所占的比例。公式如下:

[
P(C_j) = \frac{\text{属于类别 } C_j \text{ 的文档数量}}{\text{文档总数}}
]

似然概率的计算

对于似然概率,即给定类别 ( C_j ) 时,单词 ( w_i ) 出现的概率 ( P(w_i | C_j) ),我们需要统计单词 ( w_i ) 在类别 ( C_j ) 的所有文档中出现的次数,然后除以类别 ( C_j ) 中所有单词的总数。公式如下:

[
P(w_i | C_j) = \frac{\text{单词 } w_i \text{ 在类别 } C_j \text{ 中出现的次数}}{\text{类别 } C_j \text{ 中所有单词的总数}}
]

具体操作时,我们会为每个类别 ( C_j ) 创建一个“超级文档”,即将所有属于该类别的文档拼接在一起。然后,我们在这个超级文档中计算每个单词的频率。


最大似然估计的问题与平滑处理 ⚠️

上一节我们介绍了使用最大似然估计计算参数的方法,本节中我们来看看这种方法存在的一个主要问题及其解决方案。

最大似然估计的一个关键问题是“零概率”问题。例如,假设单词“fantastic”在训练集的“正面”类别中从未出现过。那么,根据最大似然估计,其似然概率 ( P(\text{fantastic} | \text{positive}) ) 将为零。

这会导致严重问题,因为在分类时,我们计算的是后验概率 ( P(C_j | \text{文档}) \propto P(C_j) \times \prod_i P(w_i | C_j) )。如果其中任何一个似然项为零,整个乘积就会变为零,导致模型永远无法选择该类别。

解决方案:加一平滑

解决零概率问题的标准方法是使用加一平滑(拉普拉斯平滑)。其做法是在计数时,为每个单词的计数都加上一个常数(通常为1)。

平滑后的似然概率计算公式变为:

[
P_{\text{smooth}}(w_i | C_j) = \frac{\text{单词 } w_i \text{ 在类别 } C_j \text{ 中的出现次数} + 1}{\text{类别 } C_j \text{ 中所有单词的总数} + |V|}
]

其中,( |V| ) 是词汇表的大小。这样,即使某个单词在训练集中从未出现,其概率也不会为零。


参数计算步骤 📝

以下是训练朴素贝叶斯模型并计算参数的具体步骤:

  1. 提取词汇表:从训练语料库中提取所有不同的单词,构成词汇表 ( V )。
  2. 计算先验概率:对于每个类别 ( C_j ),计算其先验概率。
    • 找出所有属于类别 ( C_j ) 的文档集合 ( \text{Docs}_j )。
    • 计算 ( P(C_j) = \frac{|\text{Docs}_j|}{\text{文档总数}} )。
  3. 计算似然概率:对于每个类别 ( C_j ) 和词汇表中的每个单词 ( w_k ),计算平滑后的似然概率。
    • 为类别 ( C_j ) 创建超级文档 ( \text{Text}_j )(拼接所有 ( \text{Docs}_j ) 中的文档)。
    • 在 ( \text{Text}_j ) 中统计单词 ( w_k ) 出现的次数,记为 ( n_k )。
    • 计算 ( P_{\text{smooth}}(w_k | C_j) = \frac{n_k + \alpha}{N_j + \alpha \times |V|} ),其中 ( N_j ) 是 ( \text{Text}_j ) 中的总词数,( \alpha ) 是平滑参数(通常为1)。

处理未知词与停用词 🔍

未知词

未知词是指在测试集中出现,但未包含在训练集词汇表中的单词。

处理未知词的常见方法是直接忽略它们。在分类时,我们将这些词从测试文档中移除,不为其分配任何概率值。

通常不专门为未知词建立概率模型,因为简单地根据未知词数量来调整概率,对于区分类别通常没有帮助。

停用词

停用词是指在语料库中出现频率极高的词(如“the”、“a”、“is”)。

识别停用词的方法是:根据单词在训练集中的频率进行排序,取前K个(如50或100个)作为停用词列表。然后,在训练和测试阶段,直接移除这些停用词。

然而,在朴素贝叶斯文本分类中,移除停用词通常并不会带来显著的性能提升。因此,在实践中,大多数朴素贝叶斯分类器会使用所有单词,而不使用停用词列表。


总结 ✨

本节课中我们一起学习了朴素贝叶斯模型的训练过程。

我们首先介绍了使用最大似然估计来计算先验概率和似然概率的基本方法。接着,我们探讨了最大似然估计中存在的零概率问题,并引入了加一平滑技术来解决它。然后,我们详细列出了从数据中学习模型参数的具体步骤。最后,我们讨论了如何处理未知词和停用词,并指出在朴素贝叶斯分类中,通常忽略未知词并保留所有单词是更常见的做法。

通过学习,我们了解到训练朴素贝叶斯模型是一个参数计算相对直接的过程,关键在于通过平滑技术处理数据稀疏性问题,从而构建一个更鲁棒的分类器。

课程 P22:L4.4 - 情感分析与朴素贝叶斯 📊

在本节课中,我们将通过一个具体示例,学习如何应用朴素贝叶斯方法进行情感分析。我们还将介绍二元朴素贝叶斯算法。


概述

我们将通过一个简单的教学示例,演示如何从训练集中计算概率,以及如何为测试句子分配情感值。示例包含五个训练句子和一个测试句子。

计算先验概率

首先,我们需要计算两个类别(负面和正面)的先验概率。根据上一讲的内容,一个类别的概率等于该类别的文档数量除以文档总数。

我们有三个负面文档和两个正面文档。

因此,负面类别的先验概率为:
[
P(\text{negative}) = \frac{3}{5}
]

正面类别的先验概率为:
[
P(\text{positive}) = \frac{2}{5}
]

处理测试集词汇

接下来,我们移除测试集中未出现在训练集词汇表中的词。这意味着,如果一个词从未在训练集中出现过,我们在测试集中也将其忽略。

计算似然概率

我们将使用上一讲中提到的加一平滑公式来计算似然概率。对于给定类别中的任意一个词,其似然概率计算公式如下:
[
P(w \mid c) = \frac{\text{count}(w, c) + 1}{\sum_{w' \in V} \text{count}(w', c) + |V|}
]
其中,(\text{count}(w, c)) 是词 (w) 在类别 (c) 中出现的次数,(|V|) 是词汇表的大小。

我们需要知道每个词在类别中的出现次数,以及该类别中所有词的总出现次数(词条数)和词汇表大小。

  • 在负面类别中,共有 14 个词条。
  • 在正面类别中,共有 9 个词条。
  • 词汇表大小 (|V|) 为 20(基于训练集统计)。

现在,让我们计算测试集中三个词(predictable, no, fun)的似然概率。

以下是计算过程:

  • predictable 在负面类别中:出现 1 次。
    [
    P(\text{predictable} \mid \text{negative}) = \frac{1 + 1}{14 + 20}
    ]
  • no 在负面类别中:出现 1 次。
    [
    P(\text{no} \mid \text{negative}) = \frac{1 + 1}{14 + 20}
    ]
  • fun 在负面类别中:出现 0 次。
    [
    P(\text{fun} \mid \text{negative}) = \frac{0 + 1}{14 + 20}
    ]

类似地,我们可以计算这些词在正面类别中的似然概率。

对测试集进行分类

现在,我们准备对测试句子进行分类。我们将分别计算句子属于负面类别和正面类别的概率,并选择概率较高的类别作为预测结果。

计算公式为:
[
P(c \mid d) \propto P(c) \times \prod_{w \in d} P(w \mid c)
]

对于负面类别:
[
P(\text{negative} \mid \text{test}) \propto \frac{3}{5} \times \frac{2}{34} \times \frac{2}{34} \times \frac{1}{34} \approx 6.1 \times 10^{-5}
]

对于正面类别:
[
P(\text{positive} \mid \text{test}) \propto \frac{2}{5} \times \frac{1}{29} \times \frac{1}{29} \times \frac{2}{29} \approx 1.1 \times 10^{-5}
]

由于负面类别的概率更高,因此我们预测测试句子“predictable with no fun”的情感为负面。

二元朴素贝叶斯算法 🎯

在情感分析等任务中,一个词是否出现通常比它出现的频率更重要。例如,“fantastic”这个词的出现强烈暗示了用户对电影的积极感受,但这个词出现五次可能并不比出现一次提供多得多的信息。

基于这种直觉,我们引入二元多项式朴素贝叶斯算法(简称二元朴素贝叶斯)。该算法非常简单:

在训练和测试时,我们将每个文档内的词频截断为1。也就是说,如果一个词在文档中出现多次,我们只按出现一次处理。

请注意,这与伯努利朴素贝叶斯不同,后者通常不用于文本分类。

二元朴素贝叶斯的算法步骤如下:

训练阶段:

  1. 计算先验概率,方法与标准朴素贝叶斯完全相同。
  2. 在计算似然概率之前,对每个文档进行去重处理。对于文档 (j) 中的每个词类型 (w),如果存在多个副本,只保留一个。然后将所有文档的文本拼接起来,像之前一样计算似然概率。

测试阶段:

  1. 同样,先移除测试文档 (d) 中的所有重复词。
  2. 然后使用标准的朴素贝叶斯公式进行计算。

让我们通过一个例子来理解二元化处理的影响。

假设我们有四个文档(两正两负)。在标准计数中,“and”在正面文档中出现2次,在负面文档中出现0次;“great”在正面文档中出现3次,在负面文档中出现1次。

在对每个文档进行二元化(即文档内去重)后:

  • 原本有两个“and”的文档现在只计为一个“and”。
  • 原本有两个“great”的文档现在只计为一个“great”。

然后,我们基于二元化后的文档重新统计词频。请注意,一个词在某个类别中的总计数仍然可能大于1,因为它可以出现在该类别的多个文档中。例如,“great”在二元化后,仍然在两个正面文档中出现,因此它在正面类别中的计数为2。

总结

本节课中,我们一起学习了朴素贝叶斯方法在情感分析中的详细应用。我们通过示例演示了如何计算先验概率和似然概率,并对测试句子进行分类。此外,我们还介绍了适用于情感分析任务的二元朴素贝叶斯算法,该算法通过文档内词频二元化来强调词的出现与否,而非出现频率,在实践中往往能取得更好的效果。

📚 课程 P23:L4.5 - 情感分析延展内容

在本节课中,我们将要学习情感分析中的两个重要方面:语言否定现象和词典的使用。同时,我们也会探讨朴素贝叶斯模型在文本分类中的其他应用场景,例如垃圾邮件过滤和语言识别。


🔍 语言否定现象的处理

上一节我们介绍了情感分类的基础,本节中我们来看看一个在情感分类中常见的重要问题:语言否定现象。

例如,句子 “I really like this movie”“I really don't like this movie” 的含义截然不同。在这里,否定词“don't”将“like”的含义从正面转变为负面。否定同样可以将负面含义弱化为相对正面,例如“dismiss this film”被“don't”修饰后,其负面程度会降低。

一个简单且经典的处理基线方法是:在否定词(如 not, never, no, don't)与其后的标点符号之间的每个单词前,添加字符串 NOT_

以下是一个处理示例:

# 原始句子: "didn't like this movie, but I..."
# 处理后: "didn't NOT_like NOT_this NOT_movie, but I..."

这种方法本质上是将词汇表大小翻倍,创建了如 NOT_like 这样的新词元,这些词元将成为判断负面评论的有力线索。


📖 词典在情感分析中的应用

有时,我们可能没有足够的标注训练数据,或者训练数据与测试数据的分布不同。在这种情况下,使用预先构建的词典会很有帮助。

以下是两个常用的公开情感词典示例:

  • MP QA 主观性线索词典:标注了约7000个单词的正负极性,例如 admirable(正面)、awful(负面)。
  • General Inquirer 词典:一个更早期的词典,除了正负面词,还包含如主动/被动、愉悦/痛苦等类别的词汇列表。

在情感分类中使用词典的方法是:创建新的特征。我们不再为每个具体的词(如 good, great)单独计数,而是将它们汇总。

以下是特征构建方式:

  • 正面词特征:每当出现词典中的任何一个正面词时,该特征计数加1。
  • 负面词特征:每当出现词典中的任何一个负面词时,该特征计数加1。

公式可以表示为:
正面特征值 = Σ (文档中每个属于正面词典的词)
负面特征值 = Σ (文档中每个属于负面词典的词)

这样,我们就将大量稀疏的词汇特征压缩成了两个密集的特征。这在训练数据稀疏或与测试集分布不一致时特别有用。


⚙️ 朴素贝叶斯的其他应用

朴素贝叶斯模型不仅适用于情感分析,也能很好地应用于其他文本分类任务。

垃圾邮件过滤

这是一个著名的应用场景。朴素贝叶斯分类器可以使用以下特征:

  • 邮件正文是否包含“millions of dollars”。
  • 发件人地址是否以数字开头。
  • 邮件主题是否全部大写。
  • 是否出现“100% guaranteed”等短语。

语言识别

这是确定一段文本使用何种语言的任务,是一个重要的预处理步骤。事实证明,基于字符N-gram的特征对此任务非常有效,因为某些字符组合对特定语言具有高度辨识度。

需要注意的是,训练时应涵盖每种语言的不同变体。例如,对于英语,应确保训练数据包含美式英语、非洲裔美国人英语、印度英语等多种变体。


✅ 课程总结

本节课中我们一起学习了情感分析的延展内容,包括如何处理语言否定现象以及如何利用词典来增强模型在数据稀缺情况下的表现。同时,我们也看到了朴素贝叶斯这一“并不朴素”的模型在其他文本分类任务(如垃圾邮件检测和语言识别)中的广泛应用。

总结来说,朴素贝叶斯模型速度快、存储需求低、对小规模训练数据友好,并且对无关特征相对稳健。在特征独立性假设成立的情况下,它甚至是最优分类器。因此,它是文本分类任务中一个可靠的基础模型。当然,当拥有足够训练数据时,逻辑回归、神经网络等分类器通常能获得更高的准确率。

📚 课程 P24:L4.6 - 朴素贝叶斯与语言模型的关系

在本节课中,我们将要学习朴素贝叶斯分类器与语言模型之间的紧密联系。我们将看到,朴素贝叶斯本质上可以被视为一种特殊的语言模型。


🔗 朴素贝叶斯与语言模型的联系

朴素贝叶斯与语言模型有着非常密切的关系。

让我们看看这是如何成立的。我们将从观察多项式朴素贝叶斯的生成模型开始。

想象我有一个类别,假设是“中国”。我们想象正在随机生成一篇关于中国的文档。

我们可能会这样开始:第一个词 x1 是“上海”,第二个词是“和”,第三个词是“深圳”,第四个词是“发行”,第五个词是“债券”,以此类推。我们就生成了一篇关于中国的小文档,一篇随机的小文档。

这个生成模型向我们展示的是,每个词都是独立地从某个类别中,以特定概率生成的词。我们为每个词都维护了一组概率。

让我们思考一下这一点。


🧩 朴素贝叶斯作为语言模型

通常,朴素贝叶斯分类器可以使用各种特征,如URL、电子邮件地址等。我们将在垃圾邮件检测中讨论这一点。但是,如果在前面的幻灯片中,我们只使用词作为特征,并且使用文本中的所有词,那么结果就是,朴素贝叶斯的这个生成模型使其与语言模型有了重要的相似性。朴素贝叶斯实际上是一种语言模型。

具体来说,在朴素贝叶斯分类器中,每个类别都是一个一元语言模型

我们可以这样理解:在朴素贝叶斯分类器中,似然项为每个词分配了给定类别的概率 P(词 | 类别)

对于一个句子(甚至整个文档),由于我们将所有词的概率相乘,我们计算的是给定类别的句子概率 P(句子 | 类别)。我们只是将所有词在给定类别下的似然相乘。

让我们看看这是如何运作的。想象我们有一个“积极”类别。我们有我们的似然:P(I | 积极)P(love | 积极)P(this | 积极) 等等。假设 P(I | 积极) 是 0.1,P(love | 积极) 是 0.1,以此类推。

好的,这就是我们的朴素贝叶斯分类器。我们可以将其完全视为一个语言模型。我们有一个词序列:“I love this fun film”。朴素贝叶斯为这个序列中的每个词分配了一组概率(来自该类别的概率),例如 0.1, 0.1, 0.05 等等。

因此,如果我们将所有这些概率相乘,我们就可以得到这个句子的概率。所以,朴素贝叶斯中的每个类别,就是一个以类别为条件的一元语言模型。


🏃‍♂️ 运行两个语言模型进行比较

上一节我们介绍了朴素贝叶斯如何作为语言模型,本节中我们来看看它是如何进行分类决策的。

当我们问“哪个类别为文档分配了更高的概率”时,这就像我们在运行两个独立的语言模型。

这里我展示了两个独立的语言模型:“积极”类别和“消极”类别。每个模型都有独立的概率。例如,这是 P(I | 消极),这是 P(I | 积极)。我猜人们在不喜欢某物时更可能使用“I”这个词。

现在,如果我们取一个特定的句子:“I love this fun film”。我们问:这个序列的概率是多少?根据我们的第一个模型,这个序列的概率是多少?根据我们的第二个模型,概率又是多少?

每个模型为每个词分配一个概率,这就是朴素贝叶斯的似然。我们可以将它们全部相乘。通过观察,你可以看出,正面的概率相乘(主要因为这个词和那个词)将远高于负面的概率相乘。

因此,你可以将朴素贝叶斯视为:每个类别都是一个独立的、以类别为条件的语言模型。我们将运行每个语言模型来计算测试句子的似然,然后选择具有更高概率的语言模型作为其类别,也就是更可能的类别。


📝 总结

本节课中,我们一起学习了朴素贝叶斯与语言模型之间的紧密关系。我们了解到,朴素贝叶斯分类器中的每个类别都可以被看作一个一元语言模型,分类过程本质上是比较不同类别语言模型对文档生成概率的高低。

📊 课程 P25:Le4.7 - 精确率、召回率与F值

在本节课中,我们将学习如何评估文本分类系统的性能。我们将正式介绍精确率(Precision)和召回率(Recall)这两个核心概念,并了解如何将它们组合成一个综合指标——F值(F-measure)。这些评估指标不仅适用于文本分类,也广泛应用于自然语言处理的其他任务中。

📈 理解评估的起点:混淆矩阵

上一节我们提到了评估的重要性,本节中我们来看看评估的起点——混淆矩阵。混淆矩阵是一个2x2的表格,它描述了分类系统在预测时可能出现的四种情况。

对于任何一条待评估的数据,其真实情况(Truth)和系统预测(System)可以构成以下四种状态:

  • 真正例(True Positive, TP):数据确实属于某个类别,系统也预测它属于该类别。
  • 假负例(False Negative, FN):数据确实属于某个类别,但系统预测它不属于该类别。
  • 假正例(False Positive, FP):数据不属于某个类别,但系统预测它属于该类别。
  • 真负例(True Negative, TN):数据不属于某个类别,系统也预测它不属于该类别。

⚖️ 准确率的局限性

在两类样本数量大致相当的分类任务中(例如垃圾邮件分类),一个合理的评估指标是准确率(Accuracy)。准确率衡量的是所有预测中正确的比例。

准确率公式
准确率 = (TP + TN) / (TP + FP + FN + TN)

然而,当处理类别分布极不均衡的任务时,准确率会失效。例如,在网页中检测“鞋类品牌”的提及。假设在10万个词中,只有10个是鞋类品牌,其余99990个都不是。如果一个系统简单地预测“所有词都不是鞋类品牌”,那么它的准确率高达99.99%,但它完全没有完成我们检测品牌的目标。因此,我们需要更关注少数类别的评估指标。

🎯 精确率与召回率

为了解决上述问题,我们引入精确率和召回率。这两个指标完全聚焦于我们关心的“正例”(例如鞋类品牌)上。

以下是这两个指标的定义:

  • 精确率(Precision):在所有被系统判定为正例的样本中,真正为正例的比例。它衡量的是系统预测的“准度”。
    • 公式精确率 = TP / (TP + FP)
  • 召回率(Recall):在所有真实为正例的样本中,被系统正确找出的比例。它衡量的是系统预测的“查全率”。
    • 公式召回率 = TP / (TP + FN)

回到鞋类品牌的例子。如果一个系统预测了40个词为品牌,其中8个是正确的(TP=8),32个是错误的(FP=32),还有2个真实品牌没被找到(FN=2)。那么:

  • 精确率 = 8 / (8+32) = 20%
  • 召回率 = 8 / (8+2) = 80%

🔄 精确率与召回率的权衡

从上面的例子可以看出,精确率和召回率之间存在一种权衡关系。通常,为了提高召回率(找到更多正例),系统需要放宽判断标准,这会导致误判增加,从而降低精确率。反之,为了提高精确率(确保预测结果更可靠),系统需要更严格,这可能会漏掉一些正例,从而降低召回率。

这种权衡在不同应用场景下有不同的侧重点:

  • 在法律证据发现等场景中,高召回率至关重要,因为不能遗漏任何相关证据。
  • 在向用户展示推荐结果时,高精确率可能更重要,因为需要确保展示的内容是用户真正感兴趣的。

🧮 综合指标:F值

虽然精确率和召回率各自提供了有价值的信息,但有时我们需要一个单一指标来综合比较不同系统。这就是F值(F-measure)的作用。F值是精确率和召回率的加权调和平均数。

F值通用公式
F = ( (β² + 1) * P * R ) / ( β² * P + R )
其中,P代表精确率,R代表召回率,β是一个参数,用于控制对召回率的相对重视程度(β>1时更重视召回率,β<1时更重视精确率)。

最常用的是平衡F值,即F1值,它赋予精确率和召回率相同的权重(β=1)。

F1值简化公式
F1 = 2 * P * R / (P + R)

这个公式简洁明了,是实践中使用最广泛的综合评估指标。

📝 课程总结

本节课中,我们一起学习了文本分类的核心评估指标。

  1. 我们首先通过混淆矩阵理解了分类结果的四种可能状态。
  2. 我们认识到在类别不均衡的任务中,准确率这一指标存在严重缺陷。
  3. 为此,我们引入了精确率召回率,它们能有效评估系统对目标类别的识别能力。
  4. 我们探讨了精确率与召回率之间存在的权衡关系,这在设计系统时需要根据应用场景进行考量。
  5. 最后,我们学习了将两者结合的F值,特别是F1值,它为我们提供了一个单一、平衡的综合评估指标。

掌握这些概念,将帮助你更好地设计、优化和评估自然语言处理系统。

📊 课程 P26:L4.8 - 文本分类评估

在本节课中,我们将学习如何评估文本分类系统的性能。我们将介绍多类别分类的评估方法,包括混淆矩阵、精确率、召回率以及如何通过宏平均和微平均来综合多个类别的性能。最后,我们会讨论如何正确划分数据集以避免过拟合。


📄 路透社数据集

上一节我们介绍了精确率和召回率,现在让我们转向文本分类评估中的其他问题。

一个常用的文本分类数据集是路透社数据集。它包含21000篇文档,并提供了标准的训练集和测试集划分。该数据集有118个类别,这是一个多值分类问题,因为一篇文章可以属于多个类别。这意味着我们将训练118个独立的分类器,每个分类器进行二元判断。平均每篇文档属于略多于一个类别。以下是一些常见类别及其训练和测试文档的数量:例如,有433篇关于谷物的训练文档和149篇测试文档,类别包括小麦、玉米、利率等。

以下是一篇路透社文档的示例,可以看到它涉及牲畜和猪两个主题。我们的任务是根据文本内容,将这篇文档分类到“牲畜”和“猪”这两个类别中。


📊 混淆矩阵

对于多类别分类,混淆矩阵非常重要。

混淆矩阵告诉我们,对于任意一对类别 C1 和 C2,有多少篇属于 C1 的文档被错误地分配到了 C2。这里有一个小例子:我们有一些关于家禽、小麦或咖啡的文档,以及它们的真实类别和文档数量。旁边是我们的分类器给出的预测类别。

例如,单元格 C(3,2) 中的数字 90 表示,有90篇实际上是关于小麦的文档,但我们的分类器认为它们是关于家禽的。这个分类器似乎非常“偏爱”鸡。混淆矩阵的每个单元格都告诉我们,每个类别的文档有多少被分类到了其他类别。这意味着混淆矩阵的对角线给出了正确分类的数量:例如,有95篇我们预测为“英国”的文档实际上就是关于英国的,而我们预测为“小麦”的文档中,实际上没有一篇是关于小麦的。


🧮 计算评估指标

我们可以使用混淆矩阵来计算之前讨论过的相同指标:精确率和召回率。

让我们从召回率开始。召回率是类别 i 中被正确分类的文档比例,即我们找到了多少篇属于类别 i 的文档。其计算公式为:

召回率 = 真正例 (C_ii) / 该行总和

回顾我们的表格,对于“小麦”这一行,如果我们的真正例是0(说明分类器对小麦的分类非常糟糕),那么召回率就是 0 除以该行所有数字之和(10 + 90 + 1)。

对于精确率,我们要问的是:在我们返回的文档(即整个预测列)中,有多少是正确的?例如,在我们预测为“小麦”的文档中,有多少篇真正是关于小麦的?其计算公式为:

精确率 = 真正例 (C_ii) / 该列总和

准确率则是所有被正确分类的文档比例,即混淆矩阵对角线元素之和除以矩阵中所有元素之和。


⚖️ 宏平均与微平均

由于我们有多个类别,我们需要一种方法将每个类别的精确率和召回率值合并为一个综合指标。通常,拥有一个单一指标很有用。有两种标准方法可以实现这一点。

在宏平均中,我们为每个类别单独计算性能指标(精确率、召回率或F1分数),然后取平均值。例如,如果我们有113个类别,我们将计算113个精确率,然后对它们进行平均,得到宏平均精确率。

在微平均中,我们将所有类别的决策汇总到一个单一的列联表中,然后基于这个总表计算精确率。


📈 宏平均与微平均示例

让我们看一个例子。这里有两个类别:类别1和类别2。表格显示了每个类别的真实正例和负例数量,以及分类器的预测结果。

对于宏平均精确率,我们分别为两个类别计算:

  • 类别1:10 / (10 + 10) = 0.5
  • 类别2:90 / (90 + 10) = 0.9
    因此,宏平均精确率是 (0.5 + 0.9) / 2 = 0.7。

对于微平均,我们将两个列联表相加,得到一个单一的微平均列联表。然后直接从这个总表计算精确率:100 / (100 + 20) ≈ 0.83。

可以看到,微平均分数受常见类别(类别2)的分数主导,因为它的数量更大,在汇总的列联表中会占主导地位。而在宏平均中,每个类别平等参与计算。


🧪 数据集划分与交叉验证

对于文本分类评估,我们需要的不仅仅是精确率或召回率。与许多基于机器学习的自然语言处理算法一样,我们需要训练集、用于衡量性能的测试集,以及一个称为开发测试集或开发集的数据集。

我们在训练集上计算模型参数。开发集则用于在开发系统时测试性能。无论是查看精确率、召回率、F1分数还是准确率,我们都会根据开发集上的分数来发现算法中的错误并开发新特征。

一旦算法开发完成,我们就可以在一个干净的、未曾见过的测试集上进行最终测试。拥有这样一个干净的测试集非常重要,否则,如果我们一直在开发集上报告结果,最终会导致过拟合,报告的性能可能虚高,因为我们的算法已经针对这个开发集进行了调优。一个干净的、未见过的测试集能给我们一个更保守的性能估计。


🔄 交叉验证

由于数据集可能较小或不具代表性,我们可能会遇到抽样误差。因此,如前所述,交叉验证是常见做法。

例如,我们预留一部分数据作为开发集,用其余数据训练模型,然后在开发集上查看性能。接着,我们采用不同的划分方式,用另一部分数据训练,在同一开发集上报告性能。如此重复多次。最后,我们将每次划分的结果汇总,计算一个总的、汇总的开发集性能。这让我们可以避免使用非常小或不具代表性的测试集,并且大部分数据在不同的划分中都能既用于训练也用于测试。

尽管如此,在最后,我们仍然需要一个干净的、未见过的最终测试集,以避免对这些开发集产生过拟合。


📝 总结

本节课中,我们一起学习了多种评估文本分类的方法。我们介绍了精确率、召回率和F1分数,并讨论了在多类别(超过两个类别)问题中该如何处理。我们还了解了宏平均和微平均的概念。这些思想和方法将在整个自然语言处理领域中得到广泛应用。

课程 P27:L5.1 - 生成模型与判别模型 🧠

在本节课中,我们将学习生成模型与判别模型之间的区别,并重点介绍朴素贝叶斯分类器与逻辑回归分类器及其关系。


逻辑回归是自然科学与社会科学中的重要分析工具。它也是分类任务中基础的监督机器学习工具,并且是神经网络的基础。我们将在后续课程中看到,前馈神经网络可以被视为一系列堆叠的逻辑回归分类器。

朴素贝叶斯分类器与逻辑回归之间存在一个重要区别:朴素贝叶斯是一种生成式分类器,而逻辑回归是一种判别式分类器。我们来理解一下这意味着什么。

生成式 vs 判别式分类器 🐱🐶

假设我们要区分猫和狗的图片,构建一个分类器来判断图像是猫还是狗。

生成式分类器以这种方式思考问题:我们将为猫图像建立一个模型。这个模型可能了解胡须、耳朵和眼睛等特征。它会为一张图像分配一个概率,即这张图像有多像猫。我们同样会为狗建立一个模型,了解狗的特征,如耷拉的耳朵、鼻子等。当给定一张新图像时,我们会运行猫分类器和狗分类器,然后看哪个模型能更好地拟合这张图像的细节。

判别式分类器的任务则仅仅是区分狗和猫。例如,它可能注意到训练集中所有狗的图像都有项圈。判别式分类器会专注于那些能区分两类图像的特征,它被训练来寻找区分性特征,并可能忽略其他一切。

形式化定义与核心概念 📊

更正式地说,我们的任务是从文档 D 中找到正确的类别 C。

在朴素贝叶斯中,我们计算所有类别中最可能的类别。我们通过将似然(给定类别下看到此图像特征的可能性)与先验(该类别的普遍可能性)相乘来实现。

公式: P(C|D) ∝ P(D|C) * P(C)

逻辑回归则进行类似的最大化操作,我们试图从所有类别中选出能最大化一个分数的类别。但这里的分数是直接的后验概率。我们将直接计算给定图像下类别 C 的概率。

公式: P(C|D) (直接建模)

逻辑回归分类器的组成部分 ⚙️

一个概率机器学习分类器包含一组组件。给定 M 个输入-输出对 (x⁽ⁱ⁾, y⁽ⁱ⁾),其中 x 是输入,y 是输出。

以下是逻辑回归分类器的核心组成部分:

  • 特征表示:对于每个输入观测 x⁽ⁱ⁾,我们有一个特征向量 [x₁, x₂, ..., xₙ]xⱼ 表示第 j 个特征。
  • 分类函数:该函数计算预测类别 ŷ(例如,狗或猫,或 0/1)。它通过计算给定 x 下 y 的概率 P(y|x) 来实现。本节课将引入 sigmoid 或相关的 softmax 函数作为核心分类函数。
  • 参数:我们需要学习这个函数的参数。
  • 目标函数:为了学习参数,我们需要一个目标。我们将使用交叉熵损失作为目标函数。
  • 优化算法:最后,我们需要一个算法来优化这个目标函数。我们将引入随机梯度下降算法来实现优化。

逻辑回归的两个阶段 🔄

像许多机器学习分类器一样,逻辑回归分为两个阶段:

上一节我们介绍了逻辑回归的组成部分,本节中我们来看看它的工作流程。

训练阶段

  • 学习权重(一组权重 w 和偏置 b)。
  • 使用随机梯度下降来寻找最优权重。
  • 使用交叉熵损失来衡量权重的优劣。

测试阶段

  • 给定一个测试样本 x
  • 使用学习到的权重,为每个可能的类别 y 计算 P(y|x)
  • 返回概率较高的那个标签(对于二分类,y 为 0 或 1)。

本节课中,我们一起学习了生成模型与判别模型的核心区别,并初步了解了逻辑回归分类器的直觉、组成部分及其与朴素贝叶斯分类器的关系。在后续课程中,我们将深入更多细节。

📚 课程 P28:L5.2 - 逻辑回归分类

在本节课中,我们将学习如何使用逻辑回归进行分类,并介绍重要的 Sigmoid 函数。

📖 概述

我们将探讨逻辑回归如何将文本分类任务(如情感分析、垃圾邮件识别)转化为一个概率预测问题。逻辑回归通过加权特征和 Sigmoid 函数,为输入文档计算其属于某个类别的概率。

📝 文本分类任务

逻辑回归可用于多种文本任务,例如判断情感是积极还是消极,判断消息是否为垃圾邮件,或判断作者身份(例如著名的《联邦党人文集》作者是汉密尔顿还是麦迪逊的案例)。

对于文本分类,我们有一个文档 X 和一个固定的类别集合 C。我们的目标是输出一个预测类别 Y_hat,它是给定 X 后,从 C 中选出的一个类别。

给定一系列输入输出对(我们使用上标表示每个观测值 X 及其标签 Y),我们将表示一组特征向量 x^1 到 x^N。然后,我们将从集合 {0, 1} 中给出一个预测类别 Y_hat,这就是二元分类。

⚖️ 逻辑回归中的特征与权重

在逻辑回归中,我们利用特征。每个特征 X_i 表达了关于文本的某个事实,它有一个权重 W_i,告诉我们该特征对分类决策的重要性。

例如,一个特征 X_i 可能是“评论包含‘awesome’这个词”。假设其权重 W_i 是 +10,这表明对于情感分类器来说,包含“awesome”这个词是强烈指向积极情感的正面线索。

相反,特征“评论包含‘abysmal’”可能有一个权重 W_j = -10,这意味着“abysmal”的权重很高,但是负权重,因此强烈指向消极类别。

另一个特征 X_k,“评论包含‘mediocre’”,可能略微负面,因此其权重为轻微的负值,使我们略微倾向于消极类别。

总而言之,对于逻辑回归,我们取一个输入观测值,将其表示为一组特征。X 将被表示为一组 n 个特征。我们将有一组 n 个权重 W_1 到 W_n(有时这些权重被称为 θ_1 到 θ_n,有时我们将整个参数集称为 θ)。然后,我们将从集合 {0, 1} 中输出一个预测类别 Y_hat。

这就是二元逻辑回归。我们稍后会看到其扩展到多项逻辑回归,其中输出可以来自更大的可能类别集合,但目前我们专注于二元分类。

🧮 分类计算过程

权重 W 告诉我们特征的重要性。我们还会有一个偏置项(有时称为截距),这是另一个加到加权输入上的实数。

我们将所有这些加权特征和偏置项相加。也就是说,我们取每个特征 X 及其权重 W,将它们相乘,然后全部求和,再加上偏置项 B,计算出一个总分。

在这些课程中,我们将使用线性代数中的点积符号来表示此类求和。两个向量 A 和 B 的点积写作 A·B,是对应元素乘积的总和。因此,这里所有 W_i * X_i 的总和我们将表示为 W·x

因此,我们计算这个总和 Z = W·x + B。如果 Z 值高,我们会说这是正类(例如积极情感)。如果 Z 值低(W·x + B 低),那么我们会说这是负类(y = 0)。

🎲 构建概率分类器

然而,我们的目标是构建一个概率分类器。我们希望形式化上一张幻灯片中提到的“如果总和高”这一概念。我们想要一个能给出概率的模型,就像朴素贝叶斯那样。

我们希望模型能给出一个概率,即在给定输入 x 和所有参数 θ(权重)的情况下,y=1 的概率 P(y=1 | x; θ),或者 y=0 的概率 P(y=0 | x; θ)。

问题在于,Z = W·x + B 本身不是一个概率。它只是一个数字。实际上,没有任何东西强制 W·x + B 位于 0 和 1 之间,权重是实数值,它甚至可能是负数。事实上,Z 的范围是从负无穷到正无穷。

因此,我们将使用一个 Z 的函数,该函数的输出范围在 0 到 1 之间。我们将使用的函数是非常有用的 Sigmoid 函数或逻辑函数。

📈 Sigmoid 函数

y = σ(z),其中 Sigmoid 函数定义为 σ(z) = 1 / (1 + e^{-z})

下图展示了 Sigmoid 函数,因其形状像字母 S 而得名。该函数取一个实数值,并将其巧妙地映射到范围 (0, 1) 内。

请注意,它在零点附近几乎是线性的,但异常值会被压缩趋向于 0 或 1,因此它是一个非常有用的函数。

🔄 逻辑回归的工作流程

逻辑回归的工作流程如下:我们拥有特征、权重和偏置项,我们计算 Z = W·x + B,将其通过 Sigmoid 函数,然后将其视为概率。

现在,我们只需要弄清楚如何将 Sigmoid 的输出转化为概率。

🧾 从 Sigmoid 输出到概率

我们几乎已经完成了。如果我们将 Sigmoid 应用于加权特征的总和,我们得到一个介于 0 和 1 之间的数字。为了使其成为概率,我们只需要确保两种情况 P(y=1) 和 P(y=0) 之和为 1。

我们可以按如下方式实现:我们令 P(y=0) = 1 - σ(W·x + B),而 P(y=1) = σ(W·x + B)。这样我们就保证了这两者之和为 1。

我们可以在这里做一点算术,代入 σ(z) = 1 / (1 + e^{-z})。顺便说一下,Sigmoid 函数有一个有用的性质:1 - σ(x) = σ(-x)。因此,概率 P(y=0),即我们所说的 1 - σ(W·x + B),也可以表示为 σ(-(W·x + B))。我们有时会看到这种形式。

🚦 决策边界

现在,为了将概率转化为分类器,我们的估计 Y_hat(我们对示例 X 类别的估计)是:如果 P(y=1) > 0.5,则 Y_hat = 1;否则 Y_hat = 0。这里的 0.5 就是决策边界。

在下图中,X 轴表示 W·x + B,Y 轴表示由我们的 Sigmoid 函数产生的概率 P(y=1)。这是我们的 σ(W·x + B) 图,这里是我们的决策边界。

因此,任何大于 0.5 的情况:如果 W·x + B > 0.5,那么 P(y=1) > 0.5,我们将把这个示例标记为正类。

换句话说,给定一个示例 x,如果 W·x + B > 0,我们将赋予其类别 Y_hat = 1;如果 W·x + B ≤ 0,我们将称其为类别 0。

📌 总结

本节课中,我们一起学习了逻辑回归如何使用 Sigmoid 函数,从输入示例 X 的加权特征出发,将其分配到类别 y=1 或 0。我们了解了特征权重的重要性、如何计算加权和 Z、以及如何通过 Sigmoid 函数将 Z 转化为概率,并最终利用决策边界(0.5)做出分类预测。逻辑回归为我们提供了一种强大而直观的方法来构建文本概率分类器。

📝 课程 P29:L5.3 - 应用逻辑回归解决文本情感分析问题

在本节课中,我们将学习如何应用逻辑回归模型来解决一个具体的文本情感分析问题。我们将通过一个电影评论情感分类的例子,详细拆解从特征提取到最终分类预测的完整过程。


🎬 示例:电影评论情感分类

假设我们需要对一段电影评论进行二元情感分类。我们的目标是判断该评论的情感类别是 1(积极)还是 0(消极)。以下是待分析的评论文本:

It's Hokey. There are virtually no surprises. The writing is second rate. So why was it so enjoyable, you get the idea.

我们将每一条评论视为一个观测样本,并使用六个特征来表征它。

以下是这六个特征的定义及其在该评论中的取值:

  • 特征 X1:文档中出现在某个积极词典里的单词数量。
    • 取值:假设 “enjoyable”, “great”, “nice” 都在我们的积极词典中,那么 X1 的值为 3
  • 特征 X2:文档中出现在某个消极词典里的单词数量。
    • 取值:评论中出现了 “hokey” 和 “second rate”,因此 X2 的值为 2
  • 特征 X3:单词 “no” 是否出现在文档中。
    • 取值:出现了 “no”,因此该特征被激活。
  • 特征 X4:文档中代词的数量。
  • 特征 X5:文档中大写单词的数量。
  • 特征 X6:文档单词总数的自然对数值。
    • 取值:该评论有 66 个单词,其自然对数值约为 4.19

⚖️ 模型权重与偏置

上一节我们定义了特征,本节我们来看看模型如何利用这些特征进行决策。我们假设已经为每个特征学习到了一个实数值权重,并为模型学习到了一个偏置项。这六个特征对应的权重如下:

  • 权重 W1:2.5
  • 权重 W2:-5.0
  • 权重 W3:-1.2
  • 权重 W4:0.5
  • 权重 W5:0.7
  • 权重 W6:0.1

偏置 B:0.1

权重的含义:权重 W1 为正,表示积极词典中的单词数量对做出积极情感判断有正面贡献。权重 W2 为负,表示消极词典中的单词数量与积极情感判断呈负相关,并且其重要性(绝对值)大约是积极特征的两倍。权重揭示了不同特征对最终决策的影响力。


🧮 计算分类概率

现在,我们将特征值 x 和对应的权重 w 代入逻辑回归的公式,来计算该评论属于各个类别的概率。

要计算该文档为积极评论的概率,即 P(y=1),我们计算 sigmoid(W·X + B)

点积计算 W·X
W·X = (2.5*3) + (-5.0*2) + (-1.2*1) + (0.5*0) + (0.7*0) + (0.1*4.19) = 7.5 - 10 - 1.2 + 0 + 0 + 0.419 = -3.281

加上偏置并应用Sigmoid函数
z = -3.281 + 0.1 = -3.181
P(y=1) = σ(z) = 1 / (1 + e^(-(-3.181))) ≈ 1 / (1 + 24.0) ≈ 0.04

因此,该评论为消极评论的概率为:
P(y=0) = 1 - P(y=1) ≈ 1 - 0.04 = 0.96

分类结果:根据这个计算结果,该分类器会判定这条评论为 消极 评论。

注意:此处的计算结果与视频中示例(0.7 和 0.3)不同,这是因为我们此处严格使用了给定的特征值和权重进行计算。视频中的演示可能使用了不同的数值或简化步骤以说明概念。本教程旨在展示正确的计算流程。


🔧 逻辑回归的通用性

逻辑回归的强大之处在于其灵活性,我们可以为任何分类任务构建特征。让我们随机选择另一个任务:句号消歧。

任务描述:判断一个句点 “.” 是表示句子结束,还是缩写的一部分(如 “Dr.” 中的句点)。

以下是可能用到的特征示例:

  • 特征 X1:当前单词是否为小写。
    • 可能权重:正权重。因为以小写单词结尾的句点更可能是句子结束。
  • 特征 X2:当前单词是否在我们的缩写词典中(如 “ST”, “DR”)。
    • 可能权重:负权重。因为缩写后的句点不太可能是句子结束。
  • 特征 X3:一个更复杂的组合特征,例如 “句点前是一个大写单词,但该大写单词是 ‘ST’ 且再前一个单词也是大写”。
    • 可能权重:强负权重。这很可能表示是 “STREET” 的缩写形式。

📋 核心概念总结

本节课中我们一起学习了逻辑回归应用于文本分类的完整流程。

以下是逻辑回归二元分类的核心要素总结:

  1. 类别:我们有两个类别,例如积极和消极情感,可以用 01 表示。
  2. 特征向量 (X):一组表征输入数据的值,可以是计数、布尔值或对数值等。
  3. 权重向量 (W):每个特征对应一个权重,表示该特征对决策的重要性。
  4. 偏置 (B):一个常数项,用于调整决策边界。
  5. 决策函数:通过计算 z = W·X + B,并应用 Sigmoid函数 σ(z) = 1 / (1 + e^{-z}),得到样本属于正类(y=1)的概率。

我们已经了解了逻辑回归如何接收特征值及其权重,并为输入样本计算出一个类别概率。关键在于设计能够有效捕捉问题本质的特征,并通过学习得到合适的权重与偏置。

📚 课程 P3:L1.3 - 词汇与语料集

在本节课中,我们将学习文本处理的基础知识,重点探讨词汇的基本属性(例如,如何计算词汇数量)以及语料集(即文本集合)的概念和不同类型语料集在研究中的特性。


🔢 如何计算句子中的词汇数量

上一节我们介绍了文本处理的基本概念,本节中我们来看看如何具体计算一个句子中的词汇数量。这是一个看似简单但实际复杂的问题。

例如,考虑这个句子:“I do main... mainly business data processing.” 这个句子有多少个单词?答案取决于我们如何定义“单词”。句子中的“main”是一个片段,而“um”则是一个填充停顿词。在语音识别等特定应用中,我们可能需要计算这些元素。

再看另一个例子:“How many cats are in the cat house?” 这里的“cat”和“cats”是同一个词吗?这引出了词元词形的区别。

  • 词元:指具有相同词干、词性和含义的词汇基本形式。例如,“cat”和“cats”属于同一个词元(CAT)。
  • 词形:指词汇在文本中出现的具体表面形式,包括所有屈折变化。例如,“cat”和“cats”是不同的词形。

因此,计算词汇时,明确目标是统计词元还是词形至关重要。


📊 词例与词类型

现在,让我们分析另一个句子片段:“They lay back on the San Francisco grass and looked at the stars and the.” 这个句子有多少个单词?请暂停视频并尝试自己计算。

同样,答案取决于计数方式:

  • 词类型:指句子中唯一出现的不同单词的数量。例如,句子中“the”出现了两次,但作为词类型只计数一次。
  • 词例:指句子中出现的每一个单词实例的总数。例如,两个“the”会分别计数。

此外,“San Francisco”是一个单词还是两个?这同样取决于我们的应用场景和目标。在报告词汇数量时,明确说明使用的是词例数还是词类型数非常重要。

在语料库研究中,我们通常使用以下符号:

  • N:代表语料库中的总词例数。
  • V:代表词汇表,即所有唯一词类型的集合。词汇表的大小(词类型的数量)记为 |V|

词例数(N)和词类型数(|V|)之间存在一种经验关系,称为赫普定律(Heap‘s Law)。该定律指出,词汇表的大小大致与词例数的平方根成正比。公式表示为:

|V| ≈ k * N^β

其中,k和β是常数(对于英语文本,β通常在0.6到0.8之间)。这意味着,随着语料库规模(N)的增长,新词出现的速度会逐渐放缓。


🌍 语料集的多样性及其属性

词汇并非凭空出现在语料库中。任何文本都是由特定的作者、在特定的时间、使用特定的语言变体、为特定的功能而创作的。因此,当我们研究语料集时,需要考虑其多个维度的属性。

以下是影响语料集特性的几个关键维度:

  • 语言与变体:世界上有约7000种语言,每种语言对“词”的定义可能不同。即使在同一种语言(如英语)内部,也存在不同的变体(如非洲裔美国人白话英语)。
  • 语码转换:全球多数人使用多种语言,有时会在同一句子中混合使用,这种现象称为语码转换。处理社交媒体等数据时必须考虑这一点。
  • 文体与体裁:语料集可能包含新闻、小说、非虚构作品、科学论文或维基百科等百科全书文本,不同的文体用词差异很大。
  • 作者人口统计学特征:作者的年龄、性别、种族和社会经济地位等因素都会影响文本的语言特征。

📋 语料集数据表的重要性

由于语料集存在上述多样性,在创建或使用一个语料集时,仔细查阅其数据表至关重要。数据表应详细记录语料集的元数据,以确保研究的可重复性和伦理性。

一个完整的数据表通常包含以下信息:

  • 动机与目的:为何以及由谁收集此语料集。
  • 创作情境:文本在何种情境下创作(如是否与他人对话)。
  • 采集过程:采样方法、作者是否知情同意。
  • 预处理与标注:数据经过了哪些清洗、处理,以及是否有任何人工标注(如词性、句法树等)。
  • 基本属性:涵盖之前提到的语言、变体、体裁、作者信息等。

在构建自己的语料集时,详细记录这些决策同样重要。


✅ 总结

本节课中,我们一起学习了词汇与语料集分析的核心基础。我们明确了词例词类型词元词形的关键区别,并介绍了描述它们规模的符号(N, V, |V|)及赫普定律。我们还探讨了语料集的多样性,强调必须关注其语言、变体、体裁、作者和采集背景等属性,并通过数据表来规范记录这些信息。理解这些概念是进行任何有意义的文本数据分析的第一步。

📚 课程 P30:L5.4 - 交叉熵损失

在本节课中,我们将学习逻辑回归的参数学习过程。我们将从交叉熵损失函数开始,了解如何衡量模型预测与真实标签之间的差距,并理解其在训练过程中的作用。

🎯 概述:损失函数与优化算法

逻辑回归是一种监督分类方法。对于每个观测数据 X,我们知道其正确的标签 Y(0 或 1)。模型会输出一个预测值,我们称之为 Y_hat。我们的目标是学习参数 WB,使得对于每个训练样本,Y_hat 都尽可能接近真实的 Y

为了实现这个目标,我们需要两样东西:

  1. 一个距离估计器,即损失函数成本函数,用于衡量当前预测 Y_hat 与真实标签 Y 的差距。
  2. 一个优化算法,用于迭代更新权重 WB,以最小化这个损失函数。

我们将介绍逻辑回归和神经网络中常用的损失函数——交叉熵损失。在后续课程中,我们将介绍用于最小化损失的标准优化算法——梯度下降及其变体随机梯度下降。

📉 定义损失函数

我们需要一个损失函数 L,来表达对于观测 X,分类器输出 Y_hat(通过 sigmoid(WX + B) 得到)与真实输出 Y(0 或 1)之间的差距。我们希望损失函数能促使训练样本的正确标签拥有更高的预测概率,这种方法被称为条件最大似然估计

我们选择参数 WB,使得在给定观测 X 的条件下,训练数据中真实标签 Y 的对数概率最大。由此推导出的损失函数是负对数似然损失,通常称为交叉熵损失

🔢 推导交叉熵损失

让我们推导应用于单个观测 X 的损失函数。我们希望学习到的权重能最大化正确标签 Y 的概率,即 P(Y|X)。由于只有两个离散结果(0 或 1),我们可以将分类器给出的概率 P(Y|X) 表示为以下两项的乘积:

P(Y|X) = (Y_hat)^Y * (1 - Y_hat)^(1-Y)

现在,让我们将真实值 Y=1Y=0 代入这个方程:

  • 如果 Y=1,方程简化为 Y_hat
  • 如果 Y=0,方程简化为 1 - Y_hat

我们的目标是最大化这个概率。为了数学上的便利,我们对两边取对数,因此我们现在要最大化 log P(Y|X)

log P(Y|X) = Y * log(Y_hat) + (1-Y) * log(1 - Y_hat)

因为最大化概率的对数等价于最大化概率本身。

在机器学习中,更常见的做法是讨论最小化一个损失,而不是最大化一个概率。因此,我们将这个要最大化的目标转化为要最小化的目标,这就得到了交叉熵损失。我们只需对 log P(Y|X) 取负值:

L_CE = -log P(Y|X) = - [Y * log(Y_hat) + (1-Y) * log(1 - Y_hat)]

我们可以代入 Y_hat 的定义来提醒自己如何计算:

L_CE = - [Y * log(σ(WX + B)) + (1-Y) * log(1 - σ(WX + B))]

🧪 在情感分析示例中验证

直观上,我们希望当模型预测接近正确时损失较小,当模型困惑(预测错误)时损失较大。让我们通过情感分析的例子来看看交叉熵损失是否做到了这一点。

情况一:模型预测正确
假设有一个影评:“Hokey. There are virtually no surprises, so why was it so enjoyable?”,其真实标签 Y=1(正面评价)。假设我们的模型计算 σ(WX + B) 后,给出 Y_hat = 0.7。这是一个不错的预测。

Y=1Y_hat=0.7 代入交叉熵损失公式:
L = -[1 * log(0.7) + 0 * log(1-0.7)] = -log(0.7) ≈ 0.36
我们得到了一个相对较小的损失值 0.36。

情况二:模型预测错误
现在,假设同一个句子的真实标签其实是 Y=0(负面评价),例如评论是:“bottom line, this movie's terrible. I beg you not to see it.”。此时,我们的模型仍然给出了 Y_hat = 0.7 的高概率(认为它是正面的),模型是困惑的、错误的。我们希望此时的损失会更高。

对于 Y=0,模型认为它是负面评价的概率是 1 - Y_hat = 0.3

Y=0Y_hat=0.7(即 1 - Y_hat = 0.3)代入公式:
L = -[0 * log(0.7) + 1 * log(0.3)] = -log(0.3) ≈ 1.20
我们得到了一个更高的损失值 1.20,远大于模型正确时 0.36 的损失。

正如我们所愿,交叉熵损失确实做到了:当模型正确时损失小,当模型错误时损失大。

🎓 总结

本节课中,我们一起推导了交叉熵损失函数,并看到了它如何应用于我们的情感分析示例。交叉熵损失通过惩罚错误的预测并奖励正确的预测,有效地衡量了模型输出与真实标签之间的差异。这个损失函数对于逻辑回归至关重要,同样,正如我们将在未来的课程中看到的,它对于神经网络也同等重要。

课程 P31:L5.5 - 随机梯度下降 🎯

在本节课中,我们将学习用于优化逻辑回归和神经网络权重的随机梯度下降算法。我们的目标是找到能够最小化模型损失函数的最优权重。

概述 📋

梯度下降是一种通过确定参数空间中函数斜率上升最陡的方向,并向相反方向移动,从而找到函数最小值的方法。对于逻辑回归,其损失函数是凸函数,只有一个最小值,因此梯度下降总能找到全局最优解。然而,对于多层神经网络,损失函数是非凸的,梯度下降可能会陷入局部最小值。

梯度下降的直观理解 🧭

假设我们的系统参数只是一个标量 W。损失函数 L 的形状如图所示,X轴代表参数 W。我们的目标是从起点(例如 W=0)出发,找到使损失函数最小的 W 值(W_min)。

梯度上升算法通过计算当前点的损失函数梯度,并向相反方向移动来回答“应该向左还是向右移动”的问题。对于单变量函数,梯度可以非正式地理解为斜率。如果斜率为负,则意味着我们应该向正方向移动以找到最小值。

学习率与参数更新 📈

移动的幅度由损失函数相对于 W 的斜率值乘以学习率 η 决定。学习率越高,意味着每一步移动 W 的幅度越大。参数更新的公式为:

W_new = W_old - η * (dL/dW)

扩展到多变量情况 🔄

在实际应用中,我们通常需要处理多个参数(例如权重 W 和偏置 B)。梯度是一个向量,它表达了沿着每个维度最陡斜率的方向分量。对于二维情况,梯度向量有两个正交分量,分别告诉我们地面在 W 方向和 B 方向上的倾斜程度。

在逻辑回归中,参数向量 W 可能很长,因为输入特征向量 x 可能很长,我们需要为每个 x_i 分配一个权重 w_i。对于每个变量 w_i,梯度都有一个分量,告诉我们该变量对总损失函数 L 的影响。这个斜率表示为损失函数相对于 w_i 的偏导数 ∂L/∂w_i

梯度则定义为这些偏导数的向量:

∇_θ L(ŷ, y) = [∂L/∂θ_1, ∂L/∂θ_2, ..., ∂L/∂θ_n]^T

其中,θ 代表所有参数(WB)。

因此,基于梯度更新 θ 的最终公式为:

θ_new = θ_old - η * ∇_θ L(ŷ, y)

逻辑回归的梯度计算 🧮

对于逻辑回归,交叉熵损失函数为:

L = -[y * log(σ(wx + b)) + (1 - y) * log(1 - σ(wx + b))]

这个函数关于单个权重 w_j 的梯度有一个非常直观的形式:

∂L/∂w_j = (σ(wx + b) - y) * x_j

它等于模型预测值 ŷ 与真实值 y 的差值,乘以对应的输入特征值 x_j

随机梯度下降算法 ⚙️

随机梯度下降是一种在线算法,它通过在每个训练样本后计算损失函数的梯度,并将参数 θ 向梯度的相反方向“微调”,来最小化损失函数。

以下是算法的步骤:

  1. 对于每个训练样本 (x, y),计算预测值 ŷ
  2. 计算损失 L(ŷ, y),了解预测值与真实值的差距。
  3. 计算梯度 ∇_θ L,它指示了哪个方向会增加损失。
  4. 向梯度的相反方向更新参数:θ = θ - η * ∇_θ L

算法可以在以下情况终止:

  • 收敛(参数变化很小)。
  • 梯度范数小于预设的阈值 ε
  • 在验证集上的损失开始上升(早停)。

超参数:学习率 ⚖️

学习率 η 是一个必须调整的超参数

  • 如果 η 太高,学习者步长过大,可能会越过损失函数的最小值。
  • 如果 η 太低,学习者步长过小,需要很长时间才能到达最小值。

通常的做法是从一个较高的学习率开始,然后缓慢降低它。

超参数是机器学习模型中的一种特殊参数。与算法从训练集中学习的常规参数(如权重 WB)不同,超参数是由算法设计者选择的,会影响算法的工作方式。

总结 🎓

本节课我们一起学习了随机梯度下降算法。我们了解了其核心思想:通过计算损失函数的梯度并沿反方向更新参数来寻找最小值。我们探讨了从单变量到多变量的扩展,学习了参数更新的公式,并理解了学习率这一关键超参数的作用。最后,我们概述了随机梯度下降在逻辑回归中的具体应用步骤。在下一讲中,我们将提供更多细节。

课程 P32:L5.6 - 随机梯度下降拓展 🚀

在本节课中,我们将通过一个具体的例子来演示随机梯度下降的过程,并补充一些相关细节。

我们将一起完成梯度下降算法的一个单步计算。

1. 设定示例场景

我们将使用一个简化版的情感分类示例,它处理一个单独的观测样本 X。其正确标签是 y = 1,代表这是一个正面评价。该样本仅有两个特征:X1 的值为 3,代表评论中正面词汇的数量;X2 的值为 2,代表评论中负面词汇的数量。

我们假设有三个参数:两个权重 W1W2,以及一个偏置项 B。在初始阶段 θ⁰,我们假设所有参数值都设为 0。初始学习率 η 设为 0.1。

2. 计算梯度

以下是随机梯度下降的更新方程:
θ_new = θ_old - η * ∇L(θ)
为了执行这个更新,我们需要知道损失函数 L 的梯度,然后将其乘以学习率并减去。

在逻辑回归中,我们已经知道,对于权重 Wⱼ 的梯度计算公式为:
∇Wⱼ = (σ(W·X + b) - y) * Xⱼ
在我们的示例中,有三个参数,因此梯度向量有三个维度,分别对应 W1W2B

我们可以按如下方式计算第一个梯度。这里我们直接使用预先推导出的导数公式进行计算。

首先,计算 W·X + B。在初始值下,这个值为 0。真实标签 y 是 1。因此,我们有 σ(0) - 1。而 σ(0) = 0.5

最终,我们得到:

  • 对于 W1(0.5 - 1) * X1 = -0.5 * 3 = -1.5
  • 对于 W2(0.5 - 1) * X2 = -0.5 * 2 = -1.0
  • 对于 B(0.5 - 1) * 1 = -0.5

所以,我们最终的梯度向量 ∇L(θ)[-1.5, -1.0, -0.5]

3. 更新参数

现在我们已经有了梯度,可以通过将初始参数向量 θ⁰ 沿着梯度相反方向移动来计算出新的参数向量 θ¹

新的参数计算公式为:
θ¹ = θ⁰ - η * ∇L(θ)

代入我们的数值:

  • 新的 W1 = 0 - 0.1 * (-1.5) = 0.15
  • 新的 W2 = 0 - 0.1 * (-1.0) = 0.10
  • 新的 B = 0 - 0.1 * (-0.5) = 0.05

因此,第一步更新后,我们的参数 θ¹ 变为 [0.15, 0.10, 0.05]

需要注意的是,这个观测样本 X 恰好是一个正面例子。如果我们后续看到更多包含大量负面词汇的负面例子,我们可能会期望 W2 的值最终会变为负数。

4. 从随机到小批量梯度下降

上一节我们计算了单个样本的梯度更新。随机梯度下降之所以称为“随机”,是因为它每次随机选择一个样本来计算梯度并更新权重,以优化该单个样本的性能。这可能导致权重更新路径非常“颠簸”和不稳定。

因此,更常见的做法不是基于单个实例,而是基于一批训练样本来计算梯度。

  • 批量训练:在整个数据集上计算梯度。这种方法能提供权重移动方向的极佳估计,但代价是需要花费大量时间处理训练集中的每一个样本来计算这个“完美”方向。
  • 小批量训练:这是一种折衷方案。我们在一个包含 M 个样本的组(例如 512 或 1024,远小于整个数据集)上进行训练。

小批量训练还具有计算效率上的优势。小批量可以很容易地进行向量化操作,其大小可以根据计算资源来选择。这允许我们并行处理一个小批量中的所有样本,然后累加损失,这是单个样本训练或全批量训练所无法实现的。

5. 总结

本节课中,我们一起学习了随机梯度下降算法,并通过一个具体例子逐步计算了参数更新。我们还讨论了重要的变体——小批量训练,它通过在多个样本上计算梯度来平衡更新稳定性和计算效率,是实践中广泛采用的方法。

课程 P33:L6.1 - 网络爬虫与信息检索初步 🕷️🔍

在本节课中,我们将要学习信息检索任务的基本概念,特别是当前占主导地位的网络搜索形式。我们将从定义信息检索开始,了解其基本框架,并初步探讨如何评估检索系统的性能。


信息检索的定义

信息检索任务可以定义如下:我们的目标是从通常存储在计算机中的大型集合(通常是文本等非结构化文档)中,找到满足用户信息需求的材料。这里提到的“通常”是典型情况,也存在其他形式的信息检索,例如音乐信息检索(处理声音而非文本文档)。但本课程主要讨论所有“通常”情况都成立的情形。

信息检索有许多应用场景。如今人们首先想到的几乎总是网络搜索,但也存在许多其他场景,例如:搜索你的电子邮件、搜索笔记本电脑中的内容、在公司知识库中查找资料,或进行法律信息检索(例如为法律背景查找相关案例)。

长期以来,人类知识和信息的很大一部分都以人类语言文档的形式存储,但这也一直存在一个悖论。下图(并非真实图表,仅为示意)展示了这种状况:在20世纪90年代中期,尽管公司、组织和家庭中非结构化形式(即人类语言文本)的数据量已经远远超过结构化形式(如关系数据库和电子表格)的数据量,但当时结构化数据的管理和检索已是一个成熟的领域,并存在大型数据库公司。相比之下,非结构化数据管理领域则非常薄弱,只有少数小型公司从事企业文档检索等业务。

这种情况在千禧年前后发生了彻底改变。如今,数据量在两方面都变得更大,尤其是在非结构化方面——博客、推文、论坛等平台产生了海量信息。同时,在企业层面也发生了转变,现在已有大型公司(如主要的网络搜索巨头)在解决非结构化信息检索的问题。


信息检索的基本框架

上一节我们介绍了信息检索的定义和背景,本节中我们来看看其基本操作框架。

我们首先假设有一个文档集合,我们将在此集合上进行检索。目前我们假设这是一个静态集合,后续我们将讨论在类似网站搜索的场景中,当文档被添加或删除时,如何找到它们。

我们的目标是检索与用户信息需求相关、并能帮助用户完成任务的文档。

让我们通过下图更详细地分解这个过程:

  1. 用户任务:用户有一个想要执行的任务。例如,我想清除车库里的老鼠,并且我不想用毒药杀死它们。这就是我的用户任务。
  2. 信息需求:为了完成这个任务,我感觉需要更多信息。这就是信息需求:我想了解如何在不杀死老鼠的情况下清除它们。信息检索系统的评估正是基于这个信息需求。
  3. 查询:我们无法直接将某人的信息需求输入计算机。我们必须将信息需求转化为可以输入搜索框的内容,这就是查询。例如,我尝试的查询可能是:how trap mice alive。这代表了我将信息需求具体化为一个特定查询的尝试。
  4. 检索与结果:然后,这个查询被输入搜索引擎,搜索引擎查询我们的文档集合并返回一些结果。
  5. 查询优化:有时,如果我对检索结果不满意,我可能会根据返回的证据,返回并构思一个更好的查询。例如,我可能认为“alive”这个词不好,换成“without killing”试试效果是否会更好。

在这个过程中,可能会出现一些问题。这里有几个解释阶段:

  • 首先是我的初始任务,我决定了我的信息需求是什么。我可能在这里就产生了误解。
  • 我们更关注的是信息需求和查询之间的转化。查询表述不当可能以多种方式出错:我可能选择了错误的词语来表达查询;我可能使用了查询搜索运算符(如引号),这可能对查询的实际效果产生或好或坏的影响。这些查询表述中的选择,并没有改变我的信息需求本身。

初步评估:精确率与召回率

在讨论了信息检索的基本流程后,了解如何评估其效果至关重要。当我们向信息检索系统提交查询时,会得到一些文档返回,我们想知道这些结果是否良好。

以下是评估结果质量的两个基本互补指标:

  1. 精确率:精确率衡量系统返回的文档中有多少与用户的信息需求相关。它评估返回结果中“坏结果”的多少。其公式为:
    精确率 = 相关且被检索出的文档数 / 系统检索出的文档总数

  2. 召回率:召回率衡量系统成功找到了文档集合中多少“好信息”。它评估系统找全相关文档的能力。其公式为:
    召回率 = 相关且被检索出的文档数 / 文档集合中所有相关文档的总数

我们将在后续课程中给出更精确的定义和测量方法。现在需要明确的一点是,要使这些指标有意义,必须根据用户的信息需求进行评估。

对于某些类型的查询(如下一节我们将看到的),返回的文档是由提交的查询确定的。如果我们仅根据用户查询来评估返回结果,那么精确率必然是100%。但我们并非如此操作,我们是根据用户的信息需求来评估结果的精确率。特别是,如果用户查询表述不当或构思不佳,这将被视为降低了返回结果的精确率。


总结

本节课中,我们一起学习了信息检索的初步知识。我们了解了信息检索的定义及其从结构化数据到非结构化数据主导的演变历程。我们探讨了信息检索的基本框架,包括从用户任务到信息需求,再到查询表述和结果返回的完整流程。最后,我们初步认识了评估检索系统性能的两个核心指标:精确率和召回率,并强调了评估必须基于用户的真实信息需求而非字面查询。这只是信息检索学习的开始,希望你已经对如何思考这一任务以及如何初步判断搜索引擎的优劣有了基本概念。

📚 课程 P34:L6.2 - 词项-文档矩阵构建

在本节课中,我们将学习信息检索中的一个核心概念——词项-文档矩阵。我们将了解它的基本思想、如何用它来回答布尔查询,以及为什么在实际的大型系统中,我们通常不会直接存储这种完整的矩阵形式。


🧠 词项-文档矩阵的概念

上一节我们提到了信息检索的基本任务。本节中,我们来看看一个重要的概念工具:词项-文档矩阵。

我们以在莎士比亚作品中进行信息检索为例。假设我们有一个具体问题:哪些莎士比亚的剧本包含单词“Brutus”和“Caesar”,但不包含“Calpurnia”?

如果从最基本的文本搜索命令开始,你可能会想到的第一个解决方案是穷举地搜索文档的文本内容,这在 Unix 世界中被称为 grep

我们可以先 grep 查找包含“Brutus”和“Caesar”的剧本。如果你熟悉 grep 命令,可以添加一个标志来排除匹配项,从而得到不包含“Calpurnia”的结果。

对于莎士比亚作品这种规模的数据集,使用 grep 来回答这类查询是完全可行的。我们的磁盘和计算机速度足够快,使用这种方法可以瞬间找到答案。

然而,这并非解决完整信息检索问题的好方法。它在多个方面存在不足:

  • 一旦语料库变得庞大(例如你硬盘上的所有内容,或者更极端的,整个万维网),我们无法承受每次查询都对所有文档进行线性扫描。
  • 像“非”这样的逻辑操作,实现起来比单纯查找更复杂。
  • 我们将面临更复杂的查询,例如查找“Romans”出现在“countrymen”附近的用法,这是 grep 命令无法直接完成的。
  • 更重要的是,信息检索领域发生的一大变革是排序的概念,即为查询找到最相关的文档返回。这是线性扫描匹配模型无法提供的。

我们将在后续课程中讨论现代信息检索系统如何处理所有这些问题。


📊 构建词项-文档矩阵

现在,让我们进入词项-文档矩阵的概念。

在词项-文档矩阵中,矩阵的代表单词(在信息检索中也常被称为词项),矩阵的代表文档

我们在这里做一件非常简单的事情:根据单词是否出现在剧本中,来填充这个布尔矩阵中的每一个单元格。

例如,“Antony”出现在《安东尼与克莉奥佩特拉》中,但“Calpurnia”没有。

这个矩阵表示了单词在文档中的出现情况。如果我们有了这个矩阵,就可以直接回答布尔查询,例如我们之前的例子:查找包含“Brutus”和“Caesar”但不包含“Calpurnia”的文档。

让我们具体看看如何操作。我们将取出查询中词项对应的向量,然后用布尔运算将它们组合起来。

以下是具体步骤:

  1. 首先,取出代表“Brutus”的行向量。
  2. 然后,取出代表“Caesar”的行向量。
  3. 最后,取出代表“Calpurnia”的行向量,并对其进行取反操作。

“Calpurnia”只出现在《尤利乌斯·凯撒》中。因此,取反后我们得到一个向量,其中除了《尤利乌斯·凯撒》的位置是0,其他位置都是1。

此时,我们将这三个向量相加。我们的答案是向量 [1, 0, 0, 1, 0, 0]

这样,我们就成功地完成了信息检索,并可以判断出这个查询由《安东尼与克莉奥佩特拉》和《哈姆雷特》这两份文档满足。我们确实可以回到文档集去确认这一点。

这似乎表明,我们可以简单地通过操作词项-文档矩阵来进行信息检索。


⚠️ 实际应用中的挑战

然而,重要的是要认识到,一旦我们转向合理规模的集合,这种方法就不再真正可行。

让我们花一分钟来探讨一个规模合理但仍然较小的集合。假设我们有 100万份文档(我们常用 N 表示文档数量),每份文档平均有 1000个单词

那么,我们的文档集合有多大?我们的矩阵又有多大?

如果我们假设每个单词(包括空格和标点)平均占6字节,那么这里的数据量大约是 6 GB。这只是你笔记本电脑上一块现代硬盘的极小一部分。

但让我们接着计算文档集合中不同词项的数量。我们需要知道这个数字,因为它对应着矩阵的行数。假设大约有 50万 个不同词项,这对于100万份文档来说是典型的数量。我们常用 M 来表示这个不同词项的数量。

这意味着什么?这意味着,即使是这种规模的文档集合,我们也无法构建这个完整的词项-文档矩阵。因为它将有 50万行100万列。这就是 5000亿个 0和1,这个矩阵已经非常庞大,可能超出了我们的存储空间。

随着文档集合超过100万份,情况只会变得更糟。

但这里有一个非常重要的观察结果:尽管这个矩阵有5000亿个0和1的条目,但实际上,几乎所有的条目都是0。文档中最多只有10亿个“1”。

你可以停下来思考一下:为什么最多只有10亿个“1”?

答案是:如果我们有100万份文档,且平均每份文档有1000个单词,那么实际的词项实例总数只有 10亿。因此,即使我们假设每个文档中的每个单词都不同,我们也最多只能有10亿个“1”条目。而实际上,我们很可能远少于这个数字,因为像“the”、“of”、“to”这样的常见词会在每份文档中出现很多次。

因此,关键的观察结果是:我们处理的矩阵是非常、非常稀疏的。所以,信息检索数据结构设计的核心问题就是利用这种稀疏性,并提出更好的数据表示方法。

实现高效存储机制的秘诀在于:我们只想记录那些值为1的位置,而不是那些值为0的位置


🎯 课程总结

本节课中,我们一起学习了词项-文档矩阵。它是一个重要的概念性数据结构,在我们讨论各种算法时,会反复回到这个矩阵的视角来思考问题。

然而,当我们实际进行存储和构建计算机系统时,我们也清楚地看到,我们实际上永远不会以这种完整矩阵的形式来存储文档及其信息检索表示。利用矩阵的稀疏性,设计更紧凑、高效的数据结构,是构建实用信息检索系统的关键。

课程 P35:L6.3 - 倒排索引:信息检索背后的核心数据结构 📚

在本节课中,我们将要学习信息检索系统的核心数据结构——倒排索引。我们将了解它的构成、构建过程以及它在现代搜索系统中的重要性。

概述 📖

倒排索引是所有现代信息检索系统(从单机系统到大型商业搜索引擎)所依赖的关键数据结构。它利用了我们在上一节中讨论的“词项-文档矩阵”的稀疏性,能够实现非常高效的检索。在信息检索系统中,它基本上是无可替代的数据结构。

倒排索引的构成 🧱

上一节我们介绍了词项-文档矩阵,本节中我们来看看如何将其转化为更高效的倒排索引。

倒排索引的核心思想是:为每个词项存储一个包含该词项的所有文档的列表。每个文档由一个文档ID(即文档序列号)标识,例如第一个文档为1,第二个为2,依此类推。

以下是倒排索引的主要组成部分:

  • 词典:包含所有出现在文档中的词项。
  • 倒排记录表:对于词典中的每个词项,都有一个指向其“倒排记录表”的指针。该列表记录了包含该词项的所有文档ID。

一个“词项-文档”对的出现被称为一个记录项。所有倒排记录表的集合被称为记录项列表。倒排记录表的一个关键属性是它们按文档ID排序,这对于高效的检索操作至关重要。

这两个数据结构(词典和记录项列表)在规模和存储上有所不同。词典相对较小,但通常必须存储在内存中以实现快速访问。而记录项列表非常庞大,对于中小型企业搜索引擎,它们通常存储在磁盘上。

构建倒排索引 🛠️

现在,让我们深入了解如何从原始文档构建一个倒排索引。起点是一批待索引的文档,每个文档被视为一个字符序列。

构建过程首先需要一系列文本预处理步骤,然后才是核心的索引构建。

文本预处理

以下是文本预处理的主要步骤:

  • 分词:将文档字符序列切分成基本的索引单位——词项(单词)。这涉及处理标点、连字符、所有格等问题。
  • 归一化:将词项映射到规范形式,以确保匹配。例如,将“USA”和“U.S.A.”视为同一个词项,或者进行词干还原,使“author”和“authorship”映射到同一词干“author”。
  • 停用词移除:传统上,许多搜索引擎会省略“the”、“a”、“to”等最常见词汇。但在存储空间充裕的现代,这种做法已不那么必要,因为某些查询(如“to be or not to be”)需要这些词。

经过预处理后,我们得到的是经过修改的词项序列,它们将被输入给索引器。

索引器:核心构建步骤

索引器的任务是将归一化后的词项序列转换为倒排索引。我们通过一个简单的例子(两个文档)来说明。

以下是构建倒排记录表的核心步骤:

  1. 收集与排序:首先,我们收集所有文档中的所有词项及其对应的文档ID。然后,我们进行排序:主排序键是词项(字母顺序),次排序键是文档ID。这样,相同词项的所有出现会聚集在一起,并且按文档ID排序。
  2. 合并与构建:接下来,我们对排序后的列表进行合并。对于同一文档内的同一词项的多次出现,我们将其合并为一个条目(记录文档ID和词频)。然后,我们将同一词项在所有文档中的条目汇总,构建出该词项的倒排记录表。由于之前的排序,这个列表自然就是按文档ID排序的。

最终,我们得到词典(词项及其总频率)和对应的、已排序的倒排记录表。

存储考量与效率 💾

在考虑倒排索引的大小时,我们需要分析存储开销:

  • 词典:存储词项列表及其频率。词项数量相对适中(例如50万个)。
  • 指针:存储指向每个词项倒排记录表的指针,数量与词项数相同。
  • 倒排记录表:这是索引中最大的部分。但其总条目数受限于整个文档集合中所有词项的出现总数。例如,100万个平均长度为1000词的文档,记录项总数将少于10亿条,存储是可控的。

在实际构建高效的信息检索系统时,我们会进一步考虑如何通过压缩等技术来优化索引的存储和检索速度。虽然本节课不深入细节,但希望你能理解,倒排索引为检索操作提供了一个高效的基础。

总结 ✨

本节课中我们一起学习了倒排索引。我们了解到,倒排索引通过为每个词项维护一个排序的文档ID列表,巧妙地利用了数据的稀疏性。我们探讨了它的构成(词典和倒排记录表),并逐步解析了从文档预处理到核心排序、合并的构建过程。尽管其实现涉及复杂的工程优化(如压缩),但其核心思想并不复杂,正是这个数据结构支撑着所有现代信息检索系统的高效运行。

在下一节中,我们将详细讨论如何利用这个数据结构来执行实际的检索操作。

课程 P36:L6.4 - 基于倒排索引的请求预处理 📚

在本节课中,我们将继续学习倒排索引,并了解它为何是信息检索系统中用于执行查询操作的高效数据结构。我们将详细讲解如何处理一种常见的查询类型,即对两个词项进行“与”查询。


查询处理细节

上一节我们介绍了倒排索引的基本结构。本节中,我们来看看如何使用它来处理查询。

假设我们要处理一个查询。首先从一个简单的例子开始,假设我们想查询词项 Brutus。处理方式非常直接。我们在词典中定位 Brutus,然后返回其对应的倒排记录表。这个记录表就是包含 Brutus 的所有文档集合,无需进行其他操作。

现在,让我们看一个稍微复杂一点的例子。假设查询是 Brutus AND Caesar。我们需要在词典中定位这两个词项,并查找它们各自的倒排记录表。我们的目标是找出同时包含 BrutusCaesar 的文档。将两个列表组合起来的过程,通常被称为 合并 这两个倒排记录表。

这里“合并”一词可能有些误导,因为对于“与”查询,我们实际上是在对两个文档集合求交集,以找到两个词项都出现的文档。而“合并”一词通常暗示进行某种并集操作。但在算法领域,“合并”算法家族指的是一类可以遍历一对有序列表并对其执行各种布尔运算的算法。


合并算法的具体步骤

接下来,我们具体看看如何通过合并操作来处理 Brutus AND Caesar 查询。

我们有两个已按文档ID排序的倒排记录表。算法开始时,我们有两个指针,分别指向两个列表的头部。

我们的目标是计算两个列表的交集。具体做法如下:我们比较两个指针当前所指的文档ID是否相等。如果不相等,则将指向较小文档ID的指针向前移动一位。如果相等,则将该文档ID加入结果集,并将两个指针都向前移动一位。重复此过程,直到其中一个列表被遍历完毕,此时即可停止并返回结果集。

以下是该过程的逐步演示:

  1. 初始指针位置不同,不相等。移动指向较小ID的指针。
  2. 再次比较,不相等。再次移动指向较小ID的指针。
  3. 此时指针指向的文档ID相等(例如文档2)。将其加入结果集,然后两个指针同时前进。
  4. 重复上述比较和移动步骤。
  5. 当再次发现相等的文档ID(例如文档8)时,将其加入结果集。
  6. 继续此过程,直到其中一个列表的指针到达末尾。此时,交集计算完成。

最终,我们得到的结果集是文档2和文档8,它们同时包含了 BrutusCaesar

如果两个列表的长度分别是 xy,那么这个合并算法的时间复杂度是 O(x + y),即与两个倒排记录表长度之和成线性关系。

使该操作保持线性时间复杂度的关键,在于倒排记录表是按文档ID排序的。正因为如此,我们可以对两个列表进行线性扫描。如果列表无序,该算法将退化为时间复杂度更高的算法。


倒排记录表求交算法

以下是倒排记录表求交算法的伪代码描述,它精确地对应了上述手动演示的步骤:

def intersect(p1, p2):
    answer = []  # 初始化结果集
    while p1 is not None and p2 is not None:  # 当两个列表均未遍历完时
        if docID(p1) == docID(p2):  # 如果文档ID相同
            add(answer, docID(p1))  # 加入结果集
            p1 = next(p1)  # 移动p1指针
            p2 = next(p2)  # 移动p2指针
        elif docID(p1) < docID(p2):  # 如果p1的ID较小
            p1 = next(p1)  # 移动p1指针
        else:  # 如果p2的ID较小
            p2 = next(p2)  # 移动p2指针
    return answer  # 返回结果集

算法从空的结果集开始。只要两个指针都不为空(即未到达列表末尾),就持续循环。在每一步中,判断两个指针所指的文档ID是否相同。如果相同,则将其加入答案并同时推进两个指针。如果不同,则推进指向较小文档ID的那个指针。一旦任一列表被遍历完,循环结束并返回结果集。


总结

本节课中,我们一起学习了如何利用倒排索引处理查询。我们首先回顾了处理单个词项查询的简单方法,然后重点讲解了如何使用合并算法对两个已排序的倒排记录表进行求交操作,以高效地完成“与”查询。我们详细分析了算法的步骤、时间复杂度(O(x+y)),并强调了倒排记录表有序存储对于保证算法效率的关键作用。理解这个基础算法,是构建更复杂信息检索功能的重要一步。

课程 P37:L6.5 - 二值检索模型及拓展 📚

在本节课中,我们将要学习二值(布尔)检索模型,并了解其在商业信息检索系统中的扩展应用。二值检索模型是信息检索领域的基础模型之一,它通过精确匹配布尔表达式来满足用户的信息需求。


二值检索模型简介 🔍

上一节我们介绍了信息检索的基本概念,本节中我们来看看二值检索模型的具体定义。

信息检索模型是指支撑信息检索操作的底层数学形式化框架。二值检索模型就是其中一个例子。在该模型中,底层的数学模型是传统的布尔表达式空间。用户可以进行查询,查询可以是单个词项,也可以是词项之间通过布尔运算符(如 AND、OR、NOT)组合而成的更复杂表达式。

在二值模型中,每个文档被视为一个词的集合。一个文档要么完全匹配布尔表达式,要么完全不匹配。正如我们在之前的词项-文档矩阵例子中看到的那样,这是构建信息检索系统最简单的模型。虽然现代世界通常不再主要使用这个模型,但它是一个很好的起点,因为现代排名检索所使用的相同数据结构可以叠加在这个基础之上。


二值模型的历史与现状 📜

二值检索模型不仅是历史性的,至今仍有许多应用场景。

二值检索曾是商业检索的主要工具,从上世纪60年代信息检索起步到90年代,持续了三十年。尽管学术界在70年代和80年代就开始倡导排名检索模型,但直到90年代网络兴起,它才真正渗透到商业信息检索领域。

目前仍有许多搜索系统基于二值检索模型。例如,你邮箱的搜索系统、学校图书馆的目录系统,或者像 Mac OS X 的 Spotlight 这样的桌面搜索系统,很可能仍然是二值检索系统。


扩展二值模型:以 Westlaw 为例 ⚖️

接下来,我们来看一个扩展二值检索模型的例子,这个例子来自 Westlaw 系统。

Westlaw 是付费用户规模最大的商业搜索服务,主要用于法律行业查找案例文档和相关法律。它始于1975年,1992年增加了排名功能,并在2010年推出了新的联邦搜索模型。它最初就是一个二值系统,有趣的是,至今仍有大量用户使用布尔查询。在法律行业,律师们喜欢通过指定自己的布尔查询来获得精确结果,并且法学院有教授 Westlaw 系统布尔查询语言的传统,因此用户对此非常熟悉。

以下是 Westlaw 查询的一个示例,它展示了法律搜索这类专业搜索场景的特点:查询通常更长、更精确。

信息需求:查找涉及《联邦侵权索赔法》的诉讼时效法规和案例。

Westlaw 查询

"statute of limitations" & "federal tort claims act" /s

Westlaw 的扩展布尔查询语言有一些特殊约定:

  • 两个单词之间仅用空格隔开,表示 OR 关系,而不是 AND
  • 除了基本的 ANDOR,它还拥有一系列关系运算符,用于指定词项在特定距离内共现。例如 /3 表示在三个词以内,/s 表示在同一句子内。
  • 使用感叹号 ! 指定词尾通配符。例如 limit! 会匹配 limitlimitedlimitations 等。

因此,这个查询精确地指定了所有信息需求的要素。

另一个信息需求示例:关于残疾人进入工作场所的要求。

对应的查询可能包含:通配符、/p(同一段落内)、/s(同一句子内)以及析取(OR)关系。

这让你对 Westlaw 典型的查询类型有了初步了解。许多专业搜索者仍然喜欢这种扩展布尔搜索,因为他们能够精确控制通配符的用法或词项之间的接近程度,而这些功能在大多数典型的网络搜索引擎查询语言中是无法由用户自行控制的。


复杂布尔查询的处理 🤔

现在让我们回到纯粹的二值检索模型,并思考如何处理其他类型的查询。

假设我们不想查询 Brutus AND Caesar,而是想查询 Brutus AND NOT CaesarBrutus OR NOT Caesar。一个值得思考的问题是:我们能否使用合并算法来处理这类查询?能否在时间复杂度与两个词项的倒排记录表长度之和成正比的情况下完成?如果不能,这些操作的时间复杂度是多少?

如果你思考了这些情况,那么可以考虑更一般的布尔表达式,例如 (A AND B) OR (C AND D)。在这些情况下,我们能否在线性时间内完成合并?如果可以,线性于什么?我们能否想象出比合并算法更好的方法来解决这些问题?这些都是值得你在课后深入思考的好问题。


查询优化策略 ⚡

如果你有一个包含多个词项的复杂查询(再次回想那些 Westlaw 查询),我们可以尝试找出满足该查询的最佳算法路径。

考虑一个由 n 个词项进行 AND 操作的查询:term1 AND term2 AND ... AND termn

我们需要获取每个词项的倒排记录表,然后对它们进行 AND 操作。为了最小化工作量,我们应该使用什么启发式策略?

一个相当直接的策略是:从文档频率最低(出现文档最少)的词项开始处理。对于一个纯 AND 查询,我们知道这个词项的倒排记录表是我们可能返回的最大结果集。然后,我们需要遍历其他词项的倒排记录表,过滤掉那些不在其列表中的文档ID。

例如,对于查询 Brutus AND Caesar AND Calpurnia,假设它们的倒排记录表如下:

  • Brutus -> [1, 4, 16, ...]
  • Caesar -> [1, 2, 4, 16, ...]
  • Calpurnia -> [13, 16, ...]

我们应该从最短的列表 Calpurnia(文档频率最低)开始。然后检查 Brutus 的列表,发现 13 不在其中,所以排除。接着检查 Caesar 的列表,确认 16 存在。最终返回结果 [16]

这个操作通过从最小的倒排记录表开始,显著提高了效率(包括时间和内存占用)。这也清楚地说明了为什么将每个词项的文档频率与词项一起存储在词典中是一个非常好的主意,因为这让我们可以快速进行内存查找,以确定最佳的查询优化策略。


更一般查询的优化 🧠

现在假设我们有一个更一般的查询,例如:(madding OR crowd) AND (ignoble OR strife)

我们当然可以轻松获取每个词项的文档频率,但优化这个查询需要更复杂一些。我们可以看到,对于每一对析取(OR)词项,我们将对它们的倒排记录表进行合并(OR 操作),产生的新列表可能比其中任何一个单独词项的列表都大。

我们可以通过取每个析取词项的文档频率之和,来估算这些子表达式结果的大小,这将给出结果倒排记录表可能长度的保守上界。然后,如果这些析取结果之间再进行 AND 操作,我们可以使用与之前相同的启发式策略:按估算出的结果大小递增顺序进行处理

这里有一个练习,你可以在听课时思考:

查询(t1 OR t2) AND (t3 OR t4) AND (t5 OR t6)

词典子集(词项及频率)

  • t1 -> 50
  • t2 -> 40
  • t3 -> 30
  • t4 -> 20
  • t5 -> 10
  • t6 -> 5

问题:我们应该首先处理哪两个词项对应的析取表达式?

思考这些合并操作的细节是非常有益的。以下是更多例子:

  • 如果查询是 friends AND romans AND NOT countrymen,我们如何在查询优化方法中使用 countrymen 的频率?
  • 对于更一般的任意布尔查询,我们能否保证执行时间与倒排记录表总大小成线性关系?你可能需要思考一下,特别是当一个查询词项在查询中只出现一次时,看看能否确定总合并时间的界限。

实践思考与总结 🎯

在更实际的层面,你也可以开始思考搜索界面的工作原理以及对用户有用的功能。

例如,可以尝试使用一个能搜索莎士比亚作品的小型搜索引擎。一个有用的练习是:进行一些搜索,看看你是否能想到一些如果这个搜索引擎能做得更好,将会对用户更有用的功能。


本节课中我们一起学习了二值检索模型,它是历史上最重要的检索模型,也是理解现代检索系统工作原理的重要基础。我们还探讨了其在专业领域(如 Westlaw)的扩展应用,以及查询优化的基本策略。理解这些基础概念,将有助于我们后续学习更复杂的排名检索模型。

课程 P38:L6.6 - 短语查询与位置索引 📚

在本节课中,我们将学习短语查询,这是实践中最重要的扩展布尔查询类型之一。我们还将探讨如何扩展倒排索引数据结构,以支持对短语查询的处理。


什么是短语查询?🔍

上一节我们介绍了基本的布尔查询。本节中我们来看看短语查询。

在实践中,我们不仅希望查询单个词语,还希望查询由多个词语组成的单元。许多有用的查询对象都是多词单元,例如“信息检索”或“斯坦福大学”。我们需要一种机制,能够将这些词语对作为一个整体进行匹配。

现代网络搜索引擎中标准的语法是使用双引号,例如 "Stanford University"。如果一个文档的内容是“我在斯坦福上大学”,它不会匹配这个短语查询。用户通常能轻松理解使用双引号表示短语查询的概念。

值得注意的是,除了这种显式的短语查询,还存在隐式的短语查询现象。例如,用户可能输入查询“San Francisco”,他们实际上希望将其解释为一个短语,但可能没有或不知道加上引号。现代搜索引擎的一个研究热点就是如何识别这些隐式短语,并利用这一信息来调整返回的文档。不过,我们暂时将这个问题放在一边,专注于处理显式短语查询,并研究如何让我们的信息检索系统高效地匹配它们。


双词索引的初步方案 🤔

为了处理短语查询,仅存储词语及其对应的文档列表显然是不够的。因为这样我们只能找到包含两个词语的文档,却无法知道这两个词语在文档中是否相邻。

一种解决方案是构建双词索引。以下是其工作原理:

  • 我们将文本中每两个连续的词语作为一个短语单元进行索引。
  • 例如,对于文本“friends Romans countrymen”,我们会生成双词单元“friends Romans”和“Romans countrymen”。
  • 每个双词单元都成为词典中的一个词项,并拥有自己的倒排记录表。

这意味着,如果我们想查询短语“friends Romans”,只需在词典中查找这个双词单元,然后获取其对应的文档列表即可。

对于更长的短语,我们可以将其分解。例如,查询“Stanford University Palo Alto”可以分解为“Stanford University” AND “University Palo” AND “Palo Alto”三个双词单元的查询。通过双词索引找到同时包含这三个双词单元的文档,虽然不能完全保证文档中一定包含连续的完整短语(可能存在误报),但可能性很高。

然而,双词索引存在一个主要问题:词典规模会急剧膨胀,因为可能的双词组合数量是词语数量的平方级。因此,虽然双词索引的概念有用,但它通常不是处理短语查询的标准解决方案。


标准解决方案:位置索引 🎯

处理短语查询的标准方案是使用位置索引。位置索引的核心思想是,在倒排记录表中不仅记录词语出现在哪些文档,还记录它在每个文档中出现的位置。

其数据结构组织如下:

  • 在词典中,存储词项及其文档频率。
  • 在倒排记录表中,对于每个文档ID,不再是一个简单的列表,而是附加一个位置列表,记录该词项在该文档中出现的所有词位置。

例如,词项“be”的倒排记录表可能如下所示(用代码结构表示):

"be": {
    "doc_freq": 999999,
    "postings": [
        {"doc_id": 1, "positions": [7, 18, 33, ...]},
        {"doc_id": 2, "positions": [1, 3]},
        {"doc_id": 4, "positions": [17, 190, 191, 429, 430, ...]},
        ...
    ]
}

有了位置索引,我们就可以通过检查词语的位置是否相邻,来精确判断短语是否出现。


处理短语查询的合并算法 ⚙️

现在,我们来看看如何使用位置索引处理短语查询。其算法是之前文档ID合并算法的扩展,但更复杂一些,因为我们需要同时检查文档ID的匹配和词位置的匹配。

假设我们的短语查询是 "to be or not to be"。处理步骤如下:

  1. 获取倒排记录表:获取短语中每个独立词语(“to”, “be”, “or”, “not”)的倒排记录表(包含位置信息)。
  2. 渐进式合并
    • 首先,像处理普通AND查询一样,对文档ID列表进行合并,找出包含所有词语的候选文档。
    • 然后,对于每个候选文档,我们需要深入到位置列表层面进行另一轮合并检查。例如,要匹配“to be”,我们需要在文档中找到位置 ii+1,使得“to”出现在位置 i,“be”出现在位置 i+1
  3. 算法细节:这本质上是一个两层循环的合并过程。外层循环遍历文档ID,内层循环遍历并检查词语在文档内的位置是否符合短语顺序(例如,对于K个词的短语,位置需要连续递增)。算法需要高效地处理位置列表的遍历和匹配条件。

这种基于位置索引的方法不仅适用于精确的短语查询(词语偏移为1),也适用于我们之前看到的邻近查询,例如“wordA within 3 words of wordB”。只需将匹配条件从“位置相差1”修改为“位置差在K以内”即可。


位置索引的代价与权衡 ⚖️

位置索引功能强大,但有其代价:倒排记录表的体积会显著增大。因为我们需要为词语的每一次出现都存储一个位置,而不仅仅是存储它出现在哪个文档。

索引的大小取决于文档的平均长度。对于短文档,影响不大;对于长文档,索引体积的膨胀会更明显。通常,对于典型的网页文本,位置索引的体积可能是非位置索引的2到4倍,并且可能达到原始文本体积的1/3到1/2。

尽管如此,由于其在处理短语和邻近查询方面的强大能力和灵活性,使用位置索引已成为完全标准的做法。这种能力无论是用于显式的短语查询,还是用于提升系统在识别隐式短语时的排名效果,都至关重要。


混合索引策略:结合双词索引 🚀

虽然双词索引本身不是标准方案,但其思想并非无用。我们可以将两种方法结合,形成一种高效的混合策略。

观察发现,在高流量查询服务(如网络搜索引擎)中,某些短语查询会反复出现,例如名人姓名“Michael Jackson”或乐队名“The Who”。如果每次都使用位置索引进行复杂的合并操作,效率较低。

一种更复杂的混合索引系统应运而生:

  • 对于高频出现的短语(如常见人名、热门短语),系统会预先计算并索引其双词单元。
  • 对于不常见的短语,则使用通用的位置索引进行处理。

研究表明,在典型的网络查询混合负载下,这种混合索引策略的执行时间可以缩短至仅使用位置索引时的四分之一,而空间开销仅比纯位置索引增加约26%。这是一个非常有吸引力的权衡。

在实践中,现代系统可能会更动态地实现这一点,例如缓存常见短语查询的结果,这相当于动态地将这些高频双词单元“添加”到了索引中。


总结 📝

本节课中,我们一起学习了经典布尔检索模型最有用的扩展之一:对短语查询的支持。

我们重点介绍了两种方法:

  1. 双词索引:通过索引相邻词对来快速处理短语,但会带来词典规模的显著膨胀。
  2. 位置索引:在倒排记录表中存储词语位置信息,通过扩展的合并算法精确处理短语查询和邻近查询。这是当前的标准方案,虽然增加了存储开销,但提供了强大的查询能力。

最后,我们还探讨了将两者结合的混合索引策略,通过为高频短语建立专门索引,可以在可接受的额外空间成本下,大幅提升查询效率。

课程 P39:L7.1 - 检索排序介绍 🚀

在本节课中,我们将要学习检索排序的基本概念,了解它与布尔检索的区别,并探讨为什么排序检索模型更适合大多数用户的需求。


概述

到目前为止,我们讨论的查询都是布尔查询。例如,输入查询 Ocean AND liner,搜索引擎会精确返回包含“ocean”和“liner”这两个词的文档,文档要么匹配,要么不匹配。

这种模式对于需求明确、对文档集有深刻理解的专家用户是有效的。当信息检索系统作为一个大型应用的组件,并且系统能处理成千上万的结果、按需调整查询时,它也是有效的。

然而,对于绝大多数用户来说,这种系统实际上并不好用。大多数用户无法写出好的布尔查询,或者即使能写,他们也认为这太费功夫。特别是,布尔系统常常产生成千上万的结果,而用户并不想费力浏览这么多结果,这在网络搜索应用中尤其明显。


布尔检索的问题:盛宴或饥荒 🍽️

布尔检索存在一个普遍问题,即“盛宴或饥荒”问题。

布尔查询通常导致两种极端结果:要么返回的结果太少(一两个甚至为零),因为文档无法精确满足搜索请求;要么返回的结果太多,数量级达到数千甚至更多。

以下是该问题的具体表现:

  • 结果过多:例如,查询 standard user dlink 650 可能返回 200,000 个结果。
  • 结果为零:尝试让查询更具体,如 standard user dlink 650 no card found,则可能得到零个结果。
  • 技能要求高:要写出能产生可控数量匹配结果的查询,需要很高的技巧。

基本问题在于:如果在词之间使用 AND,得到的结果太少;如果使用 OR,得到的结果又太多。


排序检索模型:解决方案 💡

上一节我们介绍了布尔检索的局限性,本节中我们来看看排序检索模型如何解决这些问题。

排序检索模型的核心思想是:系统不再返回一个满足查询的文档集合,而是根据查询,对文档集中的文档进行排序,并返回一个按相关性排序的顶部文档列表。

伴随这一思想而来的是自由文本查询的采用,它取代了像布尔检索模型那样带有 AND、OR、NOT 的显式查询语言。

用户的查询现在只是人类语言中的一些词。理论上,排序检索和自由文本查询是两个可以分开操作的选择,但在实践中,排序检索模型通常与自由文本查询相关联,而布尔检索模型则相反。


排序检索的优势

在排序检索中,“盛宴或饥荒”问题不复存在。当系统产生大量结果集时,用户并不会真正注意到。

结果集的大小基本上不再是个问题,因为系统通常一开始只向用户展示最顶部的少数几个结果,从而不会让用户不知所措。用户甚至可能不知道或没注意到结果的总数。

这一切都依赖于一个运行良好的排序算法,以确保顶部的结果是优质结果。


排序的基础:评分系统 ⚖️

排序检索的基础是拥有一个良好的评分系统。我们需要能够返回对搜索者最可能有用的文档,这就引出了一个问题:我们如何根据查询对文档进行排序?

我们将要探讨的方法是:为每个文档分配一个分数(例如,一个介于 0 和 1 之间的数字)。这个分数衡量了文档与查询的匹配程度。

因此,我们需要一种为“查询-文档”对分配分数的方法。让我们从一个单术语查询开始。

  • 如果查询词没有出现在文档中,该文档的分数应为 0
  • 除此之外,我们可能想说:查询词在文档中出现的频率越高,分数就应该越高。
  • 在此之后,如何精确地为文档评分就不那么明确了。因此,在接下来的课程中,我们将探讨几种不同的评分方案。

总结

本节课中,我们一起学习了排序检索模型的基本概念。我们了解了它与布尔检索模型的区别,认识到排序检索通过返回一个按相关性排序的文档列表,并结合自由文本查询,能更好地解决“盛宴或饥荒”问题,从而更适合大多数普通用户的使用习惯。排序检索的核心在于建立一个有效的评分系统,我们将在后续课程中深入探讨具体的评分方法。

📚 课程 P4:L1.4 - 分词与预处理

在本节课中,我们将学习文本归一化,这是一个将文本转换为标准单词或句子格式的过程。我们将从分词开始,即把文本切分成代表单个单词或词部分的词元。这是所有自然语言处理任务的基础步骤。


🧠 文本归一化概述

每个自然语言处理任务都需要文本归一化。我们通常认为归一化至少涉及三个过程。

  • 首先是分词或切分出单词。
  • 一旦我们完成了分词,就需要将它们规范化为一种标准格式。
  • 此外,我们还需要切分更大的语块,例如句子,有时甚至是段落。


🔤 基于空格的分词

分词最简单的方法是使用字符之间的空格。这对于拥有空格字符的语言(例如使用拉丁文字系统、阿拉伯文、西里尔文或希腊文的语言)非常有效。在这种方法中,单词被定义为空格之间的内容。

接下来,我们介绍一些简单的 Unix 工具,用于文本处理,并从 tr 命令开始,它对基于空格的分词很有用。我们的目标是获取一个文本文件,并输出词元及其频率。

以下是用于文本处理的一些标准 Unix 工具。这里有一个莎士比亚全集语料库。让我们从提取语料库中的所有单词开始。

我们将使用 tr 程序来实现。tr 程序接收字符,并将该字符的每个实例映射为另一个字符。我们指定 tr -c,这意味着“补集”。即,将所有不属于指定集合的字符转换为另一个字符。

在本例中,是将所有非字母字符转换为换行符。这样,我们将莎士比亚作品中的所有句号、逗号和空格替换为换行,从而创建每行一个单词的格式。

tr -c 'A-Za-z' '\n'

现在,我们已经将这首十四行诗转换为每行一个单词。接下来,我们将对这些单词进行排序。

让我们查看唯一的词型。为此,我们将使用 sortuniq 程序。uniq 程序将接收排序后的文件,并告诉我们每个唯一词型出现的次数。

sort | uniq -c

这样,我们就得到了莎士比亚作品中所有单词及其左侧的计数。这是 uniq 程序的输出结果。我们知道,在莎士比亚作品中,大写的 “Achievement” 出现了一次,“Achilles” 出现了 79 次,“acquaint” 出现了六次,依此类推。

如果不仅能按字母顺序查看这些单词,还能按频率顺序查看,那就更好了。让我们获取相同的单词列表,现在按频率重新排序。

sort -nr

现在,我们得到了莎士比亚作品中最常见的单词:“the”,其次是 “I”,然后是 “and”,并且我们拥有莎士比亚作品中的实际计数。这样,我们就得到了按频率排序的莎士比亚词汇表。

这里存在一些问题。一个是单词 “and” 出现了两次,因为我们没有将大写单词映射为小写单词。因此,让我们先修复大小写映射问题。

让我们再次尝试。我们将把莎士比亚作品中的所有大写字母映射为小写字母,然后通过管道传递给另一个 tr 程序实例,该实例将所有非字母字符替换为换行符。接着,我们将像之前一样进行排序:使用 uniq -c 查找所有独立的词型,然后再次排序,-n 表示按数字排序,-r 表示从最高值开始。然后,我们将查看结果。

tr 'A-Z' 'a-z' | tr -c 'a-z' '\n' | sort | uniq -c | sort -nr

现在,我们已经解决了 “and” 的问题,因此现在只有小写的 “and”,而不会出现大写的 “and”。但是,我们遇到了另一个问题。我们这里有一个 “d”。为什么 “d” 或 “s” 在莎士比亚作品中如此频繁?


⚠️ 分词中的复杂情况

当然,在大多数实际情况中,分词并不像使用简单的 Unix 工具所建议的那样简单。

一个问题是你不能盲目地删除标点符号,因为有些单词的标点符号是单词的一部分,例如 Ph.D.A&T。存在许多这类标点符号与分词交互的情况。

  • 价格可能包含美元符号、句点或欧元符号。
  • 日期可能包含斜杠或破折号。
  • 当然,网址、话题标签和电子邮件地址都包含标点符号,必须以特殊方式处理。

另一个问题是附着语。附着语是一个不能独立存在的单词。例如,英语单词 “we’re” 中的 “’re” 是缩略形式并附着在 “we” 上;或者在法语中,单词 “l’homme” 中的 “l’” 倾向于附着在相邻的单词上。对于这些附着语,我们必须决定是否要将它们作为单独的单词分离出来。

关于什么算作一个单词的问题也适用于多词表达式。例如,“New York” 应该算作一个单词还是两个单词?“rock and roll” 是一个单词还是三个单词?

因此,大多数针对英语或具有类似书写系统的语言的标准分词程序都会处理每个这些问题。例如,自然语言工具包中有一个简单的 Python 分词器,它使用小型正则表达式来处理连字符、缩写、货币符号等。


🌏 无空格语言的分词

但是,对于那些单词之间没有空格的语言呢?许多语言,如中文、日文、泰文等,不使用空格来分隔单词。在这些语言中,我们如何决定词元边界应该在哪里?

让我们以中文分词为例。中文单词由称为“汉字”的字符组成,每个字符代表一个称为语素的意义单位(我们将在后面讨论语素)。每个单词平均由大约两个半字符组成,但在中文中,决定什么算作一个单词是复杂的,并且没有一致的意见。

想象以下中文句子:“姚明进入东决赛”,意思是“Yao Ming reaches the finals”。

  • 这是三个词吗?[姚明] [进入] [东决赛]
  • 也许是五个词?[姚] [明] [进入] [东] [决赛](也许我们将姚明的姓和名分开,也许“决赛”真的包含两部分:“总”和“决赛”的其余部分?)
  • 或者我们可以完全将其切分为字符。[姚] [明] [进] [入] [东] [决] [赛](现在,“进入”这个词的两个部分,它们本身都是动词,变成了独立的单词。所以,一切都变成了一个字符。)

事实上,最后一种解决方案非常常见。在中文中,将字符视为词元是非常普遍的。这样,分词就变得非常简单。

但在其他语言,如泰文和日文中,则需要更复杂的词语切分。在这里,标准的算法是通过监督机器学习训练的神经序列模型,我们将在课程后面讨论这些内容。


📝 课程总结

在本节课中,我们一起学习了分词作为文本归一化的重要步骤。我们介绍了两种基线方法:基于空格的分词基于字符的分词。在未来的课程中,我们将转向更先进的方法。

课程P40:L7.2 - 基于Jaccard相关系数的打分 📊

在本节课中,我们将要学习一个简单的排序检索模型示例:基于Jaccard相关系数进行文档打分。我们将了解其定义、计算方法,并探讨其作为检索模型的优缺点。


概述

作为排序检索模型的一个简单入门示例,我们来考虑使用Jaccard系数进行打分。

Jaccard系数是衡量两个集合A和B之间重叠程度的常用指标。


Jaccard系数的定义与计算

Jaccard系数的计算方法是:取两个集合交集的大小,除以它们并集的大小。

用公式表示如下:
Jaccard(A, B) = |A ∩ B| / |A ∪ B|

如果计算一个集合与其自身的Jaccard系数,交集和并集的大小相同,因此比值为1,Jaccard系数为1。

如果两个集合不相交,没有共同成员,那么Jaccard系数的分子为零,因此Jaccard系数为零。

两个集合的大小不必相同。可以理解,Jaccard系数始终会给出一个介于0和1之间的数值,因为交集的大小最大只能等于并集的大小。


应用于文档检索

假设我们决定将查询与文档的匹配分数,定义为两者所包含词语集合的Jaccard系数。

具体思路是:假设我们的查询是“Ides of March”,包含三个词。我们有两个文档。我们可以这样计算:查询中有三个不同的词。

对于文档1,“Caesar”未出现,“die”未出现,“in”未出现,“March”出现了。因此,交集的大小仅为1个词,总词数为6。

对于文档2,“that”未在查询中出现,“long”未在查询中出现,但“March”再次在查询中出现。因此,文档2的Jaccard系数将是1除以总词数,这次是5。

于是,这个文档将以更高的Jaccard得分胜出。当然,这个差异可能看起来并不显著。本质上,这个文档胜出仅仅是因为它更短。

如果我们设想另一个例子,也许第二个文档中包含了单词“of”,那么文档2的Jaccard系数将变成两个重叠词除以六。这可能让你觉得更合理,因为获得了更多的重叠,所以Jaccard分数更高。但“在其他条件相同的情况下,更短的文档应被优先考虑”这个想法,是我们在信息检索模型中会再次看到的常见理念。


Jaccard打分的局限性

那么,Jaccard打分作为通用的检索模型是一个好主意吗?通常认为它不是。它存在几个问题。

首先,它不考虑词频,它只使用文档中的词语集合,忽略了词语在文档中出现的次数。但这通常不是我们想要的全部信息。我们稍后将看到处理词频的模型。

其次,还有一个更细致的点:在评估查询时,集合中的稀有术语比频繁术语更具信息量。这也是我们希望纳入模型的因素。

Jaccard系数效果不佳的另一个方面在于,它通过除以并集进行归一化的方式不一定完全正确。具体来说,在本课程后面的部分,我们将引入使用余弦相似度的概念。

我们将为更一般的情况推导其数学原理。但如果你在了解之后,想回过头来计算一下,当仅使用1/0词袋模型时,余弦相似度得分是多少,你会发现它类似于Jaccard系数,只是在分母上略有不同:我们现在取并集大小的平方根。事实证明,这是一种更好的长度归一化形式。


总结

本节课中,我们一起学习了Jaccard系数作为排序检索模型的一个非常简单的示例。希望这也是一种展示优秀检索模型需要处理的一些问题的方式:如何考虑词频、词语的稀有度,以及如何对不同长度的文档进行分数归一化。

课程 P41:L7.3 - 词频权重 📊

在本节课中,我们将要学习信息检索系统中的一个核心概念:词频权重。我们将了解如何从简单的词项-文档出现矩阵,发展到词袋模型,并最终利用词频信息来计算文档的检索得分。

上一节我们介绍了词项-文档出现矩阵,它是一种基础的文档表示方法。本节中我们来看看如何在此基础上引入词频信息。

最初,我们使用词项-文档出现矩阵,矩阵中的每个单元格记录一个值:如果词项在文档中出现则为1,否则为0。这样,每个文档就被表示为一个二进制向量,其维度等于词汇表的大小。

但我们不必局限于这种二进制向量表示。一个显而易见的替代方案是使用计数向量。现在,每个文档仍然是一个向量,但我们不再填入1或0,而是填入该词项在文档中出现的次数。

因此,我们得到一个维度为词汇表大小的向量,但现在是自然数向量空间中的一个向量。在之前的布尔检索模型中,我们只关注文档中出现的词项集合,并进行“与”、“或”等集合运算。现在,通过这个计数模型,我们过渡到了常用的词袋模型。

在词袋模型中,我们不考虑文档中词项的顺序,但会考虑一个词项在文档中出现的次数。词袋是集合的一种扩展,它额外记录了词项被使用的频率。

词袋模型存在一些巨大的局限性。例如,“John is quicker than Mary”和“Mary is quicker than John”这两个句子具有完全相同的向量表示,模型无法区分它们。这显然限制了其表达能力。

在某种意义上,这是一种倒退。早些时候,当我们引入位置索引时,它们能够通过邻近度或短语查询来区分这类文档。我们后续会希望恢复位置信息,但眼下,我们将重点发展词袋模型及其在向量空间检索模型中的应用。

我们有了“词项在文档中的词频”这个量,它就是一个词项出现的次数。那么问题来了:我们如何在检索得分中使用它呢?

稍加思考,你可能会被说服:原始的词频或许并非我们真正想要的。利用词频的基本思想是:如果我搜索“squirrels”,那么我应该更倾向于一个提到“squirrel”三次的文档,而不是只提到一次的文档。

但另一方面,如果我找到一个提到“squirrel”三十次的文档,我不确定是否应该认为它比只提到一次的文档好三十倍。因此,建议是:相关性随着提及次数的增加而增加,但不是线性增长。我们需要找到一种方法来缩放词频,使其与频率相关,但增长低于线性。

在继续概述这种度量方法之前,让我强调最后一点:我们在这里谈论“词频”。实际上,“频率”这个词有两种用法:一是指某事发生的速率(如盗窃案的频率),另一个是信息检索中一直使用的含义——当我们谈论信息检索中的“频率”时,它仅指计数,即一个词在文档中出现的次数。

以下是处理词频的标准做法。我们取词频的对数。现在,如果词频为0(即词项未在文档中出现),那么0的对数是负无穷,这有点问题。

标准的解决方法是采用一个两段式构造:如果词项确实出现在文档中,我们给词频加1。因此,如果它出现一次,这个值将变为1(因为log(1)=0),然后我们再加1,最终返回的值为0。如果词项未出现,则直接返回0。

这意味着,如果我们使用以10为底的对数(如上所示),你可以看到我们是如何实现低于线性增长的:如果一个词在文档中出现两次,其权重为1.3(略高于1);出现10次,权重为2;出现1000次,权重为4,依此类推。

为了给一个文档-查询对打分,我们只需对查询和文档中共有的每个词项的这些权重值进行求和。因此,只需取查询和文档中共同出现的词项交集即可,因为其他词项对得分没有贡献。然后,对交集内的每个词项,我们计算这个量并求和。

特别要注意,如果查询中的词项没有一个出现在文档中,得分确实仍为0。

好的,这就是词频权重的概念,以及它如何用于为特定查询下的文档给出一个得分,该得分可用于对返回的文档进行排序。

本节课中,我们一起学习了词频权重。我们从基础的二进制表示出发,引入了词袋模型和词频计数。我们讨论了原始词频的局限性,并介绍了通过取对数(log(tf+1))来获得低于线性增长的权重值的标准方法。最后,我们了解了如何将这些权重求和,以得到文档对于查询的检索得分,从而实现文档排序。

课程 P42:L7.4 - 逆文本频率权重 🧮

在本节课中,我们将要学习一种用于评估文档与查询匹配程度的重要评分方法——逆文本频率权重。我们将了解其核心思想、计算方法,以及它为何比简单的词频统计更有效。


概述

上一节我们介绍了文档频率的概念。本节中,我们来看看如何利用文档频率的倒数来为查询中的词语分配权重,这种方法被称为逆文本频率权重。其核心思想是:在文档集合中出现频率较低的词语,通常比常见词语包含更多信息。

核心思想:稀有词 vs. 常见词

利用文档频率背后的理念是,稀有术语比常见术语更具信息量

例如,我们之前讨论过停用词,如“the”、“and”、“to”。这些词语非常普遍,语义信息量少,因此在信息检索系统中,它们对判断文档与查询的匹配度贡献很小。相反,如果一个查询词在文档集合中非常罕见,例如“arachnocentric”(蜘蛛中心论),那么包含该词的文档很可能就是用户想找的。因此,我们希望为这类稀有术语赋予更高的匹配权重。

另一方面,常见术语的信息量较低。例如,“high”、“increased”、“line”这类词可能出现在许多文档中。虽然包含这些词的文档比不包含的文档更可能与查询相关,但这并非一个非常确定的关联指标。因此,对于常见术语,我们仍会给予正向权重,但权重要低于稀有术语。

逆文档频率的计算

我们将通过使用文档频率这一概念来实现上述目标。

文档频率 是指包含某个术语的文档数量。它衡量的是术语在整个集合中的分布广度,而非出现总次数。文档频率是术语信息量的一个反向指标。

以下是逆文档频率的计算公式:

IDF(t) = log₁₀(N / df(t))

其中:

  • N 是文档集合中的文档总数。
  • df(t) 是术语 t 的文档频率(即包含 t 的文档数量)。
  • log 用于抑制 IDF 值的绝对大小,避免其对最终评分产生过强的影响。虽然这里以10为底,但实际使用中对数底数的选择并不关键。

这个公式的结果值域在 0 到 log₁₀(N) 之间。如果一个词出现在所有文档中,其 IDF 值为 0,这意味着它对文档排序没有影响,这符合逻辑,因为它不具备区分文档的能力。

计算示例

假设我们的文档集合包含 1,000,000 个文档。

以下是不同文档频率的术语其 IDF 值计算示例:

  • 极罕见词(如“Caluria”,仅出现在1个文档中):
    IDF = log₁₀(1,000,000 / 1) = 6
  • 较罕见词(出现在100个文档中):
    IDF = log₁₀(1,000,000 / 100) = 4
  • 常见词(出现在10,000个文档中):
    IDF = log₁₀(1,000,000 / 10,000) = 2
  • 极常见词(出现在所有文档中):
    IDF = log₁₀(1,000,000 / 1,000,000) = 0

由此可见,逆文档频率权重会为稀有词赋予较大的乘数因子,从而在排序时更关注这些词。

需要注意的是,IDF 值是针对集合中每个术语预先计算好的一个静态值,不随具体查询而改变。

IDF 对排序的影响

现在有一个问题:IDF 对单术语查询的排序有影响吗?

答案是否定的。对于单术语查询,IDF 只是一个应用于所有文档的相同缩放因子,因此不会改变文档之间的相对排名。

IDF 只在多术语查询中发挥作用。例如,对于查询“capricious person”(善变的人),“capricious”是一个比“person”罕见得多的词。IDF 机制会在排序检索结果时,更加重视包含“capricious”的文档,而不是仅包含“person”的文档。

为何使用文档频率而非总词频?

你可能想知道,为什么我们使用文档频率而不是总词频。总词频是指一个术语在整个集合中出现的总次数(多次出现累加),这在构建语言模型或垃圾邮件分类器时常用。

但在信息检索排序中,我们通常使用文档频率。以下例子可以说明原因:

考虑“insurance”(保险)和“try”(尝试)这两个词。假设它们在集合中的总词频几乎相同,都略高于10,000次。

  • “try” 出现在约 8,700 个文档中。
  • “insurance” 出现在略低于 4,000 个文档中。

这意味着:“try”分布广泛,但在每个文档中通常只出现一次;而“insurance”则倾向于集中在某些特定文档中,并在这些文档中多次出现(例如,关于保险的文档会反复提及该词)。

对于查询“try to buy insurance”,最重要的匹配词是“insurance”,其次是“buy”,“try”的重要性相对最低。文档频率能够正确反映这一点,为“insurance”赋予更高的权重。而如果使用总词频,则“try”和“insurance”会被视为同等重要,这不符合信息检索的直觉。

总结

本节课中我们一起学习了逆文档频率权重的概念。我们了解到:

  1. IDF 通过 log(N/df) 公式计算,用于衡量术语的稀有程度和信息含量。
  2. 稀有词获得高 IDF 权重,常见词(尤其是停用词)获得低权重或零权重。
  3. IDF 是一个全局的、与查询无关的静态值。
  4. 它主要影响多术语查询的排序结果,对单术语查询的排序无影响。
  5. 在信息检索中,使用文档频率而非总词频来计算 IDF,能更好地捕捉术语的区分能力,因为文档频率反映了术语的分布集中度,而总词频可能被少数文档中的多次出现所主导。

理解 IDF 是掌握现代信息检索排序模型(如 TF-IDF 及其变体)的重要基础。

课程 P43:L7.5 - TF-IDF权重 🧮

在本节课中,我们将学习如何将词频(TF)和逆文档频率(IDF)结合,形成信息检索中最核心的权重计算方案——TF-IDF权重。我们将详细介绍其计算公式、特性,并展示如何利用它进行文档排序。


我们已经介绍了信息检索排序过程中使用的两种权重:词频(TF)和逆文档频率(IDF)。本节中,我们将把它们结合起来,得到词的TF-IDF权重。

一个词在文档中的TF-IDF权重,就是其经过对数缩放的TF权重与其IDF权重的乘积。公式如下:

TF-IDF(t, d) = TF(t, d) × IDF(t)

这是信息检索领域最著名、最核心的词权重计算方案。虽然存在许多其他研究方案,但这是必须掌握的一个。请注意,公式中的连字符“-”表示乘积关系,而非减法,有时也会用点号或乘号来更明确地表示。


那么,TF-IDF权重有哪些特性呢?

以下是TF-IDF权重的核心特性:

  • 与文档内词频正相关:一个词在文档中出现的次数越多,其TF-IDF权重通常越高。这意味着,一个查询词的权重是依赖于具体文档的。
  • 与词在集合中的稀有度正相关:一个词在整个文档集合中出现的文档越少(即越稀有),其IDF部分权重越高,从而提升整体的TF-IDF权重。

上一节我们介绍了TF-IDF权重的特性,本节我们来看看如何利用它对查询进行文档排序。

为了计算一个文档相对于某个查询的得分,我们执行以下步骤:

  1. 找出同时出现在查询和文档中的词。
  2. 对于每一个这样的词,计算其在当前文档中的TF-IDF权重。
  3. 将所有词的TF-IDF权重相加,得到该文档的总得分。

然后,根据这个得分对所有文档进行降序排列,得分最高的文档被认为与查询最相关。


至此,我们已经逐步完成了从布尔模型到向量空间模型的演进。

我们最初使用的是布尔模型中的二值向量。随后,我们使用了未经缩放的词频计数向量。现在,我们为每个文档构建了由TF-IDF值组成的实数值权重向量。

例如,文档《Julius Caesar》可以由这样一个实数值向量表示。每个文档都位于一个高维实数向量空间中,其维度等于我们文档集合中不同词项的数量。

当我们为集合中的所有文档都构建了这样的向量后,我们就得到了一个实数值的词-文档矩阵

这个矩阵是后续许多信息检索操作的基础。我们稍后会再讨论它的一些性质。目前,通过这个实数值矩阵,我们已经能够清晰地看到如何根据查询,利用为每个词和每个文档计算的TF-IDF得分来进行文档排序了。


本节课中,我们一起学习了信息检索系统的核心概念之一——TF-IDF权重。我们了解了它是词频(TF)和逆文档频率(IDF)的结合,掌握了其计算公式和特性,并明白了如何利用它为文档评分和排序。通过构建实数值的词-文档矩阵,我们为文档建立了一种更精细、更有效的数学表示,这是现代信息检索技术的基石。

课程 P44:L7.6 - 向量空间模型 🧮

在本节课中,我们将学习信息检索中最常用的模型之一——向量空间模型。我们将了解如何将文档和查询表示为向量,并利用向量之间的角度(余弦相似度)来衡量它们的相似性,从而对文档进行排序。


从词频到向量空间

上一节我们介绍了词频和逆文档频率等概念。本节中,我们将看看如何利用这些概念构建一个实用的检索模型——向量空间模型。

在之前的章节中,我们学习了如何将文档转换为实值向量。现在我们拥有一个 V 维的向量空间,其中 V 是我们词汇表中的单词数量。

  • 词汇表中的单词是这个空间的坐标轴。
  • 文档可以被视为这个空间中的点,或者是从原点指向这些点的向量。

在实际系统(如网络搜索引擎)中应用此模型时,我们会得到一个非常高维的空间,维度可能达到数千万。这些向量的一个关键属性是它们非常稀疏,因为每个单独的文档通常只包含几百或几千个单词,所以向量中的大多数条目都是零。


查询处理与相似性排序

既然我们有了文档的向量空间,那么当查询到来时,我们如何处理呢?核心思想是,我们用完全相同的方式处理查询,查询也将是同一空间中的向量。

以下是处理查询的关键步骤:

  1. 将查询转换为向量。
  2. 根据文档向量与查询向量在空间中的接近程度对文档进行排序。

接近程度对应于向量的相似性,因此它大致与距离成反比。我们这样做是为了摆脱“非此即彼”的布尔模型,获得一个关于文档与查询匹配程度的相对分数,从而将更相关的文档排在不太相关的文档之前。


从欧氏距离到角度度量

让我们尝试让这个概念更精确一些。如何在向量空间中形式化地定义“接近”?

最初的尝试是计算两点之间的距离,即它们向量端点之间的欧氏距离。但事实证明,仅使用欧氏距离并不是一个好主意,因为对于长度不同的向量,欧氏距离会很大。

让我解释一下这是什么意思。假设在我们的向量空间中,两个点之间的距离很大。但如果我们从信息检索的角度思考,这似乎是不对的。

在一个简化的例子中,我们有两个词轴:“gossip”和“jealous”。如果查询是“gossip and jealous”,那么查询向量在这两个轴上都有相等的权重。文档1主要关于“gossip”,与“jealousy”无关;文档3主要关于“jealousy”,与“gossip”无关;而文档2则同时大量涉及这两个主题。我们希望文档2被认为是与查询最相似的文档。

这启发我们采用另一种方法:与其只讨论距离,不如开始关注向量空间中的角度。


余弦相似度的引入

我们可以使用角度来代替距离。让我们通过一个思想实验来进一步说明这一点。

假设我们取一个文档 D,并将其自身附加一次,得到文档 D‘。显然,D 和 D’ 在语义上具有相同的内容。但如果我们在使用欧氏距离的常规向量空间中计算,这两个文档之间的距离会相当大,因为 D‘ 的向量长度是 D 的两倍。我们不希望这样。

相反,我们注意到这两个向量在同一条直线上,它们之间的角度为零,对应于最大的相似性。因此,我们的想法是根据文档向量与查询向量之间的角度来对文档进行排序。

以下两个概念是等价的:

  • 按照查询与文档之间角度的递减顺序对文档进行排序。
  • 按照查询与文档之间角度的余弦值递增顺序对文档进行排序。

你经常会听到“余弦相似度”这个短语,这就是我们在这里引入的概念。其原理在于,余弦函数在 0 到 180 度的区间内是单调递减的。因此,余弦分数可以作为一种角度的逆度量。


向量长度归一化与余弦公式

使用余弦相似度的另一个原因是,有一种非常高效的方法可以使用向量算术来计算文档之间的角度余弦,而无需实际计算耗时的超越函数(如余弦函数)。

计算的起点是理解向量的长度以及如何归一化向量长度。对于任何向量 x,其长度可以通过对其每个分量的平方求和,然后取外部平方根来计算。

公式:向量长度
|x| = sqrt( sum(x_i^2) )

例如,向量 (3, 4) 的长度是 sqrt(3^2 + 4^2) = 5

如果我们取任何向量并将其除以其长度,就会得到一个单位长度向量,你可以将其视为触及原点周围单位超球面表面的向量。

回到之前 D 和 D‘ 的例子,如果对它们都进行长度归一化,它们将回到完全相同的位置。这样一来,经过长度归一化后,长文档和短文档就具有了可比较的权重。

我们余弦度量的秘诀就是进行这种长度归一化。两个文档之间的余弦相似度,即它们之间角度的余弦,计算方式如下:

公式:余弦相似度
cos_sim(q, d) = (q · d) / (|q| * |d|) = sum(q_i * d_i) / ( sqrt(sum(q_i^2)) * sqrt(sum(d_i^2)) )

在分子中,我们计算点积(对应分量相乘后求和)。在分母中,我们考虑两个向量的长度。实际上,这等价于先对每个向量进行长度归一化,然后计算它们的点积。

具体来说,我们可以预先对文档向量进行长度归一化,并在查询到来时对查询向量进行长度归一化。这样,余弦相似度度量就简化为长度归一化向量的点积。

在实际操作中,我们不会遍历向量的所有元素,而只会遍历同时出现在查询 Q 和文档中的词汇项。


实例解析:简·奥斯汀的小说

现在让我们通过一个具体的例子来深入理解。在这个例子中,我们有三本简·奥斯汀的小说,我们将它们在向量空间中表示为长度归一化的向量,然后计算不同小说之间的余弦相似度。

我们的起点是这些小说的词频计数向量。词汇表包括“affection”、“jealous”、“gossip”等词。为了简化,本例中我们只使用词频加权,暂不考虑逆文档频率加权。

首先,我们对词频进行对数加权处理。然后,我们对这些向量进行长度归一化,使每个向量的长度变为 1。

代码:长度归一化向量示例(概念)

# 假设原始加权向量
vec_sense = [log(115), log(10), log(2), ...] # 对应 affection, jealous, gossip...
vec_pride = [log(58), log(7), log(0), ...]
vec_wuthering = [log(20), log(11), log(6), ...]

# 长度归一化后(数值为示意)
norm_sense = [0.996, 0.087, 0.017, ...] # 模长为1
norm_pride = [0.993, 0.120, 0, ...]
norm_wuthering = [0.847, 0.466, 0.254, ...]

然后,我们可以通过简单计算点积来得到余弦相似度:

  • 《理智与情感》和《傲慢与偏见》的相似度:0.996*0.993 + 0.087*0.120 + ... ≈ 0.94
  • 《理智与情感》和《呼啸山庄》的相似度:0.996*0.847 + 0.087*0.466 + ... ≈ 0.79
  • 《傲慢与偏见》和《呼啸山庄》的相似度:0.993*0.847 + 0.120*0.466 + ... ≈ 0.69

为什么《理智与情感》和《傲慢与偏见》的相似度高于《理智与情感》和《呼啸山庄》?我们可以观察向量。《理智与情感》向量中最大的分量是“affection”,这与《傲慢与偏见》中该词的高权重产生了很大的相似性贡献,而《呼啸山庄》中该词的权重较低。因此,点积的结果更大,相似度更高。这表明文档中不同单词的出现比例对整体相似性度量有很大影响。


总结

本节课中,我们一起学习了信息检索中的向量空间模型。其核心思想是,我们可以基于文档在高维向量空间中与查询向量的角度相似性(通过余弦相似度计算)来对文档进行检索排序。关键步骤包括将文档和查询表示为 TF-IDF 加权向量,进行长度归一化,然后计算余弦相似度作为排序依据。这种方法克服了布尔模型的局限性,能够对相关度进行量化评分。

课程 P45:L7.7 - 信息检索中计算TF-IDF的余弦得分 📊

在本节课中,我们将学习如何将TF-IDF权重与余弦相似度度量结合使用,以构建一个排序式信息检索系统。我们将了解TF-IDF加权的不同变体,并通过一个具体示例演示如何为一个查询计算单个文档的得分。最后,我们将概述在大型文档集合中高效计算余弦得分的算法思路。


TF-IDF加权:一个家族方法 👨‍👩‍👧‍👦

上一节我们介绍了TF-IDF的基本概念。实际上,TF-IDF加权并非单一方法,而是一个方法家族。让我们更详细地了解一下。

以下是构成TF-IDF加权的几个核心组件及其常见选择:

  • 词频(TF)处理:可以使用原始词频,但通常会对词频进行某种“钝化”处理,例如对数加权(log(1 + tf))。这是最常见的方法,但并非唯一。
  • 文档频率(DF)处理:可以完全不使用文档频率加权,也可以使用对数逆文档频率加权(log(N/df))。后者极为常见,但人们也尝试过其他方法。
  • 向量归一化:为了获得更好的相似度计算,我们可能希望以某种方式对向量进行归一化。我们讨论过使用余弦长度归一化,它既有优点也有缺点。人们还尝试过其他方法,例如近年来提出的“枢轴长度归一化”。

总的来说,我们有一系列广泛的选择。在著名的SMART信息检索系统(由信息检索领域的先驱Gerry Salton在康奈尔大学开发)的背景下,这些选择被赋予了字母代号。我们主要讨论的系统对应着 LTC 加权方案,即对数词频(L)、对数逆文档频率(T)和余弦归一化(C)。


查询与文档的不同加权方案 ⚖️

这种加权方案可以分别应用于查询和文档,并且方式可以不同。根据SMART的表示法,标准方式是用六个字母(中间用点分隔)来表示,前面是文档加权方案,后面是查询加权方案。

有多种变体,但20世纪90年代SMART工作中一个相当标准的方案是 lnc.ltc。让我们更详细地了解一下这个方案。

  • 查询部分(.ltc)
    • 对数查询归一化(l):这主要对长查询(可能多次提及某些词)有影响。对于短查询,如果每个词只出现一次,那么权重就是1(出现)或0(未出现)。
    • 查询词的IDF加权(t)
    • 余弦归一化(c)
  • 文档部分(lnc.)
    • 对数词频加权(l)
    • 无IDF加权(n):文档部分没有IDF归一化。这值得思考:这是一个坏主意吗?有一些理由支持这样做。其中之一是,相同的词已经在查询部分加入了IDF因子(因为只有同时出现在查询和文档中的词才会得到非零分数)。此外,不在文档索引中存储IDF值,在压缩索引和提高效率方面也有优势。
    • 余弦归一化(c)

具体计算示例 🔢

让我们使用 lnc.ltc 加权方案,通过一个具体示例来计算一个查询与一个文档的得分。我们将进行深入计算。

我们的文档是:“car insurance auto insurance”。查询是:“best car insurance”。

第一步:计算查询向量

首先处理查询“best car insurance”。

  1. 原始权重:每个出现词的权重为1。

    词项 原始权重
    best 1
    car 1
    insurance 1
  2. 对数缩放(l):由于每个词只出现一次,权重保持为1。

    词项 对数权重
    best 1
    car 1
    insurance 1
  3. IDF加权(t):获取每个词的文档频率并映射为逆文档频率。越稀有的词(如insurance)获得越高权重。

    词项 文档频率 (df) IDF = log(N/df)
    best 50,000 0.3
    car 10,000 1.0
    insurance 1,000 2.0
  4. 相乘:将对数权重列与IDF列相乘。

    词项 权重 (l * t)
    best 0.3
    car 1.0
    insurance 2.0
  5. 余弦归一化(c):将上述向量转换为单位向量。计算向量的欧几里得长度:sqrt(0.3^2 + 1.0^2 + 2.0^2) = sqrt(0.09 + 1 + 4) = sqrt(5.09) ≈ 2.256。然后将每个分量除以该长度。

    词项 归一化后权重
    best 0.133
    car 0.443
    insurance 0.886

最终查询向量[0.133, 0.443, 0.886](对应顺序:best, car, insurance)

第二步:计算文档向量

现在处理文档“car insurance auto insurance”。

  1. 原始词频

    词项 词频 (tf)
    auto 1
    car 1
    insurance 2
  2. 对数词频加权(l):应用公式 1 + log(tf)

    词项 加权后 tf
    auto 1 + log(1) = 1
    car 1 + log(1) = 1
    insurance 1 + log(2) ≈ 1.693
  3. 无IDF加权(n):文档部分不使用IDF,所以权重保持不变。

    词项 权重 (l * n)
    auto 1
    car 1
    insurance 1.693
  4. 余弦归一化(c):计算向量的欧几里得长度:sqrt(1^2 + 1^2 + 1.693^2) = sqrt(1 + 1 + 2.866) = sqrt(4.866) ≈ 2.206。然后将每个分量除以该长度。

    词项 归一化后权重
    auto 0.453
    car 0.453
    insurance 0.767

最终文档向量[0.453, 0.453, 0.767](对应顺序:auto, car, insurance)。注意,查询中有但文档中没有的“best”项,在文档向量中对应权重为0。

第三步:计算余弦相似度得分

余弦相似度是这两个长度归一化向量的点积(内积)。只有同时出现在两个向量中的维度(即“car”和“insurance”)才对点积有贡献。

  • 对于“car”:查询权重(0.443) * 文档权重(0.453) ≈ 0.201
  • 对于“insurance”:查询权重(0.886) * 文档权重(0.767) ≈ 0.679
  • 总分 = 0.201 + 0.679 = 0.88

该文档与查询匹配度很高。需要注意的是,由于余弦函数在顶部较为平缓,对于相当相似的文档,余弦得分往往偏向于较高的值。因此,记住分数的排序比记住精确的值更重要。

一个小练习:如果你知道本例中使用的IDF分数和文档频率,你应该能推算出这个示例基于的文档总数N是多少。


在检索系统中计算余弦得分 🖥️

上一节我们为一个文档计算了得分。现在,让我们看看如何在一个向量空间检索系统中为整个文档集合计算余弦得分。以下是我们要使用的大致算法思路。

我们假设查询是典型的短查询(如网页搜索),因此我们只考虑词项是否在查询中出现(即二进制权重)。同时,我们将跳过查询长度归一化这一步。原因在于,在这种情况下,查询向量有一个固定的长度,对其进行长度归一化只是一个对所有查询-文档计算都适用的缩放操作,不会改变最终的排序结果。

基于这个背景,算法步骤如下:

  1. 初始化

    • 创建一个scores数组,为所有文档设置初始得分为0。我们将在此累积不同查询词项为文档贡献的分数。这些分数也常被称为累加器(accumulators)
    • 创建另一个lengths数组,用于存储各文档向量的(未归一化)长度。
  2. 遍历查询词项

    • 对于查询中的每个词项t(其查询权重w_q简化为1,因为我们假设二进制查询且跳过查询归一化):
      • 获取词项t的倒排记录表(postings list)。
      • 对于倒排记录表中的每个文档d
        • 获取词项t在文档d中的词频tf_{t,d}
        • 应用文档的词频加权(如对数加权),得到文档权重w_{d,t}
        • w_q * w_{d,t}(本例中即w_{d,t})累加到scores[d]中。
        • (同时,我们可以在另一轮预处理中或此处计算并存储每个文档的向量长度平方length^2[d],即各词项w_{d,t}^2的和)。
  3. 文档长度归一化

    • 对于每个有分数的文档d,计算其归一化长度:norm_length[d] = sqrt(length^2[d])
    • scores[d]除以norm_length[d],完成文档的长度归一化。根据开头的假设(查询向量长度为1且未归一化),此时scores[d]的值就等同于余弦相似度分数。
  4. 排序与返回

    • 我们不需要线性扫描整个scores数组来找出最高分。在实际系统中,会使用更高效的数据结构(如堆)来找出分数最高的前K个文档。
    • 返回这Top K个(例如前10个)文档的ID或表示给用户作为初始结果。如果用户要求,可以展示更多。

关于算法实用性的说明:这还不是一个完全实用的算法。如果文档集合非常庞大(例如200亿个文档),我们不会真的为每个文档都创建一个累加器单元。系统会使用方法来确定哪些文档可能有希望,只为这些文档创建累加器。同样,在最后一步,寻找最相关文档也有比线性扫描更高效的方法。但希望这个概述能让你对如何将余弦相似度评分构建到排序检索引擎中有一个总体的认识。


总结 📝

本节课中,我们一起学习了向量空间检索的核心步骤:

  1. 将查询表示为一个TF-IDF向量。
  2. 将每个文档也表示为一个TF-IDF向量。
  3. 为了给一个查询和文档对评分,我们计算它们的余弦相似度分数。公式可以表示为:
    cosine_similarity(q, d) = (q · d) / (||q|| * ||d||)
    其中q · d是点积,||q||||d||是向量的欧几里得范数。
  4. 使用这个分数对所有文档进行排序。
  5. 首先返回前K个(例如Top 10)分数最高的文档给用户作为初始结果。如果用户需要,再展示更多结果。

这就是构建一个基于TF-IDF的排序检索系统的基本思路。

📚 课程 P46:L7.8 - 搜索引擎评估

在本节课中,我们将学习如何评估搜索引擎的质量。我们将探讨多种评估指标,并重点学习如何衡量搜索引擎返回结果的相关性,特别是针对返回排序结果的系统。


评估搜索引擎质量的方法有很多。

技术指标包括索引速度和搜索速度。
我们还可以考察查询语言的表达能力,例如系统是否能通过短语查询、邻近查询或析取查询来表达复杂的信息需求。
用户还有其他期望,例如界面简洁、使用成本低廉。
所有这些指标都是可量化的,我们可以为它们打分以衡量其优劣。

但在实践中,尽管这些指标很重要,但往往被另一个衡量用户满意度的指标所主导,即用户在使用搜索引擎时是否感到满意。响应速度和索引规模固然是影响因素,但仅凭这些,如果返回的答案毫无用处,速度再快也无法让用户满意。
因此,用户满意度的很大一部分在于返回的结果是否符合他们的需求,这就是结果与用户信息需求的相关性指标。

我在一开始就提到过,但需要再次重申:评估信息检索系统时,我们是针对信息需求进行评估。
信息需求被转化为查询,这才是信息检索系统实际运行的对象。
但相关性是相对于信息需求而非查询来判定的。例如,如果用户的信息需求是“了解喝红酒是否比喝白酒更能降低心脏病风险”,他们可能会构造一个查询,比如“红酒 白酒 心脏病 效果”。在评估搜索引擎返回相关结果的有效性时,我们不是问搜索引擎返回的文档是否仅仅包含这些词,而是问这些文档是否解决了用户的信息需求。

那么,我们如何进行评估呢?如果搜索引擎返回一组结果,我们可以通过以下三样东西进行评估:一个用于评估的基准文档集合、一组在某种意义上能代表目标信息需求的基准查询集合,以及我们收集的第三样东西——评估员对特定文档是否与特定查询相关的判断。
在实践中,通常无法穷尽地收集所有判断,尤其是在文档集合很大的情况下。但至少,我们可以让评估员判断特定搜索引擎返回的特定文档集是否与查询相关。
如果我们拥有包含这三样东西的结果集,我们就可以开始评估了,因为我们可以使用之前见过的相同指标:精确率、召回率以及结合二者的F值。
这些是合适的优秀指标,原因与我们讨论命名实体识别时相同:通常只有少数文档与特定查询相关,因此通过精确率和召回率来衡量效果更好。

但是,如果我们现在面对的是一个返回排序结果的搜索引擎,就不能直接使用精确率、召回率和F值这些指标了,因为系统可以返回任意数量的结果。实际上,返回的数量通常取决于我们点击“查看更多”的频率。
不过,如果我们查看结果的任意初始子集,就可以计算出该子集的精确率和召回率,然后将它们组合起来,得到一条精确率-召回率曲线。让我们看看这是如何工作的。

假设这是搜索引擎返回的前10个结果,我们根据评估员的判断标记了每个结果是相关或不相关的。
然后,我们可以取这些文档的任意初始子序列来计算召回率和精确率。
对于第一个文档,系统判断正确,它是一个相关文档。假设整个集合中共有10个相关文档,那么系统找到了10个相关文档中的1个,因此其召回率是0.1。
由于该文档是相关的,系统第一个答案就正确,此时其精确率是1。
下一个文档不相关,因此前两个文档的召回率仍是0.1,精确率现在是0.5。
又一个不相关文档,召回率仍为0.1,精确率现在降至约0.33。
如果我们看前四个文档的集合,现在找到了10个相关文档中的2个,召回率是0.2,精确率回升到0.5。
第五个文档也是相关的,现在召回率上升到0.3,精确率是3/5=0.6。
我们可以继续往下计算。

我想提到的另一个指标,也是近年来最常用的指标之一,是平均精确率均值
假设我们有以下排序检索结果(为方便说明,顺序如下):第一个返回文档相关,第二个不相关,第三个不相关,然后一个相关,又一个相关,接着不相关、相关、相关。假设这是我们的前8个结果。
计算平均精确率均值时,首先需要计算单个查询的平均精确率。
方法是:计算在每个返回相关文档的位置上的精确率,因为此时召回率在增加。
所以,在第一个相关文档处,精确率是1。
在第四个文档处(此时已返回两个相关文档),精确率是0.5。
在第五个文档处,精确率是0.6。
在第七个文档处,返回了7个文档,其中4个相关,精确率约为0.57。
在第八个文档处,返回了8个文档,其中5个相关,精确率是0.625。
然后,为了计算平均精确率均值,我们继续计算这些数字。在实践中,通常不会穷尽计算,而是计算到某个点,比如前100个结果,然后取所有这些数字的平均值,这就是该查询的平均精确率。
接着,对基准查询集合中的所有其他查询计算相同的平均精确率,再取这些平均精确率的平均值,就得到了平均精确率均值。
具体来说,这被称为宏平均,即每个查询在计算平均精确率均值时权重相等。

这是一个很好的指标,它在某种程度上评估了不同召回率水平下的精确率,同时仍然最看重前几个返回文档的精确率。
在跨查询的层面上,它给予不同查询相等的权重,这往往是有用的做法,因为你总是希望系统在处理稀有词查询和常见词查询时都能表现良好。因此,这是评估信息检索系统时一个相当不错的指标。

当然,还有更多方法可以讨论,但以上内容已经很好地说明了如何评估排序检索系统的性能。


本节课中,我们一起学习了搜索引擎评估的核心概念。我们了解到,除了技术指标,用户对结果相关性的满意度至关重要。我们回顾了精确率、召回率和F值在评估固定结果集时的应用,并重点学习了如何通过精确率-召回率曲线平均精确率均值来评估返回排序结果的搜索引擎。MAP是一个广泛使用的优秀指标,它综合考虑了不同召回率下的精确率,并对所有查询给予平等关注。

课程 P47:L8.1- 词义学习 📚

在本节课中,我们将要学习词义的基本概念,了解语言学如何定义和描述词义,并探讨这些概念如何为自然语言处理中的计算模型提供基础。

词义的语言学背景

让我们从词义的一些语言学背景知识开始。

在自然语言处理系统中,我们如何表示词义?在经典的自然语言处理应用中,我们对一个词的唯一表示就是它的一串字母,或者是词汇表中的一个索引。

这种表示方式与哲学中的另一种传统并无太大不同,或许你在入门逻辑课程中见过。那种传统中,词义仅仅通过将单词大写来表示,例如将“dog”的含义表示为 DOG,将“cat”的含义表示为 CAT

仅仅通过大写来表示词义也是一个相当不令人满意的模型。你可能见过一个最初由芭芭拉·帕蒂提出的笑话:生命的意义是什么?LIFE。我们当然可以做得比这更好。一个词义理论应该为我们做什么?让我们看看从词汇语义学(语言学研究词义的领域)中得出的一些要求。

词元与词义

首先,我们来考虑词元和词义的概念。

这里有一个从在线同义词库WordNet中提取的单词“mouse”的定义,它包含两个词义。你会注意到我们有一个词元“mouse”。回想一下,词元代表一个词的核心词干,而像“mice”这样的词形则代表不同的屈折或派生形式。

同时注意,“mouse”有两个词义:词义1是“任何多种小型啮齿动物”;词义2是“一种手动操作、控制光标的设备”,这是大家都熟悉的。因此我们说,一个词义(有时我们也称之为概念)是单词的意义组成部分,并且词元可以是多义的,意味着它们可以有多个词义。在这个例子中,单词“mouse”就有两个词义。

词义间的关系

既然我们已经定义了词义,我们可以看到这些词义之间存在着各种关系。

同义关系

词义之间一种可能的关系是同义关系。同义词在部分或全部语境中具有相同的含义。例如,我们常称为“hazelnut”的坚果也可以叫做“filbert”;“couch”和“sofa”似乎是同义词;“big”和“large”或“automobile”和“car”也是。

然而,事实证明,可能不存在完美的同义词。即使两个词在许多方面的含义可能相同,它们仍可能因礼貌程度、俚语、语域或体裁等因素而有所不同。

例如,考虑“water”与“H₂O”。“H₂O”用于科学语境,在冲浪指南或徒步手册中使用是不合适的,而“water”在那里则更合适。这种体裁差异就是词义的一部分。

再以“big”和“large”为例。虽然它们的含义有重叠,但各自都有对方不具备的词义。例如,我们可以说“my big sister”来表示“我的姐姐”,但“my large sister”就没有这个意思。因此,在实践中,“同义词”这个词被用来描述一种近似或粗略的同义关系。

语言学的一个基本原则叫做“对比原则”,它指出形式上的差异总是与意义上的差异相关联。这个原则可以追溯到18世纪,当时阿贝·吉拉尔首次指出语言似乎没有完美的同义词。

词相似性

虽然单词没有很多同义词,但大多数单词确实有很多相似的词。例如,“cat”不是“dog”的同义词,但猫和狗无疑是相似的词。汽车与自行车相似,汽车与马相似。当然,这些都不是同义词。

词相似性的概念在许多语义任务中非常有用。知道两个词有多相似,有助于计算两个短语或句子的含义有多相似,这是自然语言理解任务(如问答、释义和摘要)中非常重要的组成部分。

获取词相似性数值的一种方法是请人类判断一个词与另一个词的相似程度。许多数据集就是由这类实验产生的。例如,SimLex-999数据集给出了从0到10的评分,范围从近乎同义词的“vanish”和“disappear”(极其相似),到“whole”和“agreement”这类几乎没有任何共同点的词对。

词关联性

两个词的含义除了相似性之外,还可以通过其他方式关联。其中一类连接被称为词关联性,在心理学中也传统地称为词联想。

例如,“coffee”与“tea”相似,但“coffee”与“cup”不相似。它们几乎没有共同特征。咖啡是一种植物或饮料,杯子是一个具有特定形状和功能的制成品。但“coffee”和“cup”显然是相关的,它们通过参与日常事件(用杯子喝咖啡这件事)而关联起来。

同样,“scalpel”和“surgeon”不相似,但在事件上是相关的,外科医生倾向于使用手术刀。

词之间一种常见的关联性是它们是否属于同一语义场。语义场是一组覆盖特定语义领域并彼此具有结构化关系的词。例如,与医院语义场相关的词,如“surgeon”、“scalpel”、“nurse”;或与餐厅相关的词,如“waiter”、“menu”、“plate”;或与房屋相关的词,如“door”、“kitchen”、“bed”。

反义关系

词义之间的另一种关系是反义关系。这些词义是相反的,但仅针对含义的某一个特征而言。反义词在其他方面极其相似。考虑像“dark/light”、“hot/cold”或“up/down”这样的反义词。“Hot”和“cold”非常相似,它们都是描述温度、温度标尺上某一点的术语,以及温度在现实世界中的所有含义。它们的不同仅在于描述的是标尺上的哪一点。

更正式地说,反义词可以定义二元对立,或者处于一个标尺的两端。例如,我们可以有一个像温度这样的标尺,“hot”在这一端,“cold”在另一端。或者它们可以是反向的,所以你可以有像“up/down”或“fall/rise”这样的词,这些词所表示的动作方向是相反的。

情感意义或内涵

词语具有情感意义或内涵。“Connotation”这个词在不同领域有不同的含义,但在这里我们用它来表示与作者或读者的情感、情绪、意见或评价相关的词义方面。

例如,有些词有积极的内涵,如“happy”,而另一些则有消极的内涵,如“sad”。即使在其他方面含义相似的词,其内涵也可能不同。考虑“copy”、“replica”、“reproduction”这一组词,与“fake”、“knock-off”、“forgery”另一组词之间的内涵差异。它们共享许多含义(即都是复制品),但在内涵上却有很大不同。

有些词描述积极的评价,如“great”和“love”,有些则描述消极的评价,如“terrible”和“hate”。消极或负面的评价语言被称为情感,正如我们已经看到的,词语情感在情感分析、立场检测以及自然语言处理在政治语言和消费者评论中的应用等任务中扮演着重要角色。

奥斯古德及其合作者关于情感意义的早期研究发现,词语在三个重要的情感意义维度上存在差异,这三个维度被称为效价、唤醒度和支配度。效价指刺激的愉悦度;唤醒度指其引发情绪的强度;支配度指控制的程度。

例如,像“love”这样的词,效价非常高,非常愉悦,是情感上积极的概念。“Happy”也是积极的。而“toxic”或“nightmare”则效价非常低,非常不愉快。这些数值来自赛义德·穆罕默德的NRC效价-唤醒度-支配度词典。

唤醒度是刺激引发的情绪强度,无关积极或消极,而是强度如何。例如,“mellow”的唤醒度可能非常低,而“elated”或“frenzy”的唤醒度可能非常高。支配度是控制的程度。像“powerful”或“leadership”这样的词意味着非常高的控制程度,而“weak”或“empty”则意味着很低的控制程度。

总结

本节课中,我们一起学习了词义的一些基本方面。我们看到,概念或词义与词语之间存在着复杂的多对多关联,并且这些词义彼此之间存在如同义、反义、相似、关联和内涵等关系。这些知识为我们构建计算模型提供了一些基本要求。

课程 P48:L8.2 - 语义向量 📚

在本节课中,我们将要学习语义向量,这是自然语言处理中表示词义的标准方法。我们将探讨其核心思想、工作原理以及它如何帮助我们更好地理解和处理语言。


概述

语义向量是自然语言处理中表示词义的标准方法。它帮助我们建模上一节讨论过的词义的诸多方面。该模型的根源可追溯至20世纪50年代,当时有两个重要思想交汇。


核心思想:从分布到向量

上一节我们介绍了词义的不同层面,本节中我们来看看如何用数学化的方式表示词义。

第一个思想源于哲学家维特根斯坦的观点:一个词的意义应与其使用方式紧密相连。语言学家哈里斯和费斯等人提出了一个相关理念,即通过一个词在语言使用中的分布来定义其意义,也就是它周围的词或语法环境。

以下是泽利格·哈里斯的一段引述:

如果A和B拥有几乎相同的环境(即周围的词或语法结构),我们就说它们是同义词。

让我们考虑一个例子。假设你不知道“空心菜”这个词的意思,但你在以下语境中看到它:

  • 空心菜用大蒜炒很好吃。
  • 它配米饭非常棒。
  • 空心菜的叶子沾着咸酱汁。

同时,你也看到“大蒜”、“米饭”、“叶子”这些词与“菠菜”、“牛皮菜”等词出现在相似的语境中。你可能会推断“空心菜”是一种类似菠菜或牛皮菜的绿叶蔬菜。这个推断正是基于“叶子”、“大蒜”、“米饭”、“好吃”等词既出现在“空心菜”周围,也出现在我们已知的“菠菜”等词周围。

事实上,空心菜就是“Ipomoea aquatica”(水蕹菜)。所以,这第一个思想就是:我们将通过一个词在语言使用中的分布(即其邻近的词或语法环境)来定义其意义

第二个思想是奥斯古德在1957年提出的,我们在上一讲中提到过。他认为一个词的内涵可以用三个数字来表示:效价、唤醒度和优势度。例如,“爱”可能有很高的效价,“柔和”可能有较低的唤醒度。但每个词在这三个维度上都有分数。

既然每个词在三个维度上都有分数,那就意味着我们本质上是在用一个三维空间中的点来表示一个词的内涵。如果我们能用空间中的一个点来表示内涵,那么我们或许也能用空间中的一个点来表示更多关于意义的信息。

因此,我们将结合这两个思想:通过语言分布来定义意义,并将意义表示为多维空间中的一个点


什么是语义向量?🔢

在语义向量模型中,我们根据分布将意义定义为空间中的一个点。因此,每个词都是一个向量,而不是像“G-O-O-D”这样的字母串,或像 W_45 这样的索引。相似词在语义空间中彼此靠近。关键的是,正如我们将看到的,我们可以通过观察文本中哪些词彼此邻近来自动构建这个空间。

下图展示了一个情感分析项目中学习到的词嵌入可视化结果,其中部分词语从高维空间(本例中是60维)投影到二维空间以便观察。请注意,积极词、消极词和中性的功能词分别聚集在不同的区域。

总而言之,我们将词义定义为一个向量。由于历史原因(涉及将其“嵌入”到空间中),这些向量通常被称为嵌入。这些嵌入是NLP中表示意义的标准方法,所有现代NLP算法都使用某种嵌入来表示词义。


为什么使用向量?🤔

为什么从字母串或索引转向用向量来表示词义是有帮助的?

考虑情感分析任务。假设我们使用词语本身进行情感分类,那么一个特征可能是“前一个词是‘糟糕的’”。这个特征只有在训练集和测试集中看到完全相同的词时才会被激活,否则不会。

相比之下,使用嵌入时,特征是一个向量。我们可能将特征表示为“前一个词是向量 [35, 22, 17, ...]”。现在,在测试集中,我们可能会看到一个像“可怕的”这样的词,它虽然不是“糟糕的”,但可能拥有一个相似的向量。这样,我们的分类器就能泛化到语义相似但未见过的词。


主要嵌入类型简介

在接下来的课程中,我们将讨论两大类嵌入模型。

以下是两种主要的嵌入类型:

  1. TF-IDF 嵌入

    • TF-IDF 是信息检索的主力,也是一种常见的嵌入基线模型。
    • TF-IDF 向量是稀疏的,这意味着它们是非常长的向量,其中大部分值为0。
    • 向量中的值是基于邻近词计数的简单函数计算得出。
  2. Word2Vec 嵌入

    • 这是最简单的稠密向量模型。在稠密向量模型中,大部分值非0
    • 这些向量比TF-IDF向量短得多
    • Word2Vec 表示是通过训练一个分类器来预测一个词是否可能出现在附近而创建的。

稍后,我们还将讨论更丰富的嵌入类型,称为上下文嵌入


总结与展望

从今以后,当我们为语义或与意义相关的任务表示词语时,我们将尝试使用意义表示(即向量)进行计算,而不是字符串表示。

让我用中国哲学家庄子的一句名言来结束本课:

筌者所以在鱼,得鱼而忘筌;言者所以在意,得意而忘言。


本节课中,我们一起学习了语义向量的基本直觉和核心思想。我们了解到,语义向量通过将词表示为多维空间中的点,并依据词语在文本中的分布来构建这个空间,从而能够捕捉词义并计算词语间的相似性。在接下来的课程中,我们将深入探讨TF-IDF和Word2Vec等具体嵌入模型的细节。

课程 P49:L8.3 - 词与向量 🧠📊

在本节课中,我们将学习如何将词语表示为简单的向量,特别是通过统计它们在上下文中的出现次数来实现。我们将探讨两种主要的矩阵表示方法:词-文档矩阵和词-词矩阵,并理解这些向量如何捕捉词语和文档的语义。


1. 词-文档矩阵 📄

上一节我们介绍了向量语义的基本概念。本节中,我们来看看如何用词-文档矩阵来表示文档。

想象我们有一个文档集合,例如莎士比亚的所有作品。我们可以用一个矩阵来表示这个集合中的文档,其中每一行代表词汇表中的一个词每一列代表集合中的一个文档

以下是一个简化的词-文档矩阵示例,展示了四个词语在莎士比亚四部戏剧中的出现次数:

词语/文档 《皆大欢喜》 《第十二夜》 《尤利乌斯·凯撒》 《亨利五世》
battle 1 0 7 13
good 114 80 62 89
fool 36 58 1 4
wit 20 15 2 3

矩阵中的每个单元格代表特定行所定义的词特定列所定义的文档中出现的次数。例如,fool 在《第十二夜》中出现了 58 次。

我们可以将每一列视为一个向量,将一个文档表示为 V 维空间中的一个点(V是词汇表大小)。因此,上述文档就是四维空间中的点。

以下是这四部戏剧文档向量在二维空间(仅选取 battlefool 两个维度)的可视化:

  • 喜剧(如《皆大欢喜》、《第十二夜》)在 fool 维度上值较高,在 battle 维度上值较低。
  • 历史剧/悲剧(如《尤利乌斯·凯撒》、《亨利五世》)则相反。

两个相似的文档倾向于包含相似的词语。如果两个文档包含相似的词语,它们的列向量也会相似。因此,喜剧《皆大欢喜》和《第十二夜》的向量彼此更相似(更多 foolwit,更少 battle),而与《尤利乌斯·凯撒》或《亨利五世》的向量差异较大。

一个真实的词-文档矩阵当然不止四行四列。更一般地,该矩阵有 V 行(词汇表中每个词型一行)和 D 列(集合中每个文档一列)。


2. 从文档向量到词向量 🔄

上一节我们看到了如何用向量表示文档。本节中,我们将应用相同的原理来表示词语的含义

我们通过将每个词与一个词向量(现在是行向量,而非列向量)关联起来实现这一点。例如,词语 fool 的向量 [36, 58, 1, 4] 就对应着它在四部莎士比亚戏剧中的出现次数。

对于文档,我们看到相似文档有相似向量,因为相似文档包含相似词语。同样的原理也适用于词语相似的词语具有相似的向量,因为它们倾向于出现在相似的文档中

因此,词-文档矩阵让我们可以通过一个词倾向于出现在哪些文档中来表示它的含义。


3. 词-词矩阵 🔗

除了使用词-文档矩阵将词表示为文档计数向量,另一种方法是使用词-词矩阵(也称为词-词共现矩阵或术语上下文矩阵)。

在这种矩阵中,列标签也是词语,而不是文档。因此,矩阵变为 V × V 的方阵。每个单元格记录了目标词(行)上下文词(列) 在某个特定上下文中共现的次数

上下文可以是整个文档。此时,单元格(例如 digitalcomputer)表示这两个词在同一文档中共同出现的次数。

然而,更常见的做法是使用更小的上下文窗口,通常是目标词周围的一个窗口,例如左右各四个词。在这种情况下,单元格表示在某个训练语料库中,列词出现在行词的 ±4 词窗口内的次数。

以下是一个基于窗口共现统计的示例表:

目标词/上下文词 computer data pie sugar
digital 1670 1683 0 0
information 3325 3982 0 0
strawberry 0 0 19 38

例如,表中的 1670 表示词语 digital 在语料库中出现在 computer 周围 ±4 词的窗口内共 1670 次。

请注意,根据这种向量定义,digitalinformation 的向量彼此更相似(在 computerdata 维度上值高,在 piesugar 维度上值低),而它们与 strawberry 的向量差异较大(strawberry 的向量在 piesugar 上下文上值更高)。

以下是 digitalinformation 的词向量在二维空间(仅选取 datacomputer 两个维度)的可视化,可以看到它们的位置很接近。


4. 向量维度与稀疏性 📏

当然,在实际应用中,这些向量的长度不是 2 或 4。向量的长度通常是词汇表的大小 V,一般在 10,000 到 50,000 个词之间(通常使用训练语料库中最频繁出现的词)。保留大约最高频的 50,000 个词之后的词通常没有帮助。

由于大多数共现次数为 0,这些向量是稀疏向量。存在高效的算法用于存储和计算稀疏矩阵。


总结 📝

本节课中,我们一起学习了词语向量表示的核心思想:

  1. 一个词可以表示为一个计数向量,这是所有嵌入表示方法的基础思想。
  2. 词-文档矩阵通过词语在文档中的出现模式来表示文档和词语。
  3. 词-词(共现)矩阵通过目标词周围上下文窗口内其他词的共现频率来更精细地表示词语含义。
  4. 相似的文档或词语会具有相似的向量。
  5. 这些向量通常是高维且稀疏的,但有专门的技术进行处理。

我们看到了如何将词语和文档转化为数学空间中的点,从而为计算语义相似性奠定了基础。

课程 P5:L1.5 - 字节对编码 (BPE) 🧩

在本节课中,我们将学习字节对编码算法。这是一种基于语料库统计,将文本切分成词元的方法。

上一节我们介绍了基础的按空格或字符分词的方法。本节中我们来看看如何利用数据本身来指导分词过程。这类算法通常被称为子词分词,因为生成的词元既可以是完整的单词,也可以是单词的一部分。

算法概述

字节对编码等子词分词算法通常包含两个部分:

  1. 词元学习器:接收训练语料库,并归纳出一个词元词汇表。
  2. 词元切分器:根据从训练语料库学到的词汇表,对新的测试句子进行分词。

接下来,我们先详细介绍词元学习器的核心步骤。

词元学习器:构建词汇表

我们从一个初始词汇表开始,它包含语料库中所有独立的字符。

以下是构建词汇表的核心步骤:

  1. 我们首先在训练语料库中找出出现频率最高的相邻符号对(例如字母A和B)。
  2. 然后,我们将这个新组合“AB”作为一个新的符号添加到词汇表中。
  3. 接着,我们在整个训练语料库中,将所有相邻的“A B”替换为这个新符号“AB”。
  4. 我们将重复以上步骤K次,K是算法的一个参数。

我们可以用更形式化的方式描述这个过程:

# 伪代码描述
初始化词汇表 V = 语料库中所有唯一字符
for i in range(K): # 进行K次合并
    在语料库C中找到出现频率最高的相邻词元对 (X, Y)
    创建新词元 Z = X + Y
    将 Z 加入词汇表 V
    将语料库C中所有相邻的 (X, Y) 替换为 Z
返回最终词汇表 V

一个重要的补充:词尾标记

在实践中,大多数子词分词算法会在以空格分隔的单词内部运行。因此,我们通常会在分词前,在每个空格前添加一个特殊的词尾标记(常用下划线 _ 表示)。

让我们通过一个具体的例子来理解整个过程。

实例演示

假设我们有如下语料库:
low low low low lowest newer newer newer newer newer wider wider wider new new

首先,我们在每个空格前添加词尾标记 _,得到:
low_ low_ low_ low_ lowest_ newer_ newer_ newer_ newer_ newer_ wider_ wider_ wider_ new_ new_

初始词汇表包含所有唯一字符:{d, e, i, l, n, o, r, s, t, w, _}

为了方便演示,我们用计数方式表示语料库:

  • low_ 出现 5 次
  • lowest_ 出现 1 次
  • newer_ 出现 5 次
  • wider_ 出现 3 次
  • new_ 出现 2 次

现在,我们开始执行合并步骤:

  1. 第一轮合并:找出最常相邻的字符对。er 相邻出现了 9 次(在 newer_wider_ 中)。我们将它们合并为新符号 er,加入词汇表,并替换语料库中所有 e rer
  2. 第二轮合并:现在,最常相邻的是新符号 er 和词尾标记 _,出现了 9 次。合并为 er_ 并加入词汇表。
  3. 后续合并:继续此过程。接下来合并 nene,然后合并 newnew,接着合并 lolo,再合并 lowlow,等等。

经过多轮合并后,我们的词汇表将包含原始字符以及诸如 er, er_, ne, new, lo, low, low_ 等新词元。

词元切分器:应用至新文本

在训练阶段,我们基于训练语料库的频率确定了合并的顺序。在测试阶段,我们将贪婪地按照这个学到的顺序应用这些合并规则,而不再考虑测试文本中的频率。

例如,对于测试字符串 newer_

  • 首先应用第一条规则:将 e r 合并为 er,得到 ner_
  • 然后应用后续规则,最终可能被分词为完整的词元 newer_(如果该词元已在词汇表中)。

对于测试字符串 lower_

  • 应用规则后,可能会被分词为两个词元:lower_,因为词汇表中不存在 lower_ 这个完整的词元。

BPE 的特点

生成的 BPE 词元具有以下特点:

  • 高频词通常会被保留为完整词元。
  • 高频子词(通常是词缀)也会成为独立词元,例如 -er, -est
  • 这些子词常常对应语言的语素(最小的意义单位)。例如单词 unlikely 包含语素 un-, like, -ly。BPE 算法有较大可能识别出这些语素,尽管并非总是如此。

总结

本节课中我们一起学习了字节对编码算法。它是一种基于语料库统计的子词分词方法,通过不断合并高频相邻符号对来构建词汇表,并能有效地将新文本切分成词元。BPE 及其变体(如 WordPiece)在自然语言处理领域被广泛使用,是许多现代 NLP 模型(如 BERT、GPT)分词的基础。

课程 P50:L8.4 - 基于余弦距离的相似度 📐

在本节课中,我们将学习如何衡量两个词之间的相似度。我们将重点介绍一种最常用的相似度度量方法——余弦相似度,它通过计算两个向量之间夹角的余弦值来评估它们的相似程度。

概述

为了衡量两个词之间的相似度,我们需要一种能够比较它们向量表示的度量方法。迄今为止,最常用的相似度度量是两个向量之间夹角的余弦值。

与自然语言处理中使用的大多数向量相似度度量方法一样,余弦相似度基于线性代数中的点积运算。

点积:相似度的基础

点积,也称为内积,其计算方式是将两个向量逐元素相乘,然后将所有乘积相加,得到一个标量值。

公式dot_product(A, B) = Σ (A_i * B_i)

点积之所以能作为相似度度量,是因为当两个向量在相同维度上都有较大的值时,点积值往往会很高。相反,如果向量在不同维度上存在零值(即正交向量),它们的点积将为0,这表示它们极不相似。

原始点积的问题与改进

然而,原始点积作为相似度度量存在一个问题:它偏向于较长的向量。

向量长度定义为各维度值的平方和的平方根。如果一个向量更长,在每个维度上的值更高,那么点积就会更高。更频繁出现的词往往有更长的向量,因为它们倾向于与更多的词共现,并且与每个词的共现值更高。

因此,原始点积对于高频词会更高。但这是一个问题:我们希望相似度度量能告诉我们两个词有多相似,而不受它们频率的影响。

为了解决这个问题,我们通过将点积除以两个向量各自的长度来对向量长度进行归一化。

余弦相似度的定义

这个归一化的点积结果恰好等于两个向量之间夹角的余弦值。这是基于点积的几何定义:两个向量的欧几里得模长与它们夹角余弦值的乘积。

公式cosine_similarity(A, B) = (A · B) / (||A|| * ||B||)

余弦值的范围从1(向量方向完全相同,夹角为0度)到-1(向量方向完全相反,夹角为180度)。但由于原始的频率值是非负的,这些值的余弦范围在0到1之间。

计算示例:哪个词更接近“信息”?

让我们看看如何用余弦相似度计算“cherry”(樱桃)或“digital”(数字)哪个在含义上更接近“information”(信息),这里使用一个简化计数表中的原始计数。

要计算“cherry”和“information”之间的余弦值:

  1. 计算点积:(442 * 5) + (8 * 3982) + (2 * 3325)
  2. 计算“cherry”的向量长度:sqrt(442² + 8² + 2²)
  3. 计算“information”的向量长度:sqrt(5² + 3982² + 3325²)
  4. 将点积除以两个长度的乘积,得到结果:0.017

相比之下,用同样的方法计算“digital”和“information”之间的余弦值。由于两个词在“data”(数据)和“computer”(计算机)维度上的数值都非常高,我们得到一个高得多的余弦值。

模型判定“information”与“digital”的相似度远高于与“cherry”的相似度,这个结果是合理的。

余弦相似度的直观理解

以下是一个简化的图形演示,展示了在由“computer”和“pie”(馅饼)附近词计数定义的微小二维空间中,“cherry”、“digital”和“information”的向量。

请注意,“digital”和“information”之间的夹角小于“cherry”和“information”之间的夹角。当两个向量更相似时,余弦值更大,但夹角更小。余弦的最大值为1,此时两个向量之间的夹角最小,所有其他角度的余弦值都小于1。

总结

本节课中,我们一起详细学习了向量余弦相似度,这是衡量两个词向量相似性最常用的算法。我们了解了其基础——点积运算,指出了原始点积的局限性,并通过归一化引入了余弦相似度的概念和计算公式。最后,通过一个具体示例,我们看到了余弦相似度如何有效地比较词语之间的语义接近程度。

📚 课程 P51:L8.5 - TF-IDF 详解

在本节课中,我们将学习一种在词-文档矩阵中重新加权词频的常用方法:TF-IDF。我们将了解其构成、计算方式以及为何它能比原始词频更有效地表示词语的重要性。


🧠 核心概念:为何需要重新加权?

上一节我们介绍了共现矩阵,它通过词语与其他词语或文档的频率来表示每个单元格。但事实证明,原始频率是一个偏差很大且区分度不高的指标。

例如,如果“sugar”在“apricot”的上下文中频繁出现,这是有用的信息。但像“the”或“it”这样的词,它们非常常见且与各种词同时出现,对于任何特定词来说,它们的信息量并不大。

这里存在一个矛盾:我们如何平衡这两种相互冲突的约束?有两种常见的解决方案:

  • TF-IDF算法:通常在维度是文档时使用。
  • 基于点互信息的算法:通常在维度是词语时使用。

TF-IDF使用一种称为逆文档频率的特殊权重来降低“the”或“it”这类词的权重。而PMI是一种统计度量,用于比较我们观察到的概率与偶然情况下的预期概率。


🔢 TF-IDF 的构成

TF-IDF算法是两个项的乘积,每一项都捕捉了上述的一种直觉。

第一项:词频

第一项是词频,即词语 t 在文档 d 中的频率。

我们可以直接使用原始计数作为词频,但更常见的做法是使用频率的 log₁₀ 来稍微压缩原始频率。其直觉是,一个词在文档中出现100次,并不意味着它与该文档含义的相关性就提高了100倍。

由于不能对零取对数,我们通常会在计数上加一。因此,词频的计算公式常为:
TF(t, d) = log₁₀(count(t, d) + 1)

第二项:逆文档频率

TF-IDF中的第二个因子用于给那些仅出现在少数文档中的词更高的权重。局限于少数文档的词语有助于将这些文档与集合中的其他文档区分开来。而在整个集合中频繁出现的词语则帮助不大。

一个词的文档频率是指它出现在多少个文档中。请注意,文档频率不同于词的集合频率,后者是词语在整个集合的所有文档中出现的总次数。

考虑莎士比亚37部戏剧的集合中的两个词:“Romeo”和“action”。这两个词具有相同的集合频率(都在所有戏剧中出现了113次),但文档频率却截然不同,因为“Romeo”只出现在一部戏剧中。因此,如果我们的目标是找到关于罗密欧爱情磨难的文档,“Romeo”一词应该被赋予很高的权重,而“action”则不应如此。

我们通过逆文档频率来强调像“Romeo”这样的区分性词语。IDF的定义使用了分数 N / DF_t,其中 N 是集合中文档的总数,DF_t 是出现词 t 的文档数量。词出现的文档越少,其权重越高。出现在所有文档中的词获得最低权重1。

由于许多集合中文档数量庞大,这个度量通常也会用对数函数进行压缩。因此,逆文档频率的最终定义是:
IDF(t) = log₁₀(N / DF_t)

下图右侧是莎士比亚语料库中一些词的IDF值示例,范围从仅出现在一部剧中的极高信息量词(如“Romeo”),到出现在少数剧中的词(如“salad”或“falstaff”),再到非常常见的词(如“fool”),以及因为出现在全部37部剧中而完全不具备区分度的词(如“good”或“sweet”)。


📄 如何定义“文档”?

通常,文档的界定是清晰的。在处理莎士比亚戏剧集时,文档就是一部剧;在处理像维基百科这样的百科全书文章集时,文档就是一个维基百科页面;在处理报纸文章时,文档就是单篇文章。

但很多时候,为了计算IDF,你可能需要自行将语料库分割成文档,因为“文档”可以是任何东西,它不必是原始文档。例如,我们经常将每个段落视为一个文档,这样即使我们处理的是像一本书这样的单一文档,也能计算TF-IDF值。


⚖️ 计算TF-IDF加权值

因此,词语 t 在文档 d 中的TF-IDF加权值,结合了该词在文档中的词频和该词的逆文档频率。公式为:
TF-IDF(t, d) = TF(t, d) × IDF(t)

以下是莎士比亚词-文档矩阵的原始计数示例,以及同一矩阵经过TF-IDF加权后的版本。请注意,对应词语“good”的维度TF-IDF值现在全部变为零,因为这个词出现在每个文档中,TF-IDF算法导致它被忽略。同样,出现在37部剧中的36部里的词语“fool”,其权重也远低于其他词语。


🎯 总结

本节课我们一起学习了TF-IDF。我们了解到,原始词频存在偏差,TF-IDF通过结合词频逆文档频率来解决这个问题。TF-IDF能提升区分性词语的权重,降低常见词的权重,从而生成更能代表文档语义内容的加权向量表示。

📚 课程 P52:L8.6 - Word2Vec 详解

在本节课中,我们将学习一种重要的词向量表示方法——Word2Vec。我们将从词向量的基本概念入手,逐步介绍 Skip-gram 模型的原理、目标函数以及其核心思想。


🎯 词嵌入简介

在之前的课程中,我们学习了如何将单词表示为稀疏的长向量,其维度对应词汇表中的单词或文档集合中的文档。现在,我们引入一种更强大的词表示方法:词嵌入(embeddings),即短而密集的向量。

与之前见过的向量不同,词嵌入是短向量,其维度 D 通常在 50 到 1000 之间,而不是更大的词汇表大小 V(可能达到 60000)或文档数量。这些 D 维度没有明确的解释,向量是密集的,而不是稀疏的(即向量条目大多是 0 计数或计数函数)。这些值将是实数,可以是负数。


🔍 密集向量的优势

事实证明,密集向量在每一项自然语言处理任务中都优于稀疏向量。虽然我们不完全理解其中的所有原因,但有一些直观的解释。

将单词表示为 700 维的密集向量,要求我们的分类器学习的权重远少于将单词表示为 50000 维向量时的情况。较小的参数空间可能有助于泛化和避免过拟合。

密集向量也可能更好地捕捉同义词。例如,在稀疏向量表示中,像“car”和“automobile”这样的同义词的维度是独立且无关的。因此,稀疏向量可能无法捕捉到以“car”为邻居的单词和以“automobile”为邻居的单词之间的相似性。


🚀 Word2Vec 与 Skip-gram 模型

在本节中,我们介绍一种计算词嵌入的方法:带负采样的 Skip-gram 模型(SGNS)。Skip-gram 算法是名为 Word2Vec 的软件包中的两种算法之一,因此有时该算法被宽泛地称为 Word2Vec。

Word2Vec 方法训练快速、高效,并且易于在线获取。Word2Vec 词嵌入是静态嵌入,这意味着该方法为词汇表中的每个单词学习一个固定的嵌入。这些静态嵌入的替代方案是最近学习动态上下文嵌入的方法,例如流行的 BERT 表示,其中每个单词的向量在不同上下文中是不同的。


💡 Skip-gram 的核心思想

Word2Vec 的直觉是,与其计算每个单词 W 在另一个单词(例如“apricot”)附近出现的频率,不如在一个二元预测任务上训练一个分类器:单词 W 是否可能出现在“apricot”附近?我们实际上并不关心这个预测任务本身,而是将学习到的分类器权重作为词嵌入。

这里的革命性直觉是,我们可以直接使用运行文本作为此类分类器的隐式监督训练数据。出现在目标词附近的单词 C 可以作为“单词 C 是否可能出现在目标词附近?”这个问题的正确答案。这种方法通常称为自监督,避免了任何手动标记监督信号的需要。这个想法最初是在神经语言建模任务中提出的,但 Word2Vec 是一个比神经网络语言模型简单得多的模型。


🧠 Skip-gram 的训练机制

Skip-gram 的直觉是将目标词 T 和相邻的上下文词 C 视为可以彼此靠近出现的单词的正例,然后随机采样词典中的其他单词作为负例,使用逻辑回归训练一个分类器来区分这两种情况,最后使用学习到的权重作为单词的嵌入表示。

让我们从思考分类任务开始,在下一讲中,我们将转向如何训练。想象一个像下面这样的句子,目标词是“apricot”,并假设我们使用一个正负两个词的上下文窗口。

我们的目标是训练一个分类器,使得给定一个目标词与候选上下文词(如“apricot”和“jam”,或者“apricot”和“aardvark”)的元组 (W, C),它能返回 C 是真实上下文词的概率(对“jam”为真,对“aardvark”为假)。因此,我们希望 P(+ | apricot, jam) 高,P(- | apricot, aardvark) 高。实际上,单词 C 不是 W 的真实上下文词的概率,就是 1 减去它是上下文词的概率。

Skip-gram 模型的直觉是基于嵌入相似性来计算这个概率。如果一个词的嵌入与目标嵌入相似,那么它很可能出现在目标词附近。

为了计算这些密集嵌入之间的相似性,我们依赖于这样的直觉:如果两个向量具有高的点积,那么它们是相似的。毕竟,余弦相似度只是归一化的点积。换句话说,词嵌入和上下文嵌入之间的相似性与 W·c 成正比。

我们需要将其归一化,以将这种相似性转化为概率。这是因为点积 C·W 或 W·C 不是一个概率,它只是一个范围从负无穷到正无穷的数字。由于 Word2Vec 中的元素可以是负数,点积也可以是负数。

因此,为了将点积转化为概率,我们将使用在逻辑回归中见过的逻辑或 sigmoid 函数 σ。因此,我们将单词 C 是目标词 W 的真实上下文词的概率建模为 σ(C·W),即 1 / (1 + exp(-C·W))。

现在,为了使这成为一个概率,我们还需要两个可能事件(C 是上下文词,C 不是上下文词)的总概率和为 1。因此,我们估计单词 C 不是 W 的真实上下文词的概率为 1 - P(+),即 1 / (1 + exp(C·W))(注意没有负号)。

我们刚刚看到的方程给出了一个单词的概率,但窗口中有许多上下文词。Skip-gram 做了一个简化的假设,即所有上下文词都是独立的,允许我们直接相乘它们的概率。因此,对于上下文窗口中的所有 L 个单词,我们只需相乘它们的概率,或者在 log 空间中,相加它们的对数概率。

总结一下,Skip-gram 训练一个概率分类器,给定一个目标词 W 及其包含 L 个单词的上下文窗口 C1 到 CL,根据上下文窗口与目标词的相似程度分配一个概率。这个概率基于将逻辑函数(sigmoid 函数)应用于目标词嵌入与每个上下文词嵌入的点积。

为了计算这个概率,我们只需要词汇表中每个目标词和上下文词的嵌入。


📊 模型参数与表示

以下是这些参数的直觉,我们将在下一讲中学习它们。Skip-gram 为每个单词存储两个嵌入:一个作为目标词的嵌入,另一个作为上下文词的嵌入。因此,我们需要学习的参数是两个矩阵:W 和 C,每个矩阵都包含词汇表中 V 个单词中每一个的嵌入。


🎓 总结

在本节课中,我们一起学习了 Word2Vec 方法,特别是 Skip-gram with Negative Sampling (SGNS) 模型。我们了解了词嵌入作为密集向量的优势,探讨了 Skip-gram 模型如何通过自监督学习,将目标词与上下文词的关系建模为一个概率分类问题,并利用词嵌入的点积与 sigmoid 函数来计算共现概率。我们还明确了模型需要学习目标词和上下文词两套嵌入参数。下一讲,我们将深入探讨如何训练这些权重。

课程 P53:L8.7 - 通过 Word2Vec 学习词嵌入 🧠

在本节课中,我们将要学习 Word2Vec 如何利用 Sigmoid 函数和梯度下降算法来学习词嵌入向量。我们将重点讲解 Skip-gram 模型配合负采样(SGNS)的训练过程,从随机初始化向量开始,逐步调整,使每个目标词的嵌入向量与其上下文词的嵌入向量更相似,而与随机噪声词的嵌入向量更不相似。

训练数据构建

上一节我们介绍了 Word2Vec 的基本目标,本节中我们来看看如何构建具体的训练数据。

首先考虑一个训练样本。这个例子有一个目标词 W(例如 “apricot”),以及一个大小为 L = ±2 的窗口内的四个上下文词,从而产生四个正例训练实例。例如:“apricot” 与 “tablespoon”、“apricot” 与 “of”、“apricot” 与 “in”、“apricot” 与 “jam”。

为了训练一个二元分类器,我们还需要负例。实际上,SGNS 使用的负例数量多于正例,其比例由一个参数 K 控制。

以下是构建训练实例的步骤:

  • 对于上述每一个正例,我们创建 K 个负例。
  • 每个负例由目标词 W 和一个噪声词组成。
  • 噪声词是从词典中随机抽取的,但确保它不是目标词 W
  • 噪声词根据加权的单字词频率进行选择。

例如,当 K=2 时,每个正例会对应两个负例。我们最终会得到四个正例和八个负例。

学习目标与损失函数

现在我们已经有了正例和负例训练实例集以及一组初始化的嵌入向量,学习算法的目标是调整这些嵌入向量。

其目标是最大化从正例中提取的(目标词,上下文词)对的相似度,同时最小化从负例中提取的(目标词,噪声词)对的相似度。

如果我们考虑一个目标词及其一个真实上下文词和 K 个噪声词,我们的目标是最大化目标词与真实上下文词的相似度,并最小化目标词与 K 个负采样噪声词的相似度。

我们可以将这两个目标表达为以下需要最小化的损失函数 L

L = -log[σ(c_pos · w)] - Σ_{i=1}^{K} log[σ(-c_neg_i · w)]

这个公式的含义是:

  • 第一项 -log[σ(c_pos · w)] 表示我们希望分类器为真实的上下文词 c_pos 分配一个较高的“是邻居”概率。
  • 第二项 - Σ log[σ(-c_neg_i · w)] 表示我们希望为每个噪声词 c_neg_i 分配一个较高的“不是邻居”概率。
  • 我们假设这些概率是独立的,因此使用连乘(在对数域中转化为求和)。

通过数学变换,我们可以将最大化正例对的点积、最小化负例对的点积这一目标表达得更清晰。最终,我们使用随机梯度下降来最小化这个损失函数,调整词向量的权重,使正例对更可能发生,负例对更不可能发生,并在整个训练集上重复此过程。

梯度下降的直观理解

让我们看看梯度下降一步的直观过程。

Skip-gram 模型试图移动嵌入向量的位置。对于目标词(例如 “apricot”)的嵌入向量,我们希望它更靠近(即具有更高的点积)其邻居词(例如 “jam”)的上下文嵌入向量。同时,我们希望 “apricot” 的嵌入向量远离噪声词(例如 “matrix” 和 “Tolstoy”)的上下文嵌入向量。

因此,在梯度下降的每一步,我们根据损失函数的梯度反方向移动权重。移动的距离由梯度值和学习率 η 共同决定,学习率越高,移动越快。

权重更新公式为:
W_{t+1} = W_t - η * ∇L

权重更新公式

以下是损失函数对各类嵌入向量求导后得到的非常简洁的更新公式。

对于正例上下文词向量 c_pos 的更新:
c_pos^{t+1} = c_pos^t - η * [σ(c_pos · w) - 1] * w

对于负例噪声词向量 c_neg_i 的更新:
c_neg_i^{t+1} = c_neg_i^t - η * [σ(c_neg_i · w) - 0] * w

对于目标词向量 w 的更新:
w^{t+1} = w^t - η * { [σ(c_pos · w) - 1] * c_pos + Σ_{i=1}^{K} [σ(c_neg_i · w) - 0] * c_neg_i }

在每种情况下,损失函数的导数不同,因此我们将以不同的幅度移动这些权重。

最终词向量的获取

正如我们所见,SGNS 模型为每个词学习两种独立的嵌入向量:目标嵌入 W 和上下文嵌入 C,分别存储在两个矩阵中。

但在我们各种 NLP 应用中,只需要一个向量来表示一个词。那么应该使用哪一个呢?最常见的做法是将它们相加。因此,我们通过将向量 w_ic_i 相加来获得词 i 的最终表示。

总结

本节课中我们一起学习了如何使用负采样的 Skip-gram 模型来学习 Word2Vec 词嵌入。

总结整个流程:

  1. 从维度为 D 的随机向量开始。
  2. 基于嵌入相似性训练一个分类器。
  3. 从语料库中获取共现的词对作为正例,随机组合不共现的词对作为负例。
  4. 通过缓慢调整所有嵌入向量以提高分类器性能的方式来训练该分类器。
  5. 训练完成后,丢弃分类器,仅保留学习到的嵌入向量。

我们看到了如何学习 Word2Vec 嵌入,这是最流行的静态嵌入方法之一。

课程 P54:L8.8 - 词嵌入性质 🧠

在本节课中,我们将要学习词嵌入模型的各种性质与参数,包括上下文窗口大小的影响、词嵌入如何捕捉类比关系、词义随时间的演变,以及词嵌入如何反映并量化文本中存在的隐性偏见。

上下文窗口大小的影响

上一节我们介绍了词嵌入的基本概念,本节中我们来看看影响词嵌入性质的一个重要参数:上下文窗口的大小。这个参数对于稀疏的 TF-IDF 向量和密集的 Word2Vec 向量都适用。

上下文窗口通常指目标词左右两侧 1 到 10 个词的范围。窗口大小的选择取决于表征的目标。较短的上下文窗口倾向于产生更具句法性质的表示,因为信息主要来自紧邻的词语。

以下是不同窗口大小对词相似度的影响:

  • 小窗口(如 ±2):计算出的向量中,与目标词最相似的词往往是词性相同、语义相似的词。
  • 大窗口(如 ±5):计算出的向量中,与目标词余弦相似度最高的词往往是主题相关但不一定相似的词。

例如,使用窗口大小为 ±2 的 Skip-gram 模型,与《哈利·波特》中的词 “hogwarts” 最相似的词是其他虚构学校的名称。而使用窗口大小为 ±5 时,与 “hogwarts” 最相似的词则是与《哈利·波特》系列主题相关的其他词,如 “Dumbledore” 或 “half-blood”。

捕捉类比关系的能力

词嵌入的一个重要语义特性是它们捕捉关系意义的能力。在一个早期的向量空间认知模型中,Rumelhart 和 Abramson 提出了解决 “A 之于 B 犹如 C 之于 ?” 这类简单类比问题的平行四边形模型

在该模型中,通过向量运算来寻找答案。例如,解决 “apple 之于 tree 犹如 grape 之于 ?” 这个问题,目标是找到 “vine”。其计算方法是:tree - apple + grape,得到的结果向量在空间中指向的词,我们希望就是 “vine”。

这种方法已被证明适用于稀疏和稠密嵌入。例如:

  • 公式king - man + woman ≈ queen
  • 公式Paris - France + Italy ≈ Rome

更一般地,对于问题 “A 之于 A* 犹如 B 之于 B”,目标是找到缺失的 B。我们计算嵌入空间中所有词到向量 (A* - A + B) 的距离,并返回距离最近(余弦相似度最高)的词。

在 GloVe 嵌入空间中,我们可以看到类似关于性别、家族姓氏和王室名称的类比关系。

不过,平行四边形方法有一些注意事项:它似乎只对高频词、非常小的距离以及某些特定关系(如国家与首都)有效,对其他关系则不然。理解这类类比仍然是一个开放的研究领域。

词义随时间的演变

词嵌入也可以作为一个有用的工具,通过计算不同历史时期文本构建的多个嵌入空间,来研究词义如何随时间变化。

例如,下图可视化了过去两个世纪中三个英语单词的词义变化。这是通过为每个十年(使用 Google Ngrams 或美国英语历史语料库等历史语料)构建独立的嵌入空间计算得出的。

在每个案例中,我们用粗体显示了一个历史时期和一个更现代时期的词义表示,并用浅灰色显示了在这些不同时期与该词义相近的词语。

以下是具体示例:

  • broadcast:在 1850 年代,它是一个农业词汇,意为“撒播种子”。其邻近词是 “seeds” 和 “scatter”。而现代含义则与 “radio” 或 “television” 最相似。

词嵌入与隐性偏见

除了从文本中学习词义的能力,词嵌入也会复现文本中潜在的隐性偏见和刻板印象。

正如我们刚才看到的,嵌入可以大致模拟关系相似性:JapanFrance - Paris + Tokyo 最接近的词。但这些相同的嵌入类比也表现出性别刻板印象。例如:

  • 嵌入模型暗示了类比:father : doctor :: mother : nurse
  • 在 Word2Vec 嵌入中,与 man - computer programmer + woman 计算结果最接近的职业是 homemaker

这可能导致 Crawford 所称的分配性危害,即系统将工作或信贷等资源不公平地分配给不同群体。例如,使用嵌入作为招聘潜在程序员或医生搜索算法的一部分,可能会错误地降低包含女性姓名的文档的权重。

历史嵌入也被用作衡量过去偏见的工具。例如,来自历史文本的嵌入可用于衡量形容词与各种种族或性别名称之间的关联度。

以下是具体发现:

  • 历史上,与能力相关的形容词(如 smart, wise, brilliant)的嵌入偏向于男性,但这种偏见自 1960 年代以来在缓慢减弱。

来自历史嵌入的证据也复制了旧时种族刻板印象调查的数据。例如,在 1930 年代,亚洲人名的嵌入偏向于非人化的形容词(如 barbaric, bizarre),这与 1930 年代美国的调查结果相符。但在整个 20 世纪,文本和调查中的这种偏见都在减少。

总结

本节课中我们一起学习了词嵌入的多种性质。我们探讨了上下文窗口大小如何影响词向量的句法与语义倾向,了解了词嵌入通过平行四边形模型捕捉类比关系的能力及其局限性。我们还看到了如何利用历时词嵌入研究词义演变,并重点讨论了词嵌入如何反映并量化其训练文本中所蕴含的社会文化偏见,这是应用词嵌入技术时需要谨慎对待的重要方面。

📚 课程 P55:L9.1 - 词性标注

在本节课中,我们将学习自然语言处理中的一项基础任务——词性标注。我们将了解词性的概念、分类,以及如何利用算法为文本中的每个单词自动分配正确的词性标签。

🧠 词性标注任务介绍

让我们介绍词性标注这项任务。从最早的语言学传统开始,无论是印度的梵语语法学家Yaska和Pāṇini,还是希腊的亚里士多德和斯多葛学派,都提出了单词可以划分为不同语法类别的思想。这些类别就是我们今天所说的词性、词类,或简称为POS标签。

到公元前100年,由Dionysius Thrax提出的一套包含八个词性的体系已经非常现代,包括:名词、动词、代词、介词、副词、连词、分词和冠词。这套八词性体系成为了此后2000年欧洲语言描述的基础。这些词性类别能够沿用数千年,说明了它们在人类语言处理模型中的核心地位。

📖 词性类别详解

上一节我们介绍了词性标注的起源,本节中我们来看看具体的词性类别。虽然词性类别确实有语义倾向(例如,形容词常描述属性,名词常描述人物),但词性的定义主要基于单词与相邻词的语法关系或其词缀的形态学属性。

词性主要分为两大类:封闭类词和开放类词。

以下是这两类词的主要特点:

  • 封闭类词:成员相对固定,例如介词。新的介词很少被创造出来。这类词通常是功能词,如 ofit,它们通常很短、出现频率高,在语法中起结构作用。
  • 开放类词:成员可以不断扩充,例如名词和动词。像 iPhoneto google 这样的新名词和动词在不断被创造或借用。

世界上的语言中主要有四种开放类词:名词(包括专有名词)、动词、形容词和副词,以及一个较小的开放类——感叹词。英语拥有全部五种,但并非所有语言都如此。

以下是主要词性类别的定义:

  • 名词:指代人、地点或事物的词,但也包括其他类别。普通名词包括具体术语如 catmango,以及抽象概念如 algorithmbeauty;专有名词如 JanetItaly;以及动词化的名词,如 his pacing to and fro became quite annoying 中的 pacing
  • 动词:指代动作和过程的词。包括开放类的主要动词如 eatwent,以及封闭类的助动词如 canhad。助动词用于标记主要动词的语义特征,如时态或体。
  • 形容词:描述名词属性或品质的开放类词,如颜色、年龄或价值。
  • 代词:一种封闭类功能词,作为指代实体或事件的简写。
  • 连词:连接两个短语或从句的功能词,如并列连词 andor,或从属连词,如 I thought that you might like some milk 中的 that

🎯 词性标注任务定义

词性标注是为文本中的每个单词分配一个词性的过程。因此,标注是一项消歧任务。单词具有歧义性,可能拥有多个可能的词性。目标是根据上下文找到正确的标签。

例如,单词 bookbook that flight 中是动词,在 hand me that book 中是名词。词性标注的任务就是在上下文中为每个单词决定正确的标签。

以下是词性标注的简要描述:

  • 输入:一个由分词后的单词组成的序列 X1XN,以及一个标签集(可能的标签列表)。
  • 输出:一个标签序列 y1yn,每个输出 yi 恰好对应某个输入 xi

这是“通用依存项目”使用的标签集,该项目为多种语言提供了句法树和词性标签数据。我们可以看到开放类标签(形容词、副词、名词、动词、专有名词、感叹词)和封闭类标签(如附置词——英语用介词,但有些语言用后置词;助词、连词等),以及标点符号和特殊符号的标签。

以下是一些带标签的例句。请注意,标注要求我们对分词做出一些决定,因此我们通常会将标点或撇号分开。例如,英语中的 ‘s 经常被切分为一个独立的助词。还要注意,同一个单词在不同情境下可能有不同的标签,例如 were 在这里是主要动词,而在 were reported 中是助动词。

💡 词性标注的应用与挑战

词性标注对自然语言处理任务非常有用,最显著的是在句法分析中,它长期扮演重要角色。同时,它也用于机器翻译、情感分析,甚至文本转语音系统。例如,lead 作为指代金属的名词时发音为 /lɛd/,但作为动词时发音为 /liːd/。或者 object 作为名词时发音为 /ˈɒbdʒɛkt/,但作为动词时发音为 /əbˈdʒɛkt/。标注对于语言分析计算任务也很重要,例如研究新词的创造或测量语义相似性/差异性,不同词性的单词在这些任务中表现不同。

词性标注有多难?在英语中,只有15%的词型是歧义的,意味着85%的词型是明确的。例如,Janet 总是专有名词,hesitantly 总是副词。但事实证明,那15%的歧义词型往往非常常见,出现频率远高于15%。因此,实际上,在连续文本中,大约60%的词例是歧义的。例如,单词 back 可以有五种词性标签:在 back seat 中是形容词,在 the back 中是名词,在 senators backing a bill 中是动词,在 buy back 中是助词,在 back then 中是副词。

现代词性标注器的准确度如何?准确度通过标注器为词例分配正确标签的百分比来衡量。对于英语,准确度相当高,大约在97%左右。对于英语和其他一些拥有足够手工标注训练数据且形态学相对简单的语言,标注基本上是一个已解决的问题。不同的算法——经典的如隐马尔可夫模型或条件随机场,或神经模型如BERT——表现相对类似。事实上,人类标注的准确度也大约是97%。当然,对于英语,基线本身就很高。所谓的“最频繁类别基线”就是简单地用训练集中每个单词最频繁出现的标签来标注它。对于未知词,我们将它们标注为名词。这个基线能达到约92%的准确率,仅仅因为,正如我们所看到的,许多单词没有歧义。

🔍 标注器的信息来源

词性标注器通常利用三种信息来源。让我们以消歧句子 Janet will back the bill 中的单词为例。

以下是标注器依赖的三种关键信息:

  1. 单词拥有某个标签的先验概率:例如,单词 will 通常是助动词,较少情况下是名词(如 someone of strong will)或动词(如 willing something to happen)。
  2. 相邻单词的身份:例如,单词 the 通常出现在形容词和名词之前,而不是动词之前。
  3. 单词本身的构成、形态或词形:例如,前缀 un- 通常表示形容词,后缀 -ly 几乎总是副词。大写是一个强烈的暗示,表明一个单词是专有名词。

词性标注可以通过经典的监督机器学习算法(如隐马尔可夫模型或条件随机场)完成,也可以通过神经模型(无论是从头训练的神经序列模型,还是经过微调的大语言模型)完成。所有这些都需要一个手工标注的数据集,其中人类为每个单词标记了正确的词性标签。对于拥有足够训练数据的英语,所有这些方法都能达到大致相当的性能。所有这些算法都利用了我们刚刚讨论的信息来源:隐马尔可夫模型和条件随机场通过人工创建的特征,而神经模型则通过表示学习来归纳这些特征。

📝 总结

本节课中,我们一起学习了词性标注。我们介绍了词性标签的概念,并从高层次概述了词性标注的思想。我们了解了开放类词和封闭类词的区别,明确了词性标注是一项为上下文中的歧义单词选择正确标签的任务。我们还探讨了标注的挑战、现代标注器的高准确度,以及标注器做出决策时所依赖的三种核心信息来源:单词本身的统计特性、上下文邻居单词以及单词的形态学特征。

课程 P56:L9.2 - 实体识别 👁️🗨️

在本节课中,我们将要学习自然语言处理中的一项重要任务——命名实体识别。我们将了解什么是命名实体,为什么识别它们很重要,以及如何通过序列标注技术来实现这一目标。


现在,我们来介绍命名实体识别这项任务。

专有名词是另一个重要且被长期研究的语言学范畴。

词性标注通常是为单个单词或语素分配标签。

而一个专有名词通常是一个完整的、由多个词组成的短语,例如人名 Marie Curie,地名 New York City,或机构名 Stanford University

词性标注可以告诉我们像 MarieStanfordColorado 这样的词都是专有名词,拥有专有名词这一语法属性。

但从语义角度来看,这些名词指代的是不同类型的实体。Marie Curie 是一个人,Stanford 是一个组织,Colorado 是一个地点。

粗略地说,命名实体就是任何可以用专有名称来指代的事物。最常见的实体标签是:人物地点组织地缘政治实体

然而,“命名实体”这个术语通常被扩展,以包含那些本身并非严格意义上“实体”的事物,包括日期时间、其他类型的时间表达式,甚至像价格这样的数字表达式。

命名实体识别的任务是找出构成专有名称的文本片段,然后标注实体的类型。

以下是一段文本,展示了命名实体识别的示例输出。你会看到13个命名实体的提及,包括五个组织、四个地点、两个时间、一个人和一个货币金额。

命名实体标注是许多自然语言理解任务中有用的第一步。

在情感分析中,我们可能想知道消费者对特定实体的情感倾向。

实体是问答任务或链接文本到结构化知识源(如维基百科)的第一个有用阶段。手工命名实体标注对于构建语义表示的自然语言理解任务也至关重要。

与词性标注不同(每个词都有一个标签,不存在分割问题),命名实体识别的任务是找到并标注文本片段,这很困难,部分原因在于分割的模糊性。我们需要决定什么是一个实体、什么不是,以及边界在哪里。事实上,文本中的大多数词都不是命名实体。

另一个困难是由类型模糊性造成的。例如,名称 Washington 可以指教育家 Booker T. Washington,或一支运动队,或一个地点,或一个地缘政治实体。

针对像命名实体识别这样的片段识别问题,标准的序列标注方法称为 BIO 标注。BIO 标注是一种方法,它允许我们通过捕获边界和命名实体类型的标签,将命名实体识别视为逐词的序列标注任务。

让我们看下面这个句子:

Jane Villanueva of United, a unit of United Airlines Holdings, said the fare applies to the Chicago route.

以下是该句子的命名实体标注结果。

现在,我们垂直展示同一个句子,并附上 BIO 标注。请注意,我们有对应人物、组织和地点的标签,并且每个词元都有一个标签。

BIO 标注的工作原理如下:我们用标签 B 来标注任何开始一个目标片段的词元。出现在片段内部的词元用 I 标注。任何片段之外的词元都用 O 标注。虽然只有一个 O 标签,但每个命名实体类别都有不同的 BI 标签。

因此,标签总数是 2n + 1,其中 n 是实体类型的数量。你可以看到我们有 B-PER(开始-人物)、B-ORG(开始-组织)、B-LOC(开始-地点)、I-PER(内部-人物)、I-ORG(内部-组织),以及一个 O 标签用于其他所有词。

BIO 标注有一些变体,例如没有 B 标签的 IO 标注(即每个实体类型只有一个 I 标签和一个 O 标签),或者更复杂的标签方案,其中包含开始标签、结束标签和内部标签,或者为单字实体设置特殊的单例标签。

与词性标注类似,命名实体识别是通过监督式机器学习完成的。我们需要一个由人工标注的训练集,其中一些文本被标记了命名实体标签。

我们可以使用经典的机器学习算法,如隐马尔可夫模型条件随机场,或者使用经过微调的神经模型,如序列模型大语言模型


本节课中,我们一起学习了命名实体标注的概念,这是许多自然语言处理任务中的重要第一步。我们了解了命名实体的定义、识别它们的挑战,以及如何使用 BIO 标注等序列标注技术来解决这些问题。

神经网络课程 P57:L10.1 - 神经网络单元 🧠

在本节课中,我们将学习构成神经网络的基本计算单元。我们将了解其起源、现代定义、核心计算过程,并通过一个具体例子来加深理解。最后,我们会介绍几种常见的非线性激活函数。

神经网络的起源与现代定义

神经网络之所以被称为“神经”,源于其起源与麦卡洛克-皮茨神经元模型有关。该模型是1943年提出的人体神经元的简化模型,被描述为一种基于命题逻辑的计算元件。

然而,现代神经网络在语言处理等领域的应用,已不再借鉴这些早期的生物学灵感。“神经”这个名称更多是一个历史遗留。

现代神经网络是由许多小型计算单元组成的网络。每个单元接收一个输入值向量,并产生一个单一的输出值。在接下来的几讲中,我们将介绍这些单元,了解它们如何连接在一起形成前馈网络。之所以称为“前馈”,是因为计算过程是从一层单元迭代地推进到下一层。

现代神经网络的应用常被称为“深度学习”,因为现代网络通常很深,意味着它们拥有很多层。但神经网络的基本构建块,是单个的计算单元。

神经网络单元的核心计算

一个单元接收一组实数值作为输入,对它们进行一些计算,结合权重、偏置和一个非线性变换,最终产生一个输出。

让我们更详细地看看这个过程。神经网络单元的核心是计算其输入的加权和,再加上一个称为偏置项的额外项。

给定一组输入 x1xN,一个单元有一组对应的权重 W1WN 和一个偏置 B。因此,加权和 Z 可以表示为:

公式: Z = B + Σ (Wi * xi),其中 i 从 1 到 N。

通常,使用向量表示法来表达这个加权和更为方便。因此,标量 Z 由权重向量和输入向量的点积加上标量偏置 B 计算得出:

公式: Z = W · X + B

最后,神经网络不会直接使用 Z(即 X 的线性函数)作为输出,而是会对 Z 应用一个非线性函数 F。我们将此函数的输出称为该单元的激活值 a(activation)。

由于我们只模拟单个单元,这个激活值 a 实际上就是网络的最终输出,我们通常称之为 yŷ

激活函数示例:Sigmoid

我们已经见过逻辑回归中使用的 Sigmoid 函数:1 / (1 + e^(-Z))。Sigmoid 函数有一些优点:它将输入映射到 (0, 1) 的范围内,这有助于将极端值压缩趋近于 0 或 1;同时它是可微的,这对于学习过程非常方便。

因此,在本讲中,我们将使用 Sigmoid 非线性激活函数作为例子,尽管我们很快会看到还有其他类型的非线性函数。

综上所述,一个使用 Sigmoid 激活函数的简单单元所计算的函数如下,它接收输入 X、权重向量 W 和偏置项 B

公式: y = σ(W · X + B),其中 σ 代表 Sigmoid 函数。

下图再次展示了这个单元的各个部分:输入、权重、加权和、非线性激活以及输出值。

计算实例

让我们通过一个例子来获得直观感受。

假设我们有一个单元,其权重向量包含三个权重值和一个偏置:
W = [0.2, 0.3, 0.9], B = 0.5

假设输入向量为:
X = [0.5, 0.6, 0.1]

现在我们来计算 Sigmoid 输出。我们需要计算 1 / (1 + exp(- (W·X + B)))

首先写出 W·X + B 的计算过程:
0.5 * 0.2 + 0.6 * 0.3 + 0.1 * 0.9 + 0.5

计算结果约为 0.87。因此,对于这个特定输入,Sigmoid 输出将是:
y = 1 / (1 + exp(-0.87)) ≈ 0.7

其他常见的激活函数

上一节我们通过 Sigmoid 函数进行了计算,但在实践中,Sigmoid 并不常用作激活函数。

一个非常相似但几乎总是更好的函数是 Tanh 函数,它是 Sigmoid 的一个变体,输出范围在 (-1, +1) 之间。

公式: tanh(z) = (e^z - e^(-z)) / (e^z + e^(-z))

Tanh 函数具有平滑可微的性质,并能将极端值映射向均值。

然而,最简单且可能最常用的激活函数是修正线性单元,也称为 ReLU。它的定义是:当 Z 为正时,输出等于 Z;否则输出为 0。

公式/代码: ReLU(z) = max(0, z)

与 Sigmoid 或 Tanh 函数相比,ReLU 函数具有很好的特性。对于 Sigmoid 或 Tanh,Z 值非常高会导致 y 值饱和,即极其接近 1 且导数非常接近 0。接近 0 的导数会导致学习问题,因为梯度会变得越来越小,直到小到无法用于训练,这个问题被称为“梯度消失问题”。而 ReLU 没有这个问题,因为对于较高的 Z 值,其导数恒为 1,而不是接近 0。

总结

本节课中,我们一起学习了构成现代神经网络基础的基本神经计算单元。我们了解了它的历史背景与现代定义,深入探讨了其核心计算过程——加权和与非线性激活,并通过一个具体实例演示了计算。最后,我们比较了 Sigmoid、Tanh 和 ReLU 这几种常见的激活函数及其特性,特别是 ReLU 在解决梯度消失问题上的优势。

神经网络课程 P58:L10.2 - 异或问题 🔍

在本节课中,我们将要学习神经网络如何通过组合多个单元来解决单层网络无法处理的问题,特别是经典的“异或”逻辑函数。我们将从感知机的局限性开始,逐步展示多层网络如何通过构建有效的内部表示来解决这一问题。


神经网络的强大能力源于将这些基本单元组合成更大的网络。

1969年,Minsky和Papper的证明巧妙地展示了多层网络的必要性,他们证明了单一计算单元无法计算其输入的一些非常简单的函数。

考虑计算两个输入的基本逻辑函数的任务,例如“与”、“或”和“异或”。作为回顾,以下是这些函数的真值表:0与0是0,0与1是0,1与1是1,依此类推。

这个例子最初是针对感知机展示的。感知机是一种非常简单的神经单元,具有二进制输出,并且没有非线性激活函数。感知机的输出Y是0或1,计算方式如下。

使用我们刚刚见过的相同W、X和B,如果 w·x + b <= 0,感知机将输出0;如果 w·x + b > 0,它将输出1。

构建一个能够计算其二进制输入的“与”或“或”逻辑函数的感知机非常容易。

在左侧,我们有一个“逻辑与”的例子。权重W1=1,W2=1,偏置B=-1。“逻辑或”的权重略有不同。这组权重和偏置只是实现这些函数的无限多组可能权重和偏置中的一组。

让我们看看输入为1和0时会发生什么。如果x1=1,x2=0,那么我们有11 + 01 = 1,然后加上偏置B=-1,结果等于0。确实,对于“与”运算,我们得到了正确答案0。现在看看输入1和1的情况。x1=1,x2=1,所以有11 + 11 -1 = 1。对于“或”运算,输入0和1,我们有01 + 11 + 0 = 1,这正确地给出了1。

然而,使用感知机无法计算“异或”函数。你应该暂停视频,自己尝试思考一下。为什么不可能?

关键在于,感知机是一个针对二维输入X1和X2的线性分类器。感知机方程 W1*x1 + W2*x2 + b = 0 是一条直线的方程。我们可以通过将其转换为标准线性格式 x2 = -(W1/W2)*x1 - b/W2 来看到这一点,这里有一个斜率。这条直线在二维空间中充当决策边界,将直线一侧的所有输入点分配输出0,另一侧的所有输入点分配输出1。

如果我们有超过两个输入,决策边界就变成了一个超平面而不是一条直线,但思想是相同的,即将空间分成两类。让我们看一张图。

这些图展示了可能的逻辑输入点(0,0),(0,1),(1,0),(1,1),以及由一组可能的参数为“与”分类器和“或”分类器绘制的直线。

请注意,对于“异或”函数,根本不可能画出一条直线来分隔“异或”的正例(0,1)和(1,0)与负例(0,0)和(1,1)。我们说“异或”不是一个线性可分的函数。当然,我们可以用曲线或其他函数画边界,但不能用单一直线。


上一节我们看到了单层感知机的局限性,本节中我们来看看如何使用两层基于ReLU的单元来计算“异或”。

中间层称为H,有两个单元;输出层称为Y,有一个单元。以下这组权重和偏置将计算“异或”。

考虑输入x = (0,0)。如果我们将每个输入值乘以相应的权重,求和,然后加上偏置,我们得到:

  • H1 = ReLU(01 + 01 + 0) = ReLU(0) = 0
  • H2 = ReLU(01 + 01 + (-1)) = ReLU(-1) = 0
    因此,H = (0,0)。然后,y = 01 + 0(-2) + 0 = 0。

我建议你暂停视频,计算剩余可能的输入对,看看得到的y值:对于输入(0,1)和(1,0)是1,而对于(0,0)和(1,1)仍然是0。

观察那些中间结果——两个隐藏节点H1和H2的输出——也很有启发性。这是原始的x空间,其中“异或”在(0,1)或(1,0)时应返回1。现在让我们看看相同四个输入下H层的值。

请注意,现在输入点x=(0,1)和x=(1,0)(这两个“异或”输出应为1的情况)的隐藏表示被合并成了一个点。对于这两种情况,隐藏值都是(1,0)。这种合并使得线性分离“异或”的正例和负例变得容易。下图展示了一条可以实现这一点的示例直线。

换句话说,我们可以将网络的隐藏层视为形成了输入的有用表示。在这个例子中,我们只是规定了权重,但对于真实例子,神经网络的权重是使用反向传播算法自动学习的。这意味着隐藏层将学会形成有用的表示。神经网络能够自动学习输入的有用表示,这种直觉是其关键优势之一。


本节课中我们一起学习了为什么单个单元无法计算“异或”,以及具有非线性的多层网络如何能够学习到使解决此问题成为可能的表示。

课程 P59:L10.3 - 前馈神经网络 🧠

在本节课中,我们将要学习前馈神经网络的基本概念。这是深度学习中最基础但至关重要的架构,也是理解更复杂模型(如循环神经网络和Transformer)的基石。

概述 📋

这里介绍简单的前馈神经网络。尽管自然语言处理领域使用了更多复杂的架构,例如循环神经网络、Transformer和注意力机制,但前馈神经网络仍然是一个重要的工具,并且是这些更复杂架构的基础。

一个前馈神经网络是一个多层网络,其中的单元以无环的方式连接。每一层中单元的输出被传递到下一更高层,并且没有输出被传递回更低的层。

网络的基本构成 🏗️

由于历史原因,前馈神经网络也可以被称为多层感知机或MLP,尽管它们在技术上不再由感知机构成。

简单的前馈神经网络有三种节点:输入单元、隐藏单元和输出单元。

从逻辑回归到神经网络

让我们回顾逻辑回归。我们可以将二元逻辑回归视为一个单层网络。当我们计算层数时,不计算输入层,因此我们称输入层为第0层,所以这是一个单层网络。

我们有一个输入层(可称为第0层),它由一个向量 X 组成。我们将 X 乘以一个权重矩阵 W,加上一个标量 b,然后第一层(即输出层)通过应用Sigmoid函数到 WX + b 来计算一个标量 y

因此,简单的逻辑回归可以看作一个网络。请记住,多项逻辑回归在输出端不是一个单一的Sigmoid,而是一个将输出值转换为概率的Softmax函数。

以下是逻辑回归作为网络的表示:

  • 输入:向量 X
  • 计算z = WX + b
  • 输出y = σ(z) (对于二元分类)或 y = softmax(z) (对于多元分类)

在多项逻辑回归中,W 从一个简单的向量变成了一个矩阵,b 从一个标量变成了一个向量。现在我们用权重矩阵 W 乘以输入 X,加上向量 b,以产生一个关于可能结果的向量。

请记住,Softmax是Sigmoid的推广。对于一个向量 z,Softmax将每个元素 z_i 替换为 exp(z_i) / Σ_j exp(z_j)。例如,如果我们有一个输入向量 z(只是一组分数,有正有负),通过Softmax运行它将产生一组总和为1的概率。因此,Softmax是一个非常方便的输出函数。

引入隐藏层 💡

逻辑回归是一个单层网络,但神经网络的威力在于我们拥有隐藏层时,即至少是两层网络。

这是一个两层网络:输入层(第0层)、隐藏层(第1层)和输出层(第2层)。

以下是具有单个隐藏层的前馈网络的方程:它接受输入 X,乘以权重矩阵 W 并加上偏置 b。权重矩阵 W 的每个元素 W_{j,i} 表示从第 i 个输入单元 x_i 到第 j 个隐藏单元 h_j 的连接权重。

我们在这里将激活函数表示为 σ,因此隐藏单元是 σ(Wx + b),但激活函数也可以是ReLU或tanh,正如我们之前讨论过的。注意,h 是一个向量,我们将 σ 应用于一个向量(Wx 是一个向量)。因此,我们可以将像 σ 这样的函数应用于单个值或按元素应用于向量。

正如我们在关于表示的讲座中所看到的,得到的隐藏值 h 形成了输入的一种表示。现在,输出层的作用是获取这个新的表示 h 并计算最终输出。这个输出可以是一个实数值。但在许多情况下,网络的目标是做出某种分类决策,因此我们将专注于分类的情况。

输出层与分类 🎯

如果我们正在进行二元任务(如情感分类),我们可能只有一个输出节点。值 y 是例如积极与消极情感的概率。

因此,输出层有另一个权重矩阵 U。许多模型在输出层不包括偏置向量 b,因此我们通过消除偏置项来简化示例。权重矩阵 U 乘以其输入向量(即向量 h)以产生中间输出 z。然后我们使用Sigmoid将其转换为概率。

如果你有超过两个输出类别怎么办?例如,我们正在进行多项分类,如分配词性标签。我们只需添加更多的输出单元,每个类别一个。因此,我们可能为每个潜在的词性设置一个输出单元,输出值将是该词性的概率。所以输出层给出了跨输出节点的概率分布,我们使用Softmax来计算这个分布,就像多项逻辑回归一样。

这意味着我们可以将具有一个隐藏层的神经网络分类器视为构建一个向量 h(这是输入的隐藏层表示),然后在该网络在 h 中开发的特征上运行标准的逻辑回归。相比之下,当我们介绍逻辑回归时,特征主要是通过特征模板手工设计的。

因此,神经网络就像逻辑回归,但具有许多层,因为深度神经网络就像一层又一层的逻辑回归分类器。并且,不是通过手写的特征模板形成特征,而是网络的前几层自己诱导出特征表示。

通用符号与深层网络 📝

现在让我们建立一些符号,以便更容易地讨论深度超过两层的更深网络。

我们将使用方括号中的上标来表示层号,从输入层的0开始。因此,W[1] 将表示第一个隐藏层的权重矩阵,b[2] 将表示第一个隐藏层的偏置向量。我们将使用 g 代表激活函数,中间层倾向于使用ReLU,输出层使用Softmax。

我们将使用 a[3] 表示第 i 层的输出,z[4] 表示权重和偏置的组合。输入将更一般地称为 a[5]

因此,我们可以用右边的方程重写我们的两层网络。以下是它们一起写出来的形式:

z^[1] = W^[1] * a^[0] + b^[1]
a^[1] = g^[1](z^[1])
z^[2] = W^[2] * a^[1] + b^[2]
a^[2] = g^[2](z^[2]) = y_hat

注意,在这种符号下,每一层(第1层和第2层)的计算方程是相同的。

因此,在给定输入向量 a[6] 的情况下,计算n层前馈网络中前向传播步骤的算法很简单:遍历各层,计算 z = W * a_prev + b(其中 a_prev 是前一层的输入),然后当前层的输出是 a = g(z),最后,我们的估计值 y_hat 是最后一层的输出。

简化符号(含偏置节点)⚙️

在描述网络时,我们经常使用一个稍微简化的符号,它表示完全相同的函数,但不引用显式的偏置节点 b。我们将通过向每一层添加一个虚拟节点 a_0 来实现这一点,其值始终为1。因此,第0层(输入层)将有一个虚拟节点 a_0^[0],值为1;第1层将有一个 a_0^[1],值为1,依此类推。与这个虚拟节点相关的权重代表了偏置值 b

因此,我们的输入不再是 x_1x_{n0}n0 是第0层的单元数),而是会添加一个额外的单元 x_0。我们不再讨论 σ(Wx + b),而是讨论 σ(Wx)。但如果我们分解这个计算,Wx 将是从0到 n0 的权重乘以输入值的和(因为有了这个额外的偏置值),而不是从1开始。

因此,我们不再有一个额外的偏置项 b,而是简单地添加我们的值 x_0,并将 b 纳入权重矩阵 W 中。在接下来的几节课中,我们将继续在示例中展示偏置 b,但之后我们将切换到没有显式偏置项的简化符号。

总结 ✨

本节课中,我们一起学习了前馈神经网络,这是最基本的神经网络架构。我们了解了其基本构成,包括输入层、隐藏层和输出层,并学习了如何从逻辑回归的角度理解它。我们探讨了激活函数(如Sigmoid、Softmax和ReLU)的作用,以及网络如何通过隐藏层自动学习特征表示。最后,我们介绍了描述深层网络的通用符号和一种包含偏置的简化表示方法。掌握这些基础知识是理解更复杂深度学习模型的关键。

📚 课程 P6:L1.6 - 词归一化与其他相关议题

在本节课中,我们将要学习如何将文本中的词汇转换为标准格式,这个过程称为词归一化。我们还将讨论句子分割,即将文本语料库分解为更大的话语单元,如句子或段落。


🔤 词归一化概述

词归一化是将词汇置于标准格式的过程,这需要我们做出一些决策。例如,我们应该将“USA”表示为“U.S.A.”还是“USA”?我们需要选择一个标准格式,之后无论遇到哪种形式,都将其映射为我们决定的标准形式。

许多类似的决策需要处理。例如,在转录语音时,如果有人说“a”,这个单词是否应该包含连字符?此外,我们还需要考虑词元。我们之前讨论过词形(如“am”、“are”)和词元(如“be”)。何时应该保留词形,何时又应该进行词形还原?


🔠 大小写折叠

我们还需要决定是否进行大小写折叠。例如,在信息检索中,将所有字母转换为小写是非常常见的做法。用户在搜索引擎中输入的查询通常使用小写。因此,如果我们要将用户的输入与搜索的语料库进行匹配,将其映射为小写是安全的。

一个可能的例外是,如果用户在句子中间输入了大写字母,我们可能希望利用这些信息来帮助决定如何搜索他们寻找的文本。但对于大多数自然语言处理应用,如情感分析、信息提取和机器翻译,大小写确实很重要。例如,在英语中,大写的“US”与小写的“us”含义截然不同。


📖 词形还原

另一个归一化决策是是否进行词形还原。词元是一个词的共享词根,也对应于字典中的词目。例如,我们可能将“am”、“are”映射为词目“be”,或将“car’s”映射为单词“car”。在像西班牙语这样比英语有更多屈折变化的语言中,例如“quiero”(我想要)或“quieres”(你想要),我们会将其映射为不定式形式“querer”,这就是词元。

对一个句子进行词形还原,例如“He is reading detective stories.”,我们会得到“He be read detective story.”。这样,我们去除了复数形式,并将“reading”还原为“read”等。

词形还原是通过形态分析完成的。形态素是构成词的最小意义单位,我们通常区分两种形态素:词干(承载核心意义的单位)和词缀(附加在词干上,通常具有某种语法功能的部分)。形态分析器的任务是将一个词分解为形态素。例如,“cats”有词干“cat”和词缀“-s”。分析器的任务就是将“cats”分解为“cat”和“-s”。更复杂的情况,如西班牙语“amarían”(他们将会爱),我们会将其分解为词干“am-”和表示第三人称复数、将来时虚拟式的形态特征。


✂️ 词干提取

词干提取是词形还原的一种简化形式。在词干提取中,我们不是映射到真正的形态词元,而是简单地、粗略地截断词缀。这种方法具有简单性的优势。

例如,如果我们有以下句子作为输入:

This was not the map we found in Billy Bones’s chest, but an accurate copy.

运行词干提取器后,我们会得到非常粗糙的输出。它做了一些有用的处理,例如,它将“heights”去掉了“s”得到“height”,这是一个很好的词形还原操作。它将“soundings”变成了“sound”,去掉了“-ings”。但它也去掉了“was”上的“s”,这就不太有用了。它还将所有结尾的“y”改成了“i”,并去掉了所有“-ate”结尾,所以“accurate”变成了“accur”。同样,“exception”变成了“except”。结果是,我们以牺牲精确度为代价提高了召回率。我们可能会找到更多我们正在寻找的单词,但也可能会找到一些我们可能并不寻找的单词。

词干提取的一个标准算法称为波特词干提取器。波特词干提取器非常简单,基于一系列按顺序运行的改写规则。例如,它会有这样的规则:将“-ational”变为“-ate”,或移除“-ing”,但仅当前面有元音时。所以输入“motoring”,如果我们在这里看到一个元音如“o”,现在我们可以移除“-ing”。或者,将“-sses”变为“-ss”。所以对于像“grasses”这样的词,可以移除“-es”。

然而,对于许多语言,这些简单的方法并不适用。许多语言具有非常复杂的形态。例如,土耳其语是一种具有黏着性形态的语言,由许多形态素构成非常长的单词。著名的例子是土耳其语单词,意思是“表现得好像你是我们无法文明化的那些人之一”。它包含“文明化”、“导致”、“不能”、“过去时”、“复数”等形态素。因此,处理具有复杂形态的语言需要更丰富的形态分析算法。


🔗 句子分割

最后,我们通常希望将文本分解为更大的话语块,如句子。在英语句子分割中,通常可以仅依靠结尾标点来做很多工作。如果你看到一个感叹号或问号,很可能就是句子的结尾。然而,句号则有些歧义。句号可以表示句子边界,但也可以是缩写的一部分,如“Inc.”或“Dr.”,或者数字中的小数点。

一个常见的算法是首先进行分词。我们将使用规则或构建一个机器学习分类器,将句号分类为单词的一部分(如“Dr.”或“Inc.”或数字中的句点)或句子边界。我们的分类器做出这个决策,为此,拥有一个缩写词典可能会有所帮助。如果我们有该语言中像“Inc.”和“Dr.”这样的单词列表,那将对分类决策有用。

然后,基于这个更复杂的分词任务,我们根据非常简单的规则进行句子分割。


📝 总结

在本节课中,我们一起学习了词归一化与句子分割。词归一化是将词汇转换为标准格式的关键步骤,涉及大小写折叠、词形还原和词干提取等决策。句子分割则是将文本分解为有意义的句子单元,通常依赖于标点符号和上下文信息。这些步骤是文本处理的重要基础,为后续的自然语言处理任务(如信息检索、情感分析和机器翻译)做好准备。

📚 课程 P60:L10.4 - 基于前馈神经网络的 NLP 问题方案

在本节课中,我们将学习如何将简单的前馈神经网络应用于自然语言处理任务。我们将探讨两个简化的示例任务:文本分类(如情感分类)和语言建模。虽然目前最先进的神经网络系统使用更强大的架构,但今天我们将要了解的简单前馈网络模型,对于理解基础概念非常有用。

🧠 情感分析:从逻辑回归到神经网络

上一节我们介绍了前馈神经网络的基本概念,本节中我们来看看如何将其应用于情感分析任务。

我们可以构建一个神经网络,其功能与逻辑回归完全相同。输入层可以采用与逻辑回归相同的二元特征。输出层通过一个 Sigmoid 函数输出一个 0 或 1 的值。唯一的区别在于中间增加了一个额外的隐藏层。

以下是我们可以使用的一些特征示例:

  • 词典中积极词的计数。
  • 词典中消极词的计数。
  • 文档中是否包含“不”这个词。

从架构上看,我们的逻辑回归模型包含一组特征、一个 Sigmoid 函数和权重。我们讨论的是增加一个额外的隐藏层。因此,我们现在有两组权重矩阵 WU,但使用相同的 Sigmoid 函数和相同的特征。

仅仅在逻辑回归中添加这个隐藏层,就允许网络对特征之间的交互进行建模。特征 X1 和特征 X2 在这个节点中的组合方式,可以与在另一个节点中的组合方式不同,从而以多种方式相互作用。

这赋予了网络更强的能力,可能会提升性能。

🔍 深度学习的真正力量:从数据中学习特征

然而,深度学习的真正力量来自于从数据中学习特征的能力。因此,我们将不再使用人工构建的特征进行分类,而是使用学习到的表示,例如我们在之前课程中见过的词嵌入。

以下是具体思路:假设我们有三个输入单词“the dessert is”。我们查找每个单词的嵌入向量。例如,单词“the”的索引是 534,我们查找其嵌入向量。同样地,我们查找“dessert”和“is”的嵌入向量。现在,这三个嵌入向量就构成了我们的输入层。

接着,我们有一个权重矩阵 W。我们将嵌入向量值与 W 矩阵相乘,得到隐藏层的值。然后,我们通过另一个权重矩阵 U 进行线性变换,最后通过 Sigmoid 函数输出,估计出从这三个单词“the dessert is”中得出积极情感的概率。

⚠️ 处理可变长度输入的问题

这看起来很好,但存在一个大问题:它假设输入总是三个单词,这不太现实。我们将在后续课程中看到这个问题的完整解决方案,但这里有一些简单的解决方法。

以下是两种简单的解决方案:

  • 固定长度输入:我们可以将输入长度固定为最长评论的长度。如果评论较短,我们就用零向量进行填充。如果在测试时遇到更长的评论,我们直接将其截断。
  • 创建句子嵌入:我们可以创建一个单一维度的句子嵌入向量来表示评论中的所有单词。这可以通过两种常见方式实现:
    • 取平均值:简单地取所有词嵌入向量的质心(均值),创建一个类似于所有单词平均值的句子嵌入。
    • 取最大值:通过取所有词嵌入向量在每个维度上的最大值,来创建这个单一的句子嵌入向量。

📊 扩展到多类别分类

如果我们希望输出类别超过两个怎么办?正如我们在逻辑回归中看到的,我们可以简单地添加更多的输出单元,每个类别一个,并使用一个 Softmax 层。这样,我们就可以输出积极、消极或中性情感,甚至是五种情感值。

🔄 转向语言建模任务

现在,让我们转向语言建模任务。回想一下,语言建模的任务是计算给定一段历史序列后,下一个单词出现的概率。我们已经见过基于 N-gram 的语言模型。事实证明,神经网络语言模型的表现远超 N-gram 语言模型。

目前最先进的神经语言模型基于更强大的技术,如 Transformer。但简单的前馈语言模型也能做得几乎一样好,让我们看看是如何实现的。

🪟 滑动窗口方法

回想一下,语言模型的任务是预测下一个单词 w_t,给定前面的单词 w_{t-1}, w_{t-2}, w_{t-3} 等等。这当然会导致一个问题:我们处理的是任意长度的序列。

对于前馈语言模型,一个简单的解决方案是使用固定长度的滑动窗口。通过这样做,我们做出了与 N-gram 语言模型中相同的简化:用给定有限、固定数量的前序单词的概率,来近似给定整个前序单词序列的概率。

这是一个简化的前馈神经语言模型示意图(n=3)。在时间 t,我们有一个移动窗口,其中包含代表前三个单词的嵌入向量,就像我们在情感分析中看到的那样。这些单词是 w_{t-3}, w_{t-2}w_{t-1}。它们被连接在一起以产生输入层 x

在网络的输出端,我们有一个 Softmax 层,它给出了一个关于所有单词的概率分布。例如,y_42,即输出节点 42 的值,就是下一个单词 w_t 是词汇表中索引为 42 的单词(恰好是单词“fish”)的概率。

因此,给定前三个单词,这个网络将给出所有可能的下一个单词的概率分布。然后,我们只需将窗口移动一个标记,并以同样的方式预测下一个单词。

🐱 为何神经语言模型更优:泛化能力

神经语言模型比简单的 N-gram 语言模型效果好得多,这里有一个例子可以说明原因。假设我们在训练数据中见过句子“I have to make sure the cat gets fed”。而碰巧我们从未见过三元组“dog gets fed”。

现在假设测试数据是“I forgot to make sure the dog gets”。我们需要预测下一个单词。

一个 N-gram 语言模型不会在“gets”之后预测“fed”,因为它从未见过“fed”出现在“gets”之后。即使我们使用某种平滑技术,“fed”在“gets”之后也不会是一个特别可能的单词。

但是,神经语言模型可以利用“cat”和“dog”的相似性。这些单词是相似的,它们的嵌入向量也是相似的。因此,模型可以将训练中见过的“cat”之后出现“fed”的模式,泛化到预测“dog”之后出现“fed”。

📝 总结

本节课中,我们一起学习了简单的前馈神经网络如何应用于各种 NLP 任务。我们探讨了其在情感分析中的应用,包括如何处理可变长度输入和扩展到多类别分类。接着,我们了解了如何利用滑动窗口方法构建前馈神经语言模型,并理解了其通过词嵌入的相似性实现强大泛化能力的优势。这些基础模型为我们理解更复杂的神经网络架构奠定了重要的基础。

神经网络课程 P61:L10.5 - 训练神经网络 🧠

在本节课中,我们将要学习如何训练神经网络。我们将从概述开始,然后深入探讨前向传播计算损失与反向传播更新权重的核心过程,并回顾逻辑回归中的梯度下降原理,为理解更复杂的多层网络训练打下基础。

神经网络训练概述

神经网络训练的直观理解包含两个核心步骤:前向计算损失和反向计算权重更新。

给定一个输入 X,我们通过网络进行一次前向传播,计算出系统的输出 Ŷ

然后,我们将 Ŷ 与真实答案 Y 进行比较,得到该样本的损失值。

接着,我们将通过网络进行一次反向传播,计算更新权重所需的梯度。

训练过程的正式描述

上一节我们介绍了训练的直观概念,本节中我们来看看更正式的训练步骤描述。对于每一个训练样本(输入 X, 正确答案 y),我们将执行以下操作:

  1. 运行前向计算,找出网络认为的估计值 Ŷ
  2. 运行反向计算来更新权重。

我们以一个两层网络为例进行思考。以下是具体的更新步骤:

首先,对于每一个输出节点,我们将计算真实值 y 与估计值 Ŷ 之间的损失,并根据该损失更新从隐藏层到输出层的所有权重。

接下来,我们将处理隐藏层节点。我们将找到一种方法来评估该节点对最终答案应承担多少“责任”,然后对于从输入层到隐藏层的每一个权重,我们都将进行更新。我们将在后续以更正式的方式看到如何实现这一点。

从逻辑回归中获取直觉

在深入神经网络之前,让我们回顾一下逻辑回归的做法,这有助于我们理解梯度下降的基本原理。

逻辑回归的损失函数旨在学习能够最大化正确标签对数概率的权重,即 P(y|x)

为了将其转化为最小化的损失函数,我们需要改变符号。

然后,我们可以代入由权重 sigmoid 函数计算出的概率估计值。

让我们回顾一下逻辑回归讲座中梯度下降如何进行权重更新。

梯度下降中移动的幅度,是损失函数相对于权重的梯度值,乘以一个学习率 η

因此,我们有旧的权重 W^T,为了计算新的权重,我们将旧权重移动一个由学习率加权的损失梯度。更高的学习率意味着在每一步中我们应该更多地移动 W

对于逻辑回归,我们看到损失函数相对于一个权重 w_j 的导数是:σ(w·x + b) - y,即 ŷ - y 乘以 x_j

链式法则与导数来源

这个导数从何而来?它使用了链式法则。

链式法则指出,如果我们有一个复合函数 F(x) = u(v(x)),那么 F(x) 的导数是 u 相对于 v 的导数乘以 v 相对于 x 的导数。

我们可以从这个神经单元(它与逻辑回归相同)看到其直觉:它计算作为 y 函数的损失,而 y 又是权重乘以值的和的 sigmoid 函数。

因此,如果我们想计算整个损失函数相对于一个权重 w_i 的导数,我们可以使用链式法则来计算:

∂L/∂w_i = (∂L/∂ŷ) * (∂ŷ/∂z) * (∂z/∂w_i)

其中:

  • ∂L/∂ŷ 是损失函数的导数。
  • ∂ŷ/∂z 是激活函数(如 sigmoid)的导数。
  • ∂z/∂w_i 是加权和相对于该权重的导数。

扩展到深度网络

上一节基于逻辑回归的导数只给出了最后一层权重的更新规则。逻辑回归只有一层权重,但对于具有许多层的更深网络,并且激活函数不仅仅是 sigmoid 时,我们该怎么办?

我们将在下一讲中看到解决方案,届时我们将更多地使用链式法则,并引入计算图和反向微分的重要思想。

本节课我们一起学习了神经网络训练的概述和基本数学原理。我们已经看到了神经网络训练的概述,在下一讲中,我们将看到具体的细节。

深度学习课程 P62:L10.6 - 计算图与反向传播 🧠

在本节课中,我们将学习神经网络训练中的核心算法——反向传播。我们将了解如何通过计算图这一工具,高效地计算网络中所有权重的梯度,从而利用梯度下降法来更新权重。

概述:为什么需要反向传播?

为了使用梯度下降法训练神经网络,我们需要知道损失函数相对于网络中每一层每一个权重的梯度。然而,损失函数只在网络的最终输出端被计算。那么,我们如何找到网络中较早层权重的梯度呢?解决方案是一种称为误差反向传播的方法,在神经网络领域常简称为“反向传播”。实际上,反向传播是反向微分方法的一个特例,而反向微分依赖于计算图这一概念。

什么是计算图?📊

计算图是一种表示任何数学表达式计算过程的工具。它将复杂的计算分解为一系列独立的操作,每个操作被建模为图中的一个节点。

考虑计算一个简单的函数:L(a, b, c) = c * (a + 2b)。如果我们把其中的加法和乘法操作显式地分解出来,并为中间输出命名,计算过程如下:

  • d = 2 * b
  • e = a + d
  • L = c * e

我们可以用一个图来表示这个过程,其中每个操作是一个节点,有向边表示一个操作的输出作为下一个操作的输入。

计算图最简单的用途是,在给定输入的情况下计算函数值。假设输入为 a=3, b=1, c=-2。我们可以进行前向计算:

  • d = 2 * 1 = 2
  • e = 3 + 2 = 5
  • L = 5 * (-2) = -10

然而,计算图的重要性在于其至关重要的反向传播过程。反向传播用于计算权重更新所需的导数。

反向传播与链式法则 ⛓️

在这个例子中,我们的目标是计算输出函数 L 相对于每个输入变量(a, b, c)的导数。例如,∂L/∂a 告诉我们,在保持其他变量不变的情况下,a 的微小变化会对最终输出 L 产生多大影响。

反向微分从根本上依赖于链式法则的大量应用。让我们回顾一下链式法则:

  • 假设有一个复合函数 f(x) = u(v(x))。链式法则告诉我们:f‘(x) = u’(v(x)) * v‘(x)
  • 链式法则可以扩展到更多函数。对于 F(x) = u(v(w(x))),其导数为:F’(x) = u‘(v) * v’(w) * w‘(x)

现在,让我们使用链式法则来计算我们需要的导数。在计算图中,L = c * e,我们可以直接计算 ∂L/∂c = e。对于另外两个导数,我们需要链式法则:

  • ∂L/∂a = (∂L/∂e) * (∂e/∂a)
  • ∂L/∂b = (∂L/∂e) * (∂e/∂d) * (∂d/∂b)

这些等式需要五个中间导数:∂L/∂e, ∂e/∂a, ∂e/∂d, ∂d/∂b, ∂L/∂c。我们可以计算如下:

  • ∂L/∂e = c
  • ∂L/∂c = e
  • ∂e/∂a = 1 (和的导数等于导数的和)
  • ∂e/∂d = 1
  • ∂d/∂b = 2

在反向传播过程中,我们从右向左沿着图的每条边计算这些偏导数,并将必要的偏导数相乘,得到我们最终需要的导数。

反向传播过程演示 🔄

我们从标注最后一个节点开始,∂L/∂L = 1。然后向左移动:

  1. 计算 ∂L/∂c。根据前向计算,e=5,所以 ∂L/∂c = e = 5
  2. 计算 ∂L/∂e∂L/∂e = c = -2
  3. 计算 ∂e/∂d = 1
  4. 计算 ∂L/∂d = (∂L/∂e) * (∂e/∂d) = (-2) * 1 = -2
  5. 计算 ∂d/∂b = 2
  6. 计算 ∂L/∂b = (∂L/∂d) * (∂d/∂b) = (-2) * 2 = -4

我们可以对图中的其余部分进行类似的计算。下图清晰地展示了整个反向传播过程。

当然,真实神经网络的计算图要复杂得多。

神经网络中的计算图示例 🧩

这是一个具有两个输入单元、两个隐藏单元和一个输出单元的两层神经网络的计算图示例。中间使用 ReLU 激活函数,输出层使用 Sigmoid 激活函数。

以下是其方程:

  • 第一层:z1 = W1 * x + b1, a1 = ReLU(z1)
  • 第二层:z2 = W2 * a1 + b2, a2 = σ(z2) (σ 表示 Sigmoid 函数)
  • 最终输出:ŷ = a2

为了在这个计算图上进行反向传播,我们需要知道图中所有函数的导数。例如:

  • Sigmoid 函数的导数:σ‘(z) = σ(z) * (1 - σ(z))
  • ReLU 函数的导数:当 z > 0 时为 1,否则为 0

图中需要更新的权重(即我们需要知道损失函数对其偏导数的权重)用橙色标出。对于一个特定的观测样本 (x1, x2),我们会先运行前向传播,为所有节点赋值,然后从最后面的节点开始运行反向传播。

反向传播起步示例 🚶‍♂️

让我们展示如何开始反向传播,通过计算前几个步骤来求损失函数相对于最后一个 z(即 z2)的导数。

我们使用标准的交叉熵损失函数:L = -[y * log(ŷ) + (1-y) * log(1-ŷ)]。这里用 a 表示 ŷ

我们想计算 ∂L/∂z。根据链式法则:∂L/∂z = (∂L/∂a) * (∂a/∂z)

  1. 计算 ∂L/∂a

    • 对损失函数 L 关于 a 求导:∂L/∂a = -[y * (1/a) + (1-y) * (1/(1-a)) * (-1)]
    • 简化后得到:∂L/∂a = -[y/a - (1-y)/(1-a)] = (a - y) / [a(1-a)]
  2. 计算 ∂a/∂z(Sigmoid 导数):∂a/∂z = a * (1 - a)

  3. 将两者相乘:

    • ∂L/∂z = [(a - y) / (a(1-a))] * [a(1-a)] = a - y

这个结果非常简洁:对于使用交叉熵损失和 Sigmoid 激活的输出层,损失对未激活值 z 的梯度就是预测值 a 与真实标签 y 的差。

总结 📝

本节课中我们一起学习了反向传播这一重要算法。为了训练神经网络,我们需要损失函数相对于网络早期层权重的导数,但损失只在网络末端计算。解决方案是反向微分:我们构建一个计算图,在已知图中所有函数导数的情况下,可以自动计算出损失相对于这些早期权重的导数。

我们了解了计算图的思想和反向微分的过程,这是理解和实现神经网络训练的基础。

课程 P63:L11.1 - 聊天机器人与对话系统介绍 🤖

在本系列课程中,我们将探讨聊天机器人与对话系统。

概述

在本节课中,我们将学习对话系统的基本概念、主要类别及其应用场景。对话系统旨在通过文本或语音与人类进行交互,其应用范围广泛,从简单的个人助手到复杂的任务处理系统。

对话系统简介

对话代理,也称为对话系统、对话代理或聊天机器人,是设计用于通过对话(文本或语音)与人类交互的系统。这些系统包括手机或其他设备上的个人助手,如 Siri、Alexa、Cortana 或 Google Assistant,以及用于简单、相对短暂交互的工具,例如播放音乐、设置计时器、时钟或管理购物清单。它们也延伸到更长的对话,可能只是为了娱乐,或用于实际应用,如预订旅行,甚至在心理健康临床中使用。

在这些讲座中,我们将讨论两大类对话代理。

聊天机器人

聊天机器人是旨在进行长时间对话的系统,其目标是模仿非正式人际互动中特有的无结构聊天或对话。这些系统大多设计用于娱乐。然而,从最早的 Weizenbaum 的 Eliza 系统开始,聊天机器人也被用于实际目的,包括心理咨询。

任务型对话代理

第二种系统是基于目标的对话代理,用于解决某些任务,如预订航班或维护购物清单。有时“聊天机器人”一词会用于这两种类型的系统,但在这些讲座中,我们将尝试区分它们。

与语言处理中的几乎所有其他内容一样,聊天机器人架构分为两类:基于规则的系统与基于语料库的系统。基于规则的系统包括早期有影响力的 Eliza 和 Perry 系统。基于语料库的系统则挖掘大量的人-人对话数据集,这可以通过使用信息检索从先前的对话中复制人类响应,或使用编码器-解码器系统根据用户话语生成响应来实现。

聊天机器人示例

这些聊天机器人系统通常具有娱乐价值,例如 Facebook 的 BlenderBot,这是一个进行此类对话的神经聊天机器人。用户说:“你能给我唱首歌吗?”聊天机器人回答:“当然,你想听什么歌?我可以给你发一首关于烘焙的歌。”用户说:“好的,给我唱一首关于烘焙的歌。”等等。😊

或者微软的 XiaoIce 系统,它在短信平台上用中文与人聊天,主要通过提取过去对话中人类说过的话来回应。

任务型对话代理架构

其他对话代理则构建用于解决任务,如设置计时器、预订旅行、播放歌曲。这些系统倾向于围绕一种称为“框架”的知识结构构建。

一个框架代表用户对任务的意图,由一组“槽位”组成,每个槽位可以取一组可能的值。因此,一个航班预订代理可能具有诸如目的地城市或出发时间等槽位。

框架示例:航班预订

flight_booking_frame = {
    "intent": "book_flight",
    "slots": {
        "destination_city": None,
        "departure_time": None,
        "airline": None,
        # ... 其他槽位
    }
}

总结

本节课中,我们一起快速预览了两类对话代理:聊天机器人和任务型对话系统。我们了解了它们的基本定义、典型应用以及核心的构建方法,为后续深入学习奠定了基础。

课程 P64:L11.2 - 人类对话的属性 🗣️

在本节课中,我们将学习人类对话的基本属性。理解这些属性对于设计能够与人类自然交流的对话系统至关重要。

概述

在开始设计能与人类对话的智能体之前,我们必须先理解人类之间是如何进行对话的。本节将分析一段真实的人类对话,并从中提炼出对话的关键特征。

对话的基本单位:话轮

首先,我们来看一段人类旅行代理与客户之间的对话节选。对话中的每一次发言被称为一个“话轮”。话轮可以是一个完整的句子,也可以短至一个单词,或长至多个句子。

以下是对话中的一些话轮示例:

  • C1: 我需要在五月出行。
  • C13: 西雅图。
  • A10: (代理的一段较长回复)。

话轮转换与打断

对话涉及两个或更多人,因此参与者需要协商“话轮转换”,即决定谁在何时发言。有时,双方可能同时试图发言,导致“打断”。

例如,在对话中,代理说“哦,有两个直飞航班”,而客户同时打断说“实际上,15号是星期几?”。这种重叠用“#”符号标记。人类代理知道此时应停止发言,并理解客户可能在进行更正或提出新问题。

对于对话系统而言,允许用户打断系统发言(这被称为“打断”或“barge-in”)并知道何时开始发言,是非常重要的功能。

言语行为:对话中的动作

哲学家维特根斯坦和奥斯汀指出,对话中的每一句话都是说话者执行的一种“动作”,这被称为“言语行为”或“对话行为”。

以下是巴赫与哈尼什提出的一种言语行为分类:

  • 断言: 说话者承诺某事为真。例如,陈述“我需要在五月出行”。
  • 指令: 说话者试图让听者做某事。例如,命令“把音乐调大”或提问“你想在五月的哪一天出行?”(可视为一种礼貌的命令)。
  • 承诺: 说话者承诺未来的某个行动。例如,许诺。
  • 表态: 表达说话者对听者或某种社会行为的态度。例如,感谢。

言语行为表达了说话者说这句话的意图。

共同立场与确认

对话不是一系列独立的言语行为,而是说话者和听者共同完成的集体行为。因此,参与者建立双方都认同的“共同立场”非常重要。

说话者通过“确认”彼此的话语来做到这一点。“确认”意味着听者表明自己理解了说话者的话。在人类对话中,确认可以很明确,比如直接说“好的”;也可以通过重复对方的话来实现,例如代理重复“在11号”,以向客户表明自己听懂了。

这种确认机制对人机交互同样重要。例如,电梯按钮在按下后会亮起,就是一种非语言的确认,表明你的指令已被接收。

对话结构:相邻对与旁侧序列

对话具有结构。例如,在会话分析领域研究的言语行为之间的局部结构:

  • 提问会引发对回答的期待。
  • 提议后通常会跟随接受拒绝
  • 恭维(如“外套真好看”)常引发谦辞(如“哦,这件旧衣服”)。

这种成对的结构(如“提问-回答”)被称为“相邻对”,由“第一部分”和“第二部分”组成。这些预期可以帮助系统决定采取何种行动。

然而,言语行为并不总是立即被其第二部分跟随。两个部分之间可能被“旁侧序列”或“子对话”隔开。例如,在我们的对话节选中,客户提问“15号是星期几?”就中断了代理正在寻找5月15日返程航班的进程,形成了一个“更正子对话”。代理必须先回答这个问题,然后意识到客户可能想改变计划,再回到寻找周日返程航班的任务上。

另一种常见的旁侧序列是“澄清问题”,它可以在请求和响应之间形成一个子对话。这在语音识别出错的对话系统中尤其常见。

对话主动权

有时,对话完全由一方控制。例如,记者采访厨师时,记者提问,厨师回答。我们说在这种情况下,记者拥有“对话主动权”。

然而,在正常的人类对话中,主动权更常见的是在参与者之间来回切换,双方时而回答问题,时而提出问题,引导对话走向新的方向。这种“混合主动权”是人类对话的常态,但对对话系统来说却很难实现。

相比之下,设计被动响应的系统要容易得多:

  • 在搜索引擎或简单的问答系统中,主动权完全在用户手中(用户主动系统)。
  • 在一些糟糕的对话系统中,系统提出问题后,在你回答之前不提供任何其他操作机会,这种纯粹的系统主动架构会让人非常沮丧。

对话中的推理

推理在对话理解中也很重要。考虑这个节选:代理问“你想在五月的哪一天出行?”,客户回答“哦,我需要参加一个12号到15号的会议”。

请注意,客户并没有直接回答代理的问题,而只是提到了一个会议的时间。是什么让代理能够推断出客户提及这个会议是为了告知出行日期呢?说话者似乎期望听者能做出某些推断,即说话者传达的信息比字面上看起来的更多。

哲学家格赖斯将这类例子归入他的“会话含义”理论中。“含义”指的是一类特定的、被许可的推理。这类推理对对话系统来说尤其具有挑战性。

总结

本节课我们一起学习了人类对话的几个关键属性,包括话轮、话轮转换、言语行为、共同立场与确认、对话结构(相邻对与旁侧序列)、对话主动权以及对话中的推理。在设计对话系统时,我们必须将这些属性牢记于心。

🤖 课程 P65:L11.3 - 基于规则的聊天机器人

在本节课中,我们将要学习聊天机器人领域的早期重要代表——Eliza。我们将了解它的工作原理、核心算法,并探讨其背后的伦理思考。同时,我们也会介绍另一个基于规则的聊天机器人PARRY,看看它在架构上的创新。

Eliza是一个模拟罗杰斯派心理治疗师的聊天机器人,是该领域历史上最早且最重要的聊天机器人之一。

让我们通过一段在1966年介绍Eliza的论文中发表的示例对话来了解它。这里摘录了原始论文中完整互动的一部分。

请注意,Eliza给出的回应在语言上似乎非常连贯。

男人在哪些方面都一样?
他们总是为某些事情烦我们。
你能想到一个具体的例子吗?

Eliza甚至似乎能记住对话中很早之前提到的内容。比如最后这句:“这和你男朋友让你来这里的事实有关吗?” 它引用了很久之前提到的事情。

Eliza是如何做到这一点的?

Eliza被设计用来模拟一位罗杰斯派心理学家,该临床心理学分支的方法涉及通过将患者的陈述反射回给患者来引导他们。正如魏泽鲍姆指出的,罗杰斯疗法是一种罕见的对话类型,在这种对话中,可以假定对话者对现实世界几乎一无所知。如果患者说:“我进行了一次长时间的乘船旅行”,而心理学家说:“跟我讲讲船吧。” 你不会认为她不知道船是什么,而是会假设她有一些与你的治疗相关的对话目标。大多数试图通过图灵测试的聊天机器人都会尝试寻找具有类似属性的领域。

Eliza通过模式转换规则工作,如下所示:

(0 YOU 0 ME)

在Eliza的模式中,0 表示任意字符串,在转换规则中,数字是模式中成分的索引。所以这里的数字 3 指的是这个模式中的第三个元素,即第二个 0

因此,这条规则 (0 YOU 0 ME) 会转换为 (WHAT MAKES YOU THINK I 3 YOU)。这将把 “you hate me” 翻译成 “what makes you think I hate you”,其中第一个 0 匹配空字符串,第二个 0 匹配 “hate”。

每条Eliza模式转换规则都与一个可能出现在用户句子中的关键词相关联。每个关键词都有一个模式列表,每个模式又有一个转换列表。所以,你刚才看到的模式 (0 YOU 0 ME) 可能被组织在关键词 YOU 下。然后,我展示了两种可能的转换:一种是我们刚看到的,另一种是其他的。

对于历史爱好者,这是原始论文中Eliza知识的Lisp结构:

(KEYWORD (RANK) (PATTERN1 (TRANSFORM11) (TRANSFORM12) ...) (PATTERN2 ...) ...)

关键词与一个等级相关联,特定单词的等级更高,更通用的单词等级较低。

考虑以下句子:“我知道。每个人都嘲笑我。” 因为它包含单词 I,这个句子可以匹配以下规则,其关键词是 I。这里的规则是:如果你看到 I 后面跟着任何内容,就说 “you say you ...”。这里我用了 0 来表示任意字符串。所以这会将句子 “I know everybody laughed at me” 转换为 “you say you know everybody laughed at you”。Eliza有单独的规则来转换所有这些代词。但 I 是一个非常通用的词,它的关键词会导致非常通用、模糊的回应。

关键词 EVERYBODY 则有趣得多。因为正如魏泽鲍姆指出的,使用像 everybodyalways 这样的泛指词的人,很可能是在指代某个特定的事件或人。因此,如果我们使用用户句子中的关键词 EVERYBODY 来选择模式,我们会选择将 (0 EVERYBODY 0) 转换为 (WHO IN PARTICULAR ARE YOU THINKING OF) 的模式。Eliza就会给出那个回应。这是一个有趣得多的回应。

Eliza通过为每个关键词存储一个等级来实现这个想法。所以 EVERYBODY 与等级 5 一起存储,然后是其可能的转换规则列表,比如这里的这个。而 I 与等级 0 一起存储,以及它的转换规则列表。因此,当Eliza选择转换规则时,如果句子中包含 everybody,它更可能选择与 everybody 相关的转换规则。

以下是Eliza的生成器算法,给定一个用户句子并返回一个回应:

  1. 首先,在句子中找到具有最高关键词等级的单词。
  2. 然后,为该单词选择最高等级的规则(如果它与句子文本匹配)。
  3. 应用转换规则。
  4. 我们还会做一件额外的小事:如果句子中有 my,我们对该句子应用一个特殊的转换,并将其放入一个栈中。我们稍后会回到这一点。
  5. 否则,我们给出“无匹配”回应。我们会准备一些“无匹配”回应,用于处理我们不太理解用户所说内容的情况。
  6. 或者,我们会回到这个记忆栈,并随机说点什么。

那么,“无匹配”回应是什么意思呢?如果没有关键词匹配,Eliza会选择一个非常不置可否的回应,比如“请继续”或“我明白了”。

最后,Eliza有一个巧妙的记忆技巧,这解释了我们看到的对话中的最后一句。每当单词 my 是最高等级的关键词时,Eliza会随机选择记忆列表上的一个转换规则(MEMORY 是一个特殊的列表),将其应用于句子,并将其存储在一个队列中。转换规则例如:将 my ... 转换为 let's discuss further why your ...,或者 earlier you said your ...,或者 does it have anything to do with the fact that your ...

所以现在我们将这些存储在一个队列(先进先出队列)中,然后稍后,如果没有关键词匹配句子并且我们不知道说什么,我们只需从记忆队列的顶部弹出一些内容并说出来。


上一节我们介绍了Eliza的工作原理和算法,本节中我们来看看其背后的伦理影响。

人们与Eliza产生了深刻的情感联系。魏泽鲍姆讲述了他的一个员工的故事,这位员工在与Eliza交谈时会要求魏泽鲍姆离开房间。当魏泽鲍姆建议他可能想存储Eliza的对话以供日后分析时,人们立即指出了隐私问题,这表明尽管知道它只是软件,他们仍在与Eliza进行相当私密的对话。魏泽鲍姆担心人们向Eliza倾诉,而人们被误导了计算机对自然语言的理解程度。魏泽鲍姆担心社会正在犯一个错误,即将人类从决策和选择中移除。

魏泽鲍姆在麻省理工学院的同事雪莉·特克尔教授,在几十年间研究了Eliza和其他计算系统的用户。她更细致的观点是,虽然人与人面对面的互动对人类体验至关重要(她的很多研究都表明了这一点),但人类也会继续与人工制品发展关系。例如,她研究的一些用户表示,他们使用Eliza更像是一种日记,一种私下探索自己想法的方式。得出的启示是,进行价值敏感设计很重要,即在设计过程中考虑你所设计系统的益处、危害和可能的利益相关者。


在Eliza出现几年后,我们看到了另一个专注于临床心理学的聊天机器人PARRY的发展。但这里的目标与Eliza非常不同。作者试图通过建立一个患有精神分裂症的人的心理过程和语言反应的形式化计算模型来研究精神分裂症。因此,Eliza是对可能构建的语言能力的探索,而PARRY则展示了NLP系统作为认知模型的用途。

PARRY与我们这里的讨论相关,是因为它在聊天机器人架构上的创新。最重要的是,除了类似Eliza的正则表达式和更强大的控制能力外,PARRY系统还包括一个自身心理状态的模型,其中包含诸如代理的恐惧或愤怒水平等情感变量,以及连接这种心理状态与语言输入和输出的复杂规则。

某些类型的陈述或对话主题可能导致PARRY变得更加愤怒、多疑或减少愤怒。例如,提及PARRY的妄想会增加恐惧变量。

然后,PARRY的输出取决于情感变量。例如,如果愤怒值很高,PARRY会从一组敌意的输出中选择。

PARRY是1972年已知的第一个通过图灵测试变体的系统。精神科医生无法区分与PARRY访谈的文本记录与被诊断为偏执型精神分裂症患者访谈的记录。

虽然像Eliza和PARRY这样的基于规则的方法非常简单,但它们具有重大的意义,并且基于规则的方法在现代聊天机器人中仍然被用作组件。


本节课中我们一起学习了基于规则的聊天机器人Eliza和PARRY。我们探讨了Eliza如何通过关键词、等级和模式转换规则来生成看似连贯的回应,并了解了其巧妙的记忆机制。我们还讨论了这类技术带来的伦理影响,以及进行价值敏感设计的重要性。最后,我们看到了PARRY如何通过引入情感变量和心理状态模型,在架构上进行了创新,并展示了基于规则的方法在现代聊天机器人中仍然占有一席之地。

课程 P66:L11.4 - 基于语料库的聊天机器人 🤖

在本节课中,我们将要学习如何构建不依赖于人工编写规则,而是通过自动挖掘大量人类对话语料库来决定如何回复的聊天机器人。

概述 📋

基于语料库的聊天机器人主要通过在上下文中生成对用户话语的回复来实现。其核心方法有两种:检索式生成式。无论采用哪种方法,系统通常都是根据迄今为止的整个对话(前提是对话长度在模型处理窗口内)来生成一个合适的单轮回复,因此它们也常被称为响应生成系统

语料库与数据 📊

现代基于语料库的聊天机器人是数据密集型的,通常需要数亿甚至数十亿单词的训练数据。

以下是几种常用的数据集类型:

  • 志愿者对话转录:例如包含美式英语电话对话的Switchboard语料库。
  • 电影对话:在许多方面与自然对话相似。
  • 众包对话:专门为训练对话系统而创建,例如让众包工作者扮演特定角色或基于给定知识进行交谈。
    • Topical Chat数据集包含11,000个对话,涵盖八个广泛主题。
    • Empathetic Dialogues数据集包含25,000个对话,基于说话者处于特定情感状态的情景。

尽管这些数据集规模庞大,但仍未达到数十亿单词的级别。因此,许多系统会首先在从Twitter、Reddit等社交媒体平台提取的伪对话大型数据集上进行预训练。处理数据时,必须移除其中可能出现的个人身份信息。

检索式响应方法 🔍

检索式方法将用户的当前话语视为一个查询(Query),我们的任务是从对话语料库C中检索并复述一个合适的回复R。

通常,C是系统的训练集。我们为C中的每一个可能的回复R进行评分,选择得分最高的一个。评分方法基于相似度,即使用任何信息检索方法(例如TF-IDF余弦相似度)选择与查询Q最相似的R。

如果我们将Q和R表示为TF-IDF向量,那么可以在整个语料库中找到与查询具有最高余弦归一化点积的回复。响应公式可以表示为:

R_response = argmax_{R ∈ C} similarity(Q, R)

我们也可以使用神经IR技术。其中最简单的是双编码器模型,我们训练两个独立的编码器:一个用于编码用户查询,另一个用于编码每个候选回复。然后使用查询向量和候选回复向量之间的点积作为评分。

例如,使用BERT实现时,我们会有两个编码器:BERT_QBERT_R。查询和候选回复分别表示为各自编码器的 [CLS] 标记向量。然后,我们同样选择语料库中与查询点积最高的那个话语作为回复。

基于IR的方法可以通过多种方式扩展,例如使用更复杂的神经架构,或者使用比用户最后一句话更长的上下文(直至整个前面的对话)作为查询。

生成式响应方法 🧠

使用语料库生成对话的另一种方法是将响应生成视为一个编码器-解码器任务,将用户的先前话语转换为系统的回复。

这可以看作是Eliza的机器学习版本:系统从语料库中学习如何将问题转译为答案。

编码器-解码器模型通过基于整个查询的编码以及已生成的部分回复,来生成响应中的每一个词元 R_t。因此,响应词元 R_1R_{t-1} 会帮助我们生成响应词元 R_t

生成下一个词元时,我们在词汇表中对所有可能的词元取以下概率的argmax:

P(R_t | Query, R_1, ..., R_{t-1})

下图直观展示了两种范式:检索式响应(从训练集中选择与查询点积最高的句子)和生成式响应(构建编码器-解码器模型,读入查询并逐词生成响应)。

在生成器架构中,我们通常包含更长的上下文,不仅将用户的当前话语,还将迄今为止的整个对话作为查询输入。

另一种方法是直接在对话数据集上微调一个大型语言模型,并直接使用该语言模型作为响应生成器。例如,在Chirpy Cardinal系统中,神经聊天组件就是由在Empathetic Dialogues数据集上微调过的GPT-2生成响应。

提升生成质量 🚀

基本的编码器-解码器模型倾向于产生可预测但重复、乏味的响应(如“我很好”、“我不知道”),这会导致对话终止。

为了适应响应生成任务,通常需要对基本模型进行一些修改。例如,我们可以使用增强多样性的波束搜索聚焦多样性的训练目标,而不是贪婪地选择最可能、最可预测的响应。

如果聊天机器人能够从对话以外的文本知识源(如新闻、文章)生成响应,它们会变得更有趣、信息更丰富。例如,聊天机器人Shao I会从公开讲座和新闻文章中收集信息,并基于用户话语进行查询扩展,使用IR技术来搜索并回应诸如“告诉我一些关于北京的事情”这样的请求。

一种增强编码器-解码器架构的方法是加入检索-精炼步骤:使用IR从维基百科等来源检索可能有用的段落,然后将每个检索到的句子与对话上下文(用分隔符标记连接)拼接,形成多个候选输入。编码器-解码器模型学习将维基百科句子中的文本整合到其生成的响应中。

混合架构与总结 🎯

聊天机器人也可以构建成基于规则和神经语料库架构的混合体,甚至可以融入我们将在未来课程中描述的基于框架的结构元素。

例如,Chirpy Cardinal系统对输入应用NLP流水线,然后使用一组不同的响应生成器来生成回复。有些生成器使用微调的语言模型(如学习转述维基百科内容的GPT-2),而其他生成器则更接近基于规则的方法(例如,电影或音乐生成器使用规则和情感分类器来分类用户响应,并使用手写模板生成机器人话语)。

本节课中我们一起学习了基于语料库的聊天机器人的两种核心范式:检索式响应和生成式响应。聊天机器人在特定应用领域可以很有趣且有用,尤其是在可以进行脚本编写的情况下。但另一方面,它们并非真正理解对话,这种看似理解的表现可能会带来问题。基于规则的聊天机器人成本高且脆弱,而基于IR的聊天机器人只能反映其训练数据,这可能导致一些问题(我们将在下节课讨论)。研究的一个重要下一步是找出将聊天机器人类型的能力整合到基于框架的智能体中的方法。

我们已经了解了基于语料库的聊天机器人的两种范式:检索式响应和生成式响应。

课程 P67:L11.5 - 基于帧的对话系统架构 🏗️

在本节课中,我们将介绍面向任务的对话系统中,基于帧或GUS的架构。

在面向任务的对话中,对话系统的目标是帮助用户完成某项任务,例如预订机票或购买商品。

此类系统的核心是一种称为“帧”的知识结构,它最早于1977年在具有影响力的GUS系统中被提出。

一个帧代表了系统可以从用户语句中提取的、关于用户意图的部分信息。

它由一组“槽”组成,每个槽可以填入一组可能的值。

这组帧有时被称为领域本体。

一个帧的槽集合规定了系统需要知道哪些信息,以完成该帧所代表的那部分任务。

因此,在预订机票时,我们需要知道航班的出发地、目的地、时间或航空公司。

每个槽的填充值受特定语义类型的约束。例如,一个槽的类型可能是“城市”,因此可以填入“旧金山”或“香港”等值;也可能是“日期”、“航空公司”或“时间”类型。

基于帧的任务对话系统通常有两种基本架构。

最简单的一种经典架构我们称之为“GUS架构”,它得名于提出它的论文。该架构侧重于使用一组手工构建的产生式规则来填充帧和执行动作。它已有40多年历史,但至今仍被大多数工业界的任务型对话代理所使用。

GUS架构的一个扩展,有时被称为“对话状态架构”,在研究系统中更为常见,并且其某些方面正逐渐进入工业系统。

以下是1977年与一个真实GUS系统的对话记录。

PSA和Air California是那个时期的航空公司。

GUS拥有许多复杂的能力,这些能力并非所有基于帧的系统都具备。例如,它处理了指代消解,即像“第一个”这样的表达指代的是“102号航班”。或者,它知道“周五晚上”指的是下一个周五,即可能是5月30日。

在这个例子中,它甚至处理了一个复杂的、隐含的约束:“我必须在上午10点前到达圣地亚哥”。

GUS的控制架构,以某种形式应用于所有现代基于帧的对话系统,如苹果Siri、亚马逊Alexa或谷歌助手,其设计围绕帧展开。

系统的目标是用用户意图的填充值来填满帧中的槽,然后为用户执行相关动作,例如回答问题或预订航班。

为了实现这个目标,系统会向用户提问。系统会为每个帧的每个槽预先指定问题模板,然后根据用户的回答填充槽。

例如,填充出发城市槽的问题可能是:“您想从哪里起飞?”,而用户可能回答:“我想要一张从旧金山到丹佛的单程票,下午5点后出发。”

我们可以从这些信息中填充各种槽。

如果有任何槽未被填满,我们可以继续向用户提问。

一旦帧被填满,我们就可以进行数据库查询,寻找满足用户约束的航班或其他信息。

GUS架构还有附加在模板上的条件-动作规则。例如,一旦用户指定了目的地,附加在机票预订帧的“目的地”槽上的规则可能会自动将该城市作为酒店预订帧的默认入住地点。

或者,如果用户为短途旅行指定了出发日期,系统可以自动填入抵达日期。

许多领域需要多个帧。除了汽车或酒店预订的帧,我们可能还需要包含一般路线信息的帧,用于回答诸如“哪些航空公司从波士顿飞往旧金山”的问题;或者需要包含机票价格惯例信息的帧,用于回答诸如“我必须停留特定天数吗”的问题。

系统必须能够消歧,确定给定的用户输入应该填充哪个帧的哪个槽,然后将对话控制切换到该帧。

在基于帧的架构中,自然语言理解组件的目标是从用户的话语中提取三样东西。

第一个任务是领域分类。例如,用户是在谈论航空、设置闹钟还是在处理日历。当然,对于专注于单一领域的系统,这个分类任务是不必要的,但多领域对话系统是现代标准。

第二个任务是意图确定。用户在该领域中试图完成什么一般性任务或目标。例如,任务可能是“找一部电影”、“显示航班”或“删除日历约会”。

最后,我们需要进行槽填充,从用户的话语中提取特定的槽和填充值,这些是系统根据用户意图需要理解的信息。

因此,从用户的话语“给我看看周二从波士顿到旧金山的早班航班”中,系统可能希望构建如下表示:领域是航空旅行,意图是显示航班,并且我们所有的其他槽都已填充。

从“明天六点叫醒我”这样的话语中,我们可能判定领域是闹钟,意图是设置闹钟,而时间则给出了实际的槽填充值。

接下来,我们看看如何在GUS架构中进行槽填充。

原始系统中使用的槽填充方法,在工业应用中仍然相当常见,即使用手写规则。这些规则通常作为附加在槽或概念上的条件-动作规则的一部分。例如,我们可以手动定义一个正则表达式来识别“设置闹钟”意图,匹配像“叫醒我”、“设置闹钟”、“叫我起床”这样的短语。

对于生成响应,基于帧的系统倾向于使用基于模板的生成,即向用户说出的句子中的所有或大部分词语都由对话设计者预先指定。

由这些模板创建的句子有时被称为“提示”。

模板可以是完全固定的,例如“你好,我能帮你什么?”;也可以包含一些由生成器填充的变量,例如“您想从出发城市什么时间出发?”或“您会从目的地返回出发城市吗?”。

基于规则的GUS方法在工业应用中非常普遍。与基于规则的信息提取方法一样,它具有高精度的优点。如果领域足够狭窄且有专家可用,也能提供足够的覆盖率。另一方面,手写规则或语法训练起来可能既昂贵又缓慢,并且手写规则可能存在召回率问题。

因此,现代系统倾向于用机器学习来替代许多组件,我们将在下一讲中看到。

基于帧或GUS的架构是大多数现代任务型对话系统的核心。


本节课总结

在本节课中,我们一起学习了面向任务对话的基于帧(或GUS)的架构。我们了解了“帧”作为核心知识结构,它由一系列“槽”组成,用于捕捉用户意图。我们探讨了经典的GUS架构及其扩展——对话状态架构,并介绍了系统如何通过领域分类、意图确定和槽填充来理解用户输入。最后,我们讨论了基于规则方法的优缺点,并提及了现代系统向机器学习演进的趋势。

📚 课程 P68:L11.6 - 对话状态架构

在本节课中,我们将学习一种基于框架架构的扩展形式,称为对话状态(或信念状态)架构。这是现代面向任务的对话研究系统的基础,它比之前介绍的简单框架架构更为复杂和先进。

🏗️ 对话状态架构概述

上一节我们介绍了基础的框架架构,本节中我们来看看其更复杂的演进版本——对话状态架构。

对话状态架构增加了对话行为的概念。与之前介绍的框架架构相比,它倾向于使用更多机器学习方法,并减少了模板化生成。它是大多数现代研究系统的基础,其中像使用机器学习进行语义理解等方面已在工业界得到广泛应用。

🔧 典型对话状态系统的组件

以下是典型对话状态系统的六个组件。我们以语音输入为例进行说明。

  • 语音识别:作为输入,生成一组句子。
  • 理解模块:接收句子,生成槽位填充信息。
  • 对话状态跟踪器:存储随时间变化的框架槽位及其填充状态。
  • 对话策略模块:决定接下来要说什么。
  • 自然语言生成模块:决定如何措辞。
  • 文本转语音:如果我们处理的是语音。

📝 聚焦文本处理的四个核心组件

本节课我们将聚焦于文本而非语音,重点探讨其中四个核心组件。这些组件比简单的GUS系统更为复杂。

与GUS系统类似,对话状态架构也有一个自然语言理解组件来提取槽位填充信息,但通常使用机器学习而非手写规则。

以下是这四个核心组件的详细说明:

  • 对话状态跟踪器:维护对话的当前状态,包括用户最近的对话行为以及用户迄今为止表达的所有槽位填充约束。
  • 对话策略:决定系统下一步应该说什么或做什么。在GUS中,策略很简单:不断提问直到框架填满,然后报告数据库查询结果。但更复杂的对话策略可以帮助系统决定何时回答用户问题、何时提出澄清问题、何时提出建议等。
  • 自然语言生成:对话状态系统拥有真正的自然语言生成组件。在GUS中,生成器产生的句子都是预先写好的模板。但更复杂的组件可以根据具体上下文生成看起来更自然的对话轮次。

💬 对话行为的作用

对话状态系统利用了对话行为。对话行为代表了对话轮次的交互功能,将言语行为和基础共识的概念结合到一个单一的表示中。

不同类型的对话系统需要对不同种类的行为进行标注。因此,定义对话行为具体是什么的标签集往往是为特定任务设计的。

以下是一个餐厅推荐系统的标签集示例:

  • inform:告诉别人某事。
  • request:提出问题。
  • confirm:确认回应。
  • negate:表示否定。
  • hello:打招呼。
  • bye:道别。

每个对话行为都由一组变量进行参数化。

🧩 对话行为与槽位填充示例

让我们看一个例子。这里使用相同的标签为HIS系统中的一段示例对话进行标注。

这个例子还展示了每个对话行为的内容,即正在传达的槽位填充信息。用户可能inform系统他们想要博物馆附近的意大利菜,或者request系统确认价格范围是中等。

同样,系统可以使用对话行为,例如affirm向用户确认名为“Roma”的餐厅价格范围是中等。

🤖 槽位填充的机器学习方法

槽位填充以及更简单的领域和意图分类任务,通常通过监督式语义解析来解决。我们有一个训练集,将每个句子与其正确的含义关联起来。

输入可能由“I want to fly to San Francisco on Monday, please”这样的句子组成,输出则是带有填充值的两个槽位:destination=SFdeparture_time=Monday。给定大量此类带标签的句子,我们可以构建一个分类器来进行映射。

构建此类分类器的一个标准范式是BIO标注。在BIO标注中,我们为每个槽位标签的开始和内部引入一个标签,并为所有不在任何槽位标签内的词元引入一个“外部”标签。

我们的训练数据现在将是句子与BIO标签序列的配对。例如,“outside, outside, B-destination, I-destination, B-departure_time, I-departure_time”等。

🧠 槽位填充的模型架构

给定这个训练数据,以下是一个简单的槽位填充架构:

  1. 输入是一系列词 W1WN
  2. 通过上下文嵌入模型传递,以获得上下文词表示。
  3. 接着是一个前馈层。
  4. 然后在每个词元位置上对可能的BIO标签进行softmax操作。

因此,输出将是每个输入词元的一系列BIO标签。

我们还可以通过将领域分类和意图提取任务与槽位填充结合起来,只需将领域与意图拼接起来,作为最终EOS(句子结束)标记的期望输出。因此,这个架构将使用BIO标注范式完成槽位填充、领域和意图分类。

📊 对话状态跟踪器的任务

一旦我们训练好BIO标注器,就可以在输入句子上运行它,然后为每个槽位提取填充字符串。例如,对于目的地槽位,我们有开始和内部标签,所以得到“San Francisco”,然后我们可以将其规范化为某种正确形式(例如,通过同义词词典将“San Francisco”等同于“SFO”)。

对话状态跟踪器的任务是确定框架的当前状态(即每个槽位的填充值)以及用户最近的对话行为。

因此,对话状态不仅包括当前句子中的槽位填充信息,还包括此时框架的整个状态,总结了用户的所有约束。例如,用户说“I‘m looking for a cheaper restaurant”,我们得到用户inform了price=cheap。然后用户额外告诉我们他们想要“Thai food near downtown”,但我们的状态包含了之前已知的他们想要便宜的信息。在用户问“where is it?”之后,当前状态是:用户发布了这些特定约束,并且进一步请求了地址。

🔗 对话行为检测与槽位填充的联合任务

由于对话行为对槽位和值施加了一些约束,对话行为检测和槽位填充的任务通常是联合执行的。

考虑确定“I‘d like Cantonese food near the mission district”具有结构food=Cantonesearea=mission的任务。在这种情况下,对话行为解释(从任务对话行为集中选择inform)是通过在手动标注的对话行为上训练的监督分类来完成的,基于当前句子和先前对话行为的嵌入来预测对话行为标签。

因此,最简单的对话状态跟踪器可能只是在每个句子之后,将此与槽位填充序列模型的输出结合起来。

🛠️ 处理用户纠正行为

一些对话行为因其对对话控制的影响而显得重要。如果对话系统错误识别或误解了话语,用户通常会通过重复或重新表述来纠正错误。因此,检测这些用户纠正行为非常重要。

具有讽刺意味的是,在口语对话中,纠正实际上比正常句子更难识别。事实上,在一个早期的对话系统中,纠正的词语错误率是非纠正句子的两倍。

造成这种情况的一个原因是,说话者有时会使用一种特定的韵律风格进行纠正,称为超清晰发音,其中话语具有夸张的能量、时长或音高轮廓(例如,“I said Baltimore, not Boston”)。

🎯 检测纠正行为的特征

用户纠正往往是精确重复或省略一个或多个词的重复,尽管它们也可能是原始句子的释义。

检测这些重新表述或纠正行为可以是通用对话行为检测分类器的一部分,也可以利用一些额外特征:

  • 如果我们有语音,可以使用来自语音识别器的置信度值。
  • 可以使用来自韵律的超清晰发音测量。
  • 可以使用词汇特征,如“no”、“negative”和表示愤怒的词语。
  • 可以查看每个句子之间的意义重叠。

📖 本节课总结

本节课我们一起学习了对话状态架构的理解组件,包括槽位填充、对话行为以及对话状态跟踪。我们了解了如何使用BIO标注和机器学习模型来实现语义解析,并探讨了处理用户纠正的重要性。

在下一讲中,我们将继续讨论对话状态架构中的策略和生成组件。

课程 P69:L11.7 - 对话状态架构:规则与泛化 🗣️➡️🤖

在本节课中,我们将要学习对话系统中的两个核心组件:对话策略(决定说什么)和自然语言生成(决定怎么说)。我们将探讨如何基于对话状态做出决策,以及如何将抽象的对话动作转化为用户能理解的自然语言句子。


对话策略:决定下一步行动 🎯

上一节我们介绍了对话状态的概念,本节中我们来看看如何利用它来决定系统的下一步行动。

对话策略的目标是决定系统下一步应该采取什么行动。更正式地说,在对话的第 I 轮,我们希望基于整个对话状态来预测系统应采取的动作 a_sub_I

这里的“状态”可以指代系统和用户之间所有对话动作的完整序列。任务就是计算所有可能的下一个动作中,概率最高的那个动作的 arg max

公式a_I = argmax_a P(a | 对话历史状态)

我们可以通过维护一个简化的对话状态来简化这个问题,这个状态主要指用户已表达的槽位填充信息集合。这样,我们就不需要基于整个历史状态,而只需基于当前框架状态、系统上一轮说的话和用户上一轮说的话来做决策。

公式a_I = argmax_a P(a | 当前槽位状态, 上一轮系统动作, 上一轮用户动作)

这些概率可以通过一个神经网络分类器来估计,该分类器使用槽位填充信息的神经表示(例如,话语中词跨度的表示或上下文嵌入计算的句子嵌入)。


确认与拒绝:确保正确理解 ✅❌

现代对话系统经常出错,因此确保系统获得正确理解至关重要。这通常通过两种方法实现:向用户确认理解,以及拒绝系统可能误解的话语。

以下是两种主要的确认策略:

  • 显式确认:系统直接向用户提问以确认理解。
    • 用户说:“Baltimore。”
    • 系统说:“Do you want to leave from Baltimore?”(你想从巴尔的摩出发吗?)
    • 用户说:“Yes.”
  • 隐式确认:系统通过复述其理解来作为“接地”策略的一部分,通常是在问下一个问题时。
    • 用户说:“I want to travel to Berlin.”(我想去柏林旅行。)
    • 系统说:“When do you want to travel to Berlin?”(你什么时候想去柏林?)

显式和隐式确认各有优缺点。显式确认更容易纠正系统的错误识别,但会使对话显得不自然且冗长。隐式确认则更符合自然的对话习惯。

除了确认,另一种表达不理解的方式是拒绝。系统会提示用户,例如:“I‘m sorry. I didn’t understand that.”(抱歉,我没听懂。)

当话语被多次拒绝时,可能意味着用户使用了系统无法理解的语言。因此,系统通常会采用渐进式提示升级细节的策略。例如,系统没有理解用户的长句,它不会只是重复问题,而是会给出更具体的指导,告诉用户如何组织系统能理解的话语。


利用丰富特征优化策略决策 🔧

除了对话状态表示,系统还常利用其他丰富特征来制定策略决策。

例如,自动语音识别系统为话语分配的置信度可以用来决定是否进行显式确认。系统可以设定多个置信度阈值。

伪代码示例

if asr_confidence < alpha:
    reject()
elif asr_confidence < beta:
    confirm_explicitly()
elif asr_confidence < gamma:
    confirm_implicitly()
else:
    # 高度确信,无需确认
    proceed()

另一个常见特征是错误成本。例如,在预订航班或转账等关键操作前,通常会进行显式确认。


自然语言生成:从动作到句子 ✍️

一旦策略决定了要生成什么对话动作,自然语言生成组件就需要生成回应用户的文本。

在信息状态架构中,自然语言生成通常被建模为两个阶段:内容规划(说什么)和句子实现(怎么说)。这里,我们假设内容规划已由对话策略完成,它选择了要生成的对话动作以及一些属性(如槽位和值)。

以下是句子实现阶段的输入输出示例。内容规划器选择了对话动作 recommend 以及特定的槽位(餐厅名、街区、菜系),句子实现器的目标是生成像示例1或2那样的句子。


提升泛化能力:去词汇化 🧩

然而,训练数据很难获取,我们不太可能在大量标注对话中看到每个可能的餐厅及其所有属性以不同措辞表达的句子。

因此,在句子实现中,常通过去词汇化来增加训练样本的泛化能力。去词汇化是将训练集中代表槽位值的特定词语,替换为代表该槽位的通用占位符标记的过程。

我们可以将上述两个句子去词汇化,把 “Omidi”、“midtown” 和 “French” 替换为占位符。

现在,从框架到去词汇化句子的映射可以通过在大型手工标注的任务型对话语料库上训练的编码器-解码器模型来完成。编码器的输入是代表对话动作及其参数的标记序列。解码器输出去词汇化的英语句子。然后,我们可以使用来自内容规划器的输入框架进行再词汇化,将占位符替换回具体的值。


针对特定动作的生成算法 🎯

也可以设计针对特定对话动作的自然语言生成算法。例如,考虑在语音识别未能理解用户话语的某些部分时,生成澄清问题的任务。

虽然可以使用通用的拒绝对话动作,如“请重复”或“我不理解”,但我们也可以采取更有针对性的方法。例如,系统可以复述“going”和“on the fifth”这两个词,以明确需要澄清用户话语的哪个方面。这种有针对性的澄清问题可以通过规则(例如,将“going to [未知词]”替换为“going where?”)或通过构建分类器来猜测句子中哪个槽位可能被误识别来创建。


总结 📚

本节课中,我们一起学习了对话状态架构的核心部分。我们探讨了对话策略如何基于简化状态和丰富特征(如ASR置信度)来决定下一步行动。我们了解了通过显式/隐式确认拒绝策略来确保理解正确性。最后,我们学习了自然语言生成如何将抽象的对话动作转化为自然语言,并通过去词汇化编码器-解码器模型来提升生成句子的泛化能力和流畅性。这些组件共同构成了一个能够进行有效、鲁棒对话的系统基础。

课程 P7:L2.1 - 最小编辑距离定义 📏

在本节课中,我们将学习最小编辑距离的定义。最小编辑距离是衡量两个字符串相似度的一种方法,它在拼写纠错、计算生物学和机器翻译等多个领域都有广泛应用。

什么是字符串相似度问题? 🤔

最小编辑距离是解决字符串相似度问题的一种方式。它衡量两个字符串有多相似。

让我们看一个具体例子:拼写纠错。用户输入了“GRAFFE”,他们真正想输入的是什么?将这个问题具体化的一种方法是,询问以下哪个单词与用户输入的字母更接近:graphgiraffe 还是 giraffe

字符串相似度问题也出现在计算生物学中。我们拥有核苷酸序列(如 ACGT),并试图进行比对。一个好的比对应该能告诉我们,来自两个样本的两个特定序列以某种方式对齐,并存在一定误差。

这种字符串或序列相似性的概念,在机器翻译、信息抽取和语音识别等领域也普遍存在。

编辑距离的定义 📝

两个字符串之间的最小编辑距离,是指将一个字符串转换为另一个字符串所需的最少编辑操作次数。

编辑操作通常包括以下三种:

  • 插入:添加一个字符。
  • 删除:移除一个字符。
  • 替换:将一个字符替换为另一个字符。

你可以想象更复杂的操作,比如换位或长距离移动,但我们通常避免使用这些。

例如,我们有字符串 intentionexecution。下图展示了一种对齐方式,显示了许多字母通过一些替换操作对齐,并且存在一些空缺。execution 中的字母 C 与一个空缺对齐,intention 中的字母 I 也与一个空缺对齐。

我们可以将这种对齐看作是一系列生成该对齐的操作集合。

要将 intention 转换为 execution,我们需要:

  1. 删除 I
  2. n 替换为 E
  3. T 替换为 x
  4. 插入 C
  5. n 替换为 U

其余字母 ETION 都相同。因此,如果每个操作的成本为 1,那么编辑距离就是 5。

如果替换操作的成本为 2(这被称为 莱文斯坦距离,其中插入和删除成本为 1,替换成本为 2),那么这两个字符串之间的距离就是 8。

编辑距离的应用场景 🌍

上一节我们介绍了编辑距离的基本定义,本节中我们来看看它在不同领域的具体应用。

在计算生物学中

我们见过碱基序列,我们的任务是找出这个 A 与那个 A 对齐,这个 G 与那个 G 对齐,等等。例如,这里的 C 与那里的空缺对齐,这表明存在某种插入操作。我们同样可以通过显示字符之间的对齐线来表示这种对齐关系。

因此,在生物学中的任务是:给定两个序列,将每个字母与另一个序列中的字母或空缺进行对齐。

在机器翻译中

编辑距离在机器翻译中无处不在。例如,我们想衡量一个机器翻译系统的表现如何。

假设我们的机器翻译系统将某个中文句子翻译为:“the spokesman said the senior advisor was shot dead”。而人类专家翻译的版本是:“spokesman confirmed senior government advisor was shot”。

我们可以通过计算两个句子之间有多少单词发生了变化来衡量它们的差异:

  • 替换confirmed 被替换为 said
  • 插入:单词 thedead
  • 删除:单词 government

因此,通过将机器翻译结果与人工翻译进行比较,可以衡量机器翻译的质量。

在命名实体识别中

在命名实体识别等任务中,我们想知道 IBM Inc.IBM 是否是同一个实体,或者 Stanford University President John HennessyStanford President John Hennessy 是否是同一个实体。

我们可以使用编辑距离来发现它们非常相似,只有一个单词不同。通过测量单词差异的数量,我们可以提高命名实体识别及其他任务的准确性。

如何寻找最小编辑距离? 🧠

好了,那么我们如何找到这个最小编辑距离呢?

算法的直觉是搜索一条路径。这里的路径指的是从起始字符串到最终字符串的一系列编辑操作序列。

以下是搜索路径的要素:

  • 初始状态:我们要转换的单词。
  • 操作符集合:插入、删除、替换。
  • 目标状态:我们想要得到的单词。
  • 路径成本:到达目标所需的编辑次数,这是我们试图最小化的目标。

例如,从 intention 出发,路径的一部分可以是:删除一个字母得到 ntention,插入一个字母得到 eintention,或者替换一个字母得到 ntention。这些都是从 intention 出发,最终可能通过无数种方式转换成其他字符串的路径片段。

所有可能序列的搜索空间是巨大的,因此我们不能天真地遍历所有序列。解决这个“大量可能路径”问题的直觉在于,许多路径最终会到达相同的中间状态。因此,如果两个字符串的第二部分相同,我们不必记录每一种转换方式,只需保留到达每个已访问状态的最短路径即可。

最小编辑距离的形式化定义 🔢

上一节我们介绍了寻找最小编辑距离的直观思路,本节中我们来看看它的形式化定义。

我们将最小编辑距离形式化定义如下:

对于两个字符串,设字符串 X 的长度为 N,字符串 Y 的长度为 M

我们定义一个距离矩阵 D[i][j],它表示字符串 X 的前 i 个字符(1 到 i)与字符串 Y 的前 j 个字符(1 到 j)之间的编辑距离。

因此,整个两个字符串之间的距离将是 D[N][M],因为字符串的长度分别是 N 和 M。


以上就是我们对最小编辑距离的定义。

总结 📚

本节课中我们一起学习了最小编辑距离。我们首先了解了它如何用于衡量字符串相似度,并给出了其正式定义,即通过最少的插入、删除和替换操作次数来转换字符串。我们还探讨了它在拼写纠错、计算生物学、机器翻译和命名实体识别等多个领域的应用。最后,我们介绍了寻找最小编辑距离的基本算法思路及其形式化定义,为后续学习具体的动态规划算法打下了基础。

课程 P70:L11.8 - 评估对话系统 📊

在本节课中,我们将学习如何评估对话系统。我们将探讨如何判断一个对话系统是否成功实现了其目标,并分别介绍聊天机器人和任务型对话系统的评估方法。

聊天机器人的评估方法 🤖

上一节我们介绍了对话系统评估的总体目标,本节中我们来看看如何具体评估聊天机器人。聊天机器人的主要目标是让人感到愉悦,因此其评估主要依赖人工进行。

以下是两种主要的人工评估方法:

参与者评估:由与聊天机器人直接对话的用户进行评分。

观察者评估:由第三方评估员阅读人机对话的文本记录后进行评分。

在C等人使用的参与者评估中,人类评估者与模型进行六轮对话,并在八个维度上使用李克特量表对聊天机器人进行评分。这些维度包括对话质量、避免重复、趣味性、合理性、流畅性、倾听能力、好奇度、拟人度和吸引力。

下图展示了其中三个维度的李克特量表示例,例如评估对话的重复程度。

观察者评估使用第三方标注员查看完整对话的文本。有时我们关注让评估员为系统的每一轮回复打分,例如评估每一轮对话的连贯性。然而,更多时候我们只需要一个高层次的总分来比较系统A和系统B的优劣。

“急性评估指标”就是这样一种观察者评估方法。标注员查看两段独立的人机对话,然后选择其中对话系统表现更好的一段,并回答关于四个属性的问题:吸引力、趣味性、拟人度和知识性。

下图展示了急性评估标注任务的示例,比较两个对话并选择表现更好的系统。

像BLEU分数这类用于评估机器翻译的自动评估指标,通常不用于聊天机器人,因为BLEU分数与人类对聊天机器人的判断相关性很差。开发可能的自动评估指标是一个开放的研究问题。

一种新颖的范式称为“对抗性评估”,其灵感来源于图灵测试。其思想是训练一个类似图灵测试的评估分类器,以区分人类生成的回复和机器生成的回复。一个回复生成系统越能成功“欺骗”评估器,该系统就越好。

任务型对话系统的评估方法 🎯

了解了聊天机器人的评估后,我们来看看任务型对话系统如何评估。如果任务目标明确,我们可以简单地衡量绝对任务成功率。例如,系统是否预订了正确的航班或在日历上添加了正确的事件?

更细粒度一点,我们可以测量槽位错误率,即填充了错误值的槽位百分比。其计算公式为错误槽位数除以总槽位数。

考虑一个系统处理以下句子:“在Gates 104与Chris预约10:30的会议”,并提取出以下候选槽位结构:person: Chris, time: 11:30, room: gates 104。这里,槽位错误率是三分之一,因为时间错了。

除了错误率,也可以使用槽位精确率、召回率和F1分数。槽位错误率有时也被称为概念错误率。

我们既可以衡量任务成功率,也可以计算槽位错误率。

为了更细致地了解用户满意度,我们还可以计算用户满意度评分。即让用户与对话系统交互完成任务,然后填写问卷。

以下是一些示例问题。可以将回答映射到相同范围,然后对所有问题的得分取平均,得到总的用户满意度评分。

我们还可以测量其他因素,例如通过对话总耗时(秒)、总对话轮数或系统对话轮数来衡量效率。

质量成本衡量影响用户对系统感知的其他交互方面。其中一个衡量指标是系统未能返回任何句子的次数,或系统发出拒绝提示的次数。

类似的指标还包括用户不得不打断系统的次数。

总结 📝

本节课中,我们一起学习了评估对话系统的标准方法。我们介绍了聊天机器人主要依赖人工评估(参与者评估和观察者评估),而任务型对话系统则可以通过任务成功率、槽位错误率、用户满意度问卷以及效率和质量成本等指标进行更量化的评估。

课程 P71:L11.9 - 对话系统设计方式与道德问题 👥⚖️

在本节课中,我们将要学习对话系统的设计流程,并探讨与之相关的关键道德问题。对话系统的设计不仅关乎技术实现,更与用户体验、社会影响和伦理责任紧密相连。

用户中心设计原则 🎯

上一节我们介绍了对话系统的重要性,本节中我们来看看其核心设计方法。与语音和语言处理的大多数其他领域相比,用户在对话系统中扮演着更重要的角色。对话设计与人机交互领域密切相关。

对话系统的设计遵循用户中心设计原则。以下是其核心步骤:

首先,研究用户及其任务。这包括通过用户访谈或调查类似系统,来理解潜在用户和任务本质。在此过程中,融入价值敏感设计至关重要,即在设计过程中仔细考虑最终系统可能带来的益处、危害以及相关利益方。

其次,构建模拟和原型。以下是关键工具:

  • Wizard of Oz 系统:在这种系统中,用户以为自己正在与一个软件代理交互,但实际上是由隐藏在软件界面后的人类“巫师”操控的。该名称来源于童话故事《绿野仙踪》,其中的巫师最终被发现只是幕后之人控制的模拟。

最后,我们需要在用户身上迭代测试设计。

对话系统中的道德问题 ⚖️

了解了设计流程后,我们转向一个同样重要的维度:道德考量。道德问题在人工智能代理中一直至关重要。玛丽·雪莱在200多年前的《弗兰肯斯坦》中首次提出了这些问题,引发了在不考虑伦理和人文关怀的情况下创造代理的思考。

在众多具有道德维度的问题中,以下三点尤为关键:

  • 安全性:我们不希望驾驶员因为与聊天机器人交谈而分心导致车祸。
  • 代表性伤害:我们不希望系统贬低特定的社会群体。
  • 隐私性:我们不希望系统泄露私人信息。

具体道德挑战与案例 🔍

现在,让我们具体看看这些道德挑战在实际中如何体现。

安全性在对话系统进入现实世界时变得非常重要。如果我们为心理健康构建聊天机器人,避免说错话是极其重要的。如果我们构建用于与车内人员交谈的聊天机器人,它必须能感知环境和驾驶员的注意力水平。

一个广为人知的滥用和代表性伤害案例是微软2016年的Tay聊天机器人。该机器人被设定为一个非正式交流的年轻女性人格,并设计为通过互动向用户学习。结果,Tay开始发布带有种族歧视言论、阴谋论和人身攻击用户的信息,并在16小时后被下线。这个案例表明,通过原型设计来建模用户响应至关重要。

这类滥用问题也存在于训练大多数对话系统的数据集中。Henderson等人发现,许多标准对话训练集以及基于它们训练的对话模型中,都存在仇恨言论的偏见。

最后,隐私性非常重要,尤其是对于无处不在的对话系统。Henderson等人表明,任何对话系统都可能意外泄露密码等信息。许多对话系统还存在有意向企业开发者或广告商发送信息的有意泄露。Compan等人表明,设计保护隐私的对话系统是可能的。

总结 📝

本节课中我们一起学习了对话系统的设计流程与核心道德问题。设计对话系统时,遵循用户中心原则,通过研究用户、构建原型和迭代测试来优化体验。同时,必须时刻将用户和潜在危害放在心上,严肃对待安全性、代表性伤害和隐私性等伦理挑战,以确保技术发展与社会责任并重。

课程 P72:L12.1 - 推荐系统介绍 🎯

在本节课中,我们将介绍推荐系统任务及其核心算法——协同过滤。我们将从推荐系统的应用场景开始,逐步深入到其形式化模型和主要方法。

推荐系统任务示例

上一段我们提到了推荐系统,现在来看一个具体例子。顾客 W 正在播放 Ella Fitzgerald 的歌曲。

我们应该接下来为他推荐什么?推荐系统任务的常见设定是:用户正在搜索某物(可能是一件产品、一条新闻或一个网站),而系统返回推荐结果。

事实证明,这是一个巨大的应用领域,每天有数十亿人使用。例如,亚马逊或阿里巴巴等产品销售商,苹果、Facebook、谷歌等新闻聚合网站,YouTube 或 Bilibili 等视频网站,以及 Netflix、Spotify、Apple Music 等提供点播电影或音乐的服务商。

推荐方式概览

当然,推荐可以通过多种方式实现。以下是几种常见方式:

  • 有人可以策划一份喜爱列表,或某个平台可以发布一份必备物品列表。
  • 我们可以依赖用户数据,但通过简单的聚合方式,如前十名列表或最受欢迎列表。
  • 然而,我们今天将重点关注的,是这里的第三种方法:为个体用户量身定制推荐。

了解这些个性化推荐系统的工作原理,对于构建实用的新闻或产品推荐器至关重要。同时,这些系统也涉及伦理层面,推荐系统可能传播错误信息和极端观点。

要解决这个问题,需要理解它们的工作原理。

形式化模型

现在,让我们来看一个形式化模型。我们将有用户和物品。

用户对某些物品有偏好,这用一个效用矩阵来表示。该矩阵为每个用户-物品对提供一个值,代表我们已知的该用户对该物品的偏好程度。

值来自一个有序集合。例如,1到5的整数,代表用户为该物品给出的星级评分。

该矩阵是稀疏的,意味着大多数条目是未知的。我们对该用户对该物品的偏好没有明确信息。

以下是一个效用矩阵示例,代表用户 Anita、Beyonce、Calvin 和 David 对电影的评分,范围为1到5,5为最高分。空白处代表用户未对该电影评分的情况。

电影名称是《哈利波特》1、2、3,《暮光之城》和《星球大战》1、2、3。

推荐系统的目标是预测效用矩阵中的空白。例如,Anita 会喜欢《星球大战2》吗?

在这些课程中,我们将讨论该领域的三个关键问题。首先,我们需要获取评分。

其次,今天的主要问题是预测未知评分。

请注意,我们不需要预测效用矩阵中的每一个空白条目。我们只需要找到每一行中可能较高的某些条目。换句话说,我们不关心推荐用户可能讨厌的东西。

最后,我们需要某种评估方式。

如何获取评分?

我们如何获取评分?我们可以直接明确地询问人们(自愿或付费),这在某些任务中可能有帮助。

但更多时候,我们依赖隐式信号,从用户行为中学习评分。如果有人购买了一本书、选择了一首歌或看完了一个 YouTube 视频,那就暗示它应该有一个高评分。

这不是一个完美的解决方案。也许你看了视频但讨厌它,或者你买了东西但讨厌它。尽管如此,这是一个合理的启发式方法。

请注意,这种评分系统实际上只有一个值。1 表示用户喜欢该物品,仅此而已。因此,我们经常会看到带有此类数据的效用矩阵,用零而不是空白表示,但重要的是要记住,零并不比一的评分低,它根本就是没有评分。

效用矩阵非常非常稀疏,大多数人没有对大多数物品进行评分。

我们将看到所谓的冷启动问题:新物品没有评分,新用户没有历史记录。

有时,如果你注册一个音乐网站,他们会问你一些问题,比如你喜欢什么流派,以帮助缓解冷启动问题。这增加了用户留存率,但风险是让用户做额外的事情。

推荐系统的主要方法

推荐系统主要有三种方法。我们将讨论基于内容的系统和协同过滤系统。我们将非常简要地提及更现代的第三种架构:基于神经嵌入的潜在系统。

让我们直观地看看基于内容的推荐和协同过滤推荐之间的区别。

这是顾客 W,正在播放 Ella Fitzgerald 的歌曲。接下来应该为他播放什么?这里有一个信息来源:一个歌曲或音乐家具有内容特征的数据库。我们知道 Ella Fitzgerald 是 20 世纪中期的爵士乐传奇歌手,录制了许多著名的二重唱。

嗯,这些内容特征告诉我们 Louis Armstrong 也是 20 世纪中期的爵士乐传奇歌手,录制了许多著名的二重唱。让我们推荐 Louis Armstrong。这被称为基于内容的推荐。

另一个信息来源来自顾客 D,他也播放了 Ella Fitzgerald 的歌曲,但也播放了 Louis Armstrong。因此,我们可能也会向顾客 W 推荐 Louis Armstrong。

依赖其他用户选择信息的系统被称为协同过滤推荐系统。

我们已经介绍了推荐系统的概念,现在让我们看一些细节。

总结

本节课中,我们一起学习了推荐系统的基本概念。我们了解了推荐系统的广泛应用场景,学习了其形式化模型——以用户和物品构成的稀疏效用矩阵。我们还探讨了获取评分的不同方式(显式与隐式),并简要对比了基于内容的推荐与协同过滤推荐这两种核心方法的原理。在接下来的课程中,我们将深入探讨协同过滤算法的具体实现。

课程 P73:L12.2 - 基于内容的推荐系统 🎬

在本节课中,我们将要学习推荐系统的第一种方法:基于内容的推荐。这种方法的核心思想是利用物品本身的特征来进行推荐,而不是依赖其他用户的行为数据。

核心思想与流程 📝

基于内容推荐的主要思想是:向用户X推荐那些与他们已经给予高评价的物品相似的物品。

“相似”在这里指的是内容上的相似。例如,在电影推荐中,我们可以推荐拥有相同演员、导演或类型的电影。对于网站或博客,我们可以推荐具有相似标签(如烹饪、新闻、科学)或关键词的其他网站。

以下是该方法的行动流程:

  1. 用户喜欢某些物品。
  2. 我们拥有这些物品的特征档案。
  3. 我们可以根据用户喜欢的物品,构建一个总结其偏好的用户档案
  4. 在数据库中寻找与用户档案匹配的物品。
  5. 将这些匹配的物品推荐给用户。

构建物品档案 🧱

我们需要为每个物品创建一个档案,即一个特征向量

  • 对于电影,特征可能包括:类型、导演、演员
  • 对于文本,特征通常是一组关键词

如何选取这些特征?

  • 部分特征可以手动设定,例如,根据领域知识确定“电影类型”是重要特征。
  • 部分特征可以自动学习。对于文本,我们可以计算词语的 TF-IDF 值,并选取那些在训练集中具有高TF-IDF值的词语作为特征。

我们可能不直接使用TF-IDF值,而是设定一个阈值。为每个超过阈值的词语在向量中创建一个维度。如果文档包含该词,则该维度值为1,否则为0。

以下是两个物品档案的示例:

  • 电影X(约翰尼·德普主演的海盗电影):[动作=1, 冒险=1, 约翰尼·德普=1, 喜剧=0, 梅丽莎·麦卡西=0]
  • 电影Y(梅丽莎·麦卡西主演的喜剧电影):[动作=0, 冒险=0, 约翰尼·德普=0, 喜剧=1, 梅丽莎·麦卡西=1]

每个物品档案都是一个由0和1组成的二进制向量。

处理非布尔型特征 🔢

如果我们想加入实数或有序特征呢?例如,加入一个表示“电影平均评分”的特征。这个平均值是一个实数。

这没有问题,余弦相似度计算可以处理向量中同时包含二进制值和实数值的情况。

然而,我们可能需要对非布尔型分量进行缩放,使用一个缩放因子 α,以确保它们既不会主导计算,也不会变得无关紧要。

假设我们计算电影X和电影Y向量之间的余弦相似度。它们的点积是 2 + 12α²。每个向量的长度分别是 √(5 + 9α²)√(5 + 16α²)。因此,向量间夹角的余弦值为:
cosθ = (2 + 12α²) / (√(5 + 9α²) * √(5 + 16α²))

  • 如果选择 α = 1(即直接使用评分值),则该表达式的值为 0.82
  • 如果使用 α = 2(即加倍强调评分值),则余弦值变为 0.94,相似度更高。这是因为我们强调了评分(3分和4分)是相似的。
  • 相反,如果 α = 0.5,则会得到较低的相似度 0.69

因此,α的选择非常有影响力,我们可能需要在一个验证集上进行调整,以最大化我们的评估指标。

构建用户档案 👤

我们不仅需要创建描述物品的向量,还需要创建具有相同维度、描述用户偏好的向量。

我们可以从表示用户与物品之间联系的效用矩阵中构建用户档案。

我们可以将用户喜欢的物品的特征值,估计为这些物品档案的某种聚合,例如,对用户评过分的物品档案取平均值

例如,假设物品是由布尔型档案表示的电影,其分量对应演员。效用矩阵中,如果用户看过该电影,则值为1,否则为空。

如果用户看过的电影中,有20%包含演员梅丽莎·麦卡西,那么该用户的档案中,梅丽莎·麦卡西对应的分量值就是 0.2

进行推荐 🎯

现在,我们拥有了相同维度下的用户档案向量和物品档案向量。

我们可以通过计算用户档案与物品档案之间的余弦距离,来估计用户对某个物品的偏好程度,并找到最相似的物品进行推荐。

优点与缺点 ⚖️

基于内容的方法有许多优点:

  • 不依赖其他用户的稀疏信息
  • 可以为有独特品味的用户推荐。例如,如果用户刚听了一首冷门歌曲,我们可以推荐另一首冷门歌曲。
  • 可以推荐新的或罕见的物品,因为它们都有可供选择的特征。
  • 算法具有透明度,我们可以通过列出特征向用户解释推荐的原因。

另一方面,基于内容的方法也存在缺点:

  • 需要人工设计特征,这很困难。
  • 存在新用户问题,必须为新用户构建档案。
  • 可能给出多样性不足的建议,永远不会推荐用户内容档案之外的物品。
  • 与此相关,该方法无法利用其他用户的质量判断,而这正是协同过滤方法所擅长的。

总结 📚

本节课中,我们一起学习了如何根据物品的内容特征来构建推荐系统。我们了解了构建物品和用户档案的方法,如何使用余弦相似度进行匹配,以及这种方法的优缺点。基于内容的推荐是推荐系统领域一个基础且重要的方向,特别适用于解决冷启动和可解释性问题。

课程 P74:L12.3 - 基于用户的协同过滤 👥

在本节课中,我们将要学习协同过滤,并首先介绍其基于用户的版本。协同过滤不依赖于物品的内容特征进行推荐,而是通过寻找相似用户,并推荐这些用户喜欢的物品来实现。

从内容过滤到协同过滤 🔄

上一节我们介绍了基于内容的推荐方法。本节中,我们来看看协同过滤,特别是基于用户的协同过滤。

在协同过滤中,我们不再使用物品的内容特征来决定推荐什么。我们将找到相似的用户,并推荐他们喜欢的物品。

基于用户的协同过滤原理 🧠

让我们看看基于用户的协同过滤是如何工作的。

假设我们有一个用户 X,以及一个他尚未评分的物品 I。我们已经从用户 X 对其他物品的评分中了解了他的偏好。

现在,我们找到一组 N 个其他用户,他们的评分模式与用户 X 相似。然后,我们基于这组 N 个用户的评分,来估计用户 X 对物品 I 的未知评分。

因此,我们需要找到相似的用户,并推荐他们喜欢的物品。

如何定义用户相似度? 📊

为了找到相似用户,我们将每个用户表示为效用矩阵中的一行向量。如果两个用户的向量相似,我们就认为他们相似。

例如,用户 A 和 B 可能相似,因为他们都喜欢《哈利·波特1》。而用户 A 和 C,我们希望他们不相似,因为 A 喜欢《暮光之城》,C 喜欢《星球大战》,并且他们都讨厌对方喜欢的电影。

基础余弦相似度的问题 ⚠️

让我们将评分转化为向量。假设两个用户对五个物品有如下星级评分:用户 X 对物品1评1星,对物品5评3星,对物品2和3没有评分。暂时,我们用0来表示空白评分。

现在我们可以用余弦相似度来计算相似性。但这种表示方法存在问题,会导致不直观的结果。

再次考虑我们的效用矩阵。直观上,我们希望 A 与 B 非常相似,而与 C 非常不同。A 和 C 的意见完全相反。但余弦相似度并不将1星视为负面评价,也没有正确处理缺失评分(将其视为无信息),而是将0视为与1非常接近。

我们希望 A 与 B 的相似度远大于 A 与 C 的相似度。但实际上,如果我们计算余弦相似度,两者的相似度非常接近。A 与 B 的相似度确实比与 C 的相似度稍高,但仅高一点点。

余弦相似度的问题在于,它没有真正捕捉到 A 与 C 的对立关系。另一个问题是,我们希望针对评分者进行标准化。例如,评分者 D 对所有物品的评分都一样,他的评分信息量不大。

解决方案:均值中心化 ✅

我们希望有一种方法能将0视为无信息,将低分视为负面评价,并将对所有物品评分一致的人视为没有帮助。解决方案是对用户进行均值中心化处理,即从每一行中减去该用户的平均评分。

以下是具体步骤:

  1. 计算每个用户(行)的平均评分(忽略缺失值)。
  2. 将该平均评分从该用户的每个已有评分中减去。
  3. 不处理缺失值,它们最终仍保持为0。

经过均值中心化后,0表示没有信息。0的出现可能是因为完全没有评分,也可能是因为评分者信息量不足。请注意,评分者 D 对所有物品的评分都一样,现在他对这些电影的看法不再具有信息量。一个很低的评分变成了负值。相反的意见则反映在符号相反的向量中,这些向量将指向不同的方向。

这种均值中心化在处理自然语言和数据的许多问题中都很有用,特别是当我们有一个像评分或情感这样在直觉上具有正负两端的尺度,并且存在未评分值时。

改进的相似度算法 📈

现在,如果我们在均值中心化的效用矩阵中计算余弦相似度,会发现 A 和 C 的值相反,他们的向量非常不同。实际上,他们的余弦相似度为负值。

一个快速的术语说明:减去均值称为“均值中心化”,而不是“归一化”。“归一化”通常指除以一个范数以将其转化为概率。但教科书和常见用法有时会重载“归一化”这个术语,所以你有时会看到这也被称为归一化。

让我们看看我们将要使用的余弦相似度的最终修改版本,我们还要做一项更改。

假设我们有用户评分向量。对于每个用户,我们计算其平均评分(忽略缺失值)。例如,用户 X 的平均分是 (1+1+3)/3 = 5/3,用户 Y 的平均分计算结果相同。然后我们从他们的每个评分中减去这个平均值,不处理缺失值,缺失值保持为0。

现在,对用户 X 进行均值中心化,我们得到 1 - 5/3 = -2/3,依此类推。我们不改变0值。

现在我们要增加一项更改:我们只保留两个用户都评过分的物品。因此,我们将移除这三个未被两个用户同时评分的物品,得到两个较短的向量,然后计算这两个较短向量之间的余弦相似度。

以下是该算法的总结:

均值中心化重叠项余弦相似度算法(用于计算用户-用户相似度):

  • 我们只查看用户 X 和用户 Y 都评过分的物品。
  • 对于每一个这样的物品,我们减去相应用户的平均评分。
  • 然后计算这些均值中心化后的向量的余弦相似度。

顺便提一下,均值中心化重叠项余弦相似度是皮尔逊相关系数的一个轻微变体。

如何进行预测推荐? 🎯

现在我们有了相似度函数,可以看看如何为用户 X 的某个未评分物品 I 做出推荐预测。

R_X 为用户 X 的评分向量,N 为与 X 最相似的 K 个用户的集合,且这些用户已对物品 I 评分。

对用户 X 关于物品 I 的预测,我们可以简单地说:取这 K 个“像我一样的人”的评分的平均值。

更优的方法是,我们可以将 I 的评分预测为这些相似用户评分的加权平均值。具体公式如下:

预测评分公式:
预测评分 = Σ (相似度_Y * 评分_Yi) / Σ (相似度_Y)

其中,求和遍历所有相似邻居 Y,相似度_Y 是用户 Y 与用户 X 的相似度,评分_Yi 是用户 Y 对物品 I 的评分。通过除以相似度之和来进行归一化。

总结 📝

本节课中,我们一起学习了基于用户的协同过滤方法。

我们了解到,协同过滤通过寻找相似用户来产生推荐,而不是分析物品内容。核心步骤包括:

  1. 计算用户相似度:使用改进的均值中心化重叠项余弦相似度,它比基础余弦相似度更能反映用户偏好的异同。
  2. 生成预测评分:根据最相似的 K 个邻居对目标物品的评分,进行加权平均来预测目标用户对该物品的评分。

这种方法能够有效地利用用户群体的集体智慧,为用户发现他们可能喜欢但尚未接触过的物品。

课程 P75:L12.4 - 基于物品的协同过滤 🎬

在本节课中,我们将要学习协同过滤的另一个版本,称为基于物品的协同过滤。我们将了解其核心算法、计算步骤,并通过一个具体例子来演示其工作原理。最后,我们会讨论该方法的优缺点。

上一节我们介绍了基于用户的协同过滤算法,它通过寻找与你相似的用户来为你推荐物品。本节中,我们来看看一个通常效果更好的替代视角,即基于物品的协同过滤。

这种方法的核心思想是:对于一个给定的未评分物品 I,我们推荐那些与你喜欢的物品相似的物品。具体来说,我们会找到与物品 I 相似的物品,然后根据你对这些相似物品的评分,来估算你对 I 的评分。其使用的相似度度量和预测函数,可以与基于用户的模型完全相同。

算法可以描述为:将一个未评分物品 I 的预测评分,计算为我已评分的、与 I 相似的其他物品评分的加权平均值。

更正式地,假设我们有一个集合 N,包含了我已评分且与这个未评分物品 I 相似的物品。对于集合 N 中的每一个相似物品 J,我们拥有我的评分 r_{i, J},以及该物品 J 与目标物品 I 的相似度 sim(I, J)

那么,我对物品 I 的预测评分 \hat{r}_{i, I}公式如下:

\hat{r}{i, I} = \frac{\sum (sim(I, J) \times r_{i, J})}{\sum_{J \in N} |sim(I, J)|}

这本质上就是我对其他与 I 相似的物品的评分,以它们与 I 的相似度为权重的加权平均值。

现在,让我们通过一个例子来逐步理解这个过程。这里有一个用户对电影评分的效用矩阵,我们设定 n=2,这意味着我们将通过平均该用户评分的、与目标电影最相似的两部电影,来预测其对未评分电影的评分。

假设我们想知道用户5对电影1(Mo1)的看法,即我们需要填充矩阵中的这个单元格。

首先,我们需要进行邻居选择。目标是找到与电影1最相似、且被用户5评分过的两部电影(因为 n=2)。我们将使用均值中心化的物品重叠余弦相似度来计算。

以下是计算步骤:

  1. 计算每部电影(每一行)的平均评分。
  2. 从该行的每个评分中减去这个平均分,进行均值中心化处理。
  3. 计算行与行之间的物品重叠余弦相似度,以找出哪些电影与电影1最相似。

为了节省时间,这里直接给出提示:最相似的两部电影是电影3(Mo3)和电影6(Mo6)。接下来我们演示这两部的计算。

首先,为每部电影做均值中心化。以电影1(第一行)为例,其评分是 [1, 3, 5, 5, 4]。平均分为 (1+3+5+5+4)/5 = 18/5 = 3.6。从每个评分中减去3.6,得到新的向量:[-2.6, -0.6, 1.4, 1.4, 0.4]。

这是整理后的结果。

我们对所讨论的三部电影(未评分的电影1,以及其最近邻电影3和电影6)都进行同样的操作。分别计算电影3和电影6的平均分并减去,这样低评分(如1分)就会变为负值。

现在,我们需要计算这些电影与电影1的余弦相似度。为了避免视频过长,我们仅计算电影3和电影6与电影1的相似度。

对于电影1和电影3,我们计算物品重叠余弦相似度。这需要计算点积,意味着我们将对两个向量中都存在的用户评分元素进行相乘。电影1和电影3的共同评分用户是用户1、用户9和用户11。因此,我们将这两个向量视为在这三个维度上的向量。

这是电影1和电影3的余弦相似度计算过程。它们在这三个用户(1,9,11)上都有值。我们使用这三个值进行计算。

对于电影1和电影6,它们的共同评分用户是用户1、用户3和用户11。我们同样计算得到余弦相似度。

最终,我们得到电影3与电影1的相似度约为 0.658,电影6与电影1的相似度约为 0.786

在实际操作中,我们需要计算所有电影与电影1的相似度,才能找出最相似的两部。但这里我们已知其他电影的相似度估计值都更小,因此我们只需要这两部最相似的电影(因为 n=2)。

现在,我们取用户5对电影3的评分(2分)和对电影6的评分(3分),以它们的相似度为权重,计算加权平均值,来填充电影1的预测评分。

如果使用非加权平均,(2+3)/2 = 2.5。但加权平均的结果略有不同,如下方计算所示。加权平均通常效果稍好。基本上,它表明电影6比电影3与电影1更相似一点,因此电影6的评分(3分)在计算中会获得稍高的权重。

在实践中,基于物品的方法通常优于基于用户的方法。原因如下:

以下是基于物品协同过滤的主要优势:

  • 物品分类更简单:物品往往可以用简单的术语分类。例如,音乐通常属于单一流派。很难想象一首歌同时属于碧昂丝的风格和巴赫的巴洛克风格。
  • 物品属性稳定:物品是相对恒定的。一首巴赫的赋格曲始终是巴赫的赋格曲。
  • 用户兴趣动态变化:而人是动态的,他们的品味会发生变化。

其结果是,发现相似的物品比发现相似的用户更容易。

总结来说,协同过滤适用于任何类型的物品,我们不需要预先定义描述物品的特征集。然而,协同过滤也有一些缺点。

以下是协同过滤面临的一些挑战:

  • 冷启动问题:无论是物品相似还是用户相似,都需要系统中有足够多的用户才能找到匹配项。
  • 稀疏性问题:用户评分矩阵非常稀疏,很难找到对相同物品集进行过评分的用户。
  • 首次评分者问题:难以推荐一个尚未被任何人评分过的物品。
  • 流行度偏见:难以向具有独特品味的人推荐物品,系统倾向于推荐热门物品,这可能导致诸如信息茧房和观点极端化等问题。

本节课中,我们一起学习了协同过滤的两种主要实现方式:基于用户的方法和基于物品的方法。我们重点剖析了基于物品协同过滤的算法原理、计算示例,并对比了其相对于基于用户方法的优势与仍然存在的普遍挑战。

📚 课程 P76:L12.5 - 在PA6数据集上实现简化的物品协同过滤

在本节课中,我们将学习如何在编程作业6(PA6)的数据集上实现一个简化版本的物品协同过滤算法。这个简化版本在我们的小型数据集上表现更好。


🎯 概述

在编程作业6中,我们将实现一个简化的物品协同过滤算法。这个算法之所以被简化,是因为它恰好在我们的小型数据集上效果更佳。


🔄 数据预处理

首先,我们需要对数据进行预处理。具体来说,我们将把所有评分值转换为+10和-1。

因此,原本的用户-电影效用矩阵会发生变化。转换后,所有“喜欢”的评分变为1,“无评分”变为0,“不喜欢”的评分变为-1。

假设我们已经按照上述方式完成了数据转换,助教们推荐了一些技巧,这些技巧在我们的小型数据集上效果更好。


⚙️ 算法调整建议

以下是针对我们小型数据集的一些算法调整建议:

  1. 不要对用户评分进行均值中心化:直接使用原始的+10和-1值。
  2. 不要进行归一化:在计算预测评分时,不要除以相似度的总和。
  3. 使用简单的余弦相似度:计算物品相似度时,使用普通的余弦相似度,而不是经过均值中心化的物品重叠余弦相似度。

📝 简化算法步骤

综上所述,以下是我们为小型数据集设计的简化算法步骤:

  1. 用户X对电影M1和M2给出了评分。
  2. 对于数据集中的每一部电影I,我们计算用户X对它的预测评分。
  3. 预测评分的计算公式为:
    R_x(I) = Σ [ rating(X, J) * sim(I, J) ]
    其中,求和遍历用户X已评分的所有电影J,rating(X, J)是用户X对电影J的评分,sim(I, J)是电影I和J之间的相似度。
  4. 相似度sim(I, J)使用普通的余弦相似度计算。
  5. 最后,我们推荐预测评分R_x(I)最高的电影I给用户X。

💎 总结

本节课中,我们一起学习了如何在PA6的小型数据集上实现一个简化的物品协同过滤算法。我们了解了数据预处理(将评分转换为+10和-1),掌握了针对小数据集的算法调整技巧(不进行均值中心化和归一化,使用普通余弦相似度),并明确了简化的预测评分计算步骤。这个简化版本的计算过程比标准版本更加直接明了。

📚 课程 P77:L13.1 - 网络锚点文本

在本节课中,我们将要学习网络与链接中的一个核心概念:锚点文本。我们将探讨锚点文本如何帮助搜索引擎理解网页内容,以及它在信息检索和其他语言处理任务中的重要作用。


🌐 锚点文本简介

上一节我们介绍了网络与链接的基本概念,本节中我们来看看锚点文本的具体作用。

我们可以将网络视为一个有向图,其中每个页面都可以通过链接指向另一个页面,而链接的实现方式就是通过其锚点文本。

关于锚点文本在决定搜索页面相关性时所扮演的角色,我们可以做出两个基本假设。

第一个假设是:页面之间的超链接表明作者认为第二个页面与第一个页面相关。因此,超链接可以被视为一个页面向另一个页面投出的“一票”,表明目标页面是相关的。

第二个假设是:超链接的锚点文本及其周围的文本,是对目标页面的描述。


🔍 锚点文本示例

为了更好地理解锚点文本的作用,让我们来看一个具体的例子。

假设我们正在尝试寻找IBM的官方网站。IBM的主页本身可能主要由图形和徽标构成,文本内容很少。可能还有一个版权页面,上面布满了“IBM”这个词。此外,可能还存在一个垃圾页面,它通过人为地高频率重复“IBM”这个词来试图干扰搜索结果。

那么,我们如何找到真正的IBM官网呢?直觉告诉我们,来自数百万个不同页面的锚点文本都指向IBM官网,这会发出一个强烈的信号。这些锚点文本可能包括“IBM”、“IBM公司”或“IBM主页”等。

例如,一个链接的锚点文本是“IBM主页”,这提示我们该链接指向的是IBM。如果我们汇总所有指向该页面的链接的锚点文本,就能获得一个非常强大的线索,帮助我们判断哪个页面是真正相关且优质的。


📇 索引中的锚点文本

在索引文档时,我们会将所有指向该文档的链接的锚点文本,一并纳入该文档的索引中。

以下是具体操作方式:

  • 我们有一个IBM页面,网址是 www.ibm.com
  • 有些链接称它为“IBM”,有些称它为“IBM的”,还有些称它为“蓝色巨人”。
  • 我们将所有这些锚点文本都添加到 www.ibm.com 这个URL(即这个文档)的索引中。

这样,即使目标页面本身文本内容不多,其索引也会因为汇集了大量外部描述而变得信息丰富,有助于搜索引擎更准确地理解该页面的主题。


⚠️ 锚点文本的副作用与解决方案

然而,索引锚点文本也可能带来副作用,例如“谷歌炸弹”现象。这种现象是指外部人员创建大量指向某个页面的链接,但这些链接的锚点文本却暗示该页面是关于其他内容的。

我们可以通过以下方法解决部分问题:根据链接来源页面(即锚点所在页面)的权威性,为锚点文本赋予不同的权重。

具体来说,如果一个页面(例如来自 cnn.comyahoo.com 这类我们认定为权威的网站)指向另一个页面,那么我们可以更信任来自该页面的锚点文本。其公式可以简化为:
锚点文本权重 = f(来源页面权威度)


💡 锚点文本的其他应用

除了信息检索,锚点文本还有许多其他有价值的应用。

以下是锚点文本的几个重要应用场景:

  • 寻找同义词:锚点文本是发现同义词的绝佳途径。例如,如果一个页面指向“美国联邦储备银行”,我们可以查看所有指向它的页面的锚点文本。这些文本可能包括“美联储”、“美国联邦储备委员会”或“联邦储备银行”等,这告诉我们这些词都是“美国联邦储备银行”的同义词。
  • 跨语言翻译:我们不仅可以在一种语言内进行此操作,还可以跨多种语言。例如,我们可以看到其他语言的链接指向联邦储备银行的网站,从而获得“Federal Reserve”在外语中的翻译,这非常方便。
  • 辅助句法分析:甚至在单个页面内部,锚点文本也能通过为解析器提供成分边界来帮助我们。锚点文本通常描述一个名词短语,因此通过观察锚点文本的边界,可以帮助我们的解析器更好地分析包含该锚点文本的句子结构。

📝 课程总结

本节课中,我们一起学习了网络锚点文本的核心概念与应用。

我们了解到,锚点文本不仅是网页之间投票和描述的重要载体,能极大地增强搜索引擎索引和理解页面的能力,还在同义词发现、跨语言翻译和句法分析等语言处理任务中发挥着重要作用。同时,我们也认识到需要谨慎处理锚点文本可能带来的问题,例如通过加权策略来应对“谷歌炸弹”等滥用情况。

总而言之,网络锚点文本是网络信息检索和语言处理中一个非常有用的工具。

📚 课程 P78:L13.2 - PageRank:简介与马尔可夫链

在本节课中,我们将学习 PageRank 算法的基本思想,并了解其背后的数学模型——马尔可夫链。PageRank 是链接分析中最重要的应用之一,它通过分析网页之间的链接关系来衡量网页的重要性。

🔗 链接分析的重要性

在信息检索中,我们经常讨论查询与文档之间在词汇上的重叠,例如使用 TF-IDF 等技术。然而,判断一个网页是否优质,除了词汇匹配度,还有其他因素。一个直观的想法是:一个非常受欢迎的网页很可能是一个好网页。那么如何衡量受欢迎程度呢?一个简单的衡量标准是查看有多少其他网页指向该网页。被许多网页指向的页面,很可能是一个好页面。

以下是两种使用链接计数来衡量页面受欢迎程度的简单方法:

  • 无向受欢迎度:也称为节点的度(Degree),计算公式为 度 = 入链数 + 出链数。例如,页面 B 有 3 个入链和 2 个出链,则其度为 5。
  • 有向受欢迎度:仅使用入链数。例如,页面 B 的入链数为 3。

⚠️ 简单链接计数的问题

简单链接计数方法的一个主要问题是容易被操纵(Spam)。无论是使用节点的度还是仅使用入链数,都存在这个问题。

💡 PageRank 的核心思想

PageRank 的直觉是:一个链接的重要性,取决于它来自的页面本身的重要性。我们不仅仅计算入链的数量,还要考虑这些入链的来源。如果一个链接来自一个非常重要的页面,那么这个链接的权重就应该更高。

我们将设计一个迭代算法来测量页面的重要性,并将这种重要性沿着链接传递出去。

🚶 随机游走模型

我们可以想象一个浏览器在网页上进行随机游走。我们从随机一个页面开始,在每一步,我们以均等的概率沿着当前页面上的一个链接前进。例如,如果一个页面有三个链接,那么每个链接被点击的概率都是三分之一。

PageRank 的直觉是:在经过大量游走达到稳定状态后,每个页面都有一个长期的访问率。我们将使用这个长期访问率作为页面的得分,即该页面的 PageRank。

然而,纯粹的随机游走还不够好,因为网络上充满了“死胡同”(Dead Ends)。如果一个页面没有出链,随机游走到达该页面后就会被困住。

🪂 引入“传送”机制

为了解决死胡同问题,我们引入了“传送”(Teleporting)机制。其工作原理如下:

  • 当我们到达一个死胡同时,我们直接跳转到整个网页集合中的任意一个随机页面。
  • 即使我们不在死胡同,我们也有一个固定的概率(例如 10%)跳转到随机页面。这个概率参数我们称之为 α
  • 在剩余的概率(例如 90%)下,我们仍然会沿着当前页面的一个随机出链前进。

通过引入传送机制,我们解决了局部被困的问题,并且可以计算每个页面被访问的长期稳定概率,即 PageRank。

🔄 马尔可夫链

为了计算 PageRank,我们需要从马尔可夫链开始。马尔可夫链是对随机游走的一种抽象,我们将用它来模拟网络冲浪者的行为。

一个马尔可夫链包含一组状态和一个 n × n 的转移概率矩阵 P。在马尔可夫链的每一步,我们处于某个状态。矩阵 P 中的元素 P(i, j) 表示在状态 i 的条件下,下一步转移到状态 j 的概率。对于给定的状态 i,转移到所有可能状态 j 的概率之和必须为 1。

在我们的模型中,每个状态代表一个网页,转移概率代表从一个页面跳转到另一个页面的概率。我们可以从网络的邻接矩阵生成这个转移概率矩阵 P

📊 构建转移概率矩阵

更正式地,我们这样构建转移概率矩阵 P

  1. 邻接矩阵:设 A 为网络的邻接矩阵,如果存在从页面 i 到页面 j 的超链接,则 A(i, j) = 1,否则为 0。
  2. 处理死胡同:如果节点 i 是一个死胡同(没有出链),那么其对应的行(在 A 中全为 0)在 P 中将被替换为 1/n,其中 n 是网页总数。这意味着从该页面跳转到任何其他页面的概率均等。
  3. 处理有出链的节点:对于有出链的节点(假设有 k 个出链,k > 0):
    • 首先,将邻接矩阵 A 的对应行归一化,使该行元素之和为 1(即每个出链的初始概率为 1/k)。
    • 然后,将这个归一化的行向量乘以 (1 - α)。这代表了以概率 (1 - α) 沿着随机出链前进的部分。
    • 最后,给矩阵 P 的每一个元素都加上 α / n。这代表了以概率 α 进行随机传送的部分。

公式化描述:对于每个页面 i 到页面 j 的转移概率 P(i, j),可以计算为:
P(i, j) = α * (1/n) + (1 - α) * (A(i, j) / k_i)
其中,k_i 是页面 i 的出链总数(如果 k_i = 0,则公式中第二部分视为 0,并令 P(i, j) = 1/n)。

📝 示例计算

假设我们有一个包含 3 个节点(1, 2, 3)的小型网络,其邻接矩阵 A 和对应的出链情况如下:

  • 页面 1 -> 页面 2
  • 页面 2 -> 页面 1, 3
  • 页面 3 -> 页面 2

情况一:α = 0(无传送)
我们只需将 A 的每一行归一化,得到转移概率矩阵 P

情况二:α = 0.5
以页面 1 为例进行计算:

  • 传送部分:概率 α = 0.5,跳转到任一页面的概率为 1/3。向量为 [1/3, 1/3, 1/3]
  • 链接部分:概率 (1 - α) = 0.5,页面 1 只有一个出链指向页面 2,所以概率向量为 [0, 1, 0]
  • 最终行向量:0.5 * [1/3, 1/3, 1/3] + 0.5 * [0, 1, 0] = [1/6, 2/3, 1/6]

对每个页面重复此计算,即可得到完整的转移概率矩阵 P

🎯 本节总结

本节课中,我们一起学习了 PageRank 算法的基本直觉,它通过模拟随机冲浪者的行为(结合随机游走和传送)来衡量网页的重要性。我们引入了马尔可夫链作为其数学模型,并详细讲解了如何根据网页的链接结构(邻接矩阵)和传送概率 α 来构建转移概率矩阵。在下一节中,我们将探讨如何实际计算 PageRank 值本身。

📊 课程 P79:L13.3 - PageRank 计算方法

在本节课中,我们将学习如何计算 PageRank。PageRank 是衡量网页重要性的核心算法,其核心思想是通过模拟一个随机冲浪者在网络中的随机游走来评估每个网页的长期访问概率。


🧭 概率向量与随机游走

为了计算 PageRank,我们首先需要理解随机游走的含义。为此,我们引入一个概率向量。

概率向量是一个行向量 X,它描述了随机游走在任意时刻所处的位置。假设网络中有 n 个节点(或状态),向量 X 的每个元素 xᵢ 表示当前时刻处于状态 i 的概率。

一个简单的初始状态可以是:X = [0, 0, ..., 1, ..., 0],其中只有位置 i 为 1,表示我们从状态 i 开始。更一般地,X 中的元素可以是任意概率值,但所有元素之和必须为 1,即 ∑ xᵢ = 1


🔄 状态转移与转移矩阵

上一节我们介绍了描述当前位置的概率向量。本节中,我们来看看如何计算下一步的位置。

转移矩阵 P 正是为此设计的。矩阵 P 的第 i 行描述了从状态 i 出发,下一步转移到各个状态的概率。

如果当前状态的概率分布是向量 X,那么下一步的状态分布就是 X 乘以转移矩阵 P,即:

X_next = X · P

这个公式告诉我们,通过一次矩阵乘法,就能得到随机游走下一步的概率分布。


⚖️ 马尔可夫链与稳态

并非所有随机游走都能收敛到一个稳定的状态分布。这里需要引入“遍历性”的概念。

一个马尔可夫链是遍历的,需要满足两个条件:

  1. 从任何状态出发,都存在一条路径到达任何其他状态。
  2. 从任何初始状态开始,经过一段有限的过渡时间 T₀ 后,在任意固定时间 T 处于任意状态的概率都大于零。

如果一个网络不满足这些条件(例如,一个在奇偶时间步访问不同节点的网络),则不是遍历的。

对于遍历的马尔可夫链,存在一个唯一的长期访问率,即稳态概率分布 π

π = [π₁, π₂, ..., πₙ]

其中,πᵢ 表示长期来看,随机游走处于状态 i 的概率。这个 πᵢ 正是状态 i 的 PageRank 值。重要的是,无论我们从哪里开始,最终都会收敛到这个稳态分布 π


🧮 计算稳态分布:特征向量法

从直觉上理解了稳态分布后,我们来看看如何计算它。

我们有一个稳态概率行向量 π。根据定义,如果当前状态分布是 π,那么下一步的分布 π · P 应该仍然等于 π,因为稳态分布不会改变。

因此,我们得到关键方程:

π = π · P

这意味着,稳态分布 π 是转移概率矩阵 P左特征向量,对应的特征值为 1(转移概率矩阵的最大特征值总是 1)。因此,π 就是 P 的主左特征向量。


⚡ 计算方法:幂迭代法

理论上,我们可以通过求解特征向量方程得到 π。但在实践中,更常用的是幂迭代法

由于稳态分布与初始状态无关,我们可以从任意概率分布 X⁽⁰⁾ 开始(例如,从状态1开始:[1, 0, 0, ...])。然后反复乘以转移矩阵 P

X⁽¹⁾ = X⁽⁰⁾ · P
X⁽²⁾ = X⁽¹⁾ · P = X⁽⁰⁾ · P²
...
X⁽ᵏ⁾ = X⁽⁰⁾ · Pᵏ

k 足够大时,X⁽ᵏ⁾ 将收敛于稳态分布 π。我们只需迭代计算,直到两次迭代的结果变化非常小为止。

以下是幂迭代法的一个计算示例:
假设我们有一个包含3个节点的小网络,其转移矩阵 P(已包含0.5的随机跳转概率)如下:

P = [[0.33, 0.33, 0.33],
     [0.25, 0.50, 0.25],
     [0.33, 0.33, 0.33]]

我们从状态1开始:X⁽⁰⁾ = [1, 0, 0]

  • 第一次迭代:X⁽¹⁾ = X⁽⁰⁾ · P = [0.33, 0.33, 0.33]
  • 第二次迭代:X⁽²⁾ = X⁽¹⁾ · P = [0.30, 0.40, 0.30]
  • ... 持续迭代 ...
  • 最终收敛到:π ≈ [0.278, 0.444, 0.278] (即 5/18, 4/9, 5/18)

这个 π 向量就是三个节点的 PageRank 值。


📝 PageRank 算法流程总结

本节课中我们一起学习了 PageRank 的计算全过程,现在我们来总结一下:

  1. 预处理阶段
    • 获取网络的链接关系(邻接矩阵)。
    • 根据选定的随机跳转概率(如 0.15),构建完整的转移概率矩阵 P
  2. 计算阶段
    • 使用幂迭代法等算法,计算转移矩阵 P 的主左特征向量 π
    • π 中的每个值 πᵢ 就是页面 i 的 PageRank 分数,它是一个介于 0 和 1 之间的数。
  3. 查询应用阶段
    • 当用户提交查询时,系统首先检索出所有匹配的页面。
    • 然后,结合这些页面的 PageRank 分数(衡量权威性)和其他查询相关度分数(如 TF-IDF、余弦相似度等),对结果进行综合排序。

需要注意的是,PageRank 是与查询无关的,它只反映网页链接结构的静态重要性。因此,在实际的搜索引擎排名中,PageRank 需要与那些能够理解查询内容的相关性度量方法结合使用,才能为用户提供最相关、最权威的结果。

PageRank 是评估网页重要性的一个基石性方法,也是将网络链接结构量化用于信息检索的关键创新。

📘 课程 P8:L2.2 - 最小编辑距离计算

在本节课中,我们将学习如何计算两个字符串之间的最小编辑距离。最小编辑距离是指将一个字符串转换为另一个字符串所需的最少编辑操作次数,这些操作通常包括插入、删除和替换。我们将重点介绍使用动态规划这一标准算法来解决此问题。

动态规划是一种表格化的计算方法。其核心思想是通过组合子问题的解来求解原问题。具体来说,我们将通过计算字符串 X(长度为 n)和字符串 Y(长度为 m)的所有前缀子串之间的距离,来逐步推导出整个字符串之间的距离。

我们将为字符串 X 的长度为 i 的前缀和字符串 Y 的长度为 j 的前缀计算距离 D[i][j]。最终,D[n][m] 即为两个完整字符串之间的最小编辑距离。

接下来,我们来看定义最小编辑距离的具体公式。这里我们以莱文斯坦距离为例,其操作成本定义为:插入成本为 1,删除成本为 1,替换成本为 2

首先,我们定义初始化条件。对于字符串 X 的前 i 个字符,将其转换为空字符串的成本是删除所有这些字符的成本,即 i。同理,将空字符串转换为字符串 Y 的前 j 个字符的成本是插入这些字符的成本,即 j

初始化公式如下:

  • D[i][0] = i
  • D[0][j] = j

然后,我们定义递推关系。在填充动态规划表格时,任意单元格 D[i][j] 的值可以通过三种先前状态推导出来,我们取其中成本最小的路径:

  1. D[i-1][j] 而来,表示删除 X 的第 i 个字符,成本为 D[i-1][j] + 1
  2. D[i][j-1] 而来,表示在 Y 中插入一个字符,成本为 D[i][j-1] + 1
  3. D[i-1][j-1] 而来,表示将 X 的第 i 个字符替换为 Y 的第 j 个字符。如果这两个字符相同,则替换成本为 0;如果不同,则成本为 2。因此,总成本为 D[i-1][j-1] + (0 或 2)

递推公式总结如下:
D[i][j] = min( D[i-1][j] + 1, D[i][j-1] + 1, D[i-1][j-1] + (0 if X[i]==Y[j] else 2) )

最终,两个字符串之间的最小编辑距离即为表格右下角的值:D[n][m]

现在,我们可以根据上述公式来填充整个动态规划表格。每个单元格的值都依赖于其上方、左方和左上方的单元格。

让我们以字符串 “intention”“execution” 为例进行演算。

首先初始化表格。D[0][0] 表示两个空字符串之间的距离,显然为 0。第一行表示将空字符串转换为 “execution” 的前缀所需的插入成本。第一列表示将 “intention” 的前缀转换为空字符串所需的删除成本。

现在计算 D[1][1],即字符串 “i”“e” 的距离。根据公式,我们需要计算三个值:

  • D[0][1] + 1 = 1 + 1 = 2 (从“”到“e”后,再删除“i”)
  • D[1][0] + 1 = 1 + 1 = 2 (从“i”到“”后,再插入“e”)
  • D[0][0] + (2) = 0 + 2 = 2 (将“i”替换为“e”,成本为2)

三者最小值是 2,因此 D[1][1] = 2

按照此方法,我们可以逐步填充整个表格。例如,D[4][3] 表示将 “inte” 转换为 “exe” 的最小编辑距离。

最终,我们得到完整的动态规划表格。表格右下角单元格 D[9][9] 的值 8,就是将 “intention” 转换为 “execution” 所需的莱文斯坦距离。

莱文斯坦距离 = 8

这就是计算最小编辑距离的标准动态规划算法。


本节课中,我们一起学习了最小编辑距离的概念及其核心算法——动态规划。我们了解了如何通过初始化条件和递推关系来填充一个动态规划表格,从而高效地计算出两个字符串之间的最小编辑距离。通过 “intention”“execution” 的实例演算,我们直观地看到了算法每一步的执行过程。

课程 P80:L14.1 - 社交网络基础 🕸️

在本节课中,我们将学习社交网络的一些基本属性和度量指标。我们会介绍什么是社交网络,以及如何用图论来抽象地表示它。接着,我们将探讨节点的几个核心度量指标:度、中介中心性和聚类系数。


什么是社交网络?🤔

“社交网络”一词通常指代连接人们的公司或应用程序。但在这里,我们指的是一个更抽象的概念,即用于研究一群人之间关系的图论隐喻。

在图论中,图的节点或顶点代表个人。人与人之间的各种具体关系则由节点之间的边或链接来表示。因此,由人和关系构成的整个系统就是网络或图,它包含了这些顶点和边。

例如,可以构建一个图,其中节点是演员,边是他们共同出演的电影。或者,节点是人,边是诸如兄弟或朋友这样的社会关系。但对于许多研究问题,我们可以抽象地只关注由四个节点和四条边构成的结构本身,例如节点1和2、2和3、1和3之间有链接,但节点3和4之间没有链接。


无向网络与有向网络 ↔️

上一节我们介绍了社交网络的基本概念,本节中我们来看看网络的两种基本类型。

网络可以是无向的,也可以是有向的。在无向网络中,节点之间的边是对称的。例如,Facebook上的好友关系、演员合作或Twitter上的对话关系。

然而,在有向网络中,边是不对称的,它们是具有特定方向的箭头。例如,在引文图中,节点是论文。如果论文2引用了论文1,那么边就从论文2指向论文1。在Twitter或Instagram上关注某人,也是一种有向关系。


节点的重要性度量 📊

我们已经了解了网络的类型,现在我们来学习衡量图中节点重要性的两种度量指标:度和中介中心性。我们还将介绍一个非常重要的概念——聚类系数。

节点的度

在无向图中,节点 i 的度是与该节点相连的边的数量。例如,在下图中,节点2有四条边与之相连,因此节点2的度为4。节点1和3都只有一条边,节点4和5则各有两条边。

# 示例:计算节点2的度
# 假设图结构为邻接表
graph = {
    1: [2],
    2: [1, 3, 4, 5],
    3: [2],
    4: [2, 5],
    5: [2, 4]
}
degree_of_node_2 = len(graph[2])  # 结果为 4

在有向图中,我们可以分别度量节点的入度和出度。入度是指向该节点的边的数量,出度是该节点指向其他节点的边的数量。例如,在下图中,节点2和节点4的入度均为2。节点1和节点5的入度均为1。节点3的入度为0。

节点的中介中心性

另一种衡量节点中心性的指标是中介中心性。如果一个节点位于许多节点对之间的最短路径上,那么它的中介中心性就高。例如,在下图中,节点7的中介中心性很高,因为许多节点对(如节点2和节点11,或节点4和节点8)之间的最短路径都必须经过节点7。

节点 A 的中介中心性(我们也可以计算边 AB 的中介中心性)的计算公式是:经过 A(或边 AB)的最短路径数量,除以所有节点对之间存在的总最短路径数量。换句话说,在所有节点对之间的所有最短路径中,有多少条经过了指定的节点或边?

我们可以定义节点 J 的中介中心性 b_J 为:对所有非 J 的节点对 (I, K) 求和,计算经过 J 的最短路径数量占 IK 之间总最短路径数量的比例。

让我们以计算下图中节点 B 的中介中心性为例:

  • 对于节点对 (A, C)AC 之间只有一条最短路径,且不经过 B。比例为 0/1。
  • 对于节点对 (A, D)AD 之间有两条长度相同的最短路径。其中一条经过节点 B。比例为 1/2。
  • 对于节点对 (C, D)CD 之间只有一条最短路径,且不经过 B。比例为 0/1。
  • 将所有比例相加:0 + 0.5 + 0 = 0.5。或者,从路径总数来看,在所有不包含 B 的节点对(共3对)产生的4条最短路径中,有1条经过 B,因此中介中心性为 1/4 = 0.25。

节点的聚类系数

最后,我们来介绍节点的一个非常重要的属性——聚类系数。节点的聚类系数衡量的是其邻居节点(即通过边与其相连的节点,可以非正式地称为“朋友”)之间的关系紧密程度。

节点 A 的聚类系数 C_A 衡量的是“我的朋友之间彼此也是朋友的比例”,即我的邻居节点之间也存在边的比例。

让我们计算下图中节点 A 的聚类系数:

  • A 有三个朋友:BCD
  • 在这些朋友之间,可能存在 BCBDCD 这三条边。
  • 实际上,只有 CD 这条边存在。
  • 因此,节点 A 的聚类系数是 1/3。

再来计算节点 C 的聚类系数:

  • C 有两个邻居:AD
  • 在它们之间,可能存在 AD 这一条边。
  • 实际上,这条边是存在的。
  • 因此,节点 C 的聚类系数是 1/1 = 1。

聚类系数的取值范围从0(所有朋友互不认识)到1(所有朋友彼此都认识)。我们可以用以下公式来描述:

C_A = (邻居节点间实际存在的边数) / (邻居节点间可能存在的最大边数)

其中,如果 n_A 是节点 A 的邻居数量,那么这些邻居之间可能存在的最大边数为 n_A * (n_A - 1) / 2。聚类系数就是实际存在的边数占这个最大可能边数的百分比。


总结 📝

本节课中,我们一起学习了社交网络的基础知识。我们了解了社交网络在图论中的抽象表示,区分了无向网络和有向网络。更重要的是,我们深入探讨了衡量节点重要性的三个核心度量指标:(衡量连接数量)、中介中心性(衡量在信息传播中的枢纽作用)以及聚类系数(衡量邻居群体的紧密程度)。这些指标是分析和理解社交网络结构的基础工具。

课程 P9:L2.3 - 回溯与对齐计算 📝

在本节课中,我们将要学习如何计算两个字符串之间的编辑距离,并进一步学习如何获取它们之间的具体对齐方式。对齐能告诉我们一个字符串中的每个符号如何对应到另一个字符串中,这在拼写检查、机器翻译乃至计算生物学等应用中都非常重要。

为什么需要对齐?🔍

知道两个字符串之间的编辑距离很重要,但这通常还不够。

我们常常需要更多信息,即两个字符串之间的对齐关系。

我们想知道字符串 X 中的哪个符号对应字符串 Y 中的哪个符号,这对于编辑距离的任何应用都很重要,从拼写检查到机器翻译,甚至在计算生物学中也是如此。

计算这种对齐的方法是保留一个回溯指针。

回溯指针是一个简单的指针,当我们填充动态规划矩阵的每个单元格时,它会记录我们是从哪个单元格转移过来的。当我们到达矩阵的右上角(即最终结果)时,我们可以利用这些指针一路回溯,从而读出完整的对齐方式。

回溯在实践中如何工作?🧩

让我们看看这在实践中如何运作。我们再次给出编辑距离中每个单元格的计算公式。

以下是计算每个单元格值的核心公式:

D[i][j] = min(
    D[i-1][j] + 1,      # 删除
    D[i][j-1] + 1,      # 插入
    D[i-1][j-1] + cost  # 替换(cost: 字符相同为0,不同为2)
)

如果我们填入之前看到的一些值,我们可以开始分析。

我们可以问,这个值 2 是如何得到的?

2 是我们从三个可能的值中选取的最小值。这个 2 是字符串 “I” 和 “E” 之间的距离。

我们通过以下方式得到它:要么是空字符串与 “E” 的对齐距离加上插入一个额外的 “I” 的成本(即 1 + 1 = 2),要么是 0 + 2 = 2,要么是 1 + 1 = 2。

所以我们有三个不同的来源值。如果我们问我们是从哪条最小路径来的,实际上它们都一样,我们可以来自其中任何一个。对于值 3 也是如此,我们计算它为 2+1、1+2 或 2+1 的最小值,所以它可能来自这里、这里或这里。类似地,对于其他单元格也是如此。

这里我们有一个距离差异。字符串 “INTE” 和 “E” 之间的距离。我们可以通过计算将 “INTE” 转换为空字符串的成本,然后为 “E” 添加一次插入来计算,但那样会是 4 + 1 = 5,成本很高。实际上,从 “INTE” 到 “E” 有一个更便宜的方法,那就是匹配这个 “E” 和那个 “E” 的成本为 0。所以,之前 “INT” 与空字符串的对齐距离是 3,我们加上 0 就得到了 3。

因此,这个值 3 的最小路径明确地来自那个值 3。虽然在有些情况下,一个单元格可能来自多个地方,但在这个例子中,它明确地来自前一个 3。

构建回溯指针矩阵

我们要为数组中的每个单元格都进行这个操作。

结果将类似于这样,对于每个单元格,我们都标明了它所有可能的来源。你会看到,在很多情况下,任何路径都可能成立。例如,这个 6 可能来自任何地方。

但关键的是,这个最终的对齐结果,即表示 “intention” 和 “execution” 之间最终编辑距离的 8,我们的回溯告诉我们它来自 “intentio” 和 “executio” 的最佳对齐,而后者又来自 “intenti” 和 “executi” 的最佳对齐,依此类推。

因此,我们可以沿着这条路径回溯,得到一个对齐结果,告诉我们这个 “N” 匹配那个 “N”,这个 “O” 匹配那个 “O”,等等。但可能在这里我们有一个插入操作,而不是一个完美的对齐。

计算回溯非常简单。

我们采用之前见过的最小编辑距离算法。

这里我已经为你标记了各种情况。当我们查看一个单元格时,我们可能在进行删除、插入或替换操作。

我们只需添加指针:在插入的情况下,我们指向左边;在删除的情况下,我们指向下方;在替换的情况下,我们指向对角线。我在之前的幻灯片上已经用箭头展示了这一点。

对齐路径与最优子结构

我们可以观察这个距离矩阵,并思考从起点到终点的所有路径。

从起点到点 (n, m) 的任何一条非递减路径,都对应于两个序列的某种对齐方式。

一个最优的对齐,是由最优的子序列对齐组成的。

正是这个思想,使得使用动态规划来解决这个任务成为可能。

我们回溯的结果是两个字符串以及它们之间的对齐关系。

因此,我们将知道哪些部分是完全匹配的,哪些部分是通过替换对齐的,以及何时应该进行插入或删除操作。

算法性能分析 ⚙️

这个算法的性能如何?

在时间复杂度上,它是 O(n*m),因为我们的距离矩阵大小为 n*m,并且我们只填充每个单元格一次。

在空间复杂度上也是如此。

对于回溯,在最坏情况下,如果我们有 n 次删除和 m 次插入,我们需要访问 n + m 个单元格,但不会超过这个数量。

总结

本节课中,我们一起学习了用于计算对齐的回溯算法。

我们了解到,仅知道编辑距离是不够的,通常还需要具体的对齐信息。通过动态规划矩阵和回溯指针,我们可以高效地计算出两个字符串之间的最优对齐方式,明确标出匹配、替换、插入和删除操作。该算法的时间复杂度和空间复杂度均为 O(n*m),是一种非常实用的字符串比对技术。

这就是我们用于计算对齐的回溯算法。


  1. 1 ↩︎

  2. 1 ↩︎

  3. i ↩︎

  4. i ↩︎

  5. 0 ↩︎

  6. 0 ↩︎

posted @ 2026-02-05 08:54  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报