QQ登录

只需要一步,快速开始

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

    ; b# m7 t& M3 f% Q4 d1 W
    " w% l7 ~: w( y; d7 Y  [Pytorch实战语义分割(VOC2012)+ M0 L, B, S2 I8 h* [1 F
    本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。& x) M8 n+ v, [0 _- f/ F& Z
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    , i+ V4 A) v) ~; B3 x
    3 y+ ~$ B/ w# M. X
    3 r( @3 S& o/ K: }语义分割中图像有关狗、猫和背景的标签
      U" _# z" S& n* U& ]  H3 Z文章目录
    6 h- J$ C% X: C) F' h' u- q: }- y, _/ Q: N* I
    1 图像分割和实例分割
    + Y1 @% @& A+ t5 A; K2 Pascal VOC2012语义分割数据集& Y  O. Z# a1 n. Y. G# w
    2.1 导入模块
    3 ~' c6 `! M; R; s' k2.2 下载数据集
    1 }) }; r! J8 `) B; G4 d2.3 可视化数据
    " w, N1 N& g1 T" M2.4 预处理数据, d* y- u/ I4 h% V0 M; n8 ^" [
    3 自定义数据集类
    : ]6 U+ V% A; U/ L) c3.1 数据集类
    & |% G7 c% {' J5 R  w* L3.2 读取数据集
    # M* Q6 c. ^% c. b8 E) H# k: @4 构造模型& U- o2 ?. ?6 q: z: E: ?% [
    4.1 预训练模型, X# ~) p5 s4 U
    4.2 修改成FCN9 i: H) O7 i2 L1 g: k+ D
    4.3 初始化转置卷积层6 s9 i* b1 L% F6 {
    5 训练模型
    , O6 J, U' {8 o4 d, u6 测试模型
    8 {2 l$ f% ?/ ^( W% s3 Q7 O  N6.1 通用型$ Z2 Z8 z% ^% ~2 ~% m
    6.2 不通用
    7 c7 a7 S0 U5 [8 D, e5 c) C7 结语
    2 C& C% ]% d% \% h& u% }1 图像分割和实例分割0 y5 j% G3 s3 E2 I- P, ?2 q0 v

    4 j% u' n4 M  B5 h  U/ [7 m# l计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):" A2 P0 m# q" @" v3 I8 \

    2 p' F& @. T; y+ g0 h图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。/ `, G& w" W$ g$ V) e, z
    实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。* n. j% s; v  r+ d

    # U0 g4 Q1 K1 }, G$ b2 Pascal VOC2012语义分割数据集3 P, Q/ F) Q6 a3 o$ B' }

    . s) C! ]* J" v5 }7 W2.1 导入模块) e8 I2 Q. ]$ ~3 a
    import time: B, Y2 h9 x+ ^- _9 U5 n% I# K
    import copy$ f8 g: L* n6 ?
    import torch, o7 e9 @6 [" {. O9 M% }
    from torch import optim, nn
    $ S3 S& w6 N, _# H5 w8 ^8 oimport torch.nn.functional as F
    9 s; x8 m/ A' Y% }- pimport torchvision+ z- o' i8 V1 b
    from torchvision import transforms
    ! R6 e' X/ Y! d5 U$ kfrom torchvision.models import resnet18
    * `8 r3 d% p( c5 Pimport numpy as np5 m% G! C) ^$ D; H: @  B9 C
    from matplotlib import pyplot as plt" ?5 T9 ~5 J! r5 C3 E6 q# D' J4 y
    from PIL import Image1 [: \% w* v1 C8 J) M) o# O; F
    import sys
    4 y) x( W3 ?  b* C% vsys.path.append("..")
    " b6 X, G8 X$ j: _- x, v- Hfrom IPython import display: N9 B* ^7 |* U) b2 i
    from tqdm import tqdm6 r+ U0 n8 Z& Q3 i
    import warnings
    / y% Q/ @, p& uwarnings.filterwarnings("ignore")
    - ?' m( C4 l, s1 x
    1 |4 P1 q8 _7 M( P. k2.2 下载数据集
    4 x- y4 p: I; i- [2 p+ d( n" z) d
    : C6 h$ o1 I. {6 Y. F语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    . P$ i& J2 H4 x4 O8 m( Z# u: z" v8 B  Y' E( Q8 J

    4 e$ z$ n4 y  ^: \ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件$ p  H& ^, [7 q& r  K
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。$ p: `. y0 _& [9 F( p" _* t
    2.3 可视化数据6 @# v1 {% P, X2 T0 p
    % p  b* J& b0 k  s1 Y' {
    定义read_voc_images函数将输入图像和标签读进内存。
    - V, `" i/ ^% f: g: B$ {) ^, _' e' w5 M' t+ h
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):3 c0 n; e9 l) X0 J" t& w
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')" f& R) t) D( k- ]) z3 E6 \
        with open(txt_fname, 'r') as f:! ?: q7 _" P% @7 m7 q" j4 k+ L
            images = f.read().split() # 拆分成一个个名字组成list
    . j6 L* i( Y' O& k    if max_num is not None:8 p. ~0 ~* R4 ]% D
            images = images[:min(max_num, len(images))]
    / E. g, P  k' {; p- X; P  X    features, labels = [None] * len(images), [None] * len(images)
    . o( @' Z7 \% a4 `* E1 G3 Q    for i, fname in tqdm(enumerate(images)):7 F8 _9 T- r8 [7 b* H+ S
            # 读入数据并且转为RGB的 PIL image
    : K/ [- @4 y' U0 w& l7 ~        features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    0 n- S' [3 Y# }. ]        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")& H) h' A9 n5 p- Y- r) [5 [
        return features, labels # PIL image 0-255
    5 P, k. B( D0 R4 v" \7 K/ G
    2 |; \  ^- l( N! W; G$ }: r7 D定义可视化数据集的函数show_images
    4 c$ ?8 U) e. }
    ! h: j% j5 F6 p$ U2 n; o# 这个函数可以不需要# d% G. P) x, }% [2 E: p( V
    def set_figsize(figsize=(3.5, 2.5)):. S* G( }3 Z+ K0 A$ D6 E& \
        """在jupyter使用svg显示"""
    ( [7 c0 s: y4 N& J6 F    display.set_matplotlib_formats('svg')6 [6 q/ f4 }* Q! K0 @3 s
        # 设置图的尺寸
    - O- x6 Q( \4 J8 L( I; M) T. Z) X    plt.rcParams['figure.figsize'] = figsize  D$ d5 S% @1 @& t4 f

    ) J! k( {1 {+ V( D! u, {- mdef show_images(imgs, num_rows, num_cols, scale=2):
    9 j# M1 Y1 f, i8 f4 g; p    # a_img = np.asarray(imgs)# ~- I& Z; I- T" G& r9 N) `
        figsize = (num_cols * scale, num_rows * scale)9 K  P' @, r+ a7 L. S! Z( X2 b
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    7 z( U2 }: k! C% r2 F    for i in range(num_rows):
    7 ^7 Q1 n( Q+ W  m2 a        for j in range(num_cols):& f% `3 @# O5 `" ~& k+ H8 Q* j2 ?6 \
                axes[j].imshow(imgs[i * num_cols + j])! G- i/ N6 j% m
                axes[j].axes.get_xaxis().set_visible(False)# o/ i9 A6 s9 q0 n
                axes[j].axes.get_yaxis().set_visible(False)
    ' d# S* F" l  U6 u! _+ S  H    plt.show()8 S( _! w. F& i1 e; D: l
        return axes
      e) x8 _" P6 P! n/ |8 Q; ^1 L% g) k$ w, a) z" T& g4 o
    定义可视化数据集的函数show_images& J9 s; Q# D5 G5 Y6 {. U. R

    " b3 q$ ^: a" e% X& L# 这个函数可以不需要
    9 O( r9 O! B3 U6 D& ^6 odef set_figsize(figsize=(3.5, 2.5)):
    % _: g% g4 S. P& r$ `    """在jupyter使用svg显示"""+ f3 o9 _  v/ l# u: [4 I
        display.set_matplotlib_formats('svg')" P& D5 h& o3 \: p( Q! }
        # 设置图的尺寸
    * \( z6 O; L6 c. i" v: C    plt.rcParams['figure.figsize'] = figsize
    6 {/ ]/ V4 p$ t" E, |( Q! o% Y
    % B+ l7 v, n( g: r1 C9 B+ [" Sdef show_images(imgs, num_rows, num_cols, scale=2):
    + }' c  \; `0 Z* r4 o$ c5 @: B    # a_img = np.asarray(imgs)5 l; P( J4 U+ a( G4 H
        figsize = (num_cols * scale, num_rows * scale)( t) y* o* k: x- O. _
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    6 z. v1 I, v5 |    for i in range(num_rows):' L) p) {3 C) U, o1 T+ X' g
            for j in range(num_cols):# C) a  x% W: p
                axes[j].imshow(imgs[i * num_cols + j])0 `% c6 ~: s3 y  @
                axes[j].axes.get_xaxis().set_visible(False)
    * G( N5 ^6 g; z/ _            axes[j].axes.get_yaxis().set_visible(False)
    " `2 A2 N# V* t0 V8 G: M  d    plt.show()
    ' Z$ A  j2 `/ f3 u    return axes
    9 Y, q, ~' G0 [8 c* I+ p画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。& T/ O. k. \: X9 z. a) r
    $ q6 [2 B) M; v: F! d* @$ T! G  ?
    # 根据自己存放数据集的路径修改voc_dir
    : o8 o7 k1 t$ J0 c2 z0 u" Jvoc_dir = r"[local]\VOCdevkit\VOC2012"$ c4 x0 O) S! J8 Z% _
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    . m8 H3 T. ]9 O: wn = 5 # 展示几张图像  U* Q2 J. ?# t# m5 l
    imgs = train_features[0:n] + train_labels[0:n] # PIL image4 m' N1 Y4 M6 E& u  m% u
    show_images(imgs, 2, n)% p/ @& z4 s' _/ q: S+ a
    6 Y9 j7 x/ h; x# w
    1.png 7 S4 l4 Y; u1 @0 I

    , E' h  X! \' J- w2 q列出标签中每个RGB颜色的值及其标注的类别。
    % G) R1 n5 ]' a& H2 h5 u- E# 标签中每个RGB颜色的值
    # R! e3 }6 m5 V& {6 LVOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    ) I5 _7 F8 ~8 X1 i, ^6 I                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],8 s' c# ~& f9 L- B
                    [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],( i: C3 a) h' {/ Y, ?4 c
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    ' l& }( o$ t4 R# w) X                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],& w0 h3 Q" t. U' S5 m8 d
                    [0, 64, 128]]
    7 i9 W" {7 D/ }9 t3 x' f& Y9 ?  Z0 l# 标签其标注的类别
    6 [* S6 d" a- v0 S: O0 ?VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',; T- E7 v# G. h
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    6 m6 Q& X) }+ ]) W               'diningtable', 'dog', 'horse', 'motorbike', 'person',, n( w1 g* H* m3 A( j0 x
                   'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    1 I" }0 ~8 r4 n# ^) }$ C有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。3 \$ a' }) g* l6 a% _  ~

    2 R7 l& u; X. v) ?有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。2 N& m) s, |# z" o% r- b1 d1 ~& Y0 ~
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])+ {) g8 I. a3 Z& v* u) w* z
    for i, colormap in enumerate(VOC_COLORMAP):) ^. j% S  [4 K( ^' V
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i
    0 @/ [  O# y! G  ]3 r& b    colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i: d* H- K& _4 r$ ^3 b: W4 \
    + i# a1 F2 d" J7 q
    # 构造标签矩阵1 Z! I9 P" t! Q3 h6 K7 B
    def voc_label_indices(colormap, colormap2label):
    : t. z7 y' |3 p% C  }- ?    colormap = np.array(colormap.convert("RGB")).astype('int32')
    0 ?: R/ j# S" \" S; C% r  \    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) 4 Z+ t* o  o1 Q" B
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标& o( d1 D. \' k$ ^1 R! ]
    9 ?5 W) C6 Y+ v! N
    可以打印一下结果1 J4 B4 [0 I( a% J

    , [/ A6 @$ E: m! My = voc_label_indices(train_labels[0], colormap2label)5 W$ z& C! E% ]- L1 _( `
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES
    * s" F! Z% [8 Z/ E
    1 l* X; c, v! A  Z2.4 预处理数据
    1 ~: O# D' J$ F" c
    ; P1 k  t$ D$ x2 |2 l% F5 _在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。& b2 g$ v8 D# W% e- f6 V; _

    & c4 }( R9 s; L4 a5 D; p) [8 \1 J$ K$ ?# S) W) n& \
    def voc_rand_crop(feature, label, height, width):; {1 V+ M4 [7 ~+ [9 f, S
        """; b% I* u% X8 C2 }3 ^: |
        随机裁剪feature(PIL image) 和 label(PIL image).& J  o# ^& `* P, g# F
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做$ |( j( _! H" K8 @8 y. f
        Get parameters for ``crop`` for a random crop.! c2 F( p( \0 O1 g5 V( a* b6 L
        Args:
    ! f5 @! V* a5 T4 T8 U! V        img (PIL Image): Image to be cropped.: \# ^7 I; d3 w! g6 W7 V+ u
            output_size (tuple): Expected output size of the crop.
    3 h; j4 L3 y% N& @5 l    Returns:
    + l. w1 x; F1 `5 x        tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.$ N7 ]4 n- t: n4 B+ [# D
        """
    - u$ F, b+ e& Q    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))- m9 b" E( M( _3 S8 \: R
        feature = torchvision.transforms.functional.crop(feature, i, j, h, w)- |6 o& r; P! \% l9 q2 J
        label = torchvision.transforms.functional.crop(label, i, j, h, w)# d: @. p& H  `' c# `" I" F
        return feature, label3 x# X) X2 w# I7 d6 S* y

    1 c7 E  _4 x  ~4 V& ]% |5 s% `# 显示n张随机裁剪的图像和标签,前面的n是54 _$ P) F0 d8 B1 W
    imgs = []9 p% w8 s4 {# }4 b$ u
    for _ in range(n):
    $ p5 E+ [- `$ L3 M! r; _3 x8 A1 }    imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)1 Z1 G; J7 c& Q( z6 X
    show_images(imgs[::2] + imgs[1::2], 2, n);
    + t4 z; T$ v5 A  Q# V: P  e) K7 N5 ]. E7 D7 u8 Q- g4 e2 h9 R
    ; Q7 d7 ~% [% D) e3 K/ Y1 T4 N2 [
    2.png
    5 X2 J6 U; t" H7 M% {" |5 z3 q3 E" q# T4 ?6 K5 q/ ]4 [4 P0 \6 m, s

    5 l' c% t( ?( J8 n  Z& l5 k9 q5 e) D, F7 K. n
    3 自定义数据集类
    - o, X7 ]4 x, W/ g" N2 m; y' Z# N) G2 D# S# r( U  V5 }9 `
    3.1 数据集类
    / G" z3 a  T( d8 r$ a- X
    9 h6 _. C# Y# @' G1 ?( _" ?5 E5 \torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    7 c+ b8 l- I% X  L8 N) U
    ( `$ z: k. w& Y__len__ 实现 len(dataset) 返还数据集的尺寸。
      E- ^# y/ V/ s% {__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。# j4 p6 T+ b+ B
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。) p8 V% }; P( Q( ~  Y

    / D' {7 o, j- s2 |6 J! uclass VOCSegDataset(torch.utils.data.Dataset):
    6 V% }5 R- h% x! h! d    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):* ~6 c9 f' v, H# \4 T
            """
    6 S( @: D' y3 O& d5 `7 w! U        crop_size: (h, w): z( h* t5 V* p0 ^. }: N
            """
    5 S; y5 c) b$ Q1 c' E. ~        # 对输入图像的RGB三个通道的值分别做标准化3 L( |' |$ K# }2 B
            self.rgb_mean = np.array([0.485, 0.456, 0.406])
    2 ?6 ?7 C# F' i" }. p9 ]/ O        self.rgb_std = np.array([0.229, 0.224, 0.225])& O% @# |8 `' j& r  t, s& Q
            self.tsf = torchvision.transforms.Compose([6 E' I6 ]! N/ c$ h. }
                torchvision.transforms.ToTensor(),. E7 s; G* m9 D2 R0 ]0 Q2 K
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    3 b5 b; u- J6 l8 r/ o5 N        self.crop_size = crop_size # (h, w)
    ! ^" l, I- t& S8 H  |        features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)' q- n2 Q# U  x5 @( k/ o
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除7 s1 `& |) p+ _; \
            self.features = self.filter(features) # PIL image
    . t, ?" J6 |4 o: H        self.labels = self.filter(labels)     # PIL image
    & Y) M' c3 _# x) D2 }; {        self.colormap2label = colormap2label. o9 `6 W5 C% G9 W
            print('read ' + str(len(self.features)) + ' valid examples')# r6 }' T- ?" ~, s; P! A' `

    $ f! A( J8 q+ _1 B" X4 a" ?, R    def filter(self, imgs):* Q) h3 X+ ~/ X7 a4 N8 m
            return [img for img in imgs if (
      O) P! X' y9 F8 ]1 s6 x            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]6 Q( |1 y, {6 b$ m) A
    , A' S7 L0 z' g  H; o2 B
        def __getitem__(self, idx):
    ' K6 X8 q5 T9 W        feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    # \+ m& X$ \. _: g, {6 m$ T                                # float32 tensor           uint8 tensor (b,h,w)
    7 S- N8 X) Z" [1 R  R        return (self.tsf(feature), voc_label_indices(label, self.colormap2label)), t, x' _- F6 U* @7 p' T; H

    & C; z9 [: v2 V' [0 t( k8 n; [    def __len__(self):
    & ^2 G& I! f: H" }: \% F3 i- C        return len(self.features)
    9 C8 F5 [2 ~5 N1 c) G3.2 读取数据集+ T0 }% t5 D4 X6 R

    2 D  O- Y) s. L& ?3 \- S, j通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。( b" b! _& |+ j" q  V0 m  p/ @0 N/ z
    5 B3 @% s# i1 K& @
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)9 N; ~# Z5 B( o1 x" B4 `/ W
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    * c, ?7 h! w, \% c4 kmax_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~9 |1 _$ C+ F0 \3 B" \8 f7 z( X

      U! I( {6 a- y# 创建训练集和测试集的实例' r! S8 p# n5 r/ K& n) ?
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)8 K; L5 L6 I* P$ ]
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    ' {* H4 e. ^" r3 r& p1 K6 T
    2 l3 l  L1 L) v! y1 u+ `# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器
    " n0 ?- ^( s" V' x" X, fnum_workers = 0 if sys.platform.startswith('win32') else 4
      g, N5 J$ v. W1 f* |train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,
    7 l! r" D6 z$ F/ \' M                              drop_last=True, num_workers=num_workers)
    . b! C3 E9 E/ K) t4 `6 C* r. ~0 }4 j7 Ytest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    % ~0 I; V. ]1 i. _) S                             num_workers=num_workers)
    # [1 T- ?0 s+ t) }- ^' I3 W$ i. j/ a$ d
    # 方便封装,把训练集和验证集保存在dict里/ P- P, o4 ~0 f$ Q) o
    dataloaders = {'train':train_iter, 'val':test_iter}
    9 y/ m1 P0 s4 @# H$ E. W6 Zdataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    ; C5 f5 N  p3 L, s- x# `2 Z: ~9 t# `
    1 b; [6 n; ?' s# b( {4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')' m4 c7 w3 g0 F
    ! _) _8 @0 c0 Q9 Q
    num_classes = 21 # 21分类,1个背景,20个物体. H1 v6 d; [5 i( `7 o
    model_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数. I, y5 h8 w! O9 M  ]

    ! c' d. z- Y- m- X* o$ [# 特征提取器
    ; j% ?. j8 O! E2 F& ?  u0 hfor param in model_ft.parameters():' B/ e7 q  P/ ^( ?
        param.requires_grad = False
    % b8 \( U: O" C% l+ p9 M; S4.2 修改成FCN" w/ s  o) R; c/ u' C# i6 G: v

    ; z: l7 j1 w- q! e& k全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:( ?1 ~8 \4 }3 L4 \
    最终输出的通道包含了该空间位置像素的类别预测。
    6 Y3 Y0 y; X- K  [& b' m' M# Q+ W) S! R2 S8 u" c7 M
    对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    * F, c) P5 K# O/ A. r& T9 M, v' M$ b% q
    可以先打印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 倍即可。5 e3 K( I2 k4 \1 l

    / I( d( N: f6 v' _$ m2 w& Kmodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    ( ^8 U2 r9 q) n              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class* l9 |) o3 r! A* ]- W- b' s# l
                  nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    5 u% w- g8 m, X8 _8 `4 N
    ( R" B3 l9 {" o! i3 |# 对model_ft做一个测试* f: [2 c( E+ l3 D
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据& Y' ~& Q( R) ?5 m
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    8 `) T3 L& d& F
    3 C4 M/ f9 e- X# 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组
    ' m, ?; b( |% t" c# for X, Y in train_iter:( P+ i" _+ W; u2 N4 [/ L
    #     print(X.dtype, X.shape)1 n  H' ]3 M8 r2 K
    #     print(Y.dtype, Y.shape)
    ( [, k; u$ k6 ]#     break% l7 C& e4 H1 o2 ]* F& ^1 Z
    - O# o/ P( f7 {- I% y! `1 g
    ( w4 B8 W6 n, s9 T2 ~
    4.3 初始化转置卷积层3 O# p# c! ~9 }$ A. ^

    - W- t; @$ ~0 M4 z6 b, x6 X9 A在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    - Q, b# M7 X7 D6 R2 L在坐标 (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函数构造的卷积核的转置卷积层来实现。
    % h3 X8 e7 r0 M) P
    0 X+ v1 U+ N7 O3 i: N$ c( j( B2 T; V% A0 a
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核, K3 q9 s  Y, T% |) U- E/ X7 d
    def bilinear_kernel(in_channels, out_channels, kernel_size):, F  a# \- V' W; j2 o
        factor = (kernel_size+1)//2
    ) ^. F( h. w4 }' n! l3 z+ P    if kernel_size%2 == 1:# U2 L. q8 V* `4 v7 S6 F
            center = factor-1* A8 {: A& P" L; y" S
        else:/ p+ |9 a* L$ C  f% n" p  H) z
            center = factor-0.5
    0 x; K0 ^6 T$ q. ?/ D& c    og = np.ogrid[:kernel_size, :kernel_size]8 D* ^' q# m8 Z+ r5 r$ j
        filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)+ b1 z! K  p/ p: U' G4 n
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
      C) G! R' k; O. t& P    weight[range(in_channels), range(out_channels), :, :] = filt, b) `7 K; D0 ^0 Y- w8 Y
        weight = torch.Tensor(weight)
    " j' K2 R) }1 O) o    weight.requires_grad = True
    ( c& q8 L, k/ F- O. c5 z    return weight: |% S# S, {8 r) f( V1 n

    8 p& I% e' K) J% ~
    % y  _; a! b0 @+ Z& A& b! d在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    : h+ s5 m, r9 v6 O  S) {# W3 J4 j" D5 U' M4 g1 i
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)0 |* B; z* T' P
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device). ]/ m) G& d0 j

    % U& _# l0 \2 {, Y* O; N# q4 ?2 p, O9 s% `8 d

    / Z" n) p* f3 T5 训练模型

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


    ! F5 D& e2 v: T8 cdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    $ k7 K: ~: r$ y% y) c% k    since = time.time()
    , f: k% N. I5 w* }7 A. M- T. n    best_model_wts = copy.deepcopy(model.state_dict()). d, F8 ]2 }4 ]7 R
        best_acc = 0.09 X+ I! W: g+ ]/ e
        # 每个epoch都有一个训练和验证阶段' T. Q" G( h" O  V+ `
        for epoch in range(num_epochs):
    8 ~! U; F1 L9 Z$ Q( [% W! {# R        print('Epoch {}/{}'.format(epoch, num_epochs-1))
    , S, p3 e8 m" d        print('-'*10)1 y6 Z+ i5 m  c" l. K- I8 a
            for phase in ['train', 'val']:
    $ o! Q' m& u+ Y7 J! d: x; ?3 A            if phase == 'train':
    8 l# Z9 e. Q7 A2 `7 V                scheduler.step(): X! E% s  V4 F# V( Z6 ?1 G
                    model.train()' K: c& q8 h- n
                else:8 G5 Y. V% z" u
                    model.eval()
    4 W8 F% @3 w& S* w# S( z) y            runing_loss = 0.0; ]$ A5 |+ ]. ]: k& D
                runing_corrects = 0.0
    ' l4 ]/ N( n0 H0 d' f            # 迭代一个epoch
    ' h/ p( u( E6 M1 z4 C& X5 U            for inputs, labels in dataloaders[phase]:
    * w4 Z" l# h9 T  k$ @9 h                inputs, labels = inputs.to(device), labels.to(device)3 w8 Q" k7 C5 _7 e+ U/ i0 S
                    optimizer.zero_grad() # 零参数梯度
      W# H7 S- H* s% G( i" t                                # 前向,只在训练时跟踪参数' \0 E! p; A9 y1 g- c' {; n
                    with torch.set_grad_enabled(phase=='train'):5 u* U% a/ ^) G) P. B
                        logits = model(inputs)  # [5, 21, 320, 480]- s, i& Z7 |. M1 j1 P8 X
                        loss = criteon(logits, labels.long())
    0 P9 r" D. J8 D3 G2 Z; B1 s                    # 后向,只在训练阶段进行优化% L- J" B8 ^& P8 \! z9 Z# q
                        if phase=='train':
    % ]8 m4 ^# [( M8 b                        loss.backward()
    4 `; N* b& [2 z3 O. b4 E                        optimizer.step()4 T7 A8 S4 ]4 @
                                    # 统计loss和correct
    & X) P  I1 {0 G* w! g) T                runing_loss += loss.item()*inputs.size(0)6 l3 t/ m1 N) ^4 G$ L+ E
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)
    0 ^" J; ~3 |( ]6 F5 l+ Z6 O, b& j) m. F# Q; i' y
                epoch_loss = runing_loss / dataset_sizes[phase]1 K# L0 ^  X9 N6 H3 e
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]
    4 Q$ H6 p6 @# q            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))) g5 R% |6 R" W
                            # 深度复制model参数
    # ]& N& d8 r- o3 Y+ U; j% m            if phase=='val' and epoch_acc>best_acc:
    : ]6 H- B# o/ I2 a                best_acc = epoch_acc0 P$ C9 r% j' C. o/ y
                    best_model_wts = copy.deepcopy(model.state_dict())
    2 g4 Q' e9 F- a2 N5 S5 [        print()
    " A$ l) l0 d# G& |8 U0 z( j    time_elapsed = time.time() - since;
    + y" A  q  i7 }& S2 [/ ]# I2 a% N    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    + V% g* L& r. }. e    # 加载最佳模型权重
    3 |% S' Q- t9 A1 [: r; x% x9 m    model.load_state_dict(best_model_wts)# N; N5 D% I! K+ g; L' c* R$ |+ j
        return model
    ; b5 R. G  f* B. C! ]( n
    , t' {0 i4 e( Z" M* Y3 x下面定义train_model要用到的参数,开始训练! R- ^% A- W9 ?) q! l
    % X: N: Q; M- F) m- n. O
    epochs = 5 # 训练5个epoch
    . C' _, @+ C: Hcriteon = nn.CrossEntropyLoss()
    % j* L* h/ x  k3 G( v5 Foptimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)- G5 X- {; n0 u, |
    # 每3个epochs衰减LR通过设置gamma=0.1
    - M) q4 t8 d* ?exp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)- H- R4 }1 {9 \8 z8 ^
    # B, ]- K  M5 \! B; u
    # 开始训练$ _' a9 q0 Z' S; n2 l# A
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)0 q- u% ~- m/ e8 w4 E$ g( c

    6 g# t7 Y2 P% A) c5 _% `8 s0 d6 测试模型

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

    def label2image(pred):
    8 d0 m! T! R' `% d    # pred: [320,480]- F0 Z2 ]$ T6 t1 b* ^0 k
        colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int). {# t) V+ a! Q' F* A- m' l/ Y3 ]0 x8 R
        x = pred.long()( t; {& C+ u6 Y0 j8 [9 g- M6 c
        return (colormap[x,:]).data.cpu().numpy()" N% [) y4 |# U8 e! W$ e
    ' m4 C& ~4 o* j

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)5 a. Y2 w5 l# ]+ K  k
    std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device). X3 c" K  _1 b- b8 m. C
    def visualize_model(model:nn.Module, num_images=4):
    5 }& ]  {6 k0 d5 C' A, B/ y: E6 K    was_training = model.training8 ^# v& W3 `5 r7 k7 Z2 K8 \+ P
        model.eval()
    , }, _5 N7 k: k5 r5 Y$ l7 s9 J    images_so_far = 0
    % p$ i2 b( Z3 k1 Q: |    n, imgs = num_images, []
    ! V' ^; h! I0 g# i- z7 Z8 M. W    with torch.no_grad():7 F( |* ^9 I# E- p6 H8 W% J+ J; p0 K
            for i, (inputs, labels) in enumerate(dataloaders['val']):
    ) X6 w4 S  E/ m( O. m* Q" i            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]3 F& F$ z% A1 r" ^
                outputs = model(inputs)9 r4 B, ^  P& [+ M8 |
                pred = torch.argmax(outputs, dim=1) # [b,320,480]' x  G! ]  V, w6 p
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦: _* e  K7 d; `9 B7 D) X- L

    * l, |1 P; R: Z- _6 @4 g: F3 i" [+ w8 D            for j in range(num_images):
    5 Q% l  l8 m+ D) ~9 R1 x                images_so_far += 1" Y$ i; q4 h2 a* S
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)) q9 ?& K7 J. M* m/ K+ I& ^
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]1 I0 Y% e& \& Y7 w
                    if images_so_far == num_images:! H  V3 g) ?' R1 V
                        model.train(mode=was_training)
    # `1 D5 R# `( Z) D                    # 我已经固定了每次只显示4张图了,大家可以自己修改
    " s+ m' ~3 `9 ~0 H! E5 h' p                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    ; Y4 \, A6 q1 K8 f+ W( B+ W                    return model.train(mode=was_training)/ T) j/ Z- Z! N- @$ R; V  }

    1 H4 l. _3 ~, l$ K& \0 l# 开始验证
    1 n" F2 B* i$ h1 d! Hvisualize_model(model_ft)$ Z& l, P: k- o6 X
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor2 O  P  n- ^# z6 e- G7 O' U" E) u( h
    def predict(img, model):
    / q5 h9 j7 _9 z8 |    tsf = transforms.Compose([: V; |) E  ^! b: H% L
                transforms.ToTensor(), # 好像会自动转换channel5 I3 \0 r8 F" `! ]( ]' H
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    % O8 l4 H' k, a9 G, w; b, H    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)' H8 l/ c' l( X- ?6 Z
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)0 @. }, t6 w$ l1 k7 I8 ~) O
        return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)7 R' Y4 r& Y3 X+ D$ x4 A

    * u/ A: w1 ]1 o% S$ |2 O* Fdef evaluate(model:nn.Module):8 ^& Q9 f$ @% _- P+ O
        model.eval()
    3 x9 E. }2 w9 d& T( C9 L    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) 8 U, G' Q3 W& T% d) R
        n, imgs = 4, []
    5 n, U$ m8 y+ Q4 o4 k0 S    for i in range(n):# g) J. ?, \: u/ z4 [1 l
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    # p  T3 `) ]/ h7 t4 H! Q; f6 M% d        pred = label2image(predict(xi, model))$ @6 _$ Z3 j6 Q6 f- n
            imgs += [xi, pred, yi]$ N* a8 d* U$ {  v
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)) F4 t% I; M+ ]

    ! B2 }7 N3 x  Q7 |# 开始测试1 T/ V1 b; C+ t9 _1 b1 m% d6 Z
    evaluate(model_ft)
    ' l+ J( d# a8 U* t0 R0 _0 n2 T3 ~# P4 Q- P- Q# o/ Q8 w
    7 结语

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

    5 W  Q2 c: E2 y( n, _3 J3 u: ^
    Epoch 0/2
    ' r# w7 n, t  N5 Y' j----------2 x2 q! W$ n7 Y' b
    train Loss: 1.7844 Acc: 0.5835# `: {5 o  ^( p; J3 u
    val Loss: 1.1669 Acc: 0.64561 U5 G+ H8 w( h3 y' ^& v6 g
    / ?% b/ k: d" s- W2 K3 v5 ?4 m
    Epoch 1/2
    6 V/ a- }, a  e8 C( n5 C0 {8 }----------" b& w& [" _( n0 _! c
    train Loss: 1.1288 Acc: 0.6535
    ; w0 S5 u5 D6 Qval Loss: 0.9012 Acc: 0.6929
    & u" w0 K( F( V4 n/ a  l7 Z( D8 b* H
    & c; h  d4 [& k* hEpoch 2/2
    , N/ }$ i0 l, U9 z. v+ R# R----------
    7 H) N6 g) @9 j% V/ f. xtrain Loss: 0.9578 Acc: 0.6706- V7 G8 w& V; c2 z
    val Loss: 0.8088 Acc: 0.69486 I" E/ i$ c: {; H5 t" i' D* [
    ) a* }! w% ~8 q3 N, ?$ ~
    Training complete in 6m 37s
    / f4 B+ x/ p, `' m$ Y2 m
    , n7 }' o2 E/ {% x" j2 G! l) |, a
    5 {2 b1 ]# `6 }4 v8 a! i 2.jpg 1 A, ~- D5 k% P. k
    / m% R3 Q8 ]% i: u: i' ?# u
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。8 k9 t* S/ f- g: l% j
    3 k0 C4 O1 [) N& X: c+ y! A8 [/ x) T
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。  d& R: P# B( @! j
    7 p5 u2 r3 t) c/ @
    9 }' H1 Q5 t: n# ~: d$ I; V
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:* ?4 _; h6 ^1 `0 p% O: Z

    7 u# _/ U7 y. S8 A& V5 e% j9 Y6 ~( iDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]: p( M) J. U: E/ T
    GCN 通过全局卷积网络改进语义分割[论文]
    8 J! ?$ B: Q5 I* Z* g7 N! f  LUperNet 统一感知解析) e. K# g- u6 _! Z
    ENet 用于实时语义分割的深度神经网络体系结构[论文]
    ; D( Z" a# s7 g  U. AU-Net 用于生物医学图像分割的卷积网络
    8 a" R7 c, I; m; {SegNet 用于图像分段的深度卷积编码器-解码器架构。, Y' v0 [" Z5 z% g8 D8 h
    还有(DUC,HDC)、PSPNet等。6 N; [1 S  R. u- N2 J

      P/ I7 W: V% c7 i2 j0 O7 O常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    - h3 B. W6 f8 u8 x$ e
    8 W1 h* B" N8 W% {7 e对于损失函数,除了交叉熵误差,也可以用这些:
    7 w6 L* m) d% H( h( c) ^/ u- e" a. A, z% ?  x
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。0 q& w' n/ U1 T. ]& y8 t
    CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。2 H, I1 I" ]3 [' c/ J$ Q  r' d
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。! \/ K! b+ N) C% u  H
    Lovasz Softmax 查看论文:Lovasz - softmax损失。
    ' M. E2 s' J  S( O! B; j+ X9 d; }+ y' k# c
    0 j/ j6 L0 k8 k0 f, v

    " J5 b+ \' K4 a! e! W: [
    6 W$ ^' K9 O7 {. X. f' z; f+ K! |
    8 {& B0 @9 Z2 z, l2 T6 y/ F* `————————————————( c' T( V3 W' M: v' g- O% E
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。- _1 i# I! G7 S& M0 x6 Z  J
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507/ d& `7 Z* V; r7 @1 ]

    + l4 L% |) p0 @; }
    5 s0 V, m  H- L7 x$ N% T& Z: x5 h* C8 K% N4 l1 Q+ W7 t6 c( a7 r
    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 07:26 , Processed in 0.316377 second(s), 54 queries .

    回顶部