QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 11192|回复: 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 P& E7 m( v2 O' V* F- i
    " Z$ k, s/ X9 S
    Pytorch实战语义分割(VOC2012)
    * }( ?6 k) ~  i. j. a+ x3 q本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    3 a0 |" u& T# N+ u  [语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    , Q' U% b, ?5 n3 {8 Q3 f+ A% e; Y
    . n: Z7 M4 B: T% H
    % A! a7 R1 T; }( V' v) r$ W语义分割中图像有关狗、猫和背景的标签8 x7 n" D" n) I9 t* o* y1 n9 ~! m! m
    文章目录
    " @. R$ v! S* A) ?2 l3 {( i
    3 l$ w! j" z/ X/ n9 @* [; B& T; N1 图像分割和实例分割
    ( l1 `: N1 L, @6 c/ {1 V2 Pascal VOC2012语义分割数据集8 ]+ `$ f5 @2 W0 V. H9 A
    2.1 导入模块
    7 m6 ]9 u/ _- y! d  _% F( A2.2 下载数据集' I. _% ?. j3 X2 C& b6 \+ F
    2.3 可视化数据
    6 u+ M5 D8 Q) V2 o, Y) l: j3 _4 P/ Z2.4 预处理数据! t1 T# G+ [$ C: R, ]. y4 x; k
    3 自定义数据集类7 c* H1 N9 N0 X4 Q3 F: i6 C( X
    3.1 数据集类  n0 V8 A( Y. ?& _0 q6 p. Q
    3.2 读取数据集
    ' o% s- a2 V" v+ }4 构造模型0 R6 E" ?" h; U& C/ s. n! W
    4.1 预训练模型
    # N, c3 U, c: f6 R/ g4 t, T4.2 修改成FCN
    ! C7 `0 |, E+ b- W/ `) b" u) r4.3 初始化转置卷积层
    # e) f: }' k9 z+ F! F5 训练模型
    ) u) ]5 e3 Q; I) ^6 测试模型
    & j  L( n, k7 L/ t. q6.1 通用型2 w8 s6 M; A4 A6 G
    6.2 不通用
    5 y, u5 D! r3 E$ v* s; @7 结语5 y3 r5 V: a, M/ U% [; M6 R
    1 图像分割和实例分割
    ) E9 D8 L& d+ ?" l' J- ~8 F+ z: o: Z  p5 e9 ~) d
    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):" S6 Z# S8 |' G$ [+ k; T

    & x9 D- }" m7 r. s: V: {+ r图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。) y; @1 R! |; C% H$ [# v
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。
    5 f  {  [$ l7 v  b. g* y# [6 o6 P/ [' _1 x7 x; U5 z7 p3 v3 ~& H; [
    2 Pascal VOC2012语义分割数据集9 b5 X6 }8 v' S$ C* \
    1 o% E# p& v7 F% p* x) L! Z
    2.1 导入模块
    / ^- h- F0 ~$ Z6 v+ H  e; B; B2 T, pimport time3 ]5 ~3 x2 m, I# \3 ~
    import copy- u6 s- P: F: d1 Y* J
    import torch, O8 X! ]. i7 w
    from torch import optim, nn
    2 b5 a0 |' U! h3 g2 d4 Q( Rimport torch.nn.functional as F
    # ]) `2 I* B9 w5 aimport torchvision
    / B/ K6 B. b7 V% u( efrom torchvision import transforms- M  v0 S6 ?9 x3 t0 }
    from torchvision.models import resnet18
    ' U' r$ d% f! n- Nimport numpy as np
    % ^6 O; A# }5 t- B' S4 J& Y+ ffrom matplotlib import pyplot as plt
    ! q$ _% P/ ]8 x' M2 I0 Qfrom PIL import Image
    - B4 h, E0 _; F  i, |) o* X. mimport sys
    8 ]0 m: _- [' h; M3 s7 F1 Wsys.path.append(".."): G, I- [  Y2 X$ ~9 `
    from IPython import display( ^6 S+ C% g2 E
    from tqdm import tqdm
    ; ]- P* {  K8 t- u# i5 U" `import warnings
    0 L5 W& R: [! I, {  w$ Z' Iwarnings.filterwarnings("ignore")7 v' \% g9 p; }

    9 Q8 h2 V, _5 K1 n8 Y6 |2.2 下载数据集" d$ i2 v/ c2 L. |, ?
    1 h- g8 ^' c# t3 K) f" o6 V: }! Q
    语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    ' p: G2 _: V1 G" D  S" ~! y- Z2 M# {1 J& P' v  ^

    ; R. y) H. S9 P7 KImageSets/Segmentation路径包含了指定训练和测试样本的文本文件
    - Q% S8 I2 y( a* s4 g* w1 GJPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。$ d& b- R9 g( `& n
    2.3 可视化数据
    5 u5 ]( D& e: @! p
    ! f. A7 c. S% X6 {' x0 p2 U定义read_voc_images函数将输入图像和标签读进内存。
    5 i6 @3 t8 P" Z! x7 n3 u
    6 p0 _' g& V$ y' }def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):0 a0 O% z- G7 m  `( G! }
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')1 g; G0 J$ M0 ~! n  W% k4 a
        with open(txt_fname, 'r') as f:9 a% m# ^& M+ f# E+ ^( U
            images = f.read().split() # 拆分成一个个名字组成list
    ) i+ a: d5 U; N8 o    if max_num is not None:8 D: I0 L. `/ A9 A' W" H
            images = images[:min(max_num, len(images))]  D: c3 G4 N! k; v
        features, labels = [None] * len(images), [None] * len(images): H, n3 `# N: `5 {+ S
        for i, fname in tqdm(enumerate(images)):% J8 C/ a% E' A- w# K6 f6 y
            # 读入数据并且转为RGB的 PIL image2 Z8 m5 X- q% I
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")9 G8 e& I, d  T& J0 J
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")3 l! S. `( D. n7 L9 ~* C5 ~
        return features, labels # PIL image 0-255  k3 @! g$ N& @' T9 m
    6 N( @' N1 s! S7 C& A
    定义可视化数据集的函数show_images
    ( R6 y" C2 Q+ ~0 X4 J  r2 J) h: U) J0 R4 b1 w& R6 ^, R
    # 这个函数可以不需要
    ; _0 Y+ m7 C% I+ F8 Y  Vdef set_figsize(figsize=(3.5, 2.5)):
    $ P' H4 k9 b; I  p6 Y    """在jupyter使用svg显示"""1 N4 z5 P5 ~2 }- ?/ ^
        display.set_matplotlib_formats('svg')- q8 I  W! c7 n+ Z% y1 R3 Y+ n
        # 设置图的尺寸4 m9 @3 a2 e7 _3 @& B
        plt.rcParams['figure.figsize'] = figsize! U/ e. H/ d$ Y3 U! i
    * Y" z6 c3 ?0 y, F8 P" H
    def show_images(imgs, num_rows, num_cols, scale=2):
    8 y' {+ _5 b7 l* B    # a_img = np.asarray(imgs)! Z4 y; E4 y( H: Y
        figsize = (num_cols * scale, num_rows * scale)& \! G8 J5 b- U% `: c2 H9 p  ]& m
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    : y  c9 _! [9 q# a0 @7 _! J# C/ B    for i in range(num_rows):" f) g! q) r& L; E7 ?$ g! v
            for j in range(num_cols):- f+ C" A- c0 M- J- d. ~
                axes[j].imshow(imgs[i * num_cols + j])
    & K+ t* q& k& ^( l6 m+ g            axes[j].axes.get_xaxis().set_visible(False)% K1 O; u4 n) e' N- ~, V2 o2 {
                axes[j].axes.get_yaxis().set_visible(False)
      o/ j9 u, k8 v0 R  @, e2 k9 Y    plt.show()# F: `: r1 U$ ?, X. G+ q4 J% j
        return axes
    / U  J) E3 w; C% l' ~$ q" b
    ! W5 N3 A& w4 G定义可视化数据集的函数show_images; L. z" }9 S6 N- g5 Y% G

    8 [5 R0 k. d! m3 G5 N% W# 这个函数可以不需要
    6 i$ W* p4 O3 H# G: T# Hdef set_figsize(figsize=(3.5, 2.5)):
    5 ?2 F  {- n8 z    """在jupyter使用svg显示"""3 N: P6 Z! x6 ^1 B: Q
        display.set_matplotlib_formats('svg')
    7 B0 L" G% \) v9 D/ W3 h$ W1 }  E    # 设置图的尺寸& N; D" {  d$ A: U: c
        plt.rcParams['figure.figsize'] = figsize
    ; ?; W8 ?6 D1 I5 q- y) g" V: P0 g5 c- g: y8 A& C) a+ y* A1 ?
    def show_images(imgs, num_rows, num_cols, scale=2):8 o) R2 x2 A  Q5 n' D
        # a_img = np.asarray(imgs)
    # z8 F: X- ~- _8 w( v' `( @    figsize = (num_cols * scale, num_rows * scale)
    3 G. [6 R# p; ~6 \8 T    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    ; n4 L6 w# A# M; z    for i in range(num_rows):
    . p2 p+ m8 t" N" w  n5 V        for j in range(num_cols):
    " ?& n  a% s; W( W- b            axes[j].imshow(imgs[i * num_cols + j]); Z5 \/ ^3 u, X. c8 P$ w
                axes[j].axes.get_xaxis().set_visible(False): J7 B8 s/ M0 D
                axes[j].axes.get_yaxis().set_visible(False)$ l+ l2 s; p7 k; H1 D+ p
        plt.show()2 A  [% l/ s5 u# S  D' V& Y7 u5 x. U
        return axes6 A2 B9 }+ O5 q) v/ C6 f: J
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。& n: V7 P7 x. V0 M- D0 \

    ' s: V7 a  h8 _0 J# 根据自己存放数据集的路径修改voc_dir
    7 u/ m6 d$ @, @4 K7 O6 xvoc_dir = r"[local]\VOCdevkit\VOC2012"
    / l5 ?, y$ L+ etrain_features, train_labels = read_voc_images(voc_dir, max_num=10): ]/ O9 o( m( v. G: S! {  L! o
    n = 5 # 展示几张图像3 D2 m8 Z7 G. l' n
    imgs = train_features[0:n] + train_labels[0:n] # PIL image; N0 g0 r. o' F  U0 q+ s
    show_images(imgs, 2, n)
    - N' I7 q6 Z# ^( @" D. }/ _% _2 P* J% p' P( Z! o
    1.png
    $ @- P! Y* h1 F7 [) f0 J. |. G; y% O+ l3 e0 Y5 w
    列出标签中每个RGB颜色的值及其标注的类别。9 n. z2 A) e- q9 W  C4 C
    # 标签中每个RGB颜色的值' p( V4 r. C4 M$ _5 ]. K( o
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    ' Q5 \, O% }2 [, Z5 ]                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    ! Z" ^# w. x  t0 K( U: [# E                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],' `  `8 i) w# \2 I" e
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    , h: g  Z# ~( }: P                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],$ l& f" J4 U; K, E- v; q" ?
                    [0, 64, 128]]
    0 I3 Q: o( b" X! E# 标签其标注的类别6 [8 A) O. z) q# B+ {  p1 v1 @
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    1 H" H/ u  l8 l               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    ! ]9 Q* c* C4 Q7 A* p( A4 U               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    . t) ~3 Y$ ]6 M! a               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    6 A5 z4 W; z& D  k; Q$ o' b有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    4 B! u0 t' S" x: o9 a/ C) A  A  D( Q8 c* e7 a
    有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。5 \, H* n+ k! E: K: H% s& w1 Q( u
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])! n+ I- h. Z3 u9 \) u( W$ B! E% U
    for i, colormap in enumerate(VOC_COLORMAP):' ^0 X$ L0 P2 C: c
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i; b4 k1 ?& j) B5 J5 q
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i. G2 `( o4 i1 O* z
    / E! y! _% ?% f# w3 {3 {
    # 构造标签矩阵) Y9 g  P  y& Q9 K( C$ H
    def voc_label_indices(colormap, colormap2label):) Y. h# Q# b& a: Y1 d4 O; ]$ @2 K
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    7 N$ @8 E" L# W+ o" F* C    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) 1 v7 N9 @6 v# x$ ^
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    + y4 v& E- H  M5 ]5 t
    2 [! d% C/ D: H, J! o& P可以打印一下结果! ~$ f8 V# W7 s; A" _; ]" T# d' i

    0 i# k. L: L; S4 c: Y! ly = voc_label_indices(train_labels[0], colormap2label)# ?0 y- i% |; I% a, T
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    ( C  }' x/ ~5 l6 f3 M0 k+ R1 C3 M" [% n) \7 W/ m
    2.4 预处理数据
    ( Z  z( S! b7 m* J# R8 Y
    5 N  o1 C- }  w$ R2 S) l1 }在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。) z1 y: @3 D  w8 ^! }% i7 w

    . g9 b9 o1 a! g. \4 {1 l
    ' ~% i# K" H; i9 e# G1 [# \, Qdef voc_rand_crop(feature, label, height, width):$ ]& t% B; K3 l2 X* Y
        """' A. j3 X: G! G. E
        随机裁剪feature(PIL image) 和 label(PIL image).
    ' f% {, q* ]% @    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做
    ! Q) D9 t( m9 `# G    Get parameters for ``crop`` for a random crop.8 r* X& |9 y0 S3 q: d7 A
        Args:6 y- P7 h+ [# ~7 n5 {
            img (PIL Image): Image to be cropped.
    0 V) c- U, l4 @, f5 k; c        output_size (tuple): Expected output size of the crop.
    $ b* T+ I" f- k9 j3 B    Returns:
    4 {2 h6 b; h# Z2 ^7 C& H6 o        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
    $ r  E& D/ `; l* M/ C# _; D    """
    ! s/ S# t5 y3 X7 N    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    2 p# q! [; T" L: T: S# b( s    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)
    4 M# P5 i- R+ [" Q7 c1 }    label = torchvision.transforms.functional.crop(label, i, j, h, w)
    ( W/ f6 c. t8 {: [1 w  ]    return feature, label
    ' m( v  S* x1 Z
    0 V! B! V. S, q3 a: l# 显示n张随机裁剪的图像和标签,前面的n是5+ j* @8 }8 d7 w, r7 I! E
    imgs = []3 a6 J5 V3 M% n
    for _ in range(n):% b( z4 _! H1 r2 J6 K
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    / k7 l  Y6 g- }6 J' ?- fshow_images(imgs[::2] + imgs[1::2], 2, n);' Y7 h7 I  T3 f

    : y; z9 F0 w0 j# ?0 H! P
    ( k  P: Y$ |- Y- x* J# C 2.png
    / V6 W( l9 P+ e: g! c4 W& H9 G# J+ g
    7 w7 a! j# z5 M! L% w  P  `. h* z% W5 J4 t: ~
    ! q# ]. e/ s9 W% r/ S2 c7 L
    3 自定义数据集类6 O! X$ O' d0 M

    " h: P1 H( Q3 y2 c7 [: X3.1 数据集类
    + }% z* ~: X. o- Q9 _8 U% x# ]6 @5 A6 ^" S6 T
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法* T: a$ |1 ]* F8 I  _( j/ S
    1 D; ?8 Q/ g* B1 a( x+ g! c
    __len__ 实现 len(dataset) 返还数据集的尺寸。$ t  y& r4 B9 e" `
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    ! F3 \: r2 l) F8 d4 b由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。; b: U$ n5 n; ?# H# L

    % |2 A6 S4 N% H0 E, U8 {! _class VOCSegDataset(torch.utils.data.Dataset):7 E0 j5 L- o/ M' o1 @
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):$ q8 j3 F  z4 p' G- j4 d1 b
            """
      K/ J- w( O1 f) I1 n8 H        crop_size: (h, w)
    ' E; b. H7 I" u9 g0 k5 I        """9 y7 s. Y7 O6 U6 Z
            # 对输入图像的RGB三个通道的值分别做标准化2 J5 X+ m7 {- R: y
            self.rgb_mean = np.array([0.485, 0.456, 0.406])2 G( B: r4 [" E) x4 Y
            self.rgb_std = np.array([0.229, 0.224, 0.225])
    , P3 U/ o8 q* ~        self.tsf = torchvision.transforms.Compose([5 E2 v# K2 ~5 M" @% K  h
                torchvision.transforms.ToTensor(),
    8 r9 F2 G# E1 z: ~# b6 L            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)]). R: X; c  O0 Z7 [
            self.crop_size = crop_size # (h, w)- F0 J2 |" e; S5 W) r
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)
    # m$ l/ o' T& n# Z* o# 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    ) W: |+ m8 S# l) _' J        self.features = self.filter(features) # PIL image
    : ]" [. H# T/ T/ e9 ?+ q/ f  w        self.labels = self.filter(labels)     # PIL image8 ?2 L: ?3 c7 ]& g+ a2 c# o
            self.colormap2label = colormap2label0 _* |5 n$ x) i0 ~
            print('read ' + str(len(self.features)) + ' valid examples')
    9 O' @& c3 S& X9 i& N$ A( J5 E# J# G- N. W3 L2 M+ ]+ b  x
        def filter(self, imgs):- I: }' i% E* K+ d( u
            return [img for img in imgs if (
    0 i- u4 B! T# @% r% W3 S; e            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    5 D' U! N: [5 K. j3 L9 q! d: U
    1 }9 j* {5 `: C% }- r' C3 T: t9 A    def __getitem__(self, idx):
    $ D' U, Q9 o( c* v        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)# J+ S" }: ?" |8 N3 p
                                    # float32 tensor           uint8 tensor (b,h,w)
    ! r3 K! h/ A/ Y9 e+ ~* d" N2 C7 {        return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    , V5 |- {, t4 d0 @* S2 x
    7 ]) R3 R& _2 ?, |) w# j4 }6 {    def __len__(self):! ?' F7 b( Z% |( k5 M/ v+ C5 I# m
            return len(self.features)
    & T2 B: k; S/ U( {3.2 读取数据集* R5 e) s: `* q( ^. `4 w
    * U& K3 S5 M9 d; ?+ ^+ Z
    通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。
    ! q/ V" t6 z: c" n& A4 P, ~8 ^2 x4 {. x* {. x7 t
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
    , q7 L: W) a+ B: W+ x* Icrop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    ; p1 a# Z7 ]& A! N2 Q2 g1 Ymax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~) p8 T* z7 B; M7 L" N* n

    & w& q/ Z5 p: A, G# 创建训练集和测试集的实例
    & d2 N6 j$ f5 `1 N* g1 @6 E9 `voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)- p- i; n8 z3 R' {/ g/ j( N
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    & E* a3 F1 w7 W/ _8 t. Z5 w" r
    ' [; ~! T6 X9 q# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
    + N. ?9 ?: r* P0 _num_workers = 0 if sys.platform.startswith('win32') else 4
    + ?: G4 m: J/ ~( l0 ~train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,$ X/ _9 ~; g/ s0 g+ ~. H
                                  drop_last=True, num_workers=num_workers)) E6 K$ i2 y4 o/ P! H- n
    test_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    " u. E- M+ H* u                             num_workers=num_workers)8 `+ g/ A. a0 ]) p- a2 K
    % c$ ?& G( U2 z+ e1 X
    # 方便封装,把训练集和验证集保存在dict里
    ! k3 G6 B4 X( xdataloaders = {'train':train_iter, 'val':test_iter}
    0 a) x3 s/ N4 n; g# H- \/ r# v! r& [dataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    . H% q' I& ]" f# o  F' |8 [/ a4 ^1 I
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')- E! b+ m' J3 p8 v7 `) f/ e/ E, c- V
    , Q- v- N  V. p- _. Q
    num_classes = 21 # 21分类,1个背景,20个物体
    + G7 W7 O' H& ^' U! N5 qmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    1 I* v: R  V& I
    6 Y0 G# s" O- r3 q- ^4 I+ G# 特征提取器% F& Z& ~  q0 P7 @; d' R
    for param in model_ft.parameters():  _/ J! ?5 M0 t- h% _2 P4 M9 l
        param.requires_grad = False; W& F" g6 G2 b! P. s  C6 t
    4.2 修改成FCN- I3 ^% A* z; |  b- K

    8 E; @+ X7 J1 ~* g6 L全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:7 v6 _' o+ j( V/ V
    最终输出的通道包含了该空间位置像素的类别预测。6 G% g& I1 i" [
    $ Y& l) {( S0 _8 ?4 A  r
    对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    ' V) @4 V8 g) I, E3 h6 u  S
    & D3 T' x) P  V" y1 h  b可以先打印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 倍即可。: u: j2 }7 y, _# W0 ]5 _
    5 T; a  a- q/ s2 x
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    7 j! H9 n) w$ q$ v7 C& f+ k5 A              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class+ z  |4 {/ \" \# Q) z8 K
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    $ f- k% h% b$ R. o6 u2 M9 V
    9 x1 C8 d- f+ ~) [/ a* ?8 v# 对model_ft做一个测试
    2 G" i' \/ a8 Fx = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    . ], S" N2 b, X2 H8 Tprint(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) + q& @- [! |* k- [
    5 ]+ [: `# k9 v- }
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    # @: y4 e4 R2 t1 R( m6 v4 Y9 P# for X, Y in train_iter:
    ; i/ Z* @: G% N: m#     print(X.dtype, X.shape)
    5 W- a) X3 s) ]" ?#     print(Y.dtype, Y.shape)
    : ~% C1 {: D: J$ Z$ h#     break
    3 H4 Q# U$ d/ v, b
    4 q9 k- R* A: F- M4 w- B
    " o! t- J* M$ c4.3 初始化转置卷积层5 k( o3 A+ r7 }% g0 R4 G

    1 D: J* P$ Y8 ?9 j: e5 t' C在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像! s! t  K& ?/ Y. n- a1 z8 {/ Q, @
    在坐标 (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函数构造的卷积核的转置卷积层来实现。8 ~2 ?: q, {. t% J

    6 V( K8 V8 `6 G: N. i/ K! ]; a3 W; L
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核
    3 v1 s; X2 i9 P5 ?* _def bilinear_kernel(in_channels, out_channels, kernel_size):
    ! {6 I6 i! N$ o4 ^" l2 x5 k" A1 b    factor = (kernel_size+1)//2
    + ?8 f# x  M2 G$ a+ y2 c    if kernel_size%2 == 1:
    3 \8 M( p/ F7 W        center = factor-1( a1 e( `  U- E; ]
        else:9 T- N$ v  Y5 U
            center = factor-0.5+ ^7 w4 s" j2 w1 l. h; z) u
        og = np.ogrid[:kernel_size, :kernel_size]
    $ Y/ G& E/ e$ v" }6 o" b$ _: q    filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)5 }9 W1 U( N! Z% i6 P
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    - |$ {! J4 Z+ j    weight[range(in_channels), range(out_channels), :, :] = filt
    , v# F2 w2 G) B+ J. j4 P    weight = torch.Tensor(weight)
    9 Z7 r( g0 h7 ?4 s! K$ k3 a    weight.requires_grad = True. X, Z  A: W& r1 n& \9 E
        return weight
    3 ~7 A; Y2 t/ s# p9 c& O) A' B4 E3 u) @+ H: Z# j/ b6 c
    / |# ]# A0 O. Q7 c4 y5 D
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。4 u+ ?1 Z: c: y" Y( \( a% [
    ) I% m2 ~1 a  ?0 a* M) Z, s
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)  x/ z" D9 d8 E5 t
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)0 U3 u) ?2 d# s& c4 v; O
      e2 Q5 C% M2 y" L0 D3 f  t

    " O) P' w6 ]0 L/ L% ~
    6 _- w9 S: p$ E! e5 训练模型

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


    . A1 m. K+ V! L  O7 [def train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):" o9 W0 O2 U8 T* ?- z- s/ @
        since = time.time()
    6 z6 Y" s; {7 z9 x/ w$ g1 W    best_model_wts = copy.deepcopy(model.state_dict())
    / Y8 |6 \' c: F! v3 a    best_acc = 0.0
    # `% m& o/ e* k7 g# R; l& X4 ~) L" C    # 每个epoch都有一个训练和验证阶段% _5 g  V: x9 [
        for epoch in range(num_epochs):
    1 n6 x' r# B& e        print('Epoch {}/{}'.format(epoch, num_epochs-1))
    % ~8 ~3 E1 \, n5 \) }( U  s        print('-'*10). r) w. t0 l. F$ @, w
            for phase in ['train', 'val']:/ O5 r% @# V# T0 R% b! l
                if phase == 'train':) T( P, B& B" f2 O! X" O
                    scheduler.step()
    / P$ ~5 \# `6 _, R  i0 n6 s- |: ^                model.train()3 `. E# }# x' m7 y
                else:
    ( k. [+ Y, _; Q/ u$ i) r                model.eval()
    6 d. l0 t& w$ m( B1 s* x            runing_loss = 0.03 o9 F" Q5 @) G
                runing_corrects = 0.0
    & X0 Q+ L0 i8 S: J( o            # 迭代一个epoch, j+ z7 o1 p1 \% C/ V3 n
                for inputs, labels in dataloaders[phase]:
      X  H7 A5 K% V5 j                inputs, labels = inputs.to(device), labels.to(device)) D6 I* B  h) X" P5 M, }
                    optimizer.zero_grad() # 零参数梯度
    / M$ ]# V: m) n  j( l, A                                # 前向,只在训练时跟踪参数; t/ U2 U) H6 y  W
                    with torch.set_grad_enabled(phase=='train'):
    3 u1 {1 I! \5 R; v6 ]5 w* C1 e                    logits = model(inputs)  # [5, 21, 320, 480]
    9 Y7 {1 f( i. k                    loss = criteon(logits, labels.long())! O5 Q/ z  Q: ~$ ^2 ?+ E7 p
                        # 后向,只在训练阶段进行优化4 i$ P6 Q8 k, c6 S) V3 d
                        if phase=='train':. q6 o  I* k( t% O; [
                            loss.backward()# ~! q5 l/ B$ ~' _  b
                            optimizer.step()
    ( |" V/ C& v* O7 j                                # 统计loss和correct8 Z% Z$ ]0 W3 f3 C; |% m) @
                    runing_loss += loss.item()*inputs.size(0): @# B5 @7 `, D& L: K3 O
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    2 d) f3 }$ E9 d
    ) m6 m. U6 N& c            epoch_loss = runing_loss / dataset_sizes[phase]9 E0 A$ _* U/ {7 {6 x( y
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    8 J( l$ }7 j. B            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))& t8 T' Q+ ^  x& l
                            # 深度复制model参数2 }! N5 R  w1 y  a! h
                if phase=='val' and epoch_acc>best_acc:
    - U# e3 M$ z+ Z- A) G( v                best_acc = epoch_acc
    ' X1 }+ c1 e7 D$ U: C                best_model_wts = copy.deepcopy(model.state_dict())
    & G$ S6 w, l; `: N2 ?        print()* r5 F6 Q' z: [2 \
        time_elapsed = time.time() - since;  C5 \8 s  u0 |: x' ?& ^( q3 W( p
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))4 d6 |8 @3 u& F" O2 _; b
        # 加载最佳模型权重5 C/ @0 I! p5 N3 @
        model.load_state_dict(best_model_wts)1 i: l0 a3 C& Q& a" u/ q
        return model) V/ o3 U! [6 J9 n- G, |& u4 e7 {

    ) }( x# _1 B% O# }; w下面定义train_model要用到的参数,开始训练. h9 W9 w3 `, I, }7 v
    1 {$ n' Y' g5 J4 h" b9 Z
    epochs = 5 # 训练5个epoch8 B6 T, h$ j+ V
    criteon = nn.CrossEntropyLoss()7 L8 M. L2 s# z8 p; ^
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)& p/ v+ d5 R9 I3 S- j3 u
    # 每3个epochs衰减LR通过设置gamma=0.19 ?% M+ s3 S8 _" T
    exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
    2 N' Y* F* x& ]  W/ d$ w7 \
    + t1 g: n, ~1 Q+ [# 开始训练
    , _+ p/ z) r: Dmodel_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    : U9 r; S2 u8 p) f  a. r5 {1 E: F# s! Q2 r
    6 测试模型

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

    def label2image(pred):: e8 P" B- {% X$ q1 {# i
        # pred: [320,480]$ Z. @! `1 F. }! N! G. D  w
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)/ z2 e0 B& x1 g3 X! U; q0 e. Z
        x = pred.long()( y4 P  C2 |* ]
        return (colormap[x,:]).data.cpu().numpy()2 g$ ?. F! ]3 V5 b( o7 f

    : Y: h; r/ ?5 t. E& y7 i6 ~: W& O- f6 d

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    5 L. |1 R- M, l. Nstd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    9 R! D& Z0 X% R' w; Y# ?& V3 p& Zdef visualize_model(model:nn.Module, num_images=4):7 P$ m; N4 o1 g( S% N
        was_training = model.training
    ' M# U2 k. R) y& X& u8 O2 p    model.eval()
    ! Q7 C/ g6 ^' u) }3 s4 B( l) g8 W    images_so_far = 0
    / {+ z9 S, R3 W% N% P: |" H7 Z    n, imgs = num_images, []8 ?' x% T. M- f* j; I
        with torch.no_grad():, t: ^* Q6 T  X5 t; J- K" Y/ ^
            for i, (inputs, labels) in enumerate(dataloaders['val']):
    ( }& l& o2 V" k/ g+ U& N            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]5 ^! I9 U0 _% O
                outputs = model(inputs)4 R# F; `. |6 r( v$ A
                pred = torch.argmax(outputs, dim=1) # [b,320,480]
    ' U0 H! [3 P+ O5 Z8 b6 g9 I. s            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦% @8 y# y6 D- \* h

    % c) P) P# s5 [+ o2 i) J3 S            for j in range(num_images):, ?; m& c* S. h
                    images_so_far += 1
    ; D: |. E) B0 _8 [. H! U                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)6 _) `, {* L! ~4 w4 f0 _, P
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]
    5 O9 @. ^- \. r6 _0 ^: s8 F0 U0 l                if images_so_far == num_images:
    - a+ A+ j8 H* v( m7 u* k% @                    model.train(mode=was_training)0 c) s; [7 I: I- Q( D. C
                        # 我已经固定了每次只显示4张图了,大家可以自己修改
    " v% K% r% ]; Z6 Q4 s& [                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    " |' K  p+ J+ n8 C                    return model.train(mode=was_training)& v0 Z5 f: s- U9 i7 h
    7 J* ?! K* Y* f: o
    # 开始验证* v) B# l2 p* O) X
    visualize_model(model_ft)1 G# R. \# T: g' r, ?3 O2 A( q: g
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    8 m1 K5 n. g8 Vdef predict(img, model):) b- H$ T  N) C& o
        tsf = transforms.Compose([% w8 H0 t6 q( x. \' y3 V
                transforms.ToTensor(), # 好像会自动转换channel4 N: m7 v, s, }5 ^( b
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    ! y5 W8 O. h& |1 z0 R. ^/ E" X    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)* Y! s/ d) J9 e+ x; R
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)- {, O9 C  _. g9 g' q7 H( g7 r% N8 R
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)9 L; H; q% r$ O" _# `
    & n, S* g! ^+ u; ^
    def evaluate(model:nn.Module):
    3 |1 U% A6 c) i: i9 K    model.eval()
    3 Z0 S" N7 f* Y2 Q    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) * ^& k. d1 x/ R0 P
        n, imgs = 4, []7 k0 j# j) a( y0 n
        for i in range(n):
    ) O4 m- T) M1 a4 A        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    , t( T6 n" p) I+ M. R# a9 o        pred = label2image(predict(xi, model))
    9 Y- S% g5 V& c, |/ A        imgs += [xi, pred, yi]! v4 k' z7 o; ~1 _4 q+ M
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)1 z8 Y6 h' s* B& X0 V3 l9 c# \1 z
    * a/ G( }$ C: e* \
    # 开始测试" t  O( f' R1 k8 Y4 N
    evaluate(model_ft)
    * |8 H6 D, j( W* N# y) j/ A- {: \) \( c; s6 F" B5 N
    7 结语

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


    " K7 s% |( g! {6 Y7 U* P! D, |Epoch 0/2
    7 D* {: H% C1 f) d  X1 l----------
    0 S9 P. `$ x' T( p. v5 y% Strain Loss: 1.7844 Acc: 0.5835
    2 D: h7 o' G+ N/ U: o, gval Loss: 1.1669 Acc: 0.64568 a4 e. m; t) o8 B$ u

    2 y. m9 W( f% J; e* U4 z: ZEpoch 1/2( ^$ U5 K$ Z2 R. p0 L/ ~+ d
    ----------- q: A+ ~( }2 g# ?* T0 Q) @: e
    train Loss: 1.1288 Acc: 0.6535
    - ^1 G7 V4 c- y# u$ c: G% Mval Loss: 0.9012 Acc: 0.6929
    ' E1 F; i- W5 j, H! c/ Z
    2 K2 Q; }0 D* ?- b5 v3 ]  `Epoch 2/2- r  l5 o3 S% w7 r. g/ A2 G4 w
    ----------
    2 d9 A5 V- N9 i7 P0 U( a- Y7 _6 Ntrain Loss: 0.9578 Acc: 0.6706$ X$ O( S3 R; H3 f$ `
    val Loss: 0.8088 Acc: 0.6948
    ) T4 |/ _+ D$ i% v0 f: D5 y% _9 s3 W
    Training complete in 6m 37s
      Y9 H( W/ [. _+ ?# P7 v" n6 h
    # X# X0 A2 _$ K8 f" k! ?% m
    4 {( H! i9 X' B! t% s5 x; e7 | 2.jpg $ e9 s; M* b8 O5 U( F
    1 n! V. u# L+ @, Y& d/ C6 H
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。
    ) d* Z! F6 u! j( Q( D. f0 b0 v. p9 ?- x# t! M% @9 d
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。
    3 A- |2 P( I0 J  n! [! {
    " j* B( Z3 f+ t& G6 s0 r4 L& R
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:) C7 v; q* E. w5 T1 B
    - U0 M  r; R2 x7 K& ^$ Z# I2 e' [
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]6 }1 D2 [" G9 [0 [4 O5 \8 Z
    GCN 通过全局卷积网络改进语义分割[论文]
    5 l+ d4 h; k6 B% b, |4 c% i) m" rUperNet 统一感知解析
      Q! c9 l( A( G3 @  RENet 用于实时语义分割的深度神经网络体系结构[论文]4 G1 O0 C4 G2 V+ V* a
    U-Net 用于生物医学图像分割的卷积网络7 z1 p- v- L! u( i* b1 A: o) T
    SegNet 用于图像分段的深度卷积编码器-解码器架构。/ c# x8 B# r7 g1 p+ H
    还有(DUC,HDC)、PSPNet等。
    " z3 w5 F8 ~# D& x* W1 A- M! G) i
    - Q# Y: d3 Q3 O0 R& t/ {1 H/ e  Q+ b常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
      ?, e( w8 s* V" V+ A
    " n! p$ u# |4 B" Z$ Z+ c对于损失函数,除了交叉熵误差,也可以用这些:
    ' x: p- [  K3 W+ [6 E. Q. }- O2 {+ P' U! d+ [; r
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    4 }) `. c& f: \- G2 jCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。
    ) z( m+ }2 U" WFocal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    # a- A' \4 m) _  [6 O8 d( @8 j: cLovasz Softmax 查看论文:Lovasz - softmax损失。; O. \5 I- k. u' e

    7 ^- F' t' z: Z1 e* R# T- |9 S! \! P' l4 m
    8 Q5 O8 @# z" n( f# d" J
      s' S: f' `1 m7 N9 j2 ^

    " i- h; ^% C9 [+ b1 r3 G————————————————; i8 t- F; F$ n! @" W/ l
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    2 q, ?* Z  R- o) Y" F! g, O+ e原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    ( b0 }4 B6 |; ]$ `! `8 o% F' z: G# a
    7 l' {# M1 u& A

    . C, h1 B/ R/ v5 @* M; Z: u
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

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

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

    蒙公网安备 15010502000194号

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

    GMT+8, 2026-9-2 01:39 , Processed in 0.458581 second(s), 54 queries .

    回顶部