QQ登录

只需要一步,快速开始

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

    - k% }$ S& J6 A2 m, E1 X
    . D& k; {) U4 o& N* I, V" GPytorch实战语义分割(VOC2012); m1 J; Q/ o  ?0 Q
    本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    9 o# e3 M4 Y! c2 _+ {' U语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    3 X+ Q7 Q% s, a/ a4 J* K
    7 _8 a! T4 Z, K8 j9 B! q1 F; x: v9 K; I! Y
    语义分割中图像有关狗、猫和背景的标签" t4 }+ i! Z/ A; z1 c
    文章目录7 i- R4 P( N7 e& N/ e% n! b: d

    7 ?0 ]4 O  {/ }/ q$ ]1 图像分割和实例分割3 d; P8 L  s; A2 X1 f; v. T
    2 Pascal VOC2012语义分割数据集
    ; C6 j( D# E+ I) {4 o2.1 导入模块8 _, M/ e6 f$ F  M5 K6 r
    2.2 下载数据集( N! C3 k9 c) i8 v2 }# J+ X0 G2 d
    2.3 可视化数据8 }" ~0 o- U1 o2 Z( g
    2.4 预处理数据
    : E0 _/ {7 l2 ^$ x6 b% o3 自定义数据集类, \/ _/ H$ l( z
    3.1 数据集类
    & F/ m! k! {5 N, R* e/ t3.2 读取数据集
    - t9 t2 ^3 [6 B0 ?0 v0 F4 构造模型
    8 M" D4 c: P6 ]4 ?4.1 预训练模型4 t9 z6 |; i; c0 g  T
    4.2 修改成FCN
    # C+ B) `( q2 ~, B. N1 \" |4.3 初始化转置卷积层# N5 }$ W! J3 M7 W. O
    5 训练模型& d3 K/ Q' q! E
    6 测试模型& m9 X+ q8 n0 F# }6 s" F( c
    6.1 通用型
    , p  L7 v1 [/ \; A* H4 U3 x# }6.2 不通用
      @1 p. |7 h" C! W" F" J( Q7 结语
    . x+ T3 U" r# M! ]8 T1 图像分割和实例分割8 C# P% i& h9 y6 ]0 u0 [

    5 i8 I) D+ X9 Y" m( X6 l* |计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    , I. a5 e5 x8 s% A: A+ c3 K4 a9 c1 h5 j0 E: N
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    + n! s, H9 D1 p" U( ?) R) B* P实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。: ~, D% x4 I- `  O. P3 c" F

    ( R0 s$ O# D' G' F& x2 Pascal VOC2012语义分割数据集2 U% i/ N: a* m
    # z( r9 t+ }8 A8 p3 @, H1 U
    2.1 导入模块
    # }+ ]/ k! K7 z* r! timport time
    + |8 [) Z, J4 U/ a# ^: ~! w$ bimport copy8 \7 ]: Q; Q! w8 a/ p9 T
    import torch
    : A$ ?( m% M/ k. M6 i9 z3 D6 g# N- d* Ffrom torch import optim, nn
    0 O# B' P/ {* y( J0 Dimport torch.nn.functional as F
    5 l7 L' G1 h( L  \7 w* l/ Bimport torchvision2 p  t$ n8 u  L
    from torchvision import transforms
    6 p" Z9 T7 [( ]8 Z1 W. Lfrom torchvision.models import resnet18
    7 I0 D* l, Q. c/ q* V( A. O/ Dimport numpy as np
    * }% @7 y4 A# E; tfrom matplotlib import pyplot as plt
    % J3 W2 O; @( J1 N6 `" D/ tfrom PIL import Image# w+ f. k- f" c- d5 ], q% z% f3 y1 F
    import sys
    % O! i0 F. ~8 D, g1 F& c* Vsys.path.append("..")/ Y6 n! q/ p4 @' P+ v$ U- m  F7 i
    from IPython import display; z  X5 ?( O1 J0 S6 Z! Z
    from tqdm import tqdm
    " v( D2 }" l/ [3 `, f) ?import warnings
    : e7 g7 M( s! v. ]: o9 N' _. h" d' Twarnings.filterwarnings("ignore")
    : ~6 j% O) K# i: A$ m( }3 N- }
    ' c7 `; F/ l" a; S( s0 C! e2.2 下载数据集
    + ]. n( d- D8 @) ~( x5 x
    4 ^' H* V  R5 W2 i6 N语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    - V  n& [. D5 d8 _: _$ J1 d% l! R% U5 n( [8 ~' R  J3 N

    / g/ S( |# C4 A$ P% M7 ~( rImageSets/Segmentation路径包含了指定训练和测试样本的文本文件; s* \/ B6 q. M& H. r; [! ~
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。
    ; [  t4 t. z  @6 T2.3 可视化数据6 j% z$ a  e3 p; I1 J  J2 y

    % z/ h* V) {4 A定义read_voc_images函数将输入图像和标签读进内存。
    3 B- \- ]2 L/ H$ c
    7 i8 x" i( d" C: e4 Gdef read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    # [" i7 Q/ N1 z  m6 f& M9 l4 k( }5 ^    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')* C2 t; C* }; q
        with open(txt_fname, 'r') as f:& [9 O- |8 j+ Q4 k
            images = f.read().split() # 拆分成一个个名字组成list& X5 U  y$ a6 j
        if max_num is not None:/ Z" _1 f8 f7 |$ F5 ]9 Z
            images = images[:min(max_num, len(images))]0 V; e4 X- T  ?; Q9 S/ o- p$ g# U% M: d
        features, labels = [None] * len(images), [None] * len(images)0 s* L1 b! U$ o3 W/ p% k' x# {
        for i, fname in tqdm(enumerate(images)):8 k* A# v; H2 w3 Z
            # 读入数据并且转为RGB的 PIL image
    ; Q& Y( z. h) T$ l& S" s. m        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    & p* X: l2 i, s+ _4 {        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")" O2 s" @- D4 v$ H- h) @* T
        return features, labels # PIL image 0-255& T" C+ {4 o3 }  S/ k; ~

    9 q7 q+ f! X' u  Y定义可视化数据集的函数show_images% \/ x* v- n/ G3 i

    % y7 C3 e3 M$ K9 @# 这个函数可以不需要
    + O4 c# @8 Q  ]. b7 o, w$ idef set_figsize(figsize=(3.5, 2.5)):7 a# e3 @" a9 T7 c
        """在jupyter使用svg显示"""2 K0 L# g4 J3 n  O( s4 ?0 j2 }3 b
        display.set_matplotlib_formats('svg')& w# ?% c! {/ e/ [* ^- s# h
        # 设置图的尺寸" J9 u: H+ p0 c! r" P( `& O5 ?4 T8 @
        plt.rcParams['figure.figsize'] = figsize6 d9 @0 m) O( a  z9 ?2 w

    ( @8 ~0 O; \/ Wdef show_images(imgs, num_rows, num_cols, scale=2):) Z9 l1 J: u! ^' U) k
        # a_img = np.asarray(imgs)# `. J6 a& `7 ^( u+ y. \
        figsize = (num_cols * scale, num_rows * scale)
    . }5 i# x" }0 f" [/ y    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    & O$ A; h8 v: W# }/ i% r2 b    for i in range(num_rows):, ^" f8 \3 o# O; s4 o* K3 U/ R
            for j in range(num_cols):
    ' F3 K3 ]- W5 c0 l1 l3 u            axes[j].imshow(imgs[i * num_cols + j]), t) i+ M$ W0 l4 {5 w/ ^
                axes[j].axes.get_xaxis().set_visible(False)$ Z! l" S( M. c" t' y, e5 r
                axes[j].axes.get_yaxis().set_visible(False)
    1 O8 h9 u8 I" Q    plt.show()- F' L( G' f" R3 v7 h
        return axes
    2 z, V" T/ G; m' c
    . K0 S8 k/ C0 L% A( c, M0 W定义可视化数据集的函数show_images
    9 O9 [4 }1 S" B) B: d/ d( E4 h  g% C  ]$ Z& F* l7 r
    # 这个函数可以不需要
    , K$ o+ K% Y% |7 Ydef set_figsize(figsize=(3.5, 2.5)):" E6 i7 s' T# I4 R
        """在jupyter使用svg显示"""
    ( z% f  E/ P7 ^- ?7 _, q    display.set_matplotlib_formats('svg')6 o+ |1 r: w/ j  C) h
        # 设置图的尺寸
    + j# n9 c" Y: q( g; r    plt.rcParams['figure.figsize'] = figsize# c( T5 S. i  n3 K* ]; s
      L' k0 l6 g4 N  x
    def show_images(imgs, num_rows, num_cols, scale=2):$ O$ T* t6 Z* x* ]! C
        # a_img = np.asarray(imgs)
    5 ^5 R: n* e2 c8 M* g    figsize = (num_cols * scale, num_rows * scale)+ i1 u# S2 G$ {! J$ i; Y: B# v) Y
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    2 H2 T/ K9 Q* N) e9 q. l" M    for i in range(num_rows):5 t2 ^$ }) v' C8 Y; q
            for j in range(num_cols):
    $ O2 V. r; j. F            axes[j].imshow(imgs[i * num_cols + j])9 i9 b2 V" \+ L) J3 N3 H
                axes[j].axes.get_xaxis().set_visible(False)8 ~/ J0 x, W" _9 Z7 a5 ^0 q4 C
                axes[j].axes.get_yaxis().set_visible(False)+ k& U' R, E: r% G/ d# \( s5 C
        plt.show()8 ~4 @' t' O( _' C3 t$ i
        return axes
    ' y. ]; r8 e# G& E* i) N7 L- W画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。
    6 j4 @0 U3 i! ~$ N: n" E% p$ X& J
    # 根据自己存放数据集的路径修改voc_dir/ O2 @+ ~; F3 S( T
    voc_dir = r"[local]\VOCdevkit\VOC2012". I$ H5 g5 M# U+ H, F, d3 R
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    . ]6 w6 }8 j8 ~# S9 M3 X8 hn = 5 # 展示几张图像
    / E$ L# h6 P4 V8 j7 `" m+ Zimgs = train_features[0:n] + train_labels[0:n] # PIL image
    ! d  U0 O% b6 q# L0 p; Z7 Yshow_images(imgs, 2, n)
    , \% @( y& v1 i9 @- l/ ^
    " q+ C6 }0 J# b3 ?* F/ ?; B 1.png
    3 a* m$ N$ ]; |4 X
    1 W0 x: O7 y7 G( W1 Q! @, t) g列出标签中每个RGB颜色的值及其标注的类别。
    , y. N& b, f! z4 v# 标签中每个RGB颜色的值. a4 H1 w* O( Y5 K5 \
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],3 N/ Q3 b; e- G+ e- P% T, P
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    ! u" G. q  `" W                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],4 N4 E* D1 J; g% a% Y
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    5 W" A. K2 U4 K                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],6 [) |6 z% c+ y: h) q& S- B: C
                    [0, 64, 128]]& ~- y1 |0 W. P) C# q9 ^
    # 标签其标注的类别
    / M- y' r1 ]; Y$ pVOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    - O6 q/ i8 u0 }               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    + A" z' ?3 w0 g! u- g               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    $ d7 d& x' n; f  D4 Q               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']3 L# K1 X& n! S
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    3 A+ B9 R8 k/ A$ }, k# m7 R  q5 ]4 Z2 o7 Q; X7 C
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。+ `; ?  f; \+ O: E7 W& \
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216]); O9 U/ ], Q3 a) k& |
    for i, colormap in enumerate(VOC_COLORMAP):
    4 ?: ?% j- \# U6 O    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i# N$ G8 U' y5 I; J& f. {
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i1 G' X! |3 ?7 B0 N1 D

    $ N6 [& V. D: C* v8 C7 @: y# 构造标签矩阵6 z# a6 u& I5 s
    def voc_label_indices(colormap, colormap2label):* X9 n- T( ~3 ?3 ]# q
        colormap = np.array(colormap.convert("RGB")).astype('int32')
      i1 k# r8 I: g0 I1 A    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    3 v8 J+ Z' Z- \) D# S% a- ~( d    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标, T9 _/ o/ g1 G& u- t! A

    " `( }. R' Q& h8 V- Q7 K0 n可以打印一下结果
    ( S- f# S9 U6 Z
    3 r5 }9 Z5 c8 a& p% @2 D* X/ M7 z% fy = voc_label_indices(train_labels[0], colormap2label)
    7 H. |2 D  e$ i' i! {! pprint(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES; }0 I2 S9 @0 W

    , @2 E5 S! p. E1 N% S, W2.4 预处理数据& Z9 u7 w: G! S$ B$ V

    ' H: X8 m( e8 }& a* ?/ t& S在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
      U3 {9 t5 E5 ?: x' H1 e: o" l
    1 R7 X+ p  s2 {; R# D0 F
    8 f' o' I- E/ J: ^7 o4 V0 Ldef voc_rand_crop(feature, label, height, width):
    4 e7 M: A+ ~3 n2 ]( \9 B    """" R# B' Q) Y3 u* {" R* ?% G- D# m
        随机裁剪feature(PIL image) 和 label(PIL image).2 }: R6 _/ I  G$ {* R* U5 I
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做5 l( A) l- e) ?/ \/ Y
        Get parameters for ``crop`` for a random crop.
    . J. Q+ O" h$ X# e' Y" E6 ]9 x& j6 ^    Args:3 c4 \) s8 z9 k& r2 m& b
            img (PIL Image): Image to be cropped.( J  H/ x( _% g
            output_size (tuple): Expected output size of the crop.
    7 Z2 Y% K' L( {' ]    Returns:
    ) P4 d* G6 o$ ?7 p  }2 m0 l2 J        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.+ t+ l0 @  Z6 P" b
        """! b# {+ o8 P4 M: j  {
        i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width)): E4 @. v+ R1 w& J8 ~
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)' V! o2 {6 N. `  x( \
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    1 a: y3 a) `7 o& j+ b+ k    return feature, label
    4 t$ U( G8 H; u) Z9 ]
    2 G5 l8 I8 Y+ O- |; @" x# 显示n张随机裁剪的图像和标签,前面的n是5  {+ d2 f( c: @% q& C3 N
    imgs = []
    7 S- d- c/ g& E& ?for _ in range(n):
    & V* \! n4 O  d( B% s6 _- K    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    6 ~- L8 W9 }" r* qshow_images(imgs[::2] + imgs[1::2], 2, n);
      l' H% |$ L+ F
    8 m% r2 u# I7 V9 h5 V* d$ r
    " F. L; m( `: b/ H" K/ s5 u 2.png 8 F& I8 E$ v+ \, ?/ G

    ' o6 G5 D- r3 \9 D
    ; c5 Y5 N4 |1 u
    ; F% |. k2 C  p) E9 K3 G3 自定义数据集类
    1 q  u0 h  }( J% u) ~5 v2 g+ z3 y
    1 N9 |- C7 G/ O5 [; R' u+ y! {) B3.1 数据集类- H  W+ Z' `" v2 q# q& y

    ' n5 `* f2 @* Wtorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    9 G9 W2 D& F7 C% A2 E
    8 e; u2 A, m/ r/ m. D$ f__len__ 实现 len(dataset) 返还数据集的尺寸。$ `: G: H; ?- q5 r
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    6 q" O1 h& ^, |( N0 N由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。. c+ }! b: v; g! ^9 k" A

    0 k# U% C$ G* r) y+ J' Oclass VOCSegDataset(torch.utils.data.Dataset):7 A3 T7 v8 n, ]( g3 Z7 f
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    8 _$ ^5 R0 ?4 o% S% t- m3 M        """) l! C" A+ k9 s! M/ E7 T
            crop_size: (h, w)
    . }  l7 r; A9 P3 `% Z  Q# J        """
    9 P$ V/ M. F2 X# |0 u* H, K        # 对输入图像的RGB三个通道的值分别做标准化8 C- ~/ [% \( a' t
            self.rgb_mean = np.array([0.485, 0.456, 0.406])8 k: i' u0 y- I* G. U9 U' ^
            self.rgb_std = np.array([0.229, 0.224, 0.225])
    % x6 V0 @5 A8 N! A+ j. m8 {        self.tsf = torchvision.transforms.Compose([
    $ X* N0 Z1 E3 [9 F* x& s* L            torchvision.transforms.ToTensor(),
    ' v4 t1 b- W9 O. i% m7 E) G            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])2 s3 y4 x) ?0 C: S( m
            self.crop_size = crop_size # (h, w)9 J: X/ v* U% d
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)  g+ |, O3 y2 _3 W: O$ `% q
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    5 H" J; T3 w' e2 V# V        self.features = self.filter(features) # PIL image
    + _$ Y/ W6 f0 B% Y4 _' Z1 U        self.labels = self.filter(labels)     # PIL image
    ' b% D" N- Z' \8 T( A9 _2 z        self.colormap2label = colormap2label# X- @9 `7 \3 u
            print('read ' + str(len(self.features)) + ' valid examples')
    ( t( p" v8 e% `: d+ h
    5 A5 D7 f  A6 p. b$ r& }5 h; C5 u    def filter(self, imgs):, G# _1 ^' n3 s6 y" @
            return [img for img in imgs if (  }+ t+ c- c- Y- ^) b
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]" N& l$ t( o. M5 ]& B
    5 j& X9 b' h+ i- ]. X" y1 C5 C
        def __getitem__(self, idx):" ?9 J# O2 h! h2 W4 P, ^: C
            feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    ( e# O4 y+ L- r, V* Y9 D                                # float32 tensor           uint8 tensor (b,h,w)
    : Z+ k5 ?$ i! w- @$ }        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))+ L: U# m0 @" K, D4 K; N! `4 A

    " E# E: J5 ^, X" D% U$ a    def __len__(self):
    / A/ y# i7 B& c, J) M        return len(self.features)7 Y& c2 [; v5 f& r) H( [$ I
    3.2 读取数据集
    ! |' d5 a* B5 Q% @7 @- C7 Q: Q& D/ J5 Q2 x9 F8 N; X
    通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    3 G7 f) R" h! k0 [$ _; A' t; u3 S; o7 _' t0 L( \
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
      t# a4 n$ P9 G  a1 K  Kcrop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)2 r% h5 J) c' `4 `
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    & b8 B# A% N6 _- T! H6 m6 q* p" X2 P- P6 ~5 P# Q$ d: o
    # 创建训练集和测试集的实例
    4 k$ v; a0 l5 w2 F, Ivoc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    , d0 k0 w, e1 ^- H& Y6 w- svoc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)' s  V) }: ]7 q2 M
    ' x  g0 s3 _& ?- [9 I4 I( |- S" l+ ?
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
    4 c- |- [8 e9 _( Xnum_workers = 0 if sys.platform.startswith('win32') else 4
    : @2 A! Q$ l. s( D$ Q' ]train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,& p3 _5 L( T2 W
                                  drop_last=True, num_workers=num_workers)4 H8 ~7 O: O- @; }
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    ! y' C  o/ V& k                             num_workers=num_workers)1 k( B1 Y9 A7 Y* x

    + }: g* Z% v4 s) [% a% r1 ?# 方便封装,把训练集和验证集保存在dict里1 `3 F- C1 K1 H  Z1 {4 y
    dataloaders = {'train':train_iter, 'val':test_iter}
      ^: e" `7 U) `( H7 |* k$ ^! Fdataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    ' \$ G% `9 o/ p8 w7 q6 b# x+ `% Q" M% L/ l8 w: X# Y5 |
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    1 G6 d& @. A/ |& I9 F+ A1 O7 c1 {. e: Z! x) }8 j2 h' d
    num_classes = 21 # 21分类,1个背景,20个物体, J, P) R7 p9 U2 q2 S4 ~5 n
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
      Y4 ~' Q/ s4 ~* T4 O0 w' {
    - y" o9 X7 T4 Z; P+ f# 特征提取器) y7 `) p' N$ D# v& @+ `
    for param in model_ft.parameters():% ?) Z" K/ k0 X4 m, F1 R- Y
        param.requires_grad = False6 @/ ]8 A2 D2 q/ Z% ]0 E2 ~
    4.2 修改成FCN$ ^9 a% I- Q2 {- C# z* |* b: B+ E  R

    / {( u3 R: R- I$ T/ ]& F全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:: C" D/ f8 x: M/ Z" P4 Z# M$ A; C
    最终输出的通道包含了该空间位置像素的类别预测。; a  L& h$ B& y: x

    . r7 k# }. d' w( N- q对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。' _" x4 s+ H7 ]: C8 p) Q* p

    3 g& {( _" ?' }可以先打印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 倍即可。( }; ?9 ^6 d" n2 J, W6 o
    4 D$ a4 u7 o( d3 q* L% f' i. ~' t0 H+ ]  b
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    . h8 A3 E9 S$ ~+ p5 h, n; r0 Z              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class4 k. B  r& k$ p7 S: |
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    " \; x7 ^7 E) o! u' g7 @. k# i6 x6 Z' m, W  V! \5 r
    # 对model_ft做一个测试
    ) g7 y% r$ U# D# b2 p% H' dx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    9 k- Z9 l* z$ N: x# Jprint(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    + Z+ m' o1 A) A$ t- V8 w. k& P' a/ o- ?! A7 Q
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组- r( S* A6 d4 K  [1 x, a
    # for X, Y in train_iter:# w# S* y2 v8 [7 C
    #     print(X.dtype, X.shape)
    " g! d2 u  I& C# e5 D#     print(Y.dtype, Y.shape)6 p8 I2 }6 Q3 Z  u& D9 n
    #     break
    6 k0 B* Y* ?( q: I- R: b4 G) t+ c3 u' Y% M/ G

    0 ]0 i3 M# {: M4.3 初始化转置卷积层
    & b8 X( W9 H2 _8 g2 ]1 s" e) X/ a3 x+ P1 G- {
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像4 _2 P3 }* n" @# P3 j
    在坐标 (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函数构造的卷积核的转置卷积层来实现。! s2 b) y* `7 L9 T7 s8 S7 Y
    7 b) k! H9 y0 E7 @/ H

      V9 l1 l( W2 I( ^* J# 双线性插值的上采样,用来初始化转置卷积层的卷积核
    1 t/ A9 S- U5 k+ `% ydef bilinear_kernel(in_channels, out_channels, kernel_size):
    8 g& l# p9 @/ ]    factor = (kernel_size+1)//2
    / e9 g2 W1 m$ ]: Q  f    if kernel_size%2 == 1:
    4 L- E1 a3 L- W6 {. J% `/ b9 j        center = factor-1
    ; T1 h9 q2 f9 D, }% O: ]    else:
    $ V' Q% N7 A% l        center = factor-0.56 x! K* T2 ?8 e: ]
        og = np.ogrid[:kernel_size, :kernel_size]
    " v1 \" Q+ e* f& ^    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    . t: s2 ^! n% [* W    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')% Z* s$ A1 L" O2 f7 w) U
        weight[range(in_channels), range(out_channels), :, :] = filt
    3 h9 \: J2 L$ C5 ^+ h    weight = torch.Tensor(weight)" z4 Y+ ^  s& H/ u
        weight.requires_grad = True
    . q+ t. t& ?" a% h- N    return weight
    2 t5 [8 ~% d. N( Q$ v8 |$ c. E. p( a5 Z1 ]( v' l

    # C$ d/ g  q) u) r9 z! L2 {, W在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。5 N! Z7 h8 L" F8 W: \
    0 ?, U: {1 J6 q8 I2 M9 e
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    2 E" ^  W) p- ~+ x: o7 Jmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)8 b, W5 m) j4 l- f' ^3 a
    1 F) [# R  B! |* b2 d3 Y4 Y! w

    9 b5 V) u2 n! ]2 q# d6 o5 t- |+ N& {1 ~# Z" e( f( K
    5 训练模型

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


    / ~2 U9 k' h$ qdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    $ g! F  O8 P8 w* x7 |7 i& O    since = time.time(); Q) Z  L) v' v" \
        best_model_wts = copy.deepcopy(model.state_dict())
    ( L% z" j- M* [: Y! l/ b' B5 ?( P    best_acc = 0.0  p" a2 J; a! i  w- ?4 I% {
        # 每个epoch都有一个训练和验证阶段
    - Z9 `6 `; L# f% ?) |    for epoch in range(num_epochs):* S& R5 o7 I5 `) `6 N. z8 b# [
            print('Epoch {}/{}'.format(epoch, num_epochs-1))4 O$ M8 q' V* Y/ I
            print('-'*10); D5 l3 y1 }- r, I& Q3 g
            for phase in ['train', 'val']:
    1 g' }1 \0 e( G5 Z7 }( M$ @            if phase == 'train':
    " g# \" g- `/ l5 k2 J$ B5 v* i( Z" d                scheduler.step()+ Y" Y# B) i$ n$ Y2 W
                    model.train()6 ?( t3 y4 H+ S
                else:
    * d. H1 z7 m7 ~$ Z                model.eval(). z/ }$ H( D! T) L
                runing_loss = 0.0
    0 H6 M9 V4 ~+ c7 f! ?/ d            runing_corrects = 0.0) ~: o5 |6 g% u: B8 b
                # 迭代一个epoch/ u' h) I5 I9 F0 W. \+ R9 M
                for inputs, labels in dataloaders[phase]:3 K8 c: e) b$ w. A
                    inputs, labels = inputs.to(device), labels.to(device)( A1 ~2 g% M9 V# s- {
                    optimizer.zero_grad() # 零参数梯度
    * X' u5 A- y# ?8 C                                # 前向,只在训练时跟踪参数9 _4 g& a  t, [$ D, T
                    with torch.set_grad_enabled(phase=='train'):
    ! ~8 U3 t" C0 Z, K. |& U                    logits = model(inputs)  # [5, 21, 320, 480]0 s0 N5 c9 x6 D
                        loss = criteon(logits, labels.long())" @% o6 K7 Y8 E) D" V
                        # 后向,只在训练阶段进行优化# A6 h$ ^) V* K( a) K* Y5 v/ C4 f
                        if phase=='train':
    " ~$ i% X/ Z  s7 F; {) s                        loss.backward()
    $ H  K$ k( }# {6 u" x: H/ r' D5 L                        optimizer.step()
    # H7 @% j# s4 M  E2 u$ _; R                                # 统计loss和correct$ W: G; M) T# O1 I
                    runing_loss += loss.item()*inputs.size(0)
    8 Q6 y) G  y( U  X& m& }                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)- v9 k8 k+ E* ~4 m) G! h

    $ L1 V4 H* w# ~% y2 B( N, U' [* n            epoch_loss = runing_loss / dataset_sizes[phase]0 ^' D+ u+ @6 R; W$ R
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    4 h& D) B5 m6 W* z% ]) f6 o" D" \7 p            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))+ T$ [5 \0 `  m8 k- f
                            # 深度复制model参数
    / v# q. r6 O/ T5 \            if phase=='val' and epoch_acc>best_acc:# J/ E* D0 y. k' C$ c
                    best_acc = epoch_acc
    ! Z9 `8 |- h1 {0 m                best_model_wts = copy.deepcopy(model.state_dict())
      E1 x' P) N1 Q. J/ r        print()
    ! p4 d& d% f: u    time_elapsed = time.time() - since;% f1 @% t6 t- W+ D
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    ( G  Q9 Q* e: P' X3 Y    # 加载最佳模型权重
    1 a7 Q- W! k% ]0 A4 ^/ ^- y- h    model.load_state_dict(best_model_wts)
    9 D2 x$ Z* R! E( E# `4 Q$ p    return model
    2 x$ }$ x+ O) A% Y7 C, K
    2 V1 F3 S+ {1 y, V( B下面定义train_model要用到的参数,开始训练+ s$ z+ {1 ~! p% n; X  `

    * y6 V  [" a9 jepochs = 5 # 训练5个epoch6 I. t0 c( J4 X% u; y* r
    criteon = nn.CrossEntropyLoss()$ _1 P* ^, `0 E, e
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)
    5 k% z8 F# `+ t. y) @. W: w# 每3个epochs衰减LR通过设置gamma=0.1
    6 [) k. t9 E. X  u* i) Z4 fexp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)" A4 H, p$ [3 ~

    9 A6 Z- X3 m2 u5 q+ p4 ~# 开始训练& h# A3 o- w, y- i" s) X
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    $ D$ s& `- [. f$ f% H8 J& x) O5 s' b3 J: t3 l
    6 测试模型

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

    def label2image(pred):3 m% u; e3 W$ Z3 L! t6 e7 [; i
        # pred: [320,480]
    8 x, l2 o& a/ @/ t9 Y    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    8 x# h; b1 r9 w6 P) M" X    x = pred.long()
    . @7 c% n  w* e" ]9 x2 d9 O+ V    return (colormap[x,:]).data.cpu().numpy()
    . I/ I( [2 B: c* D  B2 W8 a+ E( L8 ^; e0 a" x4 Z  ]

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    2 \( L6 v& n$ U  Fstd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    9 _5 w8 o" y, v1 tdef visualize_model(model:nn.Module, num_images=4):
    ' ^: m! |( F+ Z" L/ o1 @    was_training = model.training6 ^# S3 x; m$ s: Q8 W3 A7 ?
        model.eval()
    ' L3 w6 l8 [/ K$ d+ E    images_so_far = 0
    6 V; D/ y3 x0 }; X# J* m, i    n, imgs = num_images, []# [5 Q& P# O6 q5 z
        with torch.no_grad():, q/ M9 F) V4 Y6 r
            for i, (inputs, labels) in enumerate(dataloaders['val']):$ I* [+ p6 M" Q5 H2 L, M
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
    ) t( z$ ]! e  K- h            outputs = model(inputs)
    + P8 i& l3 c! t. w2 S/ V; A            pred = torch.argmax(outputs, dim=1) # [b,320,480]
    1 o1 K2 _5 c/ H5 q            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦# E3 @' H* n2 x* ?, b" }3 e, s. j
    # H% C+ j0 H7 l6 N: k8 O
                for j in range(num_images):2 E! A! q6 _& d3 Y1 ?0 u- I3 J* C
                    images_so_far += 1% m# x$ G1 E8 J
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)! L3 b' }4 s  a
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]! u' N* H5 E1 E$ V" S
                    if images_so_far == num_images:
    ; Q5 U4 |! `3 B) t. b% e: l9 ?7 h                    model.train(mode=was_training)
    6 m# G/ ^" a6 P                    # 我已经固定了每次只显示4张图了,大家可以自己修改$ c: D1 {2 `, v  T& v
                        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    6 _  G4 }( p3 c                    return model.train(mode=was_training); h" M$ y" r5 p2 k+ }+ p
    ! E: w0 }- ~, U% v, _8 \$ H
    # 开始验证
    6 g' T( i; z$ v' z/ Dvisualize_model(model_ft)& s4 ^) u. A$ r
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor( B4 U9 C5 K  R9 }9 C* m
    def predict(img, model):9 Z: z( h7 }3 T0 L7 z$ F
        tsf = transforms.Compose([
    + J- |5 u+ {# F: z: P( q            transforms.ToTensor(), # 好像会自动转换channel
    $ V. \* x3 k3 s: k6 G            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])" Y- |+ L8 Z1 j* t
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    & g, P: S  J) a; D    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    " W: T# r1 `; [8 X. Q    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)2 w( E& P8 }4 z- p1 X' m

    + X5 u( l! \* V' s% p) \9 `% wdef evaluate(model:nn.Module):7 o3 G  ]) \' v# ^) Y* ^3 ^  d
        model.eval()
    0 A# P" |& s) O  I6 v4 j    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    " g% d4 q! c! z$ e- p    n, imgs = 4, []. p4 q1 b- g; ~0 P9 m& v
        for i in range(n):
    1 \% V3 {- z% p8 l8 ~        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    ' u+ O! y+ q6 r' k        pred = label2image(predict(xi, model))" o6 o5 p6 }% N8 X$ `2 K6 D
            imgs += [xi, pred, yi], B: j8 c5 _% R( w
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    # G% ^$ @: p/ \+ o0 \4 m2 O. z; x( |
    # 开始测试
    , }& y0 V: t5 x% B: S, `8 a3 @evaluate(model_ft)
    # C* a, }2 K. F% S% F$ s- X1 [
    ! P5 ~" f. C: p+ [7 n/ [% h3 W7 结语

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


    $ y& c6 h% P! \$ I9 T- CEpoch 0/2/ {, k2 O# z  P5 B
    ----------
    * f. N- d8 Y3 T5 btrain Loss: 1.7844 Acc: 0.58352 m2 D% b0 t" X7 Z
    val Loss: 1.1669 Acc: 0.6456
    " n& B1 g5 \8 |4 G
    , E) |! e% R+ v3 C) D0 s' Q* \0 JEpoch 1/2/ j6 j/ L2 }! O9 J
    ----------8 Z% T& k) W! N7 m: o
    train Loss: 1.1288 Acc: 0.6535) G7 D% ~, |+ z: U/ z
    val Loss: 0.9012 Acc: 0.69297 N3 y$ d  Q& z8 b

    $ ?' x, y$ B0 ^: r) U( ]' aEpoch 2/21 I  z/ J1 K* t/ ~& K+ C
    ----------
    . e& f) \% v  rtrain Loss: 0.9578 Acc: 0.6706* e- e3 {( X# @
    val Loss: 0.8088 Acc: 0.69486 K( W! j) g) k/ J- T
    " b0 E" [; o- E  @! o
    Training complete in 6m 37s# i* T2 g! R, m9 y5 O- F
    - T2 ?& o1 p: N/ b6 g+ h

    3 ^6 |" J( J# B1 t+ i 2.jpg 4 k- C3 `* a+ x; y

    ! o) b0 a0 z" {7 U当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    / H1 {* M& v: X, h& d3 D( J( S$ B% M4 t+ i; |5 g
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。; E: O# l1 B. j0 q
    $ Q4 b! V  W; [  B: ]
    & k0 y" I+ C$ n: |4 n) A% r9 O
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:0 Q. j/ \+ _2 P8 r: D0 I/ L

    7 ]4 k3 ]* e: |0 ^! TDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]' C" m$ a% w5 p' g/ g# ]. g1 y
    GCN 通过全局卷积网络改进语义分割[论文]
    ) V2 A/ Y* ~" fUperNet 统一感知解析
    6 ^6 E* F6 u8 Q+ L! UENet 用于实时语义分割的深度神经网络体系结构[论文]$ d& b2 S3 s; E3 d
    U-Net 用于生物医学图像分割的卷积网络
    3 q6 n: t9 f+ e. h, OSegNet 用于图像分段的深度卷积编码器-解码器架构。
    5 r5 {6 g( w" ~  |" Q还有(DUC,HDC)、PSPNet等。
    3 B% o  }( ^. t: T/ ]
    ! Q+ M" E1 }: k" O! p1 ^常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。! R  g& ]) y2 ], S; m

    5 x) S+ z0 \. g7 p% s3 z7 _9 \' q# {4 `对于损失函数,除了交叉熵误差,也可以用这些:! D$ ~% e5 j  i% O
    * [% r5 ?/ B. Z% @5 g/ o6 Y7 L
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    4 n1 |6 J$ _: _CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。6 F; t) E8 I' D% T+ }
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    - T. z& O0 N& x1 ^1 L  vLovasz Softmax 查看论文:Lovasz - softmax损失。
    : t" c- h1 q$ ~/ x2 N; B' C3 {9 `3 a8 l3 P# D3 Y( U* S) a

    2 i, w. p; X$ @4 T/ W  C. M# ]" _2 Y

    ! |6 R3 p7 c8 a' P: _1 P% _3 X$ i7 B+ d: G# {( E' j& n: O, h3 m* Y7 ]
    ————————————————
    0 H  H  e! x, L$ j7 y版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。5 M' L" i, u3 K. O4 `0 n
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507) o3 J7 @) o1 z; g* B% x0 @
    : D1 f% _1 |$ g( q+ V: j
    , S# p) h9 U3 s4 h

    5 d) m' q- a! ?4 }3 g/ j7 K
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-22 14:07 , Processed in 0.431622 second(s), 54 queries .

    回顶部