QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 10070|回复: 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
    & z8 \/ \, m9 }/ I: o% Q2 w& N- ^

    1 d6 j0 p/ o9 N0 g( TPytorch实战语义分割(VOC2012)
    7 u) o$ u% X) K本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。3 Z. C' T% r- b( `
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    + J# ?. T/ v  S, O( B+ _/ C, L& c/ ?8 [' b6 `6 y9 W; G

    ! L/ O3 U, X6 G  E2 w% t! v语义分割中图像有关狗、猫和背景的标签
    : T$ c. |# m- s" S8 _- D8 a文章目录9 w# _: Y0 `9 F4 W  Z9 `  G

    1 t5 {/ o7 ]2 U+ D: U4 _. p( t5 r1 图像分割和实例分割  y; O. q6 P! u4 a
    2 Pascal VOC2012语义分割数据集
      h! U) Z  Z" ]7 Y, M7 Z/ z7 A2.1 导入模块' b5 K! l* |% H- M! y3 l5 u
    2.2 下载数据集
    % ]8 u$ w! _8 j- U2.3 可视化数据
    ( D, m/ t- {3 ~3 s2.4 预处理数据
    # |" I* a1 v+ i/ N. [3 自定义数据集类
    4 j9 _! Q' x  e1 d' X  d2 ~, M3.1 数据集类
    & f  ?) }; b" v9 G/ `0 g. I3 Q0 k3.2 读取数据集
    + V, q  }' S6 P  ]( k& Q$ b2 E4 构造模型4 j2 [0 _6 |& \3 A/ |" `
    4.1 预训练模型
      q& f: L) C2 @8 w9 o4.2 修改成FCN- C) o8 t! m+ @* n
    4.3 初始化转置卷积层
    4 ]  M$ A7 g' B! S3 u: v# ?5 训练模型1 F+ `7 c( E1 S7 L7 m4 l
    6 测试模型
    & _2 ~% \3 D& k* h2 a2 L3 E! E  `6.1 通用型! d* N* G" X9 P% i* c  A% z9 Z
    6.2 不通用
    " ?2 ^+ E' a) Z, v) u5 x2 E7 结语
    $ G7 b3 P6 [, ?0 l5 t- c1 图像分割和实例分割
    * X' W" O* S% F/ O4 X3 [
    - E8 |  D- f8 O) d3 Y) `计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    / i# q& |0 ]* F6 J9 Q0 d8 f* T& Z; ?5 R) r4 T  D- c, {
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。( ]! G& a2 @$ {5 n$ m4 w
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。9 a( P4 S5 [5 X
    ( q/ e, N1 |: l5 R, n3 B" R& e
    2 Pascal VOC2012语义分割数据集) k1 h, `3 P1 S  k  M. ?7 G* H6 m

    # y9 f) L+ r# m8 x6 C' Z2.1 导入模块0 l  M$ C! R( l8 i- N) E
    import time6 H5 N5 G4 {. O
    import copy: }% o3 j+ }# n2 {. y& w- ]# l
    import torch5 u9 O) Z! s; J, U
    from torch import optim, nn
    * @: G& b* i9 I: T1 x6 v5 @import torch.nn.functional as F
    ; `- X6 ~  d: Q; h6 oimport torchvision
    ( _0 C( e+ b( B4 O+ |from torchvision import transforms
    ( u( H" y, F9 u' }5 U( v0 mfrom torchvision.models import resnet18
    % W$ O6 ]4 H6 o4 E8 Himport numpy as np
    % Z5 L+ x2 B  hfrom matplotlib import pyplot as plt
    & e$ m% D) d2 nfrom PIL import Image  r6 E/ o0 f0 V# F
    import sys
    / q/ @5 V( l+ a5 S- asys.path.append("..")0 e( {$ O7 l6 N7 q' ]! Z! `1 K* }
    from IPython import display. T$ z7 _# @  n7 E# Z8 o
    from tqdm import tqdm
    2 w% Y, b" k& J- R: x1 |import warnings, d1 W0 T/ q0 ?
    warnings.filterwarnings("ignore")
    / v( J; N* N' p. p. ^% h3 E, L+ r/ D- {& V0 v# B  }# B4 k8 s
    2.2 下载数据集; B5 u. ^9 y% B! h+ a( v

    + t% p- f: R. `6 ~6 d3 M  T% A6 D语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    % r3 i+ n3 o# O* j  L$ a- ?8 v8 `% F& I0 M8 R  Y# Y
    * t7 z& a4 o3 [. k. q3 N
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件% w/ c  c4 D. B2 n4 c2 j
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。6 }0 h+ N9 ^7 g* p; V- m$ R5 x  A& \
    2.3 可视化数据" O- {$ F1 D5 w* s9 D
    + R& T% p( b* k  }! S) {) t
    定义read_voc_images函数将输入图像和标签读进内存。
    ! ^0 ]6 k4 ]% ^( f5 F& q1 C
    4 _& T- R2 W, b+ O! Vdef read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):6 I1 U* t' B$ x
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')) P, b4 O9 W2 f6 T9 m3 G! C5 d. J5 a
        with open(txt_fname, 'r') as f:% x4 l" K5 T" Z4 D; t; ]
            images = f.read().split() # 拆分成一个个名字组成list
    6 m7 e7 p9 s- t4 D) ^    if max_num is not None:
    9 l9 g' E4 g' q/ d4 |! S        images = images[:min(max_num, len(images))]+ S9 q8 u( t1 l- U5 ~- ~
        features, labels = [None] * len(images), [None] * len(images)+ u# y1 N2 L& O
        for i, fname in tqdm(enumerate(images)):5 e; Q) ?  F# w& P
            # 读入数据并且转为RGB的 PIL image
    & p, [( k2 W7 x; ~! y        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")/ d) }+ U; v6 t" \1 O) Y0 L
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")0 C5 o+ }# q0 U3 h- b* \3 S
        return features, labels # PIL image 0-255
    ) Q/ c" D8 c- ~$ c5 L1 U7 b
    0 K0 Q, Q+ O, }. j- t5 F定义可视化数据集的函数show_images2 d( F# h. t4 o; m7 a" j& w+ ~
    ' Y  s6 \. K) K. S- d9 L, m
    # 这个函数可以不需要
    5 _$ J' e; c. l7 N) c% Ndef set_figsize(figsize=(3.5, 2.5)):* ~. ~3 _9 e* [
        """在jupyter使用svg显示"""
    ! R( w( `6 r; `8 ?  e8 O. }) e    display.set_matplotlib_formats('svg')
    0 J+ m. H" z3 d1 d    # 设置图的尺寸2 p( W: A8 l0 T; b2 j+ D) r
        plt.rcParams['figure.figsize'] = figsize; w6 p: b4 O$ Q; J
    " w3 F3 f( b  E% B" D
    def show_images(imgs, num_rows, num_cols, scale=2):0 r$ a4 X' b! z
        # a_img = np.asarray(imgs)- H: C7 P' J. `/ E3 s. V& V5 ~
        figsize = (num_cols * scale, num_rows * scale)
    + e" s7 [7 T! X    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    1 U0 j5 I1 O: I; ~% b    for i in range(num_rows):
    * F* e9 Z" P( I7 i        for j in range(num_cols):
    " C0 s. B; D' e4 f6 T8 X            axes[j].imshow(imgs[i * num_cols + j]); p, d' v  u4 ~  ~: [6 c
                axes[j].axes.get_xaxis().set_visible(False)
    3 Y, P' A) O' c2 v            axes[j].axes.get_yaxis().set_visible(False)! w. R7 M# ?1 W9 z
        plt.show()
    : `& p) b: b8 r4 j    return axes
    ; q+ o. x% G2 C; K4 f0 [& q
    ! G4 a. C, R  v' A; o% T  I, U定义可视化数据集的函数show_images
    3 m$ `. H0 ]' p0 @( ], ^9 s+ G! a6 j  X& L( G2 I' E3 j
    # 这个函数可以不需要6 C1 ~/ F) ?2 B8 q
    def set_figsize(figsize=(3.5, 2.5)):# \* }1 ^) x* q' j8 H# L4 H  C4 b
        """在jupyter使用svg显示"""( f, w3 J8 h# s" _4 [$ P4 ]! ?
        display.set_matplotlib_formats('svg')
    * i7 L6 P( w7 h% G& c. y/ }    # 设置图的尺寸- C: k, B9 @) J. u6 D: c/ X
        plt.rcParams['figure.figsize'] = figsize  v3 S+ u# |* s7 b2 r% f, @
    0 |  N  U' I) e' Q
    def show_images(imgs, num_rows, num_cols, scale=2):3 v4 q9 b, p# r! l0 _0 g6 G
        # a_img = np.asarray(imgs)& G4 i0 z1 g& Y- g" \
        figsize = (num_cols * scale, num_rows * scale)2 F2 a# V. b. l
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    9 r7 |$ A. _. Q9 |- {$ @: F    for i in range(num_rows):
    8 W4 D6 [" Z! [/ V# Y        for j in range(num_cols):7 C  n% Q! H+ {6 j8 Z' U
                axes[j].imshow(imgs[i * num_cols + j])
    0 v9 ~" A# ?- b, P            axes[j].axes.get_xaxis().set_visible(False)
    0 ~8 R8 {: Q4 A            axes[j].axes.get_yaxis().set_visible(False)2 Z. ]7 d6 M/ ?( B5 L- a
        plt.show()! Y! M# q# v0 f& {
        return axes8 z. C: i9 K: i
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。" I$ `+ K: C9 f! X

    7 l8 B  j: ^$ K4 f) q# 根据自己存放数据集的路径修改voc_dir
    ! A1 f" [. H2 wvoc_dir = r"[local]\VOCdevkit\VOC2012"2 _# `' T" M7 \
    train_features, train_labels = read_voc_images(voc_dir, max_num=10), ?' x1 B# t5 v
    n = 5 # 展示几张图像: M1 T" L# q8 }( h: g# ^1 O
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    7 _* W! X. W6 X) K* N- |show_images(imgs, 2, n)
    . H( o0 x1 t( M) `6 O$ ]# k4 M4 }1 w% B, h: B. l* \- N  F2 L
    1.png
    3 ^  p- I* B6 R5 |1 j9 T( X2 h, R' W
    列出标签中每个RGB颜色的值及其标注的类别。
    * Y% T  f+ O( e4 \& V# 标签中每个RGB颜色的值
    $ s+ h' U; |3 n4 m% Z4 tVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],6 Y! [; n" n3 G/ W+ l, O& F
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],2 b7 h+ J% m6 H- ~$ j
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    . ?3 u" w& }8 t7 B+ H( P' \! T                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    5 V. z" \2 h: O1 v4 K9 G, t9 t; ]: g                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    & ?% G6 u3 B1 w7 }, f                [0, 64, 128]]# k2 D7 x- g0 l" T7 E
    # 标签其标注的类别' V# F, b2 s; D$ L1 H8 R; L3 ^" f; Q
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',& {% A# u  p; U
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',2 d5 l6 N- [9 s& H/ r8 Y" v! e
                   'diningtable', 'dog', 'horse', 'motorbike', 'person',
    # [! x2 h6 ^- ^# R               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
      ~5 g# V- n- M4 k1 [- s0 P9 D4 I有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    ( p! [4 J3 w7 |
    1 D: I$ L( I" s+ K有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    ) T' e# i+ j: N) M; lcolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    + ]* ~( L9 x! jfor i, colormap in enumerate(VOC_COLORMAP):
    " u/ i1 h9 a1 K) G7 _5 L% `    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i. H$ ~; C4 z' b
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    & _! W: r0 E$ V5 r" Y9 {4 b5 H
    * F  ]7 q7 h5 O) M) [# ^7 [4 O# 构造标签矩阵1 g+ _; }1 V8 s% k- c2 F# Y9 F  X, }
    def voc_label_indices(colormap, colormap2label):
    ' g. w+ _6 e( B( l    colormap = np.array(colormap.convert("RGB")).astype('int32'). W! Z% m1 [( t8 w5 C- a: t* Q
        idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) . F! ~0 F7 u  a3 z$ G
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标+ f. P# \2 H! D& s+ m$ e
    " z( m& l' L; n9 J% K! M
    可以打印一下结果
    * m3 n7 j  X. n: G, \" y& j" N' c  o* u/ B& h% o
    y = voc_label_indices(train_labels[0], colormap2label): |' R; N6 E. y  O. Y) J
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES' p5 V7 V7 D1 y! b" }4 E

    ' f/ @+ s5 ^8 L. N  N2.4 预处理数据3 @6 |; {' k( K) d. b

    - v! R7 z( k! w. d在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。6 N. `" a+ E! R. i

    ' F3 W! z' x: w, A6 \6 n6 J; D
    % i7 N4 u) |' t, ~. Hdef voc_rand_crop(feature, label, height, width):
    / |, p0 B  W% |2 V    """
    5 z8 A; Q  Y% r    随机裁剪feature(PIL image) 和 label(PIL image).
      L$ {. U0 [& `5 X7 j8 l, m    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做% }7 k) B$ G' H( _, g$ u
        Get parameters for ``crop`` for a random crop.
    7 B" A, a; k6 \: ^    Args:- m) d* r( G2 s& Y( q5 X
            img (PIL Image): Image to be cropped.( ^' q0 a+ @6 x2 y& c0 e0 H4 n+ x/ j
            output_size (tuple): Expected output size of the crop.
    * d+ x" q# x9 W    Returns:0 [8 U% k7 z# ~2 U0 ^- w: u$ k8 t
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.- g' h: p7 `" D' j) J7 W
        """2 C9 L* J! U4 `) S+ c
        i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))- G9 ~, c- }9 n* O; t; l4 S; e
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)6 c8 |( ?9 Z% C7 p+ d& j6 N, u; C. d
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    / G4 v. z) B) H  B. l' g5 W' y    return feature, label
    ) D! O* s9 b0 `9 g5 E( t  P& x! `- A' ]6 W! o
    # 显示n张随机裁剪的图像和标签,前面的n是5, m/ j* l+ d0 i7 x: ?
    imgs = []
    ' m7 B( |4 ]5 q& G. N4 n2 e* vfor _ in range(n):* b% U6 d7 ^) }
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)) l; R; B" @: p2 c  N6 U- L
    show_images(imgs[::2] + imgs[1::2], 2, n);6 a" n3 D: x- u4 N# ~# J# n

    2 t( s9 ]) f: O. i% R3 H# h- |+ I. _/ k
    2.png 4 ?7 F: [7 @# b
    & V  Z) C" ^4 `$ }- \. K! _( Q) C

    3 L5 {4 i: U( O) c: C0 n4 I) M/ w3 i" |
    3 自定义数据集类
    # b! }/ b- |' [4 k* n* A( b# y+ B0 v9 Q+ z4 n# @& M; V0 X$ H  o
    3.1 数据集类
    1 v2 [% w( m* `4 d( D# ~9 v
    2 K, }$ j# F7 @! ?* j0 Gtorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    , Q! k! t/ d1 c7 _# V- X" W4 x* u9 T4 V! i9 s( k3 Q
    __len__ 实现 len(dataset) 返还数据集的尺寸。
    ; I- x" O& T3 K" E4 f% G5 S__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。/ A/ m( ^6 o8 V
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
    7 [9 X5 q  s/ o5 z7 j/ X! g7 H8 s" O, `) c& y
    class VOCSegDataset(torch.utils.data.Dataset):
    0 ?2 `, o1 b0 d    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):, g9 f9 [6 e; x" m" b* Z3 R$ {
            """
    8 Z! u- K9 V. g5 q0 K/ v9 L        crop_size: (h, w)- F. U3 P# `; Z" D9 d0 D
            """! }, D" }' q& a& W9 c& G( A) w2 Y
            # 对输入图像的RGB三个通道的值分别做标准化
    & z  ?% g, D4 z        self.rgb_mean = np.array([0.485, 0.456, 0.406])0 w- }$ {+ v  @0 m
            self.rgb_std = np.array([0.229, 0.224, 0.225])+ J  J7 c1 h2 a2 @
            self.tsf = torchvision.transforms.Compose([  W* E% W$ O: x3 u, P
                torchvision.transforms.ToTensor(),' Z6 i5 b0 q5 E5 L, A
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    3 F3 u, _; Z. V, f9 ]' L) b6 C        self.crop_size = crop_size # (h, w)6 W" F& Z7 s6 c& s& R0 C
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)! r; z6 j  t- ]* ?9 K: {. O
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    7 {# S6 n" |: u        self.features = self.filter(features) # PIL image
    0 i' w& k  A3 P: k) U* l" v) C        self.labels = self.filter(labels)     # PIL image# s* Y* l* |0 v- C1 \6 J
            self.colormap2label = colormap2label. T8 _# B0 k& J- V4 V8 Q
            print('read ' + str(len(self.features)) + ' valid examples')8 ~9 k2 M4 x  x
    7 l0 }2 a& `8 r
        def filter(self, imgs):
    / a7 z% O% q+ p$ Q1 Y. l        return [img for img in imgs if (
    $ N# R  Z9 n, e            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]1 ^+ c8 B& [- k8 A0 \
    7 a: Z  ?0 C( p" C/ f9 C
        def __getitem__(self, idx):
      i3 A; v* _2 J, s* h        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    8 ~) x( g' V+ t8 s" d; D9 a( S' K                                # float32 tensor           uint8 tensor (b,h,w)1 j0 n6 ?+ |) b
            return (self.tsf(feature), voc_label_indices(label, self.colormap2label))7 v6 |5 e2 K0 {# f
    # C9 C7 S1 @$ ?
        def __len__(self):$ O1 l+ F1 l, h
            return len(self.features)
    ! T% c. `& n$ y; e5 a* {3.2 读取数据集8 V0 c+ [& Q6 f5 ?/ J/ |! w' {3 p

    - @3 C7 u' `9 G) }4 s6 n通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    ! c( V7 I/ D$ X" K8 e9 Q/ S0 |; T1 P7 Q
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
    $ Q3 }+ R* _  R$ Y; @- \crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    9 ^. N7 g; E: b$ lmax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    $ T- t# y: [3 h
    + o! Y; w3 s* I% N" Z/ W# 创建训练集和测试集的实例
    0 H' K+ w' r+ g: yvoc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    ; y# e9 @  I( c6 }voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    ' C% m& h7 H& ?% r0 V/ @2 C2 r/ d  J2 g4 f! |% p! w
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器  D7 W- A' G7 P9 ~
    num_workers = 0 if sys.platform.startswith('win32') else 4
    2 \% \+ ^) ]/ ]0 k  k6 `train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    1 X2 X8 ~- K* B0 {; o/ }                              drop_last=True, num_workers=num_workers)
    9 W! [9 t8 U* G! c$ G1 `) `* stest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    ( |1 W% b% k0 \2 b                             num_workers=num_workers)- B* n: v1 t+ ^$ z
    ; T4 c& X- a" F& u. r- N1 l
    # 方便封装,把训练集和验证集保存在dict里: o# p( a3 U2 n8 @. U7 T' C
    dataloaders = {'train':train_iter, 'val':test_iter}. P' x5 D- V* r+ `9 g! r* v# d
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}! @6 l* J8 l( p& d

    0 v' z! w# X& A) R! u4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    ; u. t0 R" t  C  {; w
    ! q( K4 A; `' b! N& Pnum_classes = 21 # 21分类,1个背景,20个物体- u$ O" D, i1 q6 S
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数: X9 i: A( {! b: o! c
    0 d; |8 ?. _# a1 i7 S7 w
    # 特征提取器
    + F6 m+ Y) W8 N0 y8 j+ xfor param in model_ft.parameters():
    9 J, D: v3 v" B6 i8 r6 ~    param.requires_grad = False
    + J# q  f% X, G4.2 修改成FCN9 M+ H5 g( E+ Q3 W1 z+ e" n2 O; F

    " ]- ]: `$ {# H全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    0 E2 M$ x: o- Y2 [最终输出的通道包含了该空间位置像素的类别预测。
    7 ?5 z! A3 [8 T% v0 L. W
    - q2 l  \$ @; y对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    2 Z/ T( z1 Z) l8 k3 v9 S2 v! D
    , Q! D7 s. H. n  W4 q# i5 o! A; [可以先打印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 倍即可。
    ! i& p0 H( `7 g, U0 m* Q+ f" I3 `# `* k
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层+ X% J8 c8 d8 N1 v# b1 n# Q
                  nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class6 b1 o9 C9 R4 {* G2 p0 D; p) @
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小# G  i# h8 i2 \1 O- G5 M4 G; u
    7 D0 A/ F; [# v: u
    # 对model_ft做一个测试
    : ]  M6 D& p2 T& Rx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据$ j2 e& G- r) M5 w/ |+ N% A
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) 7 i4 \9 n! h# @& n4 }6 o- z

    . v: \3 P1 X2 f% c) w* T/ i: c# 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    6 f  F( z7 e* N* F" C+ h9 j% a# for X, Y in train_iter:
    5 z4 r7 R" N( G/ U#     print(X.dtype, X.shape)
    8 {: H! N# |# E9 `#     print(Y.dtype, Y.shape)
    / t5 q1 P( R6 u4 S#     break, G7 V9 a3 X4 C  f6 e3 G

    2 z4 x7 }, }* C' G4 z8 q" r# w, w! o& _& _
    4.3 初始化转置卷积层9 o' v9 a3 z% [) o

    * D; v4 e" x/ P在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像1 t" |6 U; N: i
    在坐标 (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函数构造的卷积核的转置卷积层来实现。
    4 }: \5 l$ G1 g! P& a8 R) o, |, M9 h
    ! P& k( B' [( X) M, i: b0 d+ Z
    1 N4 Y( n! [7 `' S2 U! P+ l# 双线性插值的上采样,用来初始化转置卷积层的卷积核# Z/ k* o7 `/ M3 U: {$ H, V5 `
    def bilinear_kernel(in_channels, out_channels, kernel_size):0 A8 l& o2 w2 F" P
        factor = (kernel_size+1)//2
    ; ]" i0 K: e4 D! U    if kernel_size%2 == 1:( C: S0 |! ?2 \, y7 T0 S
            center = factor-15 a- R% f) P# G+ v! F5 j" `
        else:* h# n3 G7 S8 h) f* o
            center = factor-0.5
    / j, A5 W: w& O/ p7 z    og = np.ogrid[:kernel_size, :kernel_size]
    5 W9 E$ q. M6 ]6 x7 n" ]2 n    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    ! L3 o8 H( C6 }5 K    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')* n7 D8 @$ C2 o. N
        weight[range(in_channels), range(out_channels), :, :] = filt- ]# i; ^& [+ G$ ~! k4 S
        weight = torch.Tensor(weight)" r, b* U3 o' Q0 b+ t/ @9 r
        weight.requires_grad = True' B" S& e8 e: v: e
        return weight
    ) T  K( a& e) i5 ]& \1 \3 [' O; L1 t" ]
    ( a* O3 y# Z0 C# f
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。* b" t- T" }" F' c$ U4 E
    4 H5 x# V: x- ?' |
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    6 b) M, _" y, j2 H9 a, V8 Dmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    4 e. ~) C) _6 p% D% B1 R" i6 C
    + R% p- y5 i  m  c+ h
    ! B/ b: I/ E+ ~, [/ k. Q, u! B8 ~$ \+ c( M
    5 训练模型

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


    5 _9 S3 @* Y. I- [1 Qdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):0 s/ }5 L8 R# h+ x5 @8 a6 e
        since = time.time()
    ! s7 Z3 a3 Y* ~/ t+ O8 [    best_model_wts = copy.deepcopy(model.state_dict())
    4 `# y! k- R; p# v( e2 s    best_acc = 0.02 s: }' V3 Y) p0 p2 y& S* g
        # 每个epoch都有一个训练和验证阶段& _; j. X( I, Z' I
        for epoch in range(num_epochs):# _7 E0 z/ N, b3 v; O$ ?
            print('Epoch {}/{}'.format(epoch, num_epochs-1))
    / h/ H" a7 {: j9 S/ A        print('-'*10)
    - {; \8 N6 d2 h7 U6 S4 Y        for phase in ['train', 'val']:
    9 A+ ~& ~( f* k4 ]4 S            if phase == 'train':
      {$ }2 C" `! g4 R4 `7 R1 b                scheduler.step()
    5 h8 A: C- s& V                model.train()
    / [3 y1 A7 D' [- B6 w: S' Z4 S            else:, C5 k1 K- E$ e# H7 D7 G
                    model.eval()
    3 R; J" d0 B! }0 H            runing_loss = 0.0, R" J( @" N" ?: W) h
                runing_corrects = 0.0
    " n4 N3 S, s3 e' |            # 迭代一个epoch
    * E+ G1 o. {, {  ]$ l/ b            for inputs, labels in dataloaders[phase]:
    % u  }. h& s) b; O" G6 K                inputs, labels = inputs.to(device), labels.to(device)4 k" Q5 \" e7 w& n$ W) O$ v
                    optimizer.zero_grad() # 零参数梯度
    * @" e, ]3 I( A$ {* `  {                                # 前向,只在训练时跟踪参数
    : y) Y3 p" J) R" u                with torch.set_grad_enabled(phase=='train'):
    * D. |( B! g5 @9 G$ {7 ~                    logits = model(inputs)  # [5, 21, 320, 480]. \% W; E2 j$ l' l, P  h
                        loss = criteon(logits, labels.long())$ b* k3 F0 J+ L6 W% @! Q
                        # 后向,只在训练阶段进行优化
    ) P5 c5 T$ Q5 H2 T                    if phase=='train':
    ; c$ U) L% M! h! e9 n0 T* h. k                        loss.backward(). V5 i9 ?, L, i: k; Y! r2 n! V
                            optimizer.step()6 e( e8 A) Z# |8 f
                                    # 统计loss和correct/ `: a% N' H7 K8 \
                    runing_loss += loss.item()*inputs.size(0)8 S  x. j! T9 r3 J. |( E
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    0 c' e: g7 F% G4 _' Y0 O' g3 K6 J% ?7 `0 _# l- A) n- T
                epoch_loss = runing_loss / dataset_sizes[phase]5 \" t7 r% f: z2 e# z
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    - X  ]) P4 X; E            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))- y7 k$ p, E1 W' V' y% _- Y* F
                            # 深度复制model参数& f# l) @0 |: T/ r- f5 {+ I7 y1 d
                if phase=='val' and epoch_acc>best_acc:2 `* R# X; j( w- ?6 X& Y$ A
                    best_acc = epoch_acc
    ' r! j6 |' r' {9 i' e                best_model_wts = copy.deepcopy(model.state_dict()); \6 H7 e8 Z- j
            print()
    ' K3 H! a0 _3 F& j# c7 E* R( f    time_elapsed = time.time() - since;
    9 h- k  k9 T# e( h3 q! _  N    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    1 G8 h) H# Z/ R6 J    # 加载最佳模型权重' Z& _" Z2 M9 x0 e# e
        model.load_state_dict(best_model_wts)1 h* p" K$ I+ b' o. Y2 u6 l
        return model
    ' b3 K8 l" k$ f9 Q" U
    4 W2 c. H- v4 L) v$ Z下面定义train_model要用到的参数,开始训练% r+ W. U9 R! }. `* b% @

    # V7 w4 }/ m- D( Z% i: oepochs = 5 # 训练5个epoch
    : \# R) {( @8 V0 Q- P$ Ccriteon = nn.CrossEntropyLoss()# V2 }* l# ?: H8 K/ h- v& `
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)' F. ~- }( w( Z, J
    # 每3个epochs衰减LR通过设置gamma=0.1
    9 m1 @3 [& N8 A. i: rexp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)6 d4 A4 y5 `7 y- O1 G
    ( f& ^$ G# w6 m: c8 i
    # 开始训练
    4 G( I( A) y: L! d! ]+ j- {  c6 Lmodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    ; d/ b) [. n/ R, }6 {' l- d: h3 W) g7 \0 {
    6 测试模型

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

    def label2image(pred):
    2 C/ g- P( J4 d8 O4 N; ]5 ?    # pred: [320,480]. p( [9 }/ p0 W, Y1 ?
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)1 U; ~+ p+ o% M. v3 F7 `+ `
        x = pred.long()
    ; A5 `  V' U4 t1 m- f    return (colormap[x,:]).data.cpu().numpy()/ a: D, n, h, G! z1 W

    # j0 \! X/ a* Z& S$ _0 ^

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device). e2 ^9 i2 ]# x) J
    std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)7 V$ {' i1 V1 ~
    def visualize_model(model:nn.Module, num_images=4):
    2 j$ t6 u% [! ^3 M) [    was_training = model.training' s: K' u* f* ?8 H1 S! ?
        model.eval()
    " m# f4 |7 U  u    images_so_far = 0
    - r4 E: N; J. {2 j: x3 e( A7 w7 |    n, imgs = num_images, []- s& V8 L* z3 \/ a- f
        with torch.no_grad():! B3 A6 C( q# P4 x" l
            for i, (inputs, labels) in enumerate(dataloaders['val']):) W1 V0 r- d( t0 ]
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]4 q3 K- @# J% U
                outputs = model(inputs)
    " C; b, A+ T: `3 o# H% Z) w            pred = torch.argmax(outputs, dim=1) # [b,320,480]
    $ [. N5 Z, \9 g; [            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    3 o) n' t# ?! [) p' n- Q' p2 \4 n8 \' p- _" |
                for j in range(num_images):
    : e" Z5 [- ~' G0 G$ ]& |% \                images_so_far += 1
    * B4 o+ I% Q. ?7 g! a                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)5 y4 M) _2 J  v1 X1 O
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]& _/ `$ J( W3 ?0 C( u& I+ v/ I
                    if images_so_far == num_images:) G% |1 W' x, M" t) J; o
                        model.train(mode=was_training): X2 a5 Q* q) X/ q6 Y. e2 z1 [
                        # 我已经固定了每次只显示4张图了,大家可以自己修改
    + Y# ^6 u" \7 Q& F$ t                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)& ]# e; P" R! J( C
                        return model.train(mode=was_training)
    4 V8 z! \" y: f" v* T, D7 H% \
    1 y$ h' ~2 }0 s7 P) ]6 G# 开始验证
    0 {/ I# a6 c9 V. G7 h/ r9 Y! z- Hvisualize_model(model_ft)/ {& X! f. B$ C# c2 _
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor# R/ N, }/ B  h! E* A' y) `) N7 d
    def predict(img, model):
    $ r4 A7 ?3 I" p. K' ?1 b    tsf = transforms.Compose([$ _% ^& d/ C: q7 l) x8 K( Y& C+ G
                transforms.ToTensor(), # 好像会自动转换channel
    2 `6 a6 Q0 q$ P$ v            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    : Z7 a6 S) {7 \0 z' \3 v    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)3 {6 Q; Q$ ?9 l$ e% Q0 k0 f, j  x, n
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)$ K: R% H4 `% [/ k1 |8 V, V
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    % {+ V4 i( i6 s- O- J, B2 s+ x, d
    , Q) t9 ]( f2 j/ G2 ]3 W7 T% W1 Edef evaluate(model:nn.Module):
    , E6 o: Y3 o! w    model.eval()
    , d. v  s5 B, V- x$ H# w* X    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    + F; x3 G' W+ g+ Z) u( Q. }; I5 V    n, imgs = 4, []7 {. ]( x& O1 c& ?/ X/ N; d
        for i in range(n):' c2 w% h' R4 u1 O( m  L
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    . q) K8 F* C% N        pred = label2image(predict(xi, model))- Q  ~2 ]' ?$ M: R8 ]- |
            imgs += [xi, pred, yi]
    ( d5 Y  L- y& r6 w4 D    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    . u) D# s! T; ^* M5 E
    5 S7 L8 Q7 x+ t8 g3 y: X# 开始测试1 G# X6 j8 G/ Z  K# n
    evaluate(model_ft); Q& Z& g! W4 Q6 B
    5 I9 c; u4 [1 w$ x; V$ x2 d" Q
    7 结语

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

    ! J5 s" U# T/ B, o9 q
    Epoch 0/2
    & o2 ]" H' j- O7 ]: m----------
    ; {" q  T- J3 G7 \! b/ N' Vtrain Loss: 1.7844 Acc: 0.5835
    6 S! g/ Z& M* J" q* q+ Nval Loss: 1.1669 Acc: 0.6456" I% ~* r' {8 E, j( I
    1 n5 N: G8 Z9 Q) Z2 \$ o( `( Q
    Epoch 1/2
    % e# J. D: X# s  f----------
    5 H- q: M4 p8 o* A' _. ?5 Ptrain Loss: 1.1288 Acc: 0.65352 y* p1 a4 E  i1 G
    val Loss: 0.9012 Acc: 0.6929
    + U0 Z) w8 G" X: w  n
    # R- x, ^' t" \0 P. q/ D/ _Epoch 2/2' ]7 |0 i/ X; ]7 y
    ----------
    2 H8 G+ g, |" B& H! ~train Loss: 0.9578 Acc: 0.6706
    8 x, z( W$ f2 N* L! M/ H8 lval Loss: 0.8088 Acc: 0.6948# E) B* z1 L0 B4 Z( N- D
      T, A5 C5 q6 @3 {/ p$ n- I9 ~
    Training complete in 6m 37s% `3 I8 T: Z4 C3 }6 s
    # T( Y  Q0 E5 u. D& p! J, @
    7 _2 U) z9 s5 Q: u* u  O' m
    2.jpg
    ) m* C! A, K9 b" K
    7 ]' V2 f2 H" o- b. k0 _0 ?当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。- u2 \  Z0 V+ L, k2 s; [
    : Y/ M9 }0 s- l) N" j0 K
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。7 ?6 D3 ]5 T5 M, j, X+ N9 `5 w

    1 C; F& A; B4 `  G% H
    9 B' |( Y& G' b2 }0 k. I* N! s/ g语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:
    3 |' f$ W0 x& B2 B* Z: v0 F
    1 |' j" B' H# g' R2 _Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]
      v/ D- ^9 M' j  {- S/ XGCN 通过全局卷积网络改进语义分割[论文]
    ( T6 b5 b8 A2 I- [: }( o/ FUperNet 统一感知解析
    9 |  s; @% J; @7 r7 W% y7 kENet 用于实时语义分割的深度神经网络体系结构[论文]
    , s+ `* ^$ K/ [6 ]. \) PU-Net 用于生物医学图像分割的卷积网络+ `* t. W4 o# v0 ^4 V$ {
    SegNet 用于图像分段的深度卷积编码器-解码器架构。0 m7 f7 ]* R; s* M
    还有(DUC,HDC)、PSPNet等。
    0 j6 e+ K' P* h; o6 n0 ]7 j. k- R6 X( Y/ x  X! F3 i8 t
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。& }! N: X8 {! @. I
    6 q8 o  V/ W2 M2 N+ k
    对于损失函数,除了交叉熵误差,也可以用这些:/ K# I/ z0 E, D7 ?7 Y
    , |% U; l4 V- m8 k: M+ ^+ Y
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    ' v7 m# B! Y, q' hCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。2 T# |4 V; ~# t; f
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    ) r; u8 J0 f* t$ d1 F3 ^Lovasz Softmax 查看论文:Lovasz - softmax损失。
    , ?7 L' U4 |1 h1 j3 l  Z  [$ E9 l# b0 q

    6 O& s- \2 }4 W3 O2 x5 d& y; x8 S/ `

    2 T- t9 [; c" o& B9 |' ^: t) E. z% d6 k  f/ C) h
    ————————————————* I4 s, I. w( Q
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。$ T# W1 X4 Y4 a6 a* Q: H$ H( f( g
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    / Y3 B: x7 W. t6 s, J+ Y7 i& S& @3 B

    9 C$ i" R* c$ l  E3 _$ O+ C( q* X( K' C$ l  f' o
    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-12 10:45 , Processed in 0.405590 second(s), 53 queries .

    回顶部