QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 10365|回复: 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
    & d. C; O3 o, `9 e
    * L0 s* {: q0 N7 e
    Pytorch实战语义分割(VOC2012)
    : J4 w: X- e7 o. o, t. q本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。6 u& r; M  G: B! D) k0 i: V
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。/ z, v( P3 c5 Y

    + w, C) g3 Q) [2 L: _$ l) ?! t3 z9 |% E0 l  L2 U# o; [0 R  v
    语义分割中图像有关狗、猫和背景的标签
    " R: J8 ^- r1 ]+ R1 ^文章目录
    7 G3 @3 |( z5 [2 G6 V
    + {, j1 G) n% \+ ?2 {6 c1 ~8 N7 T& C1 图像分割和实例分割3 x, H1 L$ N. M& q. c( g& Q
    2 Pascal VOC2012语义分割数据集8 _8 _* P# p; X
    2.1 导入模块. q( N0 ?& p& r, N0 B- m
    2.2 下载数据集" t5 @/ K7 G" T/ ~7 F6 M
    2.3 可视化数据+ F: z0 ^" G) Q- T7 }% Y
    2.4 预处理数据
    3 v1 K6 W, d/ O' i! [3 自定义数据集类$ P  ~" ?$ z9 S) `+ [
    3.1 数据集类
    0 y$ o; c9 n& x3 }2 d3.2 读取数据集7 L! i" D- }. R& I/ T
    4 构造模型6 D6 e/ Q$ e0 G5 Z2 |
    4.1 预训练模型% {: c$ W6 a: l  r- Q3 x
    4.2 修改成FCN/ \# x( m+ I9 |, U+ j6 ?
    4.3 初始化转置卷积层
    " g% A9 z  p7 T& Q$ A5 _5 训练模型
    7 `; T  a! y/ ~# k6 测试模型+ D$ T* b+ t& z' W; O4 k
    6.1 通用型& V% G3 }3 `5 |9 U- I! d
    6.2 不通用
    / `. s* c* ]) f7 结语# @  y8 B: d) I9 s
    1 图像分割和实例分割
    / O3 v; A( y% _4 D/ h& g& j( M  t7 a
    ( _* N4 H  I# U7 |* S* x& u计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    $ g+ V' r( B" g* S2 F4 j
    " I* ^7 U5 v, F8 h( G7 n5 R( _# F6 L图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    / }2 ~9 O% G  _" z) R" W实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。# L9 P' Z$ E3 E0 Y

    " ?- c2 Z* T9 ~2 Pascal VOC2012语义分割数据集& `& R7 H# w6 f' A& W7 }# g
    0 _. A. w/ p+ {; `4 i  p9 N. l6 L, D
    2.1 导入模块0 J, y) ?" }1 ?; ~+ R. `5 m
    import time
    0 t/ u7 c+ l- b& I  _2 G: e6 Y& K- Nimport copy3 T9 ]# K; z4 A8 d+ q  x- I+ ~
    import torch* H& g- P0 w$ o4 H  J0 d" W: q. q& P( z
    from torch import optim, nn
    ; Q) \2 e* t' u1 d/ j. r5 c# I0 Gimport torch.nn.functional as F# |- p/ @! E. p& ]% i
    import torchvision
    " I; |/ d# I1 @$ E) d) Yfrom torchvision import transforms
    2 J5 m, b% K8 E: E# C- l7 pfrom torchvision.models import resnet18
    $ Q* W. C) t( ]* Eimport numpy as np2 V" Q8 g$ G+ Z. b- G: F
    from matplotlib import pyplot as plt2 _+ Q; `( t, D
    from PIL import Image
      o, p" F4 v6 }! o+ _7 Uimport sys
    ) H8 _+ `8 t2 j; c2 V/ \. M* b1 asys.path.append("..")
    & f3 j$ |0 N* x5 B1 g1 \from IPython import display
    ( \+ w6 o) f/ `6 x# _, A' ^8 }from tqdm import tqdm
    7 g) j. m  j* l2 A3 Y, Eimport warnings  s  b' O6 w% `
    warnings.filterwarnings("ignore")
    8 U, R0 ]! ?7 v
    0 u+ E9 a  x; v7 M2.2 下载数据集3 v1 A6 ^' [! F

    0 z, n- ]+ N0 _: n# q* S: K语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:5 x0 n3 v4 Q3 g5 D
      G6 R7 `5 X1 y

    * [  k- B' H' E) [1 EImageSets/Segmentation路径包含了指定训练和测试样本的文本文件
    2 W3 n5 V$ N9 x) a4 o; |# MJPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。. Z0 Y8 c' n, q* E, X
    2.3 可视化数据
      E* C) B3 U' G
    ( a, U4 @, }% Y: F9 c7 B, l; A$ K0 i定义read_voc_images函数将输入图像和标签读进内存。
    ' Q/ H& l& E0 I
    ! i- ^; O/ V, y, ^; L7 D4 `' Tdef read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):5 p+ u) u. S- c5 R4 F5 y" W
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    2 o( Z5 d3 q) q$ X, D, F" i: n    with open(txt_fname, 'r') as f:
    , X& e3 H$ ^, J7 I8 g        images = f.read().split() # 拆分成一个个名字组成list
    7 X- C3 i8 L! o  l: N/ C2 o. W    if max_num is not None:7 s! C  G$ O' A+ y
            images = images[:min(max_num, len(images))]
    + B. k. z8 J3 ]8 A' U. V7 q: W8 F    features, labels = [None] * len(images), [None] * len(images)
    . x6 v  N  h  w0 s- g+ {: ^7 Y    for i, fname in tqdm(enumerate(images)):
    ) [* {- ^0 Q3 L5 R  F# y* Q* t7 \- U        # 读入数据并且转为RGB的 PIL image9 e  I1 Z0 Z; ~  [1 ^% C
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB"); L- y7 t9 T7 C9 }' t$ h: T3 @& k
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")0 ~6 E# D- `( Z$ b; }$ B" r
        return features, labels # PIL image 0-255
    ! ?- r4 w/ V$ W+ L" Q; d. o8 S; r3 d
    定义可视化数据集的函数show_images
    8 D' y; H! w! i* n- ]  f
    4 N% q2 r  V  i5 g# 这个函数可以不需要
    4 R9 i$ B' \8 ?! \def set_figsize(figsize=(3.5, 2.5)):8 O2 b4 G5 h% w0 l+ k" g; `8 }
        """在jupyter使用svg显示"""
    - t: j7 s# j" p: r& T# X    display.set_matplotlib_formats('svg')
    : a+ J6 o3 E' c1 f  |    # 设置图的尺寸( I( Q2 G6 D9 Q- b
        plt.rcParams['figure.figsize'] = figsize6 V3 L4 T" M* O9 ]
    " S6 g4 v, J, K  M; H; e* x
    def show_images(imgs, num_rows, num_cols, scale=2):) g# ~+ A- X5 V" N+ n5 J6 u6 A1 C
        # a_img = np.asarray(imgs)
    * J! T! E" n6 D3 a, d( ]7 A: e; F3 Z; v    figsize = (num_cols * scale, num_rows * scale)
    ( `1 g- m' G' `+ Q    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)/ ^8 S0 m. s1 ~
        for i in range(num_rows):
    . z* b0 ?9 e: u' n, v/ u        for j in range(num_cols):
    ( {# D- l7 V3 j/ w            axes[j].imshow(imgs[i * num_cols + j])
    0 A$ j# c& s3 p; ^. }            axes[j].axes.get_xaxis().set_visible(False)
    " B! x# g3 M# }0 P7 `4 W1 {            axes[j].axes.get_yaxis().set_visible(False)" X7 g& ^6 X' e( A6 \1 \
        plt.show()5 _( ^9 m0 a1 j4 |
        return axes
    : {$ Y: j5 n# x
    - s) l2 x, H# B$ f6 E; P- e定义可视化数据集的函数show_images
    5 P3 j5 X6 i2 M- H4 m
    - w6 z) n# ?# M$ j, Q5 u: ?3 `2 C7 b- H# 这个函数可以不需要
    & Z8 Y; _, d# A9 O5 Zdef set_figsize(figsize=(3.5, 2.5)):7 m, p2 }5 A; W7 Z0 D8 }3 o) j6 w" O
        """在jupyter使用svg显示"""
    2 U3 z/ g. G# y& R7 a    display.set_matplotlib_formats('svg')
    : E8 Q3 g4 U* o    # 设置图的尺寸+ e) c. q2 O/ t4 H; a  k
        plt.rcParams['figure.figsize'] = figsize
    : c( W7 |& U( k3 k0 B$ K
    ! }; ^  F3 V% d* bdef show_images(imgs, num_rows, num_cols, scale=2):
    ( }- k5 Q* |4 s! I/ e- N% K3 y    # a_img = np.asarray(imgs)
    % w  b& }; F  [" G    figsize = (num_cols * scale, num_rows * scale)
    * q" j% `# Z& C) X    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)7 B+ F5 h6 y. s% j9 w& Z4 F
        for i in range(num_rows):
    ) h( v2 u9 \; [        for j in range(num_cols):" d# a  [  ?( p! C& \) S3 a) c
                axes[j].imshow(imgs[i * num_cols + j])
    / i: `) H9 T' \8 ~8 Y            axes[j].axes.get_xaxis().set_visible(False)* h, C1 E. o* M, B# R* F- m, _8 v
                axes[j].axes.get_yaxis().set_visible(False)
    4 D( }; X0 x4 P1 P% Q    plt.show()" ~: s' F. j7 G
        return axes
    3 p; M. H* t! W2 g画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。' M) M2 G  z8 x5 H' O& ]
    6 p2 ~" W+ w9 u2 B! Y
    # 根据自己存放数据集的路径修改voc_dir
    * ^/ G3 h/ v" H" a5 k, xvoc_dir = r"[local]\VOCdevkit\VOC2012"0 P6 P" A% }: T: q
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)  i$ {8 J3 o2 ^0 a% s, k
    n = 5 # 展示几张图像
    2 ^0 r) y7 l+ e7 ?# ximgs = train_features[0:n] + train_labels[0:n] # PIL image
    ' ]' m: V. X' a. e. nshow_images(imgs, 2, n)! e% P8 A2 ]* E# l: S' B
      {" C5 A+ z( L) f- V
    1.png
    5 X7 S% F9 W% [& L% C. L( m  ~5 k4 j9 N) p0 c
    列出标签中每个RGB颜色的值及其标注的类别。
      P# ~5 w  D# C' T% r5 E# 标签中每个RGB颜色的值/ m4 X5 N/ y1 b% e+ \
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    9 J2 j% p0 }2 k. `                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],! ~+ z# @" r. r" l6 V: O
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    * L0 T7 N# \3 x0 R& w                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],: |% R) m) S2 n3 n" J
                    [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    % m% Y' R' ~( b3 v- d2 l1 T8 L. i- x                [0, 64, 128]]
    ) J9 S, Z6 M3 U- |9 z# 标签其标注的类别6 Y- |, a' K# z1 q' Y
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',. q1 x/ x7 u+ M; k, y
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    # H' X; b+ x" M8 o               'diningtable', 'dog', 'horse', 'motorbike', 'person',. o+ W/ \: A' F: e$ A5 R; k4 o
                   'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']7 e3 A8 ^5 y9 y* W' d8 f5 }4 p
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    $ ^! \* ^$ B. {  S' j, Q9 g/ e; M% R8 @* |4 Q! n
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    0 w+ E* B! A. \4 D( j( H1 Ecolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])" s* K% j3 x8 Z; J5 E2 a; s2 i
    for i, colormap in enumerate(VOC_COLORMAP):
    $ i; j8 f% h4 ^& j( x+ T    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i+ W# o% m" N: [0 d: S7 w
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i/ A' d6 T; N3 q' @) H5 N! P% S
    6 ~& b  e4 S2 t8 i5 O" E
    # 构造标签矩阵
    4 H, V) |) d6 @3 Zdef voc_label_indices(colormap, colormap2label):. h. g$ O7 I& X6 B" q' I
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    , M( B: V: L9 U- X    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    3 Q' |; M# Y$ k) H7 b2 w9 ]    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    ( M. P4 T( n' l) N
    9 R/ R5 X- N! ?; _) U可以打印一下结果7 |4 I- ^5 {8 e

    " v! L( c1 \* e# p9 e3 l- c' M# Cy = voc_label_indices(train_labels[0], colormap2label): W8 ], w6 l3 J
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    & A+ g/ \# R7 e0 |" S3 s( B% V! J7 f1 X
    2.4 预处理数据& e% _* u9 j. L0 [9 _( N& G) O
    5 e! |) A3 }( [% ]) T
    在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。4 W/ ]: |0 q  }0 O
    * V3 u% z  _* e- C. A7 U
    5 M3 {8 f2 E8 [. y- v& i
    def voc_rand_crop(feature, label, height, width):
    3 M0 v& O) w) Z; D( Q& A) `    """! P% ^4 K5 y- l5 m* S% j
        随机裁剪feature(PIL image) 和 label(PIL image).
    7 ?6 k0 m" {: s    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做1 P$ [5 i/ x" Y/ Z3 @
        Get parameters for ``crop`` for a random crop.4 A6 p' f9 |6 c: j# L$ Y; l/ j
        Args:3 a  `3 X! y9 p! Q
            img (PIL Image): Image to be cropped.
    5 O& M% A! t. e6 Q) l        output_size (tuple): Expected output size of the crop.* D: y. |' r! P+ [4 A9 k5 U6 e% y4 L3 s2 W
        Returns:( Q+ f' T, [3 @2 l) Y' D
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
      }) c$ d, l: l: y9 J# J/ q3 ~0 Z' H9 M    """
    0 ]/ f2 @( x* m6 m5 r8 a    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    6 |; \6 `. |1 r    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)
    % m2 h/ G  z8 Q+ Q    label = torchvision.transforms.functional.crop(label, i, j, h, w)
    $ ]8 @+ c, s+ W0 N5 e    return feature, label! [! a8 H4 G8 }) {8 g. ?4 J9 b

    " B; k4 Q. b! y& i, l0 N# 显示n张随机裁剪的图像和标签,前面的n是5
    + t, h0 n: Q3 v/ H5 m( Nimgs = []! a0 M  l; E4 I$ p4 V
    for _ in range(n):$ o" j: O+ ~! g$ X' ]1 T& p" ^
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    0 g# T7 e6 p8 m! ~/ }$ j. R! cshow_images(imgs[::2] + imgs[1::2], 2, n);3 M% }+ z* A; p; \
      T3 V  c- Y9 z9 M. @7 ?3 K$ x

    & O! ?* u4 c1 @ 2.png
    ( Q6 H1 ^3 P9 r* ?7 T: e! \  C4 `# t0 J5 Y3 |4 G

    ; f% }& V2 k( h0 K: |  ]: P/ b# _% w
    3 自定义数据集类' ?4 i! t! b" T- x1 Z+ H
    3 K4 P6 o8 Q$ ?$ `5 ?" y" Q6 Q
    3.1 数据集类: }. A2 G; I1 r# S/ Q3 X( }0 |+ J  Z
    7 G" v, j6 U) B2 |  X
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    # g' I% h: |0 H2 ?5 \) g; s" ^; P' [# j. ?/ q/ ?, e
    __len__ 实现 len(dataset) 返还数据集的尺寸。
    # l' W  _7 l) z__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    2 U% Z! L9 f: H; B2 k( `由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。/ x) c- K7 n% s2 _; v" {

    4 M9 v* }# l* u) e  v7 Oclass VOCSegDataset(torch.utils.data.Dataset):
    ) p4 N5 |4 q, S! v    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):/ Q9 o! |) N9 t
            """4 m4 P: b' k) y7 q
            crop_size: (h, w)7 e8 `, Q% S" X4 F4 \# b! F
            """! `" n0 k. V. `# `1 S& F: y
            # 对输入图像的RGB三个通道的值分别做标准化
    # v2 s2 J$ N# b3 ^& J        self.rgb_mean = np.array([0.485, 0.456, 0.406])
    + z; ?4 F' V  \, d        self.rgb_std = np.array([0.229, 0.224, 0.225])6 h* m$ m$ g( }* D2 D' l& C5 F
            self.tsf = torchvision.transforms.Compose([4 ^0 f6 M6 @; j& u3 ~. u
                torchvision.transforms.ToTensor(),
    # K) ~$ E/ H7 R( K- I* a8 I            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    6 F( K6 h2 C2 }/ R' e        self.crop_size = crop_size # (h, w)
    - B$ ?. [; ^' F" [& V* `# s        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num), j5 T; a: u* g8 C
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    9 K* Q' ]7 d/ E, o3 b) W7 ^        self.features = self.filter(features) # PIL image2 W' o) F: F- X1 b. y8 F
            self.labels = self.filter(labels)     # PIL image
    . n2 N+ J$ N6 R  ~3 g; g3 j* Z        self.colormap2label = colormap2label/ K1 ?4 Z* q$ e" i+ U
            print('read ' + str(len(self.features)) + ' valid examples')
    3 ?6 c9 M9 F0 z# P+ S" M2 r. F% U* X! Z3 ~, O
        def filter(self, imgs):
    " S2 g& A7 H) R) U  X        return [img for img in imgs if (" Y1 W: z# U5 G- h$ U
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    8 O' i0 {% s. ]8 S* C! \9 ]; ^7 n# w( E' C$ N+ h( t7 m$ s# u
        def __getitem__(self, idx):( J8 u0 i- x. s; Q6 f
            feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)! p0 [  @+ t& v- ?$ c% g
                                    # float32 tensor           uint8 tensor (b,h,w)
    * B6 w! ^: r& C2 p; j5 r3 ^" @3 T+ o, G        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    . j, S7 K/ I, i
    ! B" b7 E& ?. K, L! k4 y4 F" s    def __len__(self):$ [/ Y/ `+ Y2 _5 k
            return len(self.features)
    : |, O: z5 x. F3.2 读取数据集
    , u& B8 T% \! g+ p( V
    & e% ]* d1 M! U  o通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。' q0 c' P9 \; Y* ^6 |

    - P  t3 {3 X! O. V$ t) E5 m! I& vbatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)6 Y0 }9 ?. |+ c
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    # X9 j. ]$ _3 i* Z- N( M/ t8 Ymax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~4 c2 L' [/ d# J# T2 f

    2 W4 |3 z" `0 s6 ~# 创建训练集和测试集的实例' N3 C) Q' e' u9 V5 O  V: {& I+ k0 H
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)! b& U) R5 g1 S$ u) c  {
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)! L3 H7 g6 L; [- I' ]
    2 R! |( @7 t" ^+ h0 z- K% R
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器4 u  L( n( ^1 ^+ u3 G
    num_workers = 0 if sys.platform.startswith('win32') else 4
    : L) ~1 a5 b& z3 S" Atrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,  s4 L/ r  q, r8 I4 j! |
                                  drop_last=True, num_workers=num_workers)
    2 s# F5 K; _$ M. |+ ]: X, F/ w  ltest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    $ R3 Q8 ~- p. {6 d$ ^: p" d. t, p( t                             num_workers=num_workers)1 D2 K# ^7 R! `& s- G4 ]8 O
    / b( ^# G8 M) H8 c: B
    # 方便封装,把训练集和验证集保存在dict里" {- G+ p* H/ s$ X
    dataloaders = {'train':train_iter, 'val':test_iter}( m; N6 d* W5 c: U1 P
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}/ I! E7 x: V) Y; d1 \! p( |

    6 A$ Z# l+ _; J$ q4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    ! k" F8 }; u" Z, c6 _, b+ G8 Q# u# g% V+ j3 R0 W
    num_classes = 21 # 21分类,1个背景,20个物体
    4 X2 y4 P' r; Q* l: c7 d0 Lmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    ' j3 r0 `& Y0 _, h" ]0 _$ ]7 q) Z
    / m: b9 X4 b9 z, D# 特征提取器
    2 D$ y% m4 d+ L0 B6 m' bfor param in model_ft.parameters():
    9 P/ d- A" R6 D4 [% L    param.requires_grad = False
    $ d/ v3 I) r. V: ]8 y, w! D) r: {: P4.2 修改成FCN
      b3 p5 Z5 }: ~- g' e( t  i8 ~* T$ |8 [9 Z0 w
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    3 }$ d' c$ b8 s8 }+ F最终输出的通道包含了该空间位置像素的类别预测。
    / W, I$ P5 s" q& T4 ?8 n$ M, ~, v% O# |' d5 d: U
    对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    4 \& W# x! N4 |+ \1 x; P% O4 q$ G: o+ z
    可以先打印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 倍即可。
    : Q' \) e" c# d; J' G. R. j" o7 E
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    ; V: Q' V" P" d( s% ~' l              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class8 L/ P6 Z+ ]5 j  p8 q# e! F4 B
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    ( T1 C4 Q$ C/ I6 W, P
    ( x+ A" V$ d4 z" l# _" _: U# 对model_ft做一个测试
    4 z% I( ?% {0 t. mx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据! H4 p$ [& U4 R- w! |6 W
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) ; K, k( F  D$ F$ G6 A! V. P

    8 Z$ p5 `5 m0 F" ?9 ], h# 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组( `7 A) [1 ~/ G  \) `: d2 Y5 D
    # for X, Y in train_iter:9 p+ y) c  L; U+ Q7 N5 `) F; K
    #     print(X.dtype, X.shape)0 p; Z5 p7 k2 u/ N
    #     print(Y.dtype, Y.shape)& |% J3 J# ^! f
    #     break
    0 u+ ]6 `* c) B: x" G0 d# O# t2 `5 T# Y% O8 m5 Y

    $ l& S$ k5 n$ ~& @/ d5 O* K2 u% D4.3 初始化转置卷积层) Q7 {+ x, K) d& W8 N. B3 `* j

    / n: a9 r7 r( N4 x在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    6 W/ B4 `4 K+ D* x6 p6 }+ N/ u/ s4 G在坐标 (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函数构造的卷积核的转置卷积层来实现。% D) ?  D9 e% T2 d9 N: u
    ( Q0 p# w% S1 Y/ j& C+ f: i
    1 j( @! J* y2 w- ]+ K
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核/ g/ Q( f! T5 P) i# V5 u* H+ K
    def bilinear_kernel(in_channels, out_channels, kernel_size):! f( B6 z) P0 H7 \5 d+ E/ e$ W
        factor = (kernel_size+1)//2# y& P0 Z: [" ~- G% |2 Q
        if kernel_size%2 == 1:" v9 W! z! F  @. b4 u/ F- p7 ]
            center = factor-1
    ) m' v& [9 i: G% c6 Q6 J    else:
    ' }$ @; m! M# p# n        center = factor-0.5, K0 b8 P" N4 a- w
        og = np.ogrid[:kernel_size, :kernel_size]
    3 X- x' l7 W; q5 T; }3 Z5 P2 Q    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    1 ~: E3 |/ z5 q$ I: R& f+ A    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')$ E1 O/ X9 H8 I; \* E
        weight[range(in_channels), range(out_channels), :, :] = filt- o3 f3 T: U$ E' ~; j5 T  a, P- H
        weight = torch.Tensor(weight)8 {* e- ?8 @+ r2 i0 z* W2 j
        weight.requires_grad = True4 Z7 ~( d7 @' J: s* f) e
        return weight; s9 U* O; r9 r' k4 q3 b% A. N/ t

    & D$ i( k/ M6 R7 G7 V
    7 E/ `: m6 `2 |2 ^# G在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    ) Z# O# R8 {5 t) j2 S- v- c+ {9 B7 d' X- O* H" j
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)! p, j1 i+ U/ U% w! Z
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    : d6 g4 }" D, k. i" N/ j. B
    * j, t" _3 G+ H! G/ o8 J3 N! C) u: N5 X; ^% c1 |8 s

    ( u2 _# d% }7 g2 R( w, W5 训练模型

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


    0 ?" A7 K  Q1 Z% L8 [def train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):" g* z  |9 k6 U" w) d( `
        since = time.time()9 L5 Q+ C5 f0 j2 a1 t' g8 S$ R
        best_model_wts = copy.deepcopy(model.state_dict()): y3 B: L8 Q% f: |( A* S4 y
        best_acc = 0.0
    ' ?6 @! K/ o8 C  O9 ]: k    # 每个epoch都有一个训练和验证阶段
    3 P" O% k3 F. w% J8 N  _    for epoch in range(num_epochs):8 f6 w9 _- l# a  m" _
            print('Epoch {}/{}'.format(epoch, num_epochs-1))2 V- j4 ~5 T9 L1 F3 s6 f' z# m
            print('-'*10)7 P7 L* p3 y1 V1 b: E" U: d
            for phase in ['train', 'val']:
    + u" p3 f+ Y5 a5 p7 c1 @6 u; L! X            if phase == 'train':1 I3 E( G% E$ Q$ `6 ?: q+ a
                    scheduler.step()
    6 C: ?3 Z3 E. L( G                model.train()
    - p. |4 L, w. @; ]6 w# v            else:
    * r! {' I( o( w0 M% }                model.eval()
    $ b/ L2 ]2 I4 E# |, }3 _            runing_loss = 0.06 E! V1 H: [! S+ O$ A6 u
                runing_corrects = 0.0- |5 k# x7 a) m' [
                # 迭代一个epoch
    : s+ t* L7 g9 ?; z            for inputs, labels in dataloaders[phase]:7 C+ e7 m2 B3 C2 j/ Q* M+ U
                    inputs, labels = inputs.to(device), labels.to(device)
    1 b* o( i/ \* W7 A, o                optimizer.zero_grad() # 零参数梯度" Y  v2 w8 s6 y* W
                                    # 前向,只在训练时跟踪参数
    6 _( C- ^3 `2 A' m" `  ]1 L8 n- L6 |* W( i                with torch.set_grad_enabled(phase=='train'):  g( {: C4 R. E/ |# r
                        logits = model(inputs)  # [5, 21, 320, 480]% C3 Y4 B/ N) e' Z
                        loss = criteon(logits, labels.long())& T! R$ x! B. H8 g2 k7 v+ H
                        # 后向,只在训练阶段进行优化
    % u0 V+ ]: Q, F                    if phase=='train':
    % ^7 f# j  ?, Q5 B# [                        loss.backward()
    + b  h0 E7 b+ r# J( \                        optimizer.step()/ V$ S0 q2 `4 v, y) f
                                    # 统计loss和correct
    / `$ n3 w( h# M- x% k. o, p                runing_loss += loss.item()*inputs.size(0)
    2 x' R& D. o, r) Z& R3 j( l, [0 Q; A                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    2 t! z0 p# `  P: m4 d5 f# c8 ^) @* S! h! j, s& Z% {6 z5 _% @
                epoch_loss = runing_loss / dataset_sizes[phase]
    % `; M; y& b+ M+ ~. S            epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    " A- v9 D5 j4 ~. L7 W            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    4 p7 [7 C% W" @% }, |                        # 深度复制model参数
    ) K1 U, _* R0 y1 O) N4 t: \& g            if phase=='val' and epoch_acc>best_acc:
    6 |$ Y2 J( b- E7 e) w" n                best_acc = epoch_acc: M: z6 V% \+ ]! K1 G2 z6 U/ d; G
                    best_model_wts = copy.deepcopy(model.state_dict())
    " j) V7 q, L" O: ^: U- p        print()
    & F* Y7 Z5 c, k$ B2 v    time_elapsed = time.time() - since;, y7 E5 q. N6 e
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))! j! I8 B. [7 S1 z
        # 加载最佳模型权重
    : n$ }: n, s% O" ?: @    model.load_state_dict(best_model_wts)( o  t9 C) _! o. O# r7 \) @9 L
        return model
    . K: |8 `0 W" b8 q
    " v3 e3 E( S& N$ |7 b3 T$ ]$ g5 y$ T下面定义train_model要用到的参数,开始训练& F5 {  a6 U1 Z8 j1 V. Q1 o
    8 t6 @1 v1 b# C. r
    epochs = 5 # 训练5个epoch
    0 E+ n2 C0 `+ _; V* w8 pcriteon = nn.CrossEntropyLoss()+ F+ _9 C" x3 B7 ~
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)5 e. V& X/ n7 F% I- Y( _
    # 每3个epochs衰减LR通过设置gamma=0.1
    1 S" [. C3 P$ ~# \0 nexp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
    6 d; o* N* f9 e- G7 G2 T9 L
    4 Q7 c" C+ p0 G  S1 O# 开始训练
    : j, S5 w' S3 B) w7 _model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    0 d8 G  T$ d" H$ _& p7 T. Q; k' ]
    6 测试模型

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

    def label2image(pred):0 C& _" p) A3 j! F* Z/ [2 @2 I
        # pred: [320,480]1 w3 i$ h4 \# n7 n" k( N
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)& s8 A7 e5 G2 T- |$ E
        x = pred.long()
    0 k- i# b0 E  W# Q5 K5 u    return (colormap[x,:]).data.cpu().numpy()+ S$ Y  X' z+ I! E6 ~7 Z
    - ]. L2 H6 ^6 h( Q% l

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    7 y% f5 w4 o8 U2 c2 j5 \std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    6 i+ a5 ~) h3 kdef visualize_model(model:nn.Module, num_images=4):: ?! q2 Y2 @' D7 ], N8 W7 `
        was_training = model.training
    . `" a( h+ C4 i0 |' _7 m1 t1 J    model.eval()9 W3 F) t6 T/ c2 r& V
        images_so_far = 0
      S! ]2 d* p# K* F    n, imgs = num_images, []
    * Q1 m0 E+ x  r$ L' c    with torch.no_grad():; J) g  T( ^4 }$ l# H
            for i, (inputs, labels) in enumerate(dataloaders['val']):$ r! R; R& j* Y
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
      w; }* n' J+ d3 N6 `& w6 y            outputs = model(inputs)4 i: g0 s  Q& r* \6 \
                pred = torch.argmax(outputs, dim=1) # [b,320,480]" q" C9 o8 c; w: I# z$ Q- p
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    6 E+ R" V' ?: j+ X. V+ o: f& C5 m7 I8 c, {; W' K
                for j in range(num_images):& D: G/ y% E! A7 c
                    images_so_far += 1: P! _& K# W( w: Q9 r9 Y. ^
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    . w+ i  e5 K5 H3 j) }5 G                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]! }/ l8 A1 a" K( F
                    if images_so_far == num_images:
    2 A0 P5 l) ~9 T% q# N  N& d                    model.train(mode=was_training)$ ?9 ^: @1 R& Z4 A8 g  O" K3 h% k
                        # 我已经固定了每次只显示4张图了,大家可以自己修改1 q: Q8 B4 _1 P
                        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    $ j# |) F( |7 T                    return model.train(mode=was_training)
    ; b3 p( t3 v1 N% g
    % H5 D8 t5 H$ p8 [% V# t# 开始验证7 h" L. D" y0 s5 g. L
    visualize_model(model_ft)6 _2 A. ?. G* c* \' b5 s
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor* g  p0 X) i! c& e9 }& ~
    def predict(img, model):. ?/ G- Q  ?" y+ X; f4 E! w% M
        tsf = transforms.Compose([
    0 i* r; [7 d& _2 P2 C            transforms.ToTensor(), # 好像会自动转换channel5 o# b: w  \/ }% S& H* M
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])+ Z3 T: K) Z2 o8 `4 O0 \
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)/ w8 ^' o( r' ~# ^
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)! E, v, q7 x& w6 R% s' e0 O$ w
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    " [0 a! U3 N. X3 e9 \' s8 K0 @
    : Y3 t$ o, |- _; f, _5 a9 |def evaluate(model:nn.Module):
    % v* N5 x! f0 |/ H    model.eval()/ w. [* w' N/ P8 H* K6 G. w5 {  c. y
        test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    ' M: _+ x5 I8 y5 P( K    n, imgs = 4, []
    " M0 T3 o% B1 E! M2 F, n$ m7 J    for i in range(n):" y* E; @1 v7 @( p4 f
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    2 O: o' s$ I- m        pred = label2image(predict(xi, model))
    * x, ?2 N+ q+ o# i: v; X$ H; }# j        imgs += [xi, pred, yi]4 G  L; A  W" N* Q0 {7 G  t- F( Y6 X
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    % j9 N* O5 a& c# X- x1 Z( n+ a( {0 r( D* e/ s/ h0 _4 C: _
    # 开始测试, N8 I6 l4 e0 }
    evaluate(model_ft)5 K% v" `1 Z9 K6 [& X( ?

    1 b% v+ k$ e3 C, R% c7 结语

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

    1 c* [$ ]9 I1 D' r! M
    Epoch 0/2
    6 ^# ^* a3 K" L0 }----------
    / K8 l% I' H9 U: J9 t: otrain Loss: 1.7844 Acc: 0.5835
    $ O* a2 Z0 {) U; r3 H( g8 k7 K* `) \& tval Loss: 1.1669 Acc: 0.64565 T! L' W% D  ^6 W+ R

    ' i) C) V5 A* q4 }1 dEpoch 1/2% f2 X, V; j5 r  ~- Z. S6 m
    ----------
    ( u; y& @7 W( Y. f. \% mtrain Loss: 1.1288 Acc: 0.65356 k5 `6 O* |* u0 {5 X
    val Loss: 0.9012 Acc: 0.69292 G. m% @/ L( o( H  X& J+ ]$ w) H
    7 r* m+ w7 M8 h3 U
    Epoch 2/2! E9 k" F. g5 Y: P; O
    ----------
    6 q+ K  ^# Q' j3 ]# wtrain Loss: 0.9578 Acc: 0.6706
    9 o( {' A8 I6 M" A. _& c* Zval Loss: 0.8088 Acc: 0.6948
    9 A1 C, l! h; K4 s9 H3 `! |, {8 G
    Training complete in 6m 37s
    - a& L0 w' u4 n: ^7 z$ E5 z! N' h& A+ G' _( T- V0 V# L

    . g- _' f9 Y/ u0 {3 d0 S+ ~" c 2.jpg
    2 F" X+ x6 E% B+ s) i* T% l9 s9 N
    & m" w. Y6 |2 G) A% ?: U9 `当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。2 \, C4 r9 K6 U: S( k( f0 }! I
    & d7 a; W% m. B# z' O: `) _. o: j
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。3 b( ^" n, N; V7 S' \% J0 `1 N  g
    : @- ^% p. u" b0 I

    8 y7 O  S  v- X0 R语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:0 `: M0 a/ ]  ?- ?) P
    0 O, e& X; \4 ]. j( e6 ~. B' g
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]0 i: t! G) y) t! s  m6 u8 K
    GCN 通过全局卷积网络改进语义分割[论文]% r* P. G2 n' [2 e
    UperNet 统一感知解析
    / p9 W% K8 D0 ~2 GENet 用于实时语义分割的深度神经网络体系结构[论文]
    9 `5 E4 J% p9 _( G: i/ U* e% b+ kU-Net 用于生物医学图像分割的卷积网络
    ( O- D. M8 T$ OSegNet 用于图像分段的深度卷积编码器-解码器架构。2 I: I6 W1 `' t1 `7 u
    还有(DUC,HDC)、PSPNet等。7 p3 ~- u" v  e! u9 @' H

    ; m( d3 ]! S1 q6 }) s- u常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    2 p* \+ {8 o5 U4 x7 V* I4 ^3 f9 P. I7 V& O" L
    对于损失函数,除了交叉熵误差,也可以用这些:
    / L% ]6 \  N+ R9 O$ B: f5 Z2 J2 s! {- A
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。6 Z, q. z3 x0 B! T8 h
    CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。: E$ q; P5 u+ _3 _' d9 s
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。, _9 j: v5 J: x( ]" G9 K
    Lovasz Softmax 查看论文:Lovasz - softmax损失。
    5 c: t. q" g6 p! e" _
    ; A7 D9 l9 I9 N5 E- c! O, H% o7 y1 v1 f6 F

    : g, u: U! w# u. {% [
    1 j6 O* U" G( V/ {7 D& S2 H( I& m# c, s5 B) M7 ]% g
    ————————————————
    $ K/ |( |! c1 v5 C. P0 d+ h版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。* k8 U* L4 Q% u/ @2 }
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507  F5 O4 r" m5 I, E/ h+ f& K
    1 q, F4 y; I; ~6 x! n
    & o9 z, K2 N, W" P5 \
    ( }0 K' B: {3 X5 S6 u7 Y
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2025-8-17 02:20 , Processed in 1.212720 second(s), 54 queries .

    回顶部