前一篇:《全面解释人工智能LLM模型的真实工作原理(一)》
序言: 在上一篇文章中,我们从原理上构建了一个识别"叶子"和"花朵"的神经网络,并详细讲解了它的工作过程。这包括对输入数字逐个与权重相乘后求和,加上偏置值,最后通过非线性处理和统计分布计算来得出输出。这些操作使用了简单的数学运算(乘法、加法和非线性处理)。本节的重点是解答神经网络的权重和偏置值是如何得到的以及最关键的概念:如何让神经网络输出chatGPT一样的句子。为了让神经网络学到合适的权重和偏置,我们需要提供大量的学习数据(如大量的"叶子"和"花朵"图片),让网络在学习过程中调整每个神经元的权重和偏置值,最终实现正确分类。(请动一下您的小手,订阅作者!)
如何训练这个神经网络(模型)?
在上例中,我们为了测试,给模型预设了合适的权重和偏置,这样才能得到准确的输出。但在实际应用中,权重和偏置值是如何获得的呢?获得合适的'权重'和'偏置'这个过程就称为"训练模型"或"训练神经网络",也可以理解为"人工智能的自我学习";没错,这个过程就是"训练AI"。人类需要做的就是为模型提供优质数据来进行训练。
假设我们收集了一些数据,包括各种类型的"叶子"和"花朵"。然后,我们用工具将它们的颜色和体积转换成数字,给每个数据样本贴上"叶子"或"花朵"的标签(给数据取名字就称为"标注数据"),最终这些数据组成了我们的"训练数据集"。
训练神经网络的工作原理如下:
- 初始化权重
首先,从随机数开始,将神经元的每个参数/权重设为一个随机数。(启动训练程序时,计算机内存中未初始化的都是随机数,一般无须特别设定)
- 输入数据并获得初始输出
我们给神经网络输入"叶子"的数据表示(如 R=32,G=107,B=56,Vol=11.2),期望输出层第一个神经元的值大于第二个神经元的值,表示识别出"叶子"。假如预期"叶子"神经元的值是0.8,代表"花"的神经元值是0.2。
- 计算损失
因为初始权重是随机的,实际输出往往和预期有差异。比如,两个神经元的初始输出分别是0.6和0.4。我们可以通过求差并将差值平方相加计算损失:(0.8 - 0.6)² + (0.2 - 0.4)² = 0.04 + 0.04 = 0.08。理想情况下,我们希望损失接近于零,也就是"最小化损失"。
- 计算梯度并更新权重
计算每个权重对损失的影响(称为梯度),看向哪个方向调整才能减少损失。梯度指示了每个参数的变化方向------权重会朝损失减少的方向略微调整一点。这个过程称为"梯度下降"。
- 重复迭代
持续重复这些步骤,通过不断更新权重,使得损失逐步减少,最终得到一组"训练好的"权重或参数。这就是神经网络的训练过程,称为"梯度下降"。
补充说明
• 多个训练样本
训练中通常会使用多个样本。微调权重以最小化某个样本的损失可能会导致其他样本的损失增大。为了解决这个问题,通常会计算所有样本的平均损失,并基于平均损失的梯度来更新权重。每次完整的样本循环称为"一个 epoch",多个 epoch 的训练可以帮助逐步找到更优的权重。
• 自动计算梯度
实际上,无需手动微调权重来计算梯度,数学公式可以直接推导出每个参数的最佳调整方向。例如,如果上一步权重为 0.17,且神经元的输出希望增大,那么将权重调整为 0.18 可能更有效。
在实践中,训练深度网络是一个复杂的过程,训练中可能会遇到梯度失控的情况,例如梯度值趋于零或趋向无穷大,这分别称为"梯度消失"和"梯度爆炸"问题。虽然上述的损失定义有效,但在实际应用中,通常会使用更适合特定任务的损失函数来提高训练效果。
这些原理怎样帮助神经网络生成语言?
请记住,神经网络只能接收输入一组数字,基于训练好的参数进行数学运算,最后输出另一组数字。关键在于如何解释这些数字,并通过训练来自动调整参数。如果我们能够把两组数字解释为"叶子/花朵"或"一小时后是晴天或雨天",同样也可以将它们解释为"句子的下一个字符"。
但是,英语字母远不止两个,所以我们需要将输出层的神经元数量扩展,例如扩展到26个以上的神经元(再加上一些符号,如空格、句号等)。每个神经元对应一个字母或符号,然后我们在输出层中找出数值最大的神经元,并将其对应的字符作为输出字符。现在我们就有了一个可以接收输入并输出字符的网络。
如果我们给神经网络输入"Humpty Dumpt"这个字符串,然后让它输出一个字符,并将其解释为"网络预测到的下一个字符",我们可以通过训练,确保网络在收到这样的字符串"Humpty Dumpt"输入时输出字母"y",从而达到我们想要的结果"Humpty Dumpty"。
不过,这里有一个问题:如何将字符串输入到网络中?毕竟,神经网络只接受数字!通常实践中我们可以通过"one-hot编码"或其他编码方法将字符串转换成数值数组,使其可以被神经网络理解和处理。
这里我们用一个最简单的解决方案来编码:直接为每个字符分配一个数字。例如,a=1,b=2,依此类推。现在我们可以输入"humpty dumpt"并训练网络输出"y"。网络的工作过程如下:
先在神经网络的输入层输入一串句子(字符串),它将会在输出层预测下一个字符。这样的方法可以帮助我们构建完整的句子。例如,当我们预测出"y"后,可以将这个"y"添加到前面输入的字符串尾部,并再次送回神经网络的输入层,让它预测下一个字符。如果训练得当,网络会预测出一个空格;如此循环下去,最终生成出完整的句子:"Humpty Dumpty sat on a wall"。这样,我们就得到了一个生成式 AI(人工智能语言模型),神经网络现在可以生成人类的自然语言了!
当然,在真实应用中例如chatGPT,我们不会使用这种简单的字符编号方法。在后文中,我们会介绍一种更合理的编码方式。如果你迫不及待,可以查看附录中的"编码"部分。
细心的读者可能会注意到,我们无法直接输入"Humpty Dumpty",因为如图所示,输入层只有12个神经元,对应于"humpty dumpt"中的每个字符(包括空格),并没有多余的神经元留给字母'y'输入了。那么,如何在下一步中加入"y"呢?如果在输入层加上第13个神经元,就需要重新调整整个网络,这显然不太现实。解决方案很简单:我们可以将最早的字符"h"剔除,保留最近的12个字符输入。例如,我们输入"umpty dumpty",网络会预测出一个空格;然后我们输入"mpty dumpty ",网络会输出"s",如此循环下去,过程如下所示:
这种方法有个问题,即当我们输入" sat on the wal"时,会丢失之前的许多信息。那么,现代顶尖神经网络是如何解决的呢?原理基本相似。神经网络的输入的长度是固定的(取决于输入层的大小),这种长度称为"上下文长度",即网络用来预测后续内容的参考范围。现代网络的上下文长度可以很长(通常达到几万甚至几十万个字符。例如,ChatGPT的4o模型支持12.8万个字符,Claude则支持25.6万个字符。这意味着它们在输入层中使用了超过10万个神经元来接收用户的输入。试想一下,上千亿参数意味着有多少神经元在参与运算?),这对提升效果非常有帮助。尽管某些方法允许输入无限长度的序列,但固定上下文长度较大的模型在性能上已经优于这些方法。
细心的读者可能还会注意到,我们在输入和输出端对同一个字母的解释方式不同!例如,输入"h"时我们用数字8表示它,但在输出层,我们并不直接要求模型输出数字8来代表"h",而是生成26个数值,并选择其中最大值对应的字母作为输出。如果第8个数值最大,我们将其解释为"h"。为什么不在两端使用相同的表示方式呢?事实上,这是为了构建更有效的模型------不同的输入和输出解释方式为模型性能的提升提供了更多可能。实践表明,这种不同的表示方式对语言生成更有效。实际上,我们在输入端的数字表示方式也并非最佳,稍后会介绍更优的方法。
本节是搞明白chatGPT输出人类自然语言句子的核心原理,希望感兴趣的朋友如果没有搞明白,多读几篇或者在评论区留言与作者交流,我会毫无遗漏的回答所有的评论。
未完待续...