QQ登录

只需要一步,快速开始

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

    # H/ r) p6 F  Q, h* ^  x
    7 J1 N& F% \$ i  VPytorch实战语义分割(VOC2012)
    ) h4 _- v% j! x0 y本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。$ b6 U# H4 ^$ V$ q7 F
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    1 m" z/ r: P9 y8 z2 I( \2 ]! L* g( j( _. x
    5 m4 i/ d. W' Y% l" P; w! E
    语义分割中图像有关狗、猫和背景的标签  F* d$ ^" s: `, y  `
    文章目录5 t, u8 l0 C$ B4 [+ ]+ F
    1 }1 S. k% ]* d% r  E- i# q4 s* G
    1 图像分割和实例分割8 [) Z+ j4 B3 J4 n% x
    2 Pascal VOC2012语义分割数据集% j  F( e5 k/ ?' i5 o
    2.1 导入模块
    7 v6 P& R3 ~: C! N; n* a2.2 下载数据集
    % f% N" T) n1 k2.3 可视化数据
    & \" W* d6 K7 U! I% `% w* f2.4 预处理数据
    , r! [; f0 R. Q$ c3 自定义数据集类% R7 E+ B. @/ q* \* d$ e
    3.1 数据集类
    : o8 M# x9 Z/ x( K* [; n$ f3.2 读取数据集
    7 k. u! R2 `! Y6 X$ Q4 b$ H4 构造模型
    . \; M4 b; y+ j$ ], `: J8 Q4.1 预训练模型/ }; r9 Z" z0 N6 Q: k  k9 S
    4.2 修改成FCN
    6 y+ p/ Q* u0 q7 Z. N/ P4.3 初始化转置卷积层
    $ g2 S8 r! U- v- f( b. d5 训练模型
    / n$ ]! _. Y) {5 n& y7 l' Y6 测试模型
    ) a0 x% m& _4 b  z+ o5 g+ i( q6.1 通用型
    ! F  Q- I0 g8 g+ C6.2 不通用2 u7 d+ a" G1 l' N
    7 结语
    0 f! Y/ t% u9 X4 D- y1 E1 图像分割和实例分割( h7 {$ e0 p' b, [

    ! }- ~- i) b5 p5 r2 l  e计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    ; F3 m& c/ t% l" `; M) B: G0 @- ?" T- j' ~6 l$ y% `
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    7 O; l1 j  B) ~  j' O8 O) x$ j实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。
    " ]0 k+ x4 d7 h: z8 p6 c- T. Q/ `' ]3 b) q& W8 N
    2 Pascal VOC2012语义分割数据集. x% {. {$ O, r" z4 E
    + {9 ^4 I; [& D' s4 J) _* N
    2.1 导入模块
    - P0 F& b! e. Vimport time
    $ U% {/ b2 s1 [, W- H+ I5 l8 Zimport copy
    5 }: b( z) p* R8 L+ a& \6 ^import torch
    6 q6 o" q5 e! |7 \9 mfrom torch import optim, nn
    % y" l' P/ t! N8 D- M& Ximport torch.nn.functional as F
    ' u; h! M8 u) n  o  ^% w7 x3 x& bimport torchvision
    # c$ w: L, }  B% Y! I. p" M% v# zfrom torchvision import transforms9 E2 V/ j6 ?- ?  z
    from torchvision.models import resnet18
    " i1 B  v$ X0 e# qimport numpy as np
    0 K* y$ ~: ~2 r9 bfrom matplotlib import pyplot as plt
    , H% }& ~8 I& e" bfrom PIL import Image* h+ W7 C$ i' f! F  n, g" _
    import sys
    " M/ R$ E' p! W6 Ssys.path.append("..")9 M" q% }' u4 U3 N' T; X
    from IPython import display
      r5 Y4 Q2 e; F. W( Ufrom tqdm import tqdm# S5 y" ]& c" r. x. Q
    import warnings
    7 u+ s- \- E8 K) z. ywarnings.filterwarnings("ignore")% U8 _8 U2 }! V4 S
    - a3 _" M6 M3 L1 L) r( x4 w
    2.2 下载数据集' t! o, r8 T6 G3 m: x' H

    ) T9 g- t" t  [# W. x语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    " ], D- \* m+ B5 a( M8 l- _
    ! z; A% A' W  q/ O" U% J
    # l8 `3 h4 ~1 N2 G$ HImageSets/Segmentation路径包含了指定训练和测试样本的文本文件) T; ~  ?! D  w
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。
    : y7 O# m1 l9 G2.3 可视化数据6 R  A3 f3 W0 F1 U

    6 ^" n. D/ g+ A6 Q6 m: y5 \定义read_voc_images函数将输入图像和标签读进内存。
    / P: p% K( \+ H) I0 j* S0 Y6 {- I# d% H* T  }/ L
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    ) G( M# _- z& \' o0 X1 }    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')$ M! t# s2 R, r3 M; H5 R$ D
        with open(txt_fname, 'r') as f:
    % w+ E9 }9 \) G7 I* T9 ]1 g        images = f.read().split() # 拆分成一个个名字组成list8 e2 U+ l' X+ @9 e% b# M& Y7 Y7 ]
        if max_num is not None:
    3 ~# i$ R5 G$ o/ D        images = images[:min(max_num, len(images))]% q' p( f1 U7 M& q; O$ o
        features, labels = [None] * len(images), [None] * len(images)
    5 I* y" `0 z5 C8 V    for i, fname in tqdm(enumerate(images)):
    3 N: q8 p4 J8 P7 a        # 读入数据并且转为RGB的 PIL image$ \8 j/ d6 ~& w7 x& r
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")& c$ K' G8 ^, D2 J& u) w' h
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")9 a) V# J3 [; P  Q0 {
        return features, labels # PIL image 0-255# @4 S/ z1 e$ c! O. r6 e6 P

    " L6 I' _- M9 ]* k* R2 T$ |定义可视化数据集的函数show_images
    # J* `% G; n( o  H9 A* r
    ( R5 p/ P6 L/ ]5 f3 K+ @" z# 这个函数可以不需要  _! p& c5 L/ n6 B4 ~7 ~% ~
    def set_figsize(figsize=(3.5, 2.5)):; g% H9 Q+ I+ `$ K
        """在jupyter使用svg显示"""/ [. R, X- Q& v: @  Q7 H
        display.set_matplotlib_formats('svg')$ ~+ U, E% ^4 {, j: Z; j( ?: X
        # 设置图的尺寸
    ( v, q, W7 l& }1 X2 d    plt.rcParams['figure.figsize'] = figsize4 M: s- M6 P, D" a. b) w
    # {5 w2 m1 C" l8 c9 V
    def show_images(imgs, num_rows, num_cols, scale=2):
    $ M7 y8 ?0 d7 I    # a_img = np.asarray(imgs)
    . h5 u: d3 g! i8 J7 A* ?5 G    figsize = (num_cols * scale, num_rows * scale)* I8 ?6 \7 K4 h7 [$ {8 M1 g! \
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)2 d. |: f, T" B1 @; x
        for i in range(num_rows):) ]" c0 y" U3 _* c. `4 C
            for j in range(num_cols):3 ~# m+ v$ h# i) F, Y' i
                axes[j].imshow(imgs[i * num_cols + j])5 @3 ~, ?+ m. b+ r7 `
                axes[j].axes.get_xaxis().set_visible(False)
    ; V7 I7 E7 Q7 ^' Q6 \# T            axes[j].axes.get_yaxis().set_visible(False)/ r5 H% o: h2 J  T2 _* D9 i
        plt.show(): p& o: b' o1 k6 ^; T, ]
        return axes
    1 v! l( B% L! B0 g3 \3 Z0 s5 r; N( ^% x
    定义可视化数据集的函数show_images
    1 l5 T: J' ~& j8 H7 J0 }9 s5 I" {' q! V$ @  _, M, Z. U9 d3 p5 V  S
    # 这个函数可以不需要
    - ]# [& K1 ^5 h( @( G. `9 Vdef set_figsize(figsize=(3.5, 2.5)):! j5 `# E  f- t* G" M! J) ?
        """在jupyter使用svg显示"""
    - e4 O" z! \7 m/ D% X    display.set_matplotlib_formats('svg')0 U5 ^# @5 G8 \6 l# a. d. q  ~
        # 设置图的尺寸
      q6 \, F7 C. }' u. @: I! i7 R    plt.rcParams['figure.figsize'] = figsize
    . ?  q4 f1 e4 m- ^/ c( u; ]3 |. t- D6 C' N0 p' O
    def show_images(imgs, num_rows, num_cols, scale=2):/ M/ z& p: k, ]) u3 M" y5 l* l
        # a_img = np.asarray(imgs)
    ( Q5 ?* d3 [1 e    figsize = (num_cols * scale, num_rows * scale)2 }& f0 S: X6 G! |
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)# Y7 [: V2 [4 c0 z0 I" f
        for i in range(num_rows):$ C9 j; z1 Q- y* s, u  }
            for j in range(num_cols):
    ' `* I- K: b: |& N9 Y: Y- ?& B) s            axes[j].imshow(imgs[i * num_cols + j])
    * p1 A! p5 p/ |8 W            axes[j].axes.get_xaxis().set_visible(False)# |# e- A+ k% N2 u, V
                axes[j].axes.get_yaxis().set_visible(False)6 h0 r7 d; ^' n/ L
        plt.show()
    ! ?) s: o) D$ C& ^8 Q( |    return axes
    ( ~) w$ }  L' j. M1 K! c画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。
    , N7 w: R/ W$ ], w3 b8 F
    7 ?3 Z5 w% V, O/ }7 b6 X  Z# 根据自己存放数据集的路径修改voc_dir
    7 m7 Z+ O" U* {5 T, {voc_dir = r"[local]\VOCdevkit\VOC2012"3 Z! s, B: J9 ]% U' d. i" F% v/ T
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    * ^: M* V7 f0 T7 B+ c  r! bn = 5 # 展示几张图像
    ! g- Q& O: Q" C2 m: wimgs = train_features[0:n] + train_labels[0:n] # PIL image$ p  f( u6 x3 k4 r6 y( j# m4 R
    show_images(imgs, 2, n)
    + l; A6 a: |$ ]6 n  n7 b) w. T
    : B! i9 q6 i% O) R' R 1.png
    $ G" j: J2 x" V  a5 h7 E+ H# A9 @9 X) D
    列出标签中每个RGB颜色的值及其标注的类别。
    # c+ _$ l' L6 E* D5 k4 |2 X' C# 标签中每个RGB颜色的值9 c: P. k5 U  e0 H0 M7 V+ e$ I+ m
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],0 [' f9 T' U% T; r* O
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    $ }# V/ [3 D# S5 b# N  U# X                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    # v- q: K. f; T8 M" x                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],$ c; j1 k- {  y
                    [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],8 y; J5 Y" |; J; @
                    [0, 64, 128]]9 p% F" S# P* _8 Z6 [" \
    # 标签其标注的类别
    , }7 i5 a/ S& B! W% ?" S4 _. iVOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',5 q5 q. R! S" g
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    & Z. \0 V9 u6 U% @2 |$ V0 @- ~               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    ' X: G9 o3 t9 ?. R! i               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    " e7 o+ ~( V8 d8 G1 t; R有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。) z$ y0 |1 {' T$ W) W
    . ?! p: a6 U+ i! l" R* P9 _! f
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    . E, A$ p, B& l3 c. k- h; Z& fcolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    4 i! O# m0 Y6 Hfor i, colormap in enumerate(VOC_COLORMAP):
    9 I: [9 x) I* T. G    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i; G7 D4 S& H# e3 d1 B( n6 l
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i; W7 a0 h5 Q1 s  X4 u9 ~

    , X* d( a% B( }+ B/ t/ f# 构造标签矩阵2 ?: z  S. X  w" D. m( M1 }5 z
    def voc_label_indices(colormap, colormap2label):) \* R; F7 G& {: _* l
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    / k- u2 E& ~4 K& e- b    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) $ W; ]1 W5 z/ o! A
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标" r; y$ `4 E) o3 M( X7 T

    * m/ Q# k; j3 O4 h: f9 q0 e  j% Q% h可以打印一下结果
    ! H8 Z  d5 J- h1 P  ~7 J
    & n" u  Y! l0 _4 A/ w; Yy = voc_label_indices(train_labels[0], colormap2label)3 p% j7 d6 O2 B  I
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    6 i# ^3 b0 [5 [* S  Y7 d1 b" Q# w# h* g
    2.4 预处理数据8 ~" |/ W4 w+ L3 M4 o( v

    , m% T, i7 `4 v# c# ^, C在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    - o, Q( \. Y" d  F5 o" t( W3 s. y8 P+ L/ B& s6 I# k

    ) u" b3 d1 V: ?* g8 Odef voc_rand_crop(feature, label, height, width):
    $ T8 Q8 n8 D7 D7 i3 Y7 K& r    """
    ) K& p! a, o7 S. a; |( s    随机裁剪feature(PIL image) 和 label(PIL image).
    2 m6 x' v, @6 N3 A$ C% o# M    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做( E/ o# B4 n: ]; Y  r1 k
        Get parameters for ``crop`` for a random crop.$ L, s& g2 h6 {: T
        Args:
    7 k5 I, A0 ]1 T. I) `% b- m        img (PIL Image): Image to be cropped.( @; A7 y. ~& U7 B; \; B5 g
            output_size (tuple): Expected output size of the crop.; F2 s! `4 O, C) N  s- k9 m8 {
        Returns:' h/ x: j7 h# v& w6 H6 J8 m
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
    5 @' Q1 m& L/ l& N+ P    """
    + W* T" }- z  s8 u# f& r/ X    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    9 ]% ]* W  X: b+ X6 N    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)3 S4 O% V; b, K+ c0 K: K9 ]
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    + |7 _9 ~# E6 L7 b    return feature, label  v9 q4 n: O! _" L: S; ?% }
    : e: b+ ?4 Y" c0 u1 g1 B8 y" i  D  i
    # 显示n张随机裁剪的图像和标签,前面的n是5
    & j0 ]3 E# w& v# M& @/ Yimgs = []
    ! l- S1 M5 ?6 S& ?; s( Ifor _ in range(n):
    # {9 J4 D% J, ~6 @% F    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    ( F' c& \) o' L3 H1 l7 d- Vshow_images(imgs[::2] + imgs[1::2], 2, n);
    ( a* y) n& i' C, I2 S
    + C- W7 t6 e5 x+ T6 W( I
    5 X8 @5 m" K# m* b+ R% h* ~ 2.png & K/ g0 v6 k' k; Z& g. p+ ~

    ) K. F/ R1 K) A- N: t. V" B5 V  R+ m7 x$ [' i$ }2 @* f
    * y& a5 h) a3 }) t5 K2 ?! G
    3 自定义数据集类
    2 D+ m1 S+ y& T) B& C
    1 E. C! y* @: l% |) N" x# \# x3.1 数据集类$ b, f: r; J) l/ c; ]$ O

    8 ^5 s3 [5 r3 R: s" t( \" qtorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    . v% s6 M: p# [3 t5 G4 \4 D
    3 k) F$ x* r/ X7 ^' I, Y__len__ 实现 len(dataset) 返还数据集的尺寸。. [' m4 i( `9 E
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    5 z& _) I: r" g- R由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。, C+ p) ^8 J; N! }; R0 Z( U7 a! q* i

    + O5 r6 A" a/ r9 D5 h6 oclass VOCSegDataset(torch.utils.data.Dataset):  r8 J3 h0 U4 d7 b/ x
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    3 i& p# u- M' R7 u0 Z        """
    ' U0 T5 V: g: j  m1 ~        crop_size: (h, w)
    2 P' ^5 B6 u5 H' R$ S3 g5 n        """: L5 l% D; Z4 p6 b6 m
            # 对输入图像的RGB三个通道的值分别做标准化
    7 m. q" z  F) d0 |8 `$ j% y$ ^0 w7 h        self.rgb_mean = np.array([0.485, 0.456, 0.406])
    : a$ `" v2 R3 U; ~  E        self.rgb_std = np.array([0.229, 0.224, 0.225])+ \3 X. z1 Y$ Z( S
            self.tsf = torchvision.transforms.Compose([
    7 O  i+ T& ]- q7 V1 g+ g; F            torchvision.transforms.ToTensor()," h/ Y! l1 V( c+ u" n
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    : e) T4 }/ h9 N0 ~3 k' D        self.crop_size = crop_size # (h, w)
    ; I  V% l$ u% j        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)
    8 [: }5 U1 |# [9 f# 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    $ t0 {( M( S$ d' u$ V        self.features = self.filter(features) # PIL image9 C. H6 U& k1 b* v
            self.labels = self.filter(labels)     # PIL image
    ! Q, U/ S; J) L3 h; P/ R/ Y5 R9 o        self.colormap2label = colormap2label* I) W& w- R, ^; B* w
            print('read ' + str(len(self.features)) + ' valid examples')0 v2 X; I/ o3 i6 O+ u# q* F
    ; E& `# s+ D: T5 b
        def filter(self, imgs):6 u4 O" [8 S) \, d% \
            return [img for img in imgs if (9 u1 \$ k1 G3 d: R' Z6 x+ r
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]3 j' D4 l5 y' q( U' k, \# l
    5 h! U5 ]6 n5 o% B: I, |1 }! n
        def __getitem__(self, idx):
    6 t& C3 j8 \. E/ N3 H" X( \        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)7 }) h; Z* N/ r
                                    # float32 tensor           uint8 tensor (b,h,w)
    * i. {  B% f* |        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    % D, U. B/ X$ ^$ |5 @  M: W7 ^4 ~: P% `* f8 r
        def __len__(self):/ h- g8 G1 W" \& Y/ `. X& v  k
            return len(self.features)( L6 L9 C, K2 O. B; ~+ E  a9 O
    3.2 读取数据集- n: h( u; i+ t
    . B' e9 m2 r( N1 O
    通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    1 o3 b" l1 J% W8 h" B0 Z1 L! y* b3 c, o6 ?+ ^; _: ]# P& A3 @
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
    ! b% T& R& o2 y$ w. {* rcrop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)" x! N! U' Q' q- d. b9 i
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~" A% m1 i  Q; h) o

    4 u/ j+ T; t0 G$ w# 创建训练集和测试集的实例1 u& s' V* ?* i8 m3 s
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)4 b% B' v" L2 g
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num); w0 c0 D$ p" C! U* `3 ~

    4 ~. R, Y7 u# N8 m# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
    9 ?: h9 v) L2 Znum_workers = 0 if sys.platform.startswith('win32') else 4) I* i2 H8 J) ~
    train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    , I& N/ w) Y' z, d9 }                              drop_last=True, num_workers=num_workers)* s' O0 V' P# z% F7 _
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,( B! D8 g! J$ |: C5 T' z3 u+ l
                                 num_workers=num_workers)0 a0 [- `! q' C. M8 G, x! d3 o
    % ~3 ?* M, s+ f! [0 h8 X4 d4 [
    # 方便封装,把训练集和验证集保存在dict里; s* w# l& [! q: ^$ D1 b  L, T
    dataloaders = {'train':train_iter, 'val':test_iter}" u+ n+ Q, @2 k" j$ e
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}; Q, i& L; N  o$ N2 F

    0 j! O( e" @! w9 g: O! I4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')7 r/ K9 L# i6 m1 p8 k( N4 ^9 p8 ?
    * d2 c5 _& m0 H, |: L$ n
    num_classes = 21 # 21分类,1个背景,20个物体+ [* [  A" e, `1 s; n4 [% u
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数- b) E6 p3 C: j6 O- d/ V- G

      W3 O- h# a" W; T# 特征提取器
    : ]3 v) g6 W& H6 ufor param in model_ft.parameters():
    6 |0 i: H% ?7 V7 U    param.requires_grad = False
    : e9 W/ s( x! H( I  w/ A4 f4.2 修改成FCN; @$ o: Y2 k- t6 z. @
    1 l* ^: w, {7 n% s4 i0 T; z4 l2 ]
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    - e: ]5 j' I! x4 v, v" Z9 a5 [7 {9 j最终输出的通道包含了该空间位置像素的类别预测。
    ; H4 x/ E& X# N" s7 n
    - _) h0 I) E, h+ t  t对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。( k* k" S" k( q+ {" h3 q

    2 B1 V8 L1 U6 y7 B9 m可以先打印model_ft,可见 ResNet-18 的最后两层分别是全局最⼤池化层GlobalAvgPool2D 和 全连接层。全卷积⽹络不需要使⽤这些层。通过测试,当输入图像的 size 是(batch,3,320,480) (batch,3,320,480)(batch,3,320,480) 时,通过除最后两层的预训练网络后输出的大小是 (batch,512,10,15) (batch,512,10,15)(batch,512,10,15),也就是 feature featurefeature 的宽高比输入缩小了 32 3232 倍,只需要用转置卷积层将其放大 32 3232 倍即可。
    ) {# ?, R- |3 S
    " Y7 n4 S& a+ K- Q/ ~' _: zmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    ) s# z, C: _$ l  V9 x3 j              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class4 J' w: R1 p* {
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小9 K5 [5 j2 k" e# \! g
    & k4 E% c/ h4 l& ], E, n
    # 对model_ft做一个测试
    # e  A3 J6 {! s- |x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据  }  j1 T- }5 _: @0 s& l9 V3 t
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    3 @; C0 q. V% l/ x7 ~0 z+ ], k9 l, M0 E
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组" u4 K8 T! g; X6 Q' h# ], m
    # for X, Y in train_iter:
    6 R3 l7 V- l: H6 u, u#     print(X.dtype, X.shape)
    ) u5 K' [8 J6 C) I7 H7 S#     print(Y.dtype, Y.shape)% ]$ r% y5 S) u. S# N7 r
    #     break# r  Y$ p7 a# Z% y# |

      A- f3 D2 f& T, C7 m/ o4 ?7 t. V- r- u. Q  u
    4.3 初始化转置卷积层
    5 x' I9 n- C/ n
    ; |  Z$ y9 z$ |% o& c' q在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    % z, g8 ~: _% M8 q" T/ V  a$ d  t在坐标 (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 B0 ~9 g3 ?6 `0 d7 b1 f

    ) V  O4 i# i! Y8 O! H' g% d0 v& t2 d. ?; P0 V9 a& ?. L
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核
    ! \9 Q* ~* h" T6 l6 Sdef bilinear_kernel(in_channels, out_channels, kernel_size):6 B+ O- L9 e: y* _! `4 ]( M
        factor = (kernel_size+1)//2
    + ?! r9 @7 z0 t' W3 \    if kernel_size%2 == 1:
    # M. y/ x; p! n  E7 F. _        center = factor-1
      U! g) S! h* t) D8 t4 `9 x9 D' Q. S    else:
    : e, x, N6 Z' w        center = factor-0.5' A7 Q; W3 }, h
        og = np.ogrid[:kernel_size, :kernel_size]
    " X) [- g6 h0 x) m4 `    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)/ o+ x% F0 \6 S2 v
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    9 U( o9 x2 R0 p    weight[range(in_channels), range(out_channels), :, :] = filt+ O/ X7 x$ v) N
        weight = torch.Tensor(weight)! k1 \% H1 }- C2 g8 ^
        weight.requires_grad = True
    % C- d4 n# B# v6 ]; Q" n    return weight
    % F2 Q0 s+ u' |0 _1 O5 J
    8 r. m! o) ~0 D5 `
    $ q6 f" [" o. D1 N+ o4 y# @7 _6 x在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。+ d* t9 j4 X- M5 G) Z
      f  T. P0 r: n4 o3 K* H6 r- j
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    1 N2 k" s' }. x4 l' T# F7 ?! wmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    ! S8 v+ v& N) @' a5 B2 _/ ^. I2 R( S' x9 j$ P

    4 b8 }7 G  }; p* M+ {5 R4 |" o! L+ \8 P9 t# V" K' C7 ?
    5 训练模型

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


    . G  h& b4 j- _; jdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):$ b. }! w% E" Y. e0 d
        since = time.time()
    2 L  C+ n' U( n0 J    best_model_wts = copy.deepcopy(model.state_dict())
    - c# @- ?  f7 f0 b! C9 k' a" I    best_acc = 0.0
    : |$ r7 q; M  C8 o    # 每个epoch都有一个训练和验证阶段  e# q5 v: S$ R- M- C) r5 K# y
        for epoch in range(num_epochs):  s1 U' q( p& v# }
            print('Epoch {}/{}'.format(epoch, num_epochs-1))* z1 f& A; f6 j6 g, d6 [, I7 V
            print('-'*10)( R' ?. L$ G1 z4 W
            for phase in ['train', 'val']:: l1 N! l+ `; O) S  J
                if phase == 'train':
    % _, I# u  V4 C1 y& u' j$ |                scheduler.step()
    $ C  `% x# O2 B- t8 K) ~+ O                model.train()
    8 X4 Z+ Q2 Z/ f3 O; d. i9 l2 h            else:" ~  Q. D: L) K. ]% D
                    model.eval()
    # T  [* J. s9 G, A# s; G            runing_loss = 0.06 z, R7 I0 ^9 {4 ~
                runing_corrects = 0.05 j# t+ l# O3 T- p3 h
                # 迭代一个epoch- J: ^' u- I9 P) E9 {
                for inputs, labels in dataloaders[phase]:# E* t1 l+ s4 _2 c' L* t
                    inputs, labels = inputs.to(device), labels.to(device)  g, F# I( w( u( s# J- W" K3 `
                    optimizer.zero_grad() # 零参数梯度  z1 Z7 v* Q$ i( x6 u/ z9 h
                                    # 前向,只在训练时跟踪参数
    7 v8 r, l7 h) j, U6 u                with torch.set_grad_enabled(phase=='train'):+ m' j1 R7 I# d, B8 T
                        logits = model(inputs)  # [5, 21, 320, 480]
    / n2 y8 L1 U8 p. y                    loss = criteon(logits, labels.long()), c, L, K' Y6 U
                        # 后向,只在训练阶段进行优化
    6 s; d3 Y: _( p! W: f9 ~                    if phase=='train':
    * f+ r( X' n- {                        loss.backward()- N, K2 A5 [9 z+ C
                            optimizer.step()
    - P. S, @9 @- ?                                # 统计loss和correct
    5 H0 ~7 O1 T( o                runing_loss += loss.item()*inputs.size(0)
    3 J. G( y% T8 A: V* x4 ^                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
      d+ u2 ~8 U4 ~, L8 m
    ) d7 e5 d4 W- y            epoch_loss = runing_loss / dataset_sizes[phase]( @5 e- h! W+ c7 l, i' N
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    0 Q: Y* a. Q  `            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))8 V1 ]7 R+ E  W9 o
                            # 深度复制model参数
    " H" V$ ~$ S; r6 K5 R# a' A, t            if phase=='val' and epoch_acc>best_acc:
      n( `+ v2 Q  w, Z6 K  z                best_acc = epoch_acc
    : s+ R8 X$ W2 ?$ b                best_model_wts = copy.deepcopy(model.state_dict())3 `" }- |7 E& j. a
            print()! C' B% g: _, t
        time_elapsed = time.time() - since;
    ( k8 [1 @+ E  A4 y$ v    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    3 ]) t. [3 w& B    # 加载最佳模型权重
    4 F: l6 W9 o# n% j$ w  W8 D) Y    model.load_state_dict(best_model_wts)
    % @1 W2 \) b# ?$ N    return model: R. t1 H3 g# U. p, ]2 b
    1 j$ G1 A  m5 ]/ S3 \2 y
    下面定义train_model要用到的参数,开始训练
    , Q! o& Q1 q$ G. u  Q1 l6 d; R  u5 o! [% B6 q/ f, q( ~
    epochs = 5 # 训练5个epoch
    - H* [( a3 e, |# c" U5 `criteon = nn.CrossEntropyLoss()
    1 d( w5 J& o. o" b# I6 @) Soptimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)' t% G' R2 X0 R
    # 每3个epochs衰减LR通过设置gamma=0.1
    ; {" o6 Y& T" Dexp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)4 H: G# I+ I% o

    6 P+ U8 a3 n( T" b) {# 开始训练
    ! x! y) h! H/ Z; t; ]' a9 amodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    6 _) n! V1 W3 n" k! q
    $ l  f. d/ \5 c. R2 F; E! Q6 测试模型

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

    def label2image(pred):) k/ B' n" p; T! b
        # pred: [320,480]
    3 f1 x* _9 E) ?    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    8 G: b; p! c3 A7 l( z    x = pred.long()
    4 r7 @4 ?) P' Z: p( U$ f    return (colormap[x,:]).data.cpu().numpy()
    . N1 J3 r# `, M4 @; {1 @: Q; \1 @( V7 m

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    : s9 i- q) {0 G4 ustd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    6 i& x1 Z$ ^: [8 O6 h! Qdef visualize_model(model:nn.Module, num_images=4):9 A% J2 y7 N! _6 n8 T8 M
        was_training = model.training
    : A" d/ t. [6 n2 `) A7 Q- H8 y' [    model.eval()
    " X  P0 R; R. _    images_so_far = 0
    4 N, ^5 t% h4 ], L+ l4 f0 B    n, imgs = num_images, []4 s6 A% G! e+ f1 @' B. Q
        with torch.no_grad():
      h0 K8 d7 I2 V  G        for i, (inputs, labels) in enumerate(dataloaders['val']):7 h$ X# n. H& z/ U# y3 D2 J
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]- v. L- r/ O) l9 r# G7 f/ |
                outputs = model(inputs)
    0 A* Q' t* ?8 [% b4 G% c            pred = torch.argmax(outputs, dim=1) # [b,320,480]# ?" Q1 a! K, B/ C  D% U" W
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦" w. ~+ e% A7 R2 x3 O
    1 I9 B' v- i8 m" \, m. D
                for j in range(num_images):7 C# ?' B: o9 `
                    images_so_far += 11 M' j, @" |+ l) `) `) r  Z& M) U+ Y% P
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3), y+ t) V4 _" v0 d% Z
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]# a: D$ O1 D6 u5 Z! J
                    if images_so_far == num_images:4 L- C* B" r# B4 U7 K% a! x
                        model.train(mode=was_training)" Z: p  z7 H# W7 e* H
                        # 我已经固定了每次只显示4张图了,大家可以自己修改
    ; I+ x8 I6 K9 {                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    / l+ V. {+ h+ V) }2 m% U                    return model.train(mode=was_training)
    # M+ D6 P" j$ a: X, v: t% c% ^% L! N4 S* E  K
    # 开始验证- U; }: h( D6 G: F! T* {
    visualize_model(model_ft)
    ) R8 R- a) Z4 i3 f& f, k6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor. W8 Y+ |8 k6 x  Y* v5 C
    def predict(img, model):; F# K' W) k3 H* a9 I
        tsf = transforms.Compose([
    ) `4 w* s5 T! E3 x  c: Q            transforms.ToTensor(), # 好像会自动转换channel
    5 A: E4 w2 m% A7 y' s            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    / ?8 v2 S3 v1 I' i: b8 z" W6 h    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)/ p0 L4 w2 l3 B; [: `4 s$ s  A
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480): d9 b9 }; {# P; c$ Z7 |" i
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)( G$ E7 ~) P" O9 |* ]: J
    8 L% q" G6 V' z- L9 n/ a
    def evaluate(model:nn.Module):( g5 D) S, i' @6 D- V( G5 |9 Y% C9 R
        model.eval()
    ' g) o3 [, Z" s$ }, r! a# o    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) * Z. ]4 A2 p6 r: w( u/ s
        n, imgs = 4, []
    . ?; E  ~0 `: ^2 Z/ u( `; q    for i in range(n):
    8 `4 @. o0 ?- C6 p6 d        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    / T& w9 O6 o  W( G* C8 E; o9 `        pred = label2image(predict(xi, model))
    ! E. ~2 B7 g9 m, z( J        imgs += [xi, pred, yi]
    " H4 ]$ X  u/ z5 S2 i    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)$ B( ^( K  n) m: ?. V2 U* D
    5 Z" \1 E2 x) \9 b# a! r0 K
    # 开始测试
    5 ^# K: r+ t. t- jevaluate(model_ft)% Z- g; f5 M4 |6 P/ C/ z9 n

      [5 y; m% t6 v5 W! t, y1 t7 结语

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


    , X2 h' ]3 ~( A' jEpoch 0/20 R$ x+ e, g% q+ u, N
    ----------
    6 f5 q" I3 L. \train Loss: 1.7844 Acc: 0.5835
    ! R/ V/ Q. \' K+ m- F1 H6 L8 Xval Loss: 1.1669 Acc: 0.6456" _9 ^* ?2 s, J+ z, V9 s! M

    , O' W, G; m3 ?* C3 b% j2 qEpoch 1/2
    ' J2 ?8 S& D. z----------
    , \& T4 g1 A9 U  Z9 t; K. m. |: [train Loss: 1.1288 Acc: 0.6535
    5 ^' j9 V- f: F% _0 Rval Loss: 0.9012 Acc: 0.6929" {0 {1 y, F- q7 \# J3 `6 J+ x
    5 j' a0 \1 r+ N
    Epoch 2/2
    3 a5 x0 S' b& T! E/ C& S7 @9 \+ C----------
    ( E4 `! w& q! S0 X& ^train Loss: 0.9578 Acc: 0.6706
    4 G& P6 N+ c1 \  c6 ^$ \3 T; j# H5 \val Loss: 0.8088 Acc: 0.6948$ i# M- D. {# B) x8 B. i( [

    + C4 W* G/ k& V7 C/ p. {, aTraining complete in 6m 37s+ y0 N) N, D. u* l
    6 G0 W4 A! O5 l8 a, Q

    2 P$ [7 r8 v/ A4 U& ^& t 2.jpg 9 v2 F# r( m% {- ^5 L, z1 o. f
    $ W0 y  L) [! L: L" \$ e0 B
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    % t+ `3 U3 M( T2 J* P2 u4 C6 n& s* O: F; L
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    & w! y' I- J7 x% ^
    " O4 A4 r8 T1 n* S! H# L; G) r. m, o6 v% I7 L
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:
    ! I# ?1 [- X: S  A: p+ ]0 n# y
    ' I  V3 i$ Z( i0 t* bDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]+ A( ?6 N/ ^- g
    GCN 通过全局卷积网络改进语义分割[论文]
    - u+ K: |2 [: V- O( p: I5 f8 {UperNet 统一感知解析! C, i1 N0 Y3 T9 _) d
    ENet 用于实时语义分割的深度神经网络体系结构[论文]
    % s, f. {# c& |! C$ H+ t+ UU-Net 用于生物医学图像分割的卷积网络% N# O7 o9 N  f8 Q( ?) f8 s
    SegNet 用于图像分段的深度卷积编码器-解码器架构。
    0 I, B) N3 N/ @; n6 a! R还有(DUC,HDC)、PSPNet等。: X, t9 W! b& G7 [% E* j
    3 W, u' Q  X+ h3 u9 X+ S- y( I
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。7 u5 n3 N5 M  B- h" u7 X6 A% s/ x# H

    5 M* M( ?2 l; o& T对于损失函数,除了交叉熵误差,也可以用这些:* c+ m3 H! W0 a6 U3 o; R4 W
    8 e& s3 k; q* c. ]( I) ~) S
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    * ?$ F& N: ]/ [* B4 lCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。
    & `2 b. P$ m; x) ?+ v1 [Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    * B$ l! E) x$ S) a! `1 sLovasz Softmax 查看论文:Lovasz - softmax损失。$ b! N& `# z  g) M4 r  W
    8 c* Y$ A3 p9 z% f! Q$ X
    ' M. I* [: o# Y4 n
    - y3 ?7 q% X/ T' H
    4 y7 K( C. q1 M4 Y' Q0 }# _
    7 I% J) d' ^; K* e# A& g5 @. M" p9 {
    ————————————————
    4 e+ l* f6 s: K: o/ m& F版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 z; x  O, J( l% k
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    : D# q7 n; h  ^- ~, R" O0 k& Q8 A9 `5 u& V
    8 i3 X2 ^' g4 i- F& A4 ~) T0 d* o& D
    # V. r& i0 E0 |) l+ ~2 ~
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-4-18 19:10 , Processed in 0.474643 second(s), 54 queries .

    回顶部