标签归档:人工智能

Wolfram: ChatGPT是怎么实现的?为什么它这么有效?

ChatGPT 能够自动生成类似于人类写作的文本,这一点非常引人注目,也令人意外。但它是如何实现的?为什么它能够如此出色地生成我们认为有意义的文本?我的目的是在这里概述ChatGPT内部的运行情况,并探讨它能够如此出色地产生有意义文本的原因。

首先需要解释的是,ChatGPT的基本目标是尝试产生一个“合理的延续”,无论它当前所拥有的文本是什么。这里的“合理”是指“在浏览了数十亿网页等人类书写的内容后,人们可能会写什么”。

那么假设我们有文本“AI的牛逼之处在于它能够…”,我们可以想象一下扫描数十亿页人类写作的文本(比如在网上和数字化的书籍中),找到所有这个文本的实例,然后看下一个单词出现的频率是多少。ChatGPT实际上是在类似地寻找在某种意义上“匹配”的内容,以生成一个排名列表,列出可能的后续单词和相应的“概率”。

ChatGPT写作时的一个显著特点是,它实际上只是一遍又一遍地问自己:“在当前文本的情况下,下一个词应该是什么?”然后每次添加一个单词。更准确地说,它添加的是一个“标记”,可能只是一个单词的一部分,这就是它有时可以“创造新词”的原因。

好的,在每一步中,ChatGPT都会得到一个带有概率的单词列表。但是它应该选择哪个单词来添加到正在撰写的文章(或其他文本)中呢?也许人们认为应该选择“排名最高”的单词(即被分配最高“概率”的单词)。但是这就是玄学开始的地方。因为某种原因(也许有一天我们会对此有科学式的理解),如果我们总是选择排名最高的单词,我们通常会得到一个非常“平淡”的文章,似乎从未“展现出任何创意”(有时甚至是逐字重复)。但是,如果有时(随机地)选择排名较低的单词,我们就会得到一个“更有趣”的文章。

这里有随机性,这意味着如果我们多次使用相同的问题提问,每次都可能得到不同的回答。同时,存在一个特定的所谓“temperature”参数,它决定了低排名单词被使用的频率。对于文章生成来说,实践中发现使用“temperature”为0.8最佳。(需要强调的是,这里没有运用任何“理论”;这只是经验上被发现的有效方法。)

在继续之前,我需要说明的是,出于阐述的目的,我通常不会使用ChatGPT中的完整系统,而是使用更简单的GPT-2系统。该系统有一个很好的特点,即其大小足够小,可以在标准台式计算机上运行。

例如,以下是获取上述概率表格的步骤。首先,我们需要检索底层的“语言模型”神经网络:

稍后,我们将会深入探讨这个神经网络,并讲解它是如何工作的。但现在,我们可以把这个“网络模型”视为黑匣子,应用到我们目前的文本中,并请求该模型认为应该跟随的前五个概率最高的单词:

这个步骤将结果转换为一个格式明确的“数据集”:

如果反复“应用模型”,每一步都添加具有最高概率的单词(在此代码中指定为模型的“决策”),则会发生以下情况:

如果继续下去会发生什么?在这种零“temperature”的情况下,输出很快就会变得混乱而重复。

如果不总是选择“顶部”单词,而是有时随机选择“非顶部”单词(其中“随机性”对应于 temperature=0.8),会发生什么呢?我们再次可以构建出文本:

每次执行时,会做出不同的随机选择,因此文本也会不同。以下是5个示例:

那么,如何计算下一个单词的概率?

好的,ChatGPT总是根据概率选择下一个单词。但是这些概率从哪里来呢?让我们从一个更简单的问题开始,考虑逐个字母(而不是单词)生成英语文本。如何确定每个字母的概率呢?

我们可以采取的最简单的方法就是从英文文本中随机取样,并计算不同字母的出现频率。例如,以下是在“猫”(Cat)维基百科文章中计算字母出现次数的结果:

这是狗(dog)维基百科文章中不同字母的出现次数:

结果是相似的,但并非完全相同(因为“o”在“dogs”文章中更常见,毕竟它出现在单词“dog”中)。不过,如果我们取样的英语文本足够大,最终我们可以期望得到相当一致的结果:

这是我们只是根据这些概率生成字母序列时得到的样本:

我们可以通过将空格模拟为字母添加进可选项中,从而将这些序列分为多个“单词”组成的序列:

我们可以通过让“单词长度”的分布与英语中的分布一致来更好地模拟“单词”的生成过程:

这里我们没有得到任何“实际单词”,但结果看起来稍微好了一点。但是,要进一步往下,我们需要做的不仅仅是随机选择每个字母。

例如,我们知道如果有一个“q”,下一个字母基本上必须是“u”。

这是字母本身的出现概率图示:

下面这张图展示了英语文本中一对字母(“2-grams”)的概率分布。图表的横轴为可能的第一个字母,纵轴为可能的第二个字母:

我们可以看到,例如,“q”列除了“u”行之外为空(概率为零)。好的,现在,我们不再一个字母一个字母地生成我们的“单词”,而是使用这些“2-gram”概率,一次生成两个字母来生成它们。以下是结果的一个样本 – 其中恰好包括一些真实存在的单词:

通过足够多的英文文本,我们不仅可以对单个字母或字母对(2-grams)的概率进行相当准确的估计,还可以对更长的字母序列进行估计。如果我们使用逐渐更长的n-gram概率生成“随机单词”,我们会发现它们逐渐变得“更真实”。

让我们现在假设 – 就像 ChatGPT 一样 – 我们处理的是整个单词,而不是单个字母。英语中大约有40,000个常用单词。通过查看大量的英语文本(例如几百万本书,总共几千亿个单词),我们可以估计每个单词的使用频率。利用这个估计,我们可以开始生成“句子”,其中每个单词都是独立随机选择的,其出现概率与在文本语料库中的出现概率相同。以下是我们得到的一个样本:

我们可以预料到,这样生成的句子毫无意义。那么我们应该如何改进呢?就像处理字母时一样,我们可以考虑不仅单个单词的概率,而是成对或更长的n-gram单词的概率。对于成对的情况,以下是5个例子,它们都以单词“cat”为起点:

这看起来稍微有点合理。如果我们使用足够长的n-gram,就有可能得到一个能够生成具有“正确整体文章概率”的单词序列,从而“得到一个ChatGPT”。但是问题在于,目前可用的英语文本量远远不足以推断出这些概率。在网络爬取的数百亿单词和数字化书籍中的数百亿单词中,对于40,000个常用单词,可能的2-grams数量就高达16亿,而3-grams的数量则达到60万亿。

因此,我们无法从现有的文本中估算出所有这些概率。即使是20个单词的“文章片段”,其可能性数量也已经超过了宇宙中的粒子数量,从某种意义上讲,这些可能性永远都不能全部写下来。

那么我们该怎么办呢?重要的想法是创建一个模型,使我们能够估计序列应该发生的概率,即使我们在我们查看的文本语料库中从未明确看到这些序列。而ChatGPT的核心正是一个被称为“大型语言模型”(LLM)的模型,它已经被构建成能够很好地估计这些概率的模型。

什么是模型?

假设你想知道(就像加利略在16世纪晚期那样)从比萨斜塔的每层落下的炮弹需要多长时间才能落到地面。那么,你可以在每种情况下进行测量并制作结果表。或者你可以做理论科学的本质:建立一个模型,给出一些计算答案的过程,而不是仅仅测量和记忆每种情况。

让我们想象我们有(有些理想化的)数据,了解炮弹从不同楼层掉落需要多长时间:

我们如何计算出从我们没有明确数据的楼层掉落所需的时间?在这种特定情况下,我们可以使用已知的物理定律来计算。但是假设我们只有数据,而不知道其背后的规律是什么。那么我们可以做出一个数学猜测,例如,也许我们应该使用一条直线作为模型:

我们可以选择不同的直线,但这条直线平均来看最接近我们所给的数据。从这条直线中,我们可以估计任何一层的落地时间。

我们如何知道在这里尝试使用一条直线呢?在某种程度上,我们并不确定。这只是数学上的一些简单东西,而且我们已经习惯了我们所测量的很多数据能够被数学上的简单事物很好地拟合。当然,我们也可以尝试使用数学上更复杂的东西,例如a + bx + cx^2,在这种情况下,我们可以得到更好的拟合效果:

事情有时会变得非常糟糕。就像在这里,我们尝试使用 a + b/x + c sin(x) 可以得到的最好结果:

永远不存在“model-less model”。您使用的任何模型都具有某些特定的基本结构,然后有一定数量的“旋钮可供调节”(即可以设置的参数)来拟合您的数据。在ChatGPT的情况下,使用了许多这样的“旋钮”——实际上有1750亿个。

值得注意的是,相比于3-grams的60万亿的组合可能性,ChatGPT 的这1750亿个参数,却已足够生成让我们惊讶的文本。

如何制作能完成人类任务的模型

我们上面给出的例子涉及到制作数值数据模型,这些数据基本上来自于简单的物理,是几个世纪以来我们已经知道的“简单数学”。但是对于ChatGPT,我们必须制作一种人类大脑产生的语言文本模型。对于这样的任务,我们还没有类似于“简单的数学”工具。那么,这个模型可能是什么样子的呢?

在讨论语言之前,我们先来谈谈另一个类似于人类任务:图像识别。作为一个简单的例子,我们考虑数字图像识别(这是一个经典的机器学习例子)。

首先,我们给每个数字收集一堆样本图像:

然后,为了找出我们输入的图像是否对应于特定的数字,我们可以使用样本图像进行像素逐个比较。但是作为人类,我们显然比像素逐个比较样本更擅长数字识别,因为即使数字是手写的,或者存在各种修改和扭曲,我们仍能够识别它们。

当我们上面为数值数据建立模型时,我们能够接收给定的数值x,然后为特定的a和b计算a+bx。那么,如果我们将这里每个像素的灰度值视为某个变量xi,是否有某个函数涉及所有这些变量,当我们对其进行求值时,可以告诉我们图像表示的是哪个数字?事实证明,可以构造这样的函数。但并不出人意料,它并不是特别简单。一个典型的例子可能涉及大约五十万次数学运算。

但最终结果是,如果我们将图像的像素值集合输入到此函数中,输出将是指定图像所代表的数字。稍后,我们将讨论如何构建这样的函数和神经网络的概念。但现在,让我们将该函数视为一个黑盒子,在其中输入手写数字图像(作为像素值数组),并输出它们所对应的数字:

但是这里真正发生了什么?假设我们逐渐模糊一个数字。一段时间内,我们的函数仍然可以“识别”它,例如这里被识别为“2”。但很快它就“失去了”,开始给出“错误”的结果:

但是,究竟发生了什么呢?假设我们逐渐模糊一个数字。在一段时间内,我们的函数仍然可以“识别”它,比如说是“2”。但很快,它就会“失效”,并开始给出“错误”的结果。

但是,我们为什么认为这是“错误”的结果呢?在这种情况下,我们知道我们通过模糊“2”来得到所有这些图像。但如果我们的目标是生成一个能够模拟人类识别图像能力的模型,那么真正需要问的问题是:如果向人类展示这些模糊的图像,而他们并不知道这些图像是如何产生的,那么人类会如何做出判断?

如果我们的函数得到的结果通常与人类的结果一致,那么我们就拥有了一个“好的模型”。而这个非常重要的科学事实是,对于像这样的图像识别任务,我们现在基本上知道如何构造这样的函数。

我们能否“数学证明”它们的有效性?嗯,不行。因为要做到这一点,我们必须拥有一个关于人类视觉感知的数学理论。我们拿“2”图像并改变几个像素。我们可能想象,即使只有几个像素“错位”,我们仍然应该认为这是一个“2”。但这该怎样才算合理呢?这是一个关于人类视觉感知的问题。是的,对于蜜蜂或章鱼,答案无疑会有所不同。

神经网络

那么,像图像识别这样的任务,我们的典型模型实际上是如何工作的呢?最流行且最成功的当前方法使用神经网络。神经网络的发明可以看作是大脑如何工作的简单理想化,而它们的形式与现在的使用方式非常接近。

人脑中大约有1000亿个神经元(神经细胞),每个神经元都能够每秒产生一千次的电脉冲。这些神经元以复杂的网络连接在一起,每个神经元都有树状的分支,使其能够向成千上万个其他神经元传递电信号。在粗略的近似中,任何给定神经元是否在某一时刻产生电脉冲取决于它从其他神经元接收到的脉冲,不同的连接会以不同的“权重”进行贡献。

当我们“看到图像”时,光子从图像中落在我们眼睛后面的(“光感受器”)细胞上,这些细胞会在神经细胞中产生电信号。这些神经细胞与其他神经细胞相连接,最终信号经过一系列神经元层次的传递。在这个过程中,我们“识别”图像,最终“形成思想”,认为我们“看到了一个2”(或许最终会像大声说“two”这样的动作)。

前一节中的“黑匣子”函数是这种神经网络的“数学化”版本。它恰好有11个层次(虽然只有4个“核心层”):

这个神经网络并没有什么特别“理论推导”的地方;它只是在1998年作为一项工程构建而成,并被发现可以工作。(当然,这与我们可能会描述我们的大脑是通过生物进化的过程产生的并没有太大的不同。)

好的,但是这样的神经网络如何“识别事物”呢?关键是”吸引子”的概念。想象一下我们有手写的1和2的图像:

我们希望所有的1“被吸引到一个地方”,所有的2“被吸引到另一个地方”。换句话说,如果一幅图像在某种程度上“更接近1”而不是2,我们希望它最终进入“1的位置”,反之亦然。

一个直观的类比,假设我们有平面上的某些位置,用点表示(在现实中,它们可能是咖啡店的位置)。然后我们可以想象,从平面上的任何一个点开始,我们总是想到达最近的点(也就是去最近的咖啡店)。我们可以通过将平面划分为由理想化的分隔区域来表示这一点:

所以我们可以将这看作是实现了一种“识别任务”,我们不是像识别给定图像最像哪个数字那样做某些事情,而是直接看哪个点最接近

那么如何使神经网络“执行识别任务”呢?让我们考虑这个非常简单的情况:

我们的目标是将与位置 {x,y} 相对应的“输入”识别为最接近的三个点之一。或者换句话说,我们希望神经网络计算关于 {x,y} 的函数,如下所示:

那么我们如何用神经网络实现这个任务呢?神经网络是由一系列理想化的“神经元”连接而成的,通常排列在层中。一个简单的例子是:

每个“神经元”实际上是建立在简单的数值函数上的。而要“使用”网络,我们只需在顶部输入数字(如我们的坐标 x 和 y),然后让每层神经元“评估其函数”,并将结果向前传递到网络的底部,最终产生最终结果:

在传统的(受生物学启发)设置中,每个神经元实际上都有来自前一层神经元的某些“传入连接”,每个连接都被分配一个特定的“权重”(可以是正数或负数)。一个给定神经元的值是由“前一个神经元”的值乘以它们对应的权重然后相加并加上一个常数,最后应用一个“阈值”(或“激活”)函数来确定的。在数学上,如果一个神经元有输入x = {x1,x2 …},那么我们计算f[w.x+b],其中权重w和常数b通常对于网络中的每个神经元选择不同;函数f通常是相同的。

计算 w.x + b 就是矩阵乘法和加法的问题。激活函数 f 引入了非线性,从而产生非平凡的行为。常见的激活函数有很多种,这里我们将使用 Ramp(或 ReLU)函数。

我们想要神经网络执行每个任务(或等价地,评估每个总体函数)时,我们将有不同的权重选择。 (正如我们稍后将讨论的那样,这些权重通常是通过使用机器学习从我们想要的输出示例来训练神经网络确定的。)

最终,每个神经网络都对应于某个总体数学函数,尽管它可能很混乱。 对于上面的示例,它将是:

ChatGPT的神经网络也仅仅对应着这样一个数学函数,但是实际上有数十亿个项式。

但是让我们回到单个神经元。以下是具有两个输入(表示坐标x和y)的神经元在各种权重和常量选择(以及Ramp作为激活函数)下可以计算的函数示例:

那么上面那个函数的表现如何呢?好吧,它是这样的:

这不是完全“正确”的,但它接近我们上面展示的“最近点”函数。

让我们看看其他一些神经网络会发生什么。在每种情况下,正如我们稍后将解释的那样,我们使用机器学习来找到最佳的权重选择。然后在这里展示神经网络使用这些权重计算出的结果:

较大的神经网络通常能更好地逼近我们的目标函数。如果要判断的目标在每个”吸引子”盆地的中心附近,我们通常可以获得我们想要的答案。但在边界处,神经网络“难以作出决策”,情况可能会更加混乱。

对于这种简单的数学式“识别任务”,正确答案是明确的。但在识别手写数字的问题上,情况并不那么明确。如果有人将“2”写得像“7”等等,怎么办呢?尽管如此,我们可以询问神经网络如何区分数字,这可以提供一些线索:

我们能“数学地”说明网络是如何进行区分的吗?实际上不行。它只是“按照神经网络的方式进行操作”。但事实证明,这通常与我们人类所做出的区分相当吻合。

让我们看一个更复杂的例子。假设我们有猫和狗的图像,并有一个被训练用于区分它们的神经网络。以下是它在一些示例上的表现:

现在,“正确答案”更加不明确了。那么,如果一只狗穿着猫装呢?等等。无论输入什么,神经网络都会生成一个答案。而且,事实证明,它以一种相当一致的方式生成答案,与人类可能做出的相符。正如我之前所说,这不是我们可以从第一原理中推导出来的事实。这只是在某些领域经验上被证明是正确的关键原因。但这也是神经网络有用的一个关键原因:它们以某种方式捕捉了“人类式”的做事方式。

以猫和狗的识别为例,现在“正确答案”更加不明确了。比如,一只穿着猫装的狗,该怎么识别呢?无论输入什么,神经网络都会生成一个答案。而且,事实证明,它以一种相当一致的方式生成答案,与人类可能做出的相符。正如我之前所说,这不是我们可以从第一原理中推导出来的事实。这只是在某些领域经验上被证明是正确的关键原因。但这也是神经网络有用的一个关键原因:它们以某种方式捕捉了“人类式”的做事方式。

假设你看到了一张猫的图片,你会问自己“为什么这是一只猫?”你可能会说:“我看到了它尖耳朵等特征。”但很难解释你是如何识别出这张图片的。这只是你的大脑以某种方式弄清楚了。但对于大脑来说,(至少目前)没有办法“进入内部”并查看其如何解决问题。那么对于(人工)神经网络呢?当你展示一张猫的图片时,每个“神经元”做的事情很容易看出来。但即使获得基本的可视化通常也非常困难。

在上面用于“最近点”问题的最终网络中有17个神经元。在用于识别手写数字的网络中有2190个神经元。在用于识别猫和狗的网络中有60,650个神经元。通常情况下,将60,650维空间可视化相当困难。但由于这是一个用于处理图像的网络,它的许多神经元层被组织成像素数组的形式。因此,我们可以拿一张典型的猫的图片为例进行说明:

那么,我们可以通过一系列派生图像来表示第一层神经元的状态——其中许多我们可以轻松解释为“没有背景的猫”或“猫的轮廓”之类的东西:

到了第10层,很难解释正在发生什么:

但总的来说,我们可能会说神经网络正在“挑选某些特征”(也许尖耳朵是其中之一),并使用它们来确定图像的内容。但这些特征是否具有像“尖耳朵”这样的名称?大多数情况下并不是。

我们的大脑是否也使用类似的特征?大多数情况下我们不知道。但值得注意的是,像我们在这里展示的神经网络的前几层似乎会挑选出图像的一些方面(如物体的边缘),这些方面似乎类似于我们知道的大脑视觉处理的第一层所挑选的方面。

但是假设我们想要一个神经网络的“猫识别理论”。我们可以说:“看,这个特定的网络可以做到”,这立即让我们对“它有多难”的问题有了一些感觉(例如需要多少个神经元或层来解决这个问题)。但至少目前我们没有办法“给出一个叙述性描述”来解释网络正在做什么。也许这是因为它确实是计算上不可化简的,没有一般的方法来找出它所做的事情,除非我们明确地跟踪每一步。或者也许只是因为我们还没有“找到科学的方法”,并确定了允许我们总结正在发生的事情的“自然法则”。

当我们谈论使用ChatGPT生成语言时,我们将遇到同样的问题。同样,目前尚不清楚是否有方法“总结它在做什么”。但是,语言的丰富性和细节(以及我们对它的经验)可能使我们比使用图像更进一步。

机器学习和神经网络的训练

到目前为止,我们讨论了已经“知道”如何执行特定任务的神经网络。但是神经网络如此有用(大脑也很可能如此),不仅可以原则上执行各种任务,而且可以逐步地从示例中进行“训练”以执行这些任务。

当我们制作一个神经网络来区分猫和狗时,我们不需要有效地编写一个程序,该程序明确地查找胡须; 相反,我们只展示了很多关于什么是猫和什么是狗的例子,然后让网络从中“机器学习”如何区分它们。

而且重要的是,训练后的网络从所显示的特定示例中“推广”。就像我们上面看到的那样,网络并不仅仅是识别其显示的示例猫图像的特定像素模式; 而是神经网络以某种方式成功地基于我们认为的某种“一般猫”的特征来区分图像。

那么神经网络训练的实际工作是如何进行的呢?基本上,我们一直在尝试找到使神经网络成功复制我们给出的示例的权重。然后我们依靠神经网络以一种“合理”的方式“插值”(或“推广”)这些示例之间。

让我们看一个比上面的最近点问题更简单的问题。让我们尝试让神经网络学习以下函数:

对于这个任务,我们需要一个只有一个输入和一个输出的神经网络,如下所示:

但是我们应该使用什么权重?对于每个可能的权重集合,神经网络都会计算一些函数。例如,以下是使用几组随机选择的权重计算出来的结果:

可以看到,基本没有符合我们图形的结果,那么我们到底如何找到能符合我们图形的权重?

基本思想是提供大量的“输入→输出”示例供神经网络进行“学习”,然后尝试找到能够重现这些示例的权重。下面是使用逐渐增加的示例进行训练的结果:

到目前为止,我们一直在谈论“已经知道”如何执行特定任务的神经网络。但神经网络如此有用的原因(在大脑中也是如此),不仅是因为它们原则上可以执行各种任务,而且还可以从示例中逐步“训练”以执行这些任务。

当我们制作一个神经网络来区分猫和狗时,我们不需要编写一个(比如)显式查找胡须的程序,而是展示许多关于什么是猫和什么是狗的示例,然后让网络从中“机器学习”如何区分它们。

关键是训练后的网络从它所展示的特定示例中“泛化”。正如我们上面所看到的,神经网络不仅仅识别它所展示的示例猫图像的特定像素模式;相反,神经网络以某种“一般的猫”的标准来区分图像。

那么神经网络的训练实际上是如何工作的呢?基本上,我们总是试图找到使神经网络成功重现我们所给出示例的权重。然后,我们依赖神经网络以“合理的”方式在这些示例之间“插值”(或“泛化”)。

让我们来看一个比上面最近点问题更简单的问题。让我们尝试让神经网络学习函数:

好的,我们要解释的最后一个关键点是如何调整权重以减小损失函数。正如我们所说,损失函数给出了我们获得的值和真实值之间的“距离”。但是“我们获得的值”是由当前版本的神经网络和其中的权重确定的。但是现在想象一下,权重是变量,比如wi。我们想找出如何调整这些变量的值以最小化依赖它们的损失。

例如,想象一下(极度简化了实际使用的典型神经网络),我们只有两个权重w1和w2。那么我们可能会有一个损失函数,它关于w1和w2的函数如下:

数值分析提供了各种技术来在这种情况下找到最小值。但是典型的方法就是沿着从之前的 w1, w2 开始的最陡峭的路径逐步前进:

就像水流下山一样,这个过程只能保证最终会到达曲面的某个局部最小值(“山上的湖泊”),但不一定能够到达最终的全局最小值。

在“权重空间”上找到最陡的下降路径并不容易。但是,微积分可以解决这个问题。正如前面提到的那样,我们可以将神经网络看作是计算一个依赖于其输入和权重的数学函数。现在考虑对这些权重进行微分。结果表明,微积分的链式法则让我们可以“展开”神经网络的逐层操作。结果是,至少在某种局部近似情况下,我们可以“反演”神经网络的操作,并逐步找到最小化与输出相关联的损失的权重。

上面的图片展示了我们可能需要在仅有2个权重的不现实的简单情况下进行的最小化操作。但是,实际证明,即使有更多的权重(ChatGPT使用了1750亿个),仍然可以进行最小化,至少可以进行一定程度的近似。事实上,“深度学习”在2011年左右的重大突破与发现有关,即在某种程度上,涉及许多权重时近似最小化可能比涉及较少权重时更容易。

换句话说,有点出人意料的是,用神经网络解决更复杂的问题可能比解决更简单的问题更容易。这种现象的粗略原因是,当有许多“权重变量”时,就有了高维空间,有“许多不同的方向”可以引导人到达最小值,而如果变量较少,则更容易陷入局部最小值(“山湖”),从中没有“摆脱的方向”。

值得指出的是,在典型情况下,有许多不同的权重集合可以给出基本相同性能的神经网络。通常,在实际的神经网络训练中,会有许多随机选择,导致出现“不同但等效的解决方案”,如下所示:

但是,每个这样的“不同解”都会有略微不同的行为。如果我们要求在我们提供训练示例的区域之外进行“外推”,我们可能会得到截然不同的结果:

(本文由公众号Python实用宝典翻译)

但是哪一个是“正确”的呢?实际上没有办法说。它们都“与观测数据一致”。但它们都对应于不同的“内在”方式来“思考”如何在“盒子外面”进行操作。有些可能对我们人类来说比其他的看起来更“合理”。

神经网络训练的实践

在过去的十年中,神经网络的训练艺术有了很多进展。而且,训练确实基本上是一门艺术。有时候-尤其是回顾时-人们可以看到至少一丝“科学解释”是为了做某事。但是大多数情况下,这些技巧和想法是通过试错发现的,逐步积累了许多有关如何使用神经网络的知识。

神经网络训练有几个关键部分。首先,有一个问题,即针对特定任务应该使用什么神经网络结构。然后,关键问题是如何获取用于训练神经网络的数据。越来越多的情况是,人们不再从头开始训练网络:新网络可以直接包含另一个已经训练好的网络,或者至少可以使用该网络为自己生成更多的训练样例。

可能会认为,对于每种特定的任务,都需要不同的神经网络结构。但是发现同一种结构似乎经常适用于看似完全不同的任务。在某种程度上,这让人想起了通用计算的概念,但是,正如我稍后将讨论的那样,我认为这更反映了我们通常试图让神经网络执行“类似人类”的任务的事实,而神经网络可以捕捉相当普遍的“类似人类过程”。

在神经网络的早期阶段,人们倾向于认为应该“尽量让神经网络做最少的事情”。例如,在将语音转换为文本时,认为应该先分析语音,将其分解成音素等。但是发现,对于“类似人类的任务”,通常最好只是尝试训练神经网络解决“端到端问题”,让它“发现”必要的中间特征、编码等。

还有一种想法是将复杂的单个组件引入到神经网络中,以让它实际上“明确实施特定的算法思想”。但是再次发现,这通常不值得;相反,最好只处理非常简单的组件,并让它们“自组织”(尽管通常以我们无法理解的方式)以实现等效的算法思想。

这并不是说没有适用于神经网络的“构造性思想”。例如,在图像处理的早期阶段,具有局部连接的神经元的2D阵列似乎非常有用。而且在“顺序回顾”的连接模式下集中处理看起来也很有用-正如我们稍后将在ChatGPT中处理人类语言中看到的那样。

但是神经网络的一个重要特征是,与计算机一样,它们最终只是处理数据。现有的神经网络-以及当前的神经网络训练方法-具体处理数字数组。但是在处理过程中,这些数组可以完全重组和重塑。例如,我们用于识别上述数字的网络从2D“类似图像”的数组开始,迅速“加厚”到许多通道,但然后“集中到”一个1D数组中,该数组最终包含表示不同可能输出数字的元素。

但是,如何确定一个特定任务需要多大的神经网络呢?这是一种艺术。在某种程度上,关键是知道“任务的难度”。但对于类似于人类的任务来说,通常很难估计。是的,可能有一种系统化的方法可以让计算机非常“机械化”地完成任务。但是很难知道是否存在可以让人们以更轻松的方式实现“类人级别”的技巧或捷径。可能需要列举一个巨大的博弈树来“机械化”地玩某个游戏;但是可能有一种更容易(“启发式”)实现“类人水平游戏”的方法。

当处理微小的神经网络和简单的任务时,有时可以明确地看到无法从这里到达目标。例如,以下是使用一些小型神经网络完成上一节任务的最佳结果:

而且我们可以看到,如果网络太小,它就无法重现我们想要的函数。但是在某个大小以上,它没有问题——至少如果训练足够长时间并且使用足够多的示例。顺便提一下,这些图片说明了神经网络传说中的一件事情:如果中间有一个“挤压”,强制所有东西都通过较少的中间神经元,通常可以用更小的网络来实现同样的效果。 (值得一提的是,“无中间层”或所谓的“感知器”网络只能学习基本上是线性的函数——但只要有一个中间层,理论上就总是可以任意好地逼近任何函数,至少如果有足够多的神经元,尽管为了使其可行地训练,通常需要一些正则化或规范化技巧。)

好的,假设我们已经选择了一种神经网络结构。现在的问题是如何获取用于训练网络的数据。许多关于神经网络和机器学习的实际挑战都集中在获取或准备必要的训练数据上。在许多情况下(“监督学习”),我们希望获取输入的明确示例以及预期的输出。例如,我们可能希望标记图像中包含的内容或某些其他属性。可能需要通过付出巨大的努力明确地进行打标签。但通常情况下,发现可以利用已经完成的事情作为训练集的标签,例如,可以使用Web上提供的alt标签来标记图像。或者在不同的领域中,可以使用为视频创建的字幕。或者对于语言翻译训练,可以使用存在于不同语言中的网页或其他文档的平行版本。

需要多少数据来训练神经网络以完成特定任务?同样地,从基本原理上估计很困难。通过使用“转移学习”/”预训练”将已经在另一个网络中学习的重要特征列表“转移进来”,可以大大减少要求。但是通常神经网络需要“看到很多例子”才能训练得好。至少对于某些任务而言,神经网络需要的示例可以是极其重复的。实际上,一种标准的策略就是反复地向神经网络展示所有的示例。在每个“训练轮次”中,神经网络都会处于至少稍微不同的状态,某种程度上“提醒它”特定的示例有助于它“记住那个示例”。(是的,也许这类似于人类记忆中重复的有用性。)

通常,只是反复重复相同的例子是不够的。还需要向神经网络展示示例的变化。神经网络的特征是,这些“数据增强”变化并不需要很复杂就可以发挥作用。只需使用基本图像处理轻微修改图像即可使其对神经网络训练基本“同样有效”。同样地,当用于自动驾驶汽车的实际视频等数据用尽时,可以继续在类似于模拟视频游戏的环境中运行模拟,并获得数据,而不需要所有实际场景的细节。

ChatGPT是如何训练的呢?它有一个很好的特点,它可以进行“无监督学习”,这使得训练样本的获取变得更加容易。回想一下,ChatGPT的基本任务是找出如何继续给定的一段文本。因此,要获取它的“训练样本”,我们只需获得一段文本,然后将其结尾部分遮盖,这就成为了“训练输入”——“输出”则是完整的、未被遮盖的文本。我们稍后会详细讨论这个问题,但主要的一点是:与学习图像内容所需的“显式标记”不同,ChatGPT实际上可以直接从它所得到的文本样本中进行学习。

好的,那么神经网络中的实际学习过程呢?归根结底,它都是要确定哪些权重能最好地捕捉到已给定的训练示例。而有各种各样的详细选择和“超参数设置”(因为权重可以被看作是“参数”)可以用来调整如何做到这一点。有不同的损失函数(平方和,绝对值和等)。有不同的损失最小化方法(每步在权重空间中移动多远等)。然后还有诸如每次显示多少个“批次”示例来获得每个连续估计的损失等问题。

但最终训练的整个过程可以通过观察损失的逐步减少来描述:

但通常可以看到损失会一直下降一段时间,但最终会在某个常数值上趋于平稳。如果该值足够小,则可以认为训练是成功的;否则,这可能表明应尝试更改网络结构。

人们能够判断“学习曲线”变平稳需要多长时间吗?像许多其他事情一样,似乎存在依赖于所使用的神经网络大小和数据量的近似幂律缩放关系。但总的结论是训练神经网络很难,并且需要大量的计算功率。作为实际问题,绝大部分工作量用于对数字数组进行操作,这是GPU擅长的领域。这就是为什么神经网络训练通常受GPU的可用性限制的原因。

未来会有更基本的方法来训练神经网络,或者说做神经网络的事情吗?我认为几乎肯定会有。神经网络的基本思想是利用大量简单(本质上相同)的组件创建一个灵活的“计算结构”,并让这个“结构”能够通过增量修改来从示例中学习。在当前的神经网络中,本质上使用了微积分的思想 – 应用于实数,以进行增量修改。但越来越清楚的是,拥有高精度数字并不重要;即使使用当前的方法,8位或更少的位数可能足够。

神经网络——也许有点像大脑——被设置为具有基本固定的神经元网络,其中被修改的是它们之间连接的强度(“权重”)。 (或许在至少年轻的大脑中,还有可能增加相当数量的全新连接)。但虽然这可能是生物学上的一个便利设置,但它显然离实现我们所需的功能最好的方式还有很远的路要走。而且可能会涉及相当于渐进网络重写的东西(也许类似于我们的物理项目),最终可能会更好。

但是,即使在现有神经网络的框架内,目前还存在一个关键限制:神经网络训练目前是根本顺序的,即每批示例的影响都被传播回来以更新权重。实际上,即使考虑了GPU,也是这样的计算机硬件的大部分时间都处于“闲置”状态,只有一部分在更新时才处于活动状态。从某种意义上说,这是因为我们当前的计算机倾向于具有与CPU(或GPU)分离的内存。但在大脑中,情况可能不同——每个“存储单元”(即神经元)也是一个潜在的活动计算元素。如果我们能够这样设计未来的计算机硬件,可能就可以更有效地进行训练。

“大的足够的神经网络一定可以做任何事情!”

像ChatGPT这样的东西的能力非常令人印象深刻,以至于人们可能会想象,如果能够不断训练更大的神经网络,那么它们最终将能够“做任何事情”。如果关心的是对人类思维直接可见的事物,这种可能性是相当大的。但是过去几百年科学的教训是,有些可以通过形式过程得出的东西,但对人类思维不易获得。

非平凡数学就是一个很好的例子。但是一般情况是计算。最终问题在于计算的不可约性现象。有些计算似乎需要很多步骤才能完成,但实际上可以“缩减”成非常直接的过程。但计算不可约性的发现意味着这种情况并不总是成立。相反,存在一些过程,可能像下面的过程一样,必须基本上追踪每个计算步骤才能弄清发生了什么:

我们通常使用大脑进行的事情可能是有选择地避免计算不可约的。人们需要特别的努力才能在脑中进行数学运算。在实践中,仅凭人脑“思考”非平凡程序操作的每个步骤几乎是不可能的。

但当然,我们有计算机。使用计算机,我们可以轻松地进行长时间的计算不可约的事情。关键是,这些通常没有捷径。

是的,我们可以记住许多特定的计算系统示例。或许我们甚至可以看到一些(“计算可约”)模式,从而能够进行一些泛化。但是,计算不可约意味着我们永远不能保证不会出现意外情况,只有通过显式计算才能了解每种特定情况的真实情况。

最终,学习和计算不可约之间存在根本性的张力。学习实际上是通过利用规律压缩数据。但计算不可约意味着最终规律性存在局限性。

作为实际问题,人们可以想象将小型计算设备(如元胞自动机或图灵机)构建到可训练系统(如神经网络)中。事实上,这样的设备可以作为神经网络的良好“工具”, 但是计算不可简约性意味着我们不能指望“深入”这些设备并使其学习。

换句话说,能力和可训练性之间存在根本的权衡:你越想让系统真正利用其计算能力,它就会越表现出计算不可简约性,就越难以训练。而它越是基本可训练,就越难以进行复杂的计算。

(对于当前的 ChatGPT,情况实际上要更加极端,因为用于生成每个输出令牌的神经网络是一个纯“前馈”网络,没有循环,因此无法使用非平凡的“控制流”进行任何计算。)

当然,人们可能会想知道是否重要能够进行不可简约的计算。事实上,在人类历史的大部分时间里,这并不特别重要。但是我们现代的技术世界是建立在利用至少数学计算(越来越多地包括更一般的计算)的工程学上的。如果我们看看自然界,它充满了不可简约的计算,我们正在慢慢理解如何模拟和利用它们用于我们的技术目的。

没错,神经网络当然可以注意到自然世界中的那些规律,这些规律我们也可以很容易地用“非辅助人类思维”注意到。但是,如果我们想解决数学或计算科学的问题,除非神经网络有效地“使用”一个“普通”的计算系统,否则它无法完成这些任务。

但是,所有这些都有一些潜在的混淆。过去有很多任务,包括写作文,我们认为计算机在根本上无法完成。现在我们看到像ChatGPT这样的东西完成了这些任务,我们倾向于突然认为计算机一定变得更加强大了——特别是超越了它们已经基本能够做到的事情(比如渐进式地计算细胞自动机等计算系统的行为)。

但这不是正确的结论。计算不可化简过程仍然是计算不可化简的,对于计算机来说仍然是基本困难的,即使计算机可以轻松计算它们的各个步骤。相反,我们应该得出结论:我们人类能够做到的任务(例如写作文),但我们认为计算机无法完成的任务,在某种意义上实际上比我们想象的更容易计算。

换句话说,神经网络之所以能够成功地写出一篇文章,是因为写文章的问题在某种意义上比我们想象的更为“浅显易懂”。从某种意义上说,这使我们更接近“拥有一个理论”,解释我们人类如何完成诸如写作等任务,或者如何处理语言。

如果你有一个足够大的神经网络,那么你可能能够做到人类能够轻松完成的任何事情。但你不会捕捉到自然界总体上所能做到的事情——或者我们从自然界中塑造出来的工具所能做到的事情。正是这些工具——实际上的和概念上的——使我们在近几个世纪内超越了“纯粹不受帮助的人类思维”所能接触到的边界,并为人类目的捕捉到更多存在于物理和计算宇宙中的东西。

嵌入层 Embeddings 概念

神经网络——至少目前的神经网络——基本上是基于数字的。因此,如果我们要使用它们来处理文本之类的内容,我们需要一种用数字表示文本的方法。当然,我们可以(就像ChatGPT那样)从字典中为每个单词分配一个数字。但有一个重要的想法——例如对于ChatGPT来说非常重要——超越了这一点。这就是“嵌入”(embedding)的概念。可以将嵌入看作是通过一组数字来尝试表示某物的“本质”,并具有“附近的事物”由相邻数字表示的属性。

例如,我们可以将单词嵌入看作是试图在“意义空间”中排列单词,使得在嵌入中彼此“含义相似”的单词彼此相邻。实际使用的嵌入(例如在ChatGPT中)通常涉及大量数字的列表。但是,如果我们将其投影到二维空间中,就可以展示单词如何通过嵌入进行排列:

没错,我们看到这样的嵌入在捕捉典型的日常印象方面表现得非常出色。但是我们如何构建这样的嵌入?粗略地说,这个想法是查看大量文本(这里是来自网络的50亿个单词),然后看不同单词出现在其中的“环境”有多相似。因此,例如,“alligator”(鳄鱼)和“crocodile”(鳄鱼的另一种写法)经常几乎可以互换地出现在其他类似的句子中,这意味着它们将在嵌入中附近放置。但是,“turnip”(萝卜)和“eagle”(老鹰)不会在其他类似的句子中出现,因此它们将在嵌入中远离放置。

但是,如何使用神经网络实际实现这样的嵌入呢?让我们先讨论不是单词而是图像的嵌入。我们希望找到一种方法,通过数字列表来表征图像,使得“我们认为相似的图像”被分配类似的数字列表。

我们如何判断是否应该“认为图像相似”?好吧,如果我们的图像是手写数字的图像,如果它们是相同的数字,我们可能会“认为两个图像相似”。早些时候我们讨论过一个神经网络,它被训练来识别手写数字。我们可以将这个神经网络看作是设置在其最终输出中将图像放入10个不同的箱中的神经网络,每个箱子代表一个数字。

在开始时,我们将实际图像馈送到第一层,由像素值的二维数组表示。最后——从最后一层我们得到一个包含10个值的数组,我们可以认为它表示网络对图像对应于数字0到9的“确定性”。

输入图像 :

那么最后一层神经元的值为:

但是如果我们再往前看一步呢?网络的最后一个操作是一个所谓的softmax函数,它试图”强制确定”。但在应用它之前,神经元的值是:

表示“4”的神经元仍然具有最高的数值。但是其他神经元的值中也包含了信息。我们可以预期,这个数字列表可以用某种方式来描述图像的“本质”,从而提供我们可以用作嵌入的东西。例如,这里的每个4都有略微不同的“特征嵌入”或“特征签名”,与8完全不同:

我们基本上是用10个数字来描述图像特征。但使用更多的数字通常更好。例如,在我们的数字识别网络中,我们可以通过接入前面的层获得一个包含500个数字的数组。这可能是一个合理的“图像嵌入”数组。

如果我们想要对手写数字的“图像空间”进行明确的可视化,我们需要通过将我们获得的500维向量投影到三维空间中来“减少维度”。

我们刚才讨论了如何创建一种图像的特征化(从而得到Embedding),基本上是通过确定(根据我们的训练集)它们是否对应于相同的手写数字来确定图像的相似性。如果我们有一个标识出每个图像是哪个常见对象(猫、狗、椅子等)的训练集,我们可以更普遍地为图像做同样的事情。通过这种方式,我们可以创建一个图像Embedding,该Embedding通过我们对常见对象的识别进行“锚定”,但然后根据神经网络的行为“泛化”。关键是,只要该行为与我们人类感知和解释图像的方式一致,这将最终成为一种“看起来正确”的嵌入,对于执行“类似于人类判断”的任务实际上是有用的。

那么我们如何遵循同样的方法来找到单词的Embedding呢?关键是从一个关于单词的任务开始,我们可以很容易地进行训练。标准的这样的任务是“单词预测”。想象一下,我们得到了“the ___ cat”。基于大量的文本语料库(比如网页的文本内容),可能有哪些单词“填空”呢?或者,给定“___ black ___”,不同“侧翼单词”的概率是什么?

我们如何为单词创建嵌入呢?关键是从一个关于单词的任务开始,这个任务可以很容易地进行训练。标准的这种任务是“单词预测”。想象一下,我们被给出“the ___ cat”。根据大量的文本语料库(比如网络内容),哪些单词“填空”最有可能呢?或者换句话说,给定“___ black ___”,不同“flanking words”的概率是多少?

我们如何为神经网络设置这个问题?最终,我们必须用数字来表达一切。一种方法是为英语中的每个大约50,000个常见单词分配一个唯一的数字。因此,“the”可能是914,“ cat”(前面有一个空格)可能是3542。(这些是GPT-2实际使用的数字。)因此,在“the ___ cat”问题中,我们的输入可能是{914, 3542}。输出应该是一个由大约50,000个数字组成的列表,这些数字有效地给出了每个可能的“填充”单词的概率。为了找到嵌入,我们要“截取”神经网络“达到结论之前”的“内部” – 然后获取发生在那里的数字列表,我们可以认为它们“描述每个单词”。

那么这些特征长什么样呢?在过去的10年中,已经开发出了一系列不同的系统(例如word2vec,GloVe,BERT,GPT等),每个系统都基于不同的神经网络方法。但最终,它们都采用了将单词用由几百到几千个数字组成的列表来描述的方法。

在它们的原始形式中,这些“Embedding向量”并不具备信息性。例如,这是 GPT-2 为三个特定单词生成的原始嵌入向量:

(本文由公众号Python实用宝典翻译)

如果我们进行像测量这些向量之间的距离这样的事情,那么我们可以找到单词的“接近程度”。稍后我们将更详细地讨论这种Embedding的“认知”意义。但现在主要的观点是,我们有一种有用的方式将单词转化为“神经网络友好”的数字集合。

但实际上我们可以进一步将单词序列或整个文本块用这种方式进行表征。而ChatGPT内部也是这样处理的。它会将其目前所拥有的文本转化为一个嵌入向量来表示它。然后它的目标是找到可能出现的不同单词的概率。它会将其答案表示为一系列数字,这些数字本质上给出了每个可能单词的概率,大约有50,000个左右。

(严格来说,ChatGPT不仅处理单词,还会处理类似于“词根”的东西,后文我们把这个东西叫做标记(token)。使用标记使ChatGPT更容易处理罕见的、复合的和非英语单词,并且有时可以创造新单词,有利有弊。)

ChatGPT的原理

好的,我们终于准备好讨论 ChatGPT 的内部了。实际上,它最终是一个巨大的神经网络,目前是一个带有 1750 亿个权重的所谓 GPT-3 网络版本。在许多方面,这是一个与我们讨论过的其他神经网络非常相似的神经网络。但它是一个特别为处理语言而设置的神经网络,其最显着的特征是一个名为“transformer”的神经网络架构。

在我们上面讨论的第一个神经网络中,每个神经元在任何给定层上基本上都与前一层的每个神经元(至少以某种权重)连接。但是,如果处理具有特定已知结构的数据,这种全连接的网络(大概)可能是过度的。因此,例如,在处理图像的早期阶段,通常使用所谓的卷积神经网络(“convnets”),其中神经元实际上排列在类似于图像中的像素的网格上,仅与网格上附近的神经元相连接。

transformer 的想法是对组成文本的标记序列做类似的事情。但是,transformer 并不仅仅是定义了一个在序列中可以存在连接的固定区域,而是引入了“注意力”的概念,并且有时“更多关注”序列的某些部分。也许有一天,只需启动通用神经网络并通过训练进行所有定制就会有意义。但是至少到目前为止,在实践中将事物“模块化”似乎至关重要,正如 transformers 所做的那样,也可能是我们的大脑所做的那样。

好的,那么ChatGPT(或者说它基于的GPT-3网络)到底是做什么的呢?回想一下,它的总体目标是根据它从训练数据(即来自网络等的数十亿页文本)中所看到的内容,合理地”继续”文本。因此,在任何给定时刻,它都有一定量的文本——它的目标是选择一个适当的下一个token来添加。

它分为三个基本阶段。首先,它获取与到目前为止的文本相对应的标记序列,并找到表示这些标记的embedding(即一组数字)。然后,它在这个embedding上进行操作——以“标准神经网络方式”,值会“涟漪”到网络的连续层中,以生成一个新的embedding(即一个新的数字数组)。然后,它取这个数组的最后一部分,并从中生成一个大约包含50,000个值的数组,这些值会转化为不同可能的下一个标记的概率。(是的,碰巧标记的数量与英语中常用单词的数量大致相同,尽管只有大约3000个标记是整个单词,其余都是片段。)

关键点在于,这个管道的每个部分都由神经网络实现,其权重是通过端到端训练网络确定的。换句话说,实际上除了整体架构外,没有任何部分是“明确设计”的;一切都是从训练数据中“学习”的。

然而,在架构设置方面有很多细节——反映了各种经验和神经网络知识。虽然这肯定会很深入细节,但我认为讨论一些这些细节是有用的,至少可以让人们了解构建ChatGPT这样的东西需要哪些内容。首先是嵌入模块。以下是GPT-2的一个草图表示:

(本文由公众号Python实用宝典翻译)

输入是一个由n个标记组成的向量(如前一节所述,表示为从1到约50,000的整数)。其中每个标记通过一个单层神经网络转换为一个嵌入向量(对于GPT-2长度为768,对于ChatGPT的GPT-3长度为12,288)。同时,还有一个“次要路径”,它获取标记的(整数)位置序列,并从这些整数创建另一个嵌入向量。最后,标记值和标记位置的嵌入向量被加在一起,以生成来自嵌入模块的最终嵌入向量序列。

为什么要将标记值和标记位置嵌入向量加在一起呢?我认为这并没有特定的科学依据。只是尝试了各种不同的方法,这种方法似乎是有效的。并且神经网络的传统认为,在某种程度上,只要设置大致正确,通常可以通过进行足够的训练来精细调整细节,而无需真正理解神经网络是如何配置自己的。

这是Embedding模块对字符串”hello hello hello hello hello hello hello hello hello hello bye bye bye bye bye bye bye bye bye bye”的操作:

好的,在Embdding模块之后是transformer的“主要事件”:所谓的“注意力块”序列(对于GPT-2为12个,对于ChatGPT的GPT-3为96个)。这一切相当复杂,让人想起典型的大型难以理解的工程系统或生物系统。但无论如何,这里是单个“注意力块”的原理图(适用于GPT-2):

在每个这样的注意力块中,都有一组“注意力头”(对于GPT-2来说是12个,对于ChatGPT的GPT-3来说是96个),每个头独立地操作嵌入向量中不同的值块。 (是的,我们不知道将嵌入向量拆分为若干部分的好处,或者不同部分的含义是什么;这只是那些“被发现有效”的东西之一。)

那么这些注意力头具体做什么呢?基本上它们是一种“回顾”标记序列的方式(即回顾到迄今为止已生成的文本),并将“过去的信息”以一种对查找下一个标记有用的方式打包起来。在上面的第一部分中,我们谈到了使用二元概率来选择基于其直接前驱的单词。Transformer中的“注意力”机制允许“注意力”甚至回到更早的单词,从而可能捕捉动词可以引用在句子中出现在它们前面的名词的方式,等等。

更详细地说,注意力头所做的是重新组合与不同标记相关联的嵌入向量的块,并赋予一定的权重。因此,例如,在第一个注意力块(GPT-2中)中的12个注意力头中,对于上述“hello,bye”字符串,具有以下(“回顾到标记序列开头”)“重新组合权重”的模式:

在通过注意力头处理后,结果的“重新加权嵌入向量”(GPT-2的长度为768,ChatGPT的GPT-3的长度为12,288)被传递到一个标准的“全连接”神经网络层。很难掌握这个层正在做什么。但是这里有一个使用它的768×768权重矩阵的绘图(这里是针对GPT-2):

通过64×64移动平均处理,一些(随机游走的)结构开始出现:

什么决定了这个结构?最终,这可能是人类语言特征的“神经网络编码”。但是,就目前而言,这些特征可能是相当未知的。实际上,我们正在“打开ChatGPT的大脑”(或者至少是GPT-2),发现了一些繁琐的内容,而我们并不理解,即使最终它产生了可识别的人类语言。

好的,在经过一个注意力块之后,我们得到了一个新的嵌入向量,然后连续地通过附加的注意块进行传递(GPT-2总共有12个;GPT-3有96个)。每个注意块都有自己特定的“关注”和“完全连接”权重模式。这是GPT-2的第一个注意头的“hello,bye”输入序列的注意权重序列:

以下是全连接层的(移动平均后的)“矩阵”:

有趣的是,即使在不同的attention block中,这些“权重矩阵”看起来相似,权重大小的分布也可能有所不同(而且并不总是高斯分布)。

那么,经过所有这些注意力块之后,转换器的净效应是什么呢?实质上,它将标记序列的原始嵌入集合转换为最终集合。 ChatGPT 的特定工作方式是选择此集合中的最后一个嵌入,并“解码”它以产生下一个标记应该是什么的概率列表。

这就是 ChatGPT 内部的概述。它可能看起来很复杂(尤其是因为它有许多不可避免的有点随意的“工程选择”),但实际上所涉及的最终元素非常简单。因为最终我们所处理的只是一个由“人造神经元”构成的神经网络,每个神经元都执行将一组数字输入与某些权重结合的简单操作。

ChatGPT的原始输入是一个数字数组,即迄今为止标记的嵌入(Embedding)向量。当ChatGPT“运行”以产生新的标记时,这些数字会“涟漪”通过神经网络的层,每个神经元“执行其任务”并将结果传递给下一层的神经元。没有循环或“回溯”。一切都只是通过网络“前馈”。

这与典型的计算系统(如图灵机)非常不同,在这种系统中,结果会被同一计算元素重复“重新处理”。在这里,至少在生成给定输出标记方面,每个计算元素(即神经元)仅使用一次。

但从某种程度上来说,即使在ChatGPT中,仍然存在一种在计算元素之间重复使用的“外部循环”。因为当ChatGPT准备生成一个新的标记时,它总是“读取”(即将其作为输入)在其之前出现的所有标记序列,包括ChatGPT自己先前“写入”的标记。我们可以认为这种设置意味着ChatGPT至少在其最外层级别上涉及一个“反馈循环”,尽管每次迭代都明确可见为在其生成的文本中出现的标记。

但让我们回到ChatGPT的核心:被重复使用以生成每个标记的神经网络。在某种程度上,它非常简单:是一整个相同人工神经元的集合。网络的某些部分只包含(“全连接”)神经元层,其中每个给定层上的神经元都与前一层上的每个神经元连接(具有一定的权重)。但特别是在其Transformer体系结构中,ChatGPT具有更多结构的部分,其中仅在不同层上的特定神经元相连接。(当然,仍然可以说“所有神经元都相连”——但有些只有零权重。)

此外,ChatGPT中的神经网络还有一些方面不太容易被认为只包含“同质”的层。例如,正如上面的标志性摘要所示,在attention block中,会有“复制多个副本”的地方,每个副本都会经过不同的“处理路径”,可能涉及不同数量的层,并在稍后重新组合。但虽然这可能是对正在发生的事情的一种方便的表示,但在原则上总是可以认为是“密集填充”层,只是有一些权重为零。

如果观察ChatGPT中最长的路径,涉及大约400个(核心)层-在某些方面并不是很多。但有数百万个神经元,共计1750亿个连接,因此有1750亿个权重。一个要意识到的事情是,每次ChatGPT生成一个新的标记时,都必须进行涉及到每个权重的计算。在实现上,这些计算可以通过高度并行的数组操作“按层”有序地组织在GPU上完成。但对于每个生成的标记,仍然必须执行1750亿个计算(最后还要多一点)-因此,毫不奇怪,使用ChatGPT生成一篇长文本可能需要一段时间。

但最终,值得注意的是,所有这些操作,尽管它们各自都很简单,但总能一起完成如此出色的“类人”文本生成工作。必须再次强调的是,(至少就我们目前所知),没有“最终的理论原因”说明为什么类似于ChatGPT的神经网络应该能够工作。实际上,正如我们将要讨论的,我认为我们必须将其视为一项潜在惊人的科学发现:在像ChatGPT这样的神经网络中,某种程度上可以捕捉到人类大脑在生成语言方面所能做到的本质。

ChatGPT的训练

好的,现在我们概述了ChatGPT被训练完成后的工作方式。但是它是如何训练的呢?所有那1750亿个神经网络权重是如何确定的?基本上,它们是通过基于人类撰写的大量文本语料库(网络、书籍等)进行的大规模训练得出的结果。正如我们所说,即使给定所有这些训练数据,神经网络能够成功地产生“类似人类”的文本也并不明显。并且,再次强调,似乎需要详细的工程细节才能实现这一点。但是,ChatGPT的惊喜和发现是,这是可能的。实际上,“只有”1750亿个权重的神经网络可以对人类写作的文本进行“合理的建模”。

ChatGPT训练的基本过程就像我们在上面简单的例子中讨论的那样。你提供一批例子,然后调整网络中的权重以最小化网络在这些例子上的误差(”损失”)。关于“反向传播”的主要昂贵之处在于,每次执行此操作时,网络中的每个权重通常都会至少微调一点,而要处理的权重数量就是非常庞大的。(实际的“反向计算”通常只比前向计算复杂一个小常数因子。)

通过现代 GPU 硬件,可以轻松并行计算数千个示例的结果。但是,当涉及到实际更新神经网络中的权重时,当前的方法需要基本上批量更新。 (是的,这可能是实际大脑目前至少在体系结构上具有优势的地方,因为它们具有结合计算和存储元件。)

在我们之前讨论过的学习数值函数的看似简单的情况下,我们发现我们通常需要使用数百万个示例来成功训练网络,至少是从头开始。那么,为了训练一个“类人语言”的模型,我们需要多少个示例呢?似乎没有任何根本的“理论”方法可以知道。但实际上,ChatGPT是在数千亿个单词的文本上成功训练的。

有些文本被多次输入,有些文本只被输入了一次。但是不知何故,它从它看到的文本中“得到了它所需要的”。但是,考虑到这么多文本要学习,它需要多大的网络才能“学得好”呢?同样,我们还没有一个根本的理论方法来回答这个问题。最终——正如我们将在下面进一步讨论的——人类语言及其通常使用的算法内容可能具有某种“总算法内容”。但是下一个问题是,神经网络在实现基于该算法内容的模型时有多高效。同样,我们不知道——尽管ChatGPT的成功表明它是相当有效的。

最终我们可以注意到,ChatGPT使用了几百亿个权重来完成它的任务,这个数量与它所接收的训练数据的单词(或标记)总数相当。在某些方面,这可能是令人惊讶的(尽管在ChatGPT的较小模型中也观察到了这一点),即“能够良好运行的网络规模”与“训练数据规模”是如此相似。毕竟,在ChatGPT内部并不是所有来自Web和图书等内容的文本都被“直接存储”。因为ChatGPT内部实际上包含了一堆数字——精度不到10个数字——它们是所有这些文本的聚合结构的某种分布式编码。

换句话说,我们可以问一下人类语言的“有效信息内容”是什么,以及通常使用它来表达什么。有原始的语言样本语料库,还有ChatGPT神经网络中的表示。这个表示很可能远不是“算法上最小化”的表示方式(正如我们将在下面讨论的那样)。但它是一个神经网络可以方便使用的表示方式。在这个表示中,训练数据似乎在最后很少被“压缩”;平均而言,基本上只需要不到一个神经网络权重来承载一个单词的“信息内容”。

当我们运行ChatGPT生成文本时,我们基本上需要使用每个权重一次。因此,如果有n个权重,我们需要做约n个计算步骤,尽管在实践中,许多计算可以在GPU中并行完成。但是,如果我们需要约n个单词的训练数据来设置这些权重,那么根据上面的说法,我们可以得出结论:我们需要约n^2个计算步骤来训练网络——这就是为什么用现有方法需要数十亿美元来进行训练的原因。

超越基本的训练

在训练ChatGPT中,大部分的工作量都花费在“向它展示”来自网络、书籍等大量的现有文本上。但事实证明,还有另一个看起来相当重要的部分。

完成原始文本语料库的训练后,ChatGPT即可从提示信息中生成自己的文本。尽管在许多情况下结果似乎合理,但特别是在生成较长的文本时,往往会出现偏离人类思维方式的情况。这种问题不是通过对文本进行传统的统计分析可以轻松检测到的,但读者却很容易注意到这一点。

ChatGPT的构建中的一个关键思想是,在“被动阅读”像网络这样的事物之后,有另一步是让实际人类与ChatGPT进行积极互动,查看它所生成的内容,并实际上对其进行反馈,告诉它“如何成为一个好的聊天机器人”。但神经网络如何利用这个反馈呢?第一步是让人类对神经网络生成的结果进行评分。然后建立另一个神经网络模型来尝试预测这些评分。但现在可以在原始网络上运行这个预测模型,实际上就像一个损失函数,从而允许该网络通过人类反馈进行“调整”。实践中的结果似乎对于ChatGPT在生成“类人”输出方面的成功有很大影响。

总的来说,有趣的是,“原始训练”网络似乎需要很少的“干预”即可使其有条不紊地朝着特定方向发展。人们可能认为,为了使网络表现得好像“学到了新东西”,需要进行训练算法、调整权重等操作。但事实并非如此。相反,基本上只需要告诉ChatGPT一次东西 – 作为您提供的提示的一部分 – 然后它就可以成功地在生成文本时利用您告诉它的内容。再次说明,这种方法的成功是我认为了解ChatGPT“真正做什么”以及它与人类语言和思维结构之间的关系的重要线索。

这其中肯定有某种类人的特点:至少在进行了所有这些预训练后,您只需告诉它一次东西,它就可以“记住” – 至少“足够长时间”以使用它生成一段文本。在这种情况下发生了什么?可能是“您可能告诉它的所有内容已经在某个地方了”,而您只是在引导它到正确的位置。但是这似乎不可行。相反,更可能的是,是的,这些元素已经在那里了,但具体细节是由类似于“这些元素之间的轨迹”的东西定义的,而这就是您在告诉它某些东西时所介绍的内容。

ChatGPT与人类一样,如果你告诉它一些奇怪和出乎意料的东西,完全不符合它所知道的框架,它似乎无法成功地“整合”它。只有当它基本上在已有的框架之上以相当简单的方式进行运行时,它才能够“整合”它。

再次指出的值得注意的是,神经网络的“捕捉”能力不可避免地有“算法限制”。告诉它形式为“这个东西对应那个”的“浅层规则”,神经网络很可能可以很好地表示和复制这些规则,事实上,它从语言中“已知的”将给它一个立即遵循的模式。但是,如果试图为涉及许多潜在的计算不可约简步骤的实际“深度”计算提供规则,它就行不通了。(请记住,在每一步中,它始终只是在其网络中“向前传递数据”,除了通过生成新的标记之外,从未循环。)

当然,网络可以学习特定的“不可约”计算的答案。但是,一旦存在组合数量的可能性,就不可能使用这种“表格查找式”的方法。因此,就像人类一样,现在神经网络需要“伸手”并使用实际的计算工具。

是什么让ChatGPT真正地工作起来

人类语言——以及产生语言的思维过程——一直被认为是复杂程度的巅峰。确实,人类的大脑只有“仅仅”1000亿左右的神经元(和可能达到100万亿的连接),就能够完成这项任务,这似乎相当不可思议。或许,人类大脑不仅仅是由神经元组成——也许还有一些尚未被发现的新物理层面。但现在我们有了 ChatGPT,这为我们提供了一个重要的新信息:我们知道了一个拥有与人类神经元数目相近的连接数的纯人工神经网络可以出乎意料地成功生成人类语言。

是的,这仍然是一个庞大而复杂的系统——其神经网络权重数目大约与目前世界上可用文本中的单词数目相当。但在某种程度上,似乎仍然难以相信所有语言的丰富性和它所能谈论的事物都可以被包含在这样一个有限的系统中。这背后的部分原因毫无疑问是 ubiquitous 现象,即即使基础规则很简单,计算过程也能在实际中大大放大系统的表象复杂性。但实际上,正如我们上面讨论的那样,ChatGPT 中使用的神经网络类型通常是有针对性的构造,以限制这种现象的影响,以及与之相关的计算不可约简性,以便使其训练更易于理解。

那么,这些规律会是什么样子呢?它们最终必须给我们提供一些关于语言及其表达方式的指导。稍后我们将讨论如何“深入了解ChatGPT”可能会为我们提供一些线索,以及从构建计算语言所知道的内容如何为我们指明一条前进的道路。但首先,让我们讨论两个长期以来已知的“语言规律”的例子,以及它们与ChatGPT的操作方式的关系。

第一个例子是语言的语法。语言不仅仅是随意组合的单词。相反,对于不同种类的单词,有(相当)明确的语法规则:例如,在英语中,名词可以由形容词前置并由动词后置,但通常两个名词不能紧挨在一起。这样的语法结构可以(至少近似地)通过定义如何组合“解析树”的规则来捕捉到:

ChatGPT 没有任何明确的对这些规则的“知识”。但不知何故,在其训练中,它隐式地“发现”了它们,然后似乎擅长遵循它们。那么这是如何工作的呢?在“大局”层面上,这还不清楚。但是,为了获得一些洞见,也许看一个更简单的例子会有帮助。

考虑一个由序列构成的“语言”,其语法规定括号始终应该是平衡的,如下面所示的解析树所代表的那样:

我们能训练一个神经网络生成“符合语法”的括号序列吗?神经网络中处理序列的方法有很多种,但我们可以使用转换器网络(transformer nets),就像ChatGPT一样。我们可以将规范的括号序列作为训练样本来训练一个简单的转换器网络。一个微妙之处(实际上也出现在ChatGPT生成人类语言的过程中)是,除了我们的“内容标记”(这里是“(”和“)”)之外,我们还必须包含一个“结束”标记,以表示输出不应继续下去(即对于ChatGPT,已经到达“故事的结尾”)。

如果我们只设置一个具有8个头和长度为128的特征向量的注意力块的转换器网络(ChatGPT也使用长度为128的特征向量,但具有96个注意力块,每个块具有96个头),那么似乎不可能使其学习到关于括号语言的很多知识。但是当我们使用2个注意力块时,学习过程似乎会收敛——至少在提供了大约1000万个样本之后会如此(并且,像转换器网络一样,提供更多的样本只会降低其性能)。

因此,我们可以用这个网络做类似于ChatGPT的事情,并询问下一个标记应该是什么的概率——在括号序列中:

(本文由公众号Python实用宝典翻译)

在第一个案例中,神经网络“相当确定”这个序列不能在这里结束,这很好,因为如果它结束了,括号就会不平衡。然而,在第二个案例中,它“正确地识别”到序列可以在这里结束,尽管它还指出“可以重新开始”,放下一个“(”,接着是“)”。但是,糟糕的是,即使是用它训练了约400,000个繁琐的权重,它也说有15%的概率将“)”作为下一个标记,这是不正确的,因为那必然会导致不平衡的括号。

如果我们询问网络逐渐增加( 的序列的最高概率完成情况,我们将得到以下结果:

这就意味着像ChatGPT和英语这样的语言的语法有什么含义呢?括号语言很“简单”,更像是一种“算法故事”。但在英语中,基于单词和其他提示的局部选择,我们很有可能“猜测”什么在语法上是匹配的。是的,神经网络在这方面表现得更好,尽管它也可能会漏掉一些“形式上正确”的情况,但这可能与人类的漏洞相似。

但主要观点是,语言的整体语法结构——及其所涉及的规则性——在某种程度上限制了神经网络需要学习的“范围”。而关键的“自然科学式”的观察是,像ChatGPT中的神经网络这样的 Transformer 体系结构似乎成功地学习了类似于嵌套树形的句法结构,这种结构似乎在所有人类语言中都存在(至少近似如此)。

语法提供了一种对语言的限制,但显然还有其他的限制。像“nquisitive electrons eat blue theories for fish”(探究性电子吃蓝色理论以换取鱼) 这样的句子在语法上是正确的,但并不是我们通常会说的,如果ChatGPT生成了这样的句子,也不会被认为是成功的——因为,嗯,就是用其中的单词的正常含义而言,它基本上是没有意义的。

但是有没有一种通用的方法来判断一个句子是否有意义呢?传统上没有一个总体的理论。但可以认为,在被训练了数十亿个(可能有意义的)句子后,ChatGPT在隐含地“发展了一个理论”

这个理论会是什么样子呢?有一个微小的角落基本上已经被知道了两千年,那就是逻辑。尤其是在亚里士多德发现的三段论形式中,逻辑基本上是一种说法,即遵循某些模式的句子是合理的,而其他句子则不是。因此,例如,“所有X都是Y。这个不是Y,所以它不是X”是合理的(如“所有的鱼都是蓝色的。这个不是蓝色的,所以它不是一条鱼。”)。就像可以有些任性地想象亚里士多德通过大量的修辞例子(“机器学习式”)发现三段论逻辑一样,人们也可以想象在 ChatGPT 的训练中,它将能够通过查看网络上的大量文本等来“发现三段论逻辑”。(是的,虽然可以因此期望 ChatGPT 产生包含基于三段论逻辑等的“正确推理”的文本,但当涉及到更复杂的形式逻辑时,情况就大不相同了——我认为人们可以期望它因与括号匹配相同的原因而失败。)

但除了逻辑的狭窄例子之外,还有什么可以系统地构建(或识别)即使是合理的有意义文本的方法呢?是的,有像 Mad Libs 这样使用非常具体的“短语模板”的东西。但不知何故,ChatGPT 无形中有了一个更普遍的方法来做到这一点。也许除了“当你有 1750 亿个神经网络权重时,它就会发生”之外,我们无法说出它如何做到。但我强烈怀疑,这背后存在一个更简单、更有力的故事。

意义空间和语义动力学

我们上面讨论了在ChatGPT内部,任何一段文本实际上都由一组数字表示,我们可以将其视为某种“语言特征空间”中的点的坐标。因此,当ChatGPT继续一段文本时,这相当于在语言特征空间中跟踪轨迹。但现在我们可以问,是什么让这条轨迹对应于我们认为有意义的文本?也许可能存在某种“语义运动规律”,定义或者至少限制了语言特征空间中的点如何移动,同时保持“有意义性”?

那么这个语言特征空间是什么样的?以下是一个示例,展示了单个单词(这里是普通名词)在我们将这样的特征空间投影到二维平面时的分布情况:

以下是不同词类的单词在特征空间中的布局方式:

当然,通常来说,一个词并不只有“一个意思”(也不一定只对应一种词性)。通过查看包含一个单词的句子在特征空间中的排布,我们通常可以“区分”不同的含义,就像这里的“crane”一词(是指鸟还是机器)的例子一样:

好的,我们可以将这个特征空间想象为将“语义相似的词”放置在附近的空间。但是,我们能够在这个空间中识别出什么样的额外结构呢?例如,是否有一种“平行传送”的概念,反映了空间的“平坦性”?研究类比问题可能有助于理解这一点:

那么,轨迹呢?我们可以观察ChatGPT在特征空间中跟随的提示轨迹,然后再看看它如何继续下去:

这里显然没有什么“几何明显”的运动规律。这一点并不令人惊讶;我们完全期望这将是一个相当复杂的故事。例如,即使存在“语义运动定律”,什么样的嵌入(或者实际上是什么“变量”)最自然也远非显而易见。

在上面的图片中,我们展示了“轨迹”的几个步骤,其中每个步骤我们选择了ChatGPT认为最有可能的单词(“temperature=0”情况)。但我们还可以问,在给定点时下一个可能出现的单词是什么,以及它们的概率是多少:

在这个案例中,我们可以看到一个在特征空间中有着相对明确方向的高概率词汇的“扇形”分布。如果我们继续下去会发生什么呢?下面是随着轨迹的“移动”而出现的连续的“扇形”:

这里是一个总共有40步的三维展示:

上面这个表现看起来有些混乱,没有特别鼓励这种想法,即通过实证研究“ChatGPT内部的运作”来确定“数学物理一样的”“语义运动定律”。但也许我们只是在看“错误的变量”(或错误的坐标系),如果我们只看正确的变量,我们就会立即看到ChatGPT正在做一些“数学物理简单”的事情,比如遵循测地线。但是,目前为止,我们还没有准备好从其“内部行为”“实证解码”ChatGPT已经“发现”的有关人类语言“组装”的内容。

总结

ChatGPT的基本概念在某种程度上相当简单。从网络、书籍等来源中获取大量人类创造的文本样本,然后训练神经网络生成“类似”的文本。特别地,使它能够从“提示”开始,然后继续生成“与它所受过的训练相似”的文本。

正如我们所见,ChatGPT中的实际神经网络由非常简单的元素组成。神经网络的基本操作也非常简单,本质上是对每个新单词(或单词的一部分)生成文本时,对其已生成的文本产生的输入“通过其元素”(没有任何循环等)进行一次传递。

但是,这个过程的卓越而意想不到的一点是,它可以产生成功“类似”网络、书籍等中存在的文本。它不仅是连贯的人类语言,而且“说出”了“遵循提示”的事情,利用它所“读取”的内容。它并不总是说出“全局上有意义”的话(或与正确的计算相符),它只是根据它训练材料中的内容“听起来对”的话。

ChatGPT的具体工程设计使其相当令人信服。但是,至少在它可以使用外部工具之前,它“仅仅”是从其积累的“传统智慧的统计数据”中提取出一些“连贯的文本线索”。但是,其结果与人类产生的非常相似,这很令人惊奇。正如我所讨论的,这表明某些东西至少在科学上非常重要:人类语言(及其背后的思维模式)在其结构上比我们想象的更简单、更“法则化”。ChatGPT已经隐式地发现了这一点。但我们可以通过语义语法、计算语言等,可能会明确地暴露它。

ChatGPT在生成文本方面所做的工作非常令人印象深刻,其结果通常与我们人类产生的非常相似。那么,这是否意味着ChatGPT像大脑一样工作?其基本的人工神经网络结构最终是以大脑的理想化模型为基础建模的。而且很可能当我们人类生成语言时,许多方面的情况非常相似。

但是,值得注意的是,尽管 ChatGPT 在生成文本方面表现出人类的能力,但其实现方式与大脑有所不同。与人类的大脑和当前计算机的“硬件”有所不同,ChatGPT 不得不采用一种可能非常不同(在某些方面更少效率)的策略进行训练(也就是学习)。此外,还有一点:与典型算法计算中的情况不同,ChatGPT 没有内部的“循环”或“重新计算数据”。这必然限制了它的计算能力,甚至对于当前的计算机也是如此,但对于大脑来说,更是如此。

但就目前而言,看到 ChatGPT 已经能够做到的事情令人兴奋。从某种程度上说,它是基本科学事实的一个很好的例子,即大量的简单计算元素可以做出引人注目和出乎意料的事情。但它也提供了我们在两千年里最好的推动力,去更好地理解人类语言以及其背后的思维过程的基本特征和原则。

总之,ChatGPT 是一种极其强大的自然语言生成系统,它的训练过程基于大量的人类语言文本样本,并采用了深度学习技术。尽管其基本结构与人类大脑的工作方式有所不同,但它已经展示了大量简单计算元素的组合可以产生出令人惊讶的结果。这为我们深入理解人类语言和思维模式的本质提供了有力的推动。

本文翻译自:https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python paddleseg 图像分割实战教程 — 眼底识别

在人工智能领域中,有一项非常关键的技术,那就是图像分割

图像分割是指将图像中具有特殊意义的不同区域划分开来, 这些区域互不相交,每个区域满足灰度、纹理、彩色等特征的某种相似性准则。

比如上图识别视盘。视盘是视网膜中的关键解剖学结构,其形状、面积和深度等参数是衡量眼底健康状况的重要指标,准确定位和分割视盘区域是眼底图像分析和处理的关键步骤。

在人工智能的辅助下,只需要数秒,即可初步判断被检者是否存在眼底疾病,这将有助缓解专业眼科医生不足的瓶颈,开启眼底疾病的基层筛查新模式。而图像分割就是实现这项功能的基础,可见其重要性。

下面就给大家讲讲如何基于 PaddlePaddle 平台,训练并测试一个视盘图像分割的基本模型。

1.Python 准备

为了实现这个实验,Python 是必不可少的,如果你还没有安装 Python,建议阅读我们的这篇文章:超详细Python安装指南

在安装前,确认自己需要的 PaddlePaddle 版本,比如 GPU版 或 CPU版,GPU 在计算上具有绝对优势,但是如果你没有一块强力的显卡,建议选择CPU版本。

(GPU版) 如果你想使用GPU版,请确认本机安装了 CUDA 计算平台及 cuDNN,它们的下载地址分别是:
https://developer.nvidia.com/cuda-downloads
https://developer.nvidia.com/cudnn-download-survey

具体 CUDA 和 cuDNN 对应的版本要求如下:

  • CUDA 工具包10.1/10.2配合cuDNN v7.6+
  • CUDA 工具包11.2配合cuDNN v8.1.1

CUDA安装流程很简单,下载exe程序,一路往下走。cuDNN安装流程复杂一些,你需要转移压缩包解压后的部分文件到CUDA中,具体可见这篇cuDNN的官方安装指引:
https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html

(CPU版)CPU版安装过程比较简单,直接按照下面 PaddlePaddle 的安装指引输入命令即可。


(通用)选择完你想要安装的版本,并做好基础工作后,接下来就是安装 PaddlePaddle 的具体步骤,打开安装指引流程页面:
https://www.paddlepaddle.org.cn/install/quick

根据你自己的情况选择这些选项,最后一个选项计算平台指的是 GPU 加速工具包或CPU,如果你不想用GPU,请选CPU版;想用GPU版的同学请按刚刚下载的CUDA版本进行选择。

选择完毕后下方会出现安装信息,输入安装信息里的命令即可安装成功,不得不说,PaddlePaddle 这些方面做的还是比较贴心的。

在页面下方还有具体的从头到尾的安装步骤,对 Python 基本的虚拟环境安装流程不了解的同学可以看着这些步骤进行安装。

2.初尝paddleseg图像分割

安装完 paddle 后,为了能够实现图像分割功能,我们还需要安装 paddleseg:

pip install paddleseg

并克隆 paddleseg的代码库(如果克隆不了,请在Python实用宝典公众号后台回复:图像分割 下载):

git clone https://github.com/PaddlePaddle/PaddleSeg.git

克隆完成,进入代码库文件夹:

cd PaddleSeg

执行下面命令,并在 PaddleSeg/output 文件夹中出现预测结果,则证明安装成功。

python predict.py \
       --config configs/quick_start/bisenet_optic_disc_512x512_1k.yml \
       --model_path https://bj.bcebos.com/paddleseg/dygraph/optic_disc/bisenet_optic_disc_512x512_1k/model.pdparams\
       --image_path docs/images/optic_test_image.jpg \
       --save_dir output/result

预测结果如下:

3.训练模型

前面只是利用了 PaddlePaddle 提前训练好的数据进行预测,下面我们要尝试自己训练一个模型。

为了训练模型,我们需要获得眼底训练集。事实上,在前面 初尝 Paddleseg 中,我们便获得了一份眼底训练集,其路径是 PaddleSeg\data\optic_disc_seg.

如果你没有进行 初尝 Paddleseg 这一节,也想要获取训练集数据的话,在Python实用宝典公众号后台回复:图像分割 下载。下载后解压数据集,得到一个optic_disc_seg文件夹,将其放到 PaddleSeg 代码库的 data 文件夹下。

配置化训练

PaddleSeg 提供了配置化驱动进行模型训练。他们在配置文件中详细列出了每一个可以优化的选项,用户只要修改这个配置文件就可以对模型进行定制。

所有的配置文件在PaddleSeg/configs文件夹下面

每一个文件夹代表一个模型,里面包含这个模型的所有配置文件。

在PaddleSeg的配置文件给出的学习率中,除了”bisenet_optic_disc_512x512_1k.yml”中为单卡学习率外,其余配置文件中均为4卡的学习率,因此如果你是单卡训练,则学习率设置应变成原来的1/4。

为了简化学习难度,我们继续以”bisenet_optic_disc_512x512_1k.yml”文件为例,修改部分参数进行训练,下面是这个配置的全部说明:

batch_size: 4  #设定batch_size的值即为迭代一次送入网络的图片数量,一般显卡显存越大,batch_size的值可以越大
iters: 1000    #模型迭代的次数

train_dataset: #训练数据设置
  type: OpticDiscSeg #选择数据集格式
  dataset_root: data/optic_disc_seg #选择数据集路径
  num_classes: 2 #指定目标的类别个数(背景也算为一类)
  transforms: #数据预处理/增强的方式
    - type: Resize #送入网络之前需要进行resize
      target_size: [512, 512] #将原图resize成512*512在送入网络
    - type: RandomHorizontalFlip #采用水平反转的方式进行数据增强
    - type: Normalize #图像进行归一化
  mode: train

val_dataset: #验证数据设置
  type: OpticDiscSeg #选择数据集格式
  dataset_root: data/optic_disc_seg #选择数据集路径
  num_classes: 2 #指定目标的类别个数(背景也算为一类)
  transforms: #数据预处理/增强的方式
    - type: Resize  #将原图resize成512*512在送入网络
      target_size: [512, 512]  #将原图resize成512*512在送入网络
    - type: Normalize #图像进行归一化
  mode: val

optimizer: #设定优化器的类型
  type: sgd #采用SGD(Stochastic Gradient Descent)随机梯度下降方法为优化器
  momentum: 0.9 #动量
  weight_decay: 4.0e-5 #权值衰减,使用的目的是防止过拟合

learning_rate: #设定学习率
  value: 0.01  #初始学习率
  decay:
    type: poly  #采用poly作为学习率衰减方式。
    power: 0.9  #衰减率
    end_lr: 0   #最终学习率

loss: #设定损失函数的类型
  types:
    - type: CrossEntropyLoss #损失函数类型
  coef: [1, 1, 1, 1, 1]
  #BiseNetV2有4个辅助loss,加上主loss共五个,1表示权重 all_loss = coef_1 * loss_1 + .... + coef_n * loss_n

model: #模型说明
  type: BiSeNetV2  #设定模型类别
  pretrained: Null #设定模型的预训练模型

你可以尝试调整部分参数进行训练,看看你自己训练的模型效果和官方给出的模型的效果的差别。

开始训练

(GPU版)在正式开启训练前,我们需要将CUDA设置为目前有1张可用的显卡:

set CUDA_VISIBLE_DEVICES=0 # windows
# export CUDA_VISIBLE_DEVICES=0 # linux

输入训练命令开始训练:

python train.py \
       --config configs/quick_start/bisenet_optic_disc_512x512_1k.yml \
       --do_eval \
       --use_vdl \
       --save_interval 500 \
       --save_dir output
训练参数解释
参数名用途是否必选项默认值
iters训练迭代次数配置文件中指定值
batch_size单卡batch size配置文件中指定值
learning_rate初始学习率配置文件中指定值
config配置文件
save_dir模型和visualdl日志文件的保存根路径output
num_workers用于异步读取数据的进程数量, 大于等于1时开启子进程读取数据0
use_vdl是否开启visualdl记录训练数据
save_interval_iters模型保存的间隔步数1000
do_eval是否在保存模型时启动评估, 启动时将会根据mIoU保存最佳模型至best_model
log_iters打印日志的间隔步数10
resume_model恢复训练模型路径,如:output/iter_1000None
keep_checkpoint_max最新模型保存个数5

见到如下的界面,说明你已经开始训练了:

4.训练过程可视化

PaddlePaddle 还提供了可视化分析工具:VisualDL,让我们的网络训练过程更加直观。

当打开use_vdl开关后,PaddleSeg会将训练过程中的数据写入VisualDL文件,可实时查看训练过程中的日志。记录的数据包括:

  1. loss变化趋势
  2. 学习率变化趋势
  3. 训练时间
  4. 数据读取时间
  5. mean IoU变化趋势(当打开了do_eval开关后生效)
  6. mean pixel Accuracy变化趋势(当打开了do_eval开关后生效)

使用如下命令启动VisualDL查看日志

# 下述命令会在127.0.0.1上启动一个服务,支持通过前端web页面查看,可以通过--host这个参数指定实际ip地址
visualdl --logdir output/

在浏览器输入提示的网址,效果如下:

如图所示,打开 http://127.0.0.1:8040/ 页面,效果如下:

5.模型测试评估

训练完成后,用户可以使用评估脚本val.py来评估模型效果。

假设训练过程中迭代次数(iters)为1000,保存模型的间隔为500,即每迭代1000次数据集保存2次训练模型。

因此一共会产生2个定期保存的模型,加上保存的最佳模型best_model,一共有3个模型,可以通过model_path指定期望评估的模型文件。

python val.py \
       --config configs/quick_start/bisenet_optic_disc_512x512_1k.yml \
       --model_path output/iter_1000/model.pdparams

在图像分割领域中,评估模型质量主要是通过三个指标进行判断,准确率(acc)、平均交并比(Mean Intersection over Union,简称mIoU)、Kappa系数。

  • 准确率:指类别预测正确的像素占总像素的比例,准确率越高模型质量越好。
  • 平均交并比:对每个类别数据集单独进行推理计算,计算出的预测区域和实际区域交集除以预测区域和实际区域的并集,然后将所有类别得到的结果取平均。在本例中,正常情况下模型在验证集上的mIoU指标值会达到0.80以上,显示信息示例如下所示,第2行的mIoU=0.8609即为mIoU。
  • Kappa系数:一个用于一致性检验的指标,可以用于衡量分类的效果。Kappa系数越高模型质量越好。

随着评估脚本的运行,最终打印的评估日志如下。

76/76 [==============================] - 6s 84ms/step - batch_cost: 0.0835 - reader cost: 0.0029
2021-06-05 19:38:53 [INFO]      [EVAL] #Images: 76 mIoU: 0.8609 Acc: 0.9945 Kappa: 0.8393
2021-06-05 19:38:53 [INFO]      [EVAL] Class IoU:
[0.9945 0.7273]
2021-06-05 19:38:53 [INFO]      [EVAL] Class Acc:
[0.9961 0.8975]

可以看到,我改了参数后的训练效果还是不错的。

6.效果可视化

除了分析模型的IOU、ACC和Kappa指标之外,我们还可以查阅一些具体样本的切割样本效果,从Bad Case启发进一步优化的思路。

predict.py脚本是专门用来可视化预测案例的,命令格式如下所示

python predict.py \
       --config configs/quick_start/bisenet_optic_disc_512x512_1k.yml \
       --model_path output/iter_1000/model.pdparams \
       --image_path data/optic_disc_seg/JPEGImages/H0003.jpg \
       --save_dir output/result

运行完成后,打开 output/result 文件夹。我们选择1张图片进行查看,效果如下。

我们可以直观的看到模型的切割效果和原始标记之间的差别,从而产生一些优化的思路,比如是否切割的边界可以做规则化的处理等。

大家也可以尝试自己标注一个数据集进行图像分割,你只要按照 PaddleSeg\data\optic_disc_seg 里面那样组织图片结构,就可以复用这些训练、评估的过程。

本文部分内容摘自: PaddleSeg官方文档

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 将你的照片转化为“速写”

Photo-Sketching 一个能将照片的轮廓识别出来并将其转化为“速写”型图像的开源模块。

比如,这只小狗:

经过模型的转化,会变成卡通版的小狗:

当然,也不是什么照片都处理的好,比如这个风景画就不行:

摇身一变,成了抽象风格:

非常好,这很人工智能。

这个模块的使用也相对简单,下面给大家带上全方面的教程:

1.虚拟环境及依赖安装

这个项目推荐大家直接用Anaconda进行环境的构建和开发:Python数据分析与挖掘好帮手—Anaconda,因为作者提供了一个 environment.yml 文件,你只需要输入以下命令,就能一键安装环境和依赖:

conda env create -f environment.yml

此外,推荐大家用VSCode编辑器来编写像这样的小型Python项目:Python 编程的最好搭档—VSCode 详细指南

2.下载预训练模型

作者已经训练好了一些识别模型方便大家使用,可以在下列地址找到:
https://drive.google.com/file/d/1TQf-LyS8rRDDapdcTnEgWzYJllPgiXdj/view

作者使用的是谷歌硬盘,如果你无法科学上网,可以使用我提供的完整源代码+预训练模型,在后台回复:sketch 即可获取。

下载完成后解压文件,将 latest_net_D.pth 和 latest_net_G.pth 放置到 Checkpoints 文件夹下:

3.运行预训练模型

接下来,我们需要修改使用预训练模型的启动脚本,这些脚本都放在 PhotoSketch\scripts 下,我们需要使用的是 test_pretrained.cmd 或者 test_pretrained.sh 这两个脚本。

如果你是 windows 系统,请修改 test_pretrained.cmd 脚本,重点是dataDir、results_dir、checkpoints_dir:

dataDir 指向到 PhotoSketch 所在的文件夹目录,如果你是跟我一样这么配的,results_dir 只需要配成 %dataDir%\PhotoSketch\Results\ 即可,checkpoints_dir 则为 %dataDir%\PhotoSketch\Checkpoints\

如果你是macOS或者Linux,则修改 test_pretrained.sh 文件,修改方法与上面windows 的一样,只不过 反斜杠 “\” 要换成 斜杆 “/” 。

修改完脚本后,打开命令行/终端,输入以下命令,就会将你 PhotoSketch\examples 目录下的文件转化为“速写”。

windows:

scripts\test_pretrained.cmd

Linux/Macos:

./scripts/test_pretrained.sh

转化结果可以在 PhotoSketch\Results 中看到,如下两图所示。

待转化目录:

转化后:

可以看到效果其实不是非常好,由于是作者预训练的模型,所以效果不好也正常,如果大家需要的话,可以自己针对性地拿一些图像训练模型,并针对性地做识别,这样做效果才是最好的。

你需要训练或测试自己的模型也非常简单:

  • 在仓库的根目录中,运行 scripts/train.sh 可以训练模型
  • 在仓库的根目录中,运行 scripts/test.sh 可以测试val集或测试集

当然训练过程肯定没这么简单,你会遇到不少问题,但是我相信大部分都是存放图片的目录结构上的问题,大家如果有兴趣可以动手试试。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Easyocr — 3行代码识别图片中的任意语言文字

今天给大家介绍一个超级简单且强大的OCR文本识别工具:easyocr.

这个模块支持70多种语言的即用型OCR,包括中文,日文,韩文和泰文等。

下面是使用这个模块的实战教程。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install easyocr

它会安装除了模型文件之外的所有依赖,模型文件则会在运行代码的时候下载。

对于Windows,如果在安装Torch或Torchvision时报错了,请先按照https://pytorch.org的官方说明安装Torch和Torchvision 。

在pytorch网站上,请确保选择正确的CUDA版本。如果仅打算在CPU模式下运行,请选择CUDA = None。

2.实战教程

这个模块用起来真的非常简单,三行代码完事了:

import easyocr
reader = easyocr.Reader(['ch_sim','en'])
result = reader.readtext('test.png')

运行的过程中会安装所需要的模型文件,像下面这样:

不过它的下载速度非常慢,而且经常会失败,因此这里给出第二个解决方案:先下载好模型文件,再将其放置到所需要的位置:

如果下载速度太慢,请在Python实用宝典公众号后台回复:easyocr, 下载我上传到微云网盘的文字检测模型(CRAFT)和中文简体模型文件包。

下载完模型后,将文件放到下面这个位置。

Windows:C:\Users\用户名.EasyOCR\model
Linux:~/ .EasyOCR / model

重新执行脚本不会再提醒下载模型了:

import easyocr
reader = easyocr.Reader(['ch_sim'])
result = reader.readtext('test.png')
print(result)

我随便截了一个直播弹幕的图片保存在脚本所在的文件夹下,命名为test.png:

结果如下:

基本上所有应该识别的文字都识别出来了,效果非常不错。

另外也可以看到,输出采用列表格式,每个item分别表示对应文字的边界框,识别文本结果和置信度。

这个模块还能识别多语种的情况:

我将这张图片命名为test2.jpg,修改代码中对应的图片名称:

import easyocr
reader = easyocr.Reader(['ch_sim','en'])
result = reader.readtext('test2.jpg')
print(result)

效果如下:

这张图片很复杂,而且是中英文混杂在一起的情况,但是可以看到模型除了左上角的水印,图片中的文字基本都是识别出来了,尽管有部分文字识别错误,但还在可以接受的范围之内。

不过需要注意的是,虽然可以一次性识别许多种语言,但并非所有语言都可以一起用,通常是公共语言和一个特殊语种可以一起识别,相互兼容,比如英语和日语。

如果你的电脑没有GPU或者显存不足,可以加一个gpu=false的参数仅使用CPU运行:

reader = easyocr.Reader(['ch_sim','en'], gpu = False)

另外,这个模块还支持直接使用命令行运行,相当方便,大家可以试一试:

easyocr -l ch_sim en -f test.png --detail=1 --gpu=True

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

人类训练AI的方式存在根本性缺陷

机器学习模型在实验室能被调整到近乎完美,但在现实环境中进行试验时,往往都会失败,这已经不是什么秘密了。这通常被归结为人工智能接受训练和测试的数据与它在世界上遇到的数据不匹配,这个问题被称为数据偏移(data shift)。

例如,经过训练,人类能够制造在高质量的医学图像中发现疾病迹象的AI,却难以分辨繁忙的诊所中廉价相机捕捉到的模糊或裁剪的图像。

这种现象被称为“数据不规范”(underspecification)。在机器学习世界中普遍存在。

数十名谷歌研究人员着眼于一系列不同的人工智能应用,从图像识别到自然语言处理(NLP)再到疾病预测。他们发现,“数据不规范”是这些项目表现不佳的原因。问题在于机器学习模型的训练和测试方式,没有简单的解决办法。

1.同样的模型,但表现不同

如果你阅读过我们前面的推文,你也知道建立一个机器学习模型需要在大量的数据源上进行训练,然后再在一堆模型没见过的数据源上进行测试,当模型通过测试时,模型就完成了。

谷歌人员指出,这样的判断标准太低了。事实上,训练过程可以产生许多不同的模型,即使这些模型都通过了测试,但他们内部存在许多细微的差异。比如神经网络不同节点的系数不同、被选择出来的关键因子不同。都会造成模型之间存在不同的差异。

这些差异,在模型通过测试的时候都会被忽略掉。而事实告诉我们,在现实世界中,这些细微的差异会导致巨大的表现差异。

这也就意味着,我们根本无法区分同一个算法训练出来的模型之间,哪个更适合在现实世界中运行,哪些模型根本不适合在现实生活中运行。

这与数据偏移不同,在数据偏移中,因为数据源和现实世界的实际情况不匹配,训练不能产生一个好的模型。但是在数据不规范中,你能产生许多通过测试的模型,但这些模型里掺杂着“好的模型”和“坏的模型”,它们只能通过在现实生活中实际应用来区分好坏。

研究人员研究了数据不规范对许多不同应用程序的影响,每种情况下,它们使用相同的算法训练产生多个机器学习模型,然后进行压力测试,突出它们在性能上的具体差异。

例如,ImageNet是一个日常物体的图像数据集,他们基于ImageNet上训练了50种版本的图像识别模型。每组训练的唯一区别是开始时分配给神经网络的随机值。然而,尽管所有50个模型在测试中的得分都差不多,但它们在压力测试中的表现却大相径庭。

压力测试使用ImageNet-C,这些图像被马赛克化或改变了亮度和对比度。另一个压力测试集是ObjectNet,一个日常物品的异常姿势图像集,比如倒立的茶壶,挂在钩子上的t恤。

50位模型里,有些在被马赛克化的图片上识别得很好,有些则在异常姿势上识别得很好;有些模型的整体表现比其他模型好得多。但其实就训练过程而言,它们都是一样的。

研究人员使用三种医学AI系统根据视网膜扫描预测眼病,从皮肤病变中预测癌症,从患者病历中预测肾衰竭。每个系统都有同样的问题:

那些本应同样精确的模型,在用真实世界的数据(如不同的皮肤类型)测试时,出现了不同的表现。这并不是训练集/测试集不够导致的,而是不论训练集/测试集多大都会出现这样的问题。

因此研究人员罗勒说,我们可能需要重新考虑如何评估神经网络。“我们的基本假设中出现了一些重大漏洞。”

人类现在使用的大多数机器学习模型的训练逻辑,其实都无法被证明是有效的。

2.解决方案

一种选择是在训练和测试过程中设计一个额外的阶段,在这个阶段中可以同时生产多个模型,而不是只生产一个。然后,这些相互竞争的模型可以在具体的现实任务中再次进行测试,以选择最适合这项工作的模型。

但这样需要做很多工作。苏黎世联邦理工学院的机器学习研究员Yannic Kilcher说,对于像谷歌这样建造和部署大型模型的公司来说,这样做是值得的。谷歌可以提供50种不同版本的NLP模型,应用程序开发人员可以选择最适合他们的一个。

目前研究人员们还没有解决这个问题,但正在探索改进培训过程的方法。当人工智能在现实世界中表现不佳时,人们就不太愿意使用它了。因此,如果这个问题没有尽早得到有效的解决,这个时代的人工智能浪潮或许将就此平息。

译自technologyreview, 《The way we train AI is fundamentally flawed》。

我们的文章到此就结束啦,如果你喜欢今天的Python 文章,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 识别文本情感就这么简单

很多同学都对自然语言处理感兴趣,但是却不知道应该从哪里下手。Python实用宝典曾写过一篇文章(《短文本分类识别自杀倾向》),教你从构建数据集到训练数据,再到测试数据,整个流程确实需要耐心的人才能成功走通。

不过现在有了paddlehub,我们可以先省略掉构建数据集和训练数据这两个步骤,直接拿模型过来文本分类

一旦简单版的分类成功了,你就会有动力继续前进,继续学习如何训练属于自己的模型。

今天我们用paddlehub中比较简单的情感倾向分析模型 senta_lstm 来对文本做一个简单的积极和消极的分类。

1.准备

为了实现这个实验,Python是必不可少的,如果你还没有安装Python,建议阅读我们的这篇文章哦:超详细Python安装指南

然后,我们需要安装百度的paddlepaddle, 进入他们的官方网站就有详细的指引:
https://www.paddlepaddle.org.cn/install/quick

根据你自己的情况选择这些选项,最后一个CUDA版本,由于本实验不需要训练数据,也不需要太大的计算量,所以直接选择CPU版本即可。选择完毕,下方会出现安装指引,不得不说,Paddlepaddle这些方面做的还是比较贴心的(就是名字起的不好)

不过虽然它里面写了这么多,大部分人用一句话安装,打开CMD(Win+R)或者Terminal(Command+空格搜索)输入以下命令即可安装:

pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple

还需要安装paddlehub,这点别忘了:

pip install -i https://mirror.baidu.com/pypi/simple paddlehub

2.编写代码

整个步骤分为三步:

1.加载模型
2.指定待分类文本
3.情感分类

import paddlehub as hub

# 加载模型
senta = hub.Module(name="senta_lstm")

# 待分类文本
test_text = [
    "你长得真好看",
    "《黑色四叶草》是部不错的番"
]

# 情感分类
results = senta.sentiment_classify(data={"text": test_text})

# 得到结果
for result in results:
    print(result)

将这份代码保存为 code.py, (如果你懒得打一遍,可以再公众号后台回复 识别文本情感 获得代码)在CMD或Terminal中进入该文件文件夹运行以下命令执行脚本: python code.py

就能得到以下结果:

{‘text’: ‘你长得真好看’, ‘sentiment_label’: 1, ‘sentiment_key’: ‘positive’, ‘positive_probs’: 0.9866, ‘negative_probs’: 0.0134}
{‘text’: ‘《黑色四叶草》是部不错的番’, ‘sentiment_label’: 1, ‘sentiment_key’: ‘positive’, ‘positive_probs’: 0.9401, ‘negative_probs’: 0.0599}

其中:
1.sentiment_key 代表分类结果,postive是 积极 ,negative是 消极 。
2.sentiment_label 是分类结果标签,1代表 积极 ,0代表 消极 。
3. positive_probs 是积极分类的置信度,0.9866即模型判断98.66%的可能性是正面。
4. negative_probspositive_probs 相对,是消极分类的置信度。

3.结果分析

这么看,你会发现其实在有明显的积极消极词汇面前,这个模型的分类效果还是不错的。那在特殊的例子面前效果又如何呢?我们去微博随便取一条试一下,比如银教授的段子:

分类结果:

{‘text’: ‘他们都网上办公、网上学习了,你什么时候跟我网恋?’, ‘sentiment_label’: 0, ‘sentiment_key’: ‘negative’, ‘positive_probs’: 0.0507, ‘negative_probs’: 0.9493}

竟然意外的分对了。虽然是句段子,但是明显有对方不跟自己网恋的消极态度。再试试有潜在含义的句子:

{‘text’: ‘不想说什么了,听首歌吧。’, ‘sentiment_label’: 0, ‘sentiment_key’: ‘negative’, ‘positive_probs’: 0.0321, ‘negative_probs’: 0.9679}

{‘text’: ‘我忘了世界还有一种人火星人,你从那来的吧。’, ‘sentiment_label’: 1, ‘sentiment_key’: ‘positive’, ‘positive_probs’: 0.7261, ‘negative_probs’: 0.2739}

这里第一句分对了,第二句没分对。确实,第二句太隐晦了,机器可能分不出来。不过,置信度并不高,如果真的需要应用这个模型,可以通过置信度过滤掉一些分类。

总的而言,这个模型效果还是不错的,在网上那么多情感分类开源的模型中,百度的这个应该可以打80分左右。

而且,它支持你自己做一些微调(Fine-tune),也就是能够使用自定义的训练集调整模型到你需要的样子,详见github:
https://github.com/PaddlePaddle/models/tree/develop/PaddleNLP/sentiment_classification

我们的文章到此就结束啦,如果你希望我们今天的Python 教程,请持续关注我们,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们都会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Python 超简单实现人类面部情绪的识别

还记得我们之前写过一篇文章《手把手教你人脸识别自动开机》吗?里面用OpenCV对人脸进行简单的识别,让计算机训练认识到某个特定人物后识别对象。今天来做点高级的,识别出人脸的情绪

本文分为两大部分:

1.面部检测:检测图像的脸部位置,输出边界框的坐标

2.情绪检测:将面部的情绪分为高兴、生气、悲伤、中性、惊讶、厌恶、恐惧。

一、面部检测

可以使用上次文章( 《手把手教你人脸识别自动开机》 )中讲到的方法—用openCV检测,也可以使用face_recognition项目非常简单地实现面部检测。

这里我们尝试一下face_recognition项目, face_recognition 安装:

Face_recognition需要用到一个包叫dlib, 通过pip可能不一定装得上,因此这里推荐大家使用anaconda安装dlib:

conda install -c conda-forge dlib 

然后再安装Face_recognition:

pip install face_recognition

用face_recognition三句代码就能识别图像中的脸部:

import face_recognition
image = face_recognition.load_image_file("1.png")
face_locations = face_recognition.face_locations(image)

二、情绪检测

人类习惯从面部表情中吸收非言语暗示,那么计算机可以吗?答案是肯定的,但是需要训练它学会识别情绪。今天我们不太可能讲收集数据、构建CNN模型等逻辑流程。我们直接用priya-dwivedi训练好的模型,他们用Kaggle开源数据集(人脸情感识别 FER)训练了一个六层卷积神经网络模型。

现在就调用模型识别一下孙哥在这张图里的情绪吧:

import face_recognition
import numpy as np
import cv2
from keras.models import load_model
emotion_dict= {'生气': 0, '悲伤': 5, '中性': 4, '厌恶': 1, '惊讶': 6, '恐惧': 2, '高兴': 3}

image = face_recognition.load_image_file("1.png")
# 载入图像
face_locations = face_recognition.face_locations(image)
# 寻找脸部
top, right, bottom, left = face_locations[0]
# 将脸部框起来

face_image = image[top:bottom, left:right]
face_image = cv2.resize(face_image, (48,48))
face_image = cv2.cvtColor(face_image, cv2.COLOR_BGR2GRAY)
face_image = np.reshape(face_image, [1, face_image.shape[0], face_image.shape[1], 1])
# 调整到可以进入该模型输入的大小

model = load_model("./model_v6_23.hdf5")
# 载入模型

predicted_class = np.argmax(model.predict(face_image))
# 分类情绪
label_map = dict((v,k) for k,v in emotion_dict.items()) 
predicted_label = label_map[predicted_class]
# 根据情绪映射表输出情绪
print(predicted_label)

结果:

python emotion.py
高兴

从下面终端输出的结果我们可以看到孙哥现在是高兴的情绪,这个结果应该正确(毕竟孙哥还是表里如一的)。

虽然简单,但还是建议有兴趣的同学从头到尾做一遍试一下,过程中会遇到不少的坑,慢慢百度谷歌解决就好了。

文章到此就结束啦,如果你喜欢今天的Python 教程,请持续关注Python实用宝典,如果对你有帮助,麻烦在下面点一个赞/在看哦有任何问题都可以在下方留言区留言,我们会耐心解答的!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典