TensorFlow子类API实战:从零实现widedeep推荐模型
1. 为什么还要折腾子类APITensorFlow 2.0 把tf.keras扶正之后搭模型的路子一下子多了起来Sequential、Functional、Subclassing 三种写法并存。很多刚上手的朋友会问既然 Sequential 三行就能堆一个网络Functional 画个图就能搞定多输入多输出为什么还要费劲去写子类这个问题我在第一次接触 widedeep 的时候也纠结过。先说结论widedeep 这种两条腿走路的结构用子类 API 写是最顺手的。原因不复杂——wide 部分和 deep 部分吃的是不同的特征走的是不同的前向路径最后才把 logits 加起来。Functional API 当然也能写但一旦你想在 forward 里塞点自定义逻辑比如动态加权、条件分支、特征交叉的中间处理Functional 就会开始别扭而子类 API 天然就是我自己定义call里发生什么自由度最高。这篇文章我打算把子类 API 实现 widedeep 这件事从头到尾捋一遍。适合谁看已经会写 Sequential、知道Dense、Embedding大概怎么回事但一碰到多输入 自定义前向就卡壳的朋友。看完你应该能自己动手搭一个能跑的 widedeep并且知道每一行为什么这么写。我会把踩过的坑、参数怎么定、调试怎么查都摊开讲尽量做到你照着抄就能跑通。widedeep 这个模型本身是推荐系统里的经典结构2016 年那篇论文之后被广泛用在 CTR 预估场景。它的核心思想用一句话概括wide 部分负责记忆memorization记住那些高频共现的特征组合deep 部分负责泛化generalization通过 embedding 把稀疏特征映射到低维稠密空间挖掘没见过的组合。两者互补一个管见过的一个管没见过的。2. widedeep 的结构拆解与选型考量2.1 wide 侧和 deep 侧到底各管什么先把结构讲清楚不然后面写代码就是照猫画虎。wide 侧本质上是一个广义线性模型输入是原始特征和交叉特征输出是w·x b。它的强项是记忆能力比如用户装了某类应用 AND 当前是晚上八点这个组合在训练集里出现过很多次且转化率高wide 侧就能通过这个交叉特征直接把这个模式记下来。缺点是它没法泛化训练集里没出现过的组合权重就是零预测不出来。deep 侧是一个前馈网络稀疏的类别特征先过Embedding层变成低维稠密向量然后拼接所有特征过几层全连接。它的强项是泛化即使某个特征组合没在训练集里出现过embedding 之间的相似性也能让模型给出一个合理的估计。缺点是容易过拟合而且对高频的、明确的特征组合记忆不如 wide 侧精准。两者最后把输出 logits 相加过一个 sigmoid 得到最终概率。注意是logits 相加不是概率相加这个细节很多人第一次写会搞错。2.2 为什么选子类 API 而不是 Functional我拿 Functional API 试过同一个结构能写但有两个地方很难受。第一个是特征处理逻辑的嵌入。wide 侧经常需要做特征交叉比如把城市和品类两个类别特征拼成一个新特征。在 Functional 里你得在输入层外面先做好交叉再喂进去模型定义和数据处理被割裂成两块。子类 API 里你可以在call里直接对输入做处理模型是自包含的。第二个是动态结构。有些场景下 wide 侧的权重需要根据 batch 内的统计量动态调整或者 deep 侧的层数想根据配置灵活变化。Functional 是静态图定义完就固定了子类 API 的call是每次前向都执行的 Python 代码想加什么逻辑加什么逻辑。当然子类 API 也有代价它不能直接用model.summary()看到完整结构因为 Keras 不知道你的call里到底连了什么。想看结构得自己打印或者用tf.keras.utils.plot_model配合expand_nested但效果也一般。这是自由度的代价心里有数就行。2.3 输入数据的组织方式widedeep 是多输入模型wide 侧和 deep 侧吃的特征不一样。我一般这样组织wide 输入稀疏的、需要交叉的类别特征通常用one-hot或者multi-hot表示维度可能很高但很稀疏。deep 输入需要 embedding 的类别特征 连续的数值特征。在子类 API 里call的签名可以写成call(self, inputs)inputs是一个字典或者列表。我个人偏好字典因为可读性好inputs[wide]、inputs[deep_cat]、inputs[deep_num]一目了然调试的时候不容易搞混顺序。列表虽然省事但一旦特征多了第几个对应哪个全靠记容易出错。3. 子类 API 实现的核心细节3.1 类的骨架怎么搭先看整体骨架我习惯把 wide 和 deep 拆成两个子模块主模型负责调度。import tensorflow as tf from tensorflow.keras import layers class WidePart(layers.Layer): def __init__(self, wide_dim, **kwargs): super().__init__(**kwargs) self.wide_dim wide_dim self.linear layers.Dense(1, use_biasTrue, namewide_linear) def call(self, wide_inputs): # wide_inputs: [batch, wide_dim] return self.linear(wide_inputs) class DeepPart(layers.Layer): def __init__(self, vocab_sizes, embed_dim, num_dense, hidden_units, **kwargs): super().__init__(**kwargs) self.embeddings [] for i, vs in enumerate(vocab_sizes): self.embeddings.append( layers.Embedding(input_dimvs, output_dimembed_dim, namefemb_{i}) ) self.dense_layers [layers.Dense(u, activationrelu) for u in hidden_units] self.out layers.Dense(1, namedeep_out) def call(self, cat_inputs, num_inputs): # cat_inputs: list of [batch, 1] tensors emb_list [] for i, x in enumerate(cat_inputs): emb_list.append(self.embeddings[i](x[:, 0])) x tf.concat(emb_list [num_inputs], axis-1) for layer in self.dense_layers: x layer(x) return self.out(x)这里有个细节值得说Embedding层的输入维度是词表大小输出维度是 embedding 维度。词表大小怎么定如果你的类别特征已经做了 label encoding最大值是 999那input_dim至少是 1000。我一般会留一点余量写成max_id 2防止边界情况。embedding 维度有个经验公式min(50, (vocab_size 1) // 2)但实际用的时候 8 到 32 之间比较常见太大容易过拟合太小表达不够。3.2 主模型的 call 里发生了什么主模型把两边拼起来class WideAndDeep(tf.keras.Model): def __init__(self, wide_dim, vocab_sizes, embed_dim, num_dense, hidden_units, **kwargs): super().__init__(**kwargs) self.wide WidePart(wide_dim) self.deep DeepPart(vocab_sizes, embed_dim, num_dense, hidden_units) def call(self, inputs): wide_logit self.wide(inputs[wide]) deep_logit self.deep(inputs[deep_cat], inputs[deep_num]) logits wide_logit deep_logit return tf.nn.sigmoid(logits)注意最后返回的是 sigmoid 之后的概率。如果你用tf.keras.losses.BinaryCrossentropy(from_logitsFalse)那就返回概率如果from_logitsTrue那call里就别加 sigmoid直接返回 logits让损失函数内部处理。这两种写法数值稳定性不一样from_logitsTrue更稳因为 sigmoid 和交叉熵合并计算能避免溢出。我一般推荐后者。3.3 参数初始化和正则化wide 侧的线性层权重初始化用zeros其实也行但更常见的是glorot_uniform。deep 侧的 embedding 初始化默认是uniform范围是[-0.05, 0.05]这个范围对大多数场景够用。如果你发现训练初期 loss 震荡厉害可以把 embedding 的初始化范围调小一点。正则化方面wide 侧一般不加 dropout因为它就是个线性层加了反而影响记忆能力。deep 侧可以在每个全连接后面加Dropout(0.2~0.5)具体多少看数据量。数据量小就加大一点数据量大就小一点。L2 正则我一般加在 embedding 上embeddings_regularizertf.keras.regularizers.l2(1e-5)防止 embedding 向量长得太大。4. 完整实操流程与关键环节4.1 数据准备与特征工程widedeep 的效果七分靠特征三分靠模型。我拿一个模拟的推荐场景举例假设有用户 ID、物品 ID、用户所在城市、物品品类四个类别特征外加一个连续特征用户历史点击率。wide 侧我准备这样构造输入用户 ID 和物品 ID 做交叉城市和品类做交叉再加上原始的四个特征全部 one-hot 后拼成一个大向量。假设用户 1000 个、物品 500 个、城市 50 个、品类 20 个交叉后维度是1000*500 50*20 1000 500 50 20大概 50 万维。这个维度很高但因为是 one-hot实际非零的没几个用稀疏张量存就行。deep 侧就简单了四个类别特征各自 label encoding 后过 embedding连续特征直接拼上去。import numpy as np def make_dummy_data(n_samples10000): np.random.seed(42) user_id np.random.randint(0, 1000, n_samples) item_id np.random.randint(0, 500, n_samples) city np.random.randint(0, 50, n_samples) category np.random.randint(0, 20, n_samples) hist_ctr np.random.rand(n_samples).astype(np.float32) label (np.random.rand(n_samples) 0.7).astype(np.float32) # wide 侧简化为拼接的 id 组合实际项目里用 tf.feature_column 或自己 one-hot wide np.stack([user_id, item_id, city, category], axis1).astype(np.float32) deep_cat [user_id.reshape(-1,1), item_id.reshape(-1,1), city.reshape(-1,1), category.reshape(-1,1)] deep_num hist_ctr.reshape(-1,1) return {wide: wide, deep_cat: deep_cat, deep_num: deep_num}, label这里为了 demo 简单wide 侧直接用了原始 id 拼接实际项目里应该做 one-hot 或者用tf.feature_column的crossed_column。注意 wide 侧的输入维度要和WidePart里的wide_dim对上我这里wide_dim4但真实场景可能是几十万所以 wide 侧通常用tf.sparse或者Embedding加sum的方式来实现避免稠密大矩阵。4.2 模型编译与训练编译的时候有几个参数要定model WideAndDeep( wide_dim4, vocab_sizes[1000, 500, 50, 20], embed_dim16, num_dense1, hidden_units[128, 64] ) model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losstf.keras.losses.BinaryCrossentropy(from_logitsFalse), metrics[tf.keras.metrics.AUC(nameauc)] )优化器选 Adam 是常规操作学习率 1e-3 起步。如果 wide 侧维度特别高稀疏梯度多可以考虑Adagrad或者FTRL这两个对稀疏特征更友好。损失函数用二分类交叉熵评估指标用 AUC因为 CTR 场景下正负样本极不均衡准确率没意义。训练的时候batch_size一般 256 到 1024 之间。太小梯度噪声大太大收敛慢。epochs看早停我一般设 20 然后加EarlyStopping(patience3)。callbacks [ tf.keras.callbacks.EarlyStopping(monitorval_auc, patience3, modemax, restore_best_weightsTrue), tf.keras.callbacks.ReduceLROnPlateau(monitorval_loss, factor0.5, patience2) ] history model.fit( train_inputs, train_labels, validation_data(val_inputs, val_labels), batch_size512, epochs20, callbackscallbacks )4.3 训练过程中的监控要点训练时重点看三个东西训练 loss 和验证 loss 的差距、AUC 的走势、wide 侧和 deep 侧各自的贡献。第一个看是否过拟合。如果训练 loss 一直降但验证 loss 开始升说明 deep 侧过拟合了加 dropout 或者减层数。第二个看 AUC 是否还在涨涨不动了就早停。第三个比较有意思你可以在call里把 wide_logit 和 deep_logit 分别返回训练时观察两者的量级。如果 wide 侧的输出一直是零附近说明 wide 侧没学到东西可能是特征交叉没做好或者学习率对 wide 侧不合适。我踩过一个坑wide 侧和 deep 侧共用一个优化器但两边梯度量级差很多导致 wide 侧几乎不更新。后来给 wide 侧单独设了更大的学习率或者用tf.keras.optimizers.Adam配合梯度裁剪才正常。如果你的 wide 侧不 work先查梯度。5. 常见问题与排查技巧实录5.1 维度对不上怎么办这是新手最容易遇到的报错。Embedding层的input_dim如果小于实际最大 id会报InvalidArgumentError: indices[0] 1000 is not in [0, 1000)。解决办法是训练前先扫一遍数据拿到每个类别特征的最大值input_dim设成max_id 1。wide 侧的维度对不上通常是Dense层的units和输入维度不匹配。子类 API 里Dense会自动推断输入维度但如果你的 wide 输入是稀疏张量得先tf.sparse.to_dense或者用tf.sparse.sparse_dense_matmul。我一般建议 wide 侧也用Embedding加sum的方式把稀疏 one-hot 转成稠密向量这样和 deep 侧的处理逻辑统一调试也方便。5.2 模型不收敛的几种可能现象可能原因排查方法loss 一直是 0.69 左右输出层没接对或者标签没对齐打印model(inputs)看输出范围loss 震荡剧烈学习率太大或 batch_size 太小降学习率到 1e-4增大 batchAUC 一直 0.5特征没信息量或 embedding 没更新检查特征和标签的相关性验证 loss 上升过拟合加 dropout、L2、减层数wide 侧梯度为零稀疏特征没被激活检查 one-hot 是否全零5.3 子类 API 调试的独门技巧子类 API 最大的痛点是看不到中间张量。我的做法是在call里临时加tf.print或者把中间结果存到self.debug_dict里训练完再取出来看。def call(self, inputs, trainingNone): wide_logit self.wide(inputs[wide]) deep_logit self.deep(inputs[deep_cat], inputs[deep_num]) if training: tf.print(wide_logit mean:, tf.reduce_mean(wide_logit)) tf.print(deep_logit mean:, tf.reduce_mean(deep_logit)) return tf.nn.sigmoid(wide_logit deep_logit)tf.print只在trainingTrue时打印避免推理时刷屏。另外model.summary()在子类 API 下只显示顶层 layer想看细节可以用model.layers遍历或者给每个子 layer 起个有意义的名字summary里至少能看到名字。还有一个技巧先用小数据跑通再上全量。我一般会造 100 条样本跑 2 个 epoch确认没有维度错误、没有 NaN再换全量数据。这样调试成本低很多。5.4 性能优化的一些经验wide 侧如果维度真的到了百万级稠密矩阵存不下必须用稀疏表示。tf.sparse.SparseTensor配合tf.sparse.sparse_dense_matmul可以做稀疏矩阵乘法。但注意稀疏操作在 GPU 上支持有限如果 wide 侧特别稀疏放 CPU 上算可能更快。deep 侧的 embedding 如果词表特别大比如千万级可以用tf.keras.layers.Embedding配合input_dim大值但显存会吃紧。这时候可以考虑 hash embedding把大词表哈希到固定大小的桶里代价是会有冲突但实际效果通常可以接受。混合精度训练在 TF 2.0 里用tf.keras.mixed_precision.set_global_policy(mixed_float16)就能开能省显存、加速训练但要注意 loss scaling不然梯度会下溢。我一般只在模型比较大、显存吃紧的时候开。6. 从 widedeep 延伸出去的几个方向widedeep 是个很好的起点理解了它很多后续模型都能顺藤摸瓜。比如DeepFM把 wide 侧的线性部分换成了 FM自动做二阶特征交叉省去了人工交叉的麻烦。DCN用 cross network 显式做高阶交叉。AutoInt用 attention 做特征交互。这些模型的结构都比 widedeep 复杂但核心思想一脉相承记忆和泛化要兼顾稀疏特征要 embedding交叉特征要显式建模。如果你想在 widedeep 上继续折腾我建议从这几个方向入手一是把 wide 侧的人工交叉换成自动交叉试试 FM 或者 cross layer二是给 deep 侧加 attention让模型自己学哪些特征重要三是把 embedding 换成预训练的用物品的 side information 初始化冷启动场景下效果提升明显。最后分享一个我在实际项目里的体会widedeep 的调参优先级是 特征 结构 超参。特征工程做得好一个简单的 widedeep 就能打败精心调参的复杂模型。所以别一上来就纠结 hidden_units 设多少、embed_dim 设多少先把特征交叉做扎实把类别特征的覆盖率提上去效果自然就来了。

相关新闻

monodepth2训练机制深度解析:多尺度损失与光度一致性设计

monodepth2训练机制深度解析:多尺度损失与光度一致性设计

1. 项目概述:这不是一篇“代码注释搬运工”式的文章双目测距系列(九)monodepth2训练代码分析 下——看到这个标题,我第一反应不是去翻GitHub仓库,而是先问自己:为什么是“下”?说明前篇已经拆到…

2026/10/18 23:42:49 阅读更多 →
图像26ms比文本83ms还快:Jev-Omni各模态延迟实测对比

图像26ms比文本83ms还快:Jev-Omni各模态延迟实测对比

图像26ms比文本83ms还快:Jev-Omni各模态延迟实测对比 【免费下载链接】Jev-Omni 项目地址: https://ai.gitcode.com/hf_mirrors/akhilaaa3/Jev-Omni 多模态大模型通常意味着「重」:视觉编码器、音频编码器、时序对齐、以及一串串自回归生成的 to…

2026/10/17 13:08:17 阅读更多 →
分布式数据库运维体系建设:高可用、容灾与自动化实战

分布式数据库运维体系建设:高可用、容灾与自动化实战

前阵子参加了一场金融科技产业联盟组织的分布式数据库运维研讨会,一整天的会下来,最深的感触是:大家终于不再纠结“要不要上分布式数据库”,而是开始认真讨论“上了之后怎么运维、怎么管、怎么让业务真正跑得稳”。这个转变其实是…

2026/10/17 12:39:41 阅读更多 →

最新新闻

字节跳动 AI 原生编程工具 TRAE 安装和配置:把 settings 改到 TaoToken 的完整实操

字节跳动 AI 原生编程工具 TRAE 安装和配置:把 settings 改到 TaoToken 的完整实操

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:06:55 阅读更多 →
RRT路径规划改进:基于GJK距离场与局部拓扑引导的Python实现

RRT路径规划改进:基于GJK距离场与局部拓扑引导的Python实现

简介:本资源为基于Python实现的改进型RRT路径规划算法源码包,面向机器人导航、自动驾驶及智能体运动规划方向的高校学生、科研人员与算法工程师,聚焦解决传统RRT在复杂环境下的扩展效率低、路径非最优、动态适应性弱等核心问题。压缩包共123个…

2026/10/19 0:06:55 阅读更多 →
数据库系统实验三实战:SQL复杂查询、索引优化与事务并发控制

数据库系统实验三实战:SQL复杂查询、索引优化与事务并发控制

数据库系统实验三,在很多学校的人才培养方案里都是一道分水岭。某高校软件学院的这门实验,题目看起来只是几张表、几条SQL,但真正动手后才会发现,它把关系代数、查询优化、事务控制这些课堂理论全部揉在了一起。这个实验能帮你打通…

2026/10/19 0:06:55 阅读更多 →
Tomcat 8.5.100 部署实战:轻量级Java Web容器的稳定选型与生产加固

Tomcat 8.5.100 部署实战:轻量级Java Web容器的稳定选型与生产加固

简介:本资源为 Apache Tomcat 8.5.100 官方发行版源码与二进制混合包(tar.gz 格式),面向 Java Web 开发初学者、运维工程师及中间件学习者,用于本地部署 Servlet/JSP 应用、理解容器运行机制及开展安全与性能调优实践。…

2026/10/19 0:06:55 阅读更多 →
结构协变网络(SCN)构建全流程:从T1分割到组水平脑网络分析

结构协变网络(SCN)构建全流程:从T1分割到组水平脑网络分析

简介:面向神经科学领域研究者与脑影像分析初学者,聚焦结构协变网络(SCN)的构建与分析,解决从MRI/DTI等原始影像到网络指标计算的全流程问题。资源覆盖数据获取、图像预处理、区域分割、特征提取、协方差计算、网络构建…

2026/10/19 0:06:55 阅读更多 →
DQN强化学习在柔性车间插单调度中的实战应用

DQN强化学习在柔性车间插单调度中的实战应用

简介:本资源是一个基于深度Q网络(DQN)算法实现的柔性作业车间动态调度系统Python项目,面向计算机、人工智能、自动化及工业工程等相关专业的本科生与研究生,适用于毕业设计、课程设计、期末大作业及竞赛初期方案验证等…

2026/10/19 0:05:53 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →