QQ登录

只需要一步,快速开始

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

    ) h# u6 \" j1 |. }
    0 y5 y9 w% E- D7 ~1 [1 B* bPytorch实战语义分割(VOC2012)
    " N# S6 F7 n" ~: O本文参照了《动手深度学习》的9.9、9.10章节,原书使用的是 mxnet 框架,本文改成了pytorch代码。( M: C3 @8 _* h) Q
    语义分割(semantic segmentation)问题,它关注如何将图像分割成属于不同语义类别的区域。值得一提的是,这些语义区域的标注和预测都是像素级的。5 F% m$ O" q# `( @0 \" `" [: _

    ! J* j, d) p# Y# [9 f8 g" c
    + d. J$ j$ j% u3 C$ Z1 X语义分割中图像有关狗、猫和背景的标签! a5 O5 ?: v/ I' \
    文章目录
    & A3 C2 O3 f' N! |& D; v6 m4 Q/ {/ m  S8 e
    1 图像分割和实例分割
      l0 k6 U6 I" S' K2 Pascal VOC2012语义分割数据集! R/ i+ |5 z9 B* L- K' Y
    2.1 导入模块
    . ]6 H& ], e$ @# Z. [2.2 下载数据集
    , U6 V; F% k' a0 B- C3 {2 ?7 ^2.3 可视化数据7 B" h; d2 h2 c. S0 a" k8 H
    2.4 预处理数据" e1 I0 M& W; \
    3 自定义数据集类7 u8 l/ A& H2 q* T0 p( F
    3.1 数据集类% F% `5 T  C& N" C+ I  I/ g% e
    3.2 读取数据集! F) s% b5 G5 f, [' V
    4 构造模型
    7 e3 M" J* E, W, H0 R0 R/ K4.1 预训练模型
    4 ~1 t, I7 @5 e+ k, G, T4.2 修改成FCN) u. g& n) y8 v' m- h
    4.3 初始化转置卷积层
    4 o" m* S1 q2 E5 B+ ^$ H6 G5 训练模型
    ) I1 w. |) T5 ?- _9 r8 @& O6 测试模型
    ' H) f7 g( H! o' v) a/ i) g( C& {6.1 通用型) Q6 M! b- r( [( N
    6.2 不通用
    ! M! o) ~) ]# s5 v' \" O7 结语
    & {- |( r- L) J2 U1 图像分割和实例分割
      q4 ~, @& b4 |5 x5 C$ e/ y" C5 X: g9 A; e: z: d1 g
    计算机视觉领域还有2个与语义分割相似的重要问题,即图像分割(image segmentation)和实例分割(instance segmentation):* A9 r, s, J5 {/ h
    1 |8 i8 A5 i. G
    图像分割将图像分割成若干组成区域。这类问题的方法通常利用图像中像素之间的相关性。它在训练时不需要有关图像像素的标签信息,在预测时也无法保证分割出的区域具有我们希望得到的语义。以上图的图像为输入,图像分割可能将狗分割成两个区域:一个覆盖以黑色为主的嘴巴和眼睛,而另一个覆盖以黄色为主的其余部分身体。
    % q) B9 ?( D% e9 k; _7 R实例分割又叫同时检测并分割(simultaneous detection and segmentation)。它研究如何识别图像中各个目标实例的像素级区域。与语义分割有所不同,实例分割不仅需要区分语义,还要区分不同的目标实例。如果图像中有两只狗,实例分割需要区分像素属于这两只狗中的哪一只。
    & D7 |1 n$ L, f; u9 j, U2 r
    ; r9 S8 S$ |& j7 }! |- Y2 Pascal VOC2012语义分割数据集3 j6 u/ a) g$ \6 ^5 J" F$ c
    - S" |. a3 ^+ v, e
    2.1 导入模块1 i, h6 D" h: k
    import time
    ( _( C7 k7 u' Wimport copy
    4 o) }: e, G) m* n* Eimport torch
    7 M; }$ M7 c5 s% [) j9 p9 ufrom torch import optim, nn
    ; G2 d: h& _  [# I5 Y- I% Q) Limport torch.nn.functional as F
    ! U6 D5 A9 m+ @& d' E5 |8 @, |7 Cimport torchvision0 x- Z" p7 ]5 v
    from torchvision import transforms* }: l2 p* X3 E: v6 m+ S) v
    from torchvision.models import resnet18
    1 d" z( K' {9 }. }. j* w% ~import numpy as np
    / I+ Q! I% g2 h3 |) |- Pfrom matplotlib import pyplot as plt
    # g+ l' q1 I; G& I- f2 Ffrom PIL import Image+ Z; m0 c  ^/ N
    import sys% l) ~8 Z% Y1 _( P* c0 V
    sys.path.append("..")* x. b) i; ~. C1 T6 i
    from IPython import display
    3 L- C6 N* d* y5 j& Nfrom tqdm import tqdm' P2 {" L, }; B# s
    import warnings
    $ d1 ~1 d- _, w( i+ pwarnings.filterwarnings("ignore")7 t1 S! G7 N4 L' {& W. \

    ' V# y, F: n/ c& W2.2 下载数据集
    ; f# i" @9 C0 s7 T8 c" l2 M
    / Y& r) \  }4 l. [1 K5 O" o, c语义分割的一个重要数据集叫作Pascal VOC2012,点击下载这个数据集的压缩包,大小是2 GB左右,所以下载需要一定时间。下载后解压得到VOCdevkit/VOC2012文件夹,然后将其放置在data文件夹下,VOC2012文件目录是这样的:
    9 B6 t  v( r( p! b$ `: l- N3 e5 y0 X# H+ k) }

    * k3 x6 T, h7 o5 LImageSets/Segmentation路径包含了指定训练和测试样本的文本文件0 T) f9 i' S8 U( r- |
    JPEGImages和SegmentationClass路径下分别包含了样本的输入图像和标签。这里的标签也是图像格式,其尺寸和它所标注的输入图像的尺寸相同。标签中颜色相同的像素属于同一个语义类别。+ ~1 `! A/ Q. \2 o  W4 |
    2.3 可视化数据
    . b) p3 t0 I: r) M
    9 c7 ^  h! M3 U& b定义read_voc_images函数将输入图像和标签读进内存。
    & `/ v7 e& ?* |- s) y9 e5 g) X. y
    . J3 g1 p3 _( N$ }def read_voc_images(root="../../data/VOCdevkit/VOC2012", is_train=True, max_num=None):& N, T# M2 M5 |
        txt_fname = '%s/ImageSets/Segmentation/%s' % (root, 'train.txt' if is_train else 'val.txt')5 z9 c. e6 T' ^4 [7 Z" \
        with open(txt_fname, 'r') as f:; i- K: t7 T6 k. {. ]/ p5 b9 q
            images = f.read().split() # 拆分成一个个名字组成list! l, M# j! {0 L4 u7 f% z
        if max_num is not None:
    # \- r; a( T3 S- U: C8 ~        images = images[:min(max_num, len(images))]
    ; G- a5 @1 ]! H: K' L$ p8 w7 c% K& ~6 \    features, labels = [None] * len(images), [None] * len(images)
    * A6 [( g1 u9 P: f( b  w( b    for i, fname in tqdm(enumerate(images)):
    8 S: g( A! q  L) \6 d5 L; y        # 读入数据并且转为RGB的 PIL image: Z+ v- c; x/ N$ @3 \
            features = Image.open('%s/JPEGImages/%s.jpg' % (root, fname)).convert("RGB")
    ' d  ]' k, O/ k9 q0 ?        labels = Image.open('%s/SegmentationClass/%s.png' % (root, fname)).convert("RGB")
      @% i3 M2 u! a6 Y& S    return features, labels # PIL image 0-255, \+ E/ q5 {2 T% [4 b
    * K- N4 W$ X( s+ A2 c  ~+ C
    定义可视化数据集的函数show_images
      |- Y$ }4 Y# v1 O7 ~# k
    0 d' S0 l& ]: P2 x# v1 @# 这个函数可以不需要3 n: k0 @6 X& f" D$ `* z" i* X) u
    def set_figsize(figsize=(3.5, 2.5)):
    ' d- p, e. ~) \0 z6 A" G0 Q( E    """在jupyter使用svg显示"""
    ) p' V7 X. N/ F* R9 N5 C    display.set_matplotlib_formats('svg')
    6 V% s! ]  }* n    # 设置图的尺寸
    0 L" Z, j- ]4 Y/ i    plt.rcParams['figure.figsize'] = figsize  N- X9 ~6 Z+ h+ F' a

    " z5 a9 y& G  I$ D+ ?) ]def show_images(imgs, num_rows, num_cols, scale=2):
    / W* j2 W4 i3 v4 f: v: i    # a_img = np.asarray(imgs)
    # I0 m! D  C( D. T; j- {! i3 x& @4 }    figsize = (num_cols * scale, num_rows * scale)
    ! Q$ x: J0 \3 n( ?1 X0 B  J    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    % A5 Y% U2 F; x8 A: E2 E% X% g    for i in range(num_rows):" M& G6 p/ u( `, [
            for j in range(num_cols):% \. L, L- J3 [% V
                axes[j].imshow(imgs[i * num_cols + j])
    . f9 w1 h& b7 @. \& B            axes[j].axes.get_xaxis().set_visible(False)
    ; [; |0 j( K: w' I" B            axes[j].axes.get_yaxis().set_visible(False)
    % i9 B6 v# L9 m7 ^    plt.show()" ]6 P" c7 l/ C2 l. Y; Z; t
        return axes
    7 {% ^' {8 {- K. }( G1 i  ?! C' q; x) F! O$ y9 g
    定义可视化数据集的函数show_images
    " F& t9 B, t* `& D6 ]9 e/ a" l6 M' V  n- x& ~- b) e
    # 这个函数可以不需要. e+ P+ c; y5 g
    def set_figsize(figsize=(3.5, 2.5)):* h& d4 v, L- Z: N# |" E4 S
        """在jupyter使用svg显示"""" R- u4 A2 d3 `
        display.set_matplotlib_formats('svg')
    4 S3 R3 T: a- Q& d7 w    # 设置图的尺寸! J) {! K; n/ J. ]1 J$ f
        plt.rcParams['figure.figsize'] = figsize0 H; ?: V$ D4 ?( v) k
    ! ]! M1 U0 L$ `# O' d7 p
    def show_images(imgs, num_rows, num_cols, scale=2):
    , B% [+ k8 h, d    # a_img = np.asarray(imgs)
    ! W6 R+ k3 g! q5 f0 j% I* u4 M    figsize = (num_cols * scale, num_rows * scale)
    8 b% U3 W7 F* h9 i4 H4 ~; d    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)/ H1 d' R3 j: C) l; k2 a' ?
        for i in range(num_rows):
    ; v- H0 V! X2 ~5 J/ C8 n9 A        for j in range(num_cols):' P# M1 y1 U2 d0 J8 v" S
                axes[j].imshow(imgs[i * num_cols + j])
    4 |* T+ b1 T( N0 I            axes[j].axes.get_xaxis().set_visible(False)
    + O6 N& s8 I# C8 S% H0 Z! x3 M            axes[j].axes.get_yaxis().set_visible(False)3 G, G: q3 K9 n5 E6 X6 r; ?/ Y
        plt.show()4 c; H! B2 a6 U* L) ]
        return axes: k' M: a* u' @' L: ~( J6 ^
    画出前5张输入图像和它们的标签。在标签图像中,白色和黑色分别代表边框和背景,而其他不同的颜色则对应不同的类别。1 y" W5 w0 |+ K9 _6 ~4 Z* U
    0 A) [- H6 v, n& x' Z' `
    # 根据自己存放数据集的路径修改voc_dir- l* ]6 N: o1 g, ~6 s
    voc_dir = r"[local]\VOCdevkit\VOC2012"
    7 K/ w( C- d- M* j  u+ u0 @; S0 c/ }train_features, train_labels = read_voc_images(voc_dir, max_num=10)
    - y! T- D5 _$ P" ln = 5 # 展示几张图像
    ( Z. {7 ^# C1 Mimgs = train_features[0:n] + train_labels[0:n] # PIL image% s5 e' c$ |  b* r5 B. C4 o4 m
    show_images(imgs, 2, n)
    4 Q' b' U8 P2 c2 l( _% E. c$ L0 z
    $ z& O8 b5 h0 Y- v. z$ c 1.png
    8 i! M. ^2 o0 I' u3 s. ]
    ; S% ~# u0 w9 T  n3 d列出标签中每个RGB颜色的值及其标注的类别。+ @! M- M: ^& ?% R! V) b% m
    # 标签中每个RGB颜色的值/ a% }+ d& g6 K, z9 @
    VOC_COLORMAP = [[0, 0, 0], [128, 0, 0], [0, 128, 0], [128, 128, 0],
    3 H+ v9 g! M. a8 T- Q                [0, 0, 128], [128, 0, 128], [0, 128, 128], [128, 128, 128],
    2 w/ r# I$ s1 ]  \; ~                [64, 0, 0], [192, 0, 0], [64, 128, 0], [192, 128, 0],8 ^& [) d5 C$ p9 g. S. U
                    [64, 0, 128], [192, 0, 128], [64, 128, 128], [192, 128, 128]," u% g. Q! z- Y+ P# C0 d
                    [0, 64, 0], [128, 64, 0], [0, 192, 0], [128, 192, 0],+ a0 {: p! G$ ~# B' e
                    [0, 64, 128]]- [9 y$ p% w. m
    # 标签其标注的类别& y3 J, Q5 Z+ N$ x1 M
    VOC_CLASSES = ['background', 'aeroplane', 'bicycle', 'bird', 'boat',$ R& i4 _6 l* ]% K* k7 x, w; R  o
                   'bottle', 'bus', 'car', 'cat', 'chair', 'cow',
    2 d& D! k+ U+ Z1 m2 q& j               'diningtable', 'dog', 'horse', 'motorbike', 'person',
    9 v- u, j& {; I4 l               'potted plant', 'sheep', 'sofa', 'train', 'tv/monitor']
    * Z( p, F' R6 V$ h有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。3 Z  h& X5 Q( Q5 M

    & x4 |$ a" o7 F5 v9 {+ C, L, g有了上面定义的两个常量以后,我们可以很容易地查找标签中每个像素的类别索引voc_label_indices是根据colormap2label把标签里的 rgb 颜色对应上面的VOC_COLORMAP中的下标给取出来,当作 label 。" `" }% A6 G( H7 w
    colormap2label = torch.zeros(256**3, dtype=torch.uint8) # torch.Size([16777216])6 f" o; }4 ?( i8 q: `8 C) U
    for i, colormap in enumerate(VOC_COLORMAP):4 ]+ Z) w. H( @' ~1 _5 z
        # 每个通道的进制是256,这样可以保证每个 rgb 对应一个下标 i; Q; L6 O- p  D) q, t. e. W
        colormap2label[(colormap[0] * 256 + colormap[1]) * 256 + colormap[2]] = i
    $ h# C% H- S; {: d. U  W; E& |9 e* r, Y  N* e. J) a
    # 构造标签矩阵8 K  W6 D1 _" Q. x$ R& l, r1 S  e
    def voc_label_indices(colormap, colormap2label):  j- g" n; z6 _8 Y! A  Q" n
        colormap = np.array(colormap.convert("RGB")).astype('int32')$ s' d+ {2 `8 f# G$ p9 S5 k$ _0 [
        idx = ((colormap[:, :, 0] * 256 + colormap[:, :, 1]) * 256 + colormap[:, :, 2]) 6 R& I# J; L: q! o+ [/ o
        return colormap2label[idx] # colormap 映射 到colormaplabel中计算的下标
    ( L. Y: ]! D$ c/ A  f, Q- K+ t. }+ f5 ^) ?. Y
    可以打印一下结果5 S" o- u9 Y, J- K
    ; `, q  y, q* o% W
    y = voc_label_indices(train_labels[0], colormap2label)
    5 D2 X: N# |; Z6 w$ e4 [print(y[100:110, 130:140]) #打印结果是一个int型tensor,tensor中的每个元素i表示该像素的类别是VOC_CLASSES% Q8 {8 p" H4 l4 u, r$ U

    2 J) C! j+ k$ t/ F0 C3 R2.4 预处理数据7 \# R- T" P2 q4 @2 P6 k9 V
    ! e" ^3 t4 h/ j+ a, M, p/ E8 u% R% n
    在语义分割里,如果使用缩放图像使其符合模型的输入形状的话,需要将预测的像素类别重新映射回原始尺寸的输入图像,这样的映射难以做到精确,尤其是在不同语义的分割区域。所以选择将图像裁剪成固定尺寸而不是缩放。具体来说,我们使用图像增广里的随机裁剪,并对输入图像和标签裁剪相同区域。: z' x" M7 @0 z( }- `5 Z

    1 q# v4 \5 P; ?  s" Y5 t
    5 \: N, S' i4 W0 p  T" [3 odef voc_rand_crop(feature, label, height, width):
    : U* D0 F$ n6 y, G& z. J4 f    """
    2 a2 ?9 F3 j4 ]5 p5 p    随机裁剪feature(PIL image) 和 label(PIL image).7 F1 y3 N/ L/ G3 {
        为了使裁剪的区域相同,不能直接使用RandomCrop,而要像下面这样做
    + g, |" m, {# {    Get parameters for ``crop`` for a random crop.+ v" t$ W9 Q0 C# E. L
        Args:- Z6 ~' p* i( ?( t" r; r8 y- Z
            img (PIL Image): Image to be cropped.  C& o, X3 o* w9 e6 D
            output_size (tuple): Expected output size of the crop.
    4 v' ^  q; n+ R+ V    Returns:& M- }% q$ C# B5 m  s5 ?( z' x# [
            tuple: params (i, j, h, w) to be passed to ``crop`` for random crop.
    ) W& D# H! k$ {, e4 F) q. N, i    """* D+ u4 L& h; T6 w0 e8 Y2 ?
        i,j,h,w = torchvision.transforms.RandomCrop.get_params(feature, output_size=(height, width))
    ( z! d* H1 d/ a    feature = torchvision.transforms.functional.crop(feature, i, j, h, w)+ X1 H, V$ _: |* Z
        label = torchvision.transforms.functional.crop(label, i, j, h, w)
    6 \/ w/ Y) L$ P$ f" a4 i" b    return feature, label
    4 ?2 n9 V; Q" b1 f  c; q" q
    . l3 O" V( C. z$ |6 N/ O8 b# 显示n张随机裁剪的图像和标签,前面的n是5
    ' [( J( ^! j, v% F" P. zimgs = []6 S" f- i+ o* k& N3 j% y
    for _ in range(n):" f5 P* B0 Z; H3 @% T* A
        imgs += voc_rand_crop(train_features[0], train_labels[0], 200, 300)
    ; q! j. `  ]* ?) b1 bshow_images(imgs[::2] + imgs[1::2], 2, n);
    ! t0 h3 T/ e. h, T, p9 N7 A! T9 ^
    0 r* m. Y' B2 K2 a2 ^) z# F, U2 e7 F/ U# V/ o1 Q
    2.png
    . Y9 j0 H3 V! R% P6 ~, S8 j9 r  m$ d$ g8 B# C8 x5 @, [

    ' r5 ^7 D+ I, Q$ ]3 i- O. _) s3 b, s4 b
    3 自定义数据集类; m4 x$ o* @! z* E) Y

    : t# \9 Y0 g- E! l7 w' n% v$ c3.1 数据集类  f& u. z! c3 T: w+ u# ^
    , g/ P; C2 q  t9 P
    torch.utils.data.Dataset是表示数据集的抽象类,因此自定义数据集应继承Dataset并覆盖以下方法* }( ~' l* n4 D$ p

    ) W" i& ^% k5 v( l8 E; z' _- [__len__ 实现 len(dataset) 返还数据集的尺寸。
    ) j) P1 \8 P+ |# e' Y9 h9 T__getitem__用来获取一些索引数据,例如 dataset[idx] 中的(idx)。$ X. F# \5 f; V
    由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除。此外,因为之后会用到预训练模型来做特征提取器,所以我们还对输入图像的 RGB 三个通道的值分别做标准化。/ h9 j/ s, l" r' [4 B

    / o9 H* f1 U1 Yclass VOCSegDataset(torch.utils.data.Dataset):
    - q. B; M* R. ]    def __init__(self, is_train, crop_size, voc_dir, colormap2label, max_num=None):
    $ [1 }$ z0 H/ H/ q/ ~- U        """$ l: L/ p9 Z' U1 M* E0 d& q
            crop_size: (h, w)
    2 c8 p$ a6 _5 J        """
    ! z: Y+ N; b3 R- r        # 对输入图像的RGB三个通道的值分别做标准化
    . Z2 H% Q$ G7 k* o" b# h        self.rgb_mean = np.array([0.485, 0.456, 0.406])3 G9 M7 L  z0 X- y
            self.rgb_std = np.array([0.229, 0.224, 0.225])
    0 k, @5 y1 l3 F) O& M        self.tsf = torchvision.transforms.Compose([* e$ _. ]0 K  i
                torchvision.transforms.ToTensor(),7 v- v6 I  P% {" Z  F( D& L0 ~
                torchvision.transforms.Normalize(mean=self.rgb_mean, std=self.rgb_std)])
    9 t5 A. g+ b6 ?% m( P7 L& A9 L        self.crop_size = crop_size # (h, w). q* s" B, k4 u. R5 j
            features, labels = read_voc_images(root=voc_dir, is_train=is_train,  max_num=max_num)
    0 A+ j+ X% q7 R2 P% S# 由于数据集中有些图像的尺寸可能小于随机裁剪所指定的输出尺寸,这些样本需要通过自定义的filter函数所移除9 ?9 M- z& i" [0 \
            self.features = self.filter(features) # PIL image
    # ^9 Q$ D, P! Z9 G$ B$ T        self.labels = self.filter(labels)     # PIL image% U! h0 l- V, }% u2 y/ @
            self.colormap2label = colormap2label; o" w) x2 U" x# r8 m9 x- ?  q6 T
            print('read ' + str(len(self.features)) + ' valid examples')
    " J. J7 `+ {: p; a5 ?0 P5 Z; Q9 z/ R5 Q* P3 r& a& c
        def filter(self, imgs):
    # E8 R- k  t4 S" `9 ]        return [img for img in imgs if (
    . r9 Q7 }" d! G( c9 D3 C            img.size[1] >= self.crop_size[0] and img.size[0] >= self.crop_size[1])]
    : M/ D/ R. s7 M& l' I* s) W
    6 ^' T" @/ d( |' b& ]6 u) r: [- C    def __getitem__(self, idx):7 J8 U: b+ `1 i( z
            feature, label = voc_rand_crop(self.features[idx], self.labels[idx], *self.crop_size)1 v( n0 B; T; X3 Q/ O
                                    # float32 tensor           uint8 tensor (b,h,w)- K0 @; G2 \0 q# y1 C4 W
            return (self.tsf(feature), voc_label_indices(label, self.colormap2label)), C$ \% X& w& j
    ) w( N; p: A- {
        def __len__(self):; K9 v. w- B& e% w
            return len(self.features)
    1 O7 ]" U7 [5 w3.2 读取数据集5 H) F' ]- @3 v0 r
    ( |5 o1 n# p. t! p# `- t
    通过自定义的VOCSegDataset类来分别创建训练集和测试集的实例。因为待会用的是全卷积网络,所以随机裁剪的输出图像的形状可以自己指定,这里指定为320×480​ 320\times 480​320×480​。  x5 i4 r# C7 L
    5 N0 _( p  X8 L& P; N" k
    batch_size = 32 # 实际上我的小笔记本不允许我这么做!哭了(大家根据自己电脑内存改吧)9 i- ]/ h+ {0 O* w* Q. T# k% Q
    crop_size = (320, 480) # 指定随机裁剪的输出图像的形状为(320,480)+ M( b# U$ u( i% @4 }/ t5 b) h3 ^4 E6 q/ y
    max_num = 20000 # 最多从本地读多少张图片,我指定的这个尺寸过滤完不合适的图像之后也就只有1175张~
    / P2 v9 [! m$ T# O
    : \( a% `, T2 C8 r# 创建训练集和测试集的实例9 F% T: E" o1 ]$ O# e" E5 S
    voc_train = VOCSegDataset(True, crop_size, voc_dir, colormap2label, max_num): N2 L' b9 Z, ?  g
    voc_test = VOCSegDataset(False, crop_size, voc_dir, colormap2label, max_num)
    - l5 D/ T$ L9 g2 y& `- e# J3 N
    - f& w- r" E: ?# A# 设批量大小为32,分别定义【训练集】和【测试集】的数据迭代器, f+ s9 l6 x0 f- L* q# o: {1 e' O
    num_workers = 0 if sys.platform.startswith('win32') else 4
    5 k, S, F: M. I6 i$ otrain_iter = torch.utils.data.DataLoader(voc_train, batch_size, shuffle=True,, K5 N7 h1 ^  w' K: O6 J! q
                                  drop_last=True, num_workers=num_workers)
    + b0 ^" W! @9 ~# d5 ftest_iter = torch.utils.data.DataLoader(voc_test, batch_size, drop_last=True,
    4 Z4 ]( o; C0 [- E) w                             num_workers=num_workers)$ w7 c& `5 [" p2 J; V! [

    ) n' x# W( y2 r3 S# 方便封装,把训练集和验证集保存在dict里
    ( h1 T) D+ u% s$ J3 S" pdataloaders = {'train':train_iter, 'val':test_iter}
    ( L  v1 `+ @+ \% J" \4 w* Idataset_sizes = {'train':len(voc_train), 'val':len(voc_test)}
    % a+ a. c  r( g( R( A4 A
    ( `2 F4 g' x$ r, w2 z% H, F4 构造模型4.1 预训练模型

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

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    $ K! U0 B) S6 @* D& Q8 j  O- k% j( O% }6 _
    num_classes = 21 # 21分类,1个背景,20个物体
    - i+ L1 ^  g+ t% g: Mmodel_ft = resnet18(pretrained=True) # 设置True,表明要加载使用训练好的参数+ M& u: w0 |. X/ @% _7 f

    : l- @" E. y: h( g# 特征提取器% [0 l$ j) C7 v) W
    for param in model_ft.parameters():1 I  P% q3 |/ I6 d/ b, x
        param.requires_grad = False
    9 v+ b* n# ~8 o& {0 f# t4.2 修改成FCN
    ; x6 ^! ~1 E! `  y# S, X5 {6 K& t- W0 u) [
    全卷积⽹络(顾名思义全部都是卷积层)先使⽤卷积神经⽹络抽取图像特征,然后通过 1×1​ 1\times 1​1×1​ 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的⾼和宽变换为输⼊图像的尺⼨。模型输出与输⼊图像的⾼和宽相同,并在空间位置上⼀⼀对应:
    5 B. k: e  U) L+ L4 x+ m最终输出的通道包含了该空间位置像素的类别预测。% W+ `0 ^5 L4 W7 Y. [

    ; G; ^4 v! [& g' I/ K对于转置卷积层,如果步幅为 S​ S​S​、填充为 S/2​ S/2​S/2​ (假设为整数)、卷积核的⾼和宽为 2S​ 2S​2S​,转置卷积核将输⼊的⾼和宽分别放⼤ S​ S​S​ 倍。
    ! ]. W2 Y+ t( L6 C
    ; m  _3 X1 s- ^" L4 V可以先打印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 L: x% K7 I, I& F: T6 v+ ^
    ) O" ]# q$ e7 M; b
    model_ft = nn.Sequential(*list(model_ft.children())[:-2], # 去掉最后两层
    * w! S" M0 j' Z7 [5 N( y              nn.Conv2d(512,num_classes,kernel_size=1), # 用大小为1的卷积层改变输出通道为num_class
    * }/ N" }/ p7 ~1 C( M! g              nn.ConvTranspose2d(num_classes,num_classes, kernel_size=64, padding=16, stride=32)).to(device) # 转置卷积层使图像变为输入图像的大小1 m4 m0 H  I" r7 M, k

    9 t! U' t8 s! U: v/ [) C# 对model_ft做一个测试+ b$ `) ?  x2 E
    x = torch.rand((2,3,320,480), device=device) # 构造随机的输入数据" u' j4 n  A9 z, [; Y6 R8 n
    print(net(x).shape) # 输出依然是 torch.Size([2, 21, 320, 480])
    2 B# v) j' h) W7 n. C/ p; K6 G  c0 b% y' v$ x  ?6 ]
    # 打印第一个小批量的类型和形状。不同于图像分类和目标识别,这里的标签是一个三维数组3 ^! m0 H* M5 Y7 K
    # for X, Y in train_iter:
    ; R5 ~* u6 w8 B! V) j8 `; u" D7 ~+ J#     print(X.dtype, X.shape)
    % P! _+ g5 p2 h' N, K. L- x#     print(Y.dtype, Y.shape): h9 D% T$ [5 `( x
    #     break( N8 l: |( b+ R* u
    # E4 l' _# f7 f* W9 F8 h
    7 i3 g( H1 c9 R2 W9 m
    4.3 初始化转置卷积层
      j) Y; ~1 K- P% y
    ! x$ P  _3 j+ S: `* B在图像处理中,我们有时需要将图像放⼤,即上采样(upsample)。上采样的⽅法有很多,常⽤的有双线性插值。简单来说,为了得到输出图像+ H! G4 W8 i/ ^4 U. x
    在坐标 (x,y) (x, y)(x,y)上的像素,先将该坐标映射到输⼊图像的坐标 (x',y') (x′, y′ )(x′,y′)。例如,根据输⼊与输出的尺⼨之⽐来映射。映射后的 x' x′x′ 和 y' y′y′ 通常是实数。然后,在输⼊图像上找到与坐标 (x',y') (x′, y′ )(x′,y′)最近的 4 44 个像素。最后,输出图像在坐标 (x,y) (x, y)(x,y)上的像素依据输⼊图像上这4 44个像素及其与 (x',y') (x′, y′ )(x′,y′)的相对距离来计算。双线性插值的上采样可以通过由以下bilinear_kernel函数构造的卷积核的转置卷积层来实现。
    7 q. w- ]0 `3 r# v. j8 P; N2 B. i$ X$ g

    8 i# S- e* q1 m: P5 s6 j3 |- F: ?# 双线性插值的上采样,用来初始化转置卷积层的卷积核
    $ A5 f+ L/ b+ o( b: idef bilinear_kernel(in_channels, out_channels, kernel_size):1 U# E6 D: C+ a3 I8 M/ R. }
        factor = (kernel_size+1)//2; q. e# q4 o. ^& [1 u6 f
        if kernel_size%2 == 1:
    ( f7 O2 u0 s6 v, c* A# e        center = factor-1
    1 B1 V% }0 Q5 w5 @7 t    else:
    5 B# ], Z  {. i& y        center = factor-0.5
    8 m& ]) |& _' f5 o    og = np.ogrid[:kernel_size, :kernel_size]5 ?0 w& K! q0 ?
        filt = (1-abs(og[0]-center)/factor) * (1-abs(og[1]-center)/factor)
    4 }( {' ?9 ^$ D6 L1 }3 `; `. `    weight = np.zeros((in_channels,out_channels, kernel_size,kernel_size), dtype='float32')( {* V( [9 Z' p
        weight[range(in_channels), range(out_channels), :, :] = filt0 o3 v% L  M$ l( Z4 k. F
        weight = torch.Tensor(weight)  T& b+ y* d; C0 n
        weight.requires_grad = True
    ' U3 i4 A6 d$ p: f5 F    return weight
    + r4 V6 s$ z0 y9 Y2 w3 c
    0 Q. o$ @/ \0 \# k6 T) E/ e
    0 i- n/ I4 I. a& j在全卷积⽹络中,将转置卷积层初始化为双线性插值的上采样。对于1×1 1\times 11×1卷积层,采⽤Xavier XavierXavier随机初始化。
    0 N# j! D2 F# w# q+ H. a1 M$ @' t/ y  f
    nn.init.xavier_normal_(model_ft[-2].weight.data, gain=1). L& }  S6 C' T* O) o
    model_ft[-1].weight.data = bilinear_kernel(num_classes, num_classes, 64).to(device)
      Y- P4 a) S( b* g: e" W- q8 v& ~( R! k

    ) M% Q" E; B+ o  h& j+ |) T8 H( h7 Y! ?. {, m8 O
    5 训练模型

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


    " d& z8 z6 {& n4 tdef train_model(model:nn.Module, criterion, optimizer, scheduler, num_epochs=20):
    5 H/ O3 i: e# S# {- n( q3 a    since = time.time()& }" q( V8 d7 Z" c
        best_model_wts = copy.deepcopy(model.state_dict())% `2 B: ]: j! x3 r" G
        best_acc = 0.0! L) U: ~  }7 b
        # 每个epoch都有一个训练和验证阶段
    4 `$ ~$ r/ i( d1 p7 E    for epoch in range(num_epochs):
    0 {8 G7 u8 F% x6 V7 \- i        print('Epoch {}/{}'.format(epoch, num_epochs-1))) k: M( y8 b9 z7 V7 `: Y' a; }' e/ |
            print('-'*10)
    $ u( z+ ^4 I6 L% c7 Q' b        for phase in ['train', 'val']:
    # `4 ~' b7 E& ^1 h) X, z) `            if phase == 'train':1 z  ?- L' `9 I2 T9 c( C
                    scheduler.step()# y" Q4 v; M9 \: s
                    model.train()# e4 W/ [# S' j. d9 b! W& I
                else:  \* R; k! ~$ X0 r
                    model.eval()
    1 }$ {  x; w+ Z1 N: V            runing_loss = 0.04 n7 o7 n2 E( R. P
                runing_corrects = 0.0! I( h- d3 `+ y+ L
                # 迭代一个epoch8 s8 P$ s5 k5 k8 S( Z
                for inputs, labels in dataloaders[phase]:
    3 k, h8 t% A" n$ F# ?% J3 I5 Z0 R4 ^                inputs, labels = inputs.to(device), labels.to(device)( ?( f) T/ v5 H8 L9 l' G0 K! C
                    optimizer.zero_grad() # 零参数梯度
    5 R; Z% {8 m5 x, R( y1 ~                                # 前向,只在训练时跟踪参数
      t1 P! |! k# e" A- ~                with torch.set_grad_enabled(phase=='train'):
    . p" d: y- v6 g! o( H) @) Q                    logits = model(inputs)  # [5, 21, 320, 480]
    + Y8 C. ^6 B# t3 v                    loss = criteon(logits, labels.long())5 ~" k# ?/ }; y; V/ g  t/ r3 T
                        # 后向,只在训练阶段进行优化
    ) _7 }% ?6 o- ]  ~                    if phase=='train':' H3 }7 h  n4 y
                            loss.backward()
    * q4 ?2 }6 Q, U0 A+ h1 \                        optimizer.step()
    , I5 }$ J7 Q1 O! U: J% n& R                                # 统计loss和correct
    & ?! m% Y# \5 t4 S6 I  I  a                runing_loss += loss.item()*inputs.size(0)8 u/ @$ p* G" ^( `" K5 }
                    runing_corrects += torch.sum((torch.argmax(logits.data,1))==labels.data)/(480*320). Q" Y8 T- C2 E; l8 P- h2 H2 ]& p

      d- b$ y3 m+ K* _1 }            epoch_loss = runing_loss / dataset_sizes[phase]0 o0 d+ {) Q3 W* a% Q: q
                epoch_acc = runing_corrects.double() / dataset_sizes[phase]! u. {5 D; O6 m" W
                print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
    3 f) Y- D3 S  H' O" ]! q                        # 深度复制model参数8 C/ K$ Y6 l; V' Y
                if phase=='val' and epoch_acc>best_acc:! S$ O: b" y/ V; f2 a
                    best_acc = epoch_acc
    3 A0 f- q% b9 i4 n                best_model_wts = copy.deepcopy(model.state_dict())# W+ E( a. i2 w# J# p7 A9 d2 [
            print()' V' I+ c/ p: |/ Q( z' C! M& l: C
        time_elapsed = time.time() - since;
    ) C9 z4 m; u# W4 h1 A0 ]3 p    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed//60, time_elapsed%60))
    ; Y0 W0 {- ^7 r' c, k  W( l    # 加载最佳模型权重
    1 S! M. \+ L2 N+ a1 I    model.load_state_dict(best_model_wts)* D; c; {! n9 m+ c# g; y- N* q& b7 i
        return model$ q$ L6 _' l* @

    7 J, @7 e2 T) B+ ]下面定义train_model要用到的参数,开始训练
    " k0 y' T' y9 E  i& _
    ) [1 @9 O* V6 g2 zepochs = 5 # 训练5个epoch
    1 ?& A. L/ a7 O' m8 W+ _9 xcriteon = nn.CrossEntropyLoss(); A$ S, J. Z3 S+ I1 D9 t4 f
    optimizer = optim.SGD(model_ft.parameters(), lr=0.001, weight_decay=1e-4, momentum=0.9)2 z" K* A$ V- u7 }
    # 每3个epochs衰减LR通过设置gamma=0.1
    4 Z$ S2 c( H8 u5 E: f  L3 S$ cexp_lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1), A/ \! }2 e8 j% I# Q5 w2 \
    $ @) |4 ?& a8 }. _/ @( X5 T
    # 开始训练) m) m6 k. l" ]; k1 }% D0 B( [: W
    model_ft = train_model(model_ft, criteon, optimizer, exp_lr_scheduler, num_epochs=epochs)
    & u" d& E3 O- Z0 k# y( s8 v
    + W* s& A6 D; C/ Q1 {; T3 d6 测试模型

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

    def label2image(pred):
    5 P: f. J3 B3 t( g+ p4 t    # pred: [320,480]
      U8 D/ |$ e8 J# ^9 e    colormap = torch.tensor(VOC_COLORMAP,device=device,dtype=int)
    - e: `( Z# O* d  p4 J2 A    x = pred.long()! h- v7 P, k! E+ Y! @: r
        return (colormap[x,:]).data.cpu().numpy()
    " v+ [: }: y/ S% q) ?( Y6 G: r" H9 ~- L. X7 `6 f# p

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

    6.1 通用型

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

    mean=torch.tensor([0.485, 0.456, 0.406]).reshape(3,1,1).to(device), R2 t6 f* T3 Z( v7 p8 @$ U
    std=torch.tensor([0.229, 0.224, 0.225]).reshape(3,1,1).to(device)
    + E' S  r8 V2 ?6 N# u( i% Qdef visualize_model(model:nn.Module, num_images=4):2 R  v6 c1 f8 K! `( G$ d9 ]
        was_training = model.training
    , g2 F6 d& V( W    model.eval(), g4 ^1 X0 K- X6 S# t+ n
        images_so_far = 07 @# I( Z* [/ ?3 [/ Z
        n, imgs = num_images, []1 Y. r+ t" Y8 S6 c- _, t. c2 P
        with torch.no_grad():1 \: J  Q/ B: C. j  B3 Z% u$ i
            for i, (inputs, labels) in enumerate(dataloaders['val']):
    7 `' J* O+ [! t$ o            inputs, labels = inputs.to(device), labels.to(device) # [b,3,320,480]( R/ r  l' I! P$ C
                outputs = model(inputs)1 f3 ]; A. L' k" |
                pred = torch.argmax(outputs, dim=1) # [b,320,480]/ h+ |1 o& X$ B
                inputs_nd = (inputs*std+mean).permute(0,2,3,1)*255 # 记得要变回去哦
    * L* X" [* r0 `$ E9 U. b& ^9 \
    & d5 |( E! s/ w3 }" f' P            for j in range(num_images):$ T( b4 h! w; K& y0 l2 q
                    images_so_far += 1
    4 Z5 O7 B- t* w; q- N                pred1 = label2image(pred[j]) # numpy.ndarray (320, 480, 3)( `9 C! B" h+ U: {" _7 s
                    imgs += [inputs_nd[j].data.int().cpu().numpy(), pred1, label2image(labels[j])]3 R* L5 x7 k1 x
                    if images_so_far == num_images:  x# k* d5 B+ z: }% }
                        model.train(mode=was_training)
    & i* q7 P$ G* M8 Y# D5 X0 A                    # 我已经固定了每次只显示4张图了,大家可以自己修改6 [- V  r( y7 r0 u+ E# E. W
                        show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)
    # k4 \9 b+ W: s& s+ P) a                    return model.train(mode=was_training)
    / h3 n3 v" N# p# M% t/ D8 ]6 v  D, {1 s; {0 F& x, U- Q) o
    # 开始验证% ^6 R6 n9 C3 t; Y6 q: m( ?1 e
    visualize_model(model_ft)
    - {8 N& }6 l$ a; q( D( X$ L6.2 不通用

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

    # 预测前将图像标准化,并转换成(b,c,h,w)的tensor
    $ |9 A( W2 S7 q+ Ldef predict(img, model):
    % O3 D* r7 W3 o# o- i; ?* V! b) q    tsf = transforms.Compose([% f) ]8 V9 I1 b4 E% H- P8 z
                transforms.ToTensor(), # 好像会自动转换channel
    2 |) b5 O9 h8 t. \            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])
    - y4 ]( x' V9 X3 Z6 d5 M% G    x = tsf(img).unsqueeze(0).to(device) # (3,320,480) -> (1,3,320,480)
    7 D/ Y# v( R7 F* p    pred = torch.argmax(model(x), dim=1) # 每个通道选择概率最大的那个像素点 -> (1,320,480)
    % s' g* D5 ]& g7 |    return pred.reshape(pred.shape[1],pred.shape[2]) # reshape成(320,480): Y7 N  }; v6 i

    $ T! N. W0 h% D+ p- kdef evaluate(model:nn.Module):4 s% B0 }$ Z3 s6 c5 j
        model.eval()
    0 k# c% x. w5 H) J    test_images, test_labels = read_voc_images(voc_dir, is_train=False, max_num=10)
    - O+ A- n$ C( l( J  F    n, imgs = 4, []
    * P3 s3 U& |$ r/ c  [    for i in range(n):
    9 ?1 u! u, L/ _/ l        xi, yi = voc_rand_crop(test_images, test_labels, 320, 480) # Image$ J6 N3 H) `2 l( \0 k: A/ e7 l
            pred = label2image(predict(xi, model))" o! ?( V* u4 ?3 P
            imgs += [xi, pred, yi]
    , Q4 {' O' f: C- s7 t- {    show_images(imgs[::3] + imgs[1::3] + imgs[2::3], 3, n)# X+ v8 a2 d) G# f+ _4 ~1 Z
    5 B7 m' _+ I+ e8 n% {: m$ G. {; ^
    # 开始测试
    ! N. |9 K/ H* w! nevaluate(model_ft)7 ^( k7 [% m' x- F

    & x& D7 f9 k2 b7 结语

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


    4 f  w9 m4 K9 M3 X  c) \Epoch 0/2- q4 t+ |6 i- R+ w/ l8 `4 j+ E' F
    ----------' r$ ~- H; h; A$ r
    train Loss: 1.7844 Acc: 0.58352 |* b# J+ Q( g) y9 y* B
    val Loss: 1.1669 Acc: 0.64563 N1 s0 U& E* A- c, U- W8 R' V" ^

    * x9 Z5 P1 s( x! TEpoch 1/2
    . i* A# Y1 v8 p. p- ~0 P' I----------: a- o$ j" m8 d9 Q% H+ |
    train Loss: 1.1288 Acc: 0.65355 y3 Z7 T4 d2 Z
    val Loss: 0.9012 Acc: 0.6929
    0 L0 C# d* |# X) W1 E/ U' ]' S% H) b" ]+ x
    Epoch 2/2
    # T# t% ~' ?! U& K----------0 X* \/ `8 O; a* B8 @3 K0 m8 E
    train Loss: 0.9578 Acc: 0.67062 ^; u8 X  @* Z
    val Loss: 0.8088 Acc: 0.6948
    % ?( s, |, Y$ L# @; J7 M* K9 }4 g
    6 Y2 g3 K8 F6 t( fTraining complete in 6m 37s) G- A% k! E% p3 Z; V( ~# w' @4 |

    1 x3 A- Z" c/ @* |9 b  p5 U) k8 y. S' V
    2.jpg
    . d. F; n- Z; |0 E0 p) y9 P" a' B0 \' V
    当 epochs = 5 时,训练集的精度在 89 8989% 左右,测试集的精度可以达到 86​ 86​86​ %。  f# J- |! G* z
    ! x6 T! l. y0 M
    对于这个模型用 ResNet-50 作特征提取器会有更好的效果,不过训练的时间也会更长。还有超参数lr, weight_decay, momentum, step_size, gamma 以及1×1 1×11×1卷积层和转置卷积层的初始化方式也可以继续调。' R$ ~, W5 U( g; @/ f/ r

    # Y( K& ^! J& T# Y* q* f
    5 d5 g  b) b2 t7 O) E8 e语义分割还有很多可用的模型,本文用的是 FCN,在其它一些模型上会有更好的表现:
    * z8 j& s! x0 v3 f* W6 a* W0 f) W, P7 W. d
    Deeplab V3+ 具有可分离卷积的编码器/解码器,用于语义图像分割[论文]
    # {, U0 C4 P2 z) e2 R& OGCN 通过全局卷积网络改进语义分割[论文]
    4 F" W# b: Q7 s( F8 C9 x* }UperNet 统一感知解析
    3 r7 F+ N1 J+ m2 ]9 [; U. NENet 用于实时语义分割的深度神经网络体系结构[论文]1 F! E' q. ~% c0 s9 f
    U-Net 用于生物医学图像分割的卷积网络( j9 \" B1 F& j3 v% |' E
    SegNet 用于图像分段的深度卷积编码器-解码器架构。
    / \" _: z6 b2 K  u# f/ _还有(DUC,HDC)、PSPNet等。
    5 b: s* @% b; l* j# K6 G/ ?1 D
    3 m7 S" a. x6 k! t常用的语义分割数据集也有很多:Pascal VOC、CityScapes、ADE20K、COCO Stuff等。2 Z* g9 x' x1 R' Y
    # Q1 U0 |* Y- x  b* H# W9 s) B0 J
    对于损失函数,除了交叉熵误差,也可以用这些:3 Z% S9 M% f( k

    " [4 y# a  e6 HDice-Loss 可以测试两个样本之间的重叠度量,可以更好地反映训练目标,但该损失函数具有很强的非凸性,很难优化。
    1 s# ]8 `% [% HCE Dice loss Dice 损失与 CE 的总和,CE 提供了平滑的优化,而 Dice 损失则很好地表明了分割结果的质量。
    3 G' L  v+ U! T1 s) M( kFocal Loss CE 的另一种版本,用于避免类别不平衡而降低了置信度的情况。
    : ~- J: |/ _4 R- s2 p7 E0 N8 PLovasz Softmax 查看论文:Lovasz - softmax损失。
    ' @  r% L+ }' H1 d9 x/ N. u) b, G. J
    # H) }4 E  g4 z, u3 D! L
    " B9 z0 q( C9 p! S0 n
    ( C6 ]9 a; {& b7 E7 m
    3 H! l! S; v' h1 N4 F* j! `
    ————————————————
    1 A' Z& ?4 L0 _: c6 ^' s1 ^版权声明:本文为CSDN博主「小红不吃糖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。4 a5 g0 O( a, j) ^. L
    原文链接:https://blog.csdn.net/qq_43280818/article/details/105916507$ n8 V2 W1 ^3 q0 H: h

    8 Q& F" r, B2 {5 P1 T% ^# D( _2 Q' ]% k, E: y: y8 \# K$ a0 V3 j& H2 ~

    % b& ]& f  H" M$ x$ H
    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-19 16:38 , Processed in 0.517627 second(s), 54 queries .

    回顶部