深夜十一点,狭窄的书房里,只有书桌上的屏幕还亮着。
林晨盯着PyTorch官方教程的最后一个章节,已经看了三遍。屏幕上是一个完整的神经网络代码示例——MNIST手写数字识别,深度学习界的“Hello World”。过去两周,他像海绵一样吸收着那些抽象的概念:张量、梯度、反向传播、损失函数。数学补课起了作用,至少现在看到矩阵乘法不再发怵,能理解为什么权重更新要用链式法则。但理解归理解,距离“亲手创造一个能思考的东西”,还隔着一段需要代码去填补的距离。
“跟着做一遍”。他对自己说,声音在安静的房间里显得格外清晰。新建Python文件,导入torch和torchvision。下载MNIST数据集——一个包含六万张28x28像素手写数字图片的经典数据集。代码跑起来,终端开始显示下载进度。
林晨看着那些滚动的日志,忽然有些恍惚。三十五岁,再次失业1个月,存款以每月两万多的速度消耗,自己却在这里下载一堆黑白数字图片。这个场景如果拍下来给一年前的自己看,恐怕会觉得是个荒诞的玩笑。但数据下载完成的提示音把他拉回现实。
接下来是数据预处理:将图片像素值从0-255归一化到0-1之间,转换成PyTorch能处理的Tensor格式,分割成训练集和测试集。这些步骤他已经在Jupyter Notebook里练习过多次,手指在键盘上敲击得流畅。真正的挑战从定义模型开始。
教程提供了一个简单的全连接神经网络:输入层784个节点(28x28),两个隐藏层各128个节点,输出层10个节点(对应数字0-9)。结构图在屏幕上展开,林晨逐行敲入代码。
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, 10)
def forward(self, x):
x = x.view(-1, 784)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
不到二十行代码。但林晨知道,这二十行背后是三十年来神经网络研究的结晶。每一层Linear代表一次矩阵乘法和偏置加法,每个ReLU激活函数给模型注入非线性——正是这种非线性让神经网络能够拟合复杂的函数关系。
他想起大学时上的《人工智能导论》,教授说神经网络是“黑箱”,输入输出之间是难以解释的权重迷宫。当时觉得神秘,现在亲手搭建时,却感受到一种奇异的透明感——每一层都在做确定性的数学运算,所谓的“智能”不过是这些运算在大量数据训练后,权重被调整到能提取有效特征的状态。
“代码可以骗人,但逻辑不会”。他低声念着自己的口头禅,点击运行。
模型初始化完成。接下来是训练循环的核心部分:定义损失函数(交叉熵损失)和优化器(随机梯度下降SGD)。学习率设为0.01,一个保守的起始值。第一次训练开始了。
林晨设置了5个epoch(遍历整个训练集的次数)。代码运行,GPU风扇轻微转动——他庆幸自己当年配电脑时选了带独立显卡的型号,虽然是为了打游戏,现在却成了训练模型的利器。
第一个epoch结束时,终端打印出损失值:2.302。准确率:10.3%。“跟随机猜差不多”。林晨苦笑。MNIST有十个类别,随机猜测的准确率应该是10%,模型现在几乎没学到任何东西。
但他没有停。第二个epoch,损失降到1.876,准确率升到23.7%。第三个epoch,1.432,45.2%。数字的变化有种催眠般的魔力,他盯着屏幕,看着那些代表模型正在“学习”的证据。第四个epoch结束时,准确率突破了70%。损失值降到0.876。
林晨身体前倾,手肘撑在桌上。他能感觉到心跳在加速——不是因为紧张,而是因为某种接近真相的兴奋。那些抽象的数学概念正在转化为具体的、可测量的能力提升。模型在“理解”那些手写数字的形状特征。
第五个epoch,最后一个。训练循环在运行,进度条缓慢移动。林晨起身倒了杯水,回到座位时,刚好看到最终结果打印出来:Epoch 5/5, Loss: 0.356, Accuracy: 89.7%。
89.7%。他眨了眨眼,重新读了
>>>点击查看《AI时代:码农的涅盘重生》最新章节