从零实现神经网络:反向传播与AI工程实战
发布时间:2026/10/1 12:34:07来源:尧图网络
1. 为什么“从零开始”反而是AI工程最快的路我在AI圈子里待了七八年见过太多“调包侠”的尴尬瞬间模型上线后效果崩了翻遍文档也找不到原因框架版本一升级老代码直接报废面试时聊框架头头是道一追问底层原理就露馅。这些问题的根源都一样——只学会了“用”没学会“造”。“ai-engineering-from-scratch”这个名字说白了就是一个把AI工程从地基开始重新走一遍的学习项目。不是让你去复现一个完整的TensorFlow也不是让你把论文里所有公式都徒手推一遍而是把AI系统里最核心的几块骨头——数学基础、经典机器学习算法、神经网络的前向与反向传播、模型训练的数据流与调参逻辑、部署与监控的工程闭环——用最朴素的方式亲手实现一遍。为什么“from scratch”在AI工程里这么重要因为AI工程跟传统软件工程有个本质区别传统系统你调一个API行为基本可预期但一个AI模型的行为是不可预期的它是由数据、损失函数、优化器、超参数共同塑造出来的。你如果不理解这些部件是怎么协同工作的出了问题连排查的方向都没有。举个例子训练一个神经网络loss收敛到0.5就再也下不去了。懂反向传播的人会去查梯度是否消失、学习率是否过大、数据标签是否有噪声不懂的人只能靠“调参碰运气”。这两者的差距就是工程师和调包侠的差距。这个项目适合谁三类人一是刚入门机器学习、想系统建立知识体系的新手二是已经会用sklearn、PyTorch调包但对底层原理一知半解的进阶者三是做AI工程落地训练、部署、监控时经常被各种玄学问题折磨的从业者。说白了只要你想从“会用AI”升级到“懂AI、能掌握AI”这个路线都值得走一遍。2. AI工程的知识栈从数学到代码的完整拼图2.1 数学不是门槛是地图一提到数学很多人先怂了。实际上AI工程真正高频使用的数学远没有大家想得那么高深。线性代数里最重要的就是矩阵乘法和向量运算因为神经网络的每一层本质上就是“输入特征×权重矩阵偏置”微积分里最重要的是导数和链式法则因为反向传播就是链式法则在计算图上的系统应用概率论里最重要的是分布、期望和似然因为损失函数本质上是“当前参数产生这批数据的概率有多大”。我强烈建议在学习AI工程时把数学放到“工具”位置而不是“先修课”位置。什么意思当你手写逻辑回归时你会发现你需要对损失函数求偏导于是你顺手复习一下导数的链式法则当你手写神经网络时你需要理解梯度是怎么一层层传回去的于是你认真地啃一遍向量对向量的求导。这种“遇到问题→补数学→解决问题”的路径比先花半年刷完同济版高数再回来学机器学习有效得多。数学在AI工程里不是一座山而是地图上的一个个路标你走到哪里需要哪里它自然会出现。2.2 经典机器学习算法是AI工程的最小可行闭环很多人一上来就想搞深度学习觉得LR、决策树这种“老古董”没意思。但说实话经典机器学习算法才是一个完整的AI工程实践机会。拿逻辑回归来说它只有两层运算线性加权zwxb加上一个sigmoid压缩pσ(z)。你要一个人亲手从零实现逻辑回归他需要完成整条链路构造损失函数二分类交叉熵、推导梯度对w和b求偏导、实现梯度下降更新、做数据归一化、在测试集上评估。这条链路麻雀虽小五脏俱全比任何花哨的深度学习教程都更能让人理解“机器学习系统到底在做什么”。我当时学习的时候用numpy手写了线性回归、逻辑回归、朴素贝叶斯和一个小型决策树。虽然今天这些算法一行sklearn就搞定了但手写的过程让我建立了“数据→模型→损失→优化→评估”这个完整的思维闭环。后面上手神经网络时会发现一切都是这个闭环的扩展更复杂的模型结构、更高级的优化器、更精细的评估方式但骨架并没有变。这也是“from scratch”最重要的价值——你不是在学孤立的知识点而是在搭建一张互相连接的知识网。2.3 神经网络的反向传播值得亲手推一遍如果说经典算法是地基那反向传播就是AI工程的承重墙。反向传播背后的数学其实只有一句话对损失函数关于每一层参数的梯度沿着计算图从后往前链式地求导。但这句话落到代码里有一堆魔鬼细节——广播broadcasting导致的维度不匹配数值稳定性导致的梯度爆炸激活函数饱和导致的梯度消失。你不亲手写一遍这些坑就永远只能停留在“知道概念”的层面。我见过太多人在PyTorch里跑transformer跑得飞起但对backward到底怎么把梯度传到每个参数上完全没有概念。这不怪他们——现代深度学习框架把这些都封装得太好了好到很多人以为loss.backward()是魔法。但工程和魔法之间的区别是什么魔法出了问题你只能祈祷工程出了问题你可以定位、修复、预防。手写一遍反向传播目的就是为了破除对框架的“魔法滤镜”让你在真实项目里遇到梯度问题时不是瞎猜而是准确地判断出问题可能在计算图的哪一段。3. 亲手实现一个微型神经网络完整实操拆解3.1 准备数据造一个“非线性分类”的场景为了把原理和流程讲透我用一个非常经典、又足够直观的任务来演示用两层神经网络手工实现一个非线性二分类器。数据点分布在一个平面上两类数据分别呈“月牙形”交错分布就像两个弯弯的月亮互相咬合。这种分布线性分类器无法处理正适合展示神经网络的非线性表达能力。第一步生成数据。我们自己造数据用numpy就能完成import numpy as np def make_moon_data(n_samples1000, noise0.1, random_state42): np.random.seed(random_state) n_half n_samples // 2 t np.linspace(0, np.pi, n_half) x1 np.vstack([np.cos(t), np.sin(t)]).T * 1.0 x2 np.vstack([np.cos(t np.pi), np.sin(t np.pi)]).T * 1.0 X np.vstack([x1, x2]) y np.hstack([np.zeros(n_half), np.ones(n_half)]) # 加入噪声让任务不那么“作弊” X np.random.randn(X.shape[0], 2) * noise # 打乱顺序,以保证训练时样本不按类别排列 perm np.random.permutation(n_samples) return X[perm], y[perm]生成之后把数据划分为训练集和测试集80%训练、20%测试。注意划分之前要先打乱。样本按类别排列时如果分层抽样没做好训练集和测试集的数据分布可能会出现偏差。这属于数据工程的基础细节但很多新手在这里就栽过跟头。然后做特征归一化。神经网络对输入特征的尺度非常敏感量纲不一致的特征会让梯度更新变得震荡甚至发散。把每个特征缩放到均值为0、标准差为1的区间是最稳妥的做法def normalize(X): mean X.mean(axis0) std X.std(axis0) std[std 0] 1.0 # 防除零 return (X - mean) / std X_train_norm normalize(X_train) X_test_norm normalize(X_test) # 注意测试集必须用训练集的均值和标准差这个细节值得多说一句归一化的参数mean和std只能用训练集算然后直接应用到测试集上。如果你在测试集上单独再算一遍均值标准差数据分布的信息就泄露了测试集的评估结果会虚高。这是数据工程里一个常见但很隐蔽的错误。3.2 网络结构与参数初始化的讲究我们要实现的是一个两层全连接网络输入层2个神经元两个特征、隐藏层16个神经元、输出层1个神经元。隐藏层用tanh激活函数输出层用sigmoid激活函数得到的就是二分类的概率输出。为什么激活函数要选tanh而不是ReLUtanh的输出范围是[-1, 1]而且关于原点对称。数据从输入层进来经过线性变换后如果直接进ReLU所有负值都会被置零这对隐藏层的表示能力有影响。在小型网络上tanh的对称性通常能让收敛更平稳。ReLU在深层网络上更常用因为它能缓解梯度消失但在这种浅层任务里两者差异不大tanh反而更直观、更好理解。参数初始化这里很容易被忽略。如果权重初始值太大经过tanh的时候神经元会直接进入饱和区梯度接近0网络几乎不动如果全部初始化为0那所有神经元在训练时会同步更新学习不到差异化的特征。一个简单实用的初始化方法从均匀分布中随机取值范围是[-1/sqrt(n_in), 1/sqrt(n_in)]。n_in是上一层的神经元数量。这个“缩放”的直觉是上一层神经元越多加权求和的分布就越宽为了控制方差初始权重的范围就该越小。def initialize_parameters(n_input, n_hidden, n_output): W1 np.random.uniform(-1.0/np.sqrt(n_input), 1.0/np.sqrt(n_input), (n_hidden, n_input)) b1 np.zeros((n_hidden, 1)) W2 np.random.uniform(-1.0/np.sqrt(n_hidden), 1.0/np.sqrt(n_hidden), (n_output, n_hidden)) b2 np.zeros((n_output, 1)) return W1, b1, W2, b2刚才写代码时为什么把b1、b2初始化成全零因为偏置的梯度不受上一层神经元数量的方差影响全零初始化不会像权重那样带来对称性问题反而能保证初始阶段每个神经元的出发状态一致便于观察收敛过程。3.3 前向传播与损失函数模型终于像样了前向传播是把输入数据从输入层经过隐藏层流向输出层的过程。两层的计算分别是def forward(X, W1, b1, W2, b2): # X: (n_features, n_samples) 注意这里用列向量表示样本 Z1 np.dot(W1, X) b1 # (n_hidden, n_samples) A1 np.tanh(Z1) # 隐藏层激活 Z2 np.dot(W2, A1) b2 # (1, n_samples) A2 1.0 / (1.0 np.exp(-Z2)) # sigmoid输出是概率 return Z1, A1, Z2, A2关于样本的表达方式这里有个工程细节当前用(n_features, n_samples)的列向量排列。这种做法的好处是矩阵乘法里的维度刚好顺着“权重矩阵×激活矩阵”的顺序走计算图上更自然但它和大多数人习惯的(n_samples, n_features)行向量排列不同。你只要选定一种整个前后向都保持一致即可否则维度错乱非常难排查。我自己的习惯是看哪边的代码读起来更顺手在这个例子里列向量排列写起来更清晰。损失函数用二分类交叉熵BCE。它的公式是L -(1/N) * sum(y * log(p) (1-y) * log(1-p))为什么分类问题用交叉熵而不是均方误差MSE因为sigmoid把输出压到(0,1)区间后MSE的梯度在输出接近0或1时趋近于0训练会变得很慢而交叉熵的梯度在概率偏离真实标签时很大在接近标签时变小天然匹配概率分布之间的距离度量。这也是为什么真实项目里分类任务几乎清一色用交叉熵。代码实现时一定要做数值稳定性处理。log(1-p)在p接近1时直接算会得到接近-log(0)的无穷大并且反向传播时1-p也可能为0导致除零。我用一个clip操作把概率截断在[1e-12, 1-1e-12]这样既保持了数学上的正确性也避免了数值崩溃。3.4 反向传播把梯度一行行地算出来反向传播是整个实现的灵魂。它的核心思想就是链式法则损失函数L对输出层权重W2的梯度等于L对输出A2的梯度乘以A2对Z2的梯度再乘以Z2对W2的梯度而W1的梯度还需要在此基础上继续往回流穿过隐藏层。先把损失函数对输出A2的梯度算出来。对BCE损失求导后巧妙的简化结果是dL/dA2 -(y / A2) (1-y) / (1-A2)sigmoid函数的导数是A2 * (1 - A2)这两个乘到一起再继续往后传就得到delta2 A2 - y这是交叉熵加sigmoid组合的一个便利性质损失对输出层线性加权和Z2的梯度恰好等于预测概率减去真实标签。这让代码非常清爽推导过一次之后你会有种“原来如此”的通透感。继续往隐藏层传播dL/dZ1 (W2^T · delta2) * tanh’(Z1) tanh’(Z1) 1 - A1^2然后对参数求梯度dL/dW2 delta2 · A1^T dL/db2 sum(delta2) dL/dW1 delta1 · X^T dL/db1 sum(delta1)落到代码上def backward(X, y, Z1, A1, Z2, A2, W2): m X.shape[1] # 样本数 delta2 A2 - y.reshape(1, -1) dW2 np.dot(delta2, A1.T) / m db2 np.sum(delta2, axis1, keepdimsTrue) / m delta1 np.dot(W2.T, delta2) * (1 - A1**2) dW1 np.dot(delta1, X.T) / m db1 np.sum(delta1, axis1, keepdimsTrue) / m return dW1, db1, dW2, db2注意这里除以m我们的损失函数算的是平均损失梯度也应该是每个样本梯度的平均值。如果不除以样本数梯度大小就会随batch size变化学习率就失去了可解释性。反向传播代码写完后我建议用一个极其简单的样例验证它随机生成一个(2, 4)的小批量数据把前向和反向跑一遍后再用数值梯度法finite difference去核对每个参数的手工梯度。如果你手工推的梯度没错数值梯度和解析梯度的误差应该在1e-7量级。这个验证习惯非常值得保留哪怕以后用PyTorch用torch.autograd.gradcheck验证自定义算子也是同样的思路。3.5 训练循环和可视化看见模型在学习梯度算出来之后训练循环本身并不复杂但有几个超参数需要解释。learning_rate 0.5 n_epochs 1000 batch_size 64为什么learning_rate取0.5这个值是我试出来的。大一点loss曲线会呈锯齿状剧烈震荡小一点网络可能要几万轮才能收敛。0.5在这个小网络归一化数据tanh激活的组合下刚好既能快速下降又不发散。学习率的本质决定了每次参数更新的步长它和梯度本身是乘性关系。步长太大参数会跨过最优区域甚至震荡发散步长太小收敛慢到让人怀疑人生。真实项目里可以配合学习率衰减策略动态调整但手写这个阶段用固定学习率就足够观察规律了。训练循环的写法非常传统# 简化版训练循环 for epoch in range(n_epochs): # 每个batch for i in range(0, m_train, batch_size): X_batch X_train_norm[:, i:ibatch_size] y_batch y_train[i:ibatch_size] Z1, A1, Z2, A2 forward(X_batch, W1, b1, W2, b2) dW1, db1, dW2, db2 backward(X_batch, y_batch, Z1, A1, Z2, A2, W2) W1 - learning_rate * dW1 b1 - learning_rate * db1 W2 - learning_rate * dW2 b2 - learning_rate * db2 if epoch % 100 0: loss compute_loss(X_train_norm, y_train, W1, b1, W2, b2) print(fepoch {epoch}, loss {loss:.4f})观察loss曲线能看出很多东西。如果第100个epoch时loss还在0.6以上说明学习率太小或者网络容量不够如果loss在第几个epoch突然变成nan很可能是梯度爆炸了。我在实际跑这个例子的过程中就遇到过一次因为权重初始化范围没控制好前向传播的Z2值过大导致sigmoid输出饱和到1附近然后交叉熵的梯度过大参数一步更新直接飞掉的情形。这时候要用数值梯度法或者打印每一层的梯度和激活值统计来定位问题。训练完成后模型在测试集上的准确率应该能到0.9以上。你可以画一下决策边界把一个平面的二维网格丢进训练好的网络把输出大于0.5的区域标成类1其余标成类0会看到一条弯曲的分界线完美地把两个半月牙分开。看到这条曲线的瞬间你对“非线性模型”的理解就跟背概念完全不同了——你亲眼看着一堆矩阵乘法和激活函数自动拟合出一条复杂边界这种“掌控感”是调包永远不会给你的。4. 从模型到工程AI系统的生产化落地4.1 数据工程的权重永远比模型大手写一个模型是一回事把它变成生产系统是另一回事。我在实际项目里见过太多团队花80%的时间调模型结果上线后效果不如一个精心设计的简单规则。为什么因为真实系统中的数据永远是脏的。在AI工程里数据工程的比重实际占了70%以上。所谓“AI工程”大头不是写模型代码而是管数据。数据质量差再好的模型也白搭。数据质量检查至少要做这几件事遍历字段缺失率缺失率超过阈值的字段要么填充要么舍弃检查类别特征的基数cardinality高基数的类别要处理做分布一致性检查训练集和线上数据的分布差异是否在可控范围内对异常值做截断或标记避免单条极端数据拉偏整个模型。真实系统里数据不是躺在静态文件里的而是持续流动的。这也是“特征存储”和“数据版本管理”在AI工程里越来越受重视的原因。特征是训练时和推理时共用的如果训练代码用A逻辑处理特征、线上推理服务用B逻辑处理模型效果必然漂移。把特征计算逻辑统一在一个库中训练和推理都调用同一份代码这是AI工程里最基本的防脑裂手段。4.2 训练实验与版本管理可复现是AI工程的命门在真实工程中“这个效果很好的模型是哪次实验跑出来的”这个问题能逼疯一屋子人。我见过太多团队训练一个模型环境依赖装完就再也复现不出来了。不是因为他们不努力而是因为AI训练的可复现性本身就是一个系统工程难题Python依赖的版本、GPU驱动、随机数种子、数据集的快照任何一环变了结果就不一样。解决思路分三层第一层是环境管理。用容器比如Docker把跑模型的服务器环境、依赖库、操作系统版本全部固化下来不让训练环境“漂浮”在某个人的笔记本上。这一步是基础中的基础我在任何团队都会强制要求。第二层是记录实验参数。每次训练时把超参数学习率、batch size、网络层数、优化器选择等、数据路径、代码commit号、训练时长全部记录到实验跟踪工具里。推荐MLflow或者Weights Biases前者开源免费后者界面更漂亮。记录这部分信息是为了事后能回答“为什么这个实验效果好”和“怎么复现它”。第三层是数据和模型的版本化。数据要完整复制一份快照再训练不要引用了原来路径后来被改动。模型训练好后要保存在模型仓库里标注好使用的数据版本、代码版本、评估指标方便后续回溯。这三个层次的搭建就是“AI工程化”里“化”字的含义——把单次成功变成稳定能力。4.3 部署与监控模型上线只是开始模型训练好、评估达标不是终点而是起点。部署阶段最常见的问题是把模型当作普通软件部署建一个HTTP服务请求进来模型算个结果返回就完事了。但AI系统有一个软件工程没有的特殊属性——它的行为会随着时间漂移。训练时使用的数据分布和线上真实数据分布不同用户行为变了数据变了模型的效果就会下降。这就是数据漂移data drift问题。监控AI系统的指标至少要设计三层第一层是基础设施指标请求量、延迟、错误率、GPU/CPU占用率第二层是模型在线指标预测置信度的均值/方差、预测类别的分布、用户反馈的转化率第三层是数据漂移指标对比线上输入特征分布与训练集特征分布的距离比如PSI、KS统计量。我建议把监控的落地分成两步走。第一步是上线初期先跑一段时间的shadow模式线上流量同时进旧模型和新模型但只让旧模型返回结果。新模型的预测输出只记录不生效。这一步能积累新模型在真实流量上的表现数据没有风险。第二步是正式灰度发布从小流量开始逐步放量同时盯紧监控面板上的指标一旦异常立即回滚或拉停。这个过程通常被称为“金丝雀发布”在AI系统里尤其重要因为模型的效果在离线评估指标上看着再好线上就是可能出现你没想到的情况。4.4 评估与反馈闭环把模型变成持续进化的系统AI系统上线后的持续迭代依赖的是一个扎实的反馈闭环。这是AI工程和传统软件工程最不一样的地方传统软件发布一个功能功能是确定性的AI模型发布后它还会继续“学习”吗离线评估环节要考虑清楚几个问题用什么指标评估准确率够不够在类别不均衡场景下准确率本身是骗人的——每100个样本只有1个正例一个全预测为负的模型准确率也有99%。这时候要看精确率precision、召回率recall和F1分数。二分类任务里AUC也是一个经典指标它衡量的是模型区分正负样本的能力不受分类阈值影响。线上评估就更有意思了。模型上线后到底有没有效果提升最严谨的做法是A/B测试把流量随机分成对照组和实验组对照走老模型或规则系统实验走新模型跑一段时间后比较业务指标点击率、转化率、留存率等的差异再用统计学假设检验判断这个差异是不是显著的。在AI工程实践里“评估”从来不是一个环节而是一个持续的过程。数据在变、用户在变、业务目标在变模型的价值只有在持续评估和迭代中才能被守住。这也是为什么我坚持认为做AI工程的人不能只会训练模型还要具备搭建整套监控-评估-迭代体系的能力。5. 新手上路最常踩的坑学习路径与实战心得5.1 三个典型误区我把这些年带过的新人总结了一下发现最容易走弯路的基本是这三类误区。第一个误区是“只学框架不学原理”。一上来就怼PyTorch和Transformer跑通了几个公开的demo就觉得自己会AI了。实际上demo跑通本身没有说服力因为数据、环境、脚本都是现成的你只是把“运行”这个动作做了一遍。一旦要自己解决一个新问题模型架构怎么选、数据怎么预处理、loss为什么爆炸、效果为什么不好全都答不上来。手写一遍反向传播这个问题比跑通十个demo更能说明你掌握了AI工程的基础能力。第二个误区是“只看理论不写代码”。买了五六本书学了一堆公式从线性代数到概率论却连一个完整的训练循环都没跑通过。AI工程是工程工程的衡量标准是能工作、能维护、能复用。我在面试中遇到很多候选人讲复杂模型头头是道一让他现场写一个简单的梯度下降更新就卡住了。理论知识是重要的但它必须附着在代码实践上才有工程意义。第三个误区是“东一榔头西一棒子”。今天看一篇关于CNN的文章明天学一下RNN后天又研究一下强化学习。这种“热词驱动”式学习会让你长期停留在“了解”层面无法形成系统性的知识结构。真正有效的路径是选定一个主路线比如从经典机器学习到神经网络再到深度学习沿着主线纵向深入横向扩展只在主线需要时才发生。5.2 我建议的学习路线如果要给刚进入AI工程领域的朋友一个可复制的路线我的建议是这样的第一步掌握Python和numpy达到能熟练操作矩阵和向量运算的水平。这一步如果基础不扎实后面所有代码都会卡壳。第二步从零实现经典机器学习算法——线性回归、逻辑回归、决策树、K近邻。每个算法都走一遍“数据准备→模型定义→损失函数→梯度计算→训练评估”的闭环。这个过程中不要用sklearn所有代码基于numpy手写。这一步的价值是建立对机器学习全流程的肌肉记忆。第三步从零实现一个两层神经网络。重点把反向传播的每一个梯度算明白用数值梯度法验证正确性。这一步如果做扎实了后面学任何深度学习框架都会高效得多。第四步用PyTorch替代手写实现掌握框架的建模方式但每一次用到一个新概念自动求导、优化器、数据加载器都要想一想它在手写实现中对应的是哪一步。这样框架才不会变成黑盒。第五步做完整的AI工程项目数据收集与清洗、特征工程、模型训练与调参、实验记录、部署成HTTP服务、设计监控指标。选一个业务场景比如房价预测、点击率预估、情感分析把整个链路都走通一遍。门槛从“训练出一个模型”升级到“系统可靠地运行模型”这一步才是真正的“ai-engineering”。5.3 实操过程里最有价值的几次踩坑手写神经网络那次我踩过一个特别经典的坑梯度方向反了。当时所有损失在下降但降到一定程度就横盘网络一直没有更高的准确率。我查了很久才发现是因为我在梯度下降更新时写成了W1 learning_rate * dW1——方向完全反了。这种bug用常规的经验很难发现因为loss确实在下降只是网络在往“错误方向的局部最优”走。从那之后我养成了一个习惯在训练循环里加上“梯度方向检查”用数值梯度拉出一个样本的梯度方向和解析梯度的方向做比对。还有一个印象深刻的坑是关于tanh激活函数在中间层的。tanh的导数范围在(0,1]之间当网络深度增加时逐层乘出来的梯度会指数衰减导致靠近输入的层几乎学不到东西。当时我用三层的网络跑一个中等幅度的任务中间层的权重更新变得非常慢loss曲线呈现出“迟迟不动突然好一点点”的诡异形态。排查后发现Z1的分布太宽tanh大部分时间都处于饱和区梯度传不过去。解决方法是更小尺度的权重初始化以及把输入特征的方差压得更小。这个坑对我的启发是梯度问题的排查永远是先看激活值的分布再看梯度的统计量最后才考虑学习率。手写代码的过程里我还养成了打印中间层激活值习惯。每跑一个epoch就输出一次A1的均值、标准差、最小值和最大值。这比只看loss曲线更能直观地看到网络内部发生了什么如果A1的标准差在逐渐变大说明激活开始饱和梯度可能要出问题如果A1的标准差趋于0说明神经元死掉了可能是初始化不对或者梯度消失。这个习惯直到现在用大模型训练框架时我都保留着。5.4 最后一个小建议如果你决定走“from scratch”这条路不要急着追求“复现一个transformer”或“手写一个大语言模型”。那些目标虽然听起来很酷但工程上并不适合作为起点。先把手写一个小而美的神经网络这件事做到极致把数据流、梯度计算、超参数调整这些基本盘打牢高级的东西都是这些基本盘的组合和扩展。有一次我和一个做了十年推荐系统的工程师聊天他说自己最庆幸的就是刚入行时花三个月时间手写了全部经典算法之后十几年遇到任何新模型拿过来一看心里都有底。这种“有底”的感觉才是“ai-engineering-from-scratch”能给你最值钱的东西。
网站建设高端定制企业官网