QQ登录

只需要一步,快速开始

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

    1 w3 s6 E& {( e" P# K0 h9 W$ v& \0 ?( M
    Pytorch实战语义分割(VOC2012)
    + ~# z) e+ h1 a+ `5 Q本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    % R# O0 P7 `8 ]; L) F语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。3 z: ?. {5 R9 e0 L7 |; U6 a
    ( c/ P7 h4 L3 b& G* x

    ! H) }. J/ C1 ^8 u4 m语义分割中图像有关狗、猫和背景的标签9 K$ Q0 ~* n, F
    文章目录" W2 }% ^5 C( @0 ^
    & U2 A3 t* f1 c1 {& E: a9 P, Y" O
    1 图像分割和实例分割
      R5 \2 G' h0 p, m$ x2 Pascal VOC2012语义分割数据集
      F! M! O4 R* B' o0 S2 r# _' H2.1 导入模块6 {$ G, E7 X; O7 t- u- H  q
    2.2 下载数据集" O; g7 P4 e4 h! s( [
    2.3 可视化数据
    : G8 t; t# c( {2.4 预处理数据3 o& f( T& Y! L2 K( _, I; j2 E
    3 自定义数据集类& I6 V/ p5 H& W0 x
    3.1 数据集类
    2 T# I+ t  {9 V0 m" U8 g3.2 读取数据集: n/ f& a$ K) J1 L% M2 v5 H
    4 构造模型
    + i) k- ~3 `5 I' O6 l4.1 预训练模型
    % u3 V5 r' K% h$ k. x& C, j$ Q% F4.2 修改成FCN
    2 r1 o. b& o! q2 X" _4.3 初始化转置卷积层
    ; c2 `: ~" ^& e3 j; r( s  H6 V3 h) g5 训练模型
    7 Y& s9 D. H% v2 x6 测试模型+ {& E! i* m1 M% v" t
    6.1 通用型# w6 x3 \3 Q. p$ _6 _
    6.2 不通用
    ; G! _9 m* b9 h7 结语* y0 y% W/ C5 K5 Q' m" ^0 G
    1 图像分割和实例分割2 l* \: k8 B, l6 `( C$ c; K
    ) S" q" H0 m2 l6 p$ d( S& L' n
    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    0 e7 D4 m0 L5 K% H
    , e" H3 S! G- I2 ?0 ?6 w图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。, `5 a" z/ e2 h5 j
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。/ M! s' h4 D2 B* ^2 u

    - d9 L7 F+ ^% c  Q( D; ]2 Pascal VOC2012语义分割数据集
    2 p1 o4 G: I! O5 _$ Y# d& v0 _8 }3 u" w9 S& p
    2.1 导入模块% y+ M# Z4 J7 b9 u
    import time
    % x7 _4 j( ]& g& N; h% p0 l1 [" Jimport copy
    * c' b% A" A# O5 r) nimport torch
    , T' ]. s5 ^- W" I6 F0 E6 K- Hfrom torch import optim, nn6 w# z; x2 U" s
    import torch.nn.functional as F
    * M% }  O( c* N- Kimport torchvision
    & D+ p0 S" T  o8 b) k. w( Wfrom torchvision import transforms2 f8 h& q: \( s
    from torchvision.models import resnet18
      Y9 e( B/ \$ F. H$ iimport numpy as np
    5 S0 y' Y. E2 I. s# I. Z. sfrom matplotlib import pyplot as plt
    7 j" R8 B( b5 i% lfrom PIL import Image, |  O) E5 L0 w! w( t2 x9 b3 q, i
    import sys
    + `: W) Y7 G9 d. u' Tsys.path.append("..")
    ; G0 J2 X% o$ ?" u8 T5 A5 k; {$ yfrom IPython import display
    2 Z2 \  M. }1 |1 `' _, r! P4 c) wfrom tqdm import tqdm
    + n: k$ f; s& S& u% Q: Bimport warnings
    ! b2 R7 T) b9 J; p; j& R' Ywarnings.filterwarnings("ignore")
    # W. V4 A/ D% [8 x3 G2 c) S4 q1 Z# ^9 V9 _7 ~3 u
    2.2 下载数据集
    % E' t8 R; T8 O" D% Q4 ]( i# i1 C
    语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:( q8 c* N/ ]# c" s
    # U5 E* B5 t: _% B
    ; ?# ?4 t: Y( [6 }3 R
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件# `4 x8 B5 Y& u3 o# R1 R2 c
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。
    . h4 m; v6 o' t" ?2.3 可视化数据
      M) p& m: y5 M8 W
    6 t4 A, S% ?2 A- O& E定义read_voc_images函数将输入图像和标签读进内存。6 |7 u0 K: F. Q( ]8 q
    2 D# [3 R6 {  n1 H& w
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):7 u/ H( u/ S8 H. g) S( m
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    0 n( f; v" {* p    with open(txt_fname, 'r') as f:9 l% H8 F4 Q" e' S2 p6 t' F
            images = f.read().split() # 拆分成一个个名字组成list- K% A' g! E1 B; k4 h( A- I
        if max_num is not None:# f  S( u. i4 M$ l. J1 _* m; D
            images = images[:min(max_num, len(images))]7 B8 @% z! O6 ?. K. x! e( q
        features, labels = [None] * len(images), [None] * len(images)& C( M: \3 s" d  B" H
        for i, fname in tqdm(enumerate(images)):- S) _) n  `/ C  ?) O. G
            # 读入数据并且转为RGB的 PIL image% P5 y5 u- v# p% X7 k$ A- _
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    ; ^& [* ^6 l2 W4 R7 l        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")
    / S5 o* H) j* c0 B. W+ w    return features, labels # PIL image 0-255% B0 `' g& F- Y$ ?
    / A# I( r8 z4 a/ F
    定义可视化数据集的函数show_images: A8 J0 K  b1 Y  }- ?1 E
    1 L6 {/ q% [0 X+ ]6 s
    # 这个函数可以不需要* `3 @# U; o8 p7 B
    def set_figsize(figsize=(3.5, 2.5)):
    6 b. a$ T( k5 I5 }5 p& R    """在jupyter使用svg显示"""' D2 s% N! D( P, |/ c8 l
        display.set_matplotlib_formats('svg')
    & @" A) J/ k+ _; q4 D    # 设置图的尺寸
    / ?" M# |( J- S9 q) u, B* V: `    plt.rcParams['figure.figsize'] = figsize7 W, A, @6 v0 I* n
    , V$ M' E& \# u
    def show_images(imgs, num_rows, num_cols, scale=2):+ S3 F' j9 ^, ?5 u  T9 p/ Z5 K
        # a_img = np.asarray(imgs)- s; \) K5 a& I: j+ O! B5 L/ d
        figsize = (num_cols * scale, num_rows * scale)' N: r  n5 U6 i' L9 J1 C
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    ) \2 w1 e) p  L8 p) o- Z    for i in range(num_rows):
    6 M5 a' A# s3 M# O        for j in range(num_cols):) I/ E; j0 Z! }( A9 s
                axes[j].imshow(imgs[i * num_cols + j])
    , W0 I# U/ k1 f/ x4 J            axes[j].axes.get_xaxis().set_visible(False)( a1 I: X2 y. i" e4 D. @
                axes[j].axes.get_yaxis().set_visible(False)
    ) N  G0 k- w( m  Y    plt.show()8 l: m! ~8 h; H3 D" p
        return axes
    ; ^( L; h1 e% f$ e+ v- r9 H+ W9 k8 W0 U8 O
    定义可视化数据集的函数show_images- l7 T- X" P* B+ R
    : R, {; r5 z$ g. U7 z) k5 D; G
    # 这个函数可以不需要
    - o% t; n+ M' ]! mdef set_figsize(figsize=(3.5, 2.5)):
    + @- N( R( B2 }- `2 \    """在jupyter使用svg显示"""
    4 w, R' o9 Y+ z  g* _7 D- C    display.set_matplotlib_formats('svg')0 w$ ^8 _6 [4 K
        # 设置图的尺寸
    ! ~0 b: ~+ n  R# ^    plt.rcParams['figure.figsize'] = figsize
    - n, D% h& a3 _# I
    ' V. ^) ?( W2 S8 ?" odef show_images(imgs, num_rows, num_cols, scale=2):! l, {' c8 t3 l7 R' e- F7 J
        # a_img = np.asarray(imgs)
    " d" I5 S; w7 T3 l9 _    figsize = (num_cols * scale, num_rows * scale)
    - D  y' q/ \- W# k6 T* q    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    0 ?) P  |+ k* v    for i in range(num_rows):/ F3 r  m0 q, w& Y6 T' q
            for j in range(num_cols):
    ! f' [# e' U0 K/ G% Q& ~            axes[j].imshow(imgs[i * num_cols + j])
    6 ?  s$ f! E: g, k6 z            axes[j].axes.get_xaxis().set_visible(False)+ D8 L& \9 [% ]+ x* R9 z
                axes[j].axes.get_yaxis().set_visible(False)- `8 z4 S. c) _2 g# x1 C9 y; i8 |
        plt.show()
    + R, T- e1 S3 Y3 i% p    return axes- [. j- q- c/ X1 k0 V! R$ W
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。
    , E$ \) P8 |+ V  A9 D) q! F
    ! B# Y: A" ~3 h% Z) w# 根据自己存放数据集的路径修改voc_dir
    5 N- D4 E7 P7 F+ ^voc_dir = r"[local]\VOCdevkit\VOC2012"
    ( i1 i9 X4 I- Btrain_features, train_labels = read_voc_images(voc_dir, max_num=10)3 I. r$ l3 ]: W9 R2 b8 a+ ?8 x
    n = 5 # 展示几张图像( W# r' W! O# F& ^
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    7 W* U9 I% ?6 w, ^- V2 t4 f" Hshow_images(imgs, 2, n)0 E3 |( q" E! C* d$ ], e& ~: c3 S

    ; n: y  y: D1 w) O8 j 1.png 1 D; `" n" ?# ?5 a
    5 K& z+ X4 J2 z) m+ M
    列出标签中每个RGB颜色的值及其标注的类别。2 t, b+ o% o/ Z
    # 标签中每个RGB颜色的值
    - u0 |. N' E0 k7 l4 WVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],# X' w: F: v6 N! B. j
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],+ @3 o0 V" s/ @, l
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    . q& x$ t. H! a! p                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    # K. n: o: {3 A- q                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],# {+ f" \5 k, ^# i
                    [0, 64, 128]]
    7 Z9 ^; ?( p3 c  S# H8 Y# 标签其标注的类别
    # n% C4 Y2 \, J. K$ wVOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    ; t9 j/ ?# N! l. A6 {+ j               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    " V" k; b. z8 U6 d9 o               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    & f: \- C5 t$ [  _5 l! J: c$ K) x               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    * D) L- _; ]* I, n有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。- x) {5 V# s6 f/ b
    ' X. P* n% m. @! D
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    4 y; r( ^  {( `# _$ `' N1 Ycolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])3 h4 V- q/ i& _2 s& n% L; [, K! ^
    for i, colormap in enumerate(VOC_COLORMAP):
    . @( e0 B7 k! v2 N5 H    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i$ k  E$ M" C; l( [: {
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    7 T, |. |& P$ u' O
    + ^# }! ~7 R- s0 K  p/ z' ]0 {# 构造标签矩阵
    ! e6 c. Z8 ~; v7 s1 t# S4 pdef voc_label_indices(colormap, colormap2label):
    + g, M  [5 R8 X! I, H    colormap = np.array(colormap.convert("RGB")).astype('int32')
    " i, }3 r4 o8 _5 h, w& g! P& X    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) & m. I7 L. w* K0 D
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标; B( g+ E- ?* k/ N

    + w2 {7 E; {2 Q4 k  G" `可以打印一下结果0 P& o$ i: k  O7 \! ^
    6 L' V+ d5 [, Q8 o
    y = voc_label_indices(train_labels[0], colormap2label)7 n8 w' o: B$ @% `7 H
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES/ R  k/ d2 h9 ^1 E2 t
    ! {9 {! @. _& j+ S
    2.4 预处理数据$ ], C: f0 _9 j  d
      B1 J. C7 J5 Q6 m7 |) b) ~8 M
    在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    % v; H& D3 l! b8 s) N" G/ r! }/ F
      P3 z. c/ a) p2 D+ A7 K& e0 y1 {) _# V: ~" |; c+ Y; V
    def voc_rand_crop(feature, label, height, width):
    8 e1 O; Y! N5 J9 S# n& p. G7 X3 `    """
    ) @8 x) T% K* ]" E    随机裁剪feature(PIL image) 和 label(PIL image).4 |' a. J1 I% Y
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做- k# s8 U9 W% u' L! V1 S, r
        Get parameters for ``crop`` for a random crop.& C+ k- a) A- z+ p
        Args:" G- X) k. ]  H9 y3 _! U
            img (PIL Image): Image to be cropped.# m' q7 D3 R- W- D5 ?0 I. r' e
            output_size (tuple): Expected output size of the crop.
    4 p, @$ E/ f- F* W  A    Returns:, W$ N0 ^1 W! @8 D0 J8 r  A. z+ @. s
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.1 y. C$ _3 B+ K# T% W  r7 ~
        """6 B7 _; K' n/ p* |  p: r
        i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))' f! k+ B6 G) c+ ]- I/ {
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)
    1 \3 `2 t( C5 @, R' c0 w    label = torchvision.transforms.functional.crop(label, i, j, h, w)
    6 g6 }; K% |9 i! O! C    return feature, label, J7 O# `% F( Q5 L# w

    5 l! d+ A3 h% G' O# L0 S# 显示n张随机裁剪的图像和标签,前面的n是5
    ( X5 _! ^  c* ~# E. \* L$ aimgs = []* E+ Y! N' P% E! Z# W. x0 |7 s" }% x
    for _ in range(n):7 l" S! G5 x: ]' S/ e  d
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    % _, q' }) Z" u* Eshow_images(imgs[::2] + imgs[1::2], 2, n);
    # G* \/ H- n7 w, [/ _, E( _$ w  s! d- X5 I  P' c9 }5 B% g
    + L1 ?% S6 y6 ]& K- m
    2.png
    ! x7 O- g+ B. }! G7 ]
    ( Q6 u3 m$ X8 n1 a# s" L1 H" P
    2 n0 {& H7 n. X5 m% ^0 @! F" }( @4 u. K* J, v* J
    3 自定义数据集类
    ' O$ _2 Z) t- H' s3 d  `7 a$ f- c
    ; k- k" D7 G2 N5 B" g9 I$ h3.1 数据集类* x" g* e1 \( r) ?
    $ L" S; \( W% T# z8 X' R1 _
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    , j" Q' W( i8 d) C
    ) ]# C# e6 A; A! t* C6 I/ W__len__ 实现 len(dataset) 返还数据集的尺寸。
    : N2 C# o: ~" ]" }# J' ?$ c% ]0 W__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    2 `& s- L2 Y+ H由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
      H# d4 m: @$ @) r4 a- u6 X; q$ O( o/ r+ U  h; P5 N2 M* x9 x
    class VOCSegDataset(torch.utils.data.Dataset):7 b1 Y8 }" A, j; d6 Q
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    + J! Z: K- r3 v        """
    6 Q; d+ ?' N; \) ?# z0 s7 r        crop_size: (h, w)$ v8 s2 h, k4 ]/ g" u! n- r
            """* W( Z3 U2 K* e1 q0 l
            # 对输入图像的RGB三个通道的值分别做标准化0 [( l' s7 `5 |7 h2 u
            self.rgb_mean = np.array([0.485, 0.456, 0.406])
    * v$ [2 _) i9 B9 o2 [        self.rgb_std = np.array([0.229, 0.224, 0.225])
    ! z& z6 V4 b3 d: ~: d: t+ _7 H        self.tsf = torchvision.transforms.Compose([
    ; o; f3 _1 e" ?  G4 }$ f( v( A            torchvision.transforms.ToTensor(),1 M8 I" R# i5 C$ t+ b( |
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    ; h: S: A  N" L$ W+ I        self.crop_size = crop_size # (h, w)/ i$ z. N' L& {0 P/ l2 v
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)1 }6 }1 B" I# B, O1 b$ `
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    & `9 `& O% R: W, e6 v* R0 q' |. t        self.features = self.filter(features) # PIL image( R/ k( Y. u6 V7 q( L
            self.labels = self.filter(labels)     # PIL image2 [4 ^  V& d6 u. M
            self.colormap2label = colormap2label, L& l  N" t# D
            print('read ' + str(len(self.features)) + ' valid examples')  s" t- y  h! o+ q7 z

    7 F' r' q3 M" J2 w4 Q" x+ z% d5 [    def filter(self, imgs):
    7 y2 W, E1 s4 F/ u2 C2 K        return [img for img in imgs if (  o5 E5 C( {, ^( {+ k& n, ^: Z! y
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]) j/ u% r8 v% o

    & o0 {+ ^2 S; c( J# a    def __getitem__(self, idx):
    * }3 y# t8 t- ?1 R5 @5 A        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    ; b- q0 ~& `6 t+ w, ?: }                                # float32 tensor           uint8 tensor (b,h,w)
      l. \* O6 R2 W        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    4 _2 t. r" e+ E+ d& |* B- o* H
    : J# }2 r, e8 b4 u$ u" G    def __len__(self):
    ! b2 g% K& d) U        return len(self.features)
    5 L) }9 e4 f0 ]4 A( E" ~: D; L3.2 读取数据集
    5 z8 \1 t4 P8 y* @; U: g
      ?' J7 R3 w0 m  ^0 ^! ]通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。; ^1 S  W  Z/ H5 K5 l5 u, L
      k) _2 w4 {  B0 E0 k! u
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)! G( v' E4 E9 a8 U- y* E: ]( M
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    0 D' v4 H. I- X# `; s/ `  ]max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~( j' m4 A" q1 @! {

    9 J4 x/ Y9 j; o9 I8 k) N: U) v. G0 X# 创建训练集和测试集的实例
    , f- y* v$ M- Vvoc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)1 p5 Y$ d0 Y9 F9 b5 v4 n* p
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)5 n0 Y# p9 R4 d2 b5 m: l2 E6 B

    - b% J. u% B3 E- i$ g# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器$ r- O( X' M+ Z: v& \$ x' H
    num_workers = 0 if sys.platform.startswith('win32') else 4( V& q0 M5 W) A1 b. |; r7 K
    train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    8 N; i$ D; R( d/ g                              drop_last=True, num_workers=num_workers)3 L- `% F6 D1 x: S9 K
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,& `9 r3 H# g9 Q2 v  E
                                 num_workers=num_workers)
    0 B- @- _, e* E" W/ t; d' g7 [& L4 G
    3 P7 j2 y; h5 S' l& {& k1 h2 h# 方便封装,把训练集和验证集保存在dict里( K* g. q; B) u: z' m1 \
    dataloaders = {'train':train_iter, 'val':test_iter}$ G  R0 i  `" p' ?6 O& j
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}0 O, x+ B9 l% ?1 E
    5 s- `! ?+ |( O2 R/ ^( I; Q
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    ' l, ^) m8 r: ^5 q
      M1 N) ^  L2 X# p5 o9 vnum_classes = 21 # 21分类,1个背景,20个物体
    & R& |, w1 k' o7 d) H) x/ h. Qmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    6 n/ a% k% x7 ~) e: k' x$ e, T# Y$ t  ]# n: `; c
    # 特征提取器: }- o, L4 `' X! m% X( T8 h: W
    for param in model_ft.parameters():
    / `# a4 z5 O* o0 q" L  U    param.requires_grad = False, h. A. s3 X# ?6 N+ D1 S1 ~  r
    4.2 修改成FCN$ \; v$ N/ a% Z$ p

    9 m! Y. Q5 @+ V- t' T/ z全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:9 |: I% x& Y; v9 x  A( O$ ~
    最终输出的通道包含了该空间位置像素的类别预测。
    0 r# o, J5 G7 m; o1 m
    8 y& P5 }" `" I  h' d" o/ T) e对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。8 W. @( w/ ~/ J
    2 o- d  s% I% g5 [$ 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 倍即可。; W8 m, g6 H$ Y, _* F

    - m" I' ]! e5 c1 v! q# pmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层0 B% L' e  I- E% o
                  nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class* ]+ `! e+ e, g" Q3 w2 L! V
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小$ D& r, p, d8 L' p
    8 A: n1 C% c$ o- n! ]0 x/ Z3 }
    # 对model_ft做一个测试1 q! G5 D, ]) n% b2 T3 a
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据( p- m. z) }) b+ w0 l; b. }
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    ( d( n: S* u5 C) O) ~, j& I% z# p: n4 F3 c$ Z/ R
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组+ F% G7 ?. ?# ]% o( |8 w( `
    # for X, Y in train_iter:0 M/ ~' t! j* q  T4 L+ c: m
    #     print(X.dtype, X.shape)/ T6 K7 u" o* K& m8 v0 D' s, ]
    #     print(Y.dtype, Y.shape)
    2 C) g4 ~! z, T3 t. W#     break2 f) B  E9 W4 o0 [6 Y6 A9 q; g- j; a
    & h& r% j5 ?% U7 Z
    $ @% h7 T5 k5 I$ \5 G
    4.3 初始化转置卷积层& h/ K  c( B6 r3 G
    ( @2 {1 t& |7 C  e7 _# D9 f
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    ( f( F5 c- ^# b6 c在坐标 (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函数构造的卷积核的转置卷积层来实现。7 Y. `* h$ P: E; D: t" B, x# G
    $ F7 @: e8 {+ n) g  i+ j

    0 t6 H3 i/ J, K& T/ r! L* W# 双线性插值的上采样,用来初始化转置卷积层的卷积核9 O; [7 G4 e) V
    def bilinear_kernel(in_channels, out_channels, kernel_size):0 N9 X" G% j2 F4 n6 I3 U6 ^' |
        factor = (kernel_size+1)//24 B6 J7 Z* L* o% B% ]7 {0 z
        if kernel_size%2 == 1:, r# i" ]: X; D9 |. E, S
            center = factor-16 _' u" u5 Q8 Q$ P- V
        else:
    5 l, [4 X* B# Y! r% G( N; L7 b( O        center = factor-0.5( \8 Q# _% H  T+ i! \. E
        og = np.ogrid[:kernel_size, :kernel_size]
    " X: E0 a7 d; r. b. A    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    $ Y0 N& v, {" w, Y# ~5 J  {, |    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    ( d! I' i  s+ D    weight[range(in_channels), range(out_channels), :, :] = filt
    4 Z) l6 v5 @' C* b( D! L    weight = torch.Tensor(weight)9 D6 B) o0 e5 k
        weight.requires_grad = True' H9 ^8 d  q% i9 F7 H2 o7 i* _
        return weight0 `5 p" q. U2 m% W) w$ I

    2 f. z+ Z- G$ r+ ^# e4 t& V& c6 x. u) B, }# d3 ^! S
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    8 N. V- }2 ~" A9 i+ O% w5 T' u. [
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)7 G2 v, B8 K) r+ t% d& T2 Z
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    ; g5 s( D* n/ _3 r( @
    # h; f, @+ \! U$ d  F# \- i5 P& i
    0 j' j) p4 k4 g2 W5 W, n
    ! g- g8 H4 e* m! [; Q7 N5 训练模型

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


    - W- L% B% I9 l& ndef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):( @" t) a2 _/ N+ N3 o
        since = time.time()' z" C! n- A, O. s
        best_model_wts = copy.deepcopy(model.state_dict()), _" o9 t! x+ T  O2 X: a. r% @
        best_acc = 0.0( \' G$ m6 f4 @# K( K. o
        # 每个epoch都有一个训练和验证阶段
    . X4 o) R4 L0 t4 [" ~+ X    for epoch in range(num_epochs):
    8 X# [* Q* d% ]$ m/ \# |        print('Epoch {}/{}'.format(epoch, num_epochs-1))
    % a- h1 J- b2 I: @- \; [- n" Y* G        print('-'*10)  G1 f7 {% q. R" T7 o6 Q
            for phase in ['train', 'val']:
    1 ~* \. A6 e8 R5 F            if phase == 'train':
    & n& x# T! e/ E/ c8 a; w                scheduler.step()
    * ^) j1 ~1 M5 {5 K# [" ~                model.train()
    9 k. \" y; n+ m' O: i* e' P            else:
    4 O5 Q% d2 e* E( F, v                model.eval()  Z" a) T$ U* f' P. H* c5 y
                runing_loss = 0.0" w% w( A# ]  `6 a/ Q% x5 O4 ~: W' O$ Q
                runing_corrects = 0.0
    " W. a+ q9 k/ y8 g1 [* U1 ~5 x! t            # 迭代一个epoch1 ^' b4 m( n( w' @1 z, G2 a
                for inputs, labels in dataloaders[phase]:
    ; |5 S% v8 P0 a9 _                inputs, labels = inputs.to(device), labels.to(device)+ @. ~1 k. w4 C/ X
                    optimizer.zero_grad() # 零参数梯度
    0 v0 ^# C8 Y+ p, ]8 n' t                                # 前向,只在训练时跟踪参数( R* z0 g0 X& K2 I! v8 P
                    with torch.set_grad_enabled(phase=='train'):
      ~+ j/ T9 s7 J% M% h4 X/ t& h                    logits = model(inputs)  # [5, 21, 320, 480]
    7 x1 U, l6 v, J7 W6 }2 i9 t" O                    loss = criteon(logits, labels.long())
    * B  T2 w! r3 ^9 {0 s                    # 后向,只在训练阶段进行优化
    - L9 G" s( P( Z4 t# G                    if phase=='train':; Y  g5 U' Q6 q0 _) c2 Z
                            loss.backward()
    ) x. X  X; o" L* P1 [  ?                        optimizer.step(). i5 v# D% q- T) q# n9 o$ X( k
                                    # 统计loss和correct
    $ K. B. ?+ U/ I9 I* U                runing_loss += loss.item()*inputs.size(0)2 v# g! C5 Y8 W5 M1 R$ I. m( e, u
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)2 ~9 i" R8 d: p- s; M" f! o

    8 V( X* p/ u- ~! F2 J            epoch_loss = runing_loss / dataset_sizes[phase]
    , e5 v! O6 i) D) o            epoch_acc = runing_corrects.double() / dataset_sizes[phase]2 o+ Z' h: b$ D( D; v
                print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))6 a: R) O5 R# t6 q0 H8 w) T
                            # 深度复制model参数
    0 K/ l( |/ H# Y            if phase=='val' and epoch_acc>best_acc:  |2 [) [5 \( n( W4 ?8 R2 k) A
                    best_acc = epoch_acc4 m$ Y& H/ a2 i7 h4 p
                    best_model_wts = copy.deepcopy(model.state_dict()). S/ C7 Z( a+ s, B$ p7 J) U
            print()
    , Z, V5 P1 u* S$ A) U# k9 d    time_elapsed = time.time() - since;. `) ^% O5 E' Q( ]  H2 t
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))7 X( z* x. B$ u2 z. ]3 f! U7 P, g
        # 加载最佳模型权重5 S+ n- z2 I# h) j- F; L$ F& d
        model.load_state_dict(best_model_wts)
    ' I2 x  V' u8 V( B6 `9 K    return model
    / s2 I5 A4 x# c5 ^/ v  w; U. n2 S, o, F& a3 |) H5 ?8 }
    下面定义train_model要用到的参数,开始训练6 g3 T' X1 c2 L' }. r
    ( w' {/ f! o) f2 ]
    epochs = 5 # 训练5个epoch
    : J0 r" p' W* _  I( \$ jcriteon = nn.CrossEntropyLoss()
    / l5 `  n: T7 B. g* qoptimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)2 h$ K# T" m) H6 D8 f$ X
    # 每3个epochs衰减LR通过设置gamma=0.1
    ' W' ^5 U( g  |exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
    ' B* m; S0 y; T$ F' `+ f. J/ N/ k( U5 Q3 G
    # 开始训练
    & v7 _. Y& Z. ]) D* w! O7 E8 Umodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    & }" s8 `) c5 k% |, R
    6 }$ y7 p% l* u7 z6 P6 测试模型

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

    def label2image(pred):2 b! G0 e- D' S3 t+ E
        # pred: [320,480]) {/ x6 R  ~( H; C" n3 p( q5 o% R
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    6 \9 U$ ~$ v3 x; ]. m0 ]    x = pred.long()
    : V# w* y# i5 a% K2 q    return (colormap[x,:]).data.cpu().numpy()& B+ P& E1 S) E7 D0 M. ]' s, b' l+ l
    4 B5 `/ K" M" ?0 o* L8 B

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)6 {' W% }( E  z* @
    std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    3 }2 i" m% V8 O3 w# ndef visualize_model(model:nn.Module, num_images=4):$ P. E* ]) ]. z! L
        was_training = model.training- U( x0 d) `2 u5 B
        model.eval()
    0 M- z9 d! P7 O    images_so_far = 0
    ; d" r& _/ F! w0 O    n, imgs = num_images, []1 U* A" S" p- Z- B; \& \
        with torch.no_grad():
    5 z/ B6 X. G5 E        for i, (inputs, labels) in enumerate(dataloaders['val']):' i9 {2 i2 Y( f, s
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480], E- v# @% D- U( o& {
                outputs = model(inputs)* g5 d8 u6 h! ?) \4 {
                pred = torch.argmax(outputs, dim=1) # [b,320,480]" H8 A4 T+ I2 [$ L, @! i( c
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    2 F  y  d/ p% N* M' l$ f6 Y' V/ U3 @( l2 a/ K6 t, n
                for j in range(num_images):
    $ x8 c) `9 ]6 {/ N* m4 Q% d                images_so_far += 16 L& n. T" }( @0 t6 y. i/ G
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    1 f8 P, s" W$ r& A* A6 u* Y                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]* x+ Y; {4 t& ?: U8 o  S/ h4 ]
                    if images_so_far == num_images:* N2 G# H" w* c( S# d4 \
                        model.train(mode=was_training)$ w2 G, @! h( Q* X% z9 w
                        # 我已经固定了每次只显示4张图了,大家可以自己修改! r& Y( @# T6 b" N1 s% |7 g" r
                        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)' }+ ?' c3 J/ P1 o0 e
                        return model.train(mode=was_training)$ s1 F2 W- J6 b% e, d/ g7 z. F( l

    6 s% v( L# }9 E# 开始验证
    3 F; n; x# U( V* P9 s) Qvisualize_model(model_ft)
    % [* J" r( p  Z5 o; Z  ~) D6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor) L/ p+ C+ V: E4 j; x
    def predict(img, model):
    7 w) s; O& }- F# |2 ?3 j! {5 Z" X' N    tsf = transforms.Compose([
    ' i3 S9 s" b. [/ |( Q1 [: S) T; l            transforms.ToTensor(), # 好像会自动转换channel
    4 q; `( b- Y9 ^; [            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])) s! {4 E0 M+ B, x3 n
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)6 I. e" w$ ?$ b: r: E) m7 P$ s
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    ) D% k; o% z. }% E; F    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    : }: j# [6 ^9 h% `& _5 X8 K3 a( `" Q. O
    def evaluate(model:nn.Module):
    $ C) E8 `! \- g! U  k    model.eval()
    ; T1 V) v, }' [1 u* {( b% y    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    ! W6 @: v5 @) }; f    n, imgs = 4, []' S; Z. w/ Y( W2 T
        for i in range(n):/ w) h' q0 J# }  B8 }
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    - n, u' ^- q3 s! ]# E        pred = label2image(predict(xi, model))
    * ~5 B9 f2 u& N) P' B        imgs += [xi, pred, yi]- `) L+ Q# G- X# r* b( y: e7 O
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)* _% h( ]! R6 I! {8 v5 Q8 u+ s
    + D9 K4 I2 \7 i  o6 N
    # 开始测试
    1 Y$ P1 ~( r$ _( Cevaluate(model_ft)
    / I$ Q9 s, k% o' m/ G* w+ I
    8 j3 \" |+ {2 [- p' Q( Y7 结语

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

    0 e9 l5 J5 y8 x6 X3 j( E' b
    Epoch 0/2/ C: z6 s4 r- G' p" \& [' y% A
    ----------+ }1 ?- o# S: o3 k* z1 V! c
    train Loss: 1.7844 Acc: 0.5835" R, l+ t& m' @* l# U, E
    val Loss: 1.1669 Acc: 0.6456$ D6 `& u3 C; E) d- G% W! \

    : D* A0 q( S7 [6 MEpoch 1/2
    , k" a. y# D8 N4 V; _/ Q----------
    ' X9 f" ]& B8 Jtrain Loss: 1.1288 Acc: 0.6535
    8 p8 Y* t4 U2 p! |' Uval Loss: 0.9012 Acc: 0.6929
    : C" ^% q# K& }  L$ V5 Z
    3 `' z; s& g; O  Z0 J, R0 U, y" kEpoch 2/2- y! z0 l" ?- Z% _
    ----------0 |8 ?& T! v4 X. W
    train Loss: 0.9578 Acc: 0.6706
    & X( A8 H  t  B% r7 Q, v4 \val Loss: 0.8088 Acc: 0.6948
    0 k" p' o  m3 B# J: N) ^# W- w2 u4 o8 W1 I
    Training complete in 6m 37s
      _& r* `! v# G1 g0 U
    ) O7 p) U" z4 \3 l
    . I9 K* R' D2 A4 ^2 U) ] 2.jpg
    2 v$ @" b0 x$ M6 n
    2 z, x$ }+ J# B! _当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    " h+ w0 O$ n7 P# L5 C& Z: X) l0 h- ]- P' p; Q& @* A
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    0 r1 A) r$ H% }" A( ~
    4 e, E4 A" s. `/ {4 U, E& o) U4 G
    0 H/ Y: O* H( q2 _$ R, y语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:/ N' q' g3 n; T3 w7 w
    1 R& s+ _/ d/ ]  }5 x
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]& m1 R" ~% K: B4 Z
    GCN 通过全局卷积网络改进语义分割[论文]' ~2 R+ g! T6 w8 B
    UperNet 统一感知解析
    2 U/ g8 _1 d2 Y% D5 h8 n2 v- ^- kENet 用于实时语义分割的深度神经网络体系结构[论文]  d( J% c8 G7 d- d" `% S! W. a$ b
    U-Net 用于生物医学图像分割的卷积网络* X$ [, T3 `: s: s, ^
    SegNet 用于图像分段的深度卷积编码器-解码器架构。( Q1 B7 x. V4 w# v( ?5 u$ Q- K& G; i
    还有(DUC,HDC)、PSPNet等。# K  L  A1 t, G+ h9 q6 P/ c( }2 x7 [
    + s$ F6 c+ P" @; _
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。% L* L$ d* v" J" s' g0 e/ g
    * n5 B0 ~' p( Y" q- {1 t# W
    对于损失函数,除了交叉熵误差,也可以用这些:
    0 {. V4 K# W6 @  a# \/ z. O5 l: \
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    : ?. G' q8 s8 `  `; o0 ]& mCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。! ?/ q$ k- S; Q. x' @8 Z6 b; A3 v
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    9 ~: v. ^: p% XLovasz Softmax 查看论文:Lovasz - softmax损失。
    1 u; o) M. s  G9 U" `, o& G# d$ v' ^' J/ x" a4 o1 f/ n

      V$ B* @% w. K5 z# ^6 r4 h8 ^
    5 |' i6 [. I% ]* ?0 ?) x. K. Z/ W: }0 B/ K

    + I$ X) A- N- z————————————————7 M. E$ w6 H' Q* E2 F
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。6 T  R; V. n5 T  a% W5 @" S+ o
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    1 f; V. Z; H& u  g) f( n" B
    3 {/ _. G: [3 l; V3 d2 A
    / o6 O$ [* E7 v3 F% U" I8 o2 T) H. k0 o
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-9-2 04:53 , Processed in 0.322246 second(s), 53 queries .

    回顶部