第 3 天 多层神经网络¶
再相逢!非常非常高兴在第三章和你再相逢。
其实昨天我们已经发现点苗头 —— 线性在很多关系里并不是很成立。虽然说我们也能通过数据预处理等方式将更复杂的关系退化到线性关系,但这样我们就又退化到「解」题而非「猜」题了。
我们如何能留在我们「猜」题的舒适区呢?这就要求我们的模型拥有更强的表达力,以解放我们的手工活。
一个很自然的想法,把单层的网络拓展到多层。
但是需要注意的是,如果我们每一层的网络都是线性的,那么无论我们增加到多少层,它的效果都只等价于一层。
光这么说可能不是太直观,我们举个例子来说明。就比方说我们把以下 2 个线性层 A 和 B 连接在一起。
式 3-1 线性层 A 和 线性层 B
在神经网络里所谓「连在一起」,其实就是先算 A,然后把 A 的输出结果作为 B 的输入算 B。
我们把 A 和 B 连在一起,会发现。
式 3-2 线性层 A 和 线性层 B 的连接可以化简成一个单一的线性层 C
两个线性层无损合并成了一层,根本不需要算两次,一把就能算出来。咱们白加了。
两个能变一个,三个也能变一个。按照这种玩法,无论堆多少线性层都是没用的,都是一把出。除了多费点电,这种加法看来是帮助不了大模型增进表达力的。
既然 $ \text{线性层} \overset{\text{layer}}{\circ} \text{线性层} = \text{新线性层} $,很自然的想法,我们穿插非线性层进来试试呢?
式 3-3 往线性层之间插入非线性层
3.1 非线性层带来的改变¶
让我们先尝试这样一个最简单的非线性函数 R。
式 3-4 一个最简单的非线性函数 R
原值大于 0 则不变,小于等于 0 则变为 0。我们把它穿插进 A 和 B 之间,会怎样呢?
式 3-5 A + R + B = ?
显然我们不能直接算 B 了,因为现在 B 的输入会根据 A 的输出是正是负而不同。现在我们只能分 A 的结果是正是负来分情况讨论。我们尝试化简它。
式 3-6 化简 A + R + B 的过程
最终我们得到了等价 A + R + B 的新的函数 C。
式 3-7 等价于 A + R + B 的新函数 C
显然,这次的叠加是有效果的,无法一次算尽了。
这,意味着什么呢?
3.1.1 函数视角¶
视角一,我们可以把它用编程语言表达出来。
相比于没有插入 R 的化简结果。
很明显的,有了 R 了以后,我们多出了一个 if ,以及对应的 x > 5。
有了分支结构的程序显然是更强的程序。它变成了一段更复杂的、能应对更多场景的代码。
而且,切莫忘记了,这个 if 和 x > 5 并不是我们硬编码到这个程序中的,它们都只是我们大模型中的参数。
也就是说,这段程序,是不用我们手写的。它根据我们不同情况的数据,会被「训练」出来。
想象一下,只要我们继续增加更多的层,这个程序就会越来越复杂,表达力越来越强,直至赶上我们手写的代码。
理论上,我们只用写一段「元代码」,就可以为各种不同的场景「训练」出各种适合的代码来。
甚至,只要层足够多,给它足够的if else,它是不是都有可能写出这样的人类不会去构思的,通杀全球的「大程序」呢?
def universal_program(task):
if task == "财务软件":
if module == "报销": ...
elif module == "年报": ...
else: ...
elif task == "社交媒体":
if action == "发帖": ...
elif action == "刷视频": ...
else: ...
elif task == "操作系统":
if event == "关机": ...
elif event == "蓝屏": ...
else: ...
elif task == "AI助手":
if query == "写代码": ...
elif query == "生图": ...
else: ...
# ... 游戏、医疗、航天、恋爱模拟器 ...
反正对它来说,只是无穷无尽的 if else。「梯度下降」而已。
这个,这像不像昨天我们提到的 Yann LeCun 所谓「可微编程」?用「可微」来编程。
3.1.2 曲线拟合视角¶
另一个视角,我们画出函数 C 的曲线图。
图 3-1 加入了非线性函数的曲线
我们把加入 R 之前的曲线图也画出来。
图 3-2 没有加入非线性函数的曲线
一个很明显的区别,加入非线性函数后的曲线会打弯。
非线性函数的加入使得我们在画一个曲线的时候,拥有画折线的能力。我们弯折的点和角度,都是大模型里的参数。
如果我们能使得一个曲线在任意的点弯折任意的角度,这意味着什么?
这意味着我们拥有了拟合任意曲线的能力。
根据数据集拟合任意曲线,这种能力又意味着什么呢?
浪费几分钟,我们聊聊因果……
我知道佛学里讲因果合一,也听说量子物理里有什么逆向因果,但我们先放下这些不谈。我这样的凡人认知世界的方式,大多还是更认同这世界是因在果前的,对不?
我们观察物理现象,总结验证物理学规律。
我们采集经济数据,尝试分析经济学原理。
我们在世界的各个领域,尝试寻找一个又一个的「规律」去解释已观测的 samples,并期待这个规律能完美预测未来新的数据。
咦?从采样点拟合曲线?这像不像大模型正在做的事?
再想想我们认知世界的这个逐步的过程,这个由地平说到地心说到日心说再到宇宙大爆炸的过程。这一步一步的前进,用大模型的语境来说,是不是就是加入了新的、原有规律无法解释的点,然后重新拟合,找到了新的规律、新的曲线呢?
从采样点拟合曲线,从经验研究出理论。如果这两件事在某种维度上相通,那么,拟合任意曲线的能力是不是意味着我们可以接近任意一种真相了呢?
如果数据足够多、足够真、足够精确。
3.2 激活函数¶
总之,不管哪种视角,这个非线性层,都大大提升了我们大模型的能力。这种提升,本质是由它来回答了一个问题:前方的函数是否需要被激活?
这类非线性层,我们有时也叫它「激活函数」。
上一节我们使用的那个激活函数,人们也叫它 ReLU,它是激活函数中最简单的一个。ReLU 像个电灯开关一样,只控制开启或关闭。我们也有一些更复杂的函数,不单单是个开关,而更像个调节亮度的旋钮,可以控制前方参数的「通过量」。就像下面这俩。
图 3-3 sigmoid 激活函数
图 3-4 tanh 函数
我们把脑子里 ReLU 的样子也倒出来。
图 3-5 ReLU 函数
比较一下,ReLU 这种“开关函数” 的图像是不是比其它两个 “旋钮函数” 要简单好多?我们会不会觉得 sigmoid 和 tanh 更猛些?
你觉得它们 3 个,谁作为激活函数的效果更好?
结果或许让你有些意外,ReLU 在大模型中的表现远强于上面那两个复杂的曲线函数。目前现代大模型中使用的激活函数大都和 ReLU 长得更像。
图 3-6 一些常用的激活函数
这是为什么呢?它是被应用之后才发现效果奇好的,所以对它为什么好的研究是后置的。当然,有不少论文讨论其背后的原因,也正是这些讨论触发了人们去寻找更好的激活函数。
我们简单的这么理解:想象一下,如果我们注定无法抵达终点,只是无限接近终点的话。用折线去拟合曲线,比用曲线去拟合曲线,要简单得多。
图 3-7 割圆术 刘徽 三国
看看这个用折线去逼近圆的例子。想象一下,如果我们是用无数的曲线去逼近,那得死多少脑细胞。而且,可以想像,最终拟合的结果也歪七扭八,并不会很好看。
两点题外话。
- ReLU 的“过于简单”也附赠了 2 个缺点。一个是它的负数曲线也太过平坦,另一个是它在 0 点处不可导。大家可以去这个链接 https://ai.plainenglish.io/beyond-the-curve-a-holistic-guide-to-activation-functions-from-sigmoid-to-swiglu-3e96faaa7a84 看进一步的讨论。
- 上图中激活函数 Swish 不是人设计的,是 Google 通过神经网络搞出来的。曾经,不愿意学习 AI 生成代码的我心想:也只能写点 js 罢了,什么时候那帮写 AI 的人也用 AI 生成代码了我再学学看罢。然后,听到这个消息的我就像晴天被霹雳 —— 人家已经用到这个程度了吗…… 肯定追不上了。还是不学了罢……
3.3 更复杂的问题¶
嘴上吹得那么神,真行不行咱还得手上见真章。提出问题!
回顾昨天的问题,咱们至少还是知道有几个特征是指向最终答案的。那么,有没有一类问题,我们只有原始的数据,根本无从提取特征点呢?
3.3.1 MNIST 数据集¶
有的,图像识别就是这样的问题。
图 3-8 MNIST 数据集中的数字 3
上面这张图是 MNIST 数据集中的数字 3。但是你说这幅图中,具体哪个像素决定了它是 3,而不是别的数字呢?恐怕很难讲。
即便确有这样的特征点,那我们也不想一个一个像素的看过去把它找出来。
MNIST 是 Modified NIST 的简写,NIST 是美国国家标准委员会的缩写。这个委员会在上世纪 80 年代末曾致力于解决美国邮政编码难以分拣的问题,并收集了大量手写数字的黑白扫描图像。Yann LeCun 在这个数据集的基础上清洗、缩放、平移居中、重分布、格式化、标记整理并开放了 MNIST 数据集。借深度神经网络之力,他最终解决了这个问题并在全美邮政服务系统大规模商用。这是全球首个成功商用的深度神经网络系统,比 2012 的 ImageNet 竞赛早了近 20 年。这也成为 Yann LeCun 终获图灵奖的一级重要阶梯。
本着到此一游的文化传统,马年咱唯马首是瞻,马年行好运,今儿个咱也一起踏踏这级台阶。
3.3.2 从 ModelScope 下载¶
ModelScope 是阿里巴巴贡献给大家的下载模型和数据集的网站,咱就从它这里下载 MNIST,大小大概 18 兆。
下回来发现是 parquet 格式的,这是 Twitter 贡献给 Apache 的一种列式存储格式。它有 2 个 parquet 文件,一个train-00000-of-00001.parquet,一个test-00000-of-00001.parquet。咱先把 train-00000-of-00001.parquet 给解析开。
3.3.3 装载数据集¶
先安装点小依赖,如果你使用的是云 GPU,它们很大可能是预装好的。
我们从数据集里导出几张图来,存成 PNG 看看长啥样。
import pandas as pd
# 注意:这里的路径可能需要修改成你自己的路径
df = pd.read_parquet('./mnist_data/mnist/train-00000-of-00001.parquet')
for i in range(5):
# 这里因为是列式数据库,所以我们先取列
img_bytes = df['image'].iloc[i]['bytes'] # 图像 bytes
label = df['label'].iloc[i] # 答案
# 我们就以它的答案做文件名,把它存出来看看
filename = f"{label}.png"
with open(filename, 'wb') as f:
f.write(img_bytes)
print(f"已保存: {filename}")
# 输出:
#
# 已保存: 5.png
# 已保存: 0.png
# 已保存: 4.png
# 已保存: 1.png
# 已保存: 9.png
或者我们也可以用稍复杂些,但更加通用的 Hugging Face 出品的 load_dataset() 来导出。load_dataset() 统一了多种数据集格式的加载,这个链接 https://huggingface.co/docs/datasets/main/en/package_reference/loading_methods 可以查看所有它支持的格式。
from datasets import load_dataset
# 注意:这里的路径可能需要修改成你自己的路径
# 注意:split="train" 是必须的,因为 parquet 文件本身没有 split 信息,我们需要手动指定
dataset = load_dataset(
"parquet",
data_files="./mnist_data/mnist/train-00000-of-00001.parquet",
split="train"
)
print(f"数据集已加载,共 {len(dataset)} 条样本。")
print(f"列名: {dataset.column_names}")
for i in range(5):
# load_dataset() 在访问图像文件会自动解码。
# 如果我们还是先取列,它会自动把所有图像都解码一遍,会慢。
# 因此,首先查询样本索引然后再查询 "image" 列更聪明些,
# 即 dataset[0]["image"] 应该始终优先于 dataset["image"][0]
img_pil = dataset[i]["image"]
label = dataset[i]["label"]
# 我们还是以它的答案做文件名,把它存出来看看
filename = f"{label}.png"
img_pil.save(filename)
print(f"已保存: {filename}")
# 输出:
#
# 数据集已加载,共 60000 条样本。
# 列名: ['image', 'label']
# 已保存: 5.png
# 已保存: 0.png
# 已保存: 4.png
# 已保存: 1.png
# 已保存: 9.png
用 file 命令看看我们导出的文件。
(base) chengang@VM-0-80-ubuntu:/workspace/mnist# file 0.png
0.png: PNG image data, 28 x 28, 8-bit grayscale, non-interlaced
好。MNIST 训练集里的图是横 28 像素,竖 28 像素,8 位的灰度图,一共有 60000 张。答案都是阿拉伯数字。
我们做一个预处理,把每个像素点 0-255 的整型值映射到 0-1 的浮点值装到 PyTorch Tensor 里,方便后续 PyTorch 处理。
def preprocess(dataset):
processed_images = []
for img in dataset['image']:
pixel_data = list(img.getdata())
img_tensor = torch.tensor(pixel_data, dtype=torch.float32) # 将像素转换为 PyTorch Tensor
normalized_img = img_tensor / 255.0 # 将像素值从 [0, 255] 缩放到 [0, 1]
processed_images.append(normalized_img)
images = torch.stack(processed_images)
labels = torch.tensor(dataset['label'], dtype=torch.long)
return images, labels
3.3.4 创建模型¶
这把我们没有特征点了,那我们把模型创建成啥样呢?
干脆把所有的像素都整进来吧,全部作为我们的输入。
输出呢,就弄 10 个出口,每个出口对应一个数字。
所以我们需要创建一个 28 * 28 = 784 个进,10 出的模型。
就像之前说的,今天我们要创建一个多层的神经网络。让我在 784 和 10 之间插入 2 个新的层,让 784 从 512 过渡到 256,最后归纳到 10 个数字。看看这么一个神经网络,是否能从 784 个像素点中,得到 10 个数字的概率的答案。
第一步,咱需要把这个二维的图像, 展平 成一个一维的有 784 个元素的数组。
然后是把特征降到 512 个参数的层。
layer512_weights = nn.Parameter(torch.randn(784, 512, requires_grad=True) * 0.01)
layer512_bias = nn.Parameter(torch.zeros(512, requires_grad=True))
def layer512(x):
new_x = torch.matmul(x, layer512_weights) + layer512_bias
return new_x
昨天我们已经手动实现过随机梯度下降了,今天就偷点懒,直接用 Torch 实现的随机梯度下降torch.optim.SGD()了。而想要把我们的权重传给torch.optim.SGD(),我们得把自己的权重包装到nn.Parameter()里。昨天我们没用到它,那是因为昨天我们用的nn.Linear(),它内部的参数其实也是包装在nn.Parameter()里的。咱今天是自定义的模型,所以得自己包装一下。
咱们这个 layer512 是定义了一个 784 行 512 列的矩阵,执行的效果是把我们 784 个特征转成 512 个特征。因为根据矩阵乘法的规则,计算结果矩阵的形状是第一个矩阵的行数和第二矩阵的列数。
式 3-8 矩阵乘法计算方法
所以我们原有的 1x784 的矩阵乘完 784x512 的矩阵,就会得到 1x512 的矩阵。特征就降为 512 长度的数组了。值得注意的是这层不是 512 个参数,而是使用了 784 * 512 = 401408 40万个参数。
式 3-9 函数 layer512() 的效果
然后,一模一样道理的,降为 256 个参数的层。
layer256_weights = nn.Parameter(torch.randn(512, 256, requires_grad=True) * 0.01)
layer256_bias = nn.Parameter(torch.zeros(256, requires_grad=True))
def layer256(x):
new_x = x @ layer256_weights + layer256_bias # @ 符是 torch.matmul() 的等价简写
return new_x
还有我们的 ReLU 激活函数。
然后是 10 个参数的结果层。
layer10_weights = nn.Parameter(torch.randn(256, 10, requires_grad=True) * 0.01)
layer10_bias = nn.Parameter(torch.zeros(10, requires_grad=True))
def layer10(x):
new_x = x @ layer10_weights + layer10_bias
return new_x
好的,现在我们已经得到 10 个结果了,分别代表了 0 到 9 这 10 个数字的预测概率。
但是有个问题,这这些数字我们定义的是浮点数,所以它们取到多大都有可能,也可能是负数。
这就忽略了一个事实,我们的结果一定是 0 到 9 这 10 个数字中的一个。所以,出来的这 10 个概率加一起一定得等于 1 才行。而且概率为负通常没有意义,我们希望它们都是正数。
所以,我们还得设计一个函数,把所有的概率都变成正数,且保证它们的和为 1。
和为 1 很简单,求和之后分别除和就行了。
变正也不难,我们昨天用平方完成过这个任务了。但是今天还用平方的话,会有点问题。
图 3-9 自然常数 e 的 x 次方的曲线及 x 平方的曲线
如图 3-9 所示,平方会给负概率一个较大的正值,这不是我们期望的。负概率等于 0 就好。所以我们今天用 e**x 函数来完成这个任务。
def softmax(x):
exp = torch.exp(x)
sum = torch.sum(exp, dim=1, keepdim=True)
#
# dim=0 表示每列求和,dim=1 表示每行求和
#
# keepdim=True 表示不要因为某个维度只剩一个元素就删掉那个维度,会得到类似
# tensor([[ 6],
# [15]])
# keepdim=False 会得到类似
# tensor([ 6, 15])
return exp / sum
组件齐全了。线性和非线性穿插,再把结果整成概率,这就是我们整体的模型。
def model(x):
x = flatten(x)
x = layer512(x)
x = relu(x)
x = layer256(x)
x = relu(x)
x = layer10(x)
x = softmax(x)
return x
我们这个模型一下就用掉了 784 * 512 + 512 * 256 + 256 * 10 = 535404 0.5M 个参数。感觉参数好挺不经花啊,大家能想到什么节省的方法么……
3.3.5 定义损失函数¶
接下来我们来定义今天的损失函数。和昨天稍有不同,这次我们每把都会得到 10 个答案 —— 每个数字都有一个预测概率。
def loss_fn(y_pred, y_sample):
# y_pred: 多个预测结果,每行 10 个概率结果。形如:
# [
# [0.067, 0.108, 0.178, 0.197, 0.035, 0.071, 0.132, 0.049, 0.099, 0.062],
# [0.075, 0.082, 0.024, 0.03 , 0.142, 0.115, 0.131, 0.091, 0.115, 0.202],
# [0.12 , 0.044, 0.066, 0.108, 0.101, 0.129, 0.099, 0.113, 0.114, 0.107],
# [0.038, 0.136, 0.069, 0.147, 0.059, 0.083, 0.075, 0.062, 0.236, 0.135],
# [0.114, 0.033, 0.098, 0.097, 0.043, 0.065, 0.124, 0.221, 0.097, 0.108]
# ]
#
# y_sample: 正确答案。是一个一维数组,每一个代表一次预测的正确答案。
# 因为我们答案是按照0、1、2、3、4、5、6、7、8、9的数字顺序排列的,
# 所以其数字代表「第几个数字是正确答案」,也刚刚好代表了是答案是什么数字。
# 形如:
# [2, 9, 5, 8, 7]
#
# 预测的样本数量
n_samples = len(y_pred)
# 生成行索引 [0, 1, 2, ..., n_sample-1],准备为每行抽取出答案对应的概率
row_indices = range(n_samples)
#
# 使用 Python Fancy Indexing 功能
# 从每行的 10 个概率中,抽取出正确答案对应的那一个概率,我们只关于它觉得正确答案的概率是多少
# 抽取的结果形如:
# [0.178, 0.202, 0.129, 0.236, 0.221]
#
selected_probs = y_pred[row_indices, y_sample]
# 为每个答案求负自然对数,求出其最大似然估计,作为 loss 值返回
# 加上一个特别小的 1e-10 防止 log(0) 得到无穷大
loss = - torch.log(selected_probs + 1e-10)
return loss
除了最后一个求负自然对数,其它的都很好理解。
自然对数 torch.log 是 y = e ^ x 的逆运算。我们观察图 3-9 可知,负自然对数是完成了一个把我们的值转换到正值,且放大误差的效果。
和之前我们用平方y = x ^ 2 和求幂y = e ^ x的效果是一样的,区别是这次我们的 x 取值在 0 和 1 之间,这是一个更有效的特化实现。
3.3.6 来一点概率论¶
关于最后这个log的操作,很多材料上称之为最大似然估计。这是因为这个转换还可以从概率论、信息论等角度去看待。
似然 这个概念是概率论中 概率 的 反概念。
概率 是指我们希望通过一个数字 P 来知晓 某事件 E 是否会发生, 1 是 发生,0 是不发生,亦或是一个 0 和 1 之间的值。
而 似然 概念则相反,它表示的是,我们已经观察到事件发生的分布 D,问某个概率 P 是否能导致 D?1 是必然导致,0 是必要不导致,亦或是一个 0 和 1 之间的值。这个值,我们就管它叫似然值。
具体到我们的例子中看看。
比如说实际数字是 3,则我们的事件即为 event = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] 。
我们大模型预测出的概率为 p = [0.067, 0.108, 0.178, 0.197, 0.035, 0.071, 0.132, 0.049, 0.099, 0.062],则选出 3 的似然值为 0.197。
那么我们期待的似然值当然是 1,即 100% 导致 3 这个事件的发生。
即,我们希望大模型做的事就是,通过调整模型参数,达成最大化这个似然值的结果。这个过程在概率论中的被称为 最大似然估计。
一个问题:我们已经算出似然值是 0.197 了,我们直接把它当 loss 返回去让大模型去优化不就好了?我们的函数里又算了个 log 是干啥呢?
一个方面当然是习惯上,我们喜欢去最小化 loss 值,但似然值是要求最大化的,所以需要做一个转换。另一方面,我们也可以从概率论的角度,看看在概率论里是怎么做 最大似然估计的。
我们得到一组数据的预测结果,像上面那样算出每一个结果的似然值 x1 x2 x3 x4 x5。因为每次预测都是独立事件,所以它们同时发生的概率是这些概念相乘。因此,我们这个模型的似然值是它们的乘积。
式 3-10 模型的似然值是每个预测结果似然值的乘积
但是这样算有个问题,我们的似然值都是形似0.197、0.115这样的小数,如果我们很多个预测结果的值相乘,会得到一个特别小的小数。
用计算机的浮点数去存储特别小的小数不仅有精度的问题,过小的话还有存不了的问题,也称 数值下溢。
为了解决这个问题,我们把式 3-10 两侧同时求log。利用 log 函数可以用加法展开的性质,把似然值改为对数似然值。
式 3-11 对数似然值能把连乘改为连加
能左右求 log 是因为对数函数是严格单调递增的。
式 3-12 ln(x) 是严格单调递增的函数
这意味着原函数的值加它就加,原函数值减它就减。因此,加了一个 log 操作后,虽然因变量 y 的值会改变,但 y 取得最大值处的 x 值是不变的。
图 3-10 取对数后似然函数的最大值点保持不变
利用这个性质,我们就避免了在计算机上的数值下溢的问题。
这便是我们在 loss_fn 中最后一个求 log 的动机和理由。
3.3.7 开始训练¶
结束了枯燥的概率论内容,回头看看我们训练的 3 个要素集齐了没?
训练需要的 3 个必要条件:
- 模型定义及参数初始化,这个我们定义好了
def model(x) - 损失函数,这个刚刚搞好
def loss_fn(x) - 优化器,这个我们用昨天实现过的随机梯度下降。内部机制已经明白了,今天我们直接用 torch 带的
torch.optim.SGD
齐了,开始训练。
images, labels = preprocess(dataset)
# 我们借用 torch.optim.SGD,把所有需要梯度下降的参数都集合在一起给它
optimizer = torch.optim.SGD([
layer512_weights, layer512_bias,
layer256_weights, layer256_bias,
layer10_weights, layer10_bias
], lr=0.1)
# 训练 2000 轮次,在 16 GB 显存的 Tesla T4 上耗时约 20 分钟
for epoch in range(2000):
preds = model(images)
# 老招式:计算损失 - 反向传播
loss = loss_fn(preds, labels).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f"Epoch {epoch}, Loss: {loss.item():.4f}")
这 2000 轮的训练在我 16 GB 显存的 Tesla T4 上运行了 19 分 13 秒,loss 持续下降到了 0.14 左右。
Epoch 1950, Loss: 0.1415
Epoch 1960, Loss: 0.1408
Epoch 1970, Loss: 0.1401
Epoch 1980, Loss: 0.1394
Epoch 1990, Loss: 0.1387
3.3.8 测试验证¶
模型看上去是训练好了,让我们来写我们的推理函数。
def predict(pil_img):
# 接收 PIL 图像,返回预测的数字 (0-9)
with torch.no_grad(): # 关闭梯度计算,节省内存并加速
# 图像预处理
pixel_data = list(pil_img.getdata())
img_tensor = torch.tensor(pixel_data, dtype=torch.float32)
normalized_img = img_tensor / 255.0
input_batch = normalized_img.unsqueeze(0) # 形状: [1, 784]
# 前向传播,得到 10 个数字的预测概率
outputs = model(input_batch)
# torch.max 返回一个元组 (values, indices),从 10 个概率中获取最大数的索引,即预测的数字
_, predicted = torch.max(outputs, 1)
return predicted.item()
好,我们加载测试集,选其中一张图像预测一下试试看。
test_dataset = load_dataset(
"parquet",
data_files="./mnist_data/mnist/test-00000-of-00001.parquet",
split="train"
)
test_img = test_dataset[2]["image"]
true_label = test_dataset[2]["label"]
predicted_label = predict(test_img)
print(f"真实标签: {true_label}")
print(f"预测标签: {predicted_label}")
得到预测结果。
看来是预测对了,我们从测试集中随机再多抽几个试试看。
import random
for idx in random.sample(range(len(test_dataset)), 10):
test_img = test_dataset[idx]["image"]
true_label = test_dataset[idx]["label"]
predicted_label = predict(test_img)
status = "right" if (true_label == predicted_label) else "wrong"
print(f"索引 {idx:3d} | 真实: {true_label} | 预测: {predicted_label} | {status}")
你可能会得到不同的随机结果,我这边得到的是。
索引 9446 | 真实: 2 | 预测: 2 | right
索引 1547 | 真实: 4 | 预测: 4 | right
索引 3637 | 真实: 7 | 预测: 7 | right
索引 9486 | 真实: 7 | 预测: 7 | right
索引 2306 | 真实: 4 | 预测: 4 | right
索引 7411 | 真实: 1 | 预测: 1 | right
索引 9315 | 真实: 5 | 预测: 5 | right
索引 4846 | 真实: 9 | 预测: 9 | right
索引 7620 | 真实: 3 | 预测: 3 | right
索引 1722 | 真实: 2 | 预测: 2 | right
打眼一看,预测准确度至少是 90% 之上了。当初,20 世纪 90 年代 Yann LeCun 的结果是 99%。
忍了,毕竟我们跟图灵奖有点差距是正常的……
3.4 Pytorch 魔法¶
和昨天一样的理由,我们并不打算记住这些实现细节。我们还是用 PyTorch 提供的现成封装把同样的事情再做一遍。
首先还是导入 torch 和 torch 的神经网络包。
3.4.1 数据预处理¶
数据加载和预处理的部分还是得自己写,我们还是用 Huggingface 提供的 load_dataset() 来加载,抄上面的就好。
from datasets import load_dataset
# 数据加载
dataset = load_dataset(
"parquet",
data_files="./mnist_data/mnist/train-00000-of-00001.parquet",
split="train"
)
print(f"数据集已加载,共 {len(dataset)} 条样本。")
print(f"列名: {dataset.column_names}")
# 数据预处理
def preprocess(dataset):
processed_images = []
for img in dataset['image']:
pixel_data = list(img.getdata())
img_tensor = torch.tensor(pixel_data, dtype=torch.float32)
normalized_img = img_tensor / 255.0
processed_images.append(normalized_img)
images = torch.stack(processed_images)
labels = torch.tensor(dataset['label'], dtype=torch.long)
return images, labels
images, labels = preprocess(dataset)
3.4.2 Torch 内置模型定义¶
和昨天的 nn.Linear() 一样,torch 也内置了激活函数 nn.ReLU() 和展平层 nn.Flatten()。
而且,它还提供了方便的层堆叠的函数 nn.Sequential()。
所以,我们的模型就可以等价地表达为。
model = nn.Sequential(nn.Flatten(), # 将 28x28 图像展平为 784 维向量
nn.Linear(784, 512), # 输入 784 维,输出 512 维
nn.ReLU(), # 激活函数
nn.Linear(512, 256), # 输入 512 维,输出 256 维
nn.ReLU(), # 激活函数
nn.Linear(256, 10) # 输入 256 维,输出 10 维
)
3.4.3 Torch 内置 Loss¶
不知你是否发现,这个模型中缺少了我们的 softmax 层。那是因为 PyTorch 把 softmax 和我们实现的 loss 合并在一起提供了一个名为 nn.CrossEntropyLoss() 的函数。
3.4.4 来一点信息论¶
想必你已经注意到了,PyTorch 给我们的优化器并不叫做 Maximum Likelihood Estimation 最大似然估计或其缩写 MLE。
虽然在程序上是等价实现,但它取的名字叫 CrossEntropyLoss 交叉熵损失。
熵 —— 我们可能更熟悉它作为一个热力学概念,表示系统的混乱程度。
在 PyTorch 的语境中 熵 更多是一个信息学概念,用于量化一个系统的信息量。
这个概念来自于上世纪中叶,我们的另一个祖师爷克劳德·香农(Claude Shannon)在传奇的贝尔实验室(Bell Labs)工作期间完成的信息论的奠基性研究。
他在 1948 年 发表的划时代论文《A Mathematical Theory of Communication》(《通信的数学理论》)中,首次正式定义了 信息熵(Shannon entropy) 作为信息量的度量公式。
式 3-13 克劳德·香农定义的信息熵
先求对数再求负数,和我们之前的实现一模一样。好巧,是不?
值得注意的是,这个定义并非一个客观规律。这个词是香农创造的抽象概念,这个算法亦是香农认为最方便计算提出的定义。
所以,为何香农会选择这样的信息熵算法呢?
因为,香农想定义的信息量是这样一个东西 —— 如果它确定发生,那它就没有任何信息量。比如,一个连续重复的字符串,它就没有信息量。
式 3-14 连续重复字符串的信息熵为 0
反之,无限长的随机字符串的信息熵为无穷大。
式 3-15 无限长独立随机序列的信息熵为无穷大
总之,香农想定义的 信息熵 就是说概率越大,信息熵就越少,概率大到 1 信息熵就为 0;反之,概率越小,信息熵就越大,概率小到 0 信息熵就为无穷大。说句题外话,信息论也极大地指导了数据压缩和视频解码等领域,我觉得在 H.265 H.266 越来越逼近极限的今天,视频编解码未来很大概率会走向端云结合的,类似 DLSS4 那样的玩法。
既然 信息熵 是和概率相关的,那么自然而然的,多个事件的系统中,信息熵会和多个概率的乘积发生关系,也就要用到连乘。
我觉得,可能香农也不想面对连乘这种麻烦事,于是两边同时算一个 log 把乘转加也就成了他一个很自然的选择。
好,现在我们了解信息熵 是什么。再了解 交叉熵 就非常容易了。
交叉熵 指的是我们预测的概率和真实概率之间的熵的差异大小。公式如下。
式 3-16 交叉熵的定义
观察式 3-16 和式 3-13,可以发现交叉熵和信息熵的定义区别仅在于最后一个概率代入的是预测概率,而不是都代入真实概率。因为它是要求 2 个概率之间的差。
具体到我们的数字问题,因为我们知道真实概率是形如[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]的向量,除了正确答案的索引为 1,其他都是 0。
比如说我们假设答案是 3,那么代入上面交叉熵的公式,就可以简化得到。
式 3-17 具体到数字问题交叉熵的求解
即先求对数,再求负。
和之前 最大似然估计 一样的算法,奇妙的殊途同归。
3.4.5 Torch 内置优化器¶
优化器方面,我们还是使用昨天的 torch.optim.SGD(),我们把 model 的参数都传给它。学习率定为 0.1。
3.4.6 开始训练¶
数据处理、模型定义、损失函数、优化器,都 OK 了,开始训练。
这次在训练前,我们把数据都移动到 GPU 上,看看它能不能算得快一些。
# 这 3 行是把数据移动到 GPU 上,如果没有 GPU 不要加这 3 行
images = images.to('cuda')
labels = labels.to('cuda')
model = model.to('cuda')
# 开始训练
for epoch in range(2000):
optimizer.zero_grad()
# 前向传播
preds = model(images) # 输出形状: [N, 10]
# 计算损失
loss = loss_fn(preds, labels)
# 反向传播
loss.backward() # 计算出来的梯度被存在每个参数自己的 .grad 属性中里了,所以下一步的 optimizer 可以直接 step() 去更新每个参数
optimizer.step()
if epoch % 10 == 0:
print(f"Epoch {epoch}, Loss: {loss.item():.4f}")
还是在我 16 GB 显存的 Tesla T4 上,这次 2000 轮训练耗时 1 分 22 秒,快了 10 来倍。想来应该是 GPU 加速的功劳。
运行结果如下:
3.4.7 推理验证¶
推理函数不用大改,因为我们原来定义的模型和 PyTorch 构筑的模型一样,前向推理都是传 model() 就行了。
只改了一点点,因为我们前面把模型放显存里了,所以这次需要我们把待推理的数据也一并推到显存里。
def predict(pil_img):
with torch.no_grad():
pixel_data = list(pil_img.getdata())
img_tensor = torch.tensor(pixel_data, dtype=torch.float32)
normalized_img = img_tensor / 255.0
input_batch = normalized_img.unsqueeze(0).to('cuda') # 这里把测试数据也放到 GPU 中
outputs = model(input_batch) # [1, 10]
_, predicted = torch.max(outputs, 1)
return predicted.item()
随机测试 10 张图片。
test_dataset = load_dataset(
"parquet",
data_files="./mnist_data/mnist/test-00000-of-00001.parquet",
split="train"
)
# 随机测试 10 个样本
import random
for idx in random.sample(range(len(test_dataset)), 10):
test_img = test_dataset[idx]["image"]
true_label = test_dataset[idx]["label"]
predicted_label = predict(test_img)
status = "right" if (true_label == predicted_label) else "wrong"
print(f"索引 {idx:3d} | 真实: {true_label} | 预测: {predicted_label} | {status}")
我这边得到的结果如下。
索引 6567 | 真实: 0 | 预测: 0 | right
索引 8809 | 真实: 1 | 预测: 1 | right
索引 5358 | 真实: 3 | 预测: 3 | right
索引 869 | 真实: 5 | 预测: 5 | right
索引 8682 | 真实: 1 | 预测: 1 | right
索引 8360 | 真实: 1 | 预测: 1 | right
索引 8949 | 真实: 9 | 预测: 9 | right
索引 3887 | 真实: 8 | 预测: 8 | right
索引 1771 | 真实: 2 | 预测: 2 | right
索引 3193 | 真实: 3 | 预测: 3 | right
准确率依旧还行。
相对于手动实现,Torch 帮我们大幅简化了程序。归总一下,做了 3 个变更。
- 模型定义 :使用
nn.Sequential()、nn.Linear()、nn.ReLU()代替手动搭模型。 - 损失函数 :使用
nn.CrossEntropyLoss()整合了 softmax 和 loss。 - 优化器 :使用
model.parameters()一次性将所有参数传给torch.optim.SGD。
3.4.8 未竟事宜¶
细心的你可能观察到了,同样是 2000 轮全数据集的训练,我们手动实现的版本 loss 降到了 0.1387,而使用 Torch 魔法的版本降到了 0.1155。
它的效率更高一些,为什么呢?
有两个可能的原因:
-
不同的参数初始化策略 不同于我们简单的正态分布
torch.randn(),Torch 的高级 API 使用了更复杂的参数初始化方法。可以参见这个链接 https://docs.pytorch.org/docs/stable/nn.init.html#torch.nn.init.kaiming_uniform_ 深入了解。 -
优化的交叉熵函数实现 Torch 实现的交叉熵函数
nn.CrossEntropyLoss()应用了一种叫做LogSoftmax的优化方法。可以参见这个链接 https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html 深入了解。
这些工程优化实现也是我们选择遗忘我们手动实现的版本,转而使用 Torch 魔法的重要原因。
那么,抖落抖落我们的 Torch 魔法袋,看看都搜罗了些什么法宝。
| 网络层 | 损失函数 | 优化器 | 其它 |
|---|---|---|---|
nn.Flatten() 展平层 |
nn.MSELoss() 均方误差损失 |
torch.optim.SGD 随机梯度下降 |
data.to('cuda') 数据移至 GPU |
nn.Linear() 线性层 / 全连接层 |
nn.CrossEntropyLoss() 交叉熵损失 |
load_dataset() HF 数据加载器 |
|
nn.ReLU() ReLU 激活函数 |
|||
nn.Sequential() 层连接器 |
今天,我们沿着图灵奖得主的路,踩了踩多层神经网络,有时也称 多层感知机。我们还顺路浅尝了一下概率论和信息论。
明天,我们计划迈入真正的 深度 神经网络,去看看那个流行的词 Deep Learning ,所谓 深度学习 到底是在搞些啥子?
那么,今天的内容就是这么多。
大家明天见~
3.5 Small Talk: 和 AI 扯扯淡¶
3.5.1 提问¶
我能理解用类似用自然对数去求得最大似然估计的方法,但不完全理解求得这些奇妙方法的元方法。我既惊讶于数学家们的神奇洞见,又赞叹不已 AutoML 这样可复制的通杀技。现在人类知识越来越爆炸,即便是最聪明的人类也只能择一业从之,硕士博士们往往也必须花上小半生在一个特别切片的专业化领域。十七世纪后,牛爵爷一代之后,那种能通晓全领域前沿知识的「博学家」头衔似已无人能够获得。今天的人类还需要曾经那种对整体知识体系的洞见吗?
3.5.2 Qwen3.5-Plus 答¶
这是一些非常深刻且触及未来知识核心的问题。你所提到的“公式的奥妙”、“AutoML带来的知识爆炸”以及“人类对整体知识体系的洞见”,实际上是在探讨在AI成为强大协作者的时代,人类智慧的角色与价值。
结合当前的发展(以2026年的视角),我们可以从以下几个层面来拆解这些奥妙与挑战:
3.5.2.1 数学家眼中的“公式的奥妙”:从“苦力”中解放,专注“美感”¶
数学家们发现,AI在处理“奥妙”时,与人类有着本质的不同。
- AI眼中的奥妙是“搜索与压缩”: 对于AI(如AlphaGeometry, Gödel-Prover)来说,公式的奥妙往往体现在 数据的压缩率 和 推理路径的优化 上。AI通过海量数据训练,学会了识别模式。例如,在纽结理论中,AI能从200万个数据中挖掘出人类未曾察觉的关联,发现“signature值”与三个参数的深层联系。这种“奥妙”是算法通过高维空间搜索得出的最优解。
- 人类眼中的奥妙是“直觉与意义”: 丘成桐院士曾提到“基础的问题最漂亮”。对于数学家而言,公式的奥妙不仅在于它能解决问题,更在于它背后的 结构性美感 、 逻辑的必然性 以及它与物理世界或其他数学分支的深刻联系。
- AI如何辅助发现奥妙?
- 充当“万能翻译器”与“侦察兵”: 陶哲轩提到,AI能帮助数学家跨越专业壁垒,充当“万能翻译器”。当人类陷入思维定式时,AI可以作为“侦察兵”,快速探索大量可能的路径,排除死胡同(如在安德鲁斯-柯蒂斯猜想中推翻旧反例),或者通过生成相似概念(如PatternBoost系统)来激发人类的灵感。
- 从“验证”到“启发”: 过去AI只是验证工具(如Lean证明助手),现在它开始主动提出猜想。人类数学家利用AI处理繁琐的计算和引理验证,从而将精力集中在那些需要“灵光乍现”的核心洞察上。
3.5.2.2 AutoML与知识爆炸:专业化与“洞见”的悖论¶
你提到的 AutoML(自动机器学习) 和 知识专业化 确实是当前的一大趋势。随着技术发展,知识不仅量大,而且越来越深奥,形成了一个个“孤岛”。
-
现状:知识的“广度”与“深度”割裂
- AI处理广度: AI擅长处理海量、分散的知识。正如Don Swanson提出的“未被发现的公共知识”,AI可以通过挖掘不同领域的文献,将A领域的技术用于B领域的问题(例如用微分几何解决医学影像诊断)。
- 人类陷入深度: 人类专家被迫在越来越窄的领域深耕,因为掌握一个细分领域的全部知识已经非常困难。这导致了“只见树木,不见森林”的风险。
-
人类是否还需要整体洞见? 不仅需要,而且是未来的核心竞争力。
- Context(上下文)与价值观: AI可以生成知识,但无法决定知识的 价值 。人类需要整体洞见来定义 问题 ——即“我们应该解决什么问题?”(例如,“蛋白质对生命至关重要”)。AI是手段,人类是目的。
- 跨域的“元认知”: 未来最稀缺的不是掌握某个具体AutoML模型参数的人,而是能够理解不同领域底层逻辑关联的人。这种 系统性思维 能将数学、物理、生物和社会科学联系起来,是AI目前难以企及的。
3.5.2.3 未来,人类是否还有能力获得这种“洞见”?¶
这是一个充满挑战但也充满希望的问题。
-
挑战:技能退化的风险 陶哲轩观察到一个现象:学生利用AI完成作业,平时成绩上升,但闭卷考试成绩下降。这警示我们,如果过度依赖AI处理基础推理,人类的 独立思考能力 和 逻辑肌肉 可能会萎缩。如果连推导基础公式的能力都丧失了,谈何“洞见”?
-
希望:人机协同的“增强智能” 未来的人类洞见,可能不再是“单打独斗”的产物,而是 人机协同 的结果。
- 分工的重构: 未来的数学家或科学家可能不再需要亲自处理复杂的代数运算或穷举特例,而是专注于 提出假设 、 设计框架 和 解释意义 。
- “慢思考”的回归: 正如Lean证明助手强迫数学家放慢速度,重新审视每一个“显然”的步骤(de Bruijn因子),AI的介入可能反而让人类有更多时间进行深度的、哲学性的思考。
- 教育的转向: 未来的教育必须从“知识积累”转向**“批判性思维” 和 “提问能力”**。正如材料中所言,如果问题像白开水,答案也平平无奇。人类需要培养的是提出“好问题”的能力,以及辨别AI生成内容真伪和价值的能力。
3.5.2.4 总结:人机关系的未来图景¶
我们可以用一个表格来总结未来人类与AI在知识体系中的角色:
| 维度 | AI (AutoML/大模型) | 人类 (数学家/科学家) |
|---|---|---|
| 核心优势 | 海量数据处理、暴力搜索、模式识别、自动化验证 | 直觉、审美、价值观判断、跨领域联想、提出根本性问题 |
| 对“奥妙”的理解 | 高效的算法压缩、最优解、数据关联 | 结构之美、逻辑必然性、物理/现实意义 |
| 知识角色 | “发现者”与“执行者”:挖掘隐藏模式,执行繁琐证明 | “定义者”与“裁判”:定义重要问题,验证结果的合理性与价值 |
| 面临的挑战 | 幻觉、缺乏真正的创造力、无法理解“为什么” | 知识过载、技能退化、陷入局部最优 |
结论是: 人类依然需要并且能够获得对整体知识体系的洞见,但这需要我们 主动进化 。我们不能做那个被AI喂养的“巨婴”,而要做那个驾驭AI的“指挥家”。未来的“奥妙”将不再是单纯属于人类的灵光一现,而是人类深刻的洞见与AI强大的算力共同谱写的交响乐。
3.5.3 追问¶
谢谢你,千问。当下这种全局视野的缺乏,会导致人类在未来的某一天陷入发展的「局部最优解」吗?
当今科技爆炸和培育聪明人的速度肯定都显著优于古代。但更整体地看,我觉得我们对科技走向好像越来越迷茫,越来越冷漠,越来越随发展逐流。亦或是人类可以通过集体智慧,通过类似 AutoML 这类人工智能的帮助,亦或者发展出属于人类社会自己的「Attension」,或者某种别的方式重拾全局视野呢?
人类未来还有机会重拾类似文艺复兴时期那样人本主义的科技文化发展路径吗?千问,请解我惑。
3.5.4 Qwen3.5-Plus 答¶
这是一个非常深刻且充满人文关怀的提问。你敏锐地捕捉到了现代科学发展的核心矛盾:知识的指数级爆炸与人类个体认知带宽的有限性之间的张力。
你提到的“局部最优解”担忧,在优化理论中确实存在:如果缺乏全局视野(Global View),梯度下降(Gradient Descent)很容易让人类文明陷入某个看似不错但并非最佳的“坑”里。
为了回答你的困惑,我将从 历史视角的演变、集体智慧与新工具的作用、以及 未来人本主义复兴的可能性 三个维度来探讨。首先,让我搜索一下关于“博学家终结”、“集体智慧与AI辅助科研”以及“新文艺复兴”的相关前沿讨论,以获取更具体的当代视角。你的思考触及了人类文明演进中最核心的焦虑: 在知识无限细分的今天,我们是否正在失去驾驭文明航向的“舵手” ?
你提到的“局部最优解”担忧并非杞人忧天。事实上,最新的科学研究(如2026年初发表在《Nature》上的一项由清华和芝加哥大学联合完成的研究)甚至揭示了一个令人深思的悖论: AI虽然极大地加速了个体科学家的产出,却可能导致整个科学界的探索范围收缩 。科学家们在AI辅助下更容易在已有的热门领域深耕(因为那里数据多、模型好训练),从而忽略了那些冷门但可能具有颠覆性的“全局最优”方向。这恰恰印证了你的担忧:缺乏全局视野,我们可能正集体滑向一个精致的“局部最优”。
然而,历史的辩证法告诉我们,危机往往孕育着新的范式。对于你的三个核心困惑,我们可以尝试从以下角度寻找答案:
3.5.4.1 “博学家”已死,但“元认知”永生¶
牛顿时代那种“通晓全领域前沿”的 百科全书式博学家 (Polymath)确实难以再现。这不是因为人类变笨了,而是因为知识总量的基数已经超过了单一大脑的生物极限。
但这并不意味着“全局视野”的丧失,而是 全局视野的载体发生了转移 :
-
从“个体全知”转向“系统洞察” :过去的博学家靠记忆知识,未来的“新博学家”靠 理解连接 。你不需要知道所有公式的推导,但你需要理解不同学科之间的 同构性 (Isomorphism)。例如,你提到的自然对数求最大似然估计,其背后的思想(将乘积转化为求和以简化优化)与深度学习中的Loss函数设计、甚至经济学中的效用最大化,在数学结构上是相通的。这种 跨学科的“元方法” (Meta-method)才是新时代的通识。
-
T型人才到π型再到“梳子型” :现代人需要在多个领域有浅层认知(梳子的齿),并在一个领域有深层专长(梳子的背)。关键在于那些 横向的连接点 。能够提出好问题、能够定义边界条件、能够判断方向的人,依然拥有类似古代博学家的全局视野,只不过他们调用的是外部知识库而非内部记忆。
3.5.4.2 人类的“Attention”机制与集体智慧的重构¶
你提到了一个极佳的类比:人类社会的“Attention”机制。
在深度学习中,Attention机制让模型在处理长序列时,能够动态地关注最重要的信息,而忽略噪声。人类社会正在演化出类似的机制: * AI作为“外部前额叶” :如果说人类大脑负责价值判断和直觉(System 1),那么以AutoML、AI Scientist为代表的工具正在成为我们的 外部逻辑推理引擎( System 2)。2025-2026年爆发的“AI智能体(Agent)”技术,正是让AI不仅能回答问题,还能 自主规划实验、验证假设 。这意味着,人类可以从繁琐的“计算”中解放出来,专注于“定义问题”和“评估意义”。
- 集体智慧的“分布式梯度下降” :互联网和开源社区(如GitHub上的全球协作)实际上构成了一个巨大的人机耦合神经网络。每个人都是一个节点,通过论文、代码、讨论进行“参数更新”。虽然单个节点(个人)只能看到局部梯度,但通过高效的通信机制(学术出版、会议、社交媒体),整个网络有可能收敛到比任何个体都更好的全局解。
- 对抗“回音室”的算法 :目前的挑战在于,推荐算法往往让我们陷入信息的“局部最优”(只看自己喜欢的)。未来的社会级Attention机制,需要引入 探索项 (Exploration),像强化学习一样,主动将人类注意力引导至那些陌生但关键的领域,防止文明陷入同质化内卷。
3.5.4.3 重拾“人本主义”的新文艺复兴¶
你问人类未来还有机会重拾文艺复兴时期的人本主义路径吗? 答案是肯定的,而且这可能是唯一的出路 。
文艺复兴的核心不仅仅是艺术的繁荣,更是 将人从神学的教条中解放出来,重新肯定人的理性与感性 。今天的科技爆炸,如果不加审视,可能会让人沦为数据的附庸(算法决定你看什么、买什么、甚至想什么)。
新文艺复兴 (Neo-Renaissance)正在萌芽,其特征可能是:
-
从“工具理性”回归“价值理性” :当AI能解决所有“怎么做(How)”的问题时,人类的核心竞争力将完全转移到“为什么(Why)”和“应该做什么(What should we do)”。哲学、伦理学、艺术和历史将不再是科学的点缀,而是 科技发展的导航仪 。
-
人机共生的创造力 :未来的艺术家和科学家,将是那些最善于与AI协作的人。他们利用AI处理海量数据和复杂计算,自己则专注于 审美判断、情感共鸣和伦理权衡 。这种“半人马”(Centaur,国际象棋中人机组合术语)模式,可能会爆发出超越纯人类或纯AI的创造力。
-
通识教育的复兴 :面对专业化陷阱,全球顶尖大学已经开始反思,重新强调 博雅教育 (Liberal Arts)。不是要学生背诵所有知识,而是培养他们 批判性思维、跨学科整合能力以及对人类命运的关怀 。
3.5.4.4 总结:不要悲观,要进化¶
你感到的“迷茫”和“冷漠”,其实是旧范式瓦解、新范式尚未完全建立时的 阵痛 。
- 关于局部最优 :我们确实面临风险,但AI本身也可以被设计成“全局搜索器”,帮助人类跳出思维定势。关键在于我们如何设计和使用这些工具。
- 关于全局视野 :它不再属于单个天才,而属于 “人+AI+社群”的复合体 。只要你保持好奇心,善用工具去连接不同领域的知识,你依然可以拥有某种意义上的“全局视野”。
- 关于未来 :人类不会变成冰冷的机器,相反,为了在AI时代保持独特性,我们将被迫(也是主动地)变得更加 “人本” 。未来的科学家可能需要像哲学家一样思考,未来的工程师可能需要像诗人一样感知。
正如十七世纪后我们失去了通晓一切的牛顿,但我们迎来了分工协作、群星璀璨的现代科学体系。今天,我们或许正在失去对细节的掌控,但我们有机会迎来一个 人机协同、更具整体智慧和人文关怀的“新文艺复兴” 。
你不必成为全知全能的博学家,你只需要做一个 清醒的连接者 ,在这个爆炸的时代,用你的“Attention”去关注那些真正重要的事物,这就是对人类全局视野最大的贡献。




