QQ登录

只需要一步,快速开始

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

    2 A& L* V( D# \  W' a: b
    5 @. H, q' v, o; e( T) L; r3 @" e: l; qPytorch实战语义分割(VOC2012)5 W# i( J& ?! G( N, @
    本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。: j( D6 D& w$ M: L/ A3 `$ n' u
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。8 U8 n3 I7 M! z  p1 ~7 T
    ! O6 M7 B3 ]  {/ S4 E! r

      s3 m5 c4 k% o! L' s语义分割中图像有关狗、猫和背景的标签
    - C% [9 O3 V4 u- _/ \8 z- k0 K文章目录
    0 y( o$ i+ s9 t( p, I: I. R; B- f- x0 m  ^+ B7 P2 t- y
    1 图像分割和实例分割5 B4 ?% j. m6 O) O
    2 Pascal VOC2012语义分割数据集
    8 b" K% E0 f0 Q; p6 F2.1 导入模块- |' E, m: _- J. W& s$ }1 {
    2.2 下载数据集
    8 f- ~. Z, D9 X+ {- _2.3 可视化数据
    8 ^, |* h$ S3 _; T2.4 预处理数据" G  f" U& [  F' E; [8 b/ D
    3 自定义数据集类
    1 _2 T* ?" R8 |6 c' o; B3.1 数据集类% n9 c) ^) g2 [1 f2 f5 t3 {: Q4 n
    3.2 读取数据集9 Y$ \8 m0 |/ x* n- r: J
    4 构造模型/ P3 T. V7 v! E
    4.1 预训练模型
    / n* Z: j# V. |7 y7 X: J1 _4 G7 \4.2 修改成FCN
    5 ?3 W2 k4 C! J3 k1 [+ H3 x0 Y- F4.3 初始化转置卷积层
    ! _9 s4 b- n. e2 ?5 训练模型3 I( \7 q& [2 m; h2 @1 T
    6 测试模型! W0 H1 ?* r  @0 x
    6.1 通用型, v/ ^  f+ M+ ?) ?1 R3 M5 P
    6.2 不通用! z" e) r) y9 t2 I" b
    7 结语$ d& q8 x) D  y2 u3 ?2 Y) w$ E
    1 图像分割和实例分割, o+ a0 [7 g2 C( F4 p, _$ A& d

    - Z4 |: Y& U5 e1 Y: Q计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    ) K% p# S! p4 o" _( H) f. @: G9 l$ j
    # L+ y$ `* \# b. L图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
      K2 T, T  k5 a( K实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。/ K/ t/ T4 b4 Y0 W7 x6 F! n

    ' P* f7 X7 n+ O5 c4 m2 Pascal VOC2012语义分割数据集9 Y  j. u( E7 n4 y
    ; v/ F' s- v4 o$ R
    2.1 导入模块6 K" [3 V. R. K# a5 e
    import time
    / q" |# r" i0 Kimport copy- h+ e6 @  }( E% i0 E5 Q, o
    import torch
    / E- Z) ~. {! j4 K" S3 Qfrom torch import optim, nn
    3 Z2 B$ Q& k% }. j  Qimport torch.nn.functional as F
    0 E( `, W+ h! p7 J- v$ oimport torchvision
    7 `9 _& P* t% }( @, A& t0 Wfrom torchvision import transforms7 q$ h, r0 ^6 G3 }" L( G/ ]+ u4 p! q4 Z+ j
    from torchvision.models import resnet18
    * q7 Y, C; y4 p: w) x3 l/ n5 uimport numpy as np
    $ k- t5 i0 Q7 X. Nfrom matplotlib import pyplot as plt) }; C) ~, l# |8 X
    from PIL import Image
    6 {& T6 z( h4 C. T9 Uimport sys
    / [5 u! B0 @) Z# Y; E0 D! vsys.path.append("..")
    # W$ ^$ c; K$ k1 R8 i3 ifrom IPython import display
    % t9 r4 e0 k; I& L) ^3 Rfrom tqdm import tqdm
    4 T$ E2 i$ p& D1 S7 Z5 Simport warnings
    6 q1 Q/ ~, _& ?$ f# q2 h/ R) wwarnings.filterwarnings("ignore")4 Z6 {/ V5 U# ]
    ) v) @# t7 S; |9 k3 Y% n
    2.2 下载数据集# J! ?7 I; F* K1 u, m6 H) Q, j
    9 h8 j" g; S! I( ~  t+ z
    语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:; {* h1 W; S/ |( Z5 m4 q" S1 a% `
    : S3 z  u: ^7 J" I- |0 I
    / `4 p7 p; o0 K8 _1 K
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件
    2 Z- m  E4 _0 u7 }JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。( |9 X$ e: k! w5 f; E& ~
    2.3 可视化数据
    3 i2 P1 u1 c, ]7 P2 ?- k* u& g3 t2 n, L
    定义read_voc_images函数将输入图像和标签读进内存。9 i$ u4 D! m5 U( T  H* C

    7 u) N0 u' y, J6 p9 }def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    7 a  `& }8 V) h: ^# \    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')1 k! B& S1 j9 z  j  g% t# i" n) g
        with open(txt_fname, 'r') as f:
    - n5 a! T% x8 }8 V7 v+ w        images = f.read().split() # 拆分成一个个名字组成list, y+ ]' }  o" [# G+ y1 L
        if max_num is not None:
    - |  E9 o1 P; q1 D5 j        images = images[:min(max_num, len(images))]
    # {) q7 V0 d0 m    features, labels = [None] * len(images), [None] * len(images)
    7 f( J/ V! D# v& f    for i, fname in tqdm(enumerate(images)):+ n# \6 l3 A3 u
            # 读入数据并且转为RGB的 PIL image
    % d9 x0 T8 ?) S/ C5 s        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    - S( K: O2 F0 c1 L, _        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")  R" \" r  `# ^* B% z: h
        return features, labels # PIL image 0-255
    * l9 Z* k8 Q' y  U% T5 N+ y1 Y1 b
    定义可视化数据集的函数show_images
    , c9 r, h' w, @2 v: @- i4 ]' l* [! K  `+ h
    # 这个函数可以不需要
    ' h8 b8 ~$ F' y+ i) V$ h* f) p* e+ ydef set_figsize(figsize=(3.5, 2.5)):2 S! k& J" r- s* }5 v/ q
        """在jupyter使用svg显示"""
    8 M8 h! _/ r  \9 j- }    display.set_matplotlib_formats('svg'): M/ f2 {$ s4 g- U" \
        # 设置图的尺寸+ g  l) v6 g* |1 M8 K
        plt.rcParams['figure.figsize'] = figsize; \" r! ~' T# e1 T: a. p" X. b
      A& A. v- B4 X% X% o
    def show_images(imgs, num_rows, num_cols, scale=2):
    8 z  y2 x* b! R    # a_img = np.asarray(imgs)
    ) L" |# S/ }3 h7 x    figsize = (num_cols * scale, num_rows * scale)
    ! ]% E$ N1 F3 C3 a    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    ( }" m9 V: m3 b9 E6 L/ \    for i in range(num_rows):
    + P" x7 B1 n; A* i" F        for j in range(num_cols):+ D: A/ x, n1 t* E( f/ V
                axes[j].imshow(imgs[i * num_cols + j])
    3 j' l7 C% D) Y% V* Q            axes[j].axes.get_xaxis().set_visible(False)
    ( E0 ?3 x- A: o; S/ a            axes[j].axes.get_yaxis().set_visible(False)
    4 W2 k- r* M) f% b5 }    plt.show()
    ! ]* i$ o' x8 l1 l& G- ~$ f$ U; C; }    return axes
    . r$ V9 q# @1 q4 n$ N3 `. l: c4 g: Z" x# g* g
    定义可视化数据集的函数show_images。) q* c# I) v! G7 c: u( S4 T
    5 ~8 x9 E0 j: a
    # 这个函数可以不需要2 \/ S% t7 Y9 D5 |3 T- ~
    def set_figsize(figsize=(3.5, 2.5)):" o! N+ O. c. u) h2 F; L
        """在jupyter使用svg显示"""
    8 \, B5 ]6 s9 E2 x5 L$ r    display.set_matplotlib_formats('svg')
    / T  e7 h" f4 u: h6 R    # 设置图的尺寸
    # i2 P! P, J7 u/ s6 L    plt.rcParams['figure.figsize'] = figsize$ @2 S( K; N! A2 f( Q

    ' n+ p1 }! p% wdef show_images(imgs, num_rows, num_cols, scale=2):% |. ?; ~5 `  W- X$ h
        # a_img = np.asarray(imgs)& Y" Q. I) w7 f- D( b7 |7 R
        figsize = (num_cols * scale, num_rows * scale)% G9 y% a9 Y+ m1 g0 ~
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)( q. ^7 P- w8 ?$ w4 u
        for i in range(num_rows):
    ( b" h. S; w( {8 a        for j in range(num_cols):! Y$ j( k* F7 y' w$ N
                axes[j].imshow(imgs[i * num_cols + j])" m8 v7 [$ p2 d
                axes[j].axes.get_xaxis().set_visible(False)9 U7 {+ j& R0 z' ~' X7 r
                axes[j].axes.get_yaxis().set_visible(False), {' X& @; k- a% `2 l4 W
        plt.show()/ o9 P5 i7 @8 _- y, u$ a, f/ G2 _
        return axes( d1 V4 ]: n; `- l$ B& w
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。  I& ]5 q& _2 O8 Z5 x8 Z! w& y0 o
    & _/ S" o5 m9 W- d% Z
    # 根据自己存放数据集的路径修改voc_dir
    : E0 c! K8 {, H9 T% V% t4 ]- B: Fvoc_dir = r"[local]\VOCdevkit\VOC2012", K3 e. B. ~/ B8 Y& B7 P
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    6 d- M+ {0 e1 on = 5 # 展示几张图像* p4 E- t* n, |: f$ D* s
    imgs = train_features[0:n] + train_labels[0:n] # PIL image
    9 o! z, A7 n* k+ X& \; Xshow_images(imgs, 2, n)
    : B: a  F" h! K: f
    . H2 Q: h) L0 p2 \( E, z, w 1.png
    , m3 p. j) C0 X. \# x- B3 l, _" H  V2 v) ^! N
    列出标签中每个RGB颜色的值及其标注的类别。8 I# o$ C6 v! m0 K' o
    # 标签中每个RGB颜色的值
    8 r. s1 f& H* }$ W: _$ p( PVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],$ Y; H5 V: `: \# i
                    [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    ; S& g! O; O; b) D6 }# M( |                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],6 M$ U8 U4 Z  {8 L! I) \9 o
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    + l* y/ \4 m6 d2 w: L# I                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],
    * g2 f6 B! a4 _                [0, 64, 128]]
    . u& A8 d0 n: `! E& \, M  C# 标签其标注的类别
    9 h# k8 T: j6 P& W  xVOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    ! T& D  x, M: }& D               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
      ]* F! b" n* x( H, E0 M               'diningtable', 'dog', 'horse', 'motorbike', 'person'," X) C. l7 D: S# b# m. i0 A% {# v! T, X
                   'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']* B- D. S* J2 V6 Z, A
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引,voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    8 B' [4 o' `; u  ]- }/ Z$ ?
    6 U6 D! w; Z+ m! g8 X6 V有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引,voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    5 P: s& G" C% `9 [- L; Mcolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])& s: X5 R7 U$ [/ S
    for i, colormap in enumerate(VOC_COLORMAP):$ k4 E1 G' p) d
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i+ E& r/ d6 s" P# T5 R8 |
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i+ p) Z, H0 a! R1 @- H, v
    " j3 K: h2 T1 s4 V) H0 j/ L
    # 构造标签矩阵' e" Q5 j8 v) L$ X  a
    def voc_label_indices(colormap, colormap2label):
    , Y$ D; `; h9 B* R    colormap = np.array(colormap.convert("RGB")).astype('int32')
    ) S6 [" p% V( C4 j( J    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2])
    1 J- O1 p- T- y/ g- k    return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标; j' s- V4 c$ F3 o

    . z  V# ?, ?" j& p可以打印一下结果) M( F! P( P9 ]* j2 @
    - T3 Y! N- Z: t
    y = voc_label_indices(train_labels[0], colormap2label)
    % B* O6 L! L6 U" w. \# Lprint(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES3 {1 S, ^% K" Z- o* C& U8 P
    , Y3 A: b: ~4 s$ |& v: A
    2.4 预处理数据
    ! o+ C2 b  g; S' t1 A1 C" X
    8 U4 Z3 M4 v* n4 V0 p在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。; U& J! j7 d. Q0 s7 f
    - I+ ?+ r, k+ ]. `" u. u6 Q4 ^
    1 S0 x. C4 ^5 f8 a8 F
    def voc_rand_crop(feature, label, height, width):
    , M' c' I& O. S5 A; ?/ t9 q    """
    : r2 U& A/ h+ H4 n0 ^  F  n    随机裁剪feature(PIL image) 和 label(PIL image).& v. M' Z0 r3 J
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做  t2 z& ?) r+ k, B' r
        Get parameters for ``crop`` for a random crop.
    4 z( f" |6 v/ ]    Args:' D+ i! }7 A9 S$ q- J. T7 @. \) h
            img (PIL Image): Image to be cropped.
    ' h7 l+ P8 h$ }7 D        output_size (tuple): Expected output size of the crop.' Q% q$ K$ v& B8 w
        Returns:# f7 N- c3 l. e8 m
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.! B5 W: D& P0 O; K* {( [3 P7 y
        """
    7 V) y+ G& h+ U2 V    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))( o) F: O$ ?% O& ]
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)1 ^3 T/ L/ c# I- U
        label = torchvision.transforms.functional.crop(label, i, j, h, w); K! a$ S" k$ I9 D- {3 z, A
        return feature, label1 M/ ?% g! G- o
    8 J' _7 p- X* }2 w7 s) _) T
    # 显示n张随机裁剪的图像和标签,前面的n是5  X# ?' w8 ?, O& h; l7 Q. L
    imgs = []1 p9 j: N& c% P& G7 N
    for _ in range(n):
    , D1 G9 d" M' t, h8 ~: ]; j! F/ |( M7 ~    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    % s5 T* k: M, w! w; `# A  I4 ^show_images(imgs[::2] + imgs[1::2], 2, n);  ^& j9 H) A! o. ?  t

    * p0 F4 M% Q% g+ Q; l# D' _; S; D1 h" m( r/ O
    2.png & a9 f3 T$ H/ `- ^( \8 {
    ( X  h4 f* Q" P
    * X( T( H+ Y! j# b$ O, f
    / U/ F# c/ R/ g+ I' I) r
    3 自定义数据集类
    9 H1 C" h) [$ `5 S9 B' \$ `. i: Y2 L
    3.1 数据集类: m" F5 D+ p" H1 f* m* F' f8 Z

    6 g- E+ g7 U6 u% N  k3 e7 |  storch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法0 s9 |; i1 _) m% F  K3 j
    ) m) d( \/ L4 u, ^+ j6 a( `1 ?
    __len__ 实现 len(dataset) 返还数据集的尺寸。  G. l* q, W/ J" s8 w& V5 q9 d
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。# q4 B* L. h1 T
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
    ( A, p3 ^* m4 g" r1 p" c
    6 H# G" X& w5 \0 R4 B! T, Dclass VOCSegDataset(torch.utils.data.Dataset):6 A2 X9 R$ p* {6 W9 O3 B
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):9 a6 J% G1 F& u% t& {3 U
            """
    : L7 g* h0 W  `* k+ q2 c        crop_size: (h, w)
    , X$ q0 N# T, U5 j9 m/ b& r5 h        """& c. }% v8 ]$ }  x
            # 对输入图像的RGB三个通道的值分别做标准化
    7 B- a; J! v% i* k$ W3 R        self.rgb_mean = np.array([0.485, 0.456, 0.406])8 G& i% L  u% l! H3 P3 s: L
            self.rgb_std = np.array([0.229, 0.224, 0.225])
      H: v5 r# `" b# z5 @3 X7 a        self.tsf = torchvision.transforms.Compose([& G$ h/ w! p, O: R9 N  ~1 v, m
                torchvision.transforms.ToTensor(),: \: F, s# L6 c
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])- @% ]8 b4 @% F+ _/ i: s
            self.crop_size = crop_size # (h, w)
    " V  u+ q1 c/ B9 k1 P; u        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)& d- b8 K; E7 H+ w/ ]2 `. u
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    $ m; A, A! V; z        self.features = self.filter(features) # PIL image+ A  r, _6 z; O: J3 V) r! {
            self.labels = self.filter(labels)     # PIL image
    9 ]; n3 s! ~* `$ |) N: J        self.colormap2label = colormap2label
    ! v+ r* m3 E% F$ c        print('read ' + str(len(self.features)) + ' valid examples')
    / H7 H$ g: J6 r7 U% B
    ( m4 Z/ Z! e% T  {5 e9 K    def filter(self, imgs):
    % O, w/ s$ Z; `* p1 b        return [img for img in imgs if (
    ( f9 u% x8 S5 c8 P            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    # L% `5 }% P0 o0 t
    4 V3 D  ?. n0 B# p) A; J* [    def __getitem__(self, idx):
    6 p/ f. T. Z' w* @7 R+ d        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size), l) g& u8 j7 A; S. R. ?9 I% j
                                    # float32 tensor           uint8 tensor (b,h,w)
    2 Z0 k* a6 y4 K- |5 c        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))7 p- ]1 z- e; v( p9 E* s' l0 F
    % ^& K+ C; c* t% ~9 t6 K' p
        def __len__(self):* y& |+ I/ `9 F% D
            return len(self.features)
    1 j( y* |" m4 d4 t3.2 读取数据集
    ; b$ N3 Q+ G3 W: A. w- ^
    ( |( E( c. C7 K0 A通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    2 N* U- D( o  \2 W* p. L8 S7 N, x6 H. E9 R/ }2 F% Z( z# S
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)! i  b6 z" t, ?2 R  D) }5 J. \
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)% u- z! L/ ?' z
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    9 Y& s- c* \  R  W3 \# U$ R) ?; B
    : B) n2 U" W1 o+ @; D6 m# 创建训练集和测试集的实例( Y$ ]( J# u; T' H' p; i. G. }6 m
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)
    ! b3 H0 n- y) z+ R( n; ivoc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    * U" I' i, r4 b; _/ ?  r7 h. }5 H" F+ D2 F1 K8 [" k$ B
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器* @3 o' T2 T! A0 e9 O
    num_workers = 0 if sys.platform.startswith('win32') else 4
    - A$ [% M8 ^. Z: e3 v3 q1 mtrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,& ]9 {  r! |  J, P
                                  drop_last=True, num_workers=num_workers)
    ! i( [( t: w3 ?% ]! v: jtest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,$ V& p  k! V, `5 `; Y% x6 d% C4 B
                                 num_workers=num_workers)
    6 k+ k" I" Q4 Y5 Z' B( w7 Z1 e5 C. d. O/ a& R
    # 方便封装,把训练集和验证集保存在dict里
    & f1 v8 V7 @) K+ ~% ydataloaders = {'train':train_iter, 'val':test_iter}/ X, G% M8 A" ^0 B3 v4 g
    dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}% A8 T; k5 ^% v: ~* ^1 H

    # u+ Z9 z$ ?$ s" \( P7 ?7 v! N4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    - F- k4 C" b$ B; {4 D
    , M0 p1 r" t' b7 R+ vnum_classes = 21 # 21分类,1个背景,20个物体+ D& b& O5 C  B& R/ k% D0 i2 D; O
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    ) w$ i$ F' a; K( E' T) R  |* J5 @8 u; a' K% J
    # 特征提取器" W* j5 a# u: J' L7 `
    for param in model_ft.parameters():; @. D) _* o$ P- \
        param.requires_grad = False
    1 w; l) `* ^( _; ^4.2 修改成FCN
    ) E9 W6 {$ N) H- i. U
    0 Q$ m; ~- U- _8 c6 l$ T全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    . y9 x+ A0 _8 F+ }' A9 d' W: m最终输出的通道包含了该空间位置像素的类别预测。
    5 N, j8 B" }4 j- S. S
    : j$ S' m. r$ l# W8 X对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    & ?* U. e4 c  G. ~- d% p- G
    9 @  T9 W9 {. @' r可以先打印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 倍即可。- s9 G" Q8 l# ?8 l2 w
    ) n  _" J2 y8 h+ k+ S' p- d
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层. I$ u5 R- x( g9 f
                  nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class9 M- ~$ l* T8 H0 |" V" L. R) [. `
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    0 N: E5 `$ g* @9 P4 {& S6 I1 {  d, L9 E
    # 对model_ft做一个测试5 t6 W5 }' e0 |7 k5 }9 s0 `$ m! ^
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    ; F5 X  d, b% L" V6 o+ f" Sprint(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) 1 P; S4 f, X  I
      ^/ R% f9 u9 _$ D1 L2 g
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    & ?. a$ {( h" b! F# for X, Y in train_iter:
    8 f" L( n" g2 X3 T  H9 l( o: d) d) G2 L#     print(X.dtype, X.shape)) X/ p, z, v' r* o  X9 C
    #     print(Y.dtype, Y.shape)5 y* b! B$ s8 K4 S
    #     break6 i2 \/ |5 U; Y; b/ C: p

    9 E/ L3 Y( D( g7 g( @$ q/ Q  h- U7 j3 m1 X7 Y2 I' y& w* y/ S
    4.3 初始化转置卷积层
    0 W% u  F& L! v/ ?4 s- u7 \2 E( e; q& f) o8 a5 h2 E6 ^0 _, W
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像  U2 z4 C! g" D! i% x( {
    在坐标 (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函数构造的卷积核的转置卷积层来实现。
    ( I7 ?' H" |% m/ j) c" p4 j
    , |" L2 Z. \, t8 E1 \* F, k
    2 c4 |8 O& B# d6 _( y( N; u# 双线性插值的上采样,用来初始化转置卷积层的卷积核. i# ]) k& Q/ Q1 V/ c6 g
    def bilinear_kernel(in_channels, out_channels, kernel_size):
      r2 _( B- l( _    factor = (kernel_size+1)//2
    1 Y; C4 p  G, I6 L0 j! `' D    if kernel_size%2 == 1:! z& R7 s9 n& g4 h" I
            center = factor-1+ m- z7 ^1 w$ Y; Y
        else:7 Y. z3 i! U( v$ U1 Q2 b( v
            center = factor-0.5
    2 [9 B( I2 ]* T; v: G" [% u    og = np.ogrid[:kernel_size, :kernel_size]
    0 L  y$ r8 h3 j    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)+ ?' [$ y0 X1 a' e
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    4 A+ s8 R. r+ \# i) a2 b8 ]    weight[range(in_channels), range(out_channels), :, :] = filt/ ]: z+ F1 [. g* k# h: p
        weight = torch.Tensor(weight)! |& y% k- z5 ?7 E
        weight.requires_grad = True
    $ K; t: U  e& t; }& _    return weight4 p9 N- M) p1 ?. g" V
    + [! z8 d+ l5 j5 l* x

    % F7 ^1 z% D2 V- M" Q在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    / c7 k+ s6 w& m+ w6 K+ p+ [& I. A' b* u7 t
    2 X# t) q- D* ^9 ^+ R) Unn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)
    ' }+ M" D  P4 t8 \1 I! S' X1 Vmodel_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
    * r) G5 C% N3 }# v- s
    1 D1 r# ^- O0 U3 t! B- b
    $ Q! q$ q8 R: F( j/ O! l  ]5 q, Z' O8 X. `/ ^1 }0 Z; G
    5 训练模型

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

    ( h9 x% B1 ]# C! X9 e
    def train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    ( ^  U  Q& [# N2 `    since = time.time()+ q& ?8 ~" n1 c* E9 z1 H
        best_model_wts = copy.deepcopy(model.state_dict())
    . j/ x" p  F& X; ^0 w  g! @    best_acc = 0.0
    # T8 o0 \# ^: |+ g# ^8 f    # 每个epoch都有一个训练和验证阶段
    $ x" N+ M. U/ p- H) i: I    for epoch in range(num_epochs):
    3 {6 \3 {, q0 d0 ~" {        print('Epoch {}/{}'.format(epoch, num_epochs-1))
    / q, B3 r& c6 L  C2 T) h+ G* h        print('-'*10)
    / e( C1 X$ I0 @- X  R3 b0 q        for phase in ['train', 'val']:6 y( ~+ l# [6 d5 l3 m
                if phase == 'train':
    , d4 u) D6 [7 |                scheduler.step()
    ) w: l- H' e. [* c                model.train()
    " E2 v, ?; R: @3 C1 n' j! q            else:" L# X* a6 Y) _
                    model.eval()2 y; w& H' y* G6 B4 m
                runing_loss = 0.0* |  @; F, M, W
                runing_corrects = 0.0
    + W! f) ]- t1 q. W( g1 a$ v            # 迭代一个epoch
    ( ^  q% F, }. q4 a( C2 C# ~/ h            for inputs, labels in dataloaders[phase]:
    ! p. ]! V9 W2 Z5 ?% ^' W* N                inputs, labels = inputs.to(device), labels.to(device): E) T$ r( Y! @4 L$ T2 c4 ?
                    optimizer.zero_grad() # 零参数梯度
    4 y, S& o% M) u) H                                # 前向,只在训练时跟踪参数
    ' U7 [1 c- x  u' l3 q6 a3 J                with torch.set_grad_enabled(phase=='train'):
    % N4 R* c- j$ V$ w                    logits = model(inputs)  # [5, 21, 320, 480]: T0 Q) N* [5 U) q
                        loss = criteon(logits, labels.long())  Y- U: H5 i: ?, B& O' {1 q7 Z3 K
                        # 后向,只在训练阶段进行优化
    " p4 H4 h& ?9 c& q  I) T0 @9 ^                    if phase=='train':
    3 ?0 d( ?. V, v& }2 Z0 u                        loss.backward()
    ) B6 r: v6 C; z: j& l( a  F% ~( d' o                        optimizer.step()- e3 T# Q6 [9 @
                                    # 统计loss和correct
    4 }4 n9 D2 s; N- T                runing_loss += loss.item()*inputs.size(0)
    % V* r& p1 x/ D- }# G3 @; k                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    $ b2 ]0 s) V) o" C$ p& X" h% x" O7 A2 m; J7 U; E
                epoch_loss = runing_loss / dataset_sizes[phase]
    / Y( P, K8 g8 a# p2 ]* g9 z- ~            epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    * U5 K5 u+ G8 X$ f1 `5 U7 f            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))- @* h% d8 C) N" }- p  c
                            # 深度复制model参数% U# y+ N( z/ O9 Z7 ]* M1 o
                if phase=='val' and epoch_acc>best_acc:! L; S! h' Z% m, a/ I
                    best_acc = epoch_acc
    - d7 U6 I1 [# E; ~' B! O5 n% j6 W                best_model_wts = copy.deepcopy(model.state_dict())# [  g* @1 y3 L5 B4 U
            print()  `. x  d: S; s0 f$ H% O
        time_elapsed = time.time() - since;
    # U/ W" E) W& {    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    ) ~$ U) U; h* r3 m; y    # 加载最佳模型权重- c5 c4 D6 R; v4 L
        model.load_state_dict(best_model_wts)( j3 D; N' Z4 E4 g' q0 b5 K1 k
        return model+ Y; E; _% g7 r3 n% K2 u- |! G

    # m6 j( p. L/ i$ ^下面定义train_model要用到的参数,开始训练
    + ?# t- `; e0 N+ ?  m* N4 u: y# v
    ( L5 E( Z( M' l; ~) s" ?epochs = 5 # 训练5个epoch
      t* z: ]/ k% ncriteon = nn.CrossEntropyLoss()  m7 u% X( t; z
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)( m) \/ w5 w  m! }
    # 每3个epochs衰减LR通过设置gamma=0.1% |: x0 M. K7 P8 U9 o) B
    exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)& }3 c5 x9 s" A9 ^5 D; M; ]

    $ A3 `+ w* {$ o6 S1 A# 开始训练
    " N4 T! S+ T. c4 u) b& omodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)2 h* u4 R1 X, d

    # [" a4 n# y1 l+ K5 j: T6 测试模型

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

    def label2image(pred):
    5 F+ `% M" E5 Q3 n( I8 M    # pred: [320,480]
    % c, e; [2 X8 ~" s1 [, ^2 k) S    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)2 @2 _4 g5 x+ k
        x = pred.long()
    7 j' B  V3 {+ c+ X8 f) v    return (colormap[x,:]).data.cpu().numpy()
    . o" H: b3 o# B1 M2 i& q' {
    9 [# D5 D9 x$ T; T& S- L

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)- X6 v. X( [( Z
    std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)6 Q# u; _" g: C8 i/ b5 b" Q
    def visualize_model(model:nn.Module, num_images=4):
    ' J1 X+ J$ v# R$ w    was_training = model.training
    / Q1 H6 O+ M' D9 R! g8 j    model.eval()
    2 _1 u4 M. I# t" c6 H2 @. b    images_so_far = 0
    ( \: T" b2 Z; h1 q    n, imgs = num_images, []* ]5 j8 \* F4 b# |8 k
        with torch.no_grad():0 f9 j, V% y6 B) l4 H
            for i, (inputs, labels) in enumerate(dataloaders['val']):
    - {2 \! @/ c% ]: n2 T) h            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]
    2 ~& t" ?2 F6 j4 Q) a$ ^* y! K            outputs = model(inputs)& Q; ]& T8 z0 `! |% O1 L, G( Y
                pred = torch.argmax(outputs, dim=1) # [b,320,480]# h5 g0 Q7 h% k
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦& o+ n( d  `6 {- o

    ! |+ E9 ]: J7 S7 i  e8 @            for j in range(num_images):
    9 z3 C& \% R* p- B2 _  l                images_so_far += 17 ^5 Q/ j! K  Q' B7 O
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)
    5 _* k' w0 `2 V% O" S                imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])], N6 R9 @0 x' T% W$ U5 t
                    if images_so_far == num_images:9 C+ o2 F0 K% A
                        model.train(mode=was_training)
    " e7 _; L9 ]7 i' p: L6 L/ Z' s                    # 我已经固定了每次只显示4张图了,大家可以自己修改
    2 I' g+ |: [% l                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)3 k6 y4 N/ v" @3 d/ L% o+ J
                        return model.train(mode=was_training)1 F& \  U' q1 G& o4 e$ k

    # ~  u; o% r& t' F- X# 开始验证
    % N. S# ^, A9 `* ]! rvisualize_model(model_ft)
    ) W; m2 v, n) M9 E& v& X! I1 u6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    ! n, H% T2 {) \9 |def predict(img, model):7 B. R1 E1 D$ w- a2 o9 `$ z9 c& _
        tsf = transforms.Compose([
    & h) P" }4 N2 r9 p( F0 Y; y  f            transforms.ToTensor(), # 好像会自动转换channel( k6 {8 P1 D  l# [
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])3 W/ r5 O$ P/ c4 f& a, x  W) t
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    ! v* V" y0 s% t" h& X( g    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
      m  n% ^: V/ \    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    . \- w+ _0 x: C- _2 ?" [' J, j" c& Y! ]& ?$ ?8 U. V
    def evaluate(model:nn.Module):
    # m- R) K9 j' [7 q7 E7 w    model.eval()) D9 }2 \3 ~0 q- K  F4 ?1 s
        test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    7 y/ f3 O+ z! r* `( m    n, imgs = 4, []) ]+ `% t4 ~& \0 p& e
        for i in range(n):
    7 L- P/ e. b; C: f8 `+ g% l        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    3 D$ {% ?# D2 r3 O  C0 p. a        pred = label2image(predict(xi, model))
    . y* ?$ d# a! s5 I. y  i        imgs += [xi, pred, yi]9 f* k) a" I" u/ `8 |. O
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n): T: t  L# S3 o7 d7 k* d

    6 w; G' x) r4 o6 [# g( x- z# 开始测试
    & B, D  N* E5 @, q* t$ xevaluate(model_ft)
    / i5 h9 {4 Z, y, K& f
    4 q0 P; ?  c: i$ |7 p7 结语

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


    + e/ |3 {4 w+ v2 f7 ^  nEpoch 0/2: y, }- q/ c& t; \) C
    ----------
    1 G6 O) w/ g/ U, I7 ^* ttrain Loss: 1.7844 Acc: 0.5835
    1 q+ \4 i- @2 ^+ y) ^8 Ival Loss: 1.1669 Acc: 0.6456
    2 ]- M+ G. [5 l5 T, p3 J8 F& a
    0 }) P  ~5 C- h8 a! sEpoch 1/2
    8 C; o& ]5 X2 n( s6 ^1 r# s# F----------" U( o* V5 X+ D2 E- H) P
    train Loss: 1.1288 Acc: 0.6535) o3 S; @; ^( c# R
    val Loss: 0.9012 Acc: 0.6929
    " ?: Y! v' W! q2 p! J6 c
    1 ^+ ^3 F' c9 m  ~Epoch 2/2
    ; o  R) S" k2 i  d2 K----------
    6 t& g; u/ s5 c; p- c/ Jtrain Loss: 0.9578 Acc: 0.6706
    4 |' H, a$ X) Z' l1 \: c9 j& Yval Loss: 0.8088 Acc: 0.6948
    3 x' m, a: A* _# P- u) K
    " |! w, P" [7 O7 X$ f' eTraining complete in 6m 37s
    / P* E8 k6 O5 Q1 ^0 L' m$ A4 A- H  g& S! ^" T+ V4 q8 q6 N
    # v  Z! N/ |/ f9 \' W- ?$ [( C) a
    2.jpg # c4 L0 P- P/ w0 k
    ; G7 ~# |. p9 F! S+ W: i9 o
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。# \; o, O- u) G3 ]5 k
    2 [0 N' L) g' j& w1 g
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。; |( F. }$ ]: R# G' ~

    $ E+ C( ?4 e; o: Q+ z3 g  x9 q# I+ R: n6 j
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:
    $ o' F' h/ B1 ~, j8 Y8 w' Y$ ?0 _
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]
    ( X; k! @( t$ K1 ]9 t% Z" M+ K, |GCN 通过全局卷积网络改进语义分割[论文]; e& a2 b3 y# ]3 W. f
    UperNet 统一感知解析
    * S/ l4 r! `% f, ^ENet 用于实时语义分割的深度神经网络体系结构[论文]
    " J+ b) w, m: OU-Net 用于生物医学图像分割的卷积网络
    " E9 R' k1 ?2 n5 Q7 S) wSegNet 用于图像分段的深度卷积编码器-解码器架构。
    4 s4 v: m5 `* K+ \还有(DUC,HDC)、PSPNet等。
    0 H' J: c$ A' B# m4 K5 T% V4 d! k' B3 P3 I( t2 s# U
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    7 K6 ~5 h+ K6 l% O) T9 K, J
    5 P4 o1 S/ l# w/ ?6 ?9 ?对于损失函数,除了交叉熵误差,也可以用这些:' n+ D. L+ G  y9 c5 r: ~7 K

    - E4 _8 l- B3 H3 |& H" y) FDice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    ' k5 \- i% A' ?: p( N" C6 nCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。( S  i6 \" U4 t% P  A4 o
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    " h. A  k& p9 ?Lovasz Softmax 查看论文:Lovasz - softmax损失。! u3 V5 \# w0 w4 B) `

    5 L6 s. ]' h  T3 Q% v  z1 D* O) h0 N

    # \8 [8 Z6 l" R8 i
    7 R5 @  O! o) h1 b
    ! C1 a; j: e: C; B6 F% K8 i————————————————
    5 F' h5 k9 E# Z  o3 \/ Z  z3 b  T4 T' \版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。, b  y# q* G5 ^0 V
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507- m  |( ^) `. W: [$ W$ I: k( e

    5 f$ `, j, h% _9 q( u! h- ]& @
    % S1 P5 i1 [7 i4 v! V- Q, z7 r3 P
    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-10-18 09:14 , Processed in 0.499359 second(s), 54 queries .

    回顶部