从交叉熵到Focal Loss:类别不平衡分类的损失函数实战指南

发布时间:2026/10/19 7:39:42
从交叉熵到Focal Loss:类别不平衡分类的损失函数实战指南 1. 那个让我撞了三天墙的97%准确率先说我遇到过的一个真实麻烦。某个二分类项目正样本少得可怜差不多是199的真负比。我按部就班用交叉熵损失去训练validation准确率一路冲到97%以上当时还挺高兴。结果一看混淆矩阵差点没把咖啡喷屏幕上——正样本的召回率只有12%模型基本上把所有东西都预测成负类了。准确率被压倒性多数类骗得死死的。这种情况在分类任务里太常见了类别极度不平衡模型学会了“躺平”——反正把所有样本判成大样本类准确率也不会难看。交叉熵对这种场景几乎无能为力它在梯度层面被多数类绑架少数类的信号微弱得可以忽略。随后我试过一堆方案对少数类过采样、对多数类欠采样、调整class weight、改阈值。各有各的效果但都有副作用。过采样容易过拟合欠采样又浪费数据class weight试来试去也没找到一个稳定的区间阈值后处理更像是亡羊补牢——训练出的模型表征本身就烂光调阈值救不回来。真正的转机是换了Focal Loss。这个最初写给目标检测的损失函数放到普通分类任务里一样能打。它最核心的思路就一句话让模型主动关注那些还没学会的样本而不是在所有样本上一视同仁地求损失。看起来只是给交叉熵加了一个调制因子但这个因子彻底改变了我训练时的行为曲线——正样本的召回率从12%拉到了78%左右同时负样本的误报率只涨了不到两个百分点。这篇文章我会把自己踩过的坑、调过的参数、试过的动态变体全部摊开讲。内容覆盖Focal Loss从公式推导到实际调参的完整链路适合正好被类别不平衡折磨的人也适合想在损失函数层面做文章的进阶玩家。我尽量不堆理论用实际训练日志和代码说话。2. 从交叉熵到Focal Loss一个调制因子引发的连锁反应2.1 交叉熵的“公平”在类别失衡时反而是不公平交叉熵损失的标准形式是L_CE -log(p_t)这里的p_t指的是正确类别的预测概率。样本易分的时候p_t接近1loss小样本难分的时候p_t很小loss很大。看起来很合理对吧问题恰恰出在这个“合理”上。在一个199的不平衡数据集里模型只需要把全部样本都判成负类负样本的p_t就会非常接近1它们对梯度的贡献虽然单看很小但胜在数量大、积少成多整体梯度方向完全被负样本主导。正样本本身稀疏即便它们的loss很大、梯度方向很重要但每批batch里就那么几个连被优化器“注意到”的机会都不多。更麻烦的是当模型已经能轻易识别多数一个批次里的绝大多数样本时交叉熵并不会因为“这些样本已经学好了”就减小它们的梯度权重。它对待一个p_t0.99的多数类样本和对待一个p_t0.6的少数类样本权重差异远不够大。训练后期就是这种“垃圾梯度”在拖后腿——占绝大多数的易分样本持续提供大量冗余梯度模型在多数类上打磨得越来越精细少数类的决策边界却始终模糊。所以问题的本质不是交叉熵公式错了而是在类别极不平衡时它预设的“公平竞争”环境根本不成立。压缩少数类的信号放大多数类的噪声这才是真实的训练状态。2.2 Focal Loss的公式变形到底改了什么Focal Loss的公式长这样L_FL -α_t * (1 - p_t)^γ * log(p_t)对比交叉熵多出来两个东西α_t和**(1-p_t)^γ**。(1-p_t)^γ是核心调制因子。它的行为可以分成两个区间看对于易分样本p_t → 1调制因子趋近于0loss被大幅压下来。压制的程度由γ决定γ越大压制越狠。对于难分样本p_t 很小调制因子接近1loss几乎不受影响梯度保持原样。这里拿γ2举例p_t0.9的样本(1-0.9)^20.01loss被压到了原来的百分之一p_t0.5的样本则保留了四分之一的权重p_t0.1的样本几乎完整保留。这样一来模型在反向传播时接收到的梯度信号从“所有样本的平均反馈”变成了“困难样本集中反馈”。α_t是类别权重解决的是正负样本绝对数量不均衡的问题。它和γ配合时有个很微妙的关系α在整体上给少数类提高比重γ在具体样本粒度上给难分样本保留比重。两者不是一个维度的东西但经常被混为一谈。直观理解的话你可以把交叉熵的梯度场看作“所有人投票”Focal Loss相当于引入了两轮投票权分配第一轮按类别分配基础权重α第二轮按每届当选难度分配额外权重1-p_t)^γ。多数类里的简单样本被大幅剥夺投票权少数类里的困难样本则被全额保留投票权。2.3 我一开始对调制因子的误解我第一次看论文时以为调制因子只跟“难易”有关跟“类别”无关。后来跑实验才发现这理解有偏差。如果只有(1-p_t)^γ而没有α训练时会有个奇葩现象训练前期模型能力弱所有样本的p_t都不高调制因子差别不大整体loss曲线看起来和交叉熵差不多到训练中后期模型开始能正确判断多数类样本多数类大量样本的p_t变高权重被压下来但少数类因为样本太少即便它们的p_t普遍偏低产生的梯度量依然很有限。结果就是模型很容易陷入“什么都不学”的惰性状态——难分样本权重高但样本量太少易分样本被压制没什么梯度贡献。加上α之后才真正把少数类的样本权重“抬上来”让它们哪怕数量少总梯度贡献也足以引导模型更新方向。论文里给的默认配置是α0.25、γ2这个组合在COCO检测任务上效果好但放到你自己的数据集上基本都得重调。场景α倾向γ倾向原因正样本极稀11000以上α往0.5~0.7调γ适当减小到1~1.5单靠γ压不了多数类需要α强行抬少数类正负比110左右α保持0.25附近γ往1.5~2靠类别不算极端γ主导效果更好噪声样本多α不要太极端γ减小到1左右γ太大会让模型死磕噪声难分样本3. 实际调参日志γ和α在我数据集上的规律性验证3.1 我的实验设定和评估方式项目背景是某跨平台系统的日志异常检测两条数据类别非常不均正常日志约50万条异常日志约6000条比例接近831。特征提取后是一个20维的数值向量模型用的是简单的四层全连接网络——这种场景根本不需要花哨结构损失函数的影响比网络结构大得多。评估不看accuracy因为97%的accuracy在类别不平衡任务里完全没有参考价值。我主要看异常类F1分数和PR曲线下面积。F1能同时惩罚漏报和误报PR-AUC对少数类的排序质量敏感更直接反映模型区分能力。训练参数Adam优化器初始学习率0.001batch size128训练30个epoch早停设在验证F1连续两轮不升。3.2 横扫γ的实测结果我先固定α0.25把γ从0到5横着扫了一遍。γ0的情况其实就是带α的加权交叉熵。结果挺有意思γ异常类F1负样本误报率训练收敛轮数00.523.1%110.50.603.8%121.00.684.6%141.50.745.2%162.00.766.0%193.00.728.7%245.00.5815.4%不收敛几点规律γ从0上升到2的过程中F1稳步上升但代价是误报率同步走高。这符合直觉——γ越大模型对困难样本越敏感而困难样本里除了罕见异常还包含一部分特征模糊、甚至被标注错误的正常日志把它们也算“困难”去放大权重必然制造误报。γ2之后边际收益递减过头反而崩。γ3时F1开始回落但误报率继续涨γ5时训练过程直接不稳定——验证loss剧烈震荡模型开始对若干顽固的噪声样本过拟合。我的判断是γ太大会让优化器“死盯”最难的几个点导致方向震荡甚至发散。3.3 α和γ的交互效应另一个常被忽视的点α和γ不是独立变量它们的组合效果非线性很强。我固定γ2把α从0.1调到0.7结果如下α异常类F1负样本误报率0.10.683.5%0.250.766.0%0.40.7410.2%0.70.6517.8%α0.4时F1表面看起来还行但误报率已经接近失控。α0.7时模型基本在批量制造误报——因为负样本占了98%以上提高少数类权重等同于让模型把很多疑似边界样本全部判定为异常。我后面又试了动态调整α的思路训练初期α大一点让模型快速学到少数类的粗粒度模式训练后期α衰减到较小值避免过度偏置。这个想法实践效果不错但放在第四节统一讲。3.4 稳定的经验区间总结基于这批日志我总结出一个规律性经验γ在1到2之间的收益最稳定α不要超过0.3除非你的分类任务里“分错某类”的代价极度不均衡。多数场景下α0.25、γ2这个论文默认值是个不错的起点但后续一定要做几步网格微调固定α0.25γ按0.5步长从1扫到3选F1最高的γ固定它让α按0.1、0.2、0.25、0.3、0.4扫一圈把最终两个参数组合放到不同随机种子下跑三遍确认方差不大。一个容易踩的坑不要只盯着验证集的F1选参数。模型对于“特征为正常日志但语义极为异常”的漏报和“特征模糊但真实正常”的误报代价完全不同。F1只考虑数量不考虑代价。我后来倾向于在F1接近的情况下优先选误报率更低的参数组合——线上系统里误报对用户的骚扰比漏报的一次日志翻查更讨厌。按上面的网格扫法你大概需要跑20~30组训练。每组如果样本量大、网络深时间成本确实不小。我后续的做法是先用一个抽样子集快速筛把候选区间锁定后再跑全量数据微调能省三分之二的时间。4. “动态”的几种实操路径Focal Loss不只是固定γ和α标题既然带了“动态交叉熵”这几个字我得把自己的体会说清楚Focal Loss在原始论文里γ和α是超参数固定值。但在实际工程中把这两个参数固定死远远没有“动态调整”效果好。紧接着介绍几个我自己试过且效果明确的动态变体思路。它们不一定比“一群专家精心调出的固定参数”更强但优势在于不用花那么多时间在网格搜索上而且更稳健。4.1 动态γ从温和启动到逐步聚焦最常见的做法是让γ随训练进程从0线性增加到目标值比如2。我实现过一段PyTorch代码核心逻辑很简单class FocalLossDynamicGamma(nn.Module): def __init__(self, alpha0.25, gamma_start0.0, gamma_end2.0, warmup_epochs15, total_epochs30): super().__init__() self.alpha alpha self.gamma_start gamma_start self.gamma_end gamma_end self.warmup_epochs warmup_epochs self.total_epochs total_epochs self.current_epoch 0 def forward(self, logits, targets): ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) gamma self._get_gamma() alpha_t torch.where(targets 1, torch.tensor(self.alpha, devicelogits.device), torch.tensor(1 - self.alpha, devicelogits.device)) focal_loss alpha_t * (1 - pt) ** gamma * ce_loss return focal_loss.mean() def _get_gamma(self): progress min(1.0, max(0.0, self.current_epoch / self.warmup_epochs)) return self.gamma_start (self.gamma_end - self.gamma_start) * progress def set_epoch(self, epoch): self.current_epoch epoch这个方案的心理预期是训练早期模型还没有辨别能力几乎所有样本的p_t都不高这时候用大γ会破坏梯度稳定性让训练从第一天起就陷入混沌。先以γ≈0即加权交叉熵起步保证模型学到全局区分能力再逐步加大γ去聚焦难样本。实测下来动态γ方案最明显的收益是训练稳定。固定γ2时前几个epoch的验证F1经常在0.2到0.6之间剧烈跳动动态γ版本前几个epoch的F1波动幅度小了近一半收敛路径平滑很多。此外ε需要跨epoch传递还要配合一个learning rate schedule。我自己习惯把γ的增长和warmup lr同步其实效果也会更好。4.2 动态α基于类别频率的自适应权重另一种动态思路是让α跟着训练阶段的类别分布走。这里的“类别分布”不是指数据集静态标签的分布而是每个batch里预测错误的类别分布。我的实现方式维护两个指数滑动平均变量ema_pos_loss和ema_neg_loss分别统计当前batch中正负样本的平均loss。然后α ema_pos_loss / (ema_pos_loss ema_neg_loss)这个公式的思想是如果正样本的平均loss远大于负样本说明模型还没学好正类就给正类分配更大权重反之亦然。本质上它把α变成了一个动态的“损失缺失项补偿器”优先级是“哪个类更拖后腿就倾斜哪个类”。代码长这样class FocalLossAdaptiveAlpha(nn.Module): def __init__(self, gamma1.5, momentum0.9): super().__init__() self.gamma gamma self.momentum momentum self.ema_pos None self.ema_neg None def forward(self, logits, targets): ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) gamma self.gamma focal_base (1 - pt) ** gamma * ce_loss pos_mask targets 1 neg_mask ~pos_mask pos_loss_mean focal_base[pos_mask].mean().detach() neg_loss_mean focal_base[neg_mask].mean().detach() if self.ema_pos is None: self.ema_pos pos_loss_mean self.ema_neg neg_loss_mean else: self.ema_pos self.momentum * self.ema_pos (1 - self.momentum) * pos_loss_mean self.ema_neg self.momentum * self.ema_neg (1 - self.momentum) * neg_loss_mean alpha_pos self.ema_pos / (self.ema_pos self.ema_neg 1e-8) alpha_neg 1 - alpha_pos alpha_t torch.where(pos_mask, torch.tensor(alpha_pos, devicelogits.device), torch.tensor(alpha_neg, devicelogits.device)) return (alpha_t * focal_base).mean()实测这个方案在训练初期的表现很亮眼α会快速飙到0.5以上因为正样本loss远大于负样本随着模型逐渐学会区分α慢慢回落到0.3左右。它在某种意义上代替了手动网格搜索α的过程而且比固定α更“跟手”。但要注意一个坑如果某个batch里没有正样本pos_loss_mean会出现NaN。这时只更新neg侧的EMA、保持pos侧不变即可避免位置NaN导致后续全部失效。4.3 结合软标签的“温度”动态软化严格说这个不算是Focal Loss的变体而是软化logits的技巧和FL配合会产生很奇妙的化学反应。常规的softmax交叉熵模型的预测置信度会迅速走向两极化——正确的样本p_t很快逼近0.99以上。但Focal Loss的调制因子对这类样本的压制非常狠相当于把“模型已经会了”的信息反复灌输。问题在于模型在早期阶段形成的极端置信度往往带有过拟合噪声的痕迹过早进入“高置信被压制”状态会让训练变成挤牙膏。我用了一个简单的方案在logits进入softmax之前加一个温度缩放p_t softmax(logits / T)T1就是原版softmaxT1会让概率分布更平滑最大概率不会太高调制因子的压制效果被延后T1则让概率更极端调制因子更快发力。运行时可以动态衰减训练前10个epoch用T2逐渐降到T1等于让模型先广泛探索、再局部收紧。这个方案的额外收益是能天然防止“模型对几个难样本死磕”导致的过拟合。固定γ2配合温度从2到1的降温在日志异常检测上F1比固定参数版本又涨了三个百分点。不过要说明温度缩放本身会让交叉熵的数值变小如果你习惯用学习率0.001这种常规值建议T2对应地把学习率乘上2左右补偿一下梯度量级的下降否则前几个epoch损失下降慢得仿佛模型根本没在学。5. 我自己总结的一套Focal Loss使用决策树5.1 何时使用Focal Loss何时老老实实用交叉熵Focal Loss不是万能药在很多场景下加进去反而更差。我的经验是拿下面几条过一遍场景用什么原因类别不平衡但不极端51交叉熵 class weightFocal Loss的优势不容易体现反而多出两个超参数要调类别极不平衡101Focal Loss / 动态Focal Loss调制因子带来的困难样本聚焦效果明显噪声样本比例高交叉熵或γ很小的FL大γ会死磕噪声样本并过拟合多分类且每个类样本量差异大FL 数据集均衡采样Focal Loss作为损失函数在多个类别之间需要额外定制α向量回归任务不用FLFocal Loss针对分类型的概率分布回归建议Huber或分位数损失关于最后这个用途我踩过一次坑在某传感器数值预测项目中把回归当作分类尝试用Focal Loss因为数值本身就连续切bin之后各类别间的“难分”定义非常模糊调制因子带来的影响不可控。最后还是老老实实回退到Huber Loss。5.2 和采样器配合时容易犯的错误采样器和Focal Loss在功能上重叠但不互补叠加使用需要格外小心。我在一组对照实验里试过三种组合仅Focal Loss、仅RandomOverSampler过采样、Focal Loss加过采样。结果发现Focal Loss加过采样导致验证集F1反而比单独用FL低了两个百分点原因是过采样把少数类复制进了训练分布模型有效训练样本里的“多数类”和“少数类”差距被抹平Focal Loss的调制因子开始压制的就不再是“难正样本”而是被重复复制后“显得易分”的正样本。如果你非要用采样器和Focal Loss搭配我建议只用欠采样把极端多数类砍到和少数类比不超过201剩下的不平衡交给Focal Loss去处理。换句话说采样器负责把不平衡拉到一个“可控范围”Focal Loss负责在这个范围内深挖困难样本两者错峰不重叠。5.3 我自己日常使用的模板化配置现在每次遇到类别不平衡的表格数据二分类我先锁这个模板criterion FocalLossDynamicGamma( alpha0.25, gamma_start0.5, gamma_end1.5, warmup_epochsint(total_epochs * 0.5), total_epochstotal_epochs ) # 每epoch结束调用 # criterion.set_epoch(epoch) 配合lr_scheduler然后跑10个epoch的快速验证看验证F1有没有追杀起来。如果F1能到0.7以上再考虑加温度软化进一步提点如果F1长期在0.5以下优先提升特征质量而不是继续调损伤函数。6. 数值稳定性和PyTorch实现细节6.1 我的一个NaN事故开始手写Focal Loss时我直接照抄了某些版本的公式pt torch.softmax(logits, dim1)[range(n), targets] loss -torch.log(pt)遇到类别很多且logits极端时torch.log(pt)会把pt极小小于1e-30的情况算成-inf接着(-inf) * 0变成NaN。这种NaN出过两次才让我老老实实换了更稳的写法。稳定的做法是绕过pt直接用交叉熵的结果ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss)因为F.cross_entropy在内部做了log-softmax的数值稳定处理torch.exp(-ce_loss)天然等价于正确类预测概率且不会下溢到0也就消除了inf的产生条件。6.2 一个一劳永逸的实现版本我后来把动态γ和自适应α合在一个类里做成一个可以开箱即用的实现。全程只有不到40行import torch import torch.nn as nn import torch.nn.functional as F class DynamicFocalLoss(nn.Module): 带动态γ和自适应α的Focal Loss 用法: criterion DynamicFocalLoss(gamma_start0.5, gamma_end1.5, warmup_epochs15, momentum0.9) ... for epoch in range(total_epochs): criterion.set_epoch(epoch) for x, y in dataloader: loss criterion(logits, y) def __init__(self, gamma_start, gamma_end, warmup_epochs, momentum0.9, use_adaptive_alphaTrue): super().__init__() self.gamma_start gamma_start self.gamma_end gamma_end self.warmup_epochs warmup_epochs self.momentum momentum self.use_adaptive_alpha use_adaptive_alpha self.ema_pos None self.ema_neg None self.epoch 0 property def gamma(self): p min(1, max(0, self.epoch / max(1, self.warmup_epochs))) return self.gamma_start (self.gamma_end - self.gamma_start) * p def set_epoch(self, epoch): self.epoch epoch def forward(self, logits, targets): ce_loss F.cross_entropy(logits, targets, reductionnone) pt torch.exp(-ce_loss) focal (1 - pt) ** self.gamma * ce_loss pos_mask targets 1 neg_mask ~pos_mask if self.use_adaptive_alpha: pos_loss focal[pos_mask].mean().detach() neg_loss focal[neg_mask].mean().detach() if torch.isfinite(pos_loss): self.ema_pos pos_loss if self.ema_pos is None else \ self.momentum * self.ema_pos (1 - self.momentum) * pos_loss if torch.isfinite(neg_loss): self.ema_neg neg_loss if self.ema_neg is None else \ self.momentum * self.ema_neg (1 - self.momentum) * neg_loss alpha_pos (self.ema_pos / (self.ema_pos self.ema_neg 1e-8)).clamp(0.1, 0.9) alpha torch.where(pos_mask, torch.tensor(alpha_pos, devicelogits.device), torch.tensor(1 - alpha_pos, devicelogits.device)) else: alpha torch.tensor([0.25], devicelogits.device) alpha torch.where(pos_mask, alpha, 1 - alpha) return (alpha * focal).mean()里面埋了两个小心机torch.isfinite检查空batch导致的NaNclamp(0.1,0.9)避免α跑到太极端的位置引发训练失控。这两个保护在真实项目中救过我强烈建议保留。6.3 关于梯度视角的小验证为了验证Focal Loss确实改变了“样本对梯度的贡献”我手工算了一个简单case两个样本一个p_t0.9的易分样本一个p_t0.3的难分样本。标准交叉熵下易分样本的梯度范数是0.105难分样本是1.73比例约116。换成α0.25、γ2的Focal Loss后易分样本梯度被压到0.001左右难分样本保持1.73且乘以0.25变成了0.43比例拉到约1430。难分样本的梯度主导地位提升了一个数量级以上。梯度反向传播的效率完全不一样这就是Focal Loss能逼着模型去啃硬骨头的底层原理。7. 训练录得与对比结果一组我实际跑过的完整实验记录7.1 基线交叉熵看起来很坏但不知道为什么坏基线实验交叉熵无任何处理训练30轮。验证集loss从0.41降到0.22左右看起来一切正常。但验证集的异常类F1只有0.18。训练集F1也才0.35——说明模型问题是结构性的不是过拟合导致的泛化失败而是训练阶段就根本没学会拿正样本的正确信号。7.2 加入静态Focal Loss明显好转但还不够α0.25、γ2静态参数。异常类F1升到0.76最后五个epoch收敛速度明显变慢说明模型把相当大比例的“少数字符特征异常”样本处理得比较好了但剩余的是和正常日志特征高度重合的模糊样本。7.3 动态版本再进一步最明显的是训练稳定性换成动态γ0.5→1.5加自适应α的版本异常类F1到了0.81最重要的是训练过程几乎是单调的没有出现几个epoch之后重新剧烈起伏的现象。方案异常类F1PR-AUC训练稳定性收敛轮数交叉熵0.180.42稳定但无用8加权交叉熵 (α0.25)0.520.61稳定11Focal Loss 静态0.760.82中期有抖动19Focal Loss 动态γ自适应α0.810.87平稳单调17动态FL 温度缩放0.840.90平稳单调18这个对比表不是论文级严谨但足以说明方向动态化带来的收益在真实项目里是可感知的而且它还不要求你花几个小时去网格搜索最优静态参数。7.4 为什么我就这么不舍弃加权交叉熵不只是它简单稳。我发现加权交叉熵在某些数据分布下的表现和Focal Loss差距真的不大——特别是在“少数类本身很少很纯、难分样本占比不高”的数据集里。Focal Loss的优势前提是大量难分样本不是单纯多数/少数失衡。如果你的项目里少数类虽然少但特征十分鲜明模型只要看到一个就能分清加权交叉熵就够了Focal Loss的调制因子像是个“备而未用”的雨伞反而增加了复杂度。判断方法很简单先跑一版加权交叉熵如果验证F1已经到了0.8以上没必要强行换FL如果卡在0.6、0.7上不去再上Focal Loss效果会立竿见影。8. 训练完成后我还得说说的几个“容易被忽略但致命”的小事8.1 验证集的采样方式决定了你调参的“视力”我曾经直接在原始分布上切验证集异常类样本占比0.3%——每个batch只有个别正样本算出来的F1方差大得惊人同样的配置不同随机种子跑出来的F1波动有±0.08。解决办法是给验证集做分层采样保证验证集里正样本占比不低于5%左右或者干脆按固定数量抽固定量的正样本进去比如每条验证集都固定1000条异常样本2000条正常样本。评估结果稳定了才能让参数调整有可靠的参照。这个坑一定要早挖出来不然后续所有超参数对比都是在噪声里找信号。8.2 类别不平衡的模型预测概率本身就有偏移Focal Loss优化后的模型输出的概率不代表真实的贝叶斯后验概率而是被调制因子扭曲过的“聚焦分数”。如果你后续要卡阈值上线别忘了用验证集重新校准一次阈值。我常用做法就是在验证集上画出PR曲线后直接取PR曲线上F1最高或代价最低的那个点换算成阈值而不是简单用0.5。项目上线后如果线上分布变化阈值还要配合漂移监测做定期重估。8.3 我的第一次“动态”尝试其实失败了坦白说我最早做的“动态Focal Loss”不是动态γ也不是自适应α而是每N个epoch让模型自己打印出p_t分布直方图我看着直方图手动改参数。这个方法不能说毫无帮助但它本质还是试错改着改着就忘了前面改过什么反复来回横跳。后来彻底转向公式化的自动调整才真正解脱。要把动态化实现在代码里而不是实现在你“盯曲线”的眼睛里。这也是这篇博文愿意把代码直接放出来的原因——实操的毛刺实在太多能抄就抄省下的时间不如多看几行训练日志。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询