QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 10087|回复: 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

    ; S" q0 u0 l  N: ]; K+ y
    " K$ D% F/ U6 G( w0 u: MPytorch实战语义分割(VOC2012)
    + a- V* ?( L- Z* k9 f本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    / x; M) Y) A) h) S' x. k; s语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    , c( Q5 V) b* N& }: G# g5 _) D; _
    ! |7 s( P, t$ ?  K( L" s* Q1 \9 }' O! K% w0 F8 i, ]3 z/ z: `4 ]
    语义分割中图像有关狗、猫和背景的标签# z( y7 d; R  n3 E/ p/ l* z
    文章目录4 o/ A0 ~& H5 i& o& U
    % ~$ {2 K) q: H: e. l4 k
    1 图像分割和实例分割/ t( H: L& L: q4 o
    2 Pascal VOC2012语义分割数据集" X5 j( P9 ~  i. _( u$ I
    2.1 导入模块& T& E9 w7 a7 j
    2.2 下载数据集
    & L; ~; t: G7 h/ u0 ^2.3 可视化数据4 _  {# R1 w4 h* M4 \: Y& s  p; Z# A
    2.4 预处理数据
    ' w# Y2 `7 f2 B3 自定义数据集类; O! k! D* f3 E" C3 E7 \5 h& J
    3.1 数据集类
    % d4 |: s6 B2 \2 S3.2 读取数据集% M( F6 x: a4 l7 J
    4 构造模型" o1 B" [9 |4 f+ E
    4.1 预训练模型' e4 A& M- h- k' Y; a, X( W4 m# b
    4.2 修改成FCN
    4 @4 \, j& p' k$ G! P4 I0 L4.3 初始化转置卷积层
    ' E" l( [  c( Q0 l. g5 训练模型  Y) Z1 r3 L0 h; ^3 D4 J1 g
    6 测试模型  M" ~5 [2 b& b0 s4 L2 F
    6.1 通用型6 n( F; @: f+ H1 k
    6.2 不通用! w1 N  a, g7 l" ]9 g
    7 结语7 ^( V7 N: j0 p
    1 图像分割和实例分割
    9 P& [: ]( d: F" \  u' t. @; s7 p
    0 [- Y! Q+ r7 e4 @4 Q( e计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):! [7 [3 z6 y: U6 r6 j
      s& B+ `6 j+ t9 f0 U' E
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。5 b) Q2 O) n( k: Y! M
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。/ [1 G; |9 z) `; i" s

    7 G$ s! }/ V5 @1 N! ]: D4 z3 R2 Pascal VOC2012语义分割数据集
    8 B. F3 Y9 y& J! g
    ; ]5 n+ O0 J, S2.1 导入模块7 Q& f; g* a( P! a# l
    import time
    0 t/ ]5 E7 s+ t8 l( r' Q* Iimport copy
    0 `, w1 F" U+ ?1 Y; z4 U7 W( c! D6 @8 Ximport torch. C# l+ h4 `& C! a; }1 p
    from torch import optim, nn; i5 e1 q! A6 J& F# J- P+ `
    import torch.nn.functional as F
    3 A- T& I8 e; N/ Bimport torchvision) @9 T' h1 k" a$ w5 ]' U5 _) L# r
    from torchvision import transforms0 @1 ^% d9 Z6 M! N- M, j. m
    from torchvision.models import resnet18
    . o5 O. N) L. Q  a. A! gimport numpy as np8 I/ q: }; V2 ?; Z
    from matplotlib import pyplot as plt
    # D+ R8 m* f% ^& A; Gfrom PIL import Image/ H0 c' W6 P3 @7 M0 d
    import sys! s: Z6 i+ j7 }* z8 {
    sys.path.append("..")
    $ l" B* o$ U$ x( c* u3 Jfrom IPython import display6 J. a# j* y4 n
    from tqdm import tqdm( U5 g0 o* k; \
    import warnings4 z. G5 o8 l& A) L9 i% }& [. F
    warnings.filterwarnings("ignore")0 m% p$ T1 H# B/ }4 ?* C' d$ @

    5 N& I1 X6 i$ J% u9 A- n2.2 下载数据集
    ! F7 C7 u# `+ s7 W) \/ H
    , H4 t7 W0 P$ s5 |, @) R! m1 [语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    " Z, C; {! C* Q" R
    ' Y" W6 B" L9 S* X: p( q6 N4 C( E9 O4 \% h1 Y4 `' U3 w
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件- h% g( t1 n! R$ E
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。& x1 |6 l! d' n
    2.3 可视化数据# Q& C, n, @" ~

    ! n0 v" {. c7 s定义read_voc_images函数将输入图像和标签读进内存。
    * Y6 V/ O) D1 Y7 E4 k: [4 k( n7 x4 l/ b1 E$ b3 t/ `% d
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    " W8 k5 s2 B$ y/ c) a    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')" P: k( P# @; I, E
        with open(txt_fname, 'r') as f:9 [; X" V8 C- r- m* u5 c
            images = f.read().split() # 拆分成一个个名字组成list) B( z9 L* P+ q
        if max_num is not None:, W- L) \4 {0 I
            images = images[:min(max_num, len(images))]
    2 K, c! s# Z; F" C* i    features, labels = [None] * len(images), [None] * len(images)
    % o$ R+ ~% f2 M7 Z* V$ h% d+ n    for i, fname in tqdm(enumerate(images)):* g9 A  {/ z9 B5 F4 m
            # 读入数据并且转为RGB的 PIL image
    . P0 @. L0 d5 [" l, ]* m# _# }        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    ' {5 G* {0 N# z( x. k        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")
    1 d& u) g% a1 u7 A+ h6 b  ^    return features, labels # PIL image 0-255
    : F% R9 Z' w3 n( {. Z* }( X4 l; f5 ?# ~" n* d
    定义可视化数据集的函数show_images
    ; j: w/ i* F4 ~3 L/ X$ ^( o  E" {6 i
    - p$ D: {  R3 }! C+ s) n, C# 这个函数可以不需要
    : t; H5 l4 `' ^: }6 Ydef set_figsize(figsize=(3.5, 2.5)):8 n6 R9 s! H# y
        """在jupyter使用svg显示""". z# d9 A: K# J/ g) s) ]- O
        display.set_matplotlib_formats('svg')
    $ ^7 w' s, C# N0 O% }/ Q    # 设置图的尺寸8 j3 }, N' p# b( A) c* U% [
        plt.rcParams['figure.figsize'] = figsize
    6 K9 b/ b2 i( r, V" Z
    % ~4 S# k1 Y* V8 P6 P" H- _, Odef show_images(imgs, num_rows, num_cols, scale=2):
    " |2 S2 p. d# x+ L, I, ?9 p- A% F    # a_img = np.asarray(imgs)
    ! O1 @- o% W: j, `8 ~    figsize = (num_cols * scale, num_rows * scale)
    2 c+ t6 N: T7 x, c- t    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    7 r$ J2 w& G! A    for i in range(num_rows):
    4 s) f( ?& b$ I+ S& C        for j in range(num_cols):
    3 \% F6 X7 I3 e0 H% c4 X! t4 N            axes[j].imshow(imgs[i * num_cols + j])/ u/ a% }+ L' M& ^% Y4 s
                axes[j].axes.get_xaxis().set_visible(False). U- }' ?  `: T
                axes[j].axes.get_yaxis().set_visible(False)/ y6 O4 `5 D% ^& `7 @
        plt.show()$ F% V4 _+ S4 L0 s4 P
        return axes' {/ S5 X: W  U1 `( l0 x
    + |) q- P" @1 G* {1 b1 O! s
    定义可视化数据集的函数show_images& U$ {: h( l1 M& k  _' U2 u) z! j

    % M: J1 w& }' P* @: L) i9 r# 这个函数可以不需要2 T. Z. i+ \1 ~$ O0 [: ~% d
    def set_figsize(figsize=(3.5, 2.5)):- m$ Y! \" M2 f$ ]6 p& w
        """在jupyter使用svg显示"""
    7 y& A7 ]" T9 Q7 @% f; V    display.set_matplotlib_formats('svg')) y8 l- O  @+ O5 ]  h: i
        # 设置图的尺寸" [% P+ I) B  b5 @( g& L" U+ u
        plt.rcParams['figure.figsize'] = figsize& \5 Z  I4 j* l; u7 z6 B) j% o
    % k+ Z" G$ L5 Q% J' l9 S# F
    def show_images(imgs, num_rows, num_cols, scale=2):1 {1 p  |( F* j, j+ p3 K
        # a_img = np.asarray(imgs)
    3 D4 s9 M5 e% s1 U    figsize = (num_cols * scale, num_rows * scale)
    + y1 i, t* x4 m' z2 f    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    % i4 A, V* P0 ~) c    for i in range(num_rows):. B5 Q! k+ Z! _* r) T* H) D
            for j in range(num_cols):
    6 ^3 Q/ W/ w& ~% k            axes[j].imshow(imgs[i * num_cols + j])/ [% T' ^; g" I4 o$ |! _+ g/ e) y
                axes[j].axes.get_xaxis().set_visible(False)
    ' Q& o9 |: o! j4 L; C  M. j            axes[j].axes.get_yaxis().set_visible(False)* e1 D& O! d1 N. r
        plt.show()
    1 v6 |2 V" Q- O. ?; G    return axes
    6 U9 A" s% l5 W9 U画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。3 L0 C# }1 f) y8 W8 G1 P+ A

    + z! ^/ l8 ^3 d- W7 u# 根据自己存放数据集的路径修改voc_dir
    4 Y2 n9 H  w% e/ |voc_dir = r"[local]\VOCdevkit\VOC2012"
    / B4 B- W' B# ?# Ytrain_features, train_labels = read_voc_images(voc_dir, max_num=10). w8 A. S/ K) I" f* Q5 ^4 b) p
    n = 5 # 展示几张图像
    % B. ?. n" g& H& T* y. V3 Wimgs = train_features[0:n] + train_labels[0:n] # PIL image* y! h( d, Z' g- E
    show_images(imgs, 2, n)
    5 H% q3 v# N2 ]" ^6 _2 C# M- T! p& d: Z6 B; g! C  f3 o
    1.png 9 F, V" q  z# V5 q4 W

    3 f- q. l" Y- D; o$ n: X列出标签中每个RGB颜色的值及其标注的类别。  |9 j9 t3 |7 o# c' M
    # 标签中每个RGB颜色的值* \8 C/ g0 s3 }5 y( a7 L  y# D
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    % x7 ?& G6 ~, S                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],  y* x- |5 u* z$ o: f3 K
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],  }# q: o1 v& u& U  i
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    ( m+ u" W! Y8 |. \/ K                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    ! d" R0 n% e/ t4 O1 Q3 R( g                [0, 64, 128]]6 z7 ?5 U0 T1 |* P( C* [; b% U) H' ?
    # 标签其标注的类别, ]' y: J0 k3 X3 G- _
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    9 P1 W* q6 |2 d               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
      V: K& F5 j8 e" v! S2 }  Z$ ^8 h, P               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    ( n' R5 @+ o, M) ^( i" u$ u$ F               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']: y$ Y4 f8 _& o$ o- I) o0 K
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    . x$ A1 V- Z  n8 P: R- `: M, H3 Y6 P; p
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。( S5 T& I; s  _" v( K+ L0 v8 C7 S7 q; o
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])& }( F2 x, t) t9 M3 t+ V4 h" {9 X
    for i, colormap in enumerate(VOC_COLORMAP):
    & y" h0 E) \# ~1 g9 _" K6 B    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i+ ]8 x! P9 k. h5 W" k- n
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    8 m6 P& l1 c) U) [  _! f! n
    $ }  C* B+ Z! {6 G( K8 u# 构造标签矩阵0 P! q' J8 p" r' H, G
    def voc_label_indices(colormap, colormap2label):2 u: h, R2 P. e  b4 F( z& y, c
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    6 i- P1 S3 _0 c, F    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
      O7 [& V5 q' h! Y    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    & v) n" _0 w/ U  E0 Z
    1 q4 R3 d1 c* H' {3 v( d% f可以打印一下结果/ q* j" l$ ?; ^- ]
    ) H( |( ^* r1 y  c; ~- h" Q9 U. e
    y = voc_label_indices(train_labels[0], colormap2label)" X7 Y3 j4 S/ U6 n$ p
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    $ Q& g, _: U2 ~/ s& S6 ?
    3 t3 T5 s' h5 ^! m* E2.4 预处理数据
    $ W( W% u( F  I% e. r
    2 d, |& D' r+ C5 m4 V9 t& \在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    & c- ]$ c/ T8 X, S9 b+ q/ m2 x& F) x6 c" B. h# ~/ P7 k7 s

    ; L# y. O1 q4 }( Y3 }def voc_rand_crop(feature, label, height, width):! Z0 D$ v( j; J. n- L0 S# D7 [4 k
        """) f/ g5 Q+ h$ G" R( U
        随机裁剪feature(PIL image) 和 label(PIL image).
    0 K5 F0 q* x3 A: p3 b    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做
    2 Q$ p6 k5 Z( Q4 X# R7 z+ B& n    Get parameters for ``crop`` for a random crop.$ z* j0 k: [- z/ @0 u- h- E: v
        Args:1 V3 B7 ?4 G5 P
            img (PIL Image): Image to be cropped.
    - Y! C. q: p* w. B        output_size (tuple): Expected output size of the crop.
    5 o. ^8 c. T' A4 f    Returns:
    ' D2 b/ S8 i8 V- ]+ P& p& k2 R; ^        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
    # C$ o' S8 F1 M! d4 L8 u& T    """
    : d$ _5 ^! s1 c8 ^, e    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width)): P) A. J0 O1 J; `+ `0 o* Y" w
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)6 h0 V2 J) L( M! C; H3 k
        label = torchvision.transforms.functional.crop(label, i, j, h, w); A( B, t4 p: @4 x( H- a
        return feature, label
    ! v* Z; u- u, u* _) y, t$ F6 Y" b
    # 显示n张随机裁剪的图像和标签,前面的n是58 |5 e; U7 H# n  A# a
    imgs = []9 ^. O3 S' k4 A% r+ s* G' f
    for _ in range(n):
    / m& _7 A: H9 t# }3 e- k    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300): f# k  ^: r2 e/ L4 c, r+ y
    show_images(imgs[::2] + imgs[1::2], 2, n);
    2 f8 Y: e  V& H1 N. v- `) {: b' p9 X. ?: T8 x; k& d% U& i7 z* m" V2 U
    " N+ ~+ ?. ~8 ?! |, c" P5 a. \
    2.png : L3 Q- Z$ @/ G

    7 \) m+ [6 n# y. G
    " d: j) N: `  ?2 C8 i6 v4 t0 R7 J* U$ a% t$ A) M
    3 自定义数据集类* H3 n& y% `+ o& ]9 d

    3 H. A- l! e+ d! L* M3.1 数据集类
    ; O. y) e7 Q; e& B
    0 l+ c2 _- I$ X7 b; qtorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法9 V- F$ @/ B! t4 }& `
    , a" `$ D% h+ j  D
    __len__ 实现 len(dataset) 返还数据集的尺寸。3 k0 N8 [# j; G" }/ g. [9 x
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    ) }! f( R* Y* q% \, z由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。. T" N1 g$ T  {( j4 E
      T) o$ f0 P# T
    class VOCSegDataset(torch.utils.data.Dataset):7 C+ D- G$ _% E0 T+ U* n4 y- w7 E
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):  \1 t6 M7 S$ F- I: g6 o
            """& q% }+ r% o& T4 r- P" f
            crop_size: (h, w)$ T* |& g9 e; Z  g- z
            """
    5 s0 C& W4 X& N: k' I        # 对输入图像的RGB三个通道的值分别做标准化
    ) y0 a9 w  J( E4 j, w! ?8 D        self.rgb_mean = np.array([0.485, 0.456, 0.406])% c# R3 Y# f0 G9 X/ ?; U( I* r
            self.rgb_std = np.array([0.229, 0.224, 0.225])9 g  `$ f) v6 P% X  ^5 f) ]
            self.tsf = torchvision.transforms.Compose([
    2 o. ~0 |! h( }) ]4 g+ W) i- w            torchvision.transforms.ToTensor(),
    : X1 q! X+ j! I; c6 K            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    & P! F7 n% G7 V' u3 W6 g        self.crop_size = crop_size # (h, w)
    # @# q5 B) p! b1 G! l- r: z        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)
    % _4 t. B2 _4 [0 e' y  t- R# 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除7 y$ M3 j2 r$ r. N
            self.features = self.filter(features) # PIL image6 T3 X3 n, k2 h9 k
            self.labels = self.filter(labels)     # PIL image  U; t# X! N5 R0 O2 R
            self.colormap2label = colormap2label& S4 w  S& ^+ M/ L  t0 N3 P7 g
            print('read ' + str(len(self.features)) + ' valid examples'); l' K8 }) R9 F  N
    6 b' [7 v0 C! k' D# F6 k# @
        def filter(self, imgs):( S; ]: u) g7 R! r
            return [img for img in imgs if () W" M, B2 S9 H% c! e; a, d! ]
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]' U5 X1 o) ~1 V) Y% t- U1 G
    / \3 _+ A; V0 y* P' ~4 K- s( i
        def __getitem__(self, idx):
    1 L% U6 F/ }$ V5 V- b9 I5 D        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size), a& a& C4 L& H+ Y) t$ j
                                    # float32 tensor           uint8 tensor (b,h,w)
    3 ?  ^. Z# w1 M- \        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))6 U& c! `" N* R$ n

    , f( o! V6 h! X5 ^1 f6 C    def __len__(self):
    4 ~' n1 r0 G; v* r        return len(self.features)
    1 v- j4 c, h! r9 Z/ s3.2 读取数据集
    : G8 x  O& V3 L& p/ M3 I7 ^
    0 A" p" u" U  p0 T通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    ) y, E, e8 `2 O% B/ a2 X3 p" Q# p# m- F, n
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)  A2 _( E! e0 O7 A6 G/ X+ t
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    " T0 ]6 I$ b: imax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~9 S1 f: C, p/ Y/ T. Y, u

    . K2 k( L3 L$ h8 L9 v: d0 V/ N& L# 创建训练集和测试集的实例% F9 Y- h! N' q: C* d9 G; X
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num): S/ G% A5 |+ K2 u8 X
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)4 i5 O( Z! ~& W5 v! e) |
    % ^' [' O# y, H0 N' k/ v2 X' t
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
      E/ d( d; [# A+ W9 G8 \3 {num_workers = 0 if sys.platform.startswith('win32') else 4
    5 P, G% Y# n& Q% ~4 W# E* k. Jtrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    : L* J* y* J7 K* L3 W- R: o/ w6 n                              drop_last=True, num_workers=num_workers)# K4 G/ f$ X$ n' `: A
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    ( s+ d  ?  l+ v# t" x. i  h                             num_workers=num_workers)( T  u. O4 [# ?  N
    : V# m% b! r& z5 B+ t7 F& ^
    # 方便封装,把训练集和验证集保存在dict里
    2 S* K9 t* `9 f  ~dataloaders = {'train':train_iter, 'val':test_iter}
    0 `! Z: l0 ?) m8 Rdataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    4 S! O7 N7 _) a# r; h" u' n
    ' @1 X4 S7 {- P! N4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')" R' A/ o3 ]8 [7 I# ^
    & [' T, O3 J' i
    num_classes = 21 # 21分类,1个背景,20个物体
      G: W: [/ A0 Z! ~# `! Dmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    6 }0 @3 i0 L+ S( e+ M$ r, O. j1 d: Y4 C6 E/ {3 B: R+ ~7 O& ?0 Y/ u
    # 特征提取器
    & R( Y* f7 _2 ^' j# d  \" efor param in model_ft.parameters():
    / H3 g( [+ y, V0 e0 i0 |    param.requires_grad = False2 G9 ~* U8 S6 u# N: z1 S
    4.2 修改成FCN$ |7 ]6 ~3 z, }  W: ?6 I

    ) Z1 n* U5 Q$ R1 ~全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    4 L- U4 Q& r" o最终输出的通道包含了该空间位置像素的类别预测。
    8 U9 J; N4 H. n) s* C2 G- U2 L* D' L7 f4 }, t' l
    对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。- [" f+ w- J6 {9 @

    7 N1 P- G/ g2 z; A1 P# C. Q1 x可以先打印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 倍即可。
    " C5 s/ \; C! u5 C) ]9 [
    7 q; X1 E. t0 \) A& {! B: k1 `; rmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    4 G4 X" C7 f" Y# d' t* ^% j  I              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class
    / V5 `2 T4 T0 H              nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
      h& f! V2 H; p) l5 v
    * S2 U( I. a8 b) o# 对model_ft做一个测试  F% k- v8 p4 }' K+ ^8 S
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据& |. y2 r/ y/ x9 `' H4 c; t8 b
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    - j" k2 k" y( l& }1 l& |; e# V6 g6 a  |- F9 q2 ^
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组: ~4 p: Z* c. h& `! h
    # for X, Y in train_iter:
    8 w1 X( j7 R  Q# G, h: h/ O4 V#     print(X.dtype, X.shape)9 U5 [2 U1 t+ R- z$ V
    #     print(Y.dtype, Y.shape)
    , H" g2 |# r9 W+ W#     break) ~, ^! r- S/ x3 x
    8 U/ S! @; K' ^8 O. k0 Z( j
    . i; s) h  [2 i& o
    4.3 初始化转置卷积层
    ( e& D! j$ Z  q/ \) D* F8 a0 H$ D
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    ) X* a8 B# A- R* T: @+ n- m在坐标 (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函数构造的卷积核的转置卷积层来实现。& o+ }1 K% }. U4 G
    ; L* a, s6 u+ V  a+ a
    - o6 `! ^8 M) G. T! {
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核
    0 G; p/ K2 m/ Z; z5 [2 s8 f  G- Udef bilinear_kernel(in_channels, out_channels, kernel_size):' K9 K! K% Z* H- ^, {! [
        factor = (kernel_size+1)//2  o! @6 v4 {$ c0 m& Z; N. W) {
        if kernel_size%2 == 1:
    3 s4 O( T& O; s/ R7 G" [        center = factor-1: @+ T3 p, o" X
        else:
    0 A" b' ~' X! D9 L        center = factor-0.5! B5 \* _# C$ X
        og = np.ogrid[:kernel_size, :kernel_size]
    3 p9 R( t* T, v4 V, j    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    $ [1 W0 Z4 q8 _; z3 b( |- N    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')$ a( h7 v5 h' e, J0 V9 ?; j
        weight[range(in_channels), range(out_channels), :, :] = filt8 g3 H& C4 U1 L! q% B9 R
        weight = torch.Tensor(weight)
    1 f+ h$ @# \' g8 w. z    weight.requires_grad = True6 s) S/ H3 k7 ?( H
        return weight; U) k& U* ]; p* `. j' c

    $ G9 r, r' X- E/ Y; l3 S% w4 s; E
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。$ ]& k, O% _& f. A* z& ]& ]  M
    # G9 t% e. |7 G
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    " L1 M  K# V4 |- m. M) l8 P6 Z6 \model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)2 X  J0 Q/ c: J* A. n
    4 d; n1 u2 i7 ^

    ( ^; `- S9 E& J% x; U, _: H% r+ K; H( u
    5 训练模型

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


    # ?+ D& c7 L# l) k' z4 c/ hdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
      C8 x/ b( n; l8 ]  S% T  h7 ^+ _    since = time.time()  y  L1 C2 O6 j) k3 D
        best_model_wts = copy.deepcopy(model.state_dict())7 S( U$ q, d) f+ }: @: [
        best_acc = 0.0
    . ~: X1 ?. |4 U( H% m9 a    # 每个epoch都有一个训练和验证阶段
    8 }% s2 p% f7 d4 H    for epoch in range(num_epochs):/ V8 i! z5 y/ T
            print('Epoch {}/{}'.format(epoch, num_epochs-1))
    7 X# @0 ]7 k  W1 Z        print('-'*10)
    + P9 A( \& ^' x        for phase in ['train', 'val']:" |3 b$ \" _1 U) q- ^9 w9 ~. \2 V
                if phase == 'train':0 |2 f* Q8 ?3 |1 ^
                    scheduler.step()
    + |' k: o! r. s1 i( k                model.train()
    ( y0 T4 h5 C) }8 `% f            else:9 n& G$ n$ E9 Y" h& w
                    model.eval()
    / d1 U) C7 m0 U7 _' p9 _            runing_loss = 0.0# t9 n7 D" r2 d7 N3 K$ W& X
                runing_corrects = 0.02 o) G. E- P! w4 }: o
                # 迭代一个epoch7 _& Y5 D( `7 y3 n* r, k0 z
                for inputs, labels in dataloaders[phase]:9 w( Q; v; s" \4 z3 S( o4 ?% K
                    inputs, labels = inputs.to(device), labels.to(device)
    8 Y, M, |1 \4 ~! u, ]  \9 F( y* v                optimizer.zero_grad() # 零参数梯度
    / \8 Y* U3 L( y                                # 前向,只在训练时跟踪参数" q- q; i) W& S( W
                    with torch.set_grad_enabled(phase=='train'):
    9 ]" v6 S! W* @% x; U                    logits = model(inputs)  # [5, 21, 320, 480]
    - ], r& E! u- N. W/ L& U& ]  u& P$ v                    loss = criteon(logits, labels.long())
    / X3 }- L6 S' P- S: s- @; |                    # 后向,只在训练阶段进行优化
    " y5 S, G: K! h! p6 H- K                    if phase=='train':
    2 ^8 f+ Q) n+ s  R; V4 u; ]                        loss.backward()8 J# ~& Q' f% h7 U5 s; b
                            optimizer.step()
    . H) y+ V5 I- H! l                                # 统计loss和correct" W- Y/ o! ^+ N5 y1 a
                    runing_loss += loss.item()*inputs.size(0)
    6 {' u8 v3 [: {) ~( E                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    9 t' W' d+ b: X  h0 e
    . \7 c/ e2 ]( L  Z- `7 \            epoch_loss = runing_loss / dataset_sizes[phase]8 G% H: i! K7 z; ?4 `3 m
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
      r  _& ^! t: p" s1 z            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    : _4 e% ^; R* l' U# C4 R0 r) C                        # 深度复制model参数
    & N8 q8 N, ]; X2 d8 a. x( V8 M            if phase=='val' and epoch_acc>best_acc:
    0 O$ ~7 o% V- [* t) x/ S                best_acc = epoch_acc0 \) A6 z. }7 R- Z# H
                    best_model_wts = copy.deepcopy(model.state_dict())5 T, m8 M( n; {
            print()
    , H5 f. }/ u8 {- g7 ~5 o: h    time_elapsed = time.time() - since;
    . a% }4 `$ f, W( t( F/ X/ N    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    2 O6 Z9 K, Z6 U$ Q1 y* Z    # 加载最佳模型权重
    % O- W, l4 ?7 B- K. v# ]    model.load_state_dict(best_model_wts)
    5 _/ O# }+ B- S7 ^. t) q! ?    return model5 Q0 O" m7 g/ `
    ) e% p4 O& e) j" @! l2 C) f
    下面定义train_model要用到的参数,开始训练
    : e2 J3 o5 B* r: q4 J7 c
    ; ?$ t% ^7 ?5 X6 Q# e0 t  i  Repochs = 5 # 训练5个epoch
    * l+ F$ O0 ~, k4 o* `criteon = nn.CrossEntropyLoss(): `9 e% S7 y% a  v- ?
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)/ Y! C- G% P% U- d0 v
    # 每3个epochs衰减LR通过设置gamma=0.1
    # f3 Q- U. C0 r: V9 M; }exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)5 n2 _9 l0 q4 R5 B) p

    + ~1 |# A1 a- v4 j# 开始训练  A; g, w4 d/ L
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)+ H4 f. K' G$ O& b

    3 p# A1 P9 h, h5 @- I2 X0 _5 B$ e  T6 测试模型

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

    def label2image(pred):
    9 H7 B3 j  ?+ h% C3 g. M+ }    # pred: [320,480]' v4 }9 j* W' E, t. M6 i7 e8 Q% {
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)) R8 Y; K# b3 w2 N5 \
        x = pred.long()
    0 J7 [* O$ r/ C, L8 X0 h  K    return (colormap[x,:]).data.cpu().numpy()1 `6 C# T! H; B  ?  b
    * y6 c) N, F# [# e) V

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    9 `  J: [! ]$ p1 S) d& ?std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device). Y6 s# f: m$ `
    def visualize_model(model:nn.Module, num_images=4):
    : b7 G5 ~( ~/ I    was_training = model.training# d4 m5 l" x9 z# g* C5 x1 d
        model.eval()8 |( B  o  T( J
        images_so_far = 0
    , b2 e4 R* ~, t: @0 P" q- [" w    n, imgs = num_images, []
      N( Y- X( D. z, |, t, C0 I( C9 a    with torch.no_grad():
    ! F. y1 P; q' j* O& g        for i, (inputs, labels) in enumerate(dataloaders['val']):
    - V# i) S' C0 E- Y9 R% c# Q            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]8 h0 ~4 c% ]& D& m" u4 ]! ~. v
                outputs = model(inputs)' ]7 G4 L: w& P- c9 q
                pred = torch.argmax(outputs, dim=1) # [b,320,480]8 X; {' t  c/ U' }4 F
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    , ~: G/ J4 I  b" }- B% I2 P* a$ t& \2 L7 ~
                for j in range(num_images):
    7 d& m* [  ~* `" ~2 Z+ k1 _                images_so_far += 1
    . k7 X+ i& X  C2 _, y7 [( w                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    : J  p8 [# J2 J* V1 c                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]
    # d% B" T* S' i) `) a+ T                if images_so_far == num_images:7 x" F' P# q% m7 j8 j# w4 @
                        model.train(mode=was_training)+ Z8 E+ S) a2 @! L
                        # 我已经固定了每次只显示4张图了,大家可以自己修改9 n9 s5 I- U6 ?5 i. N
                        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n): q# |6 P  h) C5 R. T8 }, b
                        return model.train(mode=was_training)
    % q# x/ h% z, i  K! ^7 p9 N& \8 S+ ?! Z, }/ `7 T! a0 ~
    # 开始验证, W1 r2 _$ X4 W; R+ m  R
    visualize_model(model_ft)8 @, y3 ^5 F$ ^, w! I6 y: l" R
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    1 x3 `( G/ ~6 C# S+ z+ |def predict(img, model):7 T( P; Y0 x3 |* u
        tsf = transforms.Compose([
    " R( E5 k6 N. Q; S! h            transforms.ToTensor(), # 好像会自动转换channel
    - x3 a9 G' \7 V% @1 H! @            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])" W; ?3 ~$ D/ i: _  K- U! Y& y. c
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    . T/ Z& e6 T0 D. u! t    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    ' m7 [( E" @4 K: ^* i) Z) O    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
      U) f! \" ~2 |6 i) d3 c8 u" d# o4 n2 q
    def evaluate(model:nn.Module):
    % q6 Q  z, R) t- E3 w: ?7 Z    model.eval()7 f8 i$ B/ ]5 x1 P6 A" P/ X" M
        test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    : |' K+ e) r! H" o6 D    n, imgs = 4, []
    / B, `$ R" ^* l1 ?8 _. r+ Z: k    for i in range(n):
    . |- l# c# Z+ Y5 a# L5 d4 r+ n        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image6 G3 M: I) W' c- \, b( n+ E$ M$ p$ K3 j
            pred = label2image(predict(xi, model))
    ( c, ]' F/ T6 D/ s        imgs += [xi, pred, yi]
    & g2 N* d- Y% ]8 b4 P* q7 z- B    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    ( ?" E4 v' W# S+ t
    . R1 t) }# _$ j: z* ?! ^/ ]# 开始测试
    & v7 z4 i% \! t  L& F& Pevaluate(model_ft)+ G. l  P) z. M1 D% {2 W, t) ?$ L
    0 K5 ~2 ?& I! b; @0 o
    7 结语

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


    8 ^2 }$ `. Q% k% ^Epoch 0/2# o' |7 b) ]5 H5 o. K5 v# u2 G
    ----------# W1 B2 V& |- G- D' K4 _+ L$ i9 c
    train Loss: 1.7844 Acc: 0.5835
    $ J7 j7 s3 G# g. [val Loss: 1.1669 Acc: 0.6456
    / p4 s/ ]  a3 q! E6 m4 B- J# s+ |- d
    # P5 M) y* C) r( `9 `: dEpoch 1/2
    , w8 W  p' J& i8 y$ A----------6 O' I  k" K) S4 q4 }% i6 y; F$ h
    train Loss: 1.1288 Acc: 0.65351 K6 y3 d2 M, G0 @
    val Loss: 0.9012 Acc: 0.6929
    ! y! f# \$ `" N4 B. M+ q3 }$ R% F" G6 M5 M  s
    Epoch 2/2
    ) o: }% u; S, A- B----------
    . l, r5 Q1 U, Z# rtrain Loss: 0.9578 Acc: 0.6706
    4 e% @0 U/ l: [% P4 ?) L7 sval Loss: 0.8088 Acc: 0.6948+ s  E! o. [; S3 k8 Y$ _* f
    4 J! i5 s( U" r) h  U
    Training complete in 6m 37s
    % h# W; y( B# Y% _
    9 Y6 u7 o( r6 W" j# \
    9 c' R  w% L9 y, |* l$ t" Y6 M 2.jpg
    $ v2 N8 x- x1 E! U7 N: y/ R  x, O8 @- a
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    % j; w6 v9 G; x. _) h- g9 d  [+ C4 z* E( K
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。) z2 m6 {  J5 k0 {4 b7 K
    * n: j( O' a' @0 y1 E# n. z

    6 }" G& E2 \6 ~# `0 h0 W语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:
    ! z; n. I1 T! a6 o; J! e. F5 @! h
    . n. W2 f5 [& K- u% |" H) `$ H. fDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]
    $ p' F* H$ c, ~6 Q2 j7 j" uGCN 通过全局卷积网络改进语义分割[论文]
    % y4 H/ v6 E" }6 Q' u0 q0 r: ^UperNet 统一感知解析4 M# k4 c+ E1 f
    ENet 用于实时语义分割的深度神经网络体系结构[论文]# n* q- j4 g$ o) `& q" L
    U-Net 用于生物医学图像分割的卷积网络
    . `3 |3 l* J) E2 jSegNet 用于图像分段的深度卷积编码器-解码器架构。
    8 M, p) f8 O) i7 [  ]还有(DUC,HDC)、PSPNet等。- M3 l/ y" h0 I2 Y1 d7 p

    8 d3 b9 y  \: u0 W2 O1 |+ X1 N常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    ; X5 I' ~0 k% _( h& ~8 _' U
    " A8 e" n% m; ^) f5 r对于损失函数,除了交叉熵误差,也可以用这些:3 `% w2 I/ Z7 T
    1 l3 v, @8 h9 ^1 C0 k! P
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    3 F- y4 q0 N# I7 k0 t* h& dCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。3 }6 e! f9 Y5 v$ f
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。/ T0 x+ }  Z' w# u4 b
    Lovasz Softmax 查看论文:Lovasz - softmax损失。2 Z4 f. n: u8 K! N/ g

    - G/ d5 {) i1 }' c8 H7 h/ B1 L$ S0 L4 F& n

    9 j; k8 ~0 q+ e% j9 b
    ; q: X. r  E& T- J" s% u  C$ L9 `: [, V2 H# k0 Y
    ————————————————) d2 }+ }6 X; R% f" w
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。- D" c6 K; x9 V& V% {" E4 t
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    0 U; `: Y. F$ h3 a% m3 C! t' U- f
    7 ]5 }+ X5 ^; Q$ h* g
    ; ]& Y& {! `9 N/ B
    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, 2025-7-14 09:18 , Processed in 0.578620 second(s), 53 queries .

    回顶部