拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零搭建AI工程:环境、数据、训练到部署的全链路实践

从零搭建AI工程:环境、数据、训练到部署的全链路实践

刚入行做AI工程的时候,我一度陷入一个误区:以为把PyTorch官网的教程跑通,再调一调别人给的模型代码,就算入门了。直到真正接手一个从零启动的项目才发现,模型训练只是冰山一角,数据怎么管、特征怎么对齐、训练和推理的gap怎么处理、模型上线后怎么监控,这些才是实打实的日常工作。也正是那段经历让我意识到,所谓AI工程,核心从来不止是“搭个网络跑个分”,而是从问题定义到系统落地的完整闭环。

这个ai-engineering-from-scratch项目,本质上是我把“从零开始做AI工程”这件事完整走了一遍的记录与沉淀。它回答的不是“怎么调用某个框架”,而是“如果一切从零开始,你应该怎么思考、怎么选型、怎么搭出一套可用的、可持续迭代的AI系统”。今天这篇就把其中最关键的部分拆开揉碎,从环境选型到数据工程,从模型训练到部署监控,全程干货,希望对正在走这条路的朋友有帮助。

1. 项目概述:从零开始做AI工程,到底在做什么

1.1 核心需求解析:为什么“从零开始”这么重要

很多人会问,现在框架这么成熟,预训练模型随处可下载,为什么还要强调从零开始?我的理解是,“从零开始”的价值不在重复造轮子,而在建立完整的心智模型。当你亲手从数据处理、模型搭建、训练循环到部署监控走完一遍,再去用那些高阶工具时,你才知道每个抽象层背后到底发生了什么。

比如你用PyTorch写一个model.fit()风格的高级API很容易,但一旦遇到梯度异常、loss不降、显存溢出这类问题,不懂底层原理的人往往无从下手。而在from scratch的实践过程中,你会理解每一行代码在数学上对应什么,这样排查问题会快得多。

这个项目适合下面几类人:

  • 刚入门AI、想系统建立工程视角的开发者;
  • 有一定基础但只会调包,想补足底层原理的工程师;
  • 需要在真实业务中从零搭建AI系统的技术决策者。

1.2 项目蓝图:一套完整的AI工程链路

这个项目的整体链路我设计为五层:环境层、数据层、模型层、训练层、部署层。每一层都有几个关键决策点,我在实践过程中一一踩过坑,后面会分别展开。

层级核心任务关键决策点
环境层开发环境与依赖管理Python版本、CUDA、包管理工具
数据层数据获取与特征工程数据来源、清洗策略、特征设计
模型层网络结构与损失函数模型选型、损失函数设计
训练层训练循环与调优优化器、学习率、正则化
部署层模型服务与监控部署方式、推理优化、监控指标

这五层不是孤立的,比如数据层的特征设计会直接影响模型层的结构选择,训练层的调优结果又会反过来暴露数据层的问题。实际做的时候,我建议不要像瀑布流一样走完一层再走下一层,而是先跑通一个最小闭环,再逐渐完善每一层。

2. 环境与工具链选型:打好地基才能走远

2.1 Python环境管理:从入门就养成好习惯

做AI工程第一步不是pip install,而是先解决环境隔离问题。我早期吃过这个亏:全局环境里装了一堆包,版本互相冲突,最后不得不花一下午排查依赖问题。现在我的做法是使用conda或venv为每个项目建独立环境。

实际操作上,我比较推荐用conda管理Python版本和CUDA相关的底层依赖,因为有些科学计算包用pip装容易出诡异的问题。比如我曾经在Ubuntu系统上用pip装PyTorch,结果装成了CPU版本,训练起来慢到怀疑人生,后来用conda install pytorch配合CUDA toolkit版本对齐才解决。

一个实用的环境创建流程:

conda create -n ai-engine python=3.10 -y conda activate ai-engine conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia pip install numpy pandas scikit-learn matplotlib

这里的核心在于CUDA版本的匹配。你要先查出自己显卡支持的CUDA版本,再看PyTorch哪个版本与之对应,这步错了后面模型根本跑不起来。查显卡算力最简单的方式是在终端输入nvidia-smi,看右上角的CUDA Version。

2.2 从NumPy手写实现到框架介入:循序渐进的学习路径

很多人一上来就用PyTorch的nn.Module写模型,我觉得这有点过早。在from scratch的语境下,我强烈建议先试着仅用NumPy实现一个最简单的线性回归和逻辑回归。这个过程会让你理解:张量是什么、前向传播在做什么、梯度从哪来。

等我用NumPy手写了反向传播之后,再去用PyTorch时会有一种豁然开朗的感觉——原来loss.backward()和optimizer.step()背后就是那些我在手写代码里一步步算过的东西。有了这个基础,后面遇到梯度消失、梯度爆炸时,你脑子里不是一团浆糊,而是有一个清晰的信号传递路径。

不过我也不是让大家什么都要手写。工业实践中,该用框架就要用框架,该调现成模型就调现成模型,只是这些都应该建立在对底层有一定理解的基础上。理解之后再封装,和不懂直接封装,出问题的排查效率是天差地别的。

3. 亲手实现一个神经网络:从数学到代码的突破

3.1 核心组件拆解:线性层、激活函数与损失函数

顺着从零开始的思路,我建议从分类任务入手,因为任务直观、评估简单、调试容易。比如经典的二分类问题:根据两个特征判断类别。

第一步是实现线性层。虽然PyTorch里有现成的nn.Linear,但我建议自己动手写一下:

class Linear: def __init__(self, in_features, out_features): self.W = np.random.randn(in_features, out_features) * 0.01 self.b = np.zeros((1, out_features)) def forward(self, x): self.x = x return np.dot(x, self.W) + self.b def backward(self, grad_output): self.grad_W = np.dot(self.x.T, grad_output) self.grad_b = np.sum(grad_output, axis=0, keepdims=True) self.grad_input = np.dot(grad_output, self.W.T) return self.grad_input

这段代码看似简单,但里面有三个关键设计:权重初始化用小的随机数(防止梯度消失或爆炸)、保存输入供反向传播使用、(维度上)严格遵循矩阵乘法规则。这些都是我在调试中踩过坑才真正理解的。

第二步是激活函数。以ReLU为例,它最大的优势是计算简单且能缓解梯度消失问题,但这个简单的函数在反向传播时有个致命陷阱:当输入小于等于0时,梯度为0。这意味着一旦某个神经元的输入落入负区间,它后续基本不会再更新了,这就是所谓的“神经元死亡”问题。

第三步是损失函数。二分类一般用二元交叉熵。我见过很多初学者在这个地方踩坑:用MSE(均方误差)做分类任务的损失函数。理论上不是绝对不行,但实践中收敛慢且容易陷入局部最优,因为MSE对概率分布的距离度量并不友好。使用交叉熵时,还有个容易忽略的点:为了数值稳定性,一般会把softmax或sigmoid算子融合进损失函数,避免中间过程出现极端的指数计算。

3.2 反向传播的实现:梯度如何流动

反向传播是神经网络训练的“发动机”,我在手写实现后对它的理解才真正上了一个台阶。简单来说,链式法则是理论基础,而工程实现上则是“从输出端到输入端逐层传递梯度”。

如果你用自己的代码实现一次全连接网络的反向传播,就会看到一套非常对称的模式:每一层都要做三件事——计算参数梯度、计算传给上一层的梯度、保存必要的中间变量。这个模式在PyTorch中就是每个nn.Module的backward方法,只不过框架自动实现了。

为了检验反向传播写得对不对,有一个很实用的技巧:数值梯度检查。思路是给某个参数加上一个很小的值(比如1e-6减去1e-6),近似计算梯度,再和你的反向传播算出来的梯度对比。如果两者差异在万分之一以内,说明反向传播大概率没问题。

def numerical_gradient(f, x, eps=1e-6): grad = np.zeros_like(x) for i in range(x.size): x_plus = x.copy() x_plus.flat[i] += eps x_minus = x.copy() x_minus.flat[i] -= eps grad.flat[i] = (f(x_plus) - f(x_minus)) / (2 * eps) return grad

这股执拗让我后来少走了很多弯路,因为大部分框架层面的梯度问题,本质都是模型定义与反向传播路径的不一致,手动实现一遍你就能识别出那些隐藏的坑。

3.3 训练循环的完整实操:前向传播、损失计算、反向传播、参数更新

有了上面那些组件,把它们拼成一个完整的训练循环就是水到渠成的事。这里我用一个非常精简的代码示意来说明训练循环的骨架:

for epoch in range(epochs): for batch_x, batch_y in data_loader: # 前向传播 logits = model(batch_x) loss = cross_entropy(logits, batch_y) # 反向传播 grad_logits = loss_gradient model.backward(grad_logits) # 参数更新 for layer in model.layers: layer.W -= learning_rate * layer.grad_W layer.b -= learning_rate * layer.grad_b

这个循环看起来简单,但我后来在实际项目中发现一个很容易被忽视的点:每个epoch应该打乱数据顺序。如果不打乱,模型会在每个epoch内看到同样顺序的样本,容易学到数据中的顺序伪相关。我第一次没做shuffle时,训练集准确率上升很快但验证集表现很差,排查了很久才发现是顺序问题。

学习率的选择也需要细心。如果设成0.1,可能不收敛甚至发散;设成0.001,又可能训练太慢。我的经验是先用对数坐标扫几轮,比如尝试0.1、0.01、0.001、0.0001,观察loss曲线的下降趋势,然后基于这个区间再细化搜索。

3.4 从两层网络到深度模型:激活函数选择的连锁反应

当我们把网络从单层加深到两层、三层时,激活函数的选择就会带来非常显著的影响。如果全程用ReLU一般还好,但如果用了sigmoid或tanh,深层网络很容易出现梯度消失——前面的层几乎学不到任何东西。

我在一个项目里试过将三层网络的隐藏层激活函数从ReLU换成sigmoid,结果就是loss在初始值附近龟速下降,训练了几个epoch基本没有改善。后来画了每一层权重的梯度分布图,发现越靠近输入的层,梯度绝对值越小,甚至趋近于0,这就是典型的梯度消失。

解决这个问题的常用手段包括:改用ReLU及其变体(LeakyReLU、ELU等)、加Batch Normalization、或者使用残差连接。刚开始做from scratch的时候不可能把这些全部实现一遍,但至少你要能理解为什么深度模型中ReLU族是主流。为了加深理解,我建议你自己实现一下LeakyReLU和Batch Normalization的前向反向,亲眼看梯度分布如何改善,这种经验比看多少文档都有用。

4. 数据工程管线搭建:真正决定上限的部分

4.1 数据采集与清洗:脏数据如何悄悄毁掉模型

我见过不少初学者把90%的精力放在调模型结构上,忽略数据的质量。但实际上,我做了几个项目后有一个强烈的体会:数据的质量直接决定了模型效果的天花板,模型结构只是在逼近这个天花板。如果你的数据本身混乱,再怎么调参都是浪费时间。

一个典型的例子:有一次我从日志系统里收集用户行为数据,看似量很大,但仔细检查后发现,有相当多的记录因为上游接口超时导致特征字段全部为空值,还有一部分样本的标签时间戳发生了偏移。如果在处理流程里不做清洗,模型就会莫名其妙学到一些不存在的模式,训练时loss也总降不下去。

常规清洗流程我认为至少应该包含下面几个环节:

  • 缺失值处理:区分“随机缺失”和“有偏缺失”,前者可以填充,后者很可能是数据采集链路出了问题。
  • 异常值检测:通过箱线图、Z-score等方法识别异常点。
  • 重复样本去除:特别要注意的是,看似不重复、实际特征完全一致的样本(比如同一用户在同一天多次请求但带的时间戳不同)。
  • 一致性校验:检查特征之间的逻辑关系,比如年龄特征和生日特征是否对应。

4.2 归一化与特征工程:让模型更容易学到规律

特征归一化听起来很简单,但处理不当会直接影响训练速度和最终效果。我最初给模型喂原始特征时,其中一个特征的取值范围是0到100000(比如金额),其他特征都在0到1之间,结果发现梯度更新很不稳定,loss曲线震荡得像过山车。

后来做了标准化处理,把每个特征减去均值再除以标准差,使其分布接近标准正态分布,训练就平稳多了。这是因为大多数优化算法(特别是基于梯度的)在对“尺度差异大”的损失曲面优化时,会遭遇条件数恶化的问题,说白了就是有些方向梯度大、有些方向梯度小,更新起来摇摆不定。

特征工程这块,我的建议是先做经典的处理:连续特征标准化、类别特征做编码、时间特征拆成周期性分量。然后再考虑是否要构造交叉特征或业务字段的衍生特征,这一步非常依赖领域知识,没有什么万能公式,多和业务方沟通比单纯堆特征更有效。

4.3 数据划分与数据加载器设计:训练/验证/测试的科学分割

数据划分是模型评估可信度的基础。我见过很多朋友用一个train_test_split把数据分成两个部分就开训了,这样其实是不推荐的。标准做法是分成训练集、验证集、测试集三份,训练集用于更新参数,验证集用于调超参和早停选择模型,测试集只在最终评估时用一次。

关于比例,如果数据量不大(比如一万条以内),我建议用70%、15%、15%的比例;数据量大了之后,可以加大训练集占比,比如98%、1%、1%,因为验证集和测试集只需要足够评估稳定性即可。

数据加载器设计也有讲究,尤其是在GPU训练时,数据读取往往是隐藏的性能瓶颈。我做过一个对比:用Python脚本几个for循环逐样本喂给模型,和用DataLoader批量加载并做多进程预取,训练速度的差距能达到5到10倍。因此,虽然from scratch阶段你可以自己写数据分批逻辑,但实际项目中务必选择框架自带的加载器并调整好num_workers和prefetch_factor等参数。

5. 模型训练与调优:从收敛到泛化的进阶之路

5.1 优化器选型与学习率策略:SGD、Adam还是别的

优化器选型是个既基础又关键的问题。作为一个具体建议,刚起步阶段直接用Adam往往是最保险的选择,因为它对学习率的敏感度较低,且自带自适应调节的能力。

但Adam也不是万能的。我后来在一个比较复杂的推荐模型上复盘时发现,模型在训练集上收敛得很好,验证集却总差那么一点。后来尝试换成SGD配合动量项,并加入合适的学习率调度策略,验证集的效果反而提升了不少。这个经历让我明白:Adam擅长快速找到一个不错的局部最优解,但在某些任务上SGD的“慢功夫”反而能探索到泛化更好的区域。

学习率调度策略方面,我最常用的是余弦退火和ReduceLROnPlateau。前者适合大模型从头训练,后者适合业务场景里的不定期调优。还有个我自己很喜欢的技巧:在训练开始的前几个epoch用较小的学习率“热身”(warmup),然后逐渐增大到目标学习率,最后再衰减,这种方式对稳定初期训练特别有效。

5.2 过拟合识别与应对:训练集分数高不等于模型好用

过拟合可能是深度学习实践中最普遍的问题,尤其是数据量不足的垂直领域。我判断过拟合时,一般盯着两条曲线的“剪刀差”——训练loss持续下降,验证loss先降后升,这就是过拟合的信号。

应对过拟合的手段有很多,我按照实用性排序会这么选:增加数据(包括数据增强)> 正则化(L2/权重衰减)> Dropout >早停 >简化模型结构。注意这里有个容易搞反的点:不要一上来就用Dropout,如果数据量本身足够,或者模型表达能力并不强,Dropout反而可能造成欠拟合。

早停是我个人认为性价比最高的技巧之一。实现起来也简单:每个epoch结束后在验证集上算一下loss,如果连续N个epoch(我常用5到10)没有刷新历史最低值,就停止训练并恢复到历史最佳参数。这不仅防止过拟合,还能节省大量训练时间。

5.3 超参数调优方法论:网格搜索、随机搜索与贝叶斯优化

超参数调优是个无底洞,如果毫无章法地蛮力试,你会陷入“调参调到头秃、效果纹丝不动”的窘境。我建议按照“从粗到细”的策略分层处理。

初期可以用网格搜索或随机搜索快速找到一个较好的超参数区域。网格搜索就是穷举几个超参数的笛卡尔积组合,简单直观但计算开销大;随机搜索则是在参数空间内随机采样,实践表明在相同预算下通常能比网格搜索找到更好的结果。

当范围缩小后,再用贝叶斯优化在重点区域精调。领域里常用的工具有Optuna和Hyperopt,他们的核心思想是通过高斯过程或TPE模型,参考历史评估结果指导下一步采样方向,比盲目搜索高效得多。

我自己习惯的做法是:先固定一个batch size,用随机粗搜找出合适的学习率和隐藏层规模区间;然后固定这些“大方向”,再用Optuna精调Dropout比例、权重衰减系数和学习率调度的具体参数。这样既不会对着草率的选择花费大量时间,也能保证搜索过程在可控的计算预算内。

5.4 训练过程的可视化与监控:loss曲线怎么读

训练过程中的可视化,说到底是帮你回答三个问题:模型在学吗?学得够快吗?有没有学歪?我一般至少绘制训练loss、验证loss、学习率(如果用了调度)、以及自定义指标(如准确率或F1)这几条曲线。

读曲线有下面几个常规经验:

  • loss在高位震荡且不下降,首先检查学习率是否过大、数据归一化是否正确;
  • 训练loss下降但验证loss不动,大概率过拟合;
  • loss出现突然跳高,多半是学习率设置不稳定或数据中混入了异常batch;
  • 如果loss下降非常缓慢但没有剧烈震荡,可以考虑加大学习率。

这里分享一个排查技巧。有一次我在训练时发现loss曲线突然从0.4跌到0.2再跳回0.4,反复出现奇怪毛刺,排查到通用工具包才发现是数据加载里混了少量重复样本导致的。所以当你看到异常的loss曲线时,不要只盯着模型,先怀疑数据,再怀疑代码,最后才怀疑算法,这条经验几乎适用于AI工程的所有环节。

6. 从模型到产品:部署与MLOps实战

6.1 模型导出与推理优化:从训练框架到高性能推理

训练完成后,模型并不会凭空变成一个线上服务。我见过一个典型的悲剧:线下用PyTorch测试单次推理几十毫秒,上了服务后变成几百毫秒,因为有人直接把整个训练环境和模型一起打包到了生产容器里,GPU显存同时被训练任务和推理任务占用,相互干扰。

正确的做法是单独把模型导出成适合推理的格式。对于PyTorch模型,可以用torch.jit.script导出TorchScript,或者导出为ONNX格式,再通过ONNX Runtime进行推理。这样做的好处是:摆脱训练框架依赖、推理引擎针对生产环境做了大量算子融合与内核优化、部署镜像体积也大幅缩小。

推理优化还有几招值得掌握:FP16半精度推理(在支持Tensor Core的GPU上能带来接近2倍的加速且精度损失通常可接受)、批处理(把请求攒起来一起过模型,显著提升吞吐量)、算子融合(合并计算图中连续的可融合算子,减少kernel启动开销)。

6.2 模型部署架构选型:在线服务、离线批处理还是边缘推理

部署架构取决于业务场景,没有“最好”,只有“最合适”。我按照请求实时性要求把常见场景分成三类:

  • 在线实时推理:比如推荐系统、风险控制系统,要求毫秒级响应。一般使用HTTP或gRPC服务,前置负载均衡,后端挂推理引擎。我常用做的是用FastAPI封装ONNX Runtime,配合多个replica水平扩容。
  • 离线批处理:比如用户画像打分、报表生成,对实时性要求低,但处理量大。这种场景常用Spark或其他分布式计算框架,在非高峰时段进行大批量推理,结果写入存储系统。
  • 边缘推理:比如手机端、IoT设备,一般要求模型足够小、推理足够快。常见的做法是量化到INT8甚至INT4,配合轻量化网络结构(如MobileNet)。

每种架构对模型的要求不同,所以在模型设计阶段就要想好未来的部署场景。我有一个切身教训:有一次做物体检测模型,只在GPU服务器上验证了准确率,没有考虑边缘设备的算力约束,结果模型做出来后,在嵌入式设备上单帧推理要好几秒,根本无法使用,只能重新做结构搜索和量化。提前设计,真的比事后补救省太多力气。

6.3 模型监控与持续迭代:上线只是开始

AI工程和普通软件开发很大的一个不同点是:模型上线后会“变旧”。数据分布会漂移、业务场景会变化、用户的反馈模式会改变,模型效果随时间推移逐渐衰减是必然的。

因此,模型上线后必须有监控体系。我推荐的监控指标分为两类:模型性能指标(如准确率、AUC)和业务指标(如点击率、转化率、用户满意度)。更关键的是要监控数据分布特征,比如输入特征的均值、方差、缺失率、类别分布是否有明显变化,一旦出现显著漂移,就要考虑重新训练或更新样本集。

数据漂移的检测可以从简单的统计指标开始,比如两个时间段内同一特征的PSI(Population Stability Index)。当PSI超过某个阈值,系统自动触发告警,然后你再决定是要重新标注数据、扩大样本集还是调整模型权重。

这套闭环说起来简单,但真正在组织里落地时,最难的反而不是技术问题,而是流程问题:谁负责告警后的响应?模型多久重新训练一次?如何保证新旧模型之间的可追溯性?这些问题如果不在项目早期就规划好,后面大概率会陷入“模型上线了却没人敢动”的僵局。

7. 常见问题与排查技巧实录

7.1 损失不下降:从数据到代码的排查清单

损失不下降是AI工程里最让人抓狂的问题之一,我整理了一套自己的排查清单,按顺序走一遍基本能定位:

  1. 先看数据:检查标签有没有大面积错误或缺失,特征是否经过合理归一化,训练集是否过小;
  2. 再看损失函数:确认损失函数和任务类型匹配,多分类用交叉熵、回归用MSE/MAE,不要混用;
  3. 检查梯度:打印一下梯度的范数,如果接近0(梯度消失)或极大(梯度爆炸),都能直接定位问题;
  4. 调整学习率:用高一点的学习率测试模型“能不能动”,如果怎么调都不动,多半是模型实现的问题;
  5. 过一遍前向传播:单独跑一次前向,检查输出形状和数值范围是否合理。

7.2 显存不足(OOM):实用的应对策略

GPU显存溢出是训练中频率极高的报错,尤其是刚接触大模型的人。除了“减小batch size”这种万能法宝,还可以从几个角度入手:

  • 使用梯度累积:原理上模拟大批量训练,让模型每N个mini-batch更新一次参数;
  • 检查是否有不必要的中间变量被保存,用with torch.no_grad()包住不需要梯度的推理过程;
  • 考虑混合精度训练,用FP16存储部分变量,显存占用能明显下降;
  • 如果以上都不行,那就得重新审视模型本身是不是太大了,比如降低隐藏层维度或使用更高效的注意力结构。

7.3 训练集与验证集效果差距大:泛化能力的细节陷阱

除了前面提到的过拟合,还有一种隐蔽的差距来源是数据泄漏。比如做时间序列预测时,如果打乱了数据再随机切分验证集,未来信息就会泄漏到训练集中,训练时效果会虚高,一到真实时间线预测就打回原形。

要避免这个问题,最稳的方法是按时间顺序划分数据集,保证验证集的时间点严格晚于训练集的所有时间点。另外,做特征工程时也要小心,有些特征本身就是用全局统计量(比如“全量用户平均活跃时长”)构造的,这种特征在离线评估中会带来信息泄漏风险,上线后这些统计量根本拿不到,效果自然崩盘。

7.4 一个完整案例:从训练到部署的故障复盘

为了让大家更有体感,我分享一个比较典型的案例。当时我在做一个文本分类服务,离线验证F1分数有0.86,上线后实时流量上的F1却只有0.61,差距大得离谱。

排查第一步是看数据分布,发现线上进来的文本长度比训练集普遍长很多,而我把文本截断成长度300的序列,导致很多有效内容被切掉了。第二步是看类别分布,线上的负样本比例比训练集高了不少,模型对高比例新类别非常不适应。第三步是检查预处理逻辑,发现线上服务用的分词版本和训练时不一致。

这三个问题分别属于:训练与推理数据分布不一致、类别不平衡偏移、逻辑不一致。这个案例给了我很深的教训:AI系统的效果不仅取决于模型本身,而是由数据、特征、部署、监控共同决定。

8. 从从零起步到持续成长:最后的一些心里话

做AI工程这条路上,与其说拼的是技术深度,不如说拼的是系统思考能力和排查问题的韧性。每当你觉得很吃力的时候,恰恰是你理解正在深入的时候。

我个人在实践中最受益的一个习惯是:无论用多高级的工具,每隔一段时间就强制自己“下探一层”。比如用熟了高级API后,回去手写一次反向传播;用惯了部署平台后,自己从零搭一次推理服务。正是这种自讨苦吃的“from scratch”的思维方式,让你在遇到问题时总能多几个排查角度,而不是只会对着报错信息发呆。

如果你也正在走这条路,我建议不用急着追求那些酷炫的大模型,先把一个小而完整的闭环做出深度。训练一个简单的模型、亲手部署、自己监控,走完全流程后收获比看十篇教程都大。从零开始很难但值得,希望这篇内容能成为你路上的一份地图。

返回列表