QQ登录

只需要一步,快速开始

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

    8 V' Y9 L  S+ i" \$ J+ W4 t) L; ?5 J* M4 a# _& @, a, m' [
    Pytorch实战语义分割(VOC2012)
    2 f* @" U  V" m' T6 O: ~本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。
    / u. ^8 F8 p% j/ X1 x! t语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。
    7 Y+ k" z5 d  z& o0 l2 [6 s5 ?# ?: W; Z

    ! K4 \  f. {6 K9 w. u语义分割中图像有关狗、猫和背景的标签
    9 z' M. X1 Y7 T, Q; U# p文章目录
    8 U( y+ S. D& U; z3 G( Q- @
    % l' l! z& B& L/ t4 N1 图像分割和实例分割
    , z! S+ L- K8 R) S& |! p2 Pascal VOC2012语义分割数据集
    " m' {: Z( s& i# M, T, P+ R: ^2.1 导入模块! }: Y# y$ q* W/ P4 E/ Z; o- ]- k
    2.2 下载数据集2 \+ b  i9 T# r% J' ]* n) L
    2.3 可视化数据  D$ K6 W, ]6 h$ X1 I  J# E) P/ P% d
    2.4 预处理数据" ^% c+ y/ t8 Y- V- C% p3 Z& W
    3 自定义数据集类5 @( }" z% ^( x8 |" X
    3.1 数据集类
    - Z3 ]; t3 Q3 b, {$ C" T) s3.2 读取数据集
    ' U( T1 `/ a" N. Q4 构造模型
    , V6 G* y4 b3 n8 N5 U4.1 预训练模型/ d! h% Y; Q# M& M& ~3 A  E! w
    4.2 修改成FCN
    7 p5 v: p5 D" X" @% P4.3 初始化转置卷积层
    : l9 Z' }& @, T8 p* Y! J) s5 训练模型6 Q- U# `) a1 k, \% [. s$ n5 k! l
    6 测试模型4 t8 Y. Z# l! g0 r# {% K: ?" E& h
    6.1 通用型7 g- x5 J* w( f0 r  n, R
    6.2 不通用0 X2 M2 a: `8 X0 Z. N
    7 结语
    7 D; F* D/ u/ g, d% e1 y2 o1 图像分割和实例分割
    9 p/ I  v6 R* [& D& c8 `
    3 k) L2 Q3 h5 H$ k: \! ]2 G计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):
    5 N- ]; v7 }3 w9 o5 n- ^+ r4 W, K6 {% {$ `
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    9 `4 u% ~+ e4 p6 g8 C! q实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。
    ) G( C7 {5 t4 X  A8 A) Z
    ' ^* _/ R7 R" D7 m% v2 Pascal VOC2012语义分割数据集
    1 H/ O& n* W. J
    0 D% ^* v5 j( t' m. s, k3 ]- M& b2.1 导入模块
    1 d6 u! [0 m( T; Z6 Kimport time! `" u# s0 R) i( e6 ]
    import copy
    ; F/ C5 `( h6 ?& Yimport torch
    - B. d. a: |- B' d1 X3 zfrom torch import optim, nn1 s1 G' X( x* w( x* B% n! M7 U) e
    import torch.nn.functional as F: {: D6 M3 V+ j9 o& P
    import torchvision
    7 J% x* ^5 s9 Rfrom torchvision import transforms
    , _+ Y( P& o+ [  k# _/ h/ X/ mfrom torchvision.models import resnet18
    5 z+ z/ g& m7 B, d- ]0 u" himport numpy as np
    " G* y, C' j# ]; rfrom matplotlib import pyplot as plt5 W4 p% a8 }5 ~8 E6 E! k
    from PIL import Image6 x* R$ g- Y- `# D
    import sys( c8 }9 A3 [/ A9 b0 S' ~3 ]
    sys.path.append("..")$ K* @* q# G: h+ o: ~
    from IPython import display& g8 `+ {, Q) H
    from tqdm import tqdm7 m' K/ Q, M, C1 S" `
    import warnings
    . K2 t3 M0 ?' W* ^warnings.filterwarnings("ignore"), k3 B. G5 n) v3 n4 j
    2 O* H  d! X5 s: k3 u
    2.2 下载数据集
    & K& E/ d7 a: F1 s* k. L/ |
    9 W4 y# t7 [4 O6 J" `1 I语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    - B; w4 Q" J, ]5 d- V1 o3 x* ?4 U7 Q6 }! d( U4 `" |( K( }
    6 Z2 E, j# S% _+ [6 u
    ImageSets/Segmentation路径包含了指定训练和测试样本的文本文件
    / s. Z3 C) Z$ L1 wJPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。
    - y4 E( ~% G# L& b- I: D" b; T% O2.3 可视化数据! R% @% E: T) w' b2 Q% w2 K
    9 a# C$ ~( ^5 L: ^. ?2 k
    定义read_voc_images函数将输入图像和标签读进内存。
    ' N* U' s# f* Y9 m. R2 w  N% r% j! f  w
    def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):
    + H: V+ C9 A* E* ~    txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')
    2 q6 E  f1 W' f# p' ~    with open(txt_fname, 'r') as f:
    5 s; ]+ ~% s2 `' y        images = f.read().split() # 拆分成一个个名字组成list
    5 R& }) B% D. C& u5 I    if max_num is not None:
    ( z# H% I2 r) l1 y  E        images = images[:min(max_num, len(images))]
    * o/ D) [# q+ i: v6 u) Q    features, labels = [None] * len(images), [None] * len(images)
    / ?! g2 e  ~7 n  {    for i, fname in tqdm(enumerate(images)):" v8 a6 E# a7 _/ y+ Y3 {7 S; V
            # 读入数据并且转为RGB的 PIL image8 p# x' ]+ |+ Q( ?9 }2 f
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")( ^4 D% h7 P. ]4 I! a$ t4 M
            labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")5 {, y0 a& K6 I4 }' m( X% C4 l
        return features, labels # PIL image 0-2557 e7 S0 q* ?, G' R; v, A, {- l

    - d# X: A& M0 s, g( u2 q4 \) q定义可视化数据集的函数show_images
    4 D; }, ^: x0 P* {% F
    9 w$ l# ]( R3 L0 m/ f+ x# 这个函数可以不需要
    - p1 K5 s) W. P% s' rdef set_figsize(figsize=(3.5, 2.5)):
    , L8 f+ t+ `, a* Z7 I7 ~    """在jupyter使用svg显示"""7 G) i  G4 U3 O* T8 o
        display.set_matplotlib_formats('svg')
    ) Y+ W; s$ \0 C/ [    # 设置图的尺寸
    % v" c4 ^% H& u% ~" n+ a    plt.rcParams['figure.figsize'] = figsize
      ~+ E$ Y) C5 Y
    " A: l  a5 e7 ]  S1 r! Ndef show_images(imgs, num_rows, num_cols, scale=2):* U% d0 E6 s- k8 c+ N% X
        # a_img = np.asarray(imgs). C0 v  {" u) C
        figsize = (num_cols * scale, num_rows * scale)
    4 \( @5 D' M3 V/ _9 ?3 @& U9 N    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)5 {2 ~5 Y2 s' Z) _! |5 M
        for i in range(num_rows):
    3 |! g2 D6 w& t7 E        for j in range(num_cols):  ~! k8 c9 m' c8 I! r1 ?
                axes[j].imshow(imgs[i * num_cols + j])
    6 L* g6 {4 s* r, c            axes[j].axes.get_xaxis().set_visible(False)
    6 s0 f- r, W. k1 v. {            axes[j].axes.get_yaxis().set_visible(False)
    6 `# Q$ `6 R; v, W9 H( v    plt.show()) F# v  ]! }0 @- N; \
        return axes
    5 m0 u. s7 h! S8 {- L( V0 a% q4 b' n, e; }
    定义可视化数据集的函数show_images6 Z3 Q8 G& X5 y
    ( o$ u0 K: {3 X+ V2 w& G
    # 这个函数可以不需要
    ' L0 R! m# m/ y; n  }def set_figsize(figsize=(3.5, 2.5)):
    . G& `; V9 Y8 v5 W9 }- A    """在jupyter使用svg显示"""
    . H! t- M7 ?, _; O5 R! ~! J    display.set_matplotlib_formats('svg')/ N, X7 l# j9 g3 z" e- Y. N
        # 设置图的尺寸
    2 }9 c0 x" h( \9 _6 R" u    plt.rcParams['figure.figsize'] = figsize
    : B; |8 M, J! k; L( o# T) i$ s2 H
    ) |& X) }$ W8 Udef show_images(imgs, num_rows, num_cols, scale=2):
    0 L) o( a. n# G    # a_img = np.asarray(imgs)" s1 K  h( f" E8 b
        figsize = (num_cols * scale, num_rows * scale)0 `  |7 P. \) ~
        _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)  @* |( x+ I3 Y1 G" Y
        for i in range(num_rows):
    / }# Z3 ]2 E( t1 ^        for j in range(num_cols):
    7 @: O5 R9 J" ]! Y            axes[j].imshow(imgs[i * num_cols + j])
    . v$ O; T: r4 _; `1 s+ S* S            axes[j].axes.get_xaxis().set_visible(False)
    5 q. k3 Q9 R- [: ^# ~) D            axes[j].axes.get_yaxis().set_visible(False)
    , y& d9 C& s" m    plt.show()& H$ z4 L& p/ U' |! M4 N5 D' z, {
        return axes/ K! u- b9 r; G0 m2 _7 B
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。
    + x2 z+ H! l3 h' d1 ?
    - y. g1 }$ c; ~( R0 G+ O# 根据自己存放数据集的路径修改voc_dir
    ( F2 O/ r1 x9 W. ?5 u, Dvoc_dir = r"[local]\VOCdevkit\VOC2012"- R  X  ]) N9 h- V
    train_features, train_labels = read_voc_images(voc_dir, max_num=10)) j* Y) h) U& A9 F( K' ^3 s7 z9 ]
    n = 5 # 展示几张图像
    ; U/ `8 _7 C6 a- u; s; e- m* T* simgs = train_features[0:n] + train_labels[0:n] # PIL image  `2 o/ b& H; K/ Z8 p
    show_images(imgs, 2, n)
    4 o! z$ [, g- w; r. ]1 T- m. L# }) P( a0 Q3 j4 V
    1.png
    4 B0 p  e" v7 k5 P* b" M3 x. y: \1 @8 `  Y  i1 o8 ?7 D) H
    列出标签中每个RGB颜色的值及其标注的类别。
    , E0 |( B; [  `" x: R3 y, d# 标签中每个RGB颜色的值3 z* H. u. `9 k
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    2 f, a7 q' o7 v: [                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    . w! B2 h5 Z* C                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],
    1 u' x( @# a! Q" _  ?                [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128],
    ! w1 B- s% p$ N6 {                [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],5 ?9 k1 t" y, {( M7 \( H
                    [0, 64, 128]]6 f: \4 L! g. A7 L# _2 u" p4 }
    # 标签其标注的类别" z8 x9 a( l7 T3 z9 _
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',
    . u/ I9 F& c( N8 V/ M' ]               'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    5 I+ [; R7 _2 f! m; x1 U7 w               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    8 L7 G; L) J) E               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    ) @( q, I" H  t有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    2 _3 b5 u# ?: c- d- ?# D0 W% A
    2 S2 y* B4 {3 E# A- p. a; B* N有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。
    8 q- @: K4 M( r1 P) |3 O5 pcolormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])
    4 h  V' ^8 {" h! kfor i, colormap in enumerate(VOC_COLORMAP):
    : S9 m/ I/ u- Z. |& j    # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i) d0 L' n/ l7 b7 @! v: ?$ `* |+ c
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    + F( }# P2 g9 q8 r
    ! n: I  B7 S, W' j6 M, C" F# 构造标签矩阵
    . M; G7 v9 N3 d+ e& v# @+ edef voc_label_indices(colormap, colormap2label):; F# \0 A# e& M: s
        colormap = np.array(colormap.convert("RGB")).astype('int32')
    * h9 ^$ `+ }7 d9 E    idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) - v! A8 p6 ]0 f! I3 F0 c( ]# d
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    & a) V: ?  @+ S6 u
    2 q( q$ v* K% d" E/ E; g可以打印一下结果
    & Y+ f4 U* \, L& i8 M) f+ K7 J- p& T8 P' i
    y = voc_label_indices(train_labels[0], colormap2label)* j0 n# w$ x1 O$ }2 B7 u- |: h7 @
    print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES& M2 V. R0 O6 x: g6 `( |. d# ?
    8 v  U. y2 @: \, t
    2.4 预处理数据
    / g0 S2 e( T: z; v9 R! c
    1 P& q2 I1 l% e, x6 f3 I3 P; f在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。
    : w, q' Y. G% S! M% {, ?2 l5 y; I' ]# L) u  s9 s8 Y% ~

    % r; Z2 @" o9 E5 U% W1 I3 ^" Q7 jdef voc_rand_crop(feature, label, height, width):
    ; }' s  i5 N2 f7 ~/ G    """
    ' m9 d- d. F5 n2 P    随机裁剪feature(PIL image) 和 label(PIL image).
    1 W- K+ \& j7 \0 ?    为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做
    . q; C: J9 Z  v5 \# q  v    Get parameters for ``crop`` for a random crop.3 g; Y) s1 M4 e2 E  @4 O
        Args:& Z. H' F: o; O
            img (PIL Image): Image to be cropped., N! B! p! j/ p5 b
            output_size (tuple): Expected output size of the crop.
    + L  k3 C; S/ ?5 q) t8 m0 N- r    Returns:) `6 {2 u8 Y5 L! W9 {* g6 h
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
    : k7 }; z2 D) v    """
    ; p4 c/ {1 z4 O& k    i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    / O- L) o$ R' P2 i$ _: ?9 z* X8 A7 Z    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)$ F# h) }, h9 J) j
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    ! M2 M( p" l2 d. \! C1 q  w; {; @    return feature, label
    # ?$ l$ e4 R' |" ~; Z, Q! t6 H% X* T' d! e
    # 显示n张随机裁剪的图像和标签,前面的n是5
    ; e/ Z$ q3 U" _3 R' |( T) E" w& ]" bimgs = [], ~8 k, U: @: S$ u
    for _ in range(n):7 A4 X4 `# |) P& G6 o6 s  [
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    ) T9 o/ n4 `' E, }; R! r; v- rshow_images(imgs[::2] + imgs[1::2], 2, n);2 n+ f" e# [. Q  r9 r: {

    1 `6 f; m1 {3 C9 U# L' r8 ?2 W
    2 G+ [) M0 x( t" C+ t& {" V) f: ~ 2.png
    0 J, y5 ^3 {' U6 u8 Q1 g$ H9 W1 `# h4 V
    & z- P2 k1 K% {
    0 K. W' V; V% r8 @2 u
    3 自定义数据集类
    * t  h, l# g' y+ W! o  p; {. s/ S+ E4 A
    3.1 数据集类
    # A( P2 p% h9 v. t3 A$ F  L3 K" R3 N+ l+ K9 Z6 K3 L
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法
    ! Z( `2 H! [% Y& A+ a6 ~0 A
    ' N( `7 i! O5 b__len__ 实现 len(dataset) 返还数据集的尺寸。7 }# y: v: ~* `. [/ D/ J6 E; B
    __getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。
    2 u& j: ^& I7 g! U2 d由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。
    3 Z; V! J; \( k' F& [
      E7 w, k$ h; v% v' \6 f8 mclass VOCSegDataset(torch.utils.data.Dataset):. U4 [; w$ u1 _3 G
        def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    + a# r5 i) }! A: h7 z3 ~        """
    ! @/ Q' e3 A# m( w5 D        crop_size: (h, w)$ s. C- v0 V9 v4 \: n
            """5 w( ]& h% `$ w5 n! g6 V
            # 对输入图像的RGB三个通道的值分别做标准化
    8 ~/ @' G) e1 p) k% F* M        self.rgb_mean = np.array([0.485, 0.456, 0.406])
    ) z1 P0 _* d6 X6 a        self.rgb_std = np.array([0.229, 0.224, 0.225])
    1 v$ m1 l1 C6 _; g. @        self.tsf = torchvision.transforms.Compose([
    : B$ a$ p3 K7 \            torchvision.transforms.ToTensor(),
    7 p. Y& e! q- a            torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    1 n* Y6 u0 O; K5 Z        self.crop_size = crop_size # (h, w)  @5 D. T' [: G" n5 ]4 i7 E3 T2 H
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)* |) H, x; p. I, p# \% I9 X
    # 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除
    7 [( B! E8 m! e+ p: r        self.features = self.filter(features) # PIL image9 o; N, h& r9 C; N( V0 D3 M
            self.labels = self.filter(labels)     # PIL image
    ; [" m( ~' Q* j5 ]5 n2 R        self.colormap2label = colormap2label
    + R; r; l5 U3 [' D4 J$ L! H6 t        print('read ' + str(len(self.features)) + ' valid examples')! V& h. e' t! G

    ! N! ^8 h+ u7 K8 `4 w    def filter(self, imgs):9 |0 h* s3 ^0 z+ q1 x
            return [img for img in imgs if (
    $ Y' r1 H. |5 Q3 h' `- u+ M            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]$ L' r" ]9 D5 y& n1 x8 m) x

    . Q* W1 i2 A1 y# K9 s  k    def __getitem__(self, idx):% k. |" O2 L! D% {) r
            feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)
    5 a' o1 i' v7 ^7 R                                # float32 tensor           uint8 tensor (b,h,w)$ X3 P2 b( ~& M& {+ T1 e& `
            return (self.tsf(feature), voc_label_indices(label, self.colormap2label))
    % z) C8 _( b- M4 s+ y
    " R  [2 k. {( j# B0 n    def __len__(self):* y' `) n* x$ z# _+ g9 u; Y" Z& V
            return len(self.features), H& y( D0 i- d5 ~& q( G4 L
    3.2 读取数据集1 ?. d# B6 c$ S) N, x0 g

    ! e0 K$ e+ o( j+ \+ T" h通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。# q1 c" U1 R5 ?( Y; Z

    $ o0 U8 y; F* _0 d% k! Obatch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)
    " Y; h- q  D$ h' F  Q4 P; mcrop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)
    ' Y% C+ {2 [; z$ i# \max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    3 B3 h" ^( C  ~8 ?: |( v3 K; r. T& a( g; k' R
    # 创建训练集和测试集的实例: b3 X5 y! r: ~6 W3 m
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num)/ }& ~3 [  G3 Q8 J6 b/ W
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    9 V! _+ m; u) t1 S- P0 P6 Z0 f* N3 e% f) Z
    # 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器9 P! N& d: N5 F
    num_workers = 0 if sys.platform.startswith('win32') else 4* K" F- @# ]7 W$ n
    train_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,3 o! c7 c" N/ e0 E  V
                                  drop_last=True, num_workers=num_workers)
    % ]7 V- O$ N3 k1 ]- ^" Ztest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    ! q& o9 {: [5 O9 |; Y3 }                             num_workers=num_workers)" y0 z' {1 e4 P& s  }7 \6 w

    4 g1 |+ |$ y9 X/ A% E4 h6 N# 方便封装,把训练集和验证集保存在dict里: W$ ^) V( [" x& B
    dataloaders = {'train':train_iter, 'val':test_iter}
    3 N+ I. ?6 d1 Adataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    . y, D) ~& F6 \. O+ ~0 c3 m5 ?$ @& Z0 c' @, t3 j( L
    4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    5 p" K! a7 \4 c1 X: w
    ( @- h3 ]" e, c# v) d# tnum_classes = 21 # 21分类,1个背景,20个物体
    2 N3 F) Q, o$ l9 g. j% H, dmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数
    8 f& N0 S9 r2 b" C. }, o6 n% H0 c  q# O  s0 E
    # 特征提取器
    : ^: H8 |# R. i, afor param in model_ft.parameters():2 f8 U5 |4 Y1 K
        param.requires_grad = False
    4 S# o& X, ^9 c/ |6 A4.2 修改成FCN
    ( N2 C9 l5 j; b/ [) L* E+ w: o+ d! ^. ~
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    1 Q$ G# y, _2 S  l* t6 a/ w最终输出的通道包含了该空间位置像素的类别预测。
    , R6 P9 l- R" I$ h) N* q9 k- x
    0 N% h# B7 x% V6 j9 }对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    ' {% q) v+ E9 f% D- A/ N3 G+ |* @) z" r. M' {. M% }2 u6 P4 a
    可以先打印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 倍即可。6 o9 \2 B6 `  K' E  s; c

    & V, A1 @, O$ \8 c8 k+ smodel_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    4 n1 y# l. Q0 _8 m. U0 \3 ~) M              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class
    4 V, d* q1 w4 _, _+ R) T5 x; B/ ^, ^              nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小
    3 @: F- x) ], y! h4 i# ?' i6 c0 f0 I6 K8 U* `9 |/ r7 {! S0 O
    # 对model_ft做一个测试) A. ]7 F% ?; |& r" T7 z; \
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据
    $ O: J& t& w& u# l) m5 Fprint(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480]) * P7 _& L) X( R
    6 ~2 q2 D& t; }' n
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组$ D' N( l% T' I6 i6 W5 M
    # for X, Y in train_iter:
    & K/ {6 b( P: _' k9 {#     print(X.dtype, X.shape)& e* H' q# ?. r$ {' G- T8 F
    #     print(Y.dtype, Y.shape)
    / B6 ^  s% `, U) X+ f  ^#     break. ~5 m: Z* f0 k- _" y
    ! w$ M) p7 t; M3 x1 u

    1 s" L$ S0 f$ p" X$ n1 U5 h4.3 初始化转置卷积层  e( V3 Y7 e$ M
    - g) |9 T2 j( W  |# `4 S' y& O
    在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像
    . w5 }1 P2 d+ Q2 K  `4 l# }. A在坐标 (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函数构造的卷积核的转置卷积层来实现。
    ' K/ Y4 C$ S4 P$ ~+ v5 d
    ' v2 Q! Q# m+ ^! o$ l9 r0 {7 G: F) F7 Y' I( b" J4 x8 T
    # 双线性插值的上采样,用来初始化转置卷积层的卷积核( X- J9 x3 ^, H: a. a
    def bilinear_kernel(in_channels, out_channels, kernel_size):
    3 z9 y- o7 p2 U* H1 N$ \; R2 j0 X    factor = (kernel_size+1)//2
    ; g% c) b* `+ C5 ]" W$ E    if kernel_size%2 == 1:" R1 |$ d0 [' i* X; J. U. h- E: i6 [9 }
            center = factor-19 J" Z, w" [4 v
        else:
    $ \# B0 l0 R5 p; L        center = factor-0.5
    ; ]+ v7 @% H  d2 L8 s, ]    og = np.ogrid[:kernel_size, :kernel_size]* |. c. w  P- P  k9 G
        filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)  |$ ~% M" l6 @9 E
        weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')
    9 @; r5 {/ D* G  H    weight[range(in_channels), range(out_channels), :, :] = filt1 ?3 F& Q5 C3 W
        weight = torch.Tensor(weight)
    4 g+ {! J9 U6 @/ T: u% g2 I    weight.requires_grad = True
    - L$ d2 S) w, [4 S    return weight& K  i% @: Y. P' k
    ( D( {/ |" j" ]' |/ D
    . H/ B9 E  ^4 H. p
    在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。% Y* h* X# z! r  T/ r- i" N

    2 u5 q  W# l. k8 unn.init.xavier_normal_(model_ft[-2].weight.data, gain=1)# k0 w( k+ E. G6 B! G- f% V
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)" I5 t6 W; r4 A/ ]0 z& F

    ) u0 \; E/ Q' G% L
    1 J( q, F, i, W# M1 G( I" s5 N3 W: @* s, ~' m/ m
    5 训练模型

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


    0 M5 B* D% B9 r4 idef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):. j+ ^2 ^+ }- M! y- C  ]8 ?
        since = time.time(), A$ {5 G& O6 p, {) d  e+ _4 G
        best_model_wts = copy.deepcopy(model.state_dict())
    0 P2 r' z/ g% l* P$ `6 W    best_acc = 0.0
    ) v" u/ I5 f, }$ |& l8 C2 J" i    # 每个epoch都有一个训练和验证阶段  c, ~! L% @0 w" ?2 f) g, c
        for epoch in range(num_epochs):3 s% l9 E9 ^9 P# v0 E# n. l
            print('Epoch {}/{}'.format(epoch, num_epochs-1)): ~3 ^2 O3 l1 k+ M
            print('-'*10)
    , x9 L1 d5 G3 p4 ]; i; P$ b( m% W$ {        for phase in ['train', 'val']:- z' h+ i1 C- k* D1 [3 F8 q5 o
                if phase == 'train':
    / }8 O" m9 ]. J. Z7 U3 ]                scheduler.step()
    ! m/ P& T# P7 L) ?8 j                model.train()
    ) e+ f2 G& \) g( U( v2 o" c            else:7 ~! O5 _3 R5 k6 T5 u' f
                    model.eval()
    1 f8 U3 x6 P, x; `5 n4 V5 c9 c4 e( F            runing_loss = 0.0' m% D4 Q" z$ A) v
                runing_corrects = 0.0
    : y3 O- G, s6 C/ r: b% d" n( l            # 迭代一个epoch
    % g6 R2 y7 n- D8 p4 b5 z            for inputs, labels in dataloaders[phase]:! f9 q( H9 W- Q! N5 V
                    inputs, labels = inputs.to(device), labels.to(device)
    4 u/ ~7 `6 Q* U% ]                optimizer.zero_grad() # 零参数梯度. A) [8 x8 b5 P0 h9 O% C
                                    # 前向,只在训练时跟踪参数
    & P" X8 Z  M8 v6 R& _1 e                with torch.set_grad_enabled(phase=='train'):
    : S  |$ b$ r7 y3 F0 ~' U4 \9 C0 A                    logits = model(inputs)  # [5, 21, 320, 480]
    # |9 O) k2 T% A7 r7 E                    loss = criteon(logits, labels.long()). K* A, C- B0 O* C/ t
                        # 后向,只在训练阶段进行优化
    9 q3 I: h: ]! B( m                    if phase=='train':
    . M$ e  U" H' }7 M  j( t) [% w                        loss.backward()
    6 y. M: S8 T5 ]( [5 P                        optimizer.step()* _& g3 c& a$ r, E7 `- D! I0 ]
                                    # 统计loss和correct2 g7 T8 U$ j: f1 J/ f2 ?
                    runing_loss += loss.item()*inputs.size(0)
      V# @$ T2 W+ i& d% w                runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320)* t0 `( N) \- F1 w

    ' ]3 O5 Z6 n, j% [) V            epoch_loss = runing_loss / dataset_sizes[phase]6 L0 r* P2 b3 u: r3 @
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]8 o1 ~% `- s3 n  T9 u# m
                print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    " d. v* _# E9 t. P* c                        # 深度复制model参数
    6 M! m" H4 E* B+ ~' Y* q' W6 _  p            if phase=='val' and epoch_acc>best_acc:
    + P3 b) e; ~( K9 ]                best_acc = epoch_acc
    ' u/ Q1 O* c7 y                best_model_wts = copy.deepcopy(model.state_dict())% ?6 \5 C. X% _7 s( W* F
            print()
    # M0 x, |! Q; ?* \% X( ~    time_elapsed = time.time() - since;
    . N. C6 C5 @" M/ O8 Z; ~2 r    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    / N, s- I$ L) M$ U( z0 W6 ^& N    # 加载最佳模型权重/ w: i4 G1 p7 Q+ ?+ l/ Y$ h
        model.load_state_dict(best_model_wts)
    ; ]4 F# M: I/ _    return model" z5 S5 K  \6 g. Y4 n5 U: u) p
    ' G: i" R2 ]% B* t9 N5 x  f
    下面定义train_model要用到的参数,开始训练
    0 J. _9 w6 d& u- ^2 b
    % `3 X. Q( [( ?: l) Kepochs = 5 # 训练5个epoch  I4 q: b" o- y) U7 a1 B# V
    criteon = nn.CrossEntropyLoss(). g4 }; N7 b( p5 q" [
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)
    2 z- a7 M5 B) |/ c/ i# 每3个epochs衰减LR通过设置gamma=0.1
    2 a5 b! f, [% g9 H; Texp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)
    1 r7 L' u- ~2 s7 _8 P6 J7 N
    ) i! N* ~, q. N2 \/ N. o% ^# 开始训练( o' J' c. l) \* q2 m, _/ U
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)8 F# O; H0 c) T5 z( n  k( w2 O" l
    ) r  U1 }1 f; ^
    6 测试模型

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

    def label2image(pred):
    , M9 s6 C9 c0 r4 s* z. M5 }    # pred: [320,480]
    6 S$ v$ x. z5 |  ]    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    , C# |8 Z( w# F$ L4 ]& q    x = pred.long()( S0 K9 |- {* u: @% s' _- Q
        return (colormap[x,:]).data.cpu().numpy()
    # e/ b' c* G- R* m# h9 ^0 `/ \2 j+ k& A1 Q3 H- `6 K

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device)
    % o$ I9 f4 D( A7 `( r1 dstd=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)0 x# r4 H$ h' r4 G
    def visualize_model(model:nn.Module, num_images=4):
    : b* X7 i9 F1 p6 M    was_training = model.training
    ; w/ \: K; D; y, f3 z4 f    model.eval()/ g# T. @( l8 H2 ^- B
        images_so_far = 02 U  k2 U2 J- {0 s! W, i
        n, imgs = num_images, []0 m) ]0 d) I3 A, F$ [* T5 l
        with torch.no_grad():
    / A7 E  _- P" B* \        for i, (inputs, labels) in enumerate(dataloaders['val']):
    6 F6 a0 f7 g* T* Y            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]  T& r4 B/ k$ k- ?
                outputs = model(inputs)
    4 M5 U8 y* _2 K9 ?            pred = torch.argmax(outputs, dim=1) # [b,320,480]
    ! G1 h( ]- v, p# _            inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    6 ], I2 X! f+ x. A) R# R2 V1 E1 `0 a* T7 D& }
                for j in range(num_images):- u% [  @$ u! Z. \* d  u3 k
                    images_so_far += 16 b8 @: A5 I( e, b" N6 M& W
                    pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)* y7 g/ e/ V2 d# o% M5 G- O
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]9 p+ S) g; k8 n4 p( N1 Q1 Y4 |- L
                    if images_so_far == num_images:3 r  _# a9 j: ?) ^  e- t% f+ L( d
                        model.train(mode=was_training)
    6 [( b& J# D$ @$ y$ b                    # 我已经固定了每次只显示4张图了,大家可以自己修改
    3 z' t1 E9 g* P2 E                    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n): F% |7 s; S% ^1 D2 t: g  g
                        return model.train(mode=was_training)
    1 b0 V& S1 p3 A9 a2 i  v# `0 D+ J8 S1 M9 Z" f0 L4 }. P9 r
    # 开始验证
    ' v( L1 V- T$ d, gvisualize_model(model_ft)8 A/ O+ x' |) ]/ n8 n5 `
    6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    6 r" M: O, G) @  ]* D/ E* ?* adef predict(img, model):
    - i, o" v2 ^" Z: z+ y7 k: \    tsf = transforms.Compose([2 E% d. W% n* m( z9 t1 B! k
                transforms.ToTensor(), # 好像会自动转换channel3 [+ ]( p4 y  ^7 d
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])]); x, x5 f6 h# f9 P/ S9 B
        x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)# }3 n# _- B# }
        pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    ; E" T% G0 t2 {8 K! h) \    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480)
    8 C& f: O9 v4 y9 t  l; {
    , t6 p& N* M4 a0 Y: b3 i3 gdef evaluate(model:nn.Module):
    ) F* t3 u( Z) P$ A* S: y: Z    model.eval()
    $ V  a* ~% L, C7 g! P0 L4 D( B$ Q    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10) $ _0 [0 V/ H: A8 S5 F
        n, imgs = 4, []( t6 r: A2 F3 q' J8 R
        for i in range(n):+ q0 n: @) q6 J% r# n+ V
            xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image
    ; w& C! R6 f; s3 ~- I        pred = label2image(predict(xi, model))
    $ m( [% m5 i/ o6 z; w        imgs += [xi, pred, yi]  _& W+ E+ i3 y' }
        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    * D/ d- B4 f2 B, ^; Y8 d" C5 a6 p
    ) L6 ?  x* ?! d$ R# 开始测试* @. ]( u# l* k5 s: Q8 m
    evaluate(model_ft)
    9 @' ^6 b; g' o3 j2 |
    2 p# t; U; p0 c9 ?# e7 结语

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

    6 X# s% R  I) \
    Epoch 0/2
      `4 u+ D8 ?4 Z5 g5 s8 l----------
    / t# H2 n0 s# d5 F! r2 h2 _0 ytrain Loss: 1.7844 Acc: 0.5835$ n6 I5 \5 J1 P6 \" \! w' G3 ?
    val Loss: 1.1669 Acc: 0.6456
    5 `5 {8 h, t2 R# [& Q8 D( I
    4 d, V6 |/ W' S' X0 Q  d# K7 A! BEpoch 1/2
      I( y; z: Z7 L6 g3 j0 N9 m" `! ]----------
    ) O- @7 a9 ]9 a2 R0 xtrain Loss: 1.1288 Acc: 0.6535
    8 w5 ?. K3 K: S% ?- Gval Loss: 0.9012 Acc: 0.69294 P$ ?3 G( D) j/ L) c7 j2 n
    & W$ y5 t: u' g6 `9 c1 b
    Epoch 2/2
    7 G/ e, Q" D( B0 X, q: o, n% ^----------" B0 w, y( f5 K& W
    train Loss: 0.9578 Acc: 0.6706# |# y: i( E; ?# K
    val Loss: 0.8088 Acc: 0.6948
    ; z: K$ N( n- G6 I/ m4 @
    8 K9 C' c, A1 F5 ~; DTraining complete in 6m 37s
    " p7 M! }( H: H; k0 v0 b4 F* w/ I1 d, i3 s0 s# j. b3 Y! R3 L

    9 f( y9 t! }) ]# z 2.jpg
    9 |3 C6 {# O2 U, T) t6 Z" w5 \' x  i  L
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。1 Q% |2 R, I; Z+ q) M& O0 m0 G  T
    & L; U) w8 _* E' q3 ], [- @
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。8 s3 {) @- m6 F) S1 l/ ~
    ! S. M# |% @1 z
    * A$ ^, N% L+ R( c
    语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:9 w3 B+ @0 b5 q8 K) A+ k+ d8 Y- L

    / Z& X  w) y- A9 b, ?1 C3 B/ ^8 XDeeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]" H8 p$ e  O: q
    GCN 通过全局卷积网络改进语义分割[论文]$ K2 o/ m- q6 L4 s8 D" P& b) j0 v
    UperNet 统一感知解析+ R3 f% Q  j) C: P' ^' @6 D
    ENet 用于实时语义分割的深度神经网络体系结构[论文]
    ) x; B4 V1 m6 _2 cU-Net 用于生物医学图像分割的卷积网络" x: p8 n" g1 I! P$ _
    SegNet 用于图像分段的深度卷积编码器-解码器架构。
    # x( G/ c8 d$ ~7 Y/ R/ Z; b+ O) X& V还有(DUC,HDC)、PSPNet等。! }! Z  |1 g- G: q7 K
    ' A9 A; \% S+ O7 d6 M
    常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。
    & ?: L! ^% l# [: u. \3 R
    ' G  _9 M$ |; ^# X: Q对于损失函数,除了交叉熵误差,也可以用这些:
    - N. `3 G. X$ K& w% \" s1 b( h% s3 W4 ~1 F
    Dice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。/ U7 q, {3 S. x7 b$ D/ K6 z) P- z
    CE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。% m. m& q" j  I9 z/ J% g3 m% m
    Focal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    " \8 O6 _( h2 c1 j8 ?; ^- d$ }Lovasz Softmax 查看论文:Lovasz - softmax损失。& x1 J& V) k; W# A" `( K

    2 r* u: c, U, o  b3 C3 B8 p7 j3 s" Q: e8 l/ a5 b" ^& x8 n0 G/ F, ]
    ; a% j; ]3 c0 V6 @
    7 _( q- D8 M1 C" A" f' J

      k6 b$ L2 H4 g————————————————2 s. P% E6 s/ G$ R( l: @$ P
    版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。7 @9 o* b6 N) d
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507
    9 i6 Y# O4 K" i/ i" D
      ^2 [2 S$ x5 F' f: m4 ^) o2 s" j& B' \0 Y% ~  i
    # k9 {+ n9 V( b0 `, [
    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, 2025-8-9 22:52 , Processed in 1.478967 second(s), 53 queries .

    回顶部