《智能重生:从垃圾堆到AI工程师》——第五章 代码与灵魂

张开发
2026/5/5 17:50:37 15 分钟阅读

分享文章

《智能重生:从垃圾堆到AI工程师》——第五章 代码与灵魂
第五章 代码与灵魂专栏总目录《智能重生》AI工程师成长小说专栏一陆鸣在赵工程师的工作间里醒来脸贴在冰凉的桌面上嘴角还粘着一道干了的记号笔印。白板上的公式被他蹭花了一大片蓝色和黑色的笔迹混在一起像某种抽象画。工作台上多了一样东西——一碗粥还冒着热气。碗旁边压着一张纸条是沈莜的字迹又大又歪“喝完记得把碗还回来。别自己藏起来当宝贝。”陆鸣端起碗喝了一口。米粒很稠不是那种能照见人影的稀粥而是真正能填肚子的、有分量的粥。他喝了两口觉得今天的粥特别香——也许是因为加了一点点盐也许是因为他太累了胃在发出最诚实的赞美。盒子亮了。“第五章Python编程与数据结构。预计学习时间6小时加速模式。完成本章后你将能够亲手编写一个完整的神经网络——从数据加载到训练循环全部由你自己实现。”陆鸣把最后一口粥喝完把碗放在一边深吸一口气。“开始。”二“Python是一种编程语言。它的设计哲学是简单、明确、优雅。在大断线前的世界它是AI领域最常用的语言因为它的语法接近人类自然语言学习曲线平缓。”屏幕上出现了第一行代码print(Hello, world!)“这是每一个程序员的第一个程序。它在屏幕上输出Hello, world!。”陆鸣在工作台的终端上——那是一台古老的、但还能运行的电脑——打开了Python解释器。他照着盒子上的代码一个字母一个字母地敲进去。 print(Hello, world!) Hello, world!屏幕回应了他。那种感觉很奇怪——不是“物理世界”的回应不是力气换来的结果而是纯粹的、由符号和规则驱动的、看不见摸不着却在眼前发生了的回应。他像个孩子一样又敲了一遍。 print(陆鸣是个废物) 陆鸣是个废物屏幕上出现了他骂自己的话。他看着那行字嘴角不自觉地歪了一下——连电脑都同意他是废物不对电脑只是在重复他告诉它的话。它没有判断没有感情只有执行。这是他第一次感受到“编程”的本质你给机器精确的指令它做精确的事。不多不少不评价。“接下来学习变量。”盒子继续。x5y3zxyprint(z)# 输出8“变量是存储数据的容器。Python中的变量不需要声明类型直接赋值即可。支持的数据类型包括整数(int)、浮点数(float)、字符串(str)、布尔值(bool)、列表(list)、元组(tuple)、字典(dict)等。”陆鸣一个一个地试。name陆鸣age22height1.72is_aliveTruehobbies[捡垃圾,吃营养膏,睡觉]profile{name:陆鸣,age:22,occupation:回收者}他看着那个hobbies列表苦笑了一下。“捡垃圾”居然是他的爱好。不对——那不是爱好是生存手段。但他没有更好的词来形容。“列表是Python中最常用的数据结构之一。它是一个有序的、可变的元素集合。你可以用索引访问元素——索引从0开始。”print(hobbies[0])# 捡垃圾hobbies.append(学习AI)print(hobbies)# [捡垃圾, 吃营养膏, 睡觉, 学习AI]他往列表里加了一个新项目。看着“学习AI”出现在“捡垃圾”旁边他觉得这个列表突然变得不那么可悲了。“控制流if语句、for循环、while循环。”ifage18:print(陆鸣是成年人)else:print(陆鸣是未成年人)forhobbyinhobbies:print(我喜欢hobby)count0whilecount5:print(count)countcount1他运行了这些代码。屏幕上依次出现了“我喜欢捡垃圾”、“我喜欢吃营养膏”、“我喜欢睡觉”、“我喜欢学习AI”。最后那行让他停了一下——“我喜欢学习AI”。这是真的吗他真的喜欢吗还是他只是不得不学他不确定。但他知道当他看到代码按照他的指令正确运行时心里有一种微弱的、像火星子一样的东西在闪烁。不是快乐是更安静的东西——满足。“现在学习函数。函数是组织好的、可重复使用的代码块。”defgreet(name):return你好name messagegreet(陆鸣)print(message)# 你好陆鸣“函数可以接收输入参数进行一些操作然后返回输出。在AI中模型的前向传播就是一个巨大的函数输入数据输出预测。”陆鸣写了一个自己的函数defcopper_probability(weight,color_score):# 根据重量和颜色分数估算铜的概率# 权重凭经验重量占70%颜色占30%returnweight*0.7color_score*0.3probcopper_probability(0.8,0.9)print(f这个零件是铜的概率约{prob:.0%})# 输出这个零件是铜的概率约83%他居然用代码把自己捡垃圾的经验写了出来。那一刻他觉得自己不是在学编程——他是在把自己的大脑翻译成机器能懂的语言。盒子的声音出现了赞许的意味。“很好。你已经开始用编程解决实际问题。接下来学习一个非常重要的数据结构——NumPy数组。”屏幕上弹出了一个警告框“NumPy是Python的科学计算库是AI的基石。在本终端中已预装。请导入并开始学习。”importnumpyasnp# 创建数组anp.array([1,2,3])bnp.array([[1,2],[3,4],[5,6]])print(a.shape)# (3,)print(b.shape)# (3, 2)“NumPy数组与Python列表不同它存储同类型数据支持向量化运算——对数组的操作会自动应用到每一个元素上不需要写循环。”arrnp.array([1,2,3,4,5])print(arr*2)# [2 4 6 8 10]print(arr10)# [11 12 13 14 15]print(np.sqrt(arr))# [1. 1.414 1.732 2. 2.236]陆鸣看着这些运算脑子里突然闪过一个念头——这不就是向量吗他第一课学的向量在这里就是NumPy数组。同样的数字列表同样的运算规则。“NumPy的核心是多维数组对象ndarray。一个二维数组就是矩阵。你可以做矩阵乘法”Anp.array([[1,2],[3,4]])Bnp.array([[5,6],[7,8]])Cnp.dot(A,B)# 或 A Bprint(C)# [[19 22]# [43 50]]他手动验证了一下1×52×7191×62×8223×54×7433×64×850。和他在第三章学的矩阵乘法完全吻合。“计算机正在把你学过的所有数学概念变成可执行的代码。”盒子说“向量、矩阵、导数——这些抽象的符号在Python和NumPy中变成了具体的操作。这就是‘计算思维’把数学转化为算法把算法转化为代码。”赵工程师不知什么时候站在了门口手里拿着一个保温杯。他看着屏幕上那些代码推了推眼镜。“你在学NumPy”“嗯。”“那你差不多可以开始写真正的神经网络了。”赵工程师走进来从抽屉里翻出一个旧硬盘“这里面有一个数据集。大断线前的经典——MNIST。六万张手写数字的图片每张28x28像素。它的标签是0到9的数字。这是AI界的‘Hello, world’。”他把硬盘接到工作台的电脑上打开了一个文件夹。里面躺着几万个文件名字像“0_1.png”、“1_23.png”之类的。“你的任务”赵工程师说“是用你学到的知识——Python、NumPy、微积分、线性代数——从零写一个神经网络能识别这些手写数字。不准用现成的深度学习框架。你要自己实现矩阵乘法、激活函数、反向传播。”陆鸣盯着屏幕上那些图片。手写数字歪歪扭扭有的写得像鬼画符有的勉强能认出来。人类一眼就能看出那是几但机器需要被教会——用数学、用代码、用无数次的计算。“如果我写不出来呢”“那你学的一切就只是纸上谈兵。”赵工程师喝了一口保温杯里的水“AI不是数学竞赛。AI是在真实数据上运行的代码。不懂代码你就只是一个会说几个名词的……废物。”这个词像一把小刀准确地扎进了陆鸣的胸口。他转过身面对着屏幕。“开始写。”三盒子上出现了一个教学界面左边是任务说明右边是代码编辑器。“实现一个最简单的神经网络输入层784个神经元28x28像素隐藏层128个神经元输出层10个神经元对应数字0-9。激活函数隐藏层使用ReLU输出层使用Softmax。损失函数交叉熵损失。优化算法随机梯度下降SGD学习率0.01。”陆鸣觉得自己像是被扔进了一个深渊。他只知道这些名词但要把它们变成代码像把一堆砖头变成一座房子。“一步一步来。”盒子说“第一步加载数据。”importnumpyasnpimportstructdefload_mnist_images(filename):withopen(filename,rb)asf:magic,num,rows,colsstruct.unpack(IIII,f.read(16))imagesnp.fromfile(f,dtypenp.uint8).reshape(num,rows*cols)returnimages.astype(np.float32)/255.0# 归一化到0-1defload_mnist_labels(filename):withopen(filename,rb)asf:magic,numstruct.unpack(II,f.read(8))labelsnp.fromfile(f,dtypenp.uint8)returnlabels X_trainload_mnist_images(train-images.idx3-ubyte)y_trainload_mnist_labels(train-labels.idx1-ubyte)X_testload_mnist_images(t10k-images.idx3-ubyte)y_testload_mnist_labels(t10k-labels.idx1-ubyte)陆鸣看着这些代码大部分细节他还不完全懂——那个struct.unpack是什么为什么要用IIII但他理解了核心思路把图片文件读进来把像素值变成0到1之间的浮点数然后用一个二维数组存储每一行是一张图片每一列是一个像素。“第二步初始化参数。”input_size784hidden_size128output_size10# He初始化权重用均值为0、方差为2/输入维度的正态分布W1np.random.randn(input_size,hidden_size)*np.sqrt(2.0/input_size)b1np.zeros((1,hidden_size))W2np.random.randn(hidden_size,output_size)*np.sqrt(2.0/hidden_size)b2np.zeros((1,output_size))“为什么权重不初始化为零因为如果所有权重相同所有神经元将学习到相同的特征网络无法打破对称性。为什么用这个特定的缩放因子这是He初始化适用于ReLU激活函数。”陆鸣把这些记在心里。每一条看似随意的代码背后都有一个数学理由。“第三步前向传播与激活函数。”defrelu(z):returnnp.maximum(0,z)defsoftmax(z):exp_znp.exp(z-np.max(z,axis1,keepdimsTrue))# 减去最大值防止溢出returnexp_z/np.sum(exp_z,axis1,keepdimsTrue)# 前向传播defforward(X,W1,b1,W2,b2):Z1np.dot(X,W1)b1# 线性变换A1relu(Z1)# ReLU激活Z2np.dot(A1,W2)b2# 线性变换A2softmax(Z2)# Softmax激活输出概率分布returnZ1,A1,Z2,A2他运行了一下用几张图片测试前向传播输出是一些随机的概率——因为还没有训练模型的预测完全是瞎猜。“第四步损失函数。交叉熵损失衡量预测分布与真实分布的差距。”defcross_entropy_loss(y_pred,y_true):my_true.shape[0]# y_true是标签(0-9的数字)需要转换为one-hot编码y_true_one_hotnp.zeros((m,output_size))y_true_one_hot[np.arange(m),y_true]1# 计算交叉熵 -Σ y_true * log(y_pred)log_likelihood-np.log(y_pred1e-8)# 加1e-8避免log(0)lossnp.sum(y_true_one_hot*log_likelihood)/mreturnloss“第五步反向传播。这是核心。你需要计算损失对每个参数的梯度。”陆鸣觉得自己的大脑在燃烧。他需要运用链式法则——从损失开始一步一步往回推。盒子上给出了公式对于输出层Softmax 交叉熵的梯度简化形式dZ2 y_pred - y_true_one_hot对于隐藏层到输出层的权重和偏置dW2 (1/m) * A1.T dZ2db2 (1/m) * np.sum(dZ2, axis0, keepdimsTrue)对于隐藏层的梯度使用ReLU的导数大于0时导数为1否则为0dA1 dZ2 W2.TdZ1 dA1 * (Z1 0) # ReLU导数dW1 (1/m) * X.T dZ1db1 (1/m) * np.sum(dZ1, axis0, keepdimsTrue)他一个字母一个字母地敲defbackward(X,y_true,y_pred,Z1,A1,W2):mX.shape[0]y_true_one_hotnp.zeros((m,output_size))y_true_one_hot[np.arange(m),y_true]1# 输出层梯度dZ2y_pred-y_true_one_hot dW2(1/m)*A1.T dZ2 db2(1/m)*np.sum(dZ2,axis0,keepdimsTrue)# 隐藏层梯度dA1dZ2 W2.T dZ1dA1*(Z10)# ReLU的导数dW1(1/m)*X.T dZ1 db1(1/m)*np.sum(dZ1,axis0,keepdimsTrue)returndW1,db1,dW2,db2“第六步参数更新——梯度下降。”learning_rate0.01defupdate_parameters(W1,b1,W2,b2,dW1,db1,dW2,db2,lr):W1-lr*dW1 b1-lr*db1 W2-lr*dW2 b2-lr*db2returnW1,b1,W2,b2“第七步训练循环。”epochs20batch_size64forepochinrange(epochs):# 每个epoch打乱数据indicesnp.random.permutation(X_train.shape[0])X_shuffledX_train[indices]y_shuffledy_train[indices]total_loss0foriinrange(0,X_train.shape[0],batch_size):X_batchX_shuffled[i:ibatch_size]y_batchy_shuffled[i:ibatch_size]# 前向传播Z1,A1,Z2,y_predforward(X_batch,W1,b1,W2,b2)# 计算损失losscross_entropy_loss(y_pred,y_batch)total_lossloss# 反向传播dW1,db1,dW2,db2backward(X_batch,y_batch,y_pred,Z1,A1,W2)# 更新参数W1,b1,W2,b2update_parameters(W1,b1,W2,b2,dW1,db1,dW2,db2,learning_rate)# 每个epoch结束后在测试集上评估准确率_,_,_,test_predforward(X_test,W1,b1,W2,b2)test_accnp.mean(np.argmax(test_pred,axis1)y_test)print(fEpoch{epoch1}/{epochs}, Loss:{total_loss/(X_train.shape[0]//batch_size):.4f}, Test Acc:{test_acc:.4f})他按下了运行键。屏幕上的光标闪烁了一下然后开始输出Epoch 1/20, Loss: 0.8473, Test Acc: 0.8524 Epoch 2/20, Loss: 0.3982, Test Acc: 0.8935 Epoch 3/20, Loss: 0.3276, Test Acc: 0.9062 Epoch 4/20, Loss: 0.2851, Test Acc: 0.9158 Epoch 5/20, Loss: 0.2584, Test Acc: 0.9231 ... Epoch 20/20, Loss: 0.1527, Test Acc: 0.958995.89%的准确率。一个从零写出来的、没有任何深度学习框架的、只有一层隐藏层的神经网络识别手写数字的准确率达到了95%以上。陆鸣靠在椅背上盯着屏幕上那个“0.9589”感觉自己的胸腔里有什么东西在膨胀。不是骄傲——是震撼。他亲手创造了一个能“看”的东西。它没有眼睛没有意识但它能从像素里看出数字。它学会了。他低头看着自己的手。这双手曾经只能在垃圾堆里翻出铜和铁。现在这双手写了200行代码创造了一个能识别手写数字的人工神经网络。“我做到了。”他说。赵工程师在他身后一直没有说话。但现在他开口了声音很轻“这只是开始。”四那天深夜工作间里只剩下陆鸣和盒子。赵工程师回去了临走前在陆鸣桌上放了一个新的保温杯里面是热的水。陆鸣没有睡意。他一遍又一遍地运行他的神经网络调整学习率、改变隐藏层大小、尝试不同的初始化方法、添加Dropout、实验不同的优化器他手动实现了带动量的SGD。他像是一个刚拿到新玩具的孩子迫不及待地想拆开它、弄懂它、改进它。盒子上出现了一个他从未见过的界面。不是教学课程而是一个问题“你已经学会了神经网络的基础实现。你理解了前向传播、反向传播、梯度下降。你甚至可以自己调优模型。但你只看到了AI的‘壳’。你想看到‘核’吗”陆鸣的手指悬停在屏幕上。“什么‘核’”“‘天工’的底层训练框架。大断线前‘天工’的核心训练代码是开源的。我现在可以让你访问一份拷贝。你可以在沙盒环境中阅读它的代码理解一个超级智能是如何被训练出来的。这不是教学——这是真正的工业级代码。数百万行涉及分布式训练、混合精度、梯度累积、模型并行……你可能会被吓到。”陆鸣沉默了很久。他想起了白天写的200行代码。那是他的骄傲。但和“天工”的数百万行相比那只是一个婴儿的蹒跚学步。他还没有准备好。但也许“准备”这件事永远不会完成。“给我看。”他说。屏幕暗了一下然后出现了第一行代码——不不是第一行是文件列表。成百上千个文件目录结构深不见底。文件名像天书distributed_trainer.py、sharding_strategy.py、gradient_checkpointer.py……陆鸣随便打开了一个文件。classShardedModelPipeline: Implements model parallelism with automatic sharding. Supports pipeline parallelism with microbatch scheduling. def__init__(self,world_size,micro_batch_size1):self.world_sizeworld_size self.micro_batch_sizemicro_batch_size...他能看懂一些关键词——model parallelism模型并行microbatch微批次。他在盒子的课程里听说过这些概念当模型太大无法放进单个GPU的内存时就需要把模型切分到多个设备上。但具体的实现细节像一座大山压在他的认知边界上。他没有退缩。他开始一行一行地读。读不懂的地方他就问盒子。盒子就像一个耐心的导师解释每一个术语、每一段逻辑、每一个设计决策背后的考量。凌晨三点他读完了分布式训练的概览。他知道了数据并行和模型并行的区别知道了环形全归约Ring All-Reduce算法知道了同步训练和异步训练各自的优缺点。他的知识星图在便携终端上爆炸式地扩张。那些光点不再是散落的星星而是连成了星座星座连成了星云星云连成了银河。每一片区域都有名字线性代数、微积分、概率论、Python、NumPy、机器学习基础、神经网络、反向传播、优化算法、分布式训练……他不再是废物。他甚至不再是一个“初学者”。他已经站在了AI工程师的门槛上一只脚已经迈了进去。盒子在凌晨四点发出了一条消息“第五章完成情况Python基础100%、NumPy数组与向量化100%、神经网络从零实现95%、读懂工业级代码30%。综合评分B。”“剩余课程进度45%。”“用户心肺功能监测心率偏高血压偏高建议休息。”陆鸣没有休息。他合上盒子走到窗边看着净土地的夜色。电磁屏障的蓝光在天幕上投下淡淡的光晕像极光一样柔和。能源核心的排热口吐着稳定的红光像大地的心跳。他掏出便携终端点亮了知识星图。那些密密麻麻的光点每一个都是他亲手点亮的——不是靠天赋而是靠笨功夫。一遍看不懂就看两遍两遍看不懂就看十遍。他用的是在垃圾堆里翻东西的劲儿——不放弃不跳过一个一个地分辨。“你还不睡”门口传来沈莜的声音。她披着一件旧外套手里拿着一个饭盒。“你怎么来了”“老赵说你在这儿通宵让我送点吃的。你不是说要终身免费粥票吗先拿这个顶一下。”她把饭盒放在桌上打开盖子。里面是粥还有两块腌萝卜——净土地的奢侈品。陆鸣坐下来慢慢地喝粥。萝卜的咸味在舌尖扩散带着一种久违的、像“家”的味道。“沈莜。”“嗯”“你说一个人如果突然变得……不一样了是因为他本来就有那个潜力还是因为外界逼的”沈莜靠在门框上想了想。“我觉得潜力这东西就像垃圾堆里的铜。你不去翻它永远埋在底下。但翻的人多了总有人能翻到。你不是突然变聪明了你是终于开始翻了。”她说完就走了。陆鸣喝完粥把饭盒洗干净放在工作台上。他看了一眼那块写着“废物回收与AI咨询”的招牌——那是他打算以后开的店。招牌还没有做但名字已经想好了。他躺在那张硬邦邦的折叠床上盒子和书并排放在枕头边。闭上眼睛之前他看到了知识星图上最后一颗刚刚亮起来的星。它的标签是“分布式训练——Ring All-Reduce 算法原理。”他还远远没有学完。但至少他知道自己该往哪个方向走了。第五章 · 完本章知识清单Python基础变量、数据类型、列表、字典、控制流if/for/while、函数定义与调用NumPy核心ndarray多维数组、向量化运算、数组切片、矩阵乘法或dot数据预处理图像归一化、训练/测试集划分、批次迭代神经网络前向传播线性变换 Z WXbReLU激活Softmax输出损失函数交叉熵——衡量预测分布与真实分布的差异反向传播核心链式法则应用于神经网络各层计算梯度参数初始化He初始化针对ReLU打破对称性优化算法小批量随机梯度下降SGD学习率调度训练循环迭代epoch、批处理、打乱数据、记录损失与准确率工业级AI的扩展分布式训练数据并行、模型并行、混合精度、梯度累积编程作业建议读者可自行尝试实现本章描述的MNIST分类器在自己的电脑上运行需要Python和NumPy尝试调整隐藏层大小、学习率、batch size观察准确率变化尝试添加第二个隐藏层变成两层隐藏层观察效果下一章预告第六章《从感知到认知》陆鸣将进入深度学习的高级主题卷积神经网络CNN和循环神经网络RNN。CNN用于图像识别RNN用于序列数据如文本、时间序列。他将在净土地的实际问题中应用这些模型——识别废墟中的危险机器类型CNN以及预测能源核心的负载变化RNN。同时他将首次接触“预训练模型”的概念了解迁移学习如何让AI在数据稀缺时也能发挥作用。

更多文章