HZNU OS Lab

让机器学会经验

学习是什么?机器学习是什么?能做什么?

你或许可以跳过一部分

如果提起「经验」、「学习」和「问题」等相关概念,你能有直观的对它们之间联系的理解,并且能在普遍的角度看待这些概念,那么你可以直接进入机器能学习吗?

思考一些没那么技术的东西?

已经学了一年半计算机的你,终于在某一天晚上突然想到一个问题,「计算机是做什么的」。废话,计算机当然是用来计算的,然后很快就再发出惊世一问,「计算是做什么的」。我们似乎太专注于学习具体的知识,学习编程,学习组成原理学习操作系统;却很少思考这一切的目的是什么?

计算机、互联网的发展,直接的带来的是我们生活里的改变;我们让计算机帮我们规划今天出去玩的路线,用手机扫码支付,用万维网获取知识;用计算机帮我们计算复杂数学公式,模拟物理世界的变化。所以,它们是用来解决「问题」的?

一个「问题」

我们暂且不讨论问题为什么是问题,哲学化的讨论到此为止;所以说计算机是用来解决「问题」的;那人呢,我们经常把人机人机放在一起说,它们有关吗?是的,计算机模仿的正是人们解决问题的能力。

希望你还能记得自己刚入学的那一年,比如在 C 语言课上写的那个计算菲波那契数列的代码,你用编程语言表示了你的思维,表示了人脑中的算法,这个算法是你的智慧。你从和他人学习,自己研究推理试错等各种方式得到了你脑中的智慧

int fib(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1, c;
    while (--n) { // 某种智慧?
        c = a + b; // 某种经验?
        a = b;
        b = c;
    }
    return b;
}

但是你其实并不只是学会了菲波那契数列的写法,通过实践之后,你有了更多的编程经验,最终让你学会写更长的 C 语言代码,也让你最后能在去年数据结构的考试里写出「桶排序」的作答。你发现,人其实还是比机器聪明

印象 · 学习(Learning)

我们学习事物的时候并不是死板线性的,通过学习的过程,我们获得了比学到的更多的进步!

对于死板的机器而言,算数列就是算数列,排序就是排序,有 100 个问题在机器上就有 100 套代码。在机器的眼里,这个世界上有很多的「问题」,但在人类的脑中则没有这么多。你学会写 100 个具体的算法可能只是因为掌握10 个基本的思想,其实你并没有也用不着记住 100 个算法。

我们可以一直抽象下去,实际上你的脑子里从大的角度来看只有 1 个「问题」,因为人们学习认知世界的方法论是统一的,只需要非常少量经验就可以解决大量的「问题」。

印象 · 经验(Experience)

经验是人们脑子一种抽象的,模糊的概念,但似乎具有神力,可以解决比机器多得多的问题。我们所说的经验应该是一种解决问题的方法

全部的「世界」

人们用很少经验认知了广阔的「世界」。而在密集计算上远超人类的机器,有着人类几亿倍的计算能力,却只能做到人们所有能力很少的一部分。

如果,我们能够让机器也有这样的经验,它能否解决更广泛的「问题」?纵使它可能不会强到用一个经验解决「世界」里的全部疑问,能否让它们一次能回答的「问题」更多一些?

印象 · 泛化(Generalization)

泛化是一种能力,一种只需要一定相关经验就可以解决自己没怎么见过问题的能力!

机器能学习吗?

希望刚才的讨论没有将你绕晕,即使你没有即刻理解,也希望你可以在闲暇的时候思考一下这样的问题!实际上,这就是「机器学习(Machine Learning)」的本质由来,机器学习是受到人类学习过程启发的一个技术(学科)。

放宽心

对于刚才的讨论,你只需要对「问题」、「学习」和「经验」有初步的印象就可以,我们会逐渐展开讨论它们具体是什么。如果你忘记了,就想想你是怎么学习的吧。

传统编程的局限

让机器分清楚狗和猫并不容易

在传统编程领域,如果要判断两个东西的区别,我们可能只能用一大堆的控制流(if else 等)来实现,设想一下,如果你要让一个传统算法从一张图片的像素点(计算机只能这么看)里判断它是狗还是猫,你要做什么?

哎你可能会说,先看一下尾巴,再看一下皮肤材质,再看一下......,诶等等,要怎么找到尾巴在哪里,以及,我要怎么用代码表示皮肤材质。看来问题没有我们想的那么容易,哪怕我们真的可以做到,这些指标之间的关系未免有点太复杂了!

Cat And Dog Traditional

还是让机器自己想办法吧

所以,既然人们可以从和环境(其他人、发生的事物等)的交互中逐渐学习到某种经验,很自然的,我们会想,如果我们给机器一样的环境,它们能否自然的从中发现一些经验,或者说是规律

小时候上幼儿园的时候老师教我们如何分辨猫和狗,他们可能会指着动物跟我们说它们叫什么名字。我们抽象一下,如果我们能够告诉机器这个什么正确答案,它们能不能从中用某种方法学会如何判断呢?

人类的学习环境其实就是「海量的数据」,你看了成百上千次,慢慢心里有数了,这时候的环境就是无数张带答案的图片。我们给机器的环境其实是一大堆成对的例子:输入(一张的照片) + 期望输出(这个标签)。

如果这样的机器学习可以完成,那我们就不用写那些复杂的控制流了!我们已经谈论了不少跟学习相关的内容,虽然我们知道经验难以表示,但我们学习的过程似乎,并不那么难以描述,既然如此,机器学习应该具有充足的机会。

机器学习的三个基本概念

我们其实已经谈论了 ML 的第一个基本概念,「数据(Data)」,现在对其性质进行一个简要总结。

性质 · 数据

  • 数据是机器学习的燃料合适的、高质量的数据可以让机器有内容可学
  • 数据需要标注(标签或 监督,见后文),用于告诉机器什么是对的
  • 关于合适:对于不同的需求(任务),数据的类型和结构需要能对学习有效

有了「环境」之后,接下来应该不难想到,「经验」怎么?放在

关于学到的东西放在哪,人类学习的结果表示在神经元的连接的建立和改变,机器学习同样也需要用一个结构来表示学习到的经验,它需要是一个可以被调整的结构,一开始是空白(可以理解为比较笨的状态),它在看了数据之后需要慢慢改变自己。

这个可以调整的结构就是我们常说的「模型(Model)」,这里的可以调节的结构我们一般称为,「可学习参数(Learnable Parameters)」。

想象模型是一台有很多旋钮的机器,每个旋钮代表一个经验值。一开始所有旋钮都是的(随机初始化),训练过程就是不断调整这些旋钮,让机器在看到输入配对的正确答案后,自动把旋钮调到一个更好的位置。

接下来,我们还需要解决机器如何学习的问题,根据我们的讨论,模型学习到一组效果更好的参数,需要有「某种方法」来改变它现在的可学习参数,这类方法就是机器学习的最后一个基本要素,「学习算法(Learning Algorithm)」。

不过我们还要一些细节问题:

  • 如何衡量现在的模型的?
  • 如果模型不够好,用什么方法进行调整?

我们可以先(随机初始化)一个答案,如何判断一下它和正确答案(之前提到的标注)之间的距离。所以我们需要用「某种方法」来计算当前模型的表现目标之间的距离,这类方法我们一般称为「损失函数(Loss Function)」。

我们知道距离之后,就需要根据这个距离判断我们需要如何调整模型参数,同样的,使用「某种方法」决定我们要调整哪些参数,调整多少量,这个方法我们一般称为「优化器(Optimizer)」;调整量的尺度称为「学习率(Learning Rate)」,尺度越大,一次调整的量的就越多。

总结 · 机器学习的核心框架

我们已经把机器学习的三大基本概念和背后的两大机制完整串联起来了:

  • 数据(Data):机器学习的燃料学习环境。它由大量「输入图片 + 正确答案(标注)」组成,相当于小时候老师指着动物教你这是猫、那是狗的过程。
  • 模型(Model):机器用来存放经验可调整结构。它一开始几乎是空白(没有意义)的,里面装满了可学习参数,通过不断调整这些参数,模型就把经验一点点记了下来。
  • 学习算法(Learning Algorithm):机器真正学会的方法。它负责指挥整个学习过程,让模型从数据中提炼规律,形成经验

而学习算法主要通过下面两个重要工具来工作:

  • 损失函数(Loss Function):一个打分器,它负责量化当前模型的输出和正确答案之间的差距(差距越大,说明模型越)。
  • 优化器(Optimizer):根据损失函数算出的差距,决定如何调整模型的参数——该往哪个方向改、每次改多少量。

三大要素 + 两大机制形成了一个完整的闭环数据喂给模型损失函数计算误差 → 优化器调整参数 → 模型经验越来越丰富 → 最终实现我们最想要的泛化能力。

这就是机器学习最核心的运作逻辑,包括后面所有的神经网络的训练过程,都建立在这个框架之上。用伪代码表示如下:

for Image in Data:
    Prediction = Model.predict(Image)
    Diff = Loss(Prediction, CorrectAnswer)
    Optimizer(Model.parameters, Diff) # 使用优化器更新模型参数 

常见的 ML 学习模式

等等!你可能在一些书上看过这个,但是我并不准备告诉你监督无监督自监督还是什么强化学习定义和性质,这么做的话这部分内容就会和网上能找到的很多教材一样变得非常的死板而且你们还会怀疑文章是用 AI 生成的虽然使用的插图确实是 AI 生成的

Do Not Struggle In Learning Pattern

三种学习模式的主要区别只有一个,「损失函数(Loss Function)」是用什么算的。损失函数的计算和我们的目的(以及勤奋程度)息息相关,和我们的任务息息相关。

我们已经差不多讨论过的,最直接、最无脑的办法肯定是人类自己给每一个想让机器学的数据都配一个对应的标准答案,模型输出一个结果之后,直接用「某个 Loss 函数」计算它和标准答案的差距。使用了这种模式的机器学习叫做「监督学习」。

印象 · 监督学习

Loss 来自外部提供的明确正确答案。我们这次图像修复实验采用的正是这一类——输入是带噪并过度压缩的图片,正确答案就是干净的原图,Loss 衡量它们的差距,让模型一步步获得把图像变清晰的经验

但如果我们根本没法,或者懒得给每张图片都人工打标签呢?我们都学过概率与统计,至少我们知道可以从数据里算出一些指标,比如我们可以有一个指标来衡量,假设我输入 1000 个数据,然后衡量这 1000 个数据是否有几个大类

是的,我们可以使用这些指标作为我们的「Loss 函数」,现有的例如 K 平均演算法(K-means),使用这样的 Loss 函数可以让模型学会如何把你的输入进行(簇)分类。

印象 · 无监督学习

Loss 来自数据自身的数学/统计关系(比如聚类内距离最小、聚类间距离最大等)。没有外部监督,也没有人为制造的标签,机器要完全靠自己发现什么才算合理,它让海量无标注数据也能变成经验

延伸 · 自监督学习

我们还可以换一种玩法:让数据自己监督自己。比如让模型把图片压缩成很小的特征,再试着把它还原回去;或者把图片的一部分遮住,让模型自己预测遮住的部分应该长什么样。这时 Loss 不再依赖外部标签,而是来自数据自身的内在结构或统计关系——模型重建(恢复)得越像原来,就说明它越理解了这张图的本质

甚至我们可以在模型中从数据里自动制造伪任务和伪标签,比如让你的模型包含对数据的压缩(如下采样)或损坏步骤,然后在模型后续步骤尝试恢复它,用 Loss 看看它恢复的怎么样,让模型自己跟自己玩。这种介于监督和无监督之间的模糊地带,我们常把它叫做自监督学习

广义地说,当下最火热的 LLM 使用的 Transformer 架构的预训练,就是自监督学习,它通过在模型掩盖后文,让模型前文推断下一个词(Next-Token),自监督学习使得它可以自主学会极其海量的数据,而不需要人们手动教它怎么做。

再极端一点,如果我们连静态数据都不想给,只想让机器在动态的环境里自己尝试呢?模型每做一个动作,环境就会给它一个奖励或者惩罚。「Loss 函数」的目标是让长期累积的奖励最大化,我们把这种模式叫做强化学习

印象 · 强化学习

Loss 来自环境的奖励和惩罚信号,模型不是在预测正确答案,而是在学习什么行为长期来看更有利。它最接近我们人类真正通过试错获得经验的过程,但也最难训练

知名围棋人工智能「Alpha Go」和近年来的类人型机器人就是用强化学习模式训练的。

无论哪一种方式,底层的逻辑其实统一的;数据提供环境模型存放经验Loss 给出反馈信号,优化器负责调整参数。不同学习模式的主要区别只有「Loss 从哪里来」。

再谈「数据」和「拟合」

两个「数据集」

假设你现在提前知道了操作系统期末考的题目和答案,你只需要学习它们,你就能在期末考中取得几乎满分然后美美结课,但实际上,你也知道这样的话你确实基本不会,因为换一张卷子,你就不知道要写什么才是对的了。

同样的,模型也是这样,如果你把带答案的图片全部塞给它,让它一边又一遍地看这一堆图片,有没有一种可能,它实际上只是把答案背下来了?你就像只能看到结果的老师一样,你不知道它到底是真的会还是不会。所以你决定给它拿一张新的考卷,是它没有学过的内容,如果它还是考的很好,那它就的确学到了一些可以泛化的经验。

还记得「泛化」吗?

回忆 · 泛化(Generalization)

泛化是一种能力,一种只需要一定相关经验就可以解决自己没怎么见过问题的能力!

我们当然希望我们的模型有好的泛化能力,所以我们得给它多准备一些考试,这样我们才能做出判断

所以我们很自然的觉得,我们至少得有两份试卷;我们称用于训练数据集为「训练集(Train Set)」,而反作弊的,用于实际测试数据集称为「测试集(Test Set)」。测试集可以反映模型泛化能力。

什么是拟合?

拟合」这个词听起来有点学术,但其实很好理解——它说的就是模型数据贴合的程度。

模型在训练的时候,会不断调整自己的参数,试图让自己在训练集上的表现越来越好。我们把这个努力去贴合训练数据的过程就叫做「拟合」。

但拟合这件事,和我们人类学习一样,是有的。

如果模型拟合得太差,连训练集里带答案的数据学不好Loss 一直降不下去,我们就说它「欠拟合」了,它没有抓住数据里的关键规律。

更常见、更麻烦的情况是「过拟合」。模型在训练集上表现得异常优秀,几乎把数据里的全部细节甚至噪声都记下来了,训练 Loss 非常低。但当你拿出一个它从未见过的新数据时,它的效果却变得很差

那我们怎么知道模型到底是过拟合了,还是欠拟合了?只看训练集的表现是看不出来的,因为它永远只会讨好自己已经见过的数据

特殊数据集:验证集

验证集不是理论上本来就该有的东西,而是一个实用的工程工具,它更像训练过程中定期给模型做的模拟考。我们在训练过程中,每隔几轮(实验代码里是 1 轮),就拿验证集测一下模型当前的真实水平:训练集上的 Loss 还在继续下降,但验证集上的表现已经开始变差?这可能意味着「过拟合」正在发生。

验证集的核心价值在于平衡训练时间、发现早期问题、停止无意义训练,帮我们及时决定什么时候早停(Early Stopping),避免把算力(很多的钱)浪费在表现差的模型上,也方便我们调整模型结构优化器参数(如学习率)等。

边界

验证集测试集有严格的边界。验证集可以在训练调参阶段反复查看、用来监控优化;而测试集必须在整个训练全部结束之后才能使用一次。它是我们对模型泛化能力的最终、也是最公正的检验,绝对不能在训练调参阶段使用测试集的数据。

数据划分的本质,都是为了让我们更规范地判断模型到底学到了经验,还是仅仅记下了答案,这最终指向的,始终是我们最开始反复强调的那个词——「泛化」。