我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

基于VGG19与Gram矩阵的非实时图像风格迁移实验

基于VGG19与Gram矩阵的非实时图像风格迁移实验 简介面向人工智能与计算机视觉学习者的深度学习实验资源聚焦非实时图像风格迁移这一经典课题帮助读者理解如何借助预训练卷积神经网络在保留源图像内容结构的同时迁移目标图像的纹理与色彩风格适用于课程实验复现、CNN原理巩固与艺术化图像处理入门。压缩包共6个文件约90KB以5个Python源码为主涵盖网络层结构定义与风格迁移主流程脚本另附1张jpg格式的运行结果示例图便于直观对照算法输出效果。资源已有1240人学习说明其在实验教学场景中具备一定参考价值。文件组织围绕内容损失与风格损失的构建、特征提取与参数优化展开读者可据此梳理VGG类模型的使用方式、理解反向传播更新像素的过程并借鉴其中的层模块划分与超参数调整思路为同类视觉实验提供可复用的代码骨架与调试参照。1. 从一张照片到一幅画非实时风格迁移到底在算什么智能计算系统实验走到 3-3 这一节很多人第一反应是训一个前馈网络做实时风格迁移结果卡在训练集规模和算力预算上。非实时图像风格迁移换了个思路不训练任何新权重只拿一张冻结的预训练卷积网络当特征尺子让一张随机噪声图在几百次迭代里同时靠近内容图和风格图的统计特征。它属于人工智能课程里少见的、能把卷积特征图直接看见的实验——损失曲线的每一次下降都对应图像上某一块纹理被换掉。这套方案适合三类人要交课程设计的学生、想讲清楚 Gram 矩阵到底在干什么的助教、以及需要一份可复现基线来做对比实验的从业者。代价很明确单张图几十秒到几分钟换来的是不用数据集、不用训练、改一行参数就能换一种画风。2. 方案选型与总体架构三路损失挂在同一张冻结的 VGG 上2.1 为什么实验场景里选迭代优化而不是训练前馈网络风格迁移有两条主流路线一条是逐图优化的慢速路线一条是先用大量「内容图 风格图」成对数据训一个前馈网络、推理时一次前向出图的快速路线。前者的代表做法是固定的预训练 VGG 提特征内容损失约束语义结构风格损失约束 Gram 矩阵统计量优化器直接优化像素。后者的代表做法是把优化过程挪到训练阶段推理时零迭代。在实验课和课程设计里我一般会明确推荐前者原因不在效果而在可观测性。逐图优化没有训练集依赖一张内容图配一张风格图就能跑整个实验的变量只有风格权重、内容权重、特征层组合、迭代步数这四个每改一个都能立刻看到差异。快速路线需要至少几万张训练图和一个能跑几小时的训练过程一旦结果不对问题可能出在数据、网络结构、训练策略任意一环排查成本对两周的课设来说太高。对比项逐图迭代优化非实时前馈网络实时是否需要训练集不需要需要数万张以上单图生成耗时30 秒到 5 分钟几十毫秒换新风格的代价换一张风格图即可需要重训或换模型可解释性每一层损失可单独打印黑匣子适合场景教学实验、单图创作、基线对比产品化、批量处理这张表是整个方案的选型依据。非实时不是缺陷是刻意保留的中间过程——实验要看的正是中间过程。如果目标是给一个 App 做滤镜那当然该走前馈路线但如果目标是证明自己理解了卷积特征、损失设计和优化过程逐图优化反而更省事也更有说服力。2.2 模块划分与数据流从两张输入图到一张输出图把整个流程拆开一共六个模块职责边界清楚任何一个模块出问题都能单独定位。模块输入输出关键职责配置模块命令行参数或配置文件参数字典统一管理权重、层号、步数、尺寸图像读写与预处理内容图、风格图路径归一化后的张量缩放、转张量、按 ImageNet 均值方差归一化特征提取器归一化张量内容特征 多尺度风格特征冻结 VGG19截断到 conv5_1损失模块生成图特征、目标特征标量损失内容损失、风格损失、总变分优化模块可学习像素张量更新后的像素LBFGS 或 Adam输出与记录模块每步损失、生成图PNG 图 日志反归一化、截断、周期性存档数据流是这样走的先生成图初始化为内容图的副本或随机噪声形状为 1×3×H×W只对这一张张量开梯度。然后用torch.no_grad()把内容图和风格图各前向一次把目标内容特征和各层 Gram 矩阵缓存下来这一步很关键能省掉接近三分之二的显存和计算。接着进入迭代生成图前向一次拿到当前特征算三路损失加权求和反向传播优化器更新像素每步结束对像素做一次截断保证值域合法。最后反归一化、转回 uint8、存盘。2.3 特征层选择与超参数初值表特征层选择是有讲究的不能随手写。内容特征取 conv4_2这一层感受野足够大保留的是物体轮廓和空间布局取太浅会只剩下边缘取太深会丢掉细节。风格特征取 conv1_1、conv2_1、conv3_1、conv4_1、conv5_1 五层浅层抓颜色和小纹理深层抓大尺度的笔触结构五层叠加出来的风格才既有色块又有质感。下面这组初值是我反复调过、在多数「风景图 油画」组合上都能直接出结果的一组可以直接拿去当起点再按效果微调。参数建议初值调整方向内容权重 content_weight1.0调大保留更多结构调小画面更抽象风格权重 style_weight1e6 到 1e7调大笔触更浓过大会出现花纹噪点总变分权重 tv_weight1e-6 到 1e-5调大画面更平滑过大会糊迭代步数300LBFGS少于 150 往往没收敛优化器LBFGSlr1.0换 Adam 时 lr 取 0.01 到 0.05输入尺寸长边 512显存不足时优先降这里注意风格权重的量级比内容权重大六个数量级这不是笔误。Gram 矩阵在除以元素个数之后数值很小不放大到 1e6 量级根本推不动画面。这三个小节的顺序其实就是我讲这个实验的顺序先说清楚为什么不用训网络再说清楚数据怎么流最后把参数摆出来让人能立刻复现。缺了中间那步学生写完代码跑出来一张灰图根本不知道是哪一环出了问题。3. 核心模块实现Gram 矩阵、三路损失与 LBFGS 主循环3.1 图像预处理与特征提取器封装预处理最容易翻车的地方是归一化。torchvision 里的 VGG19 预训练权重是在 ImageNet 上训的输入必须按mean[0.485,0.456,0.406]、std[0.229,0.224,0.225]做标准化且通道顺序是 RGB。少做这一步网络照样能跑只是输出的颜色会整体偏灰偏蓝很多人以为是风格权重的问题其实是这里漏了。import torch import torch.nn as nn from torchvision import models, transforms from PIL import Image # ImageNet 统计量必须与预训练权重一致否则颜色会整体偏掉 IMAGENET_MEAN [0.485, 0.456, 0.406] IMAGENET_STD [0.229, 0.224, 0.225] def load_image(path, size512, devicecuda): 读图 - 等比缩放到长边 size - 转张量 - 归一化返回 1x3xHxW img Image.open(path).convert(RGB) w, h img.size scale size / max(w, h) img img.resize((int(w * scale), int(h * scale)), Image.LANCZOS) tf transforms.Compose([ transforms.ToTensor(), # 0~1HWC - CHW transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD), ]) return tf(img).unsqueeze(0).to(device) def denormalize(tensor): 反归一化回 0~1用于存图 mean torch.tensor(IMAGENET_MEAN, devicetensor.device).view(1, 3, 1, 1) std torch.tensor(IMAGENET_STD, devicetensor.device).view(1, 3, 1, 1) return (tensor * std mean).clamp(0, 1) class VGGFeatureExtractor(nn.Module): 截断到 conv5_1 的 VGG19参数全部冻结只做特征提取 def __init__(self, content_idx21, style_idx(0, 5, 10, 19, 28)): super().__init__() features models.vgg19(weightsmodels.VGG19_Weights.IMAGENET1K_V1).features self.content_idx content_idx self.style_idx set(style_idx) self.max_idx max(max(style_idx), content_idx) # 只保留用得到的层后面的层直接丢掉省显存也省时间 self.layers features[: self.max_idx 1].eval() for p in self.layers.parameters(): p.requires_grad_(False) def forward(self, x): content_feat, style_feats None, {} for i, layer in enumerate(self.layers): x layer(x) if i self.content_idx: content_feat x if i in self.style_idx: style_feats[i] x return content_feat, style_feats层号是对着torchvision.models.vgg19().features的顺序数出来的索引 0 是 conv1_15 是 conv2_110 是 conv3_119 是 conv4_121 是 conv4_228 是 conv5_1。中间的 ReLU 和 MaxPool 也占索引所以不能按「第几个卷积层」去数一定要用 print 打一遍结构确认。这个索引写错一位风格迁移不会报错只会悄悄换掉某一层的特征效果差一截还找不到原因。eval()和requires_grad_(False)两个都不能省。前者固定 BatchNorm 的统计量虽然 VGG 用的是 Dropout 而非 BN但习惯要养好后者直接省掉参数梯度显存占用能降一截。3.2 Gram 矩阵与风格损失Gram 矩阵做的事是把一个 C×H×W 的特征图压成 C×C 的通道相关性矩阵等于回答「哪些通道经常一起激活」。它丢掉了空间位置信息所以风格迁移能把笔触铺到任意位置这正是我们想要的效果。def gram_matrix(feat): C x H x W - C x C除以元素个数做归一化 b, c, h, w feat.shape flat feat.view(b, c, h * w) # 拉平成 C x (H*W) g torch.bmm(flat, flat.transpose(1, 2)) # 通道两两内积 return g / (c * h * w) # 归一化防止尺度随分辨率膨胀 def style_loss(gen_feats, target_grams, weightsNone): 逐层比较 Gram 矩阵权重缺省时每层等权 total 0.0 for i, feat in gen_feats.items(): g gram_matrix(feat) w 1.0 if weights is None else weights.get(i, 1.0) total total w * torch.mean((g - target_grams[i]) ** 2) return total归一化那行g / (c * h * w)是这段代码里最容易被忽略、后果又最严重的一行。不加归一化Gram 矩阵的数值会随特征图尺寸线性膨胀512 分辨率下能到几千平方之后损失直接上百万梯度一步就把图像炸成雪花。加了归一化数值被压到个位数量级配合 1e6 的风格权重刚好落在合理区间。提示如果你换了分辨率之后发现同样的参数不出图了先看 Gram 归一化的分母是不是跟着变了。归一化方式一改风格权重的量级要重调。3.3 内容损失与总变分正则内容损失就是逐元素均方误差作用在 conv4_2 的特征图上。目标特征要detach()避免梯度回流到目标分支白白浪费显存。总变分正则是个可选项但强烈建议加上。它的作用是在像素层面惩罚相邻像素的剧烈跳变压制迭代过程中产生的棋盘状噪点。import torch.nn.functional as F def content_loss(gen_feat, target_feat): conv4_2 特征上的逐元素 MSE目标特征不参与反传 return F.mse_loss(gen_feat, target_feat.detach()) def tv_loss(img): 相邻像素差分的 L1 均值分别算水平和垂直方向 dh (img[:, :, 1:, :] - img[:, :, :-1, :]).abs().mean() dw (img[:, :, :, 1:] - img[:, :, :, :-1]).abs().mean() return dh dw三路损失合成总损失时加权方式决定了最终画面风格total content_weight * c_loss style_weight * s_loss tv_weight * t_loss内容权重和风格权重是一对跷跷板。内容权重大画面结构保得住但笔触很淡看起来像加了层轻滤镜风格权重大笔触浓烈但可能把建筑结构搅碎。tv 权重的作用域完全不同它只在像素层面起作用一般给 1e-6 到 1e-5过大画面会糊成一片水彩过小则容易出现噪点。这个参数的调整不需要和另外两个联动可以单独扫。3.4 训练主循环与输出保存主循环用 LBFGS 是经典做法每步内部会做多次函数评估收敛比 Adam 快300 步左右就能出可观的结果。代价是要写 closure写法上比 Adam 啰嗦一点。def run_style_transfer(content_path, style_path, out_path, style_weight1e7, content_weight1.0, tv_weight1e-5, steps300, size512, devicecuda): content load_image(content_path, size, device) style load_image(style_path, size, device) extractor VGGFeatureExtractor().to(device) # 目标特征只算一次全程不参与反传 with torch.no_grad(): c_target, _ extractor(content) _, s_feats extractor(style) s_target_grams {k: gram_matrix(v) for k, v in s_feats.items()} # 生成图初始化为内容图比纯噪声收敛更稳 gen content.clone().requires_grad_(True) optimizer torch.optim.LBFGS([gen], lr1.0, max_iter20) step [0] while step[0] steps: def closure(): # LBFGS 会多次调用 closure每次都必须重新清零梯度 optimizer.zero_grad() with torch.no_grad(): gen.clamp_(0, 1) # 把像素拉回合法值域 c_feat, s_feats extractor(gen) c_loss content_loss(c_feat, c_target) s_loss style_loss(s_feats, s_target_grams) t_loss tv_loss(gen) loss content_weight * c_loss style_weight * s_loss tv_weight * t_loss loss.backward() step[0] 1 if step[0] % 50 0: print(fstep {step[0]:4d} total {loss.item():.4f} fcontent {c_loss.item():.4f} style {s_loss.item():.4f}) return loss optimizer.step(closure) with torch.no_grad(): out denormalize(gen.clamp(0, 1)).squeeze(0).cpu() transforms.ToPILImage()(out).save(out_path) return out_pathclosure里的几个细节值得单独说。optimizer.zero_grad()必须写在 closure 内部而不是外部因为 LBFGS 在一次step里会多次调用 closure写在外面只有第一次是干净的。gen.clamp_(0, 1)放在前向之前是为了让每次评估都在合法像素值上进行否则中间过程会出现负值和大值前向出来的特征会失真。step[0]用列表包一层是因为闭包内不能直接对整型变量做增量赋值这是 Python 作用域的老问题绕过办法很多列表最短。输出图初始化成内容图的拷贝而不是随机噪声收敛速度差别很明显。随机噪声起步通常要多跑一百步才能把结构稳下来内容图起步相当于从「内容正确、风格为零」这个点开始优化曲线更平滑。如果做对比实验需要展示不同的随机初始点再把这一行改成torch.randn_like(content) * 0.01即可。4. 工程化落地显存、耗时与可复现性的控制办法4.1 分辨率与显存的换算风格迁移的显存瓶颈不在模型参数VGG19 只有约 1.4 亿参数冻结之后不占梯度而在特征图。一张 512×512 的图进到 conv5_1通道数到 512单层特征就是 512×32×32 大小。更要命的是反向传播要保留所有中间激活因为要回传到输入像素。粗算公式是这样的显存占用约等于「各层特征图元素总数 × 4 字节 × 2前向激活 反向梯度」再加上生成图本身的梯度和优化器状态。实际测量下来单张 512 长边的图在 8GB 卡上占用大概 3 到 4GB768 长边到 6GB 左右1024 长边在 8GB 卡上基本会 OOM。长边尺寸显存占用实测范围单图耗时LBFGS 300 步适用场景3842GB 左右20 到 40 秒快速验证参数5123 到 4GB60 到 120 秒课设默认档效果与耗时平衡7686GB 左右3 到 5 分钟出最终展示图10248GB 以上8 分钟以上需要大图输出时用显存吃紧的时候处理顺序应该是先降分辨率最有效再减少迭代步数影响效果最后才考虑换更小的网络。有人会想着把 VGG19 换成 VGG16省不了多少因为特征图尺寸是一样的通道数差异很小。真正的省法是把风格层从五层减到三层只保留 conv1_1、conv3_1、conv5_1显存能降两成左右代价是大尺度纹理的丰富度下降。4.2 可复现性与种子管理逐图优化有个容易被忽略的问题同样的参数跑两遍结果可能不一样。原因有几种GPU 上非确定性算子比如某些原子加操作的顺序不固定、LBFGS 的线搜索在某些点上会走出不同分支、以及随机初始化时的种子没固定。要让结果稳定复现我一般会在脚本开头统一做三件事import random, numpy as np, torch def set_seed(seed42): 固定 CPU/GPU/NumPy/Python 四路随机源 random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 强制使用确定性算法会牺牲一点速度 torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic True会让卷积操作走确定性的实现路径代价是慢 5% 到 15%换来的是两次运行的逐像素差异控制在极小范围内。benchmark False是配套项如果开着 benchmarkcuDNN 会自己搜最快的算法那个搜索过程本身就带随机性。注意即使全部固定LBFGS 在浮点误差累积下仍可能有细微差异。如果课设要求「完全一致」就改走 Adam并把种子固定Adams 的确定性比 LBFGS 好控得多。除了种子参数记录也不能少。每一步的损失值、当前的学习率、用的是哪几种层组合都要写进日志文件。答辩时被问到「你调了几次参数」光靠记忆答不上来日志是唯一的证据。我的习惯是把配置写成 JSON 文件命名带上风格名和步数跑完自动存档一个月后回头看还能复现出同一张图。4.3 批量跑多组风格的组织方式课程设计通常不会只跑一张图至少要做「内容图 A 配风格图 B、C、D」和「风格权重三档对比」两组实验加起来十几组。如果靠手工改代码再跑很容易记混哪个结果对应哪组参数。我的做法是拆成三层一个配置文件描述一次实验一个主脚本读配置跑单次一个批处理脚本遍历配置目录。# 目录结构 # configs/ 存放 JSON 配置一个文件一次实验 # inputs/ 存放内容图和风格图 # outputs/ 按实验名建子目录存图和日志 # run_all.sh for cfg in configs/*.json; do name$(basename $cfg .json) python style_transfer.py --config $cfg --out outputs/$name done配置文件里放的是内容图路径、风格图路径、四个权重、步数、尺寸和随机种子。主脚本用argparse读命令行覆盖配置项方便临时调参而不改文件。输出目录按实验名分图文件名里带上style_weight和步数扫一眼就知道这组实验改了什么。这套组织的价值在验收阶段体现得最明显。做对比实验时横排四张图分别是 style_weight 1e6、1e7、1e8 和一个只算内容损失的基线图名自带参数图表一次成型。如果所有图都堆在一个目录里用result_1.png到result_12.png命名整理材料的半天时间基本就浪费了。4.4 时间预算的估算方法跑之前先估时间能避免通宵等结果的情况。总耗时约等于「步数 × 每步耗时」每步耗时跟分辨率近似成正比因为特征图元素数量随尺寸的平方增长但卷积核数量和通道数不变实际是超线性的。估的时候可以用小步数先跑一次测单步时间把steps设成 20看日志里每 10 步的时间差乘上目标步数就是总时长。512 分辨率下通常每步 0.2 到 0.4 秒300 步在 1 到 2 分钟之间。如果测出来每步要 2 秒以上多半是忘了torch.no_grad()导致目标特征也在反传。5. 避坑与排查六个最容易让结果翻车的点5.1 输出是满屏雪花或规则花纹现象迭代跑完输出图像一片高频噪点看不出任何内容结构损失值在几百到几千之间反复跳。原因风格损失的量级失控。最典型的是 Gram 矩阵没做归一化512 分辨率下 Gram 元素能到几千平方后损失上百万梯度一步就把像素推到边界其次是风格权重给到了 1e9 量级同样会压过内容项。解决先打印三类损失各自的数值。正常情况下风格损失应在个位到百位之间内容损失在个位量级。如果风格损失超过一千回头检查gram_matrix里的除以c*h*w有没有写漏再把style_weight从 1e6 开始重试。5.2 跑完发现图像几乎没变化现象输出和内容图几乎一样只是轻微偏色损失曲线几乎平直。原因三种可能。生成图张量没开requires_grad优化器拿到的是一张不参与梯度的常量学习率太小LBFGS 的 lr 应该是 1.0 而不是 0.001或者传进优化器的变量列表写错了把生成图之外的变量传了进去。解决在循环里打印gen.grad.abs().mean()正常应该在 1e-4 到 1e-2 之间。如果是 0 或 None就是梯度没连上。另外确认优化器构造是torch.optim.LBFGS([gen], ...)方括号不能少少了就不是列表。5.3 整体颜色发灰、发蓝或偏色严重现象结构正确、笔触也对但整张图蒙了一层灰或者颜色明显偏蓝紫。原因归一化统计量与预训练权重不匹配。用[0.5,0.5,0.5]当均值是很多土办法里的写法但 torchvision 的 VGG19 权重是在 ImageNet 上训的必须用[0.485,0.456,0.406]。另一种常见情况是读图时用了 BGR 顺序OpenCV 读取默认就是 BGR转张量时又没换回来。解决统一用 PIL 读图convert(RGB)显式指定通道顺序并核对归一化的两个列表。如果已经跑出偏色结果可以在反归一化阶段用同一组均值和标准差做逆变换验证颜色能还原就说明预处理是对的问题在损失权重上。5.4 显存爆掉报 out of memory现象跑到第几步突然 OOM或者一开始就起不来。原因分辨率给太大或者没把目标特征的提取放在torch.no_grad()里导致内容图和风格图的中间激活也保留了计算图显存直接翻倍也可能风格层取太多五层全开比三层多占两成左右。解决先降分辨率到 384 验证流程能跑通再逐级往上加。检查目标特征提取那段有没有with torch.no_grad():。如果显存卡在临界值附近可以在每次循环末尾加torch.cuda.empty_cache()但别指望它能救多少它只释放没被引用的缓存真正的办法还是降尺寸。5.5 同样的参数两次结果差很多现象同一组参数跑两遍一张是油画效果一张是水彩效果肉眼可辨的差别。原因种子没固定或者cudnn.benchmark开着cuDNN 每次自动选不同的卷积算法也有人在循环里用了torch.rand做随机裁剪但没固定种子。解决把前面set_seed那三行和两个 cudnn 开关一起加上并且确认调用时机在模型构建和第一次前向之前。如果还不行改用 Adam 优化器它的确定性比 LBFGS 好控制虽然收敛需要调学习率但复现性稳定得多。5.6 尺寸不是 8 的倍数导致特征对不齐现象运行时报尺寸不匹配的错误或者内容特征图和生成图的特征尺寸差一个像素。原因VGG 里有五次最大池化输入尺寸必须能被 16 整除特征图才不会在某一层出现奇数尺寸再被池化取整丢掉。图像按长边缩放之后短边是任意值很容易出现奇数。解决在load_image里加一步对齐把宽高都裁到 16 的倍数def align_to(x, factor16): 把宽高向下取整到 factor 的倍数 w, h x.size return x.resize((w - w % factor, h - h % factor), Image.LANCZOS)这个坑的麻烦之处在于大部分尺寸下它不报错只是悄悄让内容特征和目标特征错开一个像素损失照样能算、图照样能出但细节会糊掉一层很难联想到是尺寸问题。我现在的习惯是缩放完立刻对齐不给自己留排查的机会。6. 验收与答辩把「能出图」变成「能证明为什么能出图」验收阶段最容易出现的情况是图能跑出来但被问「为什么风格权重取 1e7」时答不上来。这一章给一套自检和讲解顺序把结果变成可验证的证据。6.1 验收清单与对比实验设计先按这张表过一遍每一项都能拿出东西再往下走。验收项验证方法合格标准流程可跑通全新环境按 README 跑一次无报错3 分钟内出图效果可辨内容图与输出图并排看结构保留、风格明显参数有依据提供权重对比实验图至少三档风格权重对比结果可复现同参数跑两次视觉上无差异代码可读模块划分与日志三类损失能单独打印对比实验至少要设计两组。第一组固定内容图和风格图风格权重取 1e6、1e7、1e8展示从「轻滤镜」到「重笔触」到「花纹溢出」的完整区间这一组直接回答了权重怎么选的问题。第二组固定权重比较有无总变分正则的差异放大局部截图能看到噪点的区别说明tv_weight的作用。6.2 答辩讲解的推荐顺序讲解不要从代码开始从问题开始。第一句说清楚为什么不训前馈网络把「不需要数据集、可观测中间过程、改一行参数换一种画风」这三个理由讲出来评委立刻知道你想清楚了选型。第二步讲三路损失各自约束什么重点讲 Gram 矩阵丢掉了空间信息、所以风格能铺到任意位置这是整个方法的理论核心。第三步展示损失曲线和中间迭代图第 0 步、第 50 步、第 150 步、第 300 步各存一张能直观看到笔触怎么一层层叠上去。最后再讲工程细节显存怎么算、种子怎么固定、OOM 怎么排查这部分展示的是落地能力。被问到「能不能实时」的时候不要回避直接说清楚耗时在 1 到 2 分钟量级属于非实时方案如果要实时需要换成前馈网络并准备训练集本实验的目标是可解释性而不是吞吐。这个回答比硬说「优化得很好」可信得多。我从第一次带这个实验起就养成了一个习惯每跑一组参数先存三样东西——图、日志、配置 JSON缺一样都不算跑完。当时觉得麻烦后来被追着问「这组结果用的什么权重」才明白后悔药都在这些文件里。希望帮到你。本文还有配套的精品资源点击获取
返回列表