QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 11180|回复: 0
打印 上一主题 下一主题

Pytorch实战语义分割(VOC2012)

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-4 15:03 |只看该作者 |正序浏览
    |招呼Ta 关注Ta

    # o8 K7 Q' n; t( O: a" ]
    8 K) Q+ V9 G, a) q' SPytorch实战语义分割(VOC2012)
    0 Z# p$ Q( ?3 D  g5 G本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    - a& A; D. R6 z$ k/ }语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。  p/ t3 i. X8 k$ A' D! x  l

    5 s' y$ E) |4 L; M5 `6 k! B+ P7 `# A0 I% M; _
    语义分割中图像有关狗、猫和背景的标签
    ' W& e( R4 b1 S" D+ V文章目录8 i  y2 ?6 n$ W6 j) @& ~/ H, O: l
    7 r) P4 |1 |* E' k
    1 图像分割和实例分割$ R# G$ S, J. p2 d$ `1 x% E
    2 Pascal VOC2012语义分割数据集
    , i  s7 b. M/ F' F, a4 ?: d2.1 导入模块
    1 A& x8 O6 A( F" V5 w2.2 下载数据集4 Y. N" m: ?; d7 r! d) T3 W3 g
    2.3 可视化数据
    / u( F" x7 d# f( G& ?4 _$ {" V2.4 预处理数据$ A6 }) x; M1 K4 [. G
    3 自定义数据集类  m. E& ^, P7 H
    3.1 数据集类$ v9 Q  Y0 j: O) B1 x3 I: {; O
    3.2 读取数据集
    3 X  U3 J! F8 p( w" S; i4 构造模型
    . z% A6 l2 E: }0 }* z6 f4.1 预训练模型3 _' b. z4 x( ?8 }$ ?4 X
    4.2 修改成FCN- H: v4 J! z& _0 w7 R& [
    4.3 初始化转置卷积层0 m. I4 w% y/ A5 h  R  ~; A
    5 训练模型" Y. J- h2 K9 C* a! [- u8 m7 Y- `
    6 测试模型6 P1 W3 V9 k4 K  [7 b' z6 J
    6.1 通用型
    3 _$ T- |. q: v& Z, ^9 B1 z0 Q, k6.2 不通用7 s7 ~5 L  E& N) r+ G2 {
    7 结语
    , [8 h& N1 A$ s) d6 H3 B1 图像分割和实例分割! z1 g6 i  I( e$ a+ y

    2 D3 `* l" O; D( }. u计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):  \# l! \5 X7 I9 c/ X
    ; Q! Z! A0 C* c; `7 {
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。- p) f% R4 m1 _: i( V0 r& }
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。" r: B. p; p0 V

    : _' ]* y5 f" |2 Pascal VOC2012语义分割数据集
    + \) s- I0 S$ Q7 [; m+ _7 Z! U' _& f+ c1 T9 s- `3 h: g
    2.1 导入模块7 k" b6 C3 s! e' \0 V; ?3 G8 n
    import time, I. X* W- D. w6 r/ G; t
    import copy0 ?7 o, U! D, @$ I, O4 j8 ?- V
    import torch' }/ z1 B4 j3 w
    from torch import optim, nn
    1 N# z4 o5 V$ ?: Y! Q- fimport torch.nn.functional as F
    0 G8 D: q  I5 y/ w4 u2 fimport torchvision3 s. b6 C& J# r- }
    from torchvision import transforms
    2 Y* _3 V+ Y( M+ A8 s0 l3 g1 d, U% Zfrom torchvision.models import resnet18
    ; ~% Q3 @- ^  H8 [$ \) himport numpy as np
    2 q3 E5 h0 y+ n  Ufrom matplotlib import pyplot as plt
    2 m) k7 }! {3 P; Gfrom PIL import Image
    % O3 t( Q0 K2 o' `( u. t6 ]% Himport sys+ o5 M( b' `% t8 s; g
    sys.path.append(".."), X# S8 D# z# l" [  R3 y7 K
    from IPython import display4 ?" n! D% T/ h  _
    from tqdm import tqdm
    ' z) K6 @4 ?% Uimport warnings
    1 C: E, P) ]4 g3 lwarnings.filterwarnings("ignore")
    ) y6 P  k, i9 ]- [. _( d- X$ }4 R0 J* B- d3 S
    2.2 下载数据集
    1 z0 G/ f/ V; Y+ k* x
    0 v' U7 i/ ^5 e: {7 V; ]# w语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    5 ^* @4 ]; c0 ~* E8 T1 \# A5 y6 B. [4 F: h1 @. d
    ! S* O! `, v2 D; ?3 {# p, E1 a' [
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件) b  |' ]& _; m' ~& H  Y  W
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。/ u& {- A6 L0 e. w& r
    2.3 可视化数据
    : T0 O/ I8 R9 ]0 c8 |" f% M6 K: i! p5 {8 d4 C
    定义read_voc_images函数将输入图像和标签读进内存。" W3 q# f5 X4 c  H: l1 P6 ]) l& M
    % y3 \; |1 f( ~* G
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    4 J  ~# A+ g  p- l" s$ q    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    - `1 u2 v" h2 C. D7 s    with open(txt_fname, 'r') as f:0 G' j$ w0 f) X% |
            images = f.read().split() # 拆分成一个个名字组成list
    * D2 k0 k% ^" g0 ^; X. K' n    if max_num is not None:
    7 M# Y+ }( c) `0 f        images = images[:min(max_num, len(images))]% _7 \' n  s+ G8 |$ Z% k
        features, labels = [None] * len(images), [None] * len(images)
    * x8 i4 n, k. ~    for i, fname in tqdm(enumerate(images)):8 }7 }4 O0 N9 V6 I
            # 读入数据并且转为RGB的 PIL image3 q7 n! V& F( b4 S+ u8 h
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")" q) g1 A# @4 k% M- [2 C! f
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")
    ! z9 ]+ G: G! Q4 d" G# }    return features, labels # PIL image 0-255
    5 W3 A2 k1 O) _5 O& B7 R% f1 u
    * F) H2 s5 v6 D' W+ {# \: r定义可视化数据集的函数show_images
    1 d( I5 Y# K, P" v
    1 f, h/ y0 o" N# 这个函数可以不需要) p+ c& i- n* r/ l8 n
    def set_figsize(figsize=(3.5, 2.5)):) I! X8 f# E+ P  A
        """在jupyter使用svg显示"""
    $ N, }. h; e- W- f3 h6 V  Q9 i    display.set_matplotlib_formats('svg'), _, q: J% ?  `/ m+ s1 w5 _
        # 设置图的尺寸) t: Q: T5 ?( v
        plt.rcParams['figure.figsize'] = figsize3 F, y, n* E) _) i1 K

    1 s: T& h$ l* {1 Jdef show_images(imgs, num_rows, num_cols, scale=2):
    ) ?- r1 X* P5 q$ E( d9 u6 n6 E: R    # a_img = np.asarray(imgs); b& x3 R. h  E/ e
        figsize = (num_cols * scale, num_rows * scale)
    , G4 o& l, {( Z9 n    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    4 n4 v, K1 k8 O. ^' A/ E    for i in range(num_rows):
    & H* P; R5 J' w        for j in range(num_cols):0 f4 v% Y! C6 q0 b1 k8 ~9 p
                axes[j].imshow(imgs[i * num_cols + j])/ Z7 Z0 p/ Y6 j" Z: s* J7 x
                axes[j].axes.get_xaxis().set_visible(False)
    8 ^# p+ a  U+ v3 |1 m, Z            axes[j].axes.get_yaxis().set_visible(False)4 g. P9 {0 Q( p) X: Z
        plt.show()
    % \+ Y: s  F! `  m5 H0 |    return axes
    . I9 H! y: u" b8 `- d2 p+ u/ U) Y& u% G5 u  _- s0 ?; i, C' A
    定义可视化数据集的函数show_images
    ! N: P: w3 p& A: S
    2 F3 y! t7 O* G1 K8 Q; W# 这个函数可以不需要
    & b2 f. y' n, s& H; s( Q* u3 adef set_figsize(figsize=(3.5, 2.5)):
    ) X+ w; \, t1 [/ ~2 Y+ L% v# n/ ~: b    """在jupyter使用svg显示""") h& A" \$ B$ U& z; @. l0 b
        display.set_matplotlib_formats('svg')9 r8 W# T! r6 z  Y& S+ Y$ j4 w
        # 设置图的尺寸* Y5 w' N+ _1 l8 r/ x
        plt.rcParams['figure.figsize'] = figsize9 l* B. d( M2 ~) P: J7 x/ w5 i
    0 F; K; s$ |7 N7 i/ l
    def show_images(imgs, num_rows, num_cols, scale=2):
    ' M5 ?6 r( C; l* r6 n/ V8 A# e7 t    # a_img = np.asarray(imgs)3 R: G! P( q; K; r# o# o
        figsize = (num_cols * scale, num_rows * scale)
    ; d! A! R  W- e9 s9 ?# E, O% D    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)4 |, t9 a" ^; y3 b: Q- I( v0 v
        for i in range(num_rows):' J& U0 t; M( T! ^! v' D
            for j in range(num_cols):
    , F/ K8 U: E( l9 ]. G            axes[j].imshow(imgs[i * num_cols + j])) ~( }$ Y0 I6 v
                axes[j].axes.get_xaxis().set_visible(False)
    . U! p7 \" S) ?2 G. V" r            axes[j].axes.get_yaxis().set_visible(False)
    ( r* U2 e0 l/ k" _  K    plt.show()
    " K+ I2 i5 L0 i) U$ S* m# \    return axes
    $ f' o' n3 J5 E5 e* |画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。7 \) g6 e1 |2 H# l* Y9 O/ i0 u

    2 `2 t* X) \  p5 C% c) \* b# 根据自己存放数据集的路径修改voc_dir
    2 O' e/ M8 y- n; a& Tvoc_dir = r"[local]\VOCdevkit\VOC2012"
    6 N& ?" k$ b/ H2 Dtrain_features, train_labels = read_voc_images(voc_dir, max_num=10): u' H2 g% h) u1 @" [5 i/ g
    n = 5 # 展示几张图像0 r* Y" z) G4 s- r* R
    imgs = train_features[0:n] + train_labels[0:n] # PIL image- F: x' v7 I: ^* O3 @
    show_images(imgs, 2, n)$ U; B' d# [4 q: e7 K+ I# m

    ) q# r+ t: U/ U7 A3 k 1.png
    5 j% ]% @; o* _
    9 f) _3 ^1 D1 k  V# [列出标签中每个RGB颜色的值及其标注的类别。
    % G) _* f, W7 C, }# 标签中每个RGB颜色的值
      [  |/ E. O5 gVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    8 A" M$ R! G( P  ^! |8 R9 h2 _                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],/ A8 |* O, U5 _" A5 ]
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    # c& Q! u3 R, K5 Y$ W                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128]," }4 k1 w7 L, I- e6 q* Q
                    [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    # R: S8 j4 r& ?1 A4 _, x6 y9 L& I                [0, 64, 128]]* [, [5 J: S" U. F
    # 标签其标注的类别
    : F* [$ w9 H! X; M  b. y- [6 BVOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',2 D) G' V! g6 ~2 E1 l8 S
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    * N& j2 z7 ^" c8 h) m) a9 J               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    , k* `* P3 {" J! L               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']& b( v0 ]/ f* o, m0 Q7 H9 X( O
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。7 ^( c2 I& [/ a% q; i
      ?3 x- ^4 S9 [2 T& m
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。& s* E! d8 v- V, C3 v9 f7 P& U
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    ' [1 P; A+ [# P0 _& b8 K5 ~. T; afor i, colormap in enumerate(VOC_COLORMAP):
    8 v5 O2 x- I0 P" X8 h4 b5 S9 f9 U    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i
    5 N2 y# O8 u6 F) ^    colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i' g0 T1 s9 u6 L8 @4 P
    # b1 j- V6 U6 y% L7 e5 u7 U: r
    # 构造标签矩阵
    2 _1 M1 X2 m3 b, @3 |4 Y, v3 y0 Rdef voc_label_indices(colormap, colormap2label):$ I' E; {* Q( i9 G! B
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    ! J. C( q5 v' W% n    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    ; t4 G; R/ e$ `4 X/ S7 p    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    5 R8 e  M0 k& R6 v7 }" ~3 [/ W3 v& V
    可以打印一下结果
    ( @& g1 X% B$ \: h+ v) s; N; l) K) B" @( {$ C# b6 G  `- T! b
    y = voc_label_indices(train_labels[0], colormap2label)* A8 A$ U# k$ ~4 F( y6 O, c! {
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES9 p' t7 A5 }# O* e- X1 W7 t% u4 r

    : I8 _! E$ \! `- m3 Q' o* W2.4 预处理数据
    ; Z& F. N, x" E; h5 k, Y
    % x3 _+ r" Z6 w% I! _在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。! i$ _8 [: g2 w9 W9 \

    1 ]- n4 C% _# C/ U, Y1 X2 A# W
    7 O9 X: n% p1 R( ^  Ndef voc_rand_crop(feature, label, height, width):
    ' `6 W3 ^7 X, X- ]2 P% Y8 [: R4 ?* s    """) |2 k" c. m1 }
        随机裁剪feature(PIL image) 和 label(PIL image).$ \* {  a7 t" k% m" [& W
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做* ]: @" D. C5 s( h% U
        Get parameters for ``crop`` for a random crop.
    ( P3 i8 l0 c) l& w4 ^: _& T( p0 l    Args:# I: f3 C* K7 U, `, v7 J# H
            img (PIL Image): Image to be cropped.
    % N8 s7 }& ^+ u8 M        output_size (tuple): Expected output size of the crop.
    ! z& L: K- M3 Q4 e( y( w    Returns:! i; M2 h* U1 r6 y! R
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.) `- k" Y% @: a* k
        """
    ! b) |+ z) n1 d6 B% N) m" e    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width)). t' i7 d' ]- r* H+ {% C' W1 v
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w): a& J: w4 y# U4 j8 o" L
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    : T8 r2 w: @& n+ X% t    return feature, label
    1 S7 p5 [+ l4 P* \8 p; Q
    - p" k4 x. H  T2 X# 显示n张随机裁剪的图像和标签,前面的n是5
    9 ^5 s) J# b4 s5 ^9 H1 timgs = []
    , i2 h$ W% \; m+ }0 a+ J+ ^' ofor _ in range(n):" z7 V5 E, J' Y
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    1 K* G6 |+ A: g; a) ashow_images(imgs[::2] + imgs[1::2], 2, n);% u' L. q9 Z$ g' E( T+ U
    4 Q/ m+ x  Q/ j+ |( i# O8 Z1 H# X& g- i

    9 n( J( b' k: d' L" h# T3 ~! F0 k 2.png
    4 j; V  \6 z$ E% ^/ T
    - C9 @0 t5 T* f/ \! U" C4 a
    0 [  {% r. R  F7 x5 @, y* v/ C0 S, ~; Q0 {2 i5 `% o  C
    3 自定义数据集类. k3 L/ B' s/ S  W0 a
    % ~) U; {6 J$ d
    3.1 数据集类
    - L, T. O& s$ a* f* r
    # G8 q) U* U6 q6 O4 [1 i  \( Vtorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    , `4 v/ H3 U4 b: g" p
    / @$ o. J& W  C. ]__len__ 实现 len(dataset) 返还数据集的尺寸。
    7 a5 \# V6 t4 E) G__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    ; o- [! L' d2 E( e, T1 ?/ `8 d0 K% A+ w由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。1 q5 T6 ?" x: r

    0 O2 O5 x  o( L+ Zclass VOCSegDataset(torch.utils.data.Dataset):
      m$ q+ q9 l$ ?6 B8 I; }1 O    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):4 T3 E0 d  g, ?6 d
            """/ Y: V! Q/ R; B2 i
            crop_size: (h, w)
    6 O' C8 ]: S+ o% i' n+ i% y0 T& s        """) K, S* G/ F6 B0 I% n
            # 对输入图像的RGB三个通道的值分别做标准化( y5 @; E8 l0 q0 g$ b# I  h
            self.rgb_mean = np.array([0.485, 0.456, 0.406])
    # I5 H" D4 O5 v6 ?" G/ {        self.rgb_std = np.array([0.229, 0.224, 0.225])
    . E# m( N3 v  t- Y6 @, B; D. T8 A% o        self.tsf = torchvision.transforms.Compose([* C# Z( {% h; E; G9 H! i$ }
                torchvision.transforms.ToTensor()," J9 R' _" R) @; K* l: ?  z7 t
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    " m5 O. d% E, X. c+ q2 c        self.crop_size = crop_size # (h, w)1 r: Q* y1 w4 b
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)) U) \+ m- e: M! }
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除4 p8 Q1 G" n* m, n" q9 t8 z
            self.features = self.filter(features) # PIL image" {8 J/ H$ l) U) @: e8 n  `
            self.labels = self.filter(labels)     # PIL image
    ! Z( L8 r+ J, S7 d1 O        self.colormap2label = colormap2label
    1 L4 q& L8 ^# O- a4 ^: T        print('read ' + str(len(self.features)) + ' valid examples')
    $ Q- B7 i( n7 Z1 j/ B* h
    ' L" B. ^: ^- Q% E    def filter(self, imgs):4 y! N3 l- o1 c- Z' ?! S5 d
            return [img for img in imgs if (
    $ l  B  C! N6 s  O            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    2 M9 K9 t, |0 l+ g! i* D& T" {" Q- U0 |4 R
        def __getitem__(self, idx):
    ! `( O+ b7 s# v5 ^5 y' y+ O        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)% {5 W8 f3 W! ?& F6 f8 U2 H7 b2 M
                                    # float32 tensor           uint8 tensor (b,h,w)
    , ?" Z- L' g2 z4 ~& x- U  a. Z        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    2 ~0 \; x& l; N1 y. ]# f; }4 \6 t: Q1 e- p( _- Q8 o; ?
        def __len__(self):' P$ ^5 |* p& [( Z% @
            return len(self.features)
    6 F, F1 q7 n; b9 i% `0 I7 ]5 J+ c3.2 读取数据集
    0 @; L' ?3 X6 J; U
    / t- U# G1 S; o* ?! a: u4 z通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    + l1 k" g$ F, f3 q+ g, ]
    9 u0 x7 s; F1 B/ }! ibatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
    ; Q' V( V( w0 a4 a$ xcrop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480), y# d4 A) B, k  I% k
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    * ]5 a# |# h/ e& ?
      h# O( d% x% h8 d' |* [# 创建训练集和测试集的实例" a: o6 c% F. ?
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    9 N( R# [" C" P8 t6 ?voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)# h9 G/ G, j: K1 N% d% H% R$ D) v6 J; J

    ( |& ?! d  {+ J# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器/ E# F+ Z6 R  q5 @! R' M
    num_workers = 0 if sys.platform.startswith('win32') else 4
    3 H7 N. w8 d; t9 @; U& Atrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    6 @& B% v1 k: I' h" i2 r                              drop_last=True, num_workers=num_workers)' L) [& U5 c* D" J( ?0 R
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,+ K, b* T1 _3 u: Y3 C; ]
                                 num_workers=num_workers)4 f* A" L- Z; t3 Q" K$ T, A
    7 j' W9 V8 ~- \
    # 方便封装,把训练集和验证集保存在dict里" H7 G' d2 s3 O
    dataloaders = {'train':train_iter, 'val':test_iter}% P% M; U0 J- y% a
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    # C) `7 V7 ^/ K! @5 g* X/ G. d! I( {3 F+ r* H1 y1 O0 {1 H% L
    4 构造模型4.1 预训练模型

    下⾯我们使⽤⼀个基于 ImageNet 数据集预训练的 ResNet-18 模型来抽取图像特征。

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    ' o) Q5 x/ f1 m$ `
      z, j9 `2 @3 Z; Lnum_classes = 21 # 21分类,1个背景,20个物体0 v/ l! R. U) E* g4 k8 g
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数2 m1 c, [" k3 [2 E8 E. _  E
    ( A* X5 Z& n6 i- U6 M
    # 特征提取器
    / S) X1 Q/ w6 r/ e6 f1 I( ofor param in model_ft.parameters():
    ; Z0 t0 z/ r1 R. ]0 k    param.requires_grad = False
    ; g( i9 }1 u+ N, `4.2 修改成FCN
      P  ?- V  g. g5 m7 {6 |0 u8 `, ~1 g8 ]: q  j9 q
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:. L! G& Z  w5 z& a. |
    最终输出的通道包含了该空间位置像素的类别预测。4 V+ o( H; }) |7 d1 I9 \9 p! d

    4 n5 v# F" X  e) G" @对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。2 r1 u; Y1 j8 ^. B4 O; W9 G' G6 f

    9 v8 M# B  i. r+ u可以先打印model_ft,可见 ResNet-18 的最后两层分别是全局最⼤池化层GlobalAvgPool2D 和 全连接层。全卷积⽹络不需要使⽤这些层。通过测试,当输入图像的 size 是(batch,3,320,480) (batch,3,320,480)(batch,3,320,480) 时,通过除最后两层的预训练网络后输出的大小是 (batch,512,10,15) (batch,512,10,15)(batch,512,10,15),也就是 feature featurefeature 的宽高比输入缩小了 32 3232 倍,只需要用转置卷积层将其放大 32 3232 倍即可。
    0 Y4 q# w3 W( M2 r. q% S: G. O2 e
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层5 n' T1 v% ]. U4 b# x( B  ?
                  nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class( T& h# z) j6 ~) ]
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小. X$ t- a, {( W. W5 h
    ) ~$ B" ~* ?. V- @2 p# }7 p
    # 对model_ft做一个测试5 h9 F. R- O5 }/ _8 g; ]* p# |
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    / ]; M$ p4 S/ p0 Fprint(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    " Y/ f7 }) b2 A5 l: c  d: I( e  H/ J5 `7 F0 _& ^
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    " c% _. {% c9 E- ]6 y# for X, Y in train_iter:, B5 j6 E' ^9 ^: g0 _6 ]# m: D
    #     print(X.dtype, X.shape)
    , B* c8 f/ t2 z9 b#     print(Y.dtype, Y.shape)
    / X( G5 {5 E  U* {" U#     break+ L5 V# i* e* H4 @, B0 N! N  }8 W  X
    / |, |, J; `' E0 N" A

    5 t) I8 ~. H; T- o& X4.3 初始化转置卷积层
    - l' N8 y: O9 a: y( }( p/ K% X
    ) x( N1 p- n: g5 Z5 y在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像+ |0 W# E$ [6 m' R: f4 _
    在坐标 (x,y) (x, y)(x,y)上的像素,先将该坐标映射到输⼊图像的坐标 (x',y') (x′, y′ )(x′,y′)。例如,根据输⼊与输出的尺⼨之⽐来映射。映射后的 x' x′x′ 和 y' y′y′ 通常是实数。然后,在输⼊图像上找到与坐标 (x',y') (x′, y′ )(x′,y′)最近的 4 44 个像素。最后,输出图像在坐标 (x,y) (x, y)(x,y)上的像素依据输⼊图像上这4 44个像素及其与 (x',y') (x′, y′ )(x′,y′)的相对距离来计算。双线性插值的上采样可以通过由以下bilinear_kernel函数构造的卷积核的转置卷积层来实现。
    3 H: u; R( e( n% q" d
    4 b$ B. w% [( q) ], W! q. k& h/ M  q  o3 f* O
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核3 V% ?5 i# V! u5 v
    def bilinear_kernel(in_channels, out_channels, kernel_size):
    " R* G  ]7 ?, T) D( `" y; K    factor = (kernel_size+1)//25 ^( W! s/ {7 D7 P* Y+ h
        if kernel_size%2 == 1:
    ! C- K" k' c# Q4 }: J/ i& E. w        center = factor-17 t4 a6 V3 D$ P" H' I
        else:4 ^! g  |% r7 P! q* G% w, L
            center = factor-0.5! c; x* s2 m! U  ^9 E0 \& X: }
        og = np.ogrid[:kernel_size, :kernel_size]7 K1 ~( b" L* n" f& x. v6 C5 {  ~. l
        filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)7 S, I( j' G2 n
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')0 C  J: U+ i- H  C' I) L
        weight[range(in_channels), range(out_channels), :, :] = filt
    ' @7 V" O0 u( |. R' j, Q    weight = torch.Tensor(weight). C- X) w" ~3 w) Y/ V0 F
        weight.requires_grad = True
    ( K) M9 T5 P& c    return weight
    " w% ~5 D7 |( o" U% k- R1 o- M4 }% {; S' n8 g7 u, M
    9 e9 M% ]5 @* O% ^2 T2 U
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。: Y- X$ n9 w! _4 v

    6 t6 ]$ A! }7 ~: Lnn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)9 J# h( Y0 c: j; x" l$ K
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    5 _4 e0 ]9 q0 ]! D9 H6 _. s7 h! ^
    1 Z( }# L; s; N8 Q0 O# c
    ) u; p# o" {; ^& r/ E3 U& X; M2 S9 Z
    5 训练模型

    现在可以开始训练模型了。这⾥的损失函数和准确率计算与图像分类中的并没有本质上的不同。有一个 blog 我认为说的很详细,图也画得很好:https://blog.csdn.net/Fcc_bd_stars/article/details/105158215


    # ^& M- N# J. G1 y; r: c6 odef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    ) S- M1 N* g  i  a  g    since = time.time()
    , g# q; A0 X' }8 v) h3 R7 x; H    best_model_wts = copy.deepcopy(model.state_dict())
    ) g2 y( y; \2 ~1 z: N    best_acc = 0.0
      r7 S7 i( {( w7 y  l    # 每个epoch都有一个训练和验证阶段8 W, h7 t* s  y6 z% {
        for epoch in range(num_epochs):3 S# a0 n1 I$ F! N3 ~# \! a& I  y
            print('Epoch {}/{}'.format(epoch, num_epochs-1))
    8 J. |$ K' {. Z/ V# Z" a5 Z        print('-'*10)4 y  z3 u; F7 k& X: ^$ J
            for phase in ['train', 'val']:
    * b0 f' Z# P$ O( R+ k' D# t            if phase == 'train':1 p' F$ Z' U+ T7 W" D8 `$ o* l
                    scheduler.step()
    5 c( t) z; F! U- Y7 N                model.train()# F1 U, P, K3 W8 m
                else:$ g5 I# ^/ d  M* D3 r
                    model.eval()
    9 I* F1 i+ ?6 C            runing_loss = 0.0
      n2 e1 @2 B6 s1 N! }) R            runing_corrects = 0.0' C: Y+ t% y" X! N% g8 {
                # 迭代一个epoch2 x9 c. Z* a% ~, S: y
                for inputs, labels in dataloaders[phase]:( c; s! J/ O" t
                    inputs, labels = inputs.to(device), labels.to(device)2 U# D( P/ w2 F+ D( U7 A: G
                    optimizer.zero_grad() # 零参数梯度
    # `8 M9 S8 @$ y, o/ j- l) M                                # 前向,只在训练时跟踪参数
    ! R' }' c  f- W3 Q/ {                with torch.set_grad_enabled(phase=='train'):
    9 k7 m3 M! J2 w4 Y                    logits = model(inputs)  # [5, 21, 320, 480]% y9 C9 P9 d# V4 P) @
                        loss = criteon(logits, labels.long())* L/ l6 b4 @/ g
                        # 后向,只在训练阶段进行优化
    ! I# s/ O6 {+ M) O/ m* Y- V+ ]+ ^                    if phase=='train':
    : P( S; q& d- b2 O- g  A* u                        loss.backward()7 f( ~5 f4 a2 S* J7 ]- s: z
                            optimizer.step()9 Q) j1 e( r! S* y9 }, e  j  u- h+ y8 p
                                    # 统计loss和correct
    9 u. L, Z6 G6 G% R) y: j                runing_loss += loss.item()*inputs.size(0)8 s6 j( F: y$ h- j
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    & u* v( i9 ~2 i- c0 I; H! K+ o& p$ x% u5 ?0 j1 g; K- |
                epoch_loss = runing_loss / dataset_sizes[phase]
    " d% B+ o  G" _0 M: R            epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    # a% X  n/ L# E; w/ ^            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    8 w$ S2 ]- s, |2 s- V) m; u                        # 深度复制model参数. o5 J* a, J' b& r' D
                if phase=='val' and epoch_acc>best_acc:. d! h9 K5 J6 O: K, O4 P
                    best_acc = epoch_acc) F% j. K1 o3 o
                    best_model_wts = copy.deepcopy(model.state_dict())
    - K+ s% j9 i1 J+ q' Z" i        print(): j" t5 A+ |% j/ [% h. I
        time_elapsed = time.time() - since;. `, A& i1 y: v! k) G+ r
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60)), y0 u5 V* W) E" `* C( @
        # 加载最佳模型权重' [' r+ R; H( Y
        model.load_state_dict(best_model_wts)+ N) ~$ Q6 ~1 i+ f* b' ]) b
        return model
    2 X9 [9 o$ D9 M  H+ y) _, D: F# L7 M
    下面定义train_model要用到的参数,开始训练
    " b8 K, r1 h9 w5 j* n) p; o+ Q. B! Y! N( Z, V' s6 w+ w& O* N
    epochs = 5 # 训练5个epoch
    7 p" v& V$ z1 I! mcriteon = nn.CrossEntropyLoss()( O; @% \5 O+ P( f. T2 x+ h8 h5 {
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)
    : v, A$ [! R  ~1 w1 F4 r# 每3个epochs衰减LR通过设置gamma=0.15 K2 A% `' p: ^7 L" d+ i5 C
    exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)+ J2 |0 S2 J$ e, V) Z, Q

    . R, @4 l- I/ `: f# 开始训练
    ( O: |$ h2 ~3 w6 `! N1 kmodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    ! v  r. Q; a. t/ J! M8 U- J8 F( Z. ~
    6 测试模型

    为了可视化每个像素的预测类别,我们将预测类别映射回它们在数据集中的标注颜⾊。

    def label2image(pred):
    1 ^7 b% T+ P* e7 N( l    # pred: [320,480]7 g3 \5 a; W+ j* K
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)7 l  J% W7 l6 F6 Q% m
        x = pred.long(): I  e4 ~+ f! Y' k* p5 Z+ o
        return (colormap[x,:]).data.cpu().numpy()
    : e& q: I4 \# s- i! z2 U% c3 y1 R8 [3 V- ^+ \3 v. i

    下面这里提供了两种测试形式

    6.1 通用型

    其实如果要用于测试其它数据集,也是要改动一下的 : ) 😃

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    4 |3 c; O# d0 s/ f& Rstd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)) }; I0 ^4 }- J& l: c. X  v
    def visualize_model(model:nn.Module, num_images=4):
    2 k) j# i6 @5 R" ?    was_training = model.training
    % N4 O# f, b4 E' F8 D3 i8 A8 L    model.eval()& M- E8 g% @0 f4 u! J" D/ b
        images_so_far = 04 j1 R: ~" U' W! m- _* n  h$ d
        n, imgs = num_images, []5 \: ^# y# d+ ~9 I) R
        with torch.no_grad():5 z- N* P, G- g% f$ m! c
            for i, (inputs, labels) in enumerate(dataloaders['val']):% ~5 [! L. P0 `, Y
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
    $ d: {; M; F, ~2 Y/ u% v/ {            outputs = model(inputs)8 K& j# R( k$ g( q
                pred = torch.argmax(outputs, dim=1) # [b,320,480]
    ' ]7 x5 {" {* U' Q  J            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    . H0 S* k6 `2 P  D: G6 O5 I4 Q" Y1 D4 C! D: N9 O/ u6 h
                for j in range(num_images):7 o# V# n/ s. U, d
                    images_so_far += 19 s/ c/ O/ f  G) ^, ?
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    6 H7 g' g4 k+ ~                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]
    ! n* {" B) _2 M7 C                if images_so_far == num_images:1 f( ]: F5 Z! _6 u: L
                        model.train(mode=was_training)
    4 t: r$ s8 r( e) g4 I                    # 我已经固定了每次只显示4张图了,大家可以自己修改
    % Q# Q$ s  L2 p1 O# u+ P                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    1 @1 }6 i" P" z( R7 d" C  I                    return model.train(mode=was_training)
    5 k. q( h# }7 ^: V! o2 F. a
    . I4 W4 j1 b6 S5 u3 }+ J* i. x3 Y# 开始验证
    ! @* d& x5 y' c+ M- W3 Uvisualize_model(model_ft)/ X$ D$ ]3 q# @, t# ?8 b* e
    6.2 不通用

    在预测时,我们需要将输⼊图像在各个通道做标准化,并转成卷积神经⽹络所需要的四维输⼊格式。

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor  R$ [/ S& n* _7 g  o
    def predict(img, model):, y$ b( _3 U3 b6 i* |3 \( i; g
        tsf = transforms.Compose([
    7 T4 o( M( v+ c( w8 M            transforms.ToTensor(), # 好像会自动转换channel5 ?+ p" n. Q' B" S, X, E
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])* V  @5 ~3 \+ h9 m
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)+ L4 a, |& ^: U0 g1 F6 x
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    . s: b0 h0 A+ q! C( ]    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    8 _& h  \; \3 h; e
    ' G: x% w5 H( U/ X  l, m  Ldef evaluate(model:nn.Module):( t3 }: C, g9 f5 s5 {. x9 k
        model.eval()' m% q5 j+ Y! h: j1 O
        test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) ' p' ~% @0 z$ q) ~+ m  {
        n, imgs = 4, []
    - s  y9 u* A" F    for i in range(n):
    $ C1 k- h  c; [2 }5 u' U2 F        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image2 p. m% E" ~9 |
            pred = label2image(predict(xi, model))
    7 L7 F: k( s' e# q; w$ J        imgs += [xi, pred, yi], X3 e2 O" a. G( h
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n), \- B* F8 v9 f9 e7 U0 q* f1 ]
    5 s  `9 W2 T# N' @& ^& O% j
    # 开始测试
      g2 b6 N. ]5 Fevaluate(model_ft)
    6 W# m+ P9 y/ f9 X( ^% @
      u; b, A( \$ A3 d) W# M7 结语

    我只训练了3个epoch,下面是训练输出


    . D# M' O' ]' N" C7 k  y& UEpoch 0/2
    % t3 x/ z* \! [% W# \7 f% P----------0 {4 g2 t5 d+ I
    train Loss: 1.7844 Acc: 0.5835' a  Z5 M4 f% J/ {; T. V/ E
    val Loss: 1.1669 Acc: 0.6456
    ' b7 F- {% B" |/ S
    0 `8 t5 {6 O+ t3 GEpoch 1/27 q7 I& E; `& h# ~( R* j7 h+ X
    ----------
    3 W: w7 P  i. itrain Loss: 1.1288 Acc: 0.6535
    % k% m8 E1 t7 a6 w1 Y# j8 S$ Kval Loss: 0.9012 Acc: 0.6929
    2 R8 c: ^: w) B0 o/ I9 Z' H- p6 {7 w& \. e) i
    Epoch 2/20 b1 q! R2 s  v6 g
    ----------+ @0 ~2 r9 c. c7 C% o( W! s
    train Loss: 0.9578 Acc: 0.67062 a, I  A! N, S# R3 R
    val Loss: 0.8088 Acc: 0.6948$ J5 e( k6 Q) p3 b

    7 A1 P, S) o, oTraining complete in 6m 37s! G) D' N8 X! T4 P' O4 I
    $ ^% T/ Z7 l" ~
    7 ~2 A% a: e) P& B+ @9 a* d/ l
    2.jpg & D3 G# b. S  ]( j7 B

    . v( z' m' K5 |9 i当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。* v5 p' n. {: R8 i- k
    & `% ]* R. v" F5 S' T+ S: Z
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    6 n! q4 @( @, |/ z6 z" T: h+ P2 p

    : G9 m3 `1 A' s- A% d语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:% p7 O* N1 A. r* k
    : |  u, O3 G1 M9 D
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文], R' R9 A7 ^, U# Z+ |+ N. _: O2 p: v% q
    GCN 通过全局卷积网络改进语义分割[论文]' g  }* i4 m- C  c! o' X. ]4 N% ~
    UperNet 统一感知解析. z8 }2 u) a' D: D, x  p
    ENet 用于实时语义分割的深度神经网络体系结构[论文]- ~# z& ?6 J# F5 W5 W+ r
    U-Net 用于生物医学图像分割的卷积网络( ?1 d$ x1 E, _$ a4 s* w2 h; t
    SegNet 用于图像分段的深度卷积编码器-解码器架构。
    1 i! h- L# g5 h3 u# x还有(DUC,HDC)、PSPNet等。
    9 D4 Y6 X" J) E( ~" d6 }" q6 R* ?7 W
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    + l8 F. s, e3 S1 L) k4 Q
    & ^: V, T" O& z4 e: |7 y对于损失函数,除了交叉熵误差,也可以用这些:' K# H) d' J/ |+ y* n% t3 u7 L% v
    % g7 C' k, F' y* ~" r
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。$ Z- M( j8 ]! {' P
    CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。
    9 L) P  z* }) J; j9 R! @5 H: uFocal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    # [- S) T% t% b; D/ @2 t3 s2 n9 MLovasz Softmax 查看论文:Lovasz - softmax损失。4 o6 |+ J! b4 W) m/ y
    6 U% Q) i, m# k

    7 s' C# [8 j, _5 q# q/ W( U+ y4 ?) ]8 ?* k5 Q! r% G3 v

    % v) {9 Q$ ^9 P" c9 A/ I. a$ `+ u* J2 X6 Z8 D
    ————————————————4 ~5 q1 ^% ?9 E9 _8 ]
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" |3 L. W" ?3 H5 B, G
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507, O3 _: X1 \7 T
    7 l2 K6 u5 I$ z+ f$ |& \! A

    # K& z" F* ?) B4 j  I% Q4 d7 G) B0 _; Z; B  J) a) C% q9 N: s; K
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-7-18 12:17 , Processed in 1.174456 second(s), 55 queries .

    回顶部