QQ登录

只需要一步,快速开始

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

    . F0 l8 Q- g' T4 \6 k: H1 u& c- i: L
    Pytorch实战语义分割(VOC2012)1 s0 k# J! L; G' V
    本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。! p' \) Q! j2 I: ~6 h/ s
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。  Y3 r1 W  h. i' g. d! z
    " t& T: h( m+ C' F' ~2 K: U

    ! e. Z  W1 {0 u% [- N语义分割中图像有关狗、猫和背景的标签
    * ]" h: |, D4 [& V) M; {1 _文章目录# h7 H' H3 v0 E; S" q# R

    & b) w' \5 n2 m+ B0 ?  [4 c+ K2 K1 图像分割和实例分割' \9 }$ g. M* K" y% O8 k7 B* L/ x5 z
    2 Pascal VOC2012语义分割数据集
    : I( v: ~  y" g5 a2.1 导入模块) {  n+ u1 z$ W& f. z/ B" ~, r- s
    2.2 下载数据集' B9 o6 U  \5 K7 J+ f. h! Y
    2.3 可视化数据
    0 {! n: Q2 c! k2.4 预处理数据
    4 ~2 }/ F# `% F' o* @3 自定义数据集类
    5 x; U. |2 ~1 s" _% m3.1 数据集类4 l- o5 x! W3 ]5 w7 ~) G/ |  M
    3.2 读取数据集
    ; K. P( M! u# d8 o) Z4 构造模型
    , t" E- w6 K/ k1 q! q  c5 b& s: D4.1 预训练模型
    5 b0 M! m/ E/ G# ^5 Q" w4.2 修改成FCN& A; G/ n6 v) N# d1 z, J
    4.3 初始化转置卷积层
    : C% {% P% i! D5 训练模型
    $ S% d+ @, M% x+ Q6 测试模型
    ; x2 T( G1 D' E( v; m6.1 通用型/ }6 `& N) K9 V) g+ y
    6.2 不通用5 I( P6 c7 `- _/ M
    7 结语
    * S0 ?; `' l# k5 u5 c1 图像分割和实例分割; r) K0 r/ A, ^

    ! z( T0 ?9 I5 X% M- M) v+ e计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):1 }/ y& Q0 o5 x

    7 r1 i+ X1 I: d$ N1 j# ^图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    1 a0 V& y. u& d1 U6 S实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。2 a8 X* A) a( h& F: \' y
    & U: D. f" f! }
    2 Pascal VOC2012语义分割数据集2 l1 Q3 y* u( y1 V
    # S" c1 X: M* a0 m
    2.1 导入模块4 p. \9 n2 P% o! G5 V) I: p
    import time
    ( Y$ V4 d( H# i6 R4 h. Rimport copy
    0 N0 l; c/ C% K3 T5 S8 Timport torch' Z/ J( }* Z6 j7 D
    from torch import optim, nn
    6 o) w7 w) ^5 M* \import torch.nn.functional as F$ Z7 |5 a) u4 B+ X
    import torchvision* }# ]( o0 `( ]& Q! `% k7 K2 N# b
    from torchvision import transforms
    9 }+ h, a" A" Lfrom torchvision.models import resnet18/ x9 P% l5 N7 i: B& Z2 f
    import numpy as np
      y9 V3 {/ S7 w3 i7 U$ jfrom matplotlib import pyplot as plt% q) \7 C2 S" D( X9 W8 v5 d
    from PIL import Image! Z/ P9 w6 ]9 J% Y$ j
    import sys
    - \. q1 v3 D. e+ t) n# k' R$ osys.path.append("..")
    , o5 `" g% y8 c8 Y6 x  l: Sfrom IPython import display2 S$ R8 x6 }, G: V
    from tqdm import tqdm& {+ q2 j+ ]  o5 I% e; m& F
    import warnings4 G- L2 b3 s. }# C/ E
    warnings.filterwarnings("ignore")
    ( z* l4 T9 c3 w5 W, l0 a4 F- R+ Z- V. [5 }; E! w
    2.2 下载数据集
    . x3 s- r% t) s; J
    8 W! p$ T0 |% @) J9 k/ E+ B4 f( ]语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    4 ^0 c# I+ L0 z0 Q5 N( R% {) ]& a7 @

    - T2 T) k* E4 }( o9 o! u- V! @6 r; B$ cImageSets/Segmentation路径包含了指定训练和测试样本的文本文件: [$ }( x7 b- e3 M7 Q$ V8 }
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。
      C- e( T4 T' M3 R: `% P2.3 可视化数据
    : I) Z) }6 O; N
      t: [1 k; }; d  d4 t( v定义read_voc_images函数将输入图像和标签读进内存。: N/ G$ X; H7 ^9 |& M

    $ L% \' \7 O' z7 K8 q2 zdef read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):$ M' ^7 W9 L+ a4 M4 a  n0 f0 B- n
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt'), l$ I( u' K. c, @" N
        with open(txt_fname, 'r') as f:3 |+ C. C; L3 a
            images = f.read().split() # 拆分成一个个名字组成list- I+ x/ C9 X/ l# U, v1 C+ ]
        if max_num is not None:
    3 i$ B" g4 N& a1 v        images = images[:min(max_num, len(images))], p8 l, h; m/ M
        features, labels = [None] * len(images), [None] * len(images)
    / Y: |$ T# D9 ^$ t! C" g% c. k/ L    for i, fname in tqdm(enumerate(images)):' n: t# C$ F3 o
            # 读入数据并且转为RGB的 PIL image
      z8 R8 }8 M, d, x( u1 j        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    ; e4 X+ v5 s, g0 f% U1 K/ B8 j        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB"), j/ z4 L* d9 r6 k" S8 v" |- g
        return features, labels # PIL image 0-255( C6 s9 |/ l9 g  {
    % k; [9 U. Z7 U' w
    定义可视化数据集的函数show_images- A0 p/ S8 n; a: U
      k* d& S# \9 N5 e" B* F
    # 这个函数可以不需要& e- ]" l9 |$ N+ U' T6 S
    def set_figsize(figsize=(3.5, 2.5)):7 w9 I2 d, {  \9 |& F; |6 m
        """在jupyter使用svg显示"""
    " h. \0 Q& O7 n2 p! e    display.set_matplotlib_formats('svg')( B, X  g& S6 {# n' W+ V. l
        # 设置图的尺寸. R; W1 a0 K) r) r
        plt.rcParams['figure.figsize'] = figsize
    ' q' ^7 t) I0 r8 P# _. @  d& `# m, q0 T7 U
    def show_images(imgs, num_rows, num_cols, scale=2):
    + u' B$ T4 ^5 T    # a_img = np.asarray(imgs)
    : {8 K3 p2 J% \$ }    figsize = (num_cols * scale, num_rows * scale)8 }. ]+ R7 e# y- W8 U: l, o  A
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize); ~, C; N4 O  D  a9 t. n
        for i in range(num_rows):9 G- A# a0 `7 t/ m# f8 C
            for j in range(num_cols):( J6 e$ c( `6 L/ J$ I
                axes[j].imshow(imgs[i * num_cols + j])
    ) z* Z. A3 y" f- K- w            axes[j].axes.get_xaxis().set_visible(False)
    2 }  Y# X3 B# _' m8 W' J            axes[j].axes.get_yaxis().set_visible(False)
    3 M) p6 |9 C$ Z' J    plt.show()5 n9 S4 C0 k5 Y- B# @7 u6 Z4 o) b
        return axes
    8 I; h. f9 I5 ?. Y! w7 B/ ^5 ]1 w3 x6 ?4 [& X
    定义可视化数据集的函数show_images. F% F: X0 V) E- D" M+ o$ X; w

    & s" `$ I2 }  l( [# 这个函数可以不需要# P  ?; a# o( R" |( p/ E, a
    def set_figsize(figsize=(3.5, 2.5)):
    - A; [% G& @# G6 I: v    """在jupyter使用svg显示"""
    " O. `- }  _2 b( G- @    display.set_matplotlib_formats('svg')! z0 E, _9 t6 A* A' J
        # 设置图的尺寸1 K3 @4 |! _$ K$ p8 A' S; [
        plt.rcParams['figure.figsize'] = figsize
    9 @2 q: {+ L. m5 ^1 ^# l0 y$ W# `) w2 q* A+ Q
    def show_images(imgs, num_rows, num_cols, scale=2):' J% e( g; v+ j+ x4 V
        # a_img = np.asarray(imgs)9 e/ B9 t5 Y, x4 n( O6 w
        figsize = (num_cols * scale, num_rows * scale)
    4 j% d  D+ D$ n# R- x    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    0 U# F2 I4 J6 K- h4 B) f* s    for i in range(num_rows):+ O2 y, e- Y; l: M7 {8 h
            for j in range(num_cols):) b( d% I$ n6 d1 q% T6 A/ O0 I
                axes[j].imshow(imgs[i * num_cols + j])
    ' P3 w( |! `5 v! C3 V' m9 C2 F            axes[j].axes.get_xaxis().set_visible(False), a; A0 A, u+ j5 `
                axes[j].axes.get_yaxis().set_visible(False)+ Y- @/ b* P2 P7 @) M5 {. \
        plt.show()- G% f( Y% w) Y; d) u+ X% V; p
        return axes1 K# u; I  i+ u- P
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。4 P# J4 S- ^4 A! s" @: I

    * ], z0 A- r) F$ ?1 M8 \# 根据自己存放数据集的路径修改voc_dir
    8 A* O' q; m2 }4 ovoc_dir = r"[local]\VOCdevkit\VOC2012"( g( Z% F2 o0 U+ s6 ~- Y: B/ c( c
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    ) x# O' r. U: ^n = 5 # 展示几张图像0 ?* M, e8 f7 e2 I) G
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    6 B3 V8 @1 Z6 w3 R" ]. M  ~show_images(imgs, 2, n): A6 O- S" L8 O
    ( g) i! t6 P9 F" C1 h9 w' p
    1.png 1 B! V2 t; d' \4 K. i% s

      Q' Q" R0 t' A/ ]9 e& r列出标签中每个RGB颜色的值及其标注的类别。
    # S& _3 x' I8 T; M4 G7 S) z# 标签中每个RGB颜色的值' @: [+ f+ T* [2 n3 H+ `( k
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],2 }  u$ `: p) K
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],4 K: ?3 ^! b3 X, S
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
      h5 R" o: o$ d! ?+ o! d+ C( P                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],+ w6 u0 R2 \6 C( m) m  p
                    [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    6 v3 x7 T  Q+ u, ^8 A9 }/ x" j                [0, 64, 128]]
    % e6 ~4 D/ q; f5 I) G8 m# 标签其标注的类别) ^1 @$ @$ W  z7 F5 L1 i/ |+ Y/ d
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    ; i5 y' J5 m- \9 W/ Y, I& H- ]0 J               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    / J7 [0 J7 a% I7 P8 a8 X! u  f               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    7 Z" v0 }; K) o, f9 N7 ?. O               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    $ n& p- M0 O# s% @7 X有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
      q0 E5 w' m  C  S: G2 x( t
    ! ]; g. Y+ I/ A5 N, }  m( y有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。; j( T7 b& I# n1 _/ R( Y! p& a
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    , g; o- ?' U  ~( x, ofor i, colormap in enumerate(VOC_COLORMAP):$ E" k9 @5 n- `& N$ g' @
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i5 G$ r) y7 @2 H  K* D
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i$ y. K0 Q2 P4 _3 v
    8 p; L4 E; e7 `
    # 构造标签矩阵
    + @% f( J" n0 D, I4 Edef voc_label_indices(colormap, colormap2label):
    " V) k! Z4 K6 f+ H& P    colormap = np.array(colormap.convert("RGB")).astype('int32')$ p5 i  X# Y. M5 f+ r6 k  t
        idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    1 w/ {- S: }1 n, i    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标, n9 S& o* J, j" i& @

      p4 l7 e& D0 }1 E" F可以打印一下结果
    + r- X* ?" S/ T, d+ Q$ A( \; N& e. D4 [" a+ z9 U& J
    y = voc_label_indices(train_labels[0], colormap2label)/ `, |* K+ S- Y0 k8 m, Z: D0 M
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    0 ]% _4 Y; C9 l7 ?9 ]! e' l8 m2 W8 _8 m" z+ R
    2.4 预处理数据
    0 F& Y5 a) b' |- p# [+ W1 ^
    3 ~  r3 T8 J% A! L) E2 T2 U# [, O2 r在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    ; D$ }5 Y. g9 A/ c! P& q* }- X" K; R8 x5 a  f8 g

      ~9 L0 @* P- K3 fdef voc_rand_crop(feature, label, height, width):9 E! l- `# K  N5 q  u! {
        """
    ! j0 @/ Q% d$ ?    随机裁剪feature(PIL image) 和 label(PIL image).4 G* [' r" e) B, e" J/ f
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做" t. w! N% c; C' v0 l
        Get parameters for ``crop`` for a random crop.. ^" \4 \- q5 Y! h; I. g; a* x3 ?
        Args:6 V4 X+ S0 J% d! G$ M3 a" T
            img (PIL Image): Image to be cropped.
    ; h  W1 z; G$ S! C9 r1 x        output_size (tuple): Expected output size of the crop.
    & @- j: j! K2 s    Returns:
    . b. t* U' h5 y8 _8 n5 n        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.' h  k  p) v/ ^( l
        """
    8 ?9 |4 u8 Z3 W    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    9 Z* L) S" P: r8 I7 W    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)9 m- L$ N1 |4 i9 M, O+ E
        label = torchvision.transforms.functional.crop(label, i, j, h, w)  G* G5 F; l/ F; X8 R
        return feature, label; t5 R4 `- [8 q- I
    ! t* ~0 K9 E6 S7 T& R: ]
    # 显示n张随机裁剪的图像和标签,前面的n是5
    + J& \+ K( A9 A, ?% Gimgs = []
    3 W; \: s' _& a0 }! ?3 o) `for _ in range(n):0 D% b6 l: ?; n" J' t
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300). q+ a/ R" q1 n* s
    show_images(imgs[::2] + imgs[1::2], 2, n);
    1 i7 Z3 r' _/ N/ o, C3 Y
    " X' x$ O0 j- T, f+ A. ?
    2 Y4 j# V# w. G' q) E$ v# u3 F 2.png
    ) F1 c5 |$ j% i0 ]/ `7 ^0 Q4 U% h) I3 T
    4 r0 p4 c0 F0 g/ j4 i$ g

    " W" f% O3 y/ \9 s5 |' H3 自定义数据集类2 O; f9 p) G% v, @4 n' ?' m

    , A+ c$ O' f" @5 d3.1 数据集类
    2 E7 Y9 ?# ^8 }- F9 g
    . l3 p" Q+ S! @2 atorch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法  @8 e% Z( F. g" e

    " ]/ x8 _; a0 j' s+ w+ P8 c% p__len__ 实现 len(dataset) 返还数据集的尺寸。
    ( U1 a8 B' X1 S5 e1 e5 A& L6 ?0 `' L__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    5 D! y4 L' ^, C7 z9 {& X' I由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
    7 A8 f$ E! O( E! ~5 p- t) h3 C" D3 ?; M, |! C
    class VOCSegDataset(torch.utils.data.Dataset):8 j9 ?# g8 r3 H; y! f4 B" ?
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    % L& F3 [$ g+ c( b        """
    $ O' C1 s# e1 _4 C# Q) y        crop_size: (h, w)
    5 G: U' Y5 q% s/ ^) I; M% S9 j        """
    " _2 e8 U/ [4 m4 ~$ ^( Z2 C* V        # 对输入图像的RGB三个通道的值分别做标准化! a* F/ f2 p- n) A0 A# b- h3 }+ w
            self.rgb_mean = np.array([0.485, 0.456, 0.406])
    * ]7 H+ M! c7 H1 E* y        self.rgb_std = np.array([0.229, 0.224, 0.225])
    : L& V9 g( |4 `) B$ u        self.tsf = torchvision.transforms.Compose([3 s$ T# F! P* _: G" J  n) c% e8 E
                torchvision.transforms.ToTensor(),2 t5 p% }0 B8 M2 S
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    8 u2 K1 B5 t; s1 w. P4 W        self.crop_size = crop_size # (h, w)
    ; E9 c' e  R& [, F! u        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)) ^$ l- l' o: H) j/ l3 @) P
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    + _" Z2 \- K" z/ f: T5 u* Y6 v        self.features = self.filter(features) # PIL image' C$ [2 k# j& G
            self.labels = self.filter(labels)     # PIL image8 A# o% n8 k9 G6 u- \9 \$ R$ Y
            self.colormap2label = colormap2label
    ( V0 Y* d& k- V) ?        print('read ' + str(len(self.features)) + ' valid examples')
    , B) O! u& r/ `) h) Y; G( y' d; f/ U& O# X3 _# g+ c' m
        def filter(self, imgs):
    6 @$ W$ c. L& \9 S, ^1 \& Q        return [img for img in imgs if (* [5 ^5 W' `9 @, s! d
                img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    7 l* y4 p$ y$ a8 N/ {3 S( O* i) J2 ~/ s
        def __getitem__(self, idx):
      {! N) j; m1 t  Q% E0 }3 o        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)" M* C3 D( `/ T* I: _3 W: `: ?
                                    # float32 tensor           uint8 tensor (b,h,w)
    2 A& v5 R. Y" `+ i        return (self.tsf(feature), voc_label_indices(label, self.colormap2label)). @3 V2 b& R1 ~% ^" p7 Z# x
    4 b9 l5 @% E( E1 V( c6 q
        def __len__(self):5 ]7 d$ }7 z$ e7 x: s
            return len(self.features)
    8 B( n/ s) c* N/ B0 c0 C1 a& ~3.2 读取数据集& j4 A  N/ Q1 D, v9 F# v' o
    ( X  o. j  j0 x  y& P; l0 y9 V
    通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    " n, t0 A" J3 M6 u
    + ?* K% t4 V* _' obatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)* i5 F% w  |4 O5 t
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)/ W" n6 x4 G& c
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~# `4 e- v' t% w" W& {& i; _

    ( q4 H  L. s: `+ f% m# 创建训练集和测试集的实例
    9 f0 o, @$ [& B3 J$ |8 N! Kvoc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    ( a. u' E2 f! v& b/ D$ K; E% ovoc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    $ I( `, g% f; B+ b4 I! h/ a2 D- q3 s+ L0 ?1 }! x) H% y1 g- x" w! u5 y, i
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器& @) c! X. J. x! {7 n0 T
    num_workers = 0 if sys.platform.startswith('win32') else 4
      C7 o, f# j( |train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    : A- ]& j' I7 O/ `7 o, N1 h                              drop_last=True, num_workers=num_workers)) j* P# [- F+ q1 K/ v+ o- w! ~, I
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
      q+ k; r* Y; v9 N, D6 |9 f                             num_workers=num_workers)9 y: y9 B2 K( F; p9 F6 {4 I

    : e! y' u- C; J# 方便封装,把训练集和验证集保存在dict里
    ! k" u* m8 C* e+ l* [, W; zdataloaders = {'train':train_iter, 'val':test_iter}( x9 C  g. ~2 ?/ y
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    & s3 p  q: v) k  n) K- }. F+ i* o- H5 m% j7 @
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    ; v, j  }. J- ^# o
    , `, w& m# G6 A( G: l. E0 pnum_classes = 21 # 21分类,1个背景,20个物体
    " w" A8 R( V: Z+ V1 R: t: h1 _1 bmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    . _# {; l7 ~1 p8 x# j& x5 o) F, a, X8 V
    # 特征提取器
    # L4 ~' K5 v3 j+ ?1 ifor param in model_ft.parameters():
    . D" G4 o( u6 \: }5 U    param.requires_grad = False
    6 n% q/ ^: V8 l4.2 修改成FCN
    ) h- I" P1 _, l6 H( R& c/ `0 p+ W
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:/ E* U5 }4 ^/ J: N7 Y7 T, \
    最终输出的通道包含了该空间位置像素的类别预测。
    ' @5 g8 P. m# [3 i/ @, o8 x$ v
    0 ]1 z. Y/ K' }$ h! Z( S对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    7 [! _, ^. T! O4 I3 p/ R4 Q" f4 F) R7 M7 |# J* n
    可以先打印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 倍即可。
    - s! h+ V! C, ?8 n  J6 U4 y0 J6 O7 a' \( V
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    8 L& N; W8 U, p1 m( a: C" j              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class$ U( I- o4 \* W( D4 Z& w8 p# C
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    5 L# M3 D+ {* d' @3 U1 Q, k3 U8 G! T# B' f/ g$ x2 x% H
    # 对model_ft做一个测试
    % e' z# h  N+ `, S$ lx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据/ x: V4 Z: e2 S) E( Q. N, _% L
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    1 W  r# Z- e6 B8 {
    . T8 x3 }+ }  K% u5 k# 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    ) `) t# y! P( u0 Q# for X, Y in train_iter:
    9 H8 l/ ^+ p' P! M( r' w#     print(X.dtype, X.shape)
    / Z7 Q; n. c" c#     print(Y.dtype, Y.shape)- T& P; L( H5 }. y0 ~# I! g# [
    #     break
    9 h+ _! n: G( y% o- B% V
    1 t- [% u3 {  S  i  b& ^6 N
    8 b3 O, s8 \- |0 v& ^: ^2 F' l4.3 初始化转置卷积层
    - _* e9 a5 g3 O  M' P$ J4 f4 y# [5 q1 t
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    & t/ X6 x$ z2 I- ]在坐标 (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函数构造的卷积核的转置卷积层来实现。9 T/ R9 I- Z4 \- R
    : ]/ x, s! b' e& I

    4 t$ ~0 h$ t+ \+ Y! q3 ^( p2 z1 a# 双线性插值的上采样,用来初始化转置卷积层的卷积核
    2 [7 m3 g8 q" x8 [0 vdef bilinear_kernel(in_channels, out_channels, kernel_size):
    & A: v( f3 ?! u9 e. j* p    factor = (kernel_size+1)//20 }1 ]" H5 d# j& d; `, ~! W* o: V
        if kernel_size%2 == 1:, ^7 e3 o2 r3 A
            center = factor-1
    3 `$ b% p1 h3 p1 S% p    else:0 j* Y6 v8 Y, \8 v& @
            center = factor-0.5: d( p# z% {) q/ a7 h4 ~
        og = np.ogrid[:kernel_size, :kernel_size]
    6 u. g6 h' i( a5 Z    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    ! g5 q1 x+ O) v9 {: e    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')8 R! q% [. y4 _
        weight[range(in_channels), range(out_channels), :, :] = filt1 u) B( W2 p, ~* D
        weight = torch.Tensor(weight); m1 g- w  y1 [
        weight.requires_grad = True
    4 v3 p- K! S6 m& j- h    return weight3 \3 U" X, I* I: ~( q

    & t* S. c# K9 R9 D0 F- K% C2 E$ o! @1 Y* |3 N3 X! f
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    4 }: ~& C2 M. p+ c
    / \/ ~. l0 ]2 t4 D) Gnn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    , z/ C3 A+ L" ]+ ]0 r+ |, vmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)9 X' t9 V; s% ?# @+ o* C

    0 R& m+ g+ c9 P1 D
    * G  o* U9 B" _# [0 l9 U- }% t5 ]5 j5 \0 |1 H( i; K- v
    5 训练模型

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


    9 v  `" E! b3 Xdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    % g8 b! T8 g* t" \$ K$ Z6 L    since = time.time()' l" Q3 t& @! q0 H, c
        best_model_wts = copy.deepcopy(model.state_dict())
    7 P6 r$ C' R& A, i, O    best_acc = 0.0
    3 W, Y! i5 e. O( u1 H' f0 n    # 每个epoch都有一个训练和验证阶段
    0 [! S- |: C) j. |  @+ W. W" A- v    for epoch in range(num_epochs):
    ! }7 _* J: [! `; w4 ^7 j+ e  l        print('Epoch {}/{}'.format(epoch, num_epochs-1))3 b) `# D4 f  L/ Z2 I
            print('-'*10)6 e. R# I* t- W# ~2 o" U9 d
            for phase in ['train', 'val']:' o/ _3 j! j+ ?
                if phase == 'train':
    9 A) N5 w( u- a: T" R" h                scheduler.step()
    . ~$ Q9 D8 ?4 z7 a. p                model.train()
    + u/ w1 y& M4 X) y" |$ x            else:
    4 l) f$ s# @# ~( X                model.eval()2 n. p' A2 i4 \
                runing_loss = 0.0( u+ g  A' g) H+ k
                runing_corrects = 0.0& L9 I. c; }- |* u9 F$ [( F
                # 迭代一个epoch
    # ^8 g9 N; h( ^! o            for inputs, labels in dataloaders[phase]:
    9 T9 E4 L% j0 F                inputs, labels = inputs.to(device), labels.to(device)
    : W" |* [' H0 S& O- y9 ^                optimizer.zero_grad() # 零参数梯度
      K; h) J2 S8 S# b                                # 前向,只在训练时跟踪参数
    " G7 O' P& K' l/ x" Y                with torch.set_grad_enabled(phase=='train'):" ?0 R( E2 I3 W, z7 R& L
                        logits = model(inputs)  # [5, 21, 320, 480]5 \* h* ?; p, ?2 J7 C' h8 Y
                        loss = criteon(logits, labels.long())
    1 H0 r9 H  l% F- i                    # 后向,只在训练阶段进行优化
    & Y0 d; G  ?" X0 R7 ]- i                    if phase=='train':
    2 O* F: j3 {# D' o; _8 f  _& m                        loss.backward()/ M; G  x6 u; q
                            optimizer.step()
    8 g: B$ R0 Y& l. ~3 ?" V                                # 统计loss和correct
    2 k, ?9 V% @+ z& H                runing_loss += loss.item()*inputs.size(0)
    ' y( ?2 y+ C) J* j. U                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)  p+ q  L  m' S6 }2 e, l+ s! Z

    " Y( F# t$ G( x+ q- x6 F            epoch_loss = runing_loss / dataset_sizes[phase]
    , u& [* p$ a# e            epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    ; O6 |$ v# O# L9 q- l+ v& H, v/ [            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    6 N4 [0 R' q9 X! [- H0 q2 z! P                        # 深度复制model参数
    * E4 U' O2 ]% i9 ]) n8 j/ |) ?4 E            if phase=='val' and epoch_acc>best_acc:
    2 |7 Z9 c  u8 e( h# W                best_acc = epoch_acc
    1 k% u# J0 k( {* _0 K8 g                best_model_wts = copy.deepcopy(model.state_dict())4 |2 u/ _% j+ v+ b3 }& E% w
            print()
    7 {8 l6 w0 {6 ?$ b, V    time_elapsed = time.time() - since;
    , k( |, ?3 D1 [0 I7 _. N+ J6 Y    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    0 B1 Z& N( J$ C. j. X8 K( @/ z  N    # 加载最佳模型权重- y1 W; F7 d% a" T% R: B
        model.load_state_dict(best_model_wts)
      H) C, Q: B- Y- ^! G( g+ X4 P    return model! \/ B- \8 k( b7 k: u; x6 S& F

    4 t0 H6 {; i/ r5 w: A- r  u" h; m下面定义train_model要用到的参数,开始训练
    & ^( T  B$ j$ @3 e, T1 f# W5 z5 l- Q% L- R4 B; y- T
    epochs = 5 # 训练5个epoch. x, E: O8 z' ^. v; }6 k8 a4 c; ^9 \
    criteon = nn.CrossEntropyLoss()
    : z0 {7 P# T" ?9 H& `9 eoptimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)" [" w) E1 j: u8 H/ ^; x4 c  U* o
    # 每3个epochs衰减LR通过设置gamma=0.1& r2 y0 H& g4 N6 j+ f
    exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)" X9 n7 F/ o3 b( H* S; [  x# m
    " F& ?3 P" V! z2 F2 h9 y5 C
    # 开始训练
    . l. P0 k# Y. W. qmodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    8 n9 \  r# _9 g' c3 }& a% w( \1 w' b- i( h+ H' c" y0 m
    6 测试模型

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

    def label2image(pred):
    ) m9 x- p7 X# V' u& E9 F    # pred: [320,480]
    % Q* ^( c+ k0 Z4 Z9 \3 `2 [    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)( K! w( Q) g/ w9 r4 L. H  d
        x = pred.long()) u7 e+ ~% H$ s' n2 y' S- k
        return (colormap[x,:]).data.cpu().numpy()
    # f3 b) H. t" h( Y' S: O  G8 i8 {  m( X4 e- ^0 Y& b

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    / `: v% N  M8 H' Ostd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)! [; s" Y& v8 g( b
    def visualize_model(model:nn.Module, num_images=4):0 L( F- E( }9 ]7 a
        was_training = model.training
    * h) I9 V) C% e; i3 X- A    model.eval(). ~+ @$ t- O3 ^& Q+ O6 r# G
        images_so_far = 0
    * Z6 K& G0 l' \8 l# p  ^- W8 y# J    n, imgs = num_images, []1 Z1 `5 f0 D( u- U* X
        with torch.no_grad():5 ?$ w3 o! g% V" D/ N! Z. O
            for i, (inputs, labels) in enumerate(dataloaders['val']):( b, G( `8 o& c# T* y1 y0 t
                inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
    2 \2 b3 M$ P  a            outputs = model(inputs)
    " M. B" k+ P  M( b            pred = torch.argmax(outputs, dim=1) # [b,320,480]4 n3 E4 c+ l- m; O
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦# t4 ]/ l. `- J* n9 ]
    ' j6 |3 N& P" B" w7 q
                for j in range(num_images):
    5 }' q: A4 a) T  l. ~) v                images_so_far += 18 M% n# L9 l+ E( F3 P
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    - i" W3 N5 y: N3 v9 B                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]
    # E/ m! F! `/ h* O$ p; @* s                if images_so_far == num_images:8 N3 U. J9 n: L& L# [0 i# n
                        model.train(mode=was_training)
    7 D/ j" n2 y1 E/ d3 R# q                    # 我已经固定了每次只显示4张图了,大家可以自己修改
    7 M3 N' z! z$ Z% ~6 O                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    , K0 ~. u0 s6 b3 {                    return model.train(mode=was_training)
    . [4 z" _2 G6 P: w) @: J. S2 x( g- i" O& T1 C
    # 开始验证
    8 S3 F3 N9 [4 Fvisualize_model(model_ft)
    3 X" Z" f1 k- h! P; o1 Y6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor0 U0 R" S8 g2 M3 ]) Q, `- e
    def predict(img, model):
    # N4 N2 }0 d, ^$ N) U. l7 H8 Z! k$ B. m    tsf = transforms.Compose([
    : R+ _3 [" }/ {7 o1 Y1 w            transforms.ToTensor(), # 好像会自动转换channel
    . M+ F4 I, f7 R& d            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    7 _5 j3 y8 v! d5 d( ~) c% {' Y/ {    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    6 r6 i5 A4 {9 w2 k) D    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)6 ?2 n- }5 Y( d0 P
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    + {0 y3 @/ W/ \& l3 d( E, E: L8 m" q' D! F# C; u* c$ {
    def evaluate(model:nn.Module):( E* X% K" ?1 X& c
        model.eval(), d. ~$ A" j3 e4 l3 ~8 X* ?
        test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) 9 r5 b) X, z; ?% _9 |
        n, imgs = 4, []' F0 N+ k4 X) ~  o# }
        for i in range(n):
    0 W+ ?4 M/ i. Q' z        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    * Q( Z7 z& w3 Q        pred = label2image(predict(xi, model))
    2 U; L0 l& v$ J6 M+ N        imgs += [xi, pred, yi]/ U4 g+ [) q; O9 h- r1 S' l) f
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    9 |$ V- T3 `/ h& ~: t/ H+ N- h* l8 D1 L+ C2 z
    # 开始测试
    ) f6 B' M; ]6 {7 t- H0 x& wevaluate(model_ft)! O5 v/ F/ k4 [! t# m; y& t
    : R( ?* r9 C6 i
    7 结语

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


    4 s0 `2 t+ b+ F  x" m+ B$ zEpoch 0/2* p& S/ [# S* O# k7 b3 l0 B- H( S
    ----------
    " n, _6 E7 O4 Z2 ytrain Loss: 1.7844 Acc: 0.5835; K" S# n1 Z0 s( k
    val Loss: 1.1669 Acc: 0.64568 S7 o/ ~$ u# m/ @7 [+ t. O

    7 P8 ~) m2 Y( MEpoch 1/25 U5 t3 \! g- S8 v
    ----------
    ! ~1 H- b7 E0 W2 u9 Z! M+ ?/ gtrain Loss: 1.1288 Acc: 0.6535) N3 L; E- s- y' U
    val Loss: 0.9012 Acc: 0.6929
    % J9 o! D4 {: f& A/ D
    1 F- y- _" H: H& }Epoch 2/26 G5 L/ s' l7 H/ K' |
    ----------9 T1 M# o$ T4 H5 P! {( ]
    train Loss: 0.9578 Acc: 0.6706( m$ b( w' E; I% p8 d
    val Loss: 0.8088 Acc: 0.6948/ @; {# G! ~; s  A3 b4 S5 o
    5 `4 n: z; L: U
    Training complete in 6m 37s% Z  h+ ~; \% j  ]- ?

    # w3 b7 u6 B: x5 t3 r) y3 n4 r) A$ g" E
    2.jpg ! x" O7 e3 N1 N9 F

    7 c: J% N! \9 y. F1 ~5 c当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。( M9 k. \# ^! r2 i( P1 z

    9 D* `2 ?2 f& o: T  }% V对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    / u, q; A- f2 G' c5 Y, {) Y( l. y8 l, U+ p
    7 p' ^, g9 h: ?. c9 u) f
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:5 |  R& V) l! N5 H" W- k; V) m/ u- n: A

    8 Y" k" d: g* z2 T+ `Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]4 I9 l5 R" W9 f
    GCN 通过全局卷积网络改进语义分割[论文]
    : Z/ p6 T2 p# C& sUperNet 统一感知解析' I. g2 I, n( f$ [( L! _7 o
    ENet 用于实时语义分割的深度神经网络体系结构[论文]
    ; N# C) F" z& K) n1 \( _% ^, a4 u  S( XU-Net 用于生物医学图像分割的卷积网络5 {9 H, G" e3 h3 U, H5 p
    SegNet 用于图像分段的深度卷积编码器-解码器架构。$ _" ^$ I  o+ y
    还有(DUC,HDC)、PSPNet等。( H% O0 P, j, m( g0 y4 Y0 R
    8 H& d& A3 u! e! x% s
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    - u$ ]4 K1 P1 C% X
    1 O8 a; x- m- T- N对于损失函数,除了交叉熵误差,也可以用这些:9 y8 |, \' I; S$ w) N$ ]; k: y

    ( v6 \: a- G) \, f/ ADice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    2 O5 C- e: d9 W0 p0 zCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。; H& ~! V( X* k; N2 S; r8 n
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    4 o( n+ B1 w5 S* y( H4 gLovasz Softmax 查看论文:Lovasz - softmax损失。, l: N4 F3 a. o  ?* o6 c9 L1 h
    & h' c- W6 M; {$ F7 J0 R
      E" x- J) G' U& d' h& S
    ; h1 S! L( u, j/ I; U% X, W4 ?  ?
    ! U, I+ l0 m4 @4 [

    , K% Y2 Y' R/ v) O8 G9 z————————————————
    9 B& A2 p# E; P# R$ B, z版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 H9 r+ l9 n: K0 ?. R# U
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    ) W  X  P% W! K$ H0 }. U/ i$ R$ B% r! C& [+ l

    3 M" X  x, N5 e  i+ Y% ]: J' x
    ( ^, f: s$ a4 r1 ^& X7 v& ~" g6 ^
    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-7-18 08:44 , Processed in 0.560925 second(s), 54 queries .

    回顶部