QQ登录

只需要一步,快速开始

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

    ; Y7 X5 g' a# p- t; i: B$ y1 B0 r) l
    Pytorch实战语义分割(VOC2012)
    ! ?1 C8 o' m' `9 x本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    ( [& a/ U( {$ [, f语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    * m9 E% w) y5 H  F: h9 r+ ~
    0 t' S; l' A9 C5 j6 K# B2 C# [' S6 U1 z
    语义分割中图像有关狗、猫和背景的标签* ^$ c) [6 {; U* H2 A# ?9 j( G% z
    文章目录
    ! `+ J# h  [% P) u9 u, \5 n2 D; ~
    6 y4 p% V$ F) _; J1 O* [3 ^7 Y1 图像分割和实例分割
    / y( y- ]) z) F/ e2 Pascal VOC2012语义分割数据集# y5 Z( W9 m3 T+ [4 N
    2.1 导入模块
    - n5 \. q, [  [5 T) }2.2 下载数据集/ B: F5 Q5 {+ n' `# r
    2.3 可视化数据6 x! N5 V3 s; c2 K8 o7 [( [: @
    2.4 预处理数据. N. k4 |2 `" w- C7 T
    3 自定义数据集类
    0 t4 ?( v$ L( ^3.1 数据集类
    # f5 i) x. N( ]! U2 m3.2 读取数据集4 `' |" f! q5 n4 t; J
    4 构造模型
    / v5 g9 }. a9 ]5 a  E# J+ E% G4.1 预训练模型
    * f8 }: r: a2 {0 `/ B4.2 修改成FCN
    ( W/ E. F* Z9 r4 k+ i% H/ k4.3 初始化转置卷积层
    9 }# T& _% m/ ]3 J' r8 c/ F5 训练模型
    1 c. g6 V/ t; k/ ~$ i6 测试模型
    # v0 ]& ~/ g, o) m4 f5 t5 P6.1 通用型
    / w( c3 f; A2 {9 O" |: Q# c6.2 不通用5 R; E. n- d: y9 ?
    7 结语' K) T3 z$ K1 K0 J
    1 图像分割和实例分割
    6 ?7 P+ Q4 b* G1 d) z% x( x7 x/ g( P- E4 w4 Y' V8 P4 f2 W3 [
    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    3 l1 o' }9 D% G- X
    0 ]# U% K, [: k图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。/ i4 p2 r% ?+ T1 D1 a# |
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。
      a- |0 @( n3 B2 O  \1 c" u( g! I- {. t1 H% L7 B% _
    2 Pascal VOC2012语义分割数据集% k% a: g' y8 t' k' C. j  S
    + @( i8 r4 g; b- {! h+ C
    2.1 导入模块' S1 T. v: W1 Q
    import time8 `  z3 f1 i5 l/ g
    import copy, V2 ?" f8 Z  w* I0 s) w, v
    import torch: }& w% ~) J/ ?9 i5 Z, C: h4 J
    from torch import optim, nn" a: l; S& J! [4 |; {4 v/ p
    import torch.nn.functional as F8 y3 D0 i3 u+ b: y( O( `, N
    import torchvision! Y8 O7 A4 c4 ~4 \4 ?) f
    from torchvision import transforms. O7 Z% L3 h1 {8 ^! g" t) K
    from torchvision.models import resnet18* m: O9 t' b% \- t  m
    import numpy as np( i, ]# V& I7 Z% a2 ^
    from matplotlib import pyplot as plt
    & x! ^5 q6 S3 y* Zfrom PIL import Image2 T9 @) A) v- g2 d* u
    import sys# z$ }$ S8 a. c# A% ~* @
    sys.path.append("..")
      {! |- t0 ?. n, k0 ?from IPython import display
    % Z! ]4 s7 v  N1 W( z% }  ^% Ofrom tqdm import tqdm) t" }, ]. L. t; c, ^1 k
    import warnings9 o2 m7 `. w5 ^% L8 x% N+ j; j
    warnings.filterwarnings("ignore"); n( v# i8 U& B( q1 j( |
    # w+ `6 V2 T/ v
    2.2 下载数据集7 ?6 w% f5 n4 j& n% }8 Y# |5 Z  O. L1 g
    6 I' n( [- Y4 i& j
    语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    # g, D; j, i3 X
    % Y* {% @5 T4 }- t& V& ?2 U: {6 v
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件
    9 J3 s& K) w, a9 qJPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。; g" j! t  C1 c, f7 z
    2.3 可视化数据
    ) M6 ]* [, d, ^% D7 V
    3 ^  h( u' P* I3 F$ d& g# a定义read_voc_images函数将输入图像和标签读进内存。% \$ I4 f/ |2 ^% Z/ M+ G

    6 d3 w5 p% t0 Q; [. {def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):: ]& q( L2 ^7 {- e
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')7 G5 R7 |. T1 J) d! c) V* B& \2 U
        with open(txt_fname, 'r') as f:
    5 B" @$ l: o7 R6 D% j        images = f.read().split() # 拆分成一个个名字组成list) I2 B2 W% S! X- I8 k
        if max_num is not None:
    , U5 b- e' x6 `9 ?; C9 ?& P1 X4 \/ V        images = images[:min(max_num, len(images))]) j; w$ I1 y2 `4 q; {) T
        features, labels = [None] * len(images), [None] * len(images)
    * G6 @: v  M# O, S( [' j    for i, fname in tqdm(enumerate(images)):, `9 D- f" p8 O  o( x) n
            # 读入数据并且转为RGB的 PIL image7 B3 u+ t4 _/ @+ X
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")& l3 Y/ q+ k3 u9 {  n- }/ u7 E, m
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")# p9 B6 K, v+ B" h
        return features, labels # PIL image 0-255
    : ^5 D7 W' v5 {6 _4 I6 |- d4 z3 D0 T: }2 X4 \9 U* H
    定义可视化数据集的函数show_images
    - T$ q% d; C& v# ^* Q6 m% ~7 N/ y
    # 这个函数可以不需要  |% I" F6 M+ z" M; c  O
    def set_figsize(figsize=(3.5, 2.5)):
    ; e, q. ^8 G$ F1 v+ K8 b9 _8 t    """在jupyter使用svg显示"""0 H% l- S3 H! r. q6 l% T) Q
        display.set_matplotlib_formats('svg')8 X2 F) S5 v, m7 [
        # 设置图的尺寸8 j5 ?  K/ X, E! y8 d1 E( }
        plt.rcParams['figure.figsize'] = figsize
    + D/ r- d' h1 p0 v6 k) o% y1 j6 F) {1 J" b# |+ g& J" {1 N+ ^7 n
    def show_images(imgs, num_rows, num_cols, scale=2):
    ' j2 ?, J' {% ~    # a_img = np.asarray(imgs)
    7 g) `/ S  ^2 N. C    figsize = (num_cols * scale, num_rows * scale)
    : |2 ]3 O' a# [5 d( F; l2 y& w9 p    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    7 j0 x! [) }. b; o4 [    for i in range(num_rows):1 L  n0 |+ Y" d/ z$ P* K6 m
            for j in range(num_cols):$ N3 J  y  Y$ E5 v7 Z# U* w
                axes[j].imshow(imgs[i * num_cols + j])' I" U2 I- v6 j: t" Q3 c% j' u
                axes[j].axes.get_xaxis().set_visible(False)8 v* b% r- i7 \  d
                axes[j].axes.get_yaxis().set_visible(False)2 F& @# b$ v0 w4 k3 o/ e9 F- T
        plt.show()
    * o5 _6 Q4 n. a% R# c: j    return axes
    0 b5 h) ^. q6 q' i3 R
    , m6 n* D$ c: D; [# @. T" k定义可视化数据集的函数show_images9 p9 G0 S2 `$ ?& B% E( D1 W

    1 v# h% G4 j, a0 v# 这个函数可以不需要) [# |0 p: a( w+ w2 [9 H
    def set_figsize(figsize=(3.5, 2.5)):
    8 o$ K7 W1 B! G3 Q    """在jupyter使用svg显示"""
    ; \: M6 e4 e$ }' P5 m    display.set_matplotlib_formats('svg')+ {8 W- m( j$ L; H  m- S
        # 设置图的尺寸; S, k  g6 |+ i! p7 Z. @9 k2 D) b) `/ o
        plt.rcParams['figure.figsize'] = figsize+ R' ]$ u' k  V
    % D. p" b3 W! `% c+ M
    def show_images(imgs, num_rows, num_cols, scale=2):1 \* J* S0 q2 R% H4 u% _5 m
        # a_img = np.asarray(imgs)% t: b- [! ?8 ~4 ]5 C3 @  I0 ?% U
        figsize = (num_cols * scale, num_rows * scale)
    ! d# [: c' b0 I, u! S    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    9 E& F! ?. s, d1 Q( r/ [    for i in range(num_rows):8 k! N5 z9 b% r. W. X1 H
            for j in range(num_cols):9 H+ w6 }7 I8 K6 E) p
                axes[j].imshow(imgs[i * num_cols + j])' N3 y. J, ]* Q9 m9 S8 v
                axes[j].axes.get_xaxis().set_visible(False)
    * u2 s5 E) ]! e' p' J- O' {0 V6 t            axes[j].axes.get_yaxis().set_visible(False)" e  b1 C: n" f; A
        plt.show()
    6 D: f+ ~4 o. q/ H$ Y8 T    return axes
    / E) h# t; w( M) O4 o; q$ j  T画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。8 |9 Q" _# E+ F4 B
    . q' M- i; ]4 [
    # 根据自己存放数据集的路径修改voc_dir
    - ?/ d& F/ u, Ovoc_dir = r"[local]\VOCdevkit\VOC2012"# j7 ?: w! ^" x( O$ x! k. p
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)6 H$ p3 i. Y* z( D7 _
    n = 5 # 展示几张图像: |0 Y! c6 D1 S% b5 L* x5 G
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    . S" f$ b$ l2 {9 \show_images(imgs, 2, n)
    . Y: X2 ]7 w: B3 X' b- x8 |
    8 C* K/ Q1 O( \" n 1.png 3 \! D- w' |; s0 t

    3 x$ A* L" d! A' a' b列出标签中每个RGB颜色的值及其标注的类别。6 R5 q) u9 [8 h; O: o* m- q( @
    # 标签中每个RGB颜色的值
    . |) U* f' E+ FVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],2 z! m) w' W$ _- \3 Z, N
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128]," e! W( B( J( F* x
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    / X1 R2 |: U9 Q* v) t( p                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    ( V% f6 _# ]7 g+ Y, T/ W" t                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    ! m" A. o6 P1 z* w  x5 `+ A4 l4 C                [0, 64, 128]]
    1 ]/ S! A2 z8 [3 o5 v5 O8 }# 标签其标注的类别/ e( x9 q: S$ f6 Q6 w; w. v0 s+ J5 Y
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat'," l4 h! `( L+ A: x+ z+ H
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    + {0 H3 f$ o: Q/ S/ k- q               'diningtable', 'dog', 'horse', 'motorbike', 'person',: ]) q3 f, g' s2 [
                   'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    9 L; e- N: K+ K" l# j/ E7 O# g有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。4 p2 z5 s! ^. |+ |. ^+ y

    2 {2 g# V" q9 J0 ~" V有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    " P. Y" |1 k" L' e* a  bcolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    $ N, M: A- [0 l8 j: L& a7 vfor i, colormap in enumerate(VOC_COLORMAP):- M+ T8 U1 r: d: h7 y3 ]
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i
    4 |1 Y% D; ?5 L) {    colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    * X# q* u  S% }+ v7 G0 o# S$ w$ g8 |0 h5 V+ l. t/ Q
    # 构造标签矩阵6 e& ~9 t: P! d1 `, `
    def voc_label_indices(colormap, colormap2label):) ]; W* G3 T% z  ^# Z
        colormap = np.array(colormap.convert("RGB")).astype('int32'): A8 s- l9 T( v/ i+ h3 ]3 w1 \% m
        idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) 6 n6 @- q0 _. N% c" {
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    ! `$ R' a' I6 N4 C. E% W2 U! r' X& j1 q! R" q6 q1 X
    可以打印一下结果
    7 ~4 P. B* h( X; Y9 b. \9 C0 y
    / T( Y- r: g2 {8 L& m/ W$ My = voc_label_indices(train_labels[0], colormap2label)( J$ |3 F- l- \$ |' U
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES$ Z5 l3 S7 `# i& L/ x% `

    3 k( I& M$ F& a2.4 预处理数据
    2 @8 C. o3 T$ m6 [: P5 T& s) T5 }
    7 t* T9 R4 i- A8 m在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    ) c& l3 \1 X$ F5 ]% {
    1 X" C: E6 C  [; g' P8 g
    / _1 C+ F: u" \def voc_rand_crop(feature, label, height, width):+ d. H& u# h9 o+ o7 m
        """
    - G5 H. k% s0 {0 i    随机裁剪feature(PIL image) 和 label(PIL image).+ ?9 m/ P! K% C+ W4 i7 n3 y# A1 F
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做) E4 A9 D" X! D4 T
        Get parameters for ``crop`` for a random crop.+ ^" a2 G! J! \9 s- c- a0 ]
        Args:
    4 l2 Z7 {; V0 k+ C, L        img (PIL Image): Image to be cropped.
    7 v4 h2 Q1 W! G0 H7 ?        output_size (tuple): Expected output size of the crop.
    % ^1 V+ R0 e0 |" H' B( G    Returns:
    ' Y0 `( v$ j: D# U! I        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.  S0 N4 d( R, x4 S9 T: r; t
        """
    0 _5 `9 u+ c, F8 ?% x; f' }  `; R    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))# K- W) F# m3 E  T- y$ p
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)
    0 u- w' u/ U: A2 ~) E    label = torchvision.transforms.functional.crop(label, i, j, h, w)* k( D# G1 M0 I" y! a- Y1 N3 }
        return feature, label
    : T! ~4 d( u$ w0 u& K1 q/ P; U9 y& D4 N
    # 显示n张随机裁剪的图像和标签,前面的n是5
    9 P+ j7 L, N; [- N( Limgs = []) X, p  y7 C1 ]: [7 K8 f" L9 l
    for _ in range(n):7 }3 O- C7 h, m8 i+ m- k; @' C: i
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    + Q" u$ j0 \- q; k" ?; o! d( Lshow_images(imgs[::2] + imgs[1::2], 2, n);1 t4 W' L4 C+ O; a! [, g
    ! W* p" k3 T! }9 F* I! O
    & d" L' y6 |* M
    2.png
    ) i. P1 o) ^0 P, c& X  Z8 y/ e6 H5 @& b: R
    ( I0 G+ g' n' i# b

    ( Q0 \7 A+ U# h# p- ?3 自定义数据集类
    8 N% P( c# i1 x. m; |" h& B
    # F$ h# _& n. e" o9 R7 E$ \3.1 数据集类. z. t( B; P* i

    ) j2 w: u1 E! m# itorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法- B1 U5 X" o- W8 L( z, X8 z) j) |

    - g8 ~" M4 w% F+ p7 p__len__ 实现 len(dataset) 返还数据集的尺寸。
    " |9 g6 n9 @2 W; m& B% y__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。( L! X, s& g  R0 O9 P+ s
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。* M8 P8 W, S) x0 ]) C5 V
    6 C. U- F7 i, X* C4 G. B
    class VOCSegDataset(torch.utils.data.Dataset):
    2 f+ m' ~3 h  M. t, O    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):: j: A: Q' G) T+ ~$ s: j
            """, C! r4 V; C6 q0 ?% n+ h( Z
            crop_size: (h, w). I, g. x8 J: N. D8 I  m
            """
      s7 F: Y& a$ e        # 对输入图像的RGB三个通道的值分别做标准化  U8 f4 {7 z- y) f. F( P1 e
            self.rgb_mean = np.array([0.485, 0.456, 0.406])  X2 g0 g6 m) y0 ]( Q2 `
            self.rgb_std = np.array([0.229, 0.224, 0.225]): {) H% T2 _, T& d/ i+ P
            self.tsf = torchvision.transforms.Compose([( M3 ]% z" b* K9 _+ i
                torchvision.transforms.ToTensor(),
      z, X0 f1 _% p1 ~0 L/ J3 R; h            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    6 {  X" p$ Q/ s1 ]3 \# a        self.crop_size = crop_size # (h, w)' |8 h2 i% D) ?; W' u
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)+ J, X* P0 b- y" g( k! L" y/ \; }
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除1 o  ~4 D. j# K. F/ l- m) M3 a
            self.features = self.filter(features) # PIL image
    . E- N( s2 F1 e$ F7 q4 K% f        self.labels = self.filter(labels)     # PIL image
    8 D+ J' E) x7 X2 g) J6 o; T# P8 @, I        self.colormap2label = colormap2label
    & R7 M# l3 o  v' K5 N        print('read ' + str(len(self.features)) + ' valid examples')4 T  S" X$ ?7 r& s8 h
    , E* c0 `! A4 C, W
        def filter(self, imgs):! ]3 X2 U/ y7 Z' ^
            return [img for img in imgs if (; c5 q4 J; E7 @
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]3 z- `6 F+ x; W4 @* d' ^  ~
    ' A2 ]( j9 c" \2 i0 e; \
        def __getitem__(self, idx):
    8 }$ t* P/ P" M        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    9 x9 a: J4 y4 f7 t* K                                # float32 tensor           uint8 tensor (b,h,w)( C/ b' g; N/ C' G4 S/ B. |5 I6 C9 F
            return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    - l$ n: P2 E* j$ m
    3 F  Y4 z5 {- g  K    def __len__(self):
    0 [% F) P' ]. N        return len(self.features)- `4 ?! E! E7 {4 q
    3.2 读取数据集% c2 A4 O8 }/ a

    0 o) i- z9 }1 `2 Y, d通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    1 x3 e* ]8 I; Q2 b3 C
    % L8 r5 |2 q; T3 Sbatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)6 N, J" K+ r$ i5 [0 k
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    & v8 [2 v* a# K3 P! n2 Wmax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~, F2 ?1 d# x& e5 m" A
    , ~6 l" K' w% R4 `  A6 o- H
    # 创建训练集和测试集的实例/ P2 H" j3 z( y) q7 I( R& T0 @  m
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    2 p. }3 g) d) |9 X  `3 Fvoc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    7 A, ^7 Z1 g$ L8 @
    6 F0 P# ^# j) C# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器- n9 h! f  @  {
    num_workers = 0 if sys.platform.startswith('win32') else 4. U! D, W" _/ y7 a7 x
    train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,* u/ p; [$ K& X/ d! [
                                  drop_last=True, num_workers=num_workers)( b: g$ ~2 F) z6 z7 M
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,+ a# B% V2 v, C( q) P
                                 num_workers=num_workers)' y, a7 g$ x5 w! W& c' o5 H. V- z
    * s9 a. k$ {5 v
    # 方便封装,把训练集和验证集保存在dict里0 s/ o2 ^7 G8 |$ L$ I, f0 W. t
    dataloaders = {'train':train_iter, 'val':test_iter}8 l* h" Y7 R  s# t& Y/ W
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    , o6 V$ y0 S/ U$ I* I
    , J2 R. S+ p: b0 y* u# S& ]4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    : u2 b( }3 c& e' d0 N
    2 h7 i" j+ J! A" wnum_classes = 21 # 21分类,1个背景,20个物体
    ; W3 v; m0 p6 ?4 T8 D# j, ^model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    2 a7 d& ?4 q2 f3 |; G; O+ W/ A; s0 \- H( R' W9 l3 R& q
    # 特征提取器  u$ Y5 }3 [3 u
    for param in model_ft.parameters():
    , N* X3 U( H% R5 X4 D7 a3 H    param.requires_grad = False9 A& [  w1 A1 o/ o3 \
    4.2 修改成FCN" E3 U1 m; n% b

    . v5 r- g1 R0 E- S2 e) U# c% W全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    4 i. r4 y4 G" k最终输出的通道包含了该空间位置像素的类别预测。
    2 C" k$ w: N, e6 M4 J
    * N) l% Z( @. K' k对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。: I- ]0 N; O% x5 ~
    6 V$ h/ g; V5 f7 e2 j: f) e$ z5 D
    可以先打印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 倍即可。8 L0 t) X# v* N

    % ]+ Y  _* t8 L& V2 H4 kmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    # {" G& ?! E1 x4 K4 S              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class
    $ l! T8 v2 d5 \              nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    4 A; g1 n9 I* m4 J$ w
    - S# y5 O- P2 x! [, F  z# 对model_ft做一个测试
    ; D% h; n* k4 l0 bx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    6 H! L; B  S: M5 _* [print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    % F7 l& H% N/ n  N! F
    1 g. }+ Z8 W$ e6 e# 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组- x6 o( O* S/ w
    # for X, Y in train_iter:
    ) f+ l; X1 M; F( r; u- W#     print(X.dtype, X.shape)5 T- L, l/ |) a% [7 [2 C
    #     print(Y.dtype, Y.shape)9 F7 a- j% ]1 T
    #     break2 {5 K$ V6 N/ [# W; B- q
    . q) J, F9 y8 _3 l  T
    % J! N4 `( G! q/ e6 u9 a. ~
    4.3 初始化转置卷积层6 w" e, y0 n" f- D6 N1 {) Q# ^9 H

    : \9 @! e: D2 [$ G5 O% ^5 s在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    1 e0 ^+ F2 Y5 [$ F' a9 i! n0 ~  Q在坐标 (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函数构造的卷积核的转置卷积层来实现。
    6 Q9 Z5 x6 U0 @
    ' C7 b  v: G2 V* u8 w  \
    , ?7 a: ?! \% Y6 M2 t3 v8 Y& C# 双线性插值的上采样,用来初始化转置卷积层的卷积核) t# |. y) P( H2 ^  v
    def bilinear_kernel(in_channels, out_channels, kernel_size):/ [( z* D# T$ j" Y& x. ~
        factor = (kernel_size+1)//25 \. T! h! `: Y7 Q1 w; y
        if kernel_size%2 == 1:2 C' B% H/ ]- f3 ~' W
            center = factor-1
    1 r  d. `2 c+ u* \7 A( n" @+ r( |    else:
    ; p3 G) C+ s4 i$ }  H( Y/ r) |        center = factor-0.5
    ; J- E4 e1 |, T+ q    og = np.ogrid[:kernel_size, :kernel_size]
    , Y5 d0 V9 [; {: t+ C    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)' _) q: a1 w* ?
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    & i8 i1 Q2 i% c+ M4 ]& K" U6 `- q    weight[range(in_channels), range(out_channels), :, :] = filt5 l% t  g. ?2 n. a7 y4 I
        weight = torch.Tensor(weight)1 i  k5 X- O# M! N
        weight.requires_grad = True4 l! U2 L( R2 h& {' r
        return weight9 O$ B+ f/ x! \9 e/ ?+ H  r5 `* |( K

    ; N# r$ a! c; K# \& ^' F7 W, M  |6 u" ?. l) b. S
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。' }. _$ B4 \% q- [  g# _! y

    # ?0 j" u) j' D+ R2 O  s- J2 unn.init.xavier_normal_(model_ft[-2].weight.data, gain=1), a0 Y; z8 K9 a$ _- b. _* \( }; u
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)) j( h' _1 C. \% \% _

    ( D+ d5 `7 E7 G% O0 [- r0 x# `/ v! c7 i( y3 c; q# A

    - ^4 g5 ?9 S* O' w. b5 训练模型

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

    " l1 _1 K! M1 \
    def train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    ; `+ T2 N5 l% p" F9 j    since = time.time()8 d% [. c6 I0 N  G. {4 |
        best_model_wts = copy.deepcopy(model.state_dict()), s6 B, C5 ?: ~/ E: l1 E
        best_acc = 0.0
      w/ z$ x, E3 t, l8 b    # 每个epoch都有一个训练和验证阶段
    ! s4 O- i4 _; y% \    for epoch in range(num_epochs):
    0 w+ K: j* E& a) q        print('Epoch {}/{}'.format(epoch, num_epochs-1))) S" K: ]( F; r$ S5 I+ L% R
            print('-'*10)
    : A0 S. {% f( ^7 S+ r! \* u2 N( u& E        for phase in ['train', 'val']:& U, b. |  A# q# f, ]- J) D0 O7 I+ d# V
                if phase == 'train':$ R/ u8 W0 D+ \4 m7 L
                    scheduler.step()
    + O0 v* E% E9 r7 k, c1 f! S                model.train()
    1 w7 O1 s1 @7 N/ |) t& o- ~            else:' ?$ c# s8 F4 u3 R0 d
                    model.eval(); p* |# G6 m% z5 b2 S4 N: {' J  y% a& k
                runing_loss = 0.0
    7 J. `( J/ f8 X! }0 i2 K            runing_corrects = 0.0
    & w" W+ X( A3 U$ G% G6 v- Z            # 迭代一个epoch, K8 H. E' K8 P8 G0 X2 ~4 N
                for inputs, labels in dataloaders[phase]:
    ( s/ c' e1 [( Y2 ~                inputs, labels = inputs.to(device), labels.to(device)- u+ s# Z8 N! b8 T  g8 j2 J
                    optimizer.zero_grad() # 零参数梯度
    3 D" C, O9 G% h: R                                # 前向,只在训练时跟踪参数# {7 ^% _! a/ Z; M6 L& R
                    with torch.set_grad_enabled(phase=='train'):+ s# L8 [. W. g- P% |2 ^4 a
                        logits = model(inputs)  # [5, 21, 320, 480]
    3 i. c4 J' z5 ?, ?: Q                    loss = criteon(logits, labels.long())- V  k1 _4 K* b: g, V! ^
                        # 后向,只在训练阶段进行优化8 M4 m+ R- y# H# R1 F3 ?: r0 f! L9 @
                        if phase=='train':
    5 @3 v! E7 J+ U  B: ^                        loss.backward()/ J, t2 ^% y' a: b4 z
                            optimizer.step()
    4 K+ h1 z9 w; o                                # 统计loss和correct
    - x4 V$ X2 g( o+ l                runing_loss += loss.item()*inputs.size(0)- W( G+ Z3 u) t) m8 S' ?1 o( @; j# m
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    : Q; n2 T! A1 _) O6 Y! L5 c- b4 S4 [+ n2 O* N. v  g6 d
                epoch_loss = runing_loss / dataset_sizes[phase]" n, V7 P6 `) F- y
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    + L* p3 ^3 i: d- `/ t0 f            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    " `# I$ W- j" _( a9 f+ G1 W( H& m                        # 深度复制model参数5 }) A  N2 D- w/ @! I- e7 p' K
                if phase=='val' and epoch_acc>best_acc:
    5 I7 e4 N& ?0 Z( J2 P                best_acc = epoch_acc/ x& J' C3 [3 v3 h  ?' V
                    best_model_wts = copy.deepcopy(model.state_dict())
    & m( g! c" u4 ]0 l8 `        print()4 ~; L' r5 K* h5 u1 o* R0 i
        time_elapsed = time.time() - since;
    6 T; Y$ @7 K8 v# N8 y' }5 A    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    0 I8 y7 J- v5 V* D, ^- Q    # 加载最佳模型权重
    9 P# w3 Z/ f6 G( _    model.load_state_dict(best_model_wts)
    1 M; T- s( o9 p  g    return model/ P+ A6 s- K8 D
    5 b5 z" h7 ^: b# |  j
    下面定义train_model要用到的参数,开始训练3 ?: X: o+ O+ F* L( H; @" e3 w* Q
    + h/ r) @' m; w9 A0 b
    epochs = 5 # 训练5个epoch# f9 a3 v9 [- r+ K
    criteon = nn.CrossEntropyLoss()) _% i$ a  p8 Q) u% L* H
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)
    + \; u- J( F$ w. v6 @9 A# 每3个epochs衰减LR通过设置gamma=0.1( o3 h* w! q4 ~% A: V: z
    exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)+ W5 m+ D: a- k( p7 K! w- c

    6 ^6 ]: W# T; N. D0 P# 开始训练
    0 [7 ^4 `1 q  u1 _8 y$ Rmodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    9 e$ x; d& z; R. H# g6 k; Y, Q' `! g. b% u
    6 测试模型

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

    def label2image(pred):
    3 g2 O3 ?# x; n! D    # pred: [320,480]( ~4 M- a- r; e! ]9 m' E
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    & B( k+ s% R% T    x = pred.long()4 ^5 H6 v, Z! L* q# C( A% g" i  @
        return (colormap[x,:]).data.cpu().numpy()
    , Z% u7 n* d: R: C* k$ Q: q5 a! N. f* l1 P. W; n9 e

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    & P% F! Y- P: w% Z: t8 `std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    $ z& g' l9 O) N, Y4 n+ qdef visualize_model(model:nn.Module, num_images=4):' L/ m& {  p; Z+ w) P
        was_training = model.training
    & b, y. I4 J% c+ K3 I% r& P! F: c  G    model.eval()
    / Q3 I' l. K& h/ @% M# U    images_so_far = 0
    ! ^) ^+ t  x3 w  v; D& M    n, imgs = num_images, []( e2 v! b, ]" ^( t) \# B1 u: F' ], w
        with torch.no_grad():5 R+ Y8 W0 `; r: b0 a$ s
            for i, (inputs, labels) in enumerate(dataloaders['val']):. L, @3 }+ k+ n8 a: e$ ^8 h
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]2 I' S7 P) ^' P  X4 r8 U  {7 f
                outputs = model(inputs)
    + b' d  b9 c/ G) _) D: D0 z            pred = torch.argmax(outputs, dim=1) # [b,320,480]( H3 a7 B  ]# }6 D! r9 K
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦) j, B% V( Z, @5 r, l) B
    . A6 q# a) w. ^
                for j in range(num_images):1 k) D, J8 J$ j/ G8 x: Y
                    images_so_far += 1
    : x' p4 P7 t: w) d% a% ?                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    8 ?- I  h6 A$ H) N5 S                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]
    , c1 z4 x8 k0 E# q4 x                if images_so_far == num_images:4 j' @1 F* D7 t
                        model.train(mode=was_training)& ]! Y/ P; I" |2 W: U- H
                        # 我已经固定了每次只显示4张图了,大家可以自己修改
    , w4 g0 t6 e7 ?                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    ) P, r. J5 |! Q                    return model.train(mode=was_training)
    5 o2 ]- ?1 N7 U% X+ D  l9 P" W& Y2 g2 |9 P
    # 开始验证  k; V, [- N% S; F  _
    visualize_model(model_ft)
    - u, C1 e2 |( Y, u: G1 @) M+ i6 B6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor  \( p& b( m% M
    def predict(img, model):9 B5 ]2 k; `: E  P( H, W* U( W
        tsf = transforms.Compose([
    0 h- J3 e+ A8 F& a; Y3 J            transforms.ToTensor(), # 好像会自动转换channel# D: k7 j: V3 B  x% r
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])- Q% o$ z# S- w, B% M! X# I
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    " x1 D& J4 [: g2 \% ]! b    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)& \- C1 N) z0 y9 Z# I
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    9 q0 G4 M) G3 u1 u3 }# `9 Y' Y( S- u3 ?$ W2 V) J$ ]. H
    def evaluate(model:nn.Module):9 F$ D, @2 o( S
        model.eval()
    # k2 a" I7 X) u    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) 5 h2 f4 U% n& @) _
        n, imgs = 4, []% J; M" D- _4 s6 p, J$ ?: n* {- }
        for i in range(n):5 `( z4 N0 m5 X. }! X2 \6 m5 g
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image2 S  T1 r+ Z2 b
            pred = label2image(predict(xi, model))
    ' {9 M" E+ Q, Y2 N) Y* \+ Q0 ^# }        imgs += [xi, pred, yi]) b: q8 F0 c( N) Q' K
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)% J6 p" J+ Q0 b+ D! r( h  e; \
    ! z1 \7 W4 ]0 c6 l
    # 开始测试1 |$ [5 ~$ N/ f5 i* n
    evaluate(model_ft); q) `$ b4 ~6 p! B
    + y; T+ p( h7 N# B7 l0 C
    7 结语

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


    5 @, w8 O  B3 s2 Y1 w0 K1 y+ B/ w; BEpoch 0/2$ S+ U  n  G7 `5 ?+ O! a
    ----------) }6 D) J( O3 z! w! p8 A
    train Loss: 1.7844 Acc: 0.5835
    ! o+ i5 o! m5 I2 w, _val Loss: 1.1669 Acc: 0.64568 h& {/ A) |2 }

    : \! |. i  E' p* e4 H+ kEpoch 1/2
    : ]  A6 N6 h: l! P----------/ S  P: B6 R( W& o. T3 g9 s1 W$ T. `
    train Loss: 1.1288 Acc: 0.6535: ^8 d/ t8 t! }/ g( i! t
    val Loss: 0.9012 Acc: 0.69294 Z: O$ W# A$ U! s2 \
    % ]  u( x/ L9 |; x$ |" D, h6 L# Q! f
    Epoch 2/2
    0 y3 \/ @; w( L9 L5 F+ `----------
    ! J0 R' ]( d  R# w2 Z3 s6 Xtrain Loss: 0.9578 Acc: 0.67069 \, ?) Q4 v: [6 o
    val Loss: 0.8088 Acc: 0.6948
    1 x* p  R& V* O9 x; i$ Z* ~# d2 ^3 l0 I) W8 z" ]4 P$ X2 A/ k
    Training complete in 6m 37s# P0 M! N  R, J7 _
    7 s( b/ G" a( c
    8 G2 J8 c" Z* h: k3 n
    2.jpg
    ! g2 d- s( U1 X) U+ L) J% A8 J. f. Q
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    , _( R- k* C( x( ^- g- C' u. W- R8 r( e, E5 L
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。8 ^* d3 f! A& o' k1 o1 L, b

    6 `9 {* v, Z& t' r# ~* _, l0 }2 T. M& K' a, q2 ^+ h
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:8 m" \- _; a; p) c

    ; |! P$ }7 s0 S0 r8 w  u! a+ r) nDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]. h" d: }0 F) c# s/ x: }4 d
    GCN 通过全局卷积网络改进语义分割[论文]
    + J' \" i9 u/ v; s( YUperNet 统一感知解析7 M! A. X, N) P! V& K! l6 Z' `
    ENet 用于实时语义分割的深度神经网络体系结构[论文]$ f+ x. c6 f4 J5 G/ @& B# L
    U-Net 用于生物医学图像分割的卷积网络
    * ^. G( b/ h9 D7 o: o! W# }9 `SegNet 用于图像分段的深度卷积编码器-解码器架构。
    4 ?( L1 C7 j) O& R$ y3 p) z6 K: b4 b还有(DUC,HDC)、PSPNet等。
    ' P' z3 |% X* F: a8 v+ ^- j: L8 B2 ^) D' O2 K7 i
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。9 A5 B# Z; {2 s3 D4 t) f/ G3 E

    5 T0 ^3 S2 `1 T1 Y" w! d6 a对于损失函数,除了交叉熵误差,也可以用这些:
    % }4 X9 j4 F* g
    / d, A( K7 {5 M: k* p, _! z5 RDice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    / E0 h8 O( c) P9 ]$ M: A7 Y) uCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。
    5 N7 j1 q) v8 ^  G) oFocal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。; e+ `8 Q1 ?7 G% N
    Lovasz Softmax 查看论文:Lovasz - softmax损失。
    ( g2 `" n7 V/ h) K6 g6 ]$ b* m8 c7 S/ ]- ^7 }4 A3 G5 T8 f8 T

      G4 J9 k8 p- w$ G9 T2 }8 E
    4 b: T) ]( ?$ Z
    * Z% y+ d. j( k8 e$ Z% ~# r
    ( @4 f0 \5 ?4 h: r0 M————————————————
    7 R6 y7 v# b5 M2 u' A版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。* `& X) k2 W8 O) I) L
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    ! x2 z- ]. l! z, e4 _' Z
    / Z3 N9 T6 o8 u: C9 o9 n6 y( i4 q& X) i/ z$ }1 W
    & M/ ^: u7 X: J0 K& e
    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-13 12:18 , Processed in 0.388321 second(s), 54 queries .

    回顶部