FEATURED · 精选文章

基于TensorFlow 2.x与U-Net的语义分割实战:从数据管道到模型训练全解析

发布时间 / 2026/8/22 5:50:42
来源 / 创域科博编辑部
栏目 / 资讯中心
基于TensorFlow 2.x与U-Net的语义分割实战:从数据管道到模型训练全解析 1. 项目概述从“看懂”到“动手”的语义分割之旅如果你对计算机视觉感兴趣肯定听过“图像分类”和“目标检测”它们能告诉你图片里有什么、东西在哪。但“语义分割”更进一步它要回答的是“图片里每一个像素点属于什么类别”。想象一下给一张街景图分割模型能像PS的“魔棒”工具一样自动把天空、道路、车辆、行人、建筑物等不同区域用不同颜色精准地勾勒出来生成一张像素级的“语义地图”。这个技术是自动驾驶感知环境、医疗影像分析病灶、遥感图像解译地物、甚至手机人像虚化背后的核心。听起来很酷但门槛似乎很高网络结构复杂、代码调试繁琐、训练资源要求高让很多初学者望而却步。今天我们就用最直接的方式基于当下主流的TensorFlow 2.x框架和其高阶API Keras手把手带你实现一个经典的语义分割模型。我的目标很简单让你在看完这篇文章后不仅能理解语义分割的基本原理更能亲手跑通一个完整的训练-预测流程获得“我也做出来了”的成就感。我们将避开那些庞杂的理论推导聚焦于“如何用代码实现”把每一个步骤掰开揉碎讲清楚。你会发现借助现代深度学习框架的封装入门语义分割并没有想象中那么困难。本文假设你已有Python和深度学习的基础知识但即使你是刚接触TensorFlow 2.x跟着步骤走也完全没问题。2. 核心思路与工具选型为什么是U-Net与TensorFlow 2.x在动手写代码之前我们必须明确两件事用什么模型以及用什么工具。这直接决定了我们项目的可行性和最终效果。2.1 模型选择U-Net小样本分割的“常青树”语义分割模型众多从早期的FCN到后来的U-Net、DeepLab系列再到最近的Transformer架构。对于入门项目我的选择是U-Net。原因有三点这三点也是你在未来为其他任务选型时可以借鉴的思路第一结构清晰对称易于理解。U-Net的结构像一个“U”形分为左侧的编码器下采样和右侧的解码器上采样。编码器负责像传统的CNN一样通过卷积和池化层层提取高级语义特征但会丢失细节和空间信息。解码器则通过上采样和跳跃连接逐步恢复空间细节并将编码器对应层的高分辨率特征“拼接”过来实现精准定位。这种“先压缩再恢复中间有捷径”的设计非常直观是理解编解码结构的绝佳范例。第二对训练数据量要求相对友好。U-Net最初是为生物医学图像分割设计的这类数据往往标注成本极高样本量小。U-Net通过跳跃连接融合了浅层细节和深层语义使得模型即使在数据量不大的情况下也能学习到有效的特征表示。对于我们这个入门项目我们很可能会使用一个较小的公开数据集如PASCAL VOC的子集U-Net是一个稳妥且高效的选择。第三社区支持完善复现成功率高。U-Net是2015年提出的模型经过多年沉淀其各种框架的实现代码非常丰富相关调参经验、问题排查的讨论也很多。这意味着你在实现过程中遇到的大多数坑很可能已经有人踩过并提供了解决方案极大地降低了学习成本。注意虽然U-Net经典但它并非在所有场景下都是最优的。对于需要极大感受野的场景如街景中远处的物体DeepLab系列的ASPP模块可能更有效对于非常精细的边界分割可能需要注意力的机制。但作为入门掌握U-Net足以让你构建起对语义分割的完整认知框架。2.2 框架选择TensorFlow 2.x Keras平衡灵活与高效深度学习框架之争从未停歇但TensorFlow 2.x凭借其生态和工业界部署优势依然是许多人的首选。对于入门者我强烈推荐使用其内置的Keras API原因如下1. 极简的API设计。Keras的口号是“为人类设计的API”用tf.keras.Sequential或者函数式API构建模型就像搭积木一样直观。定义一层网络往往就是一行代码的事。这让我们能把精力集中在模型结构和任务逻辑上而不是框架的语法细节里。2. 与TensorFlow无缝集成。在TF2.x中Keras是首推的高级API。这意味着你可以轻松地混合使用Keras的简洁和TensorFlow底层的灵活性例如自定义训练循环、梯度操作为后续进阶打下基础。同时从数据加载tf.data、模型训练到保存部署整个流水线非常顺畅。3. 强大的社区与资源。无论是官方文档、教程还是GitHub上的开源项目基于TF/Keras的语义分割实现可能是最丰富的。遇到任何问题你都能更容易地找到相关的代码片段或讨论。环境准备清单Python: 3.7 - 3.9版本与TF2.x兼容性最好。TensorFlow: 2.4及以上版本pip install tensorflow。如果你的GPU支持CUDA可以安装tensorflow-gpu以获得加速。其他库:numpy,matplotlib,opencv-python用于图像处理scikit-image等可以通过pip一并安装。你可以通过以下命令快速创建一个隔离的环境并安装依赖以Anaconda为例conda create -n tf_seg python3.8 conda activate tf_seg pip install tensorflow matplotlib opencv-python scikit-image3. 数据准备与预处理打造模型可理解的“食粮”模型和框架选好了接下来就是数据。数据决定了模型能力的天花板。我们将使用一个经典的公开数据集——PASCAL VOC 2012。它包含20个常见的物体类别人、车、飞机等和背景图像数量适中标注质量高非常适合教学和实验。3.1 数据集获取与结构理解PASCAL VOC数据集通常提供原图JPEGImages和对应的分割标注图SegmentationClass。标注图是一种索引彩色图像其中每个像素的颜色值不是一个随机的RGB值而是一个代表类别的索引号。例如像素值为0可能代表背景1代表飞机2代表自行车……以此类推。关键点我们的模型最终要预测的正是每个像素的类别索引一个0到20之间的整数。因此在训练前我们必须将彩色的标注图转换为单通道的索引图。VOC数据集通常直接提供这种索引图但我们需要确认其格式。一个常见的预处理步骤是建立颜色到类别的映射关系Color Map。VOC官方有一个固定的颜色映射表比如“飞机”对应128 0 0这个RGB颜色。在可视化预测结果时我们需要根据这个映射表将模型输出的索引图“着色”回我们能看懂的彩色图。3.2 构建高效的数据管道tf.data直接一次性将所有图像读入内存ImageDataGenerator的flow_from_directory方式在处理大量数据或高分辨率图像时效率低下。TensorFlow 2.x推荐使用tf.data.DatasetAPI它能构建一个数据流管道实现数据的懒加载、并行预处理和预取极大提升GPU利用率。我们的数据管道需要完成以下核心步骤加载路径列出所有图像和对应标注mask的文件路径。随机打乱在训练前打乱数据顺序使模型学习更均衡。解析函数定义一个函数输入是文件路径输出是处理后的图像张量和标签张量对(image, mask)。在这个函数里我们要做读取图像使用tf.io.read_file和tf.image.decode_jpeg/png。调整大小将所有图像和mask统一缩放到一个固定的尺寸如256x256。这是必须的因为神经网络需要固定的输入维度。使用tf.image.resize注意对于mask标签应使用tf.image.ResizeMethod.NEAREST_NEIGHBOR最近邻插值防止插值产生不属于任何类别的无效像素值。数据类型转换与归一化将图像像素值从0-255的整数转换为0-1之间的浮点数image image / 255.0。对于mask确保其值为整数类别索引。数据增强仅训练集为了提升模型泛化能力防止过拟合我们需要对训练集进行实时数据增强。例如随机水平翻转、随机亮度/对比度微调、随机小幅旋转等。切记增强只应用于图像mask必须同步进行完全相同的空间变换可以使用tf.image中的相关函数或者使用像albumentations这样的专用库更强大。批处理将处理后的数据打包成一个个批次batch例如batch_size8。预取使用dataset.prefetch(tf.data.AUTOTUNE)让CPU在GPU训练当前批次时提前准备好下一个批次的数据消除I/O瓶颈。这里是一个简化的数据管道构建代码片段import tensorflow as tf import os IMG_SIZE (256, 256) BATCH_SIZE 8 NUM_CLASSES 21 # VOC包含20个物体类背景 def parse_image(img_path, mask_path): # 读取图像 image tf.io.read_file(img_path) image tf.image.decode_jpeg(image, channels3) image tf.image.resize(image, IMG_SIZE) image tf.cast(image, tf.float32) / 255.0 # 读取mask mask tf.io.read_file(mask_path) mask tf.image.decode_png(mask, channels1) # 单通道索引图 mask tf.image.resize(mask, IMG_SIZE, methodnearest) mask tf.cast(mask, tf.int32) # 确保是整数类型 # 处理VOC mask中可能存在的边界像素值255将其归为背景0 mask tf.where(mask 255, 0, mask) return image, mask def create_dataset(image_dir, mask_dir, batch_size8, is_trainingTrue): img_paths sorted([os.path.join(image_dir, f) for f in os.listdir(image_dir) if f.endswith(.jpg)]) mask_paths sorted([os.path.join(mask_dir, f) for f in os.listdir(mask_dir) if f.endswith(.png)]) dataset tf.data.Dataset.from_tensor_slices((img_paths, mask_paths)) if is_training: dataset dataset.shuffle(buffer_size1000) dataset dataset.map(parse_image, num_parallel_callstf.data.AUTOTUNE) dataset dataset.batch(batch_size) dataset dataset.prefetch(buffer_sizetf.data.AUTOTUNE) return dataset # 假设你有 train_images/, train_masks/, val_images/, val_masks/ 文件夹 train_dataset create_dataset(path/to/train_images, path/to/train_masks, BATCH_SIZE, is_trainingTrue) val_dataset create_dataset(path/to/val_images, path/to/val_masks, BATCH_SIZE, is_trainingFalse)实操心得数据管道的构建是项目成功的关键一步也是最容易出bug的地方。务必写一个小脚本可视化几个批次的数据和mask检查图像是否正常、尺寸是否正确、mask的像素值是否在预期的类别索引范围内如0-20、以及数据增强是否被正确同步应用。我经常因为mask读取错误通道数不对或归一化范围错误而浪费数小时调试模型不收敛的问题。4. U-Net模型构建用Keras函数式API“搭积木”理解了数据流现在我们来搭建U-Net模型。我们将使用Keras的函数式API因为它能清晰地定义具有多输入/多输出或分支结构的模型。4.1 核心组件卷积块与转置卷积首先定义两个可复用的基础组件卷积块Conv Block这是U-Net的基石通常由两次“卷积激活批归一化”操作组成。批归一化BatchNorm能加速训练并提升模型稳定性。上采样块UpSampling Block在解码器中我们需要扩大特征图尺寸。这里有两种主流方式转置卷积Transpose Convolution和上采样卷积Upsampling2D Conv2D。转置卷积可以学习上采样的参数但有时会产生“棋盘格”伪影上采样卷积更稳定但计算量稍大。为了教学清晰我们先使用Conv2DTranspose。from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPooling2D, Conv2DTranspose, Concatenate, Input from tensorflow.keras.models import Model def conv_block(input_tensor, num_filters): x Conv2D(num_filters, (3, 3), paddingsame)(input_tensor) x BatchNormalization()(x) x Activation(relu)(x) x Conv2D(num_filters, (3, 3), paddingsame)(x) x BatchNormalization()(x) x Activation(relu)(x) return x def upsample_block(input_tensor, skip_features, num_filters): # 上采样部分 x Conv2DTranspose(num_filters, (2, 2), strides2, paddingsame)(input_tensor) # 跳跃连接拼接来自编码器的特征图 x Concatenate()([x, skip_features]) # 拼接后再经过一个卷积块 x conv_block(x, num_filters) return x4.2 组装U-Net模型现在像搭积木一样从输入开始构建编码器路径记录下每一层的输出作为后续跳跃连接的“桥墩”然后构建解码器路径并与对应的编码器输出拼接。def build_unet(input_shape(256, 256, 3), num_classes21): inputs Input(shapeinput_shape) # 编码器 (下采样) # Block 1 c1 conv_block(inputs, 64) p1 MaxPooling2D((2, 2))(c1) # Block 2 c2 conv_block(p1, 128) p2 MaxPooling2D((2, 2))(c2) # Block 3 c3 conv_block(p2, 256) p3 MaxPooling2D((2, 2))(c3) # Block 4 (瓶颈层) c4 conv_block(p3, 512) p4 MaxPooling2D((2, 2))(c4) # 桥接层 b5 conv_block(p4, 1024) # 解码器 (上采样) # Block 6 u6 upsample_block(b5, c4, 512) # 上采样并与c4拼接 # Block 7 u7 upsample_block(u6, c3, 256) # 上采样并与c3拼接 # Block 8 u8 upsample_block(u7, c2, 128) # 上采样并与c2拼接 # Block 9 u9 upsample_block(u8, c1, 64) # 上采样并与c1拼接 # 输出层 # 使用1x1卷积将通道数映射为类别数 outputs Conv2D(num_classes, (1, 1), paddingsame, activationsoftmax)(u9) # 注意这里激活函数是softmax因为我们希望对每个像素的多个类别输出进行概率归一化。 model Model(inputs, outputs, nameU-Net) return model # 创建模型 model build_unet(input_shape(256, 256, 3), num_classes21) model.summary() # 打印模型结构检查参数和尺寸变化运行model.summary()你会看到模型各层的输出形状。确保在每次下采样后特征图尺寸减半通道数翻倍上采样过程则相反。这是U-Net设计的精髓。注意事项输出层的激活函数是softmax这意味着对于每个像素位置i, j模型会输出一个长度为num_classes的向量表示该像素属于各个类别的概率所有类别概率之和为1。这是多类别分类的标准做法。如果你的任务是二分类如前景/背景输出层可以用1个通道配合sigmoid激活函数此时损失函数也应相应改变。5. 模型训练配置损失函数、评估指标与优化器模型构建完成但直接开始训练很可能效果不佳。我们需要为语义分割任务“量身定制”训练配置。5.1 损失函数Sparse Categorical Crossentropy我们的标签mask是单通道的整数索引图例如像素值是“12”。而模型输出是经过softmax后的多通道概率图形状为(batch, height, width, num_classes)。我们需要一种损失函数能直接比较整数标签和概率分布。tf.keras.losses.SparseCategoricalCrossentropy正是为此而生。它内部会自动将整数标签如12转换为一个one-hot向量第12位为1其余为0然后与模型的softmax输出计算交叉熵损失。这避免了我们先对标签进行one-hot编码的步骤节省了内存和计算。loss_fn tf.keras.losses.SparseCategoricalCrossentropy(from_logitsFalse) # from_logitsFalse因为模型输出已过softmax5.2 评估指标IoU与Accuracy对于分割任务仅看像素准确率Accuracy是不够的。如果90%的像素都是背景模型即使把所有像素都预测为背景也能获得90%的准确率但这毫无意义。我们需要更能反映分割质量的指标。交并比Intersection over Union, IoU是语义分割的核心评估指标。对于每个类别计算模型预测区域和真实标注区域的重叠面积与并集面积的比值。均值交并比mIoU是所有类别IoU的平均值是衡量模型整体性能的金标准。Keras没有内置IoU指标但我们可以轻松自定义import tensorflow as tf class MeanIoU(tf.keras.metrics.MeanIoU): # 继承并重写update_state方法使其能直接处理Sparse标签和概率输出 def update_state(self, y_true, y_pred, sample_weightNone): # y_pred是概率分布取argmax得到预测的类别索引 y_pred tf.argmax(y_pred, axis-1) # 调用父类方法计算IoU return super().update_state(y_true, y_pred, sample_weight) # 也可以使用TensorFlow Addons库中的现成实现如果已安装 # from tensorflow_addons.metrics import MeanIoU我们同时监控SparseCategoricalAccuracy像素准确率和MeanIoU。5.3 优化器与学习率策略优化器选择常用的Adam它的自适应学习率特性对大多数任务都工作良好。学习率Learning Rate, LR是训练中最重要的超参数之一。一开始可以使用一个较大的LR如1e-3快速下降然后在训练过程中逐渐减小它以便在后期更精细地调整参数找到更优的局部最优点。这被称为学习率调度。initial_learning_rate 1e-3 lr_schedule tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate, decay_steps10000, # 每10000步衰减一次 decay_rate0.96, # 衰减率为0.96 staircaseTrue) optimizer tf.keras.optimizers.Adam(learning_ratelr_schedule)5.4 编译与训练模型将上述所有组件组合编译模型并开始训练。model.compile(optimizeroptimizer, lossloss_fn, metrics[accuracy, MeanIoU(num_classes21, namemiou)]) # 定义回调函数用于在训练过程中保存最佳模型、动态调整学习率、记录日志等。 callbacks [ tf.keras.callbacks.ModelCheckpoint(best_unet_model.h5, monitorval_miou, modemax, save_best_onlyTrue, verbose1), tf.keras.callbacks.ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, verbose1), tf.keras.callbacks.EarlyStopping(monitorval_loss, patience15, restore_best_weightsTrue), tf.keras.callbacks.TensorBoard(log_dir./logs) ] # 开始训练 epochs 50 history model.fit( train_dataset, validation_dataval_dataset, epochsepochs, callbackscallbacks )训练过程会输出每个epoch的训练和验证损失、准确率及mIoU。观察这些指标的变化理想情况训练和验证损失同步下降准确率和mIoU同步上升。过拟合训练损失持续下降但验证损失在某个点后开始上升。这时需要更多数据增强、模型简化如Dropout或提前停止EarlyStopping。欠拟合训练损失和验证损失都很高且下降缓慢。可能需要更复杂的模型、更长的训练时间或调整学习率。6. 模型预测与结果可视化见证成果的时刻训练完成后我们加载保存的最佳模型对新的图像进行预测并将结果可视化这是最有成就感的一步。6.1 单张图像预测流程预测流程本质上是前向传播输入图像 - 模型 - 输出概率图 - 后处理。import numpy as np import matplotlib.pyplot as plt def predict_and_visualize(model, image_path, img_size(256, 256)): # 1. 读取并预处理图像 original_img tf.io.read_file(image_path) original_img tf.image.decode_jpeg(original_img, channels3) original_img tf.image.resize(original_img, img_size) input_img tf.cast(original_img, tf.float32) / 255.0 input_img tf.expand_dims(input_img, axis0) # 增加batch维度 - (1, H, W, 3) # 2. 模型预测 predictions model.predict(input_img) # 输出形状 (1, H, W, num_classes) pred_mask tf.argmax(predictions[0], axis-1) # 取概率最大的类别索引形状 (H, W) pred_mask pred_mask.numpy().astype(np.uint8) # 3. 可视化 fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(original_img.numpy().astype(np.uint8)) axes[0].set_title(Original Image) axes[0].axis(off) # 将索引mask转换为彩色mask需要预定义的color map # 这里假设我们有一个函数 index_to_rgb 能将索引值映射回VOC颜色 # color_mask index_to_rgb(pred_mask) # axes[1].imshow(color_mask) # 为了简单演示我们先直接显示索引图灰度 axes[1].imshow(pred_mask, cmapjet) axes[1].set_title(Predicted Mask (Index)) axes[1].axis(off) # 可以尝试将预测mask叠加到原图上 # 这里需要先将pred_mask通过color map转为RGB再以一定透明度叠加 # overlay overlay_mask(original_img, color_mask, alpha0.5) # axes[2].imshow(overlay) axes[2].imshow(original_img.numpy().astype(np.uint8)) axes[2].imshow(pred_mask, cmapjet, alpha0.5) # 简单叠加灰度图 axes[2].set_title(Overlay) axes[2].axis(off) plt.tight_layout() plt.show() # 使用保存的最佳模型 best_model tf.keras.models.load_model(best_unet_model.h5, custom_objects{MeanIoU: MeanIoU}) predict_and_visualize(best_model, path/to/your/test_image.jpg)6.2 结果分析与调优方向运行预测后仔细观察输出主体分割是否准确模型是否能识别出主要物体的大致轮廓。边界是否清晰边缘模糊往往是下采样过程中细节丢失导致的U-Net的跳跃连接就是为了缓解这个问题。如果边界依然很差可以尝试在解码器中使用更多的特征通道或者使用更精细的上采样方式如双线性插值卷积。小物体是否被忽略小物体在多次下采样后可能信息丢失严重。可以尝试使用不同尺率的空洞卷积ASPP来捕捉多尺度信息或者使用注意力机制。类别混淆情况查看哪些类别经常被预测错误。可能是这些类别的训练样本不足或者它们在视觉上非常相似例如“沙发”和“椅子”。可以考虑使用类别权重class weights来在损失函数中给予样本少的类别更高的权重。7. 常见问题与排查技巧实录在实际操作中你几乎一定会遇到下面这些问题。我把它们和我的排查经验记录下来希望能帮你节省大量时间。问题1训练损失Loss不下降或者为NaN。可能原因与排查学习率过大这是最常见的原因。过大的LR会导致参数更新步伐太大在损失平面上“跳跃”无法收敛甚至发散。解决方案将初始学习率调小一个数量级如从1e-3改为1e-4或者使用学习率预热Warmup策略。数据预处理错误检查输入图像的像素值范围。是否已经归一化到[0,1]或[-1,1]标签mask的值是否在预期的类别索引范围内如0-20一个超出范围的标签值会导致损失计算出错。解决方案在数据管道中加入断言或打印语句检查一批数据的tf.reduce_min和tf.reduce_max。损失函数与输出层不匹配如果你在输出层使用了softmax激活函数但损失函数设置了from_logitsTrue或者相反都会导致计算错误。解决方案确保一致性。softmax输出配from_logitsFalse无激活线性输出配from_logitsTrue。梯度爆炸如果损失突然变成NaN可能是梯度爆炸。解决方案尝试梯度裁剪optimizer tf.keras.optimizers.Adam(clipvalue1.0)或者在每个卷积后增加更严格的批归一化。问题2模型过拟合严重验证集指标远低于训练集。可能原因与排查训练数据太少或缺乏多样性这是根本原因。解决方案加强数据增强。除了简单的翻转、旋转可以尝试色彩抖动、随机裁剪、CutMix等更高级的增强技术。使用albumentations库可以方便地实现复杂的增强流水线并确保图像和mask同步变换。模型过于复杂相对于数据量你的U-Net可能太“深”或太“宽”通道数太多。解决方案减少卷积层的通道数如从64/128/256/512改为32/64/128/256或者在瓶颈层和卷积块中加入Dropout层。训练时间太长模型在训练集上“记忆”了噪声。解决方案使用更激进的早停EarlyStopping回调并配合模型检查点ModelCheckpoint保存验证集上最好的模型而不是最后一个epoch的模型。问题3预测结果全是背景或者某个主导类别。可能原因与排查类别极度不平衡数据集中背景像素可能占90%以上模型会倾向于预测背景来最小化损失。解决方案在损失函数中引入类别权重。tf.keras.losses.SparseCategoricalCrossentropy支持sample_weight参数你可以根据每个类别的频率计算其权重频率越低的类别权重越高并在训练时传入。模型初始化或学习率问题模型可能一开始就陷入了不好的局部最优。解决方案尝试不同的随机种子或者使用更小的学习率配合学习率热身。问题4训练速度很慢。可能原因与排查没有使用GPU确认TensorFlow是否检测到了你的GPUtf.config.list_physical_devices(GPU)。数据管道是瓶颈tf.data管道没有正确配置预取prefetch。解决方案确保在数据集最后调用.prefetch(tf.data.AUTOTUNE)并适当增加map操作的并行数num_parallel_calls。图像尺寸过大将输入尺寸从512x512降低到256x256可以显著减少计算量和内存占用通常对精度影响不大是加速训练的有效手段。一个实用的调试流程清单从小开始先用1-2张图片过拟合你的模型。如果模型连一两张图都学不会训练损失快速下降那说明代码有根本性错误。可视化一切可视化你的输入图像、预处理后的图像、标签mask、数据增强后的结果、模型第一层的输出特征图。确保数据在进入模型前是你期望的样子。监控中间值在自定义层或回调函数中打印或记录张量的均值、标准差、最大值、最小值检查是否有异常值。简化模型先用一个只有2-3层的迷你U-Net训练确保流程能跑通再逐步增加深度和宽度。走到这一步你已经完成了一个完整的、可运行的语义分割项目。从数据准备、模型构建、训练配置到预测评估每一个环节你都亲手实践过了。U-Net只是一个起点掌握了这个流程你就可以去尝试更复杂的模型如DeepLabV3、SegFormer在更大的数据集如Cityscapes、ADE20K上挑战更困难的任务。深度学习的实践能力正是在这样一次次的“跑通-调优-理解”循环中积累起来的。记住看懂十篇论文不如亲手实现一个模型。希望这份详细的“保姆级”指南能成为你进入图像分割世界的第一块坚实跳板。如果在复现过程中遇到任何问题回顾一下“常见问题”部分或者去相关的社区搜索你会发现你遇到的绝大多数坑我们都已替你踩过。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻