QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 11096|回复: 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
    ' D4 {% U( y; X8 l

    1 v- X: F! X; W3 J+ e5 [Pytorch实战语义分割(VOC2012)( @$ }* a2 k" a, d3 y% F
    本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    ) ~0 B9 |. Y: e% {; j2 y' u# \语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    5 ?3 [" |/ }; w* k
    2 s9 ]% Y; L/ ?& t% g. K5 X6 ^7 {: K- g4 q  ?9 ^
    语义分割中图像有关狗、猫和背景的标签* P  h2 @* r8 Q, g7 g$ ~( H
    文章目录: ^- b( F8 m* M5 U
    - {% p' [( E" k7 K( Z/ B
    1 图像分割和实例分割
    8 Y* O* q, o; j; {) ]/ q2 Pascal VOC2012语义分割数据集* ]' X' B* S3 @$ x
    2.1 导入模块3 ^# D' v. I- |  g/ z
    2.2 下载数据集
    5 {* T- S0 y/ w( W, K( T& w2.3 可视化数据; J8 c) B8 \& Q: m- O1 y2 Y* ^
    2.4 预处理数据( X; N0 i4 r$ Z% l/ t4 \$ V
    3 自定义数据集类4 F0 J6 D6 z' h* ]8 m* ^: E
    3.1 数据集类: |1 g3 @6 G. |+ V& [, V' {
    3.2 读取数据集
    # o: g- O) L. _; S4 |4 构造模型
    3 E- g8 F+ n# ~* X9 W% h+ y4.1 预训练模型
    + T: A/ |9 p( t) W4.2 修改成FCN: ]+ O$ J+ }0 r0 x
    4.3 初始化转置卷积层* M/ ?" E+ q% I  P) z5 t& c* z
    5 训练模型8 a0 l& a% j/ r" Z: I
    6 测试模型# f5 o3 A5 l  \- ?6 Y6 q' \6 A
    6.1 通用型1 h+ }# K) M5 E  q
    6.2 不通用
    8 E" P+ W& |8 e" K7 结语
    ( d3 y' l3 v; e( x1 图像分割和实例分割5 O* }! N7 @+ H4 }
    7 u. H. M  _; U* X# A2 |: |4 n
    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):# @/ G  d! G7 O7 D: {# g0 ^
    . p; ]+ d# D/ T, b
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。/ T4 {: N* m* ?  e# z1 y4 i4 c2 T
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。$ p, P) t$ d6 Z, N- d* T

    $ w9 T$ |# G4 k+ p2 Pascal VOC2012语义分割数据集
    2 c2 V, j; P$ z7 n% ?, d: W, [' V+ Q; P( I5 g* {5 g6 E- K  ?
    2.1 导入模块! d4 Z" k1 N: W$ X9 Z! h. ^
    import time5 w% @! r: y8 H
    import copy
    4 W( d. Q4 G' g. Mimport torch
    7 O4 q3 d( x! [: ]from torch import optim, nn
    # o$ ^, d2 E6 t. S9 [import torch.nn.functional as F
    . n3 r/ x5 U, t% h4 k1 B3 s. A$ h6 V9 {import torchvision
    2 A% l4 f$ N6 s- R- Z* cfrom torchvision import transforms
    8 B) G7 v9 @: U* Hfrom torchvision.models import resnet18
    / q( W3 j7 N( \( T8 @2 H/ Aimport numpy as np5 t! y9 _1 P, U" P; n
    from matplotlib import pyplot as plt
    , U! u# X' a2 m1 E% v) `from PIL import Image: _* v7 F2 A' T
    import sys: Y4 y* I, Q" _( {
    sys.path.append("..")' ~% W) R% ]5 [9 \. l# n
    from IPython import display
    & X* u: z6 }/ H( l6 q. D" zfrom tqdm import tqdm
    . a3 l/ b- i% E/ W/ zimport warnings
    / M  S8 P5 Y+ m9 i# _1 n- ~/ R7 Dwarnings.filterwarnings("ignore")
    1 z6 I. J. i; F1 @7 t3 g/ w8 L$ a
    ; v2 _* Z4 h7 f/ R6 E3 a2.2 下载数据集
    4 t8 @, t. g: V, K
    $ X8 |2 `# j9 q: a: j" h" b语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:% t, f8 ~. Q* l; R6 [+ e
    8 r; W6 n2 I. O; v8 Y$ i7 e

    & b6 s) C$ o. v; n; w9 CImageSets/Segmentation路径包含了指定训练和测试样本的文本文件9 N( z: D/ f8 G# E/ ~1 q# e
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。& s3 j8 [7 Z$ k$ Y" V
    2.3 可视化数据# F6 g6 _! ^8 A; G6 @' R3 T

    4 d  y: ?* v! r+ ~5 w  R/ Z定义read_voc_images函数将输入图像和标签读进内存。) h3 F1 V0 j% K  V( ^
    ! O5 ?4 }+ M7 @! X1 t
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    : b% }! H6 `+ S7 @8 z! E+ _  N; E    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    $ z$ E; f3 o( ]+ K/ X4 i    with open(txt_fname, 'r') as f:
    ' G, j/ |2 e$ ~% ]7 b        images = f.read().split() # 拆分成一个个名字组成list
      w3 i! M0 b" ?$ Y. Q# }: p; F    if max_num is not None:
    3 ^  o# h# l: C( m, \        images = images[:min(max_num, len(images))]
    8 k, u1 V# _' G- S& o/ ?" u    features, labels = [None] * len(images), [None] * len(images)% @& ~- |- r. s+ @& a" T
        for i, fname in tqdm(enumerate(images)):' y% \5 R% L8 O0 \6 l' a5 g4 V
            # 读入数据并且转为RGB的 PIL image
    0 t" c8 m- N3 t; q/ r% @! Y        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")8 T9 I4 y- l6 ^! m7 X( F  L) i
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")
    0 o. I& A4 x4 D: o' @& T. w    return features, labels # PIL image 0-255
    ) }% _6 v6 l. U5 [$ o7 M$ G& z4 ]3 a; L5 B) T. W
    定义可视化数据集的函数show_images, T  O$ J0 |) U( y4 R8 F
    # B: S( I4 h9 d% m$ o) U+ i: n
    # 这个函数可以不需要
    * Z1 [$ ~, j0 Jdef set_figsize(figsize=(3.5, 2.5)):+ `6 ?3 f0 W& E0 J
        """在jupyter使用svg显示""", Z4 T. l% v- p
        display.set_matplotlib_formats('svg')
    % N0 G& m- N5 ^$ Z    # 设置图的尺寸! H! c/ ]+ V3 q5 C! w' r) X9 s
        plt.rcParams['figure.figsize'] = figsize
    & V" \* V+ b3 ]* _
    . s* O9 ]1 g/ Z6 [8 _+ W  u* L+ idef show_images(imgs, num_rows, num_cols, scale=2):* Y* G! C0 P/ X1 H8 O8 G# H. [
        # a_img = np.asarray(imgs): s6 e8 I  t  n$ A* P8 f
        figsize = (num_cols * scale, num_rows * scale); `6 c( e2 M+ X6 `
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    ; U1 @" g7 S3 ?5 n2 j, P+ V    for i in range(num_rows):
    + {0 w" H' @$ `        for j in range(num_cols):
    8 R: g( C  x1 l  f; F            axes[j].imshow(imgs[i * num_cols + j])
    4 w' b; b; p# N8 O5 ~8 K1 ?0 P            axes[j].axes.get_xaxis().set_visible(False)
    3 _& k3 K, q! R& B8 c( i/ W            axes[j].axes.get_yaxis().set_visible(False)
    $ ~9 P# Q; p6 m9 [3 H9 n' W    plt.show()5 e5 W8 A2 N" d
        return axes
    8 Q4 l3 R+ c' M$ |
    ( Y* o5 L6 P: m% m; ]定义可视化数据集的函数show_images
    0 K- e8 N3 w" |3 u6 \; Z! Y1 s& E7 v8 B
    # 这个函数可以不需要
    - b; ^3 N9 D8 @$ Y7 D* s% f& _& D8 wdef set_figsize(figsize=(3.5, 2.5)):
    ( A- g' A3 T3 c7 ~1 t$ B0 q    """在jupyter使用svg显示"""3 N! ^3 ~; Z- V( i7 I) K4 r2 Z
        display.set_matplotlib_formats('svg')
    # B& p: n: n1 v    # 设置图的尺寸# M& E* y% |! i  \' Q9 S  g' p6 u/ h0 Q
        plt.rcParams['figure.figsize'] = figsize
    2 C1 i$ o! ]. m
    ( H" ^7 P' @  |" ydef show_images(imgs, num_rows, num_cols, scale=2):7 T; K" H$ t. H! S4 b
        # a_img = np.asarray(imgs): }$ B2 d" K& I2 J. u0 l
        figsize = (num_cols * scale, num_rows * scale)
    0 a5 C. e6 c' a8 w: N    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)* o  J9 p1 F# |; X) I7 T
        for i in range(num_rows):3 A7 A; L9 d4 p' t8 e- r: w  w$ ~
            for j in range(num_cols):! [& k" M6 Q% Z0 I
                axes[j].imshow(imgs[i * num_cols + j])
    7 b% s% c+ l7 F8 i, u            axes[j].axes.get_xaxis().set_visible(False)
    ! V( p2 A, N) [- e            axes[j].axes.get_yaxis().set_visible(False)) Z4 B% {( [' @- a% b, n
        plt.show()$ `! A8 U- p1 S! z" y3 f% Y' z
        return axes1 J' ]9 y$ i+ r/ n
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。
    . p/ J, }, h0 Q# t) T# L
    5 I! o- _% [8 G4 i: P: O# 根据自己存放数据集的路径修改voc_dir
    : ^1 c2 _* d0 D- a( Kvoc_dir = r"[local]\VOCdevkit\VOC2012"4 d7 A* x# Z/ J/ a8 Z* a: p
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    - G9 f5 l9 M) b, z4 g2 Jn = 5 # 展示几张图像' V& L- j, Y4 b, P" w% \
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    " d5 G  L- i& W2 @' b! W6 O6 Ushow_images(imgs, 2, n). k% T6 e3 G9 O- J3 \% a% R

    ) \4 M8 @4 b# ^7 z 1.png
    1 e- k. {6 e2 H7 y7 `' m' F* {+ d  ^$ v% u" i5 W, d! T
    列出标签中每个RGB颜色的值及其标注的类别。
    # Q8 |+ w7 d! w" d# 标签中每个RGB颜色的值% S/ T7 M4 |( y5 w& X
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],1 h' k  C  G* ^
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    & z3 a& O% q- d                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    7 o2 D2 ?/ P& _, h# H                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    + @/ ^9 \+ z: T: S                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],* D6 s( N. d' J5 A, V2 T
                    [0, 64, 128]]9 \5 Z5 K5 B! S' S
    # 标签其标注的类别9 P% }0 U  n( _# I
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
      @5 B9 }, a4 m0 {+ p4 k9 a. z+ m               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    # E% X" z) R4 ?% F' R               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    ( G. V. i# l6 q# u               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    " E$ d( d4 j" @有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。4 ?! m% Q7 j. J, O

    4 v3 P& K: b, f: T+ ~有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。5 Z- b4 @+ a# B% U
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])/ f+ ^$ A" r/ }* B& R
    for i, colormap in enumerate(VOC_COLORMAP):" }' i* o5 R, Y6 d; E' b
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i( ~8 |- k) K9 x1 f( D
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    2 B& X' i( o) Z1 {* W# M% m4 o# e
    $ `. c1 s, O: ], i7 t# 构造标签矩阵2 H2 [9 x+ y6 T# n+ D* w
    def voc_label_indices(colormap, colormap2label):
    2 B. J; k3 _3 z; ]    colormap = np.array(colormap.convert("RGB")).astype('int32')
    0 _* w4 Z; C+ i  ~, q    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) . L) M  P; A  m: Y4 p
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标. [' @; r, h3 D+ i6 W

    6 G5 n7 T; Q+ ^9 O可以打印一下结果
    $ l* ~' q) A3 e6 L
    ) a8 m! u" f' d. h  _8 A% Dy = voc_label_indices(train_labels[0], colormap2label)
    9 k8 z" U; z$ j$ w; mprint(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    % K& ?/ B9 [$ v/ F& ]2 n1 a( F( T/ ]2 J1 O& T
    2.4 预处理数据& }+ g% l9 U% q; b$ m7 X3 C
    5 ~: J# B% S6 A
    在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    : I6 W6 P: Y2 E  L5 M1 F; Z2 @' S8 g' n

    ' _/ i3 A3 i$ q7 x; P+ o3 j: U6 _def voc_rand_crop(feature, label, height, width):
    , ]5 R/ ~* c8 J' i9 L7 R$ ?    """
    4 Q$ ?. v, h, i% q    随机裁剪feature(PIL image) 和 label(PIL image).
    , G+ \! Z! w0 L& Q* i$ C& A, u/ Y    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做0 d4 `) z, t0 M6 e0 I" g8 ]
        Get parameters for ``crop`` for a random crop.9 [: I+ B! c/ V, O
        Args:
    . i: k/ s: r5 C5 R8 Q+ y, r        img (PIL Image): Image to be cropped.
    , j9 t* V! c+ G/ I. K$ c* r        output_size (tuple): Expected output size of the crop.
    # F# S5 u5 |/ M    Returns:
    + z) s' }& I2 A. ?        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.  R" I. S/ k0 G/ L: z( Y& O, X
        """0 C; |) [. B; P
        i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))9 d6 P& q$ Y1 l3 |* s7 R
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)# m* d5 L7 l  V( J' W( s2 R* l
        label = torchvision.transforms.functional.crop(label, i, j, h, w)% D9 D0 f9 p/ v0 |
        return feature, label0 p& l4 w1 ^) ]

    % Y# |  r  ?; Z' Q# 显示n张随机裁剪的图像和标签,前面的n是5
    ; p0 W* D% a: q/ o( x, b4 jimgs = []
    0 \# R/ `6 _4 f$ g  _for _ in range(n):
    # N/ [( V; M5 c  a% l+ L    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    / c* C( S3 ~, _show_images(imgs[::2] + imgs[1::2], 2, n);  @7 H; V  C0 c; B

    9 `" j  e% _2 g/ Q
    & V* L3 A! H5 D/ ~ 2.png
    * y2 s6 ^6 J7 Y- T9 M, e. w' Q3 B
    1 T) z8 `* W1 N5 F% p5 ~8 t. V
    5 M9 `' h- T+ {  L5 Q* D
    # w( \. u8 }3 o8 Z3 自定义数据集类
    - A( ~- w& a; n4 b8 A
    3 _2 ?: L4 i0 y) K: a" F0 E3.1 数据集类2 V7 J5 y( y$ {
    6 U3 c! p+ C1 t. @
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法4 G; T5 E0 S1 C9 j
    : P- M/ P9 L" u/ S1 s- x6 T
    __len__ 实现 len(dataset) 返还数据集的尺寸。
    ) i% H+ A) L. c, R__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。; U8 a  V; r7 x* u/ h
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
    , J. j0 _5 y0 s9 ~: r! W. A6 y, B! U5 I# y
    class VOCSegDataset(torch.utils.data.Dataset):7 _: o4 [8 P" F3 c2 ]
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):" @, G% A; o8 w/ L$ V+ z, k
            """+ W+ A# ?0 C7 @3 f
            crop_size: (h, w)
    ; i9 U1 f6 R( S, r        """8 n/ j. J( I% N' g7 t$ ]
            # 对输入图像的RGB三个通道的值分别做标准化9 G7 j+ D6 Z$ A- S, P! }' N
            self.rgb_mean = np.array([0.485, 0.456, 0.406])0 v! ]9 ?0 Z1 z1 `: X- ]$ W" A
            self.rgb_std = np.array([0.229, 0.224, 0.225])
    / O$ @' h* q0 Y% b" \! o% v        self.tsf = torchvision.transforms.Compose([$ |- R# G$ F: u; f0 F0 [% @
                torchvision.transforms.ToTensor(),, A; Y; x* t1 r+ A$ P) s' Q- p
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])' c- i: a, y# w8 f7 M9 o. n$ o% Q, l
            self.crop_size = crop_size # (h, w)+ J+ Y# w( d7 t- W
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)3 K4 B/ n" ^& c- _/ S
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除4 q7 H5 S! K% x2 d4 b* ]$ e( f3 F
            self.features = self.filter(features) # PIL image2 y- q2 k) f; I
            self.labels = self.filter(labels)     # PIL image  a8 S5 i* z2 X) i" R7 _
            self.colormap2label = colormap2label: Y% V+ [$ H/ a2 H! G
            print('read ' + str(len(self.features)) + ' valid examples')
    6 D: ^9 q3 ~  d4 c! H: t6 ?: _' y) D& E+ K3 j" h; E
        def filter(self, imgs):
    $ ]9 f1 @3 k. C0 P        return [img for img in imgs if (0 q6 L# s/ O/ G, |5 d
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]5 M% {* S% _; {# G& U) C

    3 k' X+ E$ O0 M0 u; V  {    def __getitem__(self, idx):
    - \6 b& q0 F! J& u        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    0 u& u. \* c! g! m! ~7 C                                # float32 tensor           uint8 tensor (b,h,w)
    . I1 D8 U- f8 s. n; d        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))' B4 u# \! r# a

    , G2 O, W$ q. u. F; G' f    def __len__(self):9 H+ }% f3 F" O6 O  J
            return len(self.features)
    & w% {% n% i3 ]% }% c3.2 读取数据集
    ; b/ S; Y7 q1 i' o! d: ^! v
    8 y* M" ?; V0 T0 \' n; D通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    8 l% Q+ `+ X2 a) b* [9 t
    5 V9 t; k, u5 `' o# @, Wbatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)! N7 `1 ?4 Y+ J0 T! n$ W: u! L: `  Y
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    + X! i& F! U! B$ Pmax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    , A3 F" Z2 N7 R7 e, N% g* {8 n& P# k2 {3 e# i
    # 创建训练集和测试集的实例2 U+ d6 r* k) A: K2 }; A8 w/ Z9 n
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)) ?3 R6 n& W: e/ l+ m; U
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    9 W& h& t0 m9 u- m: D  `* s
    6 W) p4 q# A0 y; L0 ~. e# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
    & R; r/ V$ D, p. |% Knum_workers = 0 if sys.platform.startswith('win32') else 4
    : D- A1 R' Z, m6 R4 Y" g" F. s2 h$ R2 dtrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,( x, ^4 a9 j# Y$ n, X! V
                                  drop_last=True, num_workers=num_workers)
    ' ]9 n( q3 e# a( N) k4 rtest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,# _8 H5 Z. x, V$ `8 D! g0 k) f! ]/ i: }
                                 num_workers=num_workers)
    & E: s$ w: j$ q% ?8 x  n" r
    # P% g( }; |1 `; v$ h# 方便封装,把训练集和验证集保存在dict里+ |  _  N9 B' k! i5 g) {
    dataloaders = {'train':train_iter, 'val':test_iter}9 t/ B7 o( I0 W! d2 }+ ?6 q
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    / {% E- ]6 ^, Q% U! f6 M9 a9 |. p- D0 \* \' o* E. C) t- b
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    9 w' e% V# K* a
    # v6 n/ [& w& ]) D7 bnum_classes = 21 # 21分类,1个背景,20个物体  }  Y- U, s6 `" i' ]( U- N' L
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数& q1 I# [/ Z' r- r/ Q$ y

    + q# L- l# a* [* }% {# 特征提取器
    8 m9 ^! c$ ]' H* Y) u" Afor param in model_ft.parameters():
    1 X0 [* z( O4 i8 ?) {8 B( ~+ r    param.requires_grad = False( O+ T: I/ R$ E7 U/ i
    4.2 修改成FCN
    & O9 ~. v4 C1 w
    ! a: k. a/ Y8 Y" I) w全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    * P+ }8 [. d, C# V, S8 }最终输出的通道包含了该空间位置像素的类别预测。
    / _8 H) F# }" h7 D5 z+ f! q; j- o
    对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。' d/ j  V, Z' @' x

    & v7 t& s7 w$ U7 |9 C1 C* ]可以先打印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 倍即可。
    ( r  A! W8 ^# N# I7 l  b' \% o% u! t
      }% {1 M: S9 Q7 Bmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层0 q$ d4 s* q; U3 y& _' g1 L) ^
                  nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class" {+ R  }. C: G/ R* e/ k
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    7 m1 k0 a4 _2 i9 B7 U6 A  Y1 `' d2 w5 N7 a4 {/ J7 R4 r
    # 对model_ft做一个测试& ^" N6 \# O; K0 L7 [
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据, F* X) V. H# z5 U3 L3 \3 q
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) ' y& `* n4 M) I4 p( n
    + O7 S0 R9 E" j7 r' A
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组8 Q  j0 @+ M6 n0 t. g$ u& @
    # for X, Y in train_iter:) A! I; {+ H! F+ D
    #     print(X.dtype, X.shape)' u" P) v" g/ Z! y
    #     print(Y.dtype, Y.shape)
    9 C/ y2 C* `8 ^% F+ V/ S#     break
    # ~& s( U  Y( }. d% `# l
    ! U% g, i, Y( Y$ A2 A1 ]0 p$ q3 [8 h) Y  b
    4.3 初始化转置卷积层
    ( V8 b0 j- H+ D. _  A% X
    1 _- u# ?$ L$ G. R3 P8 A4 Q在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像$ {% h# C; s+ q9 |
    在坐标 (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 i: T# X! j/ c3 `. ]

    9 b; a% Y! x; p0 x. G, ^. z6 Q! c! B$ C+ l
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核
    1 e- M7 }& p7 wdef bilinear_kernel(in_channels, out_channels, kernel_size):/ e' G! j1 i4 c1 V0 }
        factor = (kernel_size+1)//2* F* B# |& J# M0 W" A, J% B
        if kernel_size%2 == 1:
    $ K8 J  c% c6 G( E( k        center = factor-1
    # Z" q  }: x) E( P. U5 Y    else:
    6 F! V" ?; @7 V9 A        center = factor-0.5# j: w6 L2 z! X6 x
        og = np.ogrid[:kernel_size, :kernel_size]
    + q3 ]9 ^% @6 }4 z+ e, w5 t' J    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    2 w. ~8 _3 J! }5 U. w  z    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    7 A! W! c# \8 H! `4 v    weight[range(in_channels), range(out_channels), :, :] = filt* }+ J& V% A+ @$ D/ P% {) Q  C
        weight = torch.Tensor(weight)4 `' h( s7 j: l' M2 c
        weight.requires_grad = True$ I9 }; i. ~2 B7 `2 y/ V- a
        return weight9 O+ @3 J* W* E
    % ]6 m( p% D$ r3 q3 a# B8 c

    ! w# B4 J- K1 B" z% }6 L6 o; b1 W在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。; W6 ?* \6 w' p2 L# j, m9 ]
    + O, v0 h- G1 Y/ N6 {! }' Y: q
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    6 I+ s- h9 o7 w% c, [+ H- U  e3 A; Cmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device); d7 c( m2 J( K- }
    6 X2 K  G) _0 C0 y" m0 o1 N/ A0 W
    6 F& h2 D2 t( H9 t

    . m+ a8 n0 d& W( c8 ?5 训练模型

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


    # E6 f4 b2 q& i. _def train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):! w! _1 z& `3 S% a
        since = time.time()9 s/ k$ E! A- Y# o
        best_model_wts = copy.deepcopy(model.state_dict())
    ! \. F  Q9 h4 v, [    best_acc = 0.06 Z1 h' O+ i; ~0 L" A# N% M
        # 每个epoch都有一个训练和验证阶段
    ! s* B1 m( y5 M8 M    for epoch in range(num_epochs):
    & U* h: N% ?5 I) [        print('Epoch {}/{}'.format(epoch, num_epochs-1))
    ; X. v% h  T8 c+ [1 \        print('-'*10)1 E) o1 A( T' m* e1 X
            for phase in ['train', 'val']:
    3 v+ o, `; U9 a, }" E+ q            if phase == 'train':+ ~( L6 a6 ^- p$ P$ r& D1 c
                    scheduler.step()
    " H( S0 A0 [, {: L. W# G4 ^# z                model.train()4 ~2 K/ `& A, N; J/ j
                else:
    3 j. V1 F) y5 |                model.eval()
    2 ?1 e7 ^4 Q- L            runing_loss = 0.0
    # z' C$ m- \4 C+ p            runing_corrects = 0.01 [+ c2 X9 Z1 K$ ?4 N' V
                # 迭代一个epoch! X- m7 }) v# {/ [9 y
                for inputs, labels in dataloaders[phase]:
    ' [( `. H0 f- \: y5 ?+ E, `: B                inputs, labels = inputs.to(device), labels.to(device)
    ! F" F( O* ~$ {, p: H1 ^                optimizer.zero_grad() # 零参数梯度
    8 P" E1 }. n+ g* s5 B                                # 前向,只在训练时跟踪参数
    % k5 f+ G, D1 ~: L* y                with torch.set_grad_enabled(phase=='train'):
    2 n* ?" n1 D& w& l" X3 q- l" f                    logits = model(inputs)  # [5, 21, 320, 480]
    , g1 E; T- J7 G8 `# c$ Q9 ]                    loss = criteon(logits, labels.long())" H- b' G) Z+ U9 q2 u
                        # 后向,只在训练阶段进行优化
    / i4 g7 x, L& Z# M                    if phase=='train':
    . L$ P, ^# c9 j0 o                        loss.backward()
    & m5 ?2 c. \; D, k9 V- b                        optimizer.step()
    ) B. H. Y0 k, Q4 Q, j) k                                # 统计loss和correct2 i, h! R# A  c7 L: [& y) ~& I
                    runing_loss += loss.item()*inputs.size(0)
    3 U( X* F% S; }* @4 U: ]6 S                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320), _* w# U: P* |2 v7 C- E* Z

    $ Z) `! L! u' p            epoch_loss = runing_loss / dataset_sizes[phase]! d( V$ y8 [. f! I$ R/ U0 T/ C4 W
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]3 r" I8 m+ M. z5 E4 h
                print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    ( G( k2 `5 w" F9 X- B0 [                        # 深度复制model参数
    " m+ o/ l* e. e* `& ?. J  t            if phase=='val' and epoch_acc>best_acc:6 O1 ?3 w6 f6 c. g
                    best_acc = epoch_acc
    0 s' b1 q: h9 ]; d" Q; h, U                best_model_wts = copy.deepcopy(model.state_dict())/ H& k7 S* y+ s
            print()
      ?- n/ W* V4 z    time_elapsed = time.time() - since;
    & N3 Q8 m  a" b% s4 y0 F; `* B    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))& K2 _; c- I, W0 r
        # 加载最佳模型权重) N, Q/ ?! b6 s3 C; K6 A& b2 S
        model.load_state_dict(best_model_wts)
    , e1 D7 P1 M' M) I8 i    return model
    ; [/ I  O) o) |+ b, X) R8 r# Y. t3 ^& t' X
    下面定义train_model要用到的参数,开始训练$ V* P  G3 V& n8 s9 q* v. N

    3 q0 J7 A" o  n! `epochs = 5 # 训练5个epoch5 ?* x* Q% G4 z
    criteon = nn.CrossEntropyLoss(). G0 _" _  D- F9 _4 S
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)& L/ O+ _' O( g+ y! t# g  l  F
    # 每3个epochs衰减LR通过设置gamma=0.1
    # {$ N/ |7 P- j: W# [# Texp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)$ k$ o" F% _1 N  g

    6 C9 q( v( m" }9 B, I( S) \! D0 W# 开始训练; m* a7 N. V2 y: E* W; r
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    * g2 M& O/ _7 s4 c# A) D% A3 _
    - M( ^- k1 N; Z  _! L) X6 测试模型

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

    def label2image(pred):
    % K, D* {: a4 ^: b- D    # pred: [320,480]
    6 i4 A9 C3 i; a& s    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int). d* t4 \5 V) B- R4 l0 Z
        x = pred.long()# v( b" \6 j( T5 E: Q3 U2 M
        return (colormap[x,:]).data.cpu().numpy()
    7 ?$ a' v$ Y& z3 A) V3 `" C6 g8 Z) A; f/ e' H6 V2 u) Y

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    & R! r; a+ @- [3 A$ \& y) ^0 [std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)' s3 y4 a; R$ e
    def visualize_model(model:nn.Module, num_images=4):
    ( z( M0 D* E5 l; h8 _4 e3 g    was_training = model.training
    8 U! U" m) E4 K$ R) g8 G    model.eval()
    . ~" v2 T7 W* Q6 n3 G# A. ?    images_so_far = 0
    7 g! q; s/ V& z0 I9 v' h/ ^" F+ @    n, imgs = num_images, []
    ( i8 ]- G+ U: _/ Y/ W    with torch.no_grad():
      R  v" N8 d8 ?6 B0 A6 E  d        for i, (inputs, labels) in enumerate(dataloaders['val']):. w7 u2 ]' f  u7 m" |
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
    ) V0 [  y/ W0 J            outputs = model(inputs)
    $ D2 n& r6 G) P. N# T9 G            pred = torch.argmax(outputs, dim=1) # [b,320,480]
    * p! ]2 A0 e  m8 a            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    1 p0 K# h7 h) V$ I; v* L4 S
    $ H2 `9 i8 G7 [3 ^. K2 S$ s            for j in range(num_images):
    % _- w* U* @. b2 }, a                images_so_far += 1
    0 W* _2 H, m5 F) H; [- G                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)" j5 \4 ^/ {4 O) y* Y+ W  r
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]: X& U; l& Y6 \' S; ~9 i
                    if images_so_far == num_images:
    1 a/ ?+ b2 P- l4 f8 o                    model.train(mode=was_training)9 m' z) @) V6 N
                        # 我已经固定了每次只显示4张图了,大家可以自己修改
    . T4 y5 ]+ a5 n: Z: a                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    0 R$ Z# m+ k7 C/ w& k0 M                    return model.train(mode=was_training)
    9 ~# a! D. I/ b
    : P! S) v& }% e$ t# L; K, S, x5 d# 开始验证
    0 q9 N6 J. a9 N+ @" F/ ivisualize_model(model_ft)2 A; a- m; I! E; s) g. t
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    1 }+ W9 m  @8 a' L- A" g6 x8 edef predict(img, model):# Q% {& u' C' b/ A6 i2 D0 G$ Q
        tsf = transforms.Compose([2 c1 K: r. K6 {: C: J5 z
                transforms.ToTensor(), # 好像会自动转换channel
    " G" F5 \6 N0 [" L! \: C            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])* }# n' d! f' H- F1 z. J) Z
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)1 v8 n( G0 W* q! \% d! b; `
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)* }2 a% D3 b6 f0 O' G
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    ( @; D3 q8 r  b$ a: O2 ~8 n% _& `0 C& S( g  `0 B
    def evaluate(model:nn.Module):1 S3 O+ ^' A; W5 W$ s3 T
        model.eval()
    : F8 ^) k3 {6 V7 f* t7 F1 ]    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    0 E# f( T, m8 x) P$ q8 Y    n, imgs = 4, []
    ) D; a, a( b( }. ?9 m$ O; f( v$ F    for i in range(n):* V+ F& w' j$ t  P# B. p! e/ _
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    $ s5 @0 G5 g( w) c( n7 J2 v        pred = label2image(predict(xi, model))
    $ U) u; ~" O- j! L. i& _( j        imgs += [xi, pred, yi]
    3 l3 ^# e' m0 j8 N: c* h    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)+ b* n/ b6 z6 D1 Z: I; H

    & f3 `7 c* I. j* Y# 开始测试
    ( L  c8 ]4 @8 _& w& X+ levaluate(model_ft)
    6 H' l3 l! e0 H! f2 o) M1 r: P" y, \  p3 S; \3 J1 z
    7 结语

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

    # A5 s3 ~# a* X" q$ i
    Epoch 0/23 L6 C+ ]7 M2 K  U- E8 a) b
    ----------9 }4 C$ C/ \! b2 Z6 H* U
    train Loss: 1.7844 Acc: 0.5835! \3 W! ]! [& o7 Q
    val Loss: 1.1669 Acc: 0.6456! o; ]5 V9 g" B  f0 |+ J1 s% m/ Z

    % `, C0 Q' p% h, m) ~5 a- d2 m+ |Epoch 1/2
    ; }; j; b. l. O----------9 m* z+ n0 _7 u& A$ V
    train Loss: 1.1288 Acc: 0.6535
    1 y7 m- s& Y4 f6 yval Loss: 0.9012 Acc: 0.6929( G1 L9 C/ D: y& P7 x% t* C; @. e  x

    0 `! d, v8 v" o# p' k( \5 jEpoch 2/28 c0 G$ j' \7 Z, G& K4 ^
    ----------3 r3 b  {5 M( q  q: q! O+ L' m
    train Loss: 0.9578 Acc: 0.6706! T$ l! s% w" I: p/ Q
    val Loss: 0.8088 Acc: 0.69487 ?$ `% `6 r( i8 G' [

    : b5 Q) T3 s" `0 {# tTraining complete in 6m 37s. m2 b  N7 D4 y0 U

      _1 {9 X/ U% f% j& o: e3 ^! z0 @# d# H! E7 e3 \
    2.jpg
    ( C! P- U( [( V9 q/ g8 m# N6 Q5 k% B
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。# C$ b6 f1 ?) l" [

    , B5 B3 j7 |- V0 h/ y- v8 a对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    # e8 `+ r  n* V3 g
    9 S) c, M' R# U2 N' e& _
    : k1 C# ?# F1 M  v8 U) w1 z! w语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:  x: o, {4 ^# p: t7 x

    0 n) Q6 I' ?6 J, w' qDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]* e2 R& z# y0 w8 y; C5 I; I  a
    GCN 通过全局卷积网络改进语义分割[论文]6 X: H7 N/ T! _0 Y9 o
    UperNet 统一感知解析- T  `: i' V6 M" q; {& T5 F
    ENet 用于实时语义分割的深度神经网络体系结构[论文]
    / c, o) i7 w2 m$ X; _; ZU-Net 用于生物医学图像分割的卷积网络
    5 e7 w4 a5 J  v& rSegNet 用于图像分段的深度卷积编码器-解码器架构。& \- M9 y( p- V
    还有(DUC,HDC)、PSPNet等。
    4 h& N: G; ~4 r4 k# u- j& o- I
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
      P& ?) |" x5 T+ ~8 d
    , ^6 P, b: m1 `6 s+ R对于损失函数,除了交叉熵误差,也可以用这些:
    9 h  |1 K% j$ e* G( n* c; q
    - `, a' u. `  q( g4 [* w+ ]Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。* N- e+ \. X  r+ {
    CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。) n) m2 r/ _! H3 z
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    9 {+ h" y& P1 _* V* ?( q9 yLovasz Softmax 查看论文:Lovasz - softmax损失。- h2 {, l$ r4 Q. ^, }+ T
    ) t% n" _& Q; R/ j9 D! Q
    4 X; l% b' T7 m" S; M/ c% I

    , s  ^( K6 y$ q5 ?* ]% j3 }6 C3 b/ N7 [# c
    ) T4 }# C5 A2 Z# X3 n! f
    ————————————————
    8 @3 o5 m! Z! t; Z' X' x9 i6 M版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。$ G, ]) W# q' ~- k$ p( e/ w
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    , v6 l* T4 M2 y9 X
    ) @; _1 Q- h% E& c' k! @0 q
    2 ~2 `1 G8 f/ z- u* z5 A, }7 |6 O$ s  `/ o: b# Y
    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-4-20 12:59 , Processed in 0.434957 second(s), 54 queries .

    回顶部