• Pytorch知识点学习


    1 Numpy

    本节要点:

    • ·如何生成Numpy数组。
    • 如何存取元素。
    • Numpy的算术运算。
    • 数组变形。
    • 批量处理。
    • Numpy的通用函数。
    • Numpy的广播机制。

    1.1 生成Numpy数组

    1. 从已有数据中创建数组
    import numpy as np
    # np.+Tab键,查看可使用的函数
    # np.abs? 可查看函数abs的详细帮助信息
    # random创建,创建特定形状的多维数组
    
    list1 = [1,2,3]
    ls1 = np.array(list1)
    ls1.dtype
    ls1.ndim
    type(ls1)
    
    list2 = [[1,2,3],[4,5,6]]
    ls2 = np.array(list2)
    len(list2)
    ls2.ndim
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 利用random模块生成数组
    np.random.random(100).reshape(10,10).shape
    np.random.uniform()
    np.random.uniform(0,1,[2,3])
    np.random.randn(10)
    np.random.randint(0,255,dtype=np.uint8)
    np.random.randint(0,255,[2,3],dtype=np.uint8)
    np.random.normal(10,100,[2,3])
    
    arr = np.arange(10).reshape(2,5)
    np.random.shuffle(arr)
    arr
    
    np.random.sample(20)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 创建特定形状的多维数组
    a = np.zeros([3,3])
    b = np.ones([3,3])
    c = np.empty([2,3])
    a0 = np.zeros_like(a)
    b0 = np.ones_like(b)
    c0 = np.empty_like(c)
    np.eye(5)
    np.full([2,3],10)
    
    # 应用
    img = np.random.randint(0,255,[5,5],dtype=np.uint8)
    mask = np.zeros_like(img,dtype=np.bool)
    mask[1:4,1:4] = 1
    img0 = img[mask]
    
    img = np.random.randint(0,255,[5,5],dtype=np.uint8)
    mask = np.zeros_like(img)
    mask[1:4,1:4] = 1
    img1 = img*mask
    
    np.savetxt(X=img0,fname = '/Users/ls/PycharmProjects/LS_Test/img0.txt')
    t = np.loadtxt('/Users/ls/PycharmProjects/LS_Test/img0.txt')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. 利用arange、linspace函数生成数组
    np.arange(1,10,2)
    np.arange(2,10,2)
    x = np.linspace(0, 1, 10)
    y = np.sin(x)
    
    • 1
    • 2
    • 3
    • 4

    1.2 获取元素

    1.获取元素

    n11 = np.random.randint(0,10,10)
    n11[5]
    n11[:3]
    n11[4:8]
    n11[2:9:2]
    n11[::-1]
    n11[::-3]
    n11[::3]
    
    n45 = np.random.randint(0,10,[4,5])
    n45[1,3]
    n45[[1,3],:]
    n45[[1,3],2:4]
    n45[[1,3],[1,3,5]]
    n45[[1,3],:][:,[0,4]]
    n45[(n45>3)&(n45<9)]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. random.choice函数从指定的样本中随机抽取数据。
    a=np.arange(1,25,0.5,dtype=float)
    s1 = np.random.choice(a,10)
    s2 = np.random.choice(a,[3,4],replace=True)
    s3 = np.random.choice(a,[3,4],p=a/sum(a))
    
    • 1
    • 2
    • 3
    • 4

    1.3 Numpy的算术运算

    Numpy的算术运算,一种是对应元素相乘,又称为逐元乘法(Element-Wise Product),运算符为 np.multiply(),或*。另一种是点积或内积元素,运算符为np.dot()。

    1. 对应元素相乘
    np.random.randint(0,10,[2,3])
    B = np.random.randint(3,12,[2,3])
    A,B
    A*B
    np.multiply(A,B)
    A*2   # 广播机制
    A/2
    A*np.full([2,3],2)
    
    # [2,3]
    # [1,1]
    # [2,3]
    #
    # [2,4]--> [2,1,4]-->[2,3,4]
    # [3,4]--> [1,3,4]-->[2,3,4]
    A = np.random.randint(0,10,[2,4])
    B = np.random.randint(0,10,[3,4])
    np.expand_dims(A,1)+np.expand_dims(B,0)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 点积运算
    A = np.random.randn(3,5)
    B = np.random.randn(5,3)
    np.dot(A,B)
        a.dot(B)
        B.dot(A)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.4 数组变形

    1. 更改数组的形状
    np.reshape()       # 不修改arr本身
    np.resize()        # 修改arr本身
    arr.T              # 转置
    arr.ravel()        # 不产生原数组的副本
    arr.faltten()      # 返回原数组的副本
    arr.squeeze()      # 降维
    arr.transpose()    # 调整坐标轴位置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    a = np.random.randint(0,10,[2,3])
    a.reshape(3,2) # 不改变变量本身
    a
    a.resize([3,2])   # 改变变量本身
    a
    a.T
    a.ravel() # 不改变变量本身
    a.ravel('F')
    a
    a.flatten() # 不改变变量本身
    a
    a.squeeze()
    a = np.random.randint(0,10,[1,2,3,4,1])
    a.squeeze().shape
    a.transpose()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 合并数组
    np.append()       # 占内存
    np.concatenate()  # 没有内存问题
    np.stack()        # 增加新的轴
    np.hstack()       # 按照行堆砌
    np.vstack()       # 按照列堆砌
    np.dstack()       # 按照第d维堆砌
    np.vsplit()       # 按照列分割
    # append和concatenate,按照行(列)合并时,必须要求数组有相同的列(行),即其他坐标轴的数值相等。
    # stack、hstack、dstack,要求待合并的数组必须具有相同的形状(shape)。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    a0 = np.random.randint(0,10,[4,2,3,4])
    a1 = np.random.randint(0,10,[3,2,3,4])
    a01 = np.append((a0,a1),0)
    a01 = np.append(a0,a1,0)
    a01.shape
    
    a01 = np.concatenate((a0,a1),0)
    a01.shape
    
    
    a0 = np.random.randint(0,10,[4,2,3,4])
    a1 = np.random.randint(0,10,[4,2,3,4])
    a = np.stack(a0,a1)
    a = np.stack((a0,a1))
    a.shape
    np.hstack((a0,a1)).shape
    np.vstack((a0,a1)).shape
    np.dstack((a0,a1)).shape
    np.stack((a0,a1),-1).shape
    np.vsplit(a,0)
    np.vsplit(a,2)[0].shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.5 批量处理

    samples = np.random.randn(1000,3,2)
    np.random.shuffle(samples)
    bs = 100
    for i in range(0,1000,bs):
        s_sum = np.sum(samples[i:i+bs])
        print(f'epoch:{i},sum of data:{s_sum}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.6 通用函数

    s= np.random.randint(0,10,[3,4,5])
    np.sqrt(s)
    np.sin(s)
    np.log10(s)
    np.exp(s)
    np.sum(s,0).shape
    np.mean(s,2).shape
    np.median(s)
    np.median(s,0)
    np.median(s,1)
    np.median(s,2)
    np.std(s)
    np.std(s,0)
    np.std(s,1)
    np.std(s,2)
    np.var(s)
    np.var(s,0)
    np.var(s,1)
    np.var(s,2)
    np.corrcoef(s[0])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.7 广播机制

    两个数组能否做数学运算,把数组的shape右对齐排列,如果对应位置上的数值为1、相同、不存在就可以运算,反之需要增加维度才可以运算。

    [2,3]
    [  3]
    [2,3]
    
    [3,1,5]
    [1,4,5]
    [3,4,5]
    
    [2,3]
    [2,3]
    [2,3]
    
    [2,3]-->[1,2,1,3]
    [3,2]-->[3,1,2,1]
    [3,2,2,3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    
    (np.random.randn(2,3)+np.random.randn(3)).shape
    
    (np.random.randn(3,1,5)+np.random.randn(1,4,5)).shape
    
    (np.random.randn(2,3)+np.random.randn(2,3)).shape
    
    (np.expand_dims(np.random.randn(2,3),[0,2])+np.expand_dims(np.random.randn(3,2),[1,3])).shape
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2 PyTorch

    本章要点:

    • Numpy与Tensor。
    • Tensor与Autograd。
    • 使用Numpy实现机器学习。
    • 使用Tensor及Antograd实现机器学习。
    • 使用TensorFlow架构。

    2.1 Numpy与Tensor

    1 Tensor

    import torch
    import numpy as np
    
    a = torch.tensor([1,2])
    b = torch.tensor([1,2])*2
    torch.add(a,b)
    
    a.add_(b)   # 改变a的值,修改自身数据
    a.add(b)    # 不改变a的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2 创建Tensor

    a = torch.tensor([[1,2]])
    torch.eye(3)
    torch.eye(3,4)
    torch.linspace(0,100,10)
    torch.logspace(1,3,10)
    torch.rand(3,3)
    torch.ones(3,4)
    torch.zeros(5,6)
    torch.ones_like(a)
    torch.zeros_like(a)
    torch.arange(0,10,0.9)
    b=np.array([1,2,3])
    torch.from_numpy(b)
    
    t=torch.Tensor([[1,2,3],[4,5,6]])
    t.size()
    t.shape
    torch.Tensor(t.size())
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    
    1)torch.Tensor是torch.empty和torch.tensor之间的一种混合,但是,当传入数据时,
    torch.Tensor使用全局默认dtype(FloatTensor),而torch.tensor是从数据中推断数据类型。 
    
    2)torch.tensor(1)返回一个固定值1,而torch.Tensor(1)返回一个大小为1的张量,它是
    随机初始化的值。
    
    torch.tensor(1)
    torch.Tensor(1)
    torch.tensor(1).type()
    torch.Tensor(1).type()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3 修改Tensor形状

    a = torch.randn(3,4)
    a.size()
    a.numel()
    a.view(2,6)
    a.reshape(2,6)
    a.resize(2,6)
    a.view(-1)
    [i.item() for i in a.flatten()]
    [i.item() for i in a.view(-1)]
    a.unsqueeze([0,-1]).shape
    a.squeeze().shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4 索引操作

    a = torch.randn(10,5,4)
    torch.index_select(a,0,torch.tensor([0,2]))
    
    b = torch.zeros(10)
    b[5:]+=torch.rand(5)
    b.nonzero()
    
    mask = b>0.5
    torch.masked_select(b,mask)
    torch.gather(b,0,torch.tensor([2,3]))
    torch.gather(b,0,torch.tensor([2,3,7,9]))
    torch.scatter(b,0,torch.tensor([2,3,7,9]),100)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5 广播机制

    a = torch.tensor([1,2,3])
    a.expand(4,3)
    
    
    • 1
    • 2
    • 3

    6 逐元素操作

    
    x1 = -torch.randint(0,10,[2,3])
    x2 = torch.randint(0,10,[2,3])
    torch.abs(x1)
    torch.add(x1,x2)
    x1.add_(x2)
    
    t = torch.randn(1, 3)
    t1 = torch.randn(3, 1)
    t2 = torch.randn(1, 3)
    torch.addcdiv(t, t1, t2, value=0.1)
    torch.addcmul(t, t1, t2, value=0.1)
    
    t.ceil()
    t.floor()
    t.clamp(-0.03,0.02)
    t.exp()
    t.log()
    t.pow(2)
    t.mul(3)
    t.neg()
    t.sigmoid()
    t.tanh()
    t.softmax(dim=0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    7 归并操作

    a = torch.randint(0,10,[2,3])
    b = torch.randint(0,10,[2,3])
    a.cumprod(1)
    a.cumsum(1)
    torch.dist(a*1.,b*1.,p=2)
    (a*1.).mean(0)
    torch.norm(a*1.,p=2)
    a.prod(1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8 比较操作

    a = torch.randn(4,5)
    b = a*1.0
    a.eq(b)
    a.equal(b*2)
    a.max(1)
    a.min(0)
    a.ge(b)
    a.le(b*0.2)
    a.gt(b*2)
    a.lt(b*0.2)
    torch.topk(a,1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    9 矩阵操作

    1)Torch的dot与Numpy的dot有点不同,Torch中的dot是对两个为1D张量进行点积运 算,Numpy中的dot无此限制。

    2)mm是对2D的矩阵进行点积,bmm对含batch的3D进行点积运算。

    3)转置运算会导致存储空间不连续,需要调用contiguous方法转为连续。

    a = torch.rand(3)
    b = torch.rand(3)
    torch.dot(a,b)
    
    a = torch.rand(2,3)
    b = torch.rand(2,3)
    torch.mm(a,b.T)
    b.t()
    b.svd()
    
    a = torch.rand(3)
    b = torch.rand(2,3)
    torch.mv(b,a)
    
    a = torch.rand(2,3,4)
    b = torch.rand(2,4,3)
    torch.bmm(b,a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.2 Tensor与Autograd

    1 自动求导要点

    为实现对Tensor自动求导,需考虑如下事项:
    1)创建叶子节点(Leaf Node)的Tensor,使用requires_grad参数指定是否记录对其 的操作,以便之后利用backward()方法进行梯度求解。requires_grad参数的缺省值为 False,如果要对其求导需设置为True,然后与之有依赖关系的节点会自动变为True。

    2)可利用requires_grad_()方法修改Tensor的requires_grad属性。可以调用.detach()或 with torch.no_grad():,将不再计算张量的梯度,跟踪张量的历史记录。这点在评估模 型、测试模型阶段中常常用到。

    3)通过运算创建的Tensor(即非叶子节点),会自动被赋予grad_fn属性。该属性表 示梯度函数。叶子节点的grad_fn为None。

    4)最后得到的Tensor执行backward()函数,此时自动计算各变量的梯度,并将累加结 果保存到grad属性中。计算完成后,非叶子节点的梯度自动释放。

    5)backward()函数接收参数,该参数应和调用backward()函数的Tensor的维度相同, 或者是可broadcast的维度。如果求导的Tensor为标量(即一个数字),则backward中的参 数可省略。

    6)反向传播的中间缓存会被清空,如果需要进行多次反向传播,需要指定backward 中的参数retain_graph=True。多次反向传播时,梯度是累加的。

    7)非叶子节点的梯度backward调用后即被清空。

    8)可以通过用torch.no_grad()包裹代码块的形式来阻止autograd去跟踪那些标记
    为.requesgrad=True的张量的历史记录。这步在测试阶段经常使用。

    2.3使用Numpy实现参数更新

    用Numpy求解梯度

    y = w*x^2+b
    1. 生成样本x,y.
    2. 计算预测值、损失,参数梯度。
    3. 初始化参数,迭代循环更新参数,直到循环结束或者参数收敛。
    
    • 1
    • 2
    • 3
    • 4
    import torch
    import numpy as np
    
    # 1 数据集
    N = 1000
    w = 1.3
    b = 1
    lr = 1e-3
    x = torch.linspace(-1,1,N) # [1000]
    y = w*torch.pow(x,2)+b        # [1000]
    
    epoch = 60
    w = torch.rand(1)
    b = torch.rand(1)
    for i in range(epoch):
        y_ = w*torch.pow(x,2)+b
        loss = 1/2*torch.sum((y_-y)**2)
        w -= lr*torch.sum((y_-y)*torch.pow(x,2))
        b -= lr*torch.sum(y_-y)
        if i%10 == 0:
            print(f'i:{i},w:{w},b:{b}')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.4使用Tensor及Antograd实现参数更新

    N = 1000
    w =1.3 #torch.tensor([[1.3]])
    b = 1
    lr = 1e-3
    x = torch.linspace(-1,1,N) # [1000]
    y = w*torch.pow(x,2)+b+torch.rand(1000)*0.01        # [1000]
    
    epoch = 120
    w = torch.randn(1,dtype=torch.float32,requires_grad=True)
    b = torch.randn(1,dtype=torch.float32,requires_grad=True)
    for i in range(epoch):
        y_ = w*torch.pow(x,2)+b
        loss = 1/2*torch.sum((y_-y)**2)
        loss.backward()
        with torch.no_grad():
            w -= lr*w.grad
            b -= lr*b.grad
            w.grad.zero_()
            b.grad.zero_()
        if i%10 == 0:
            print(f'i:{i},w:{w},b:{b}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3 PyTorch神经网络工具箱

    • 介绍神经网络核心组件。
    • 如何构建一个神经网络。
    • 详细介绍如何构建一个神经网络。
    • 如何使用nn模块中Module及functional。
    • 如何选择优化器。
    • 动态修改学习率参数。

    3.1 实现神经网络实例

    1. 主要步骤
    1)数据集。 
    2)利用torchvision对数据进行预处理,调用torch.utils建立一个数据迭代器。 
    3)可视化源数据。
    4)利用nn工具箱构建神经网络模型。 5)实例化模型,并定义损失函数及优化器。
    6)训练模型。
    7)可视化结果。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    # 1.导人必要的模块
    import torch
    import numpy as np
    from torchvision.datasets import mnist
    import torchvision.transforms as transforms
    from torch.utils.data import DataLoader
    import matplotlib.pyplot as plt
    import torch.nn.functional as F
    import torch.optim as optim
    from torch import nn
    
    # 2.定义一些超参数
    train_batch_size = 8
    test_batch_size = 8
    learning_rate = 0.01
    num_epoches = 20
    lr = 0.01
    momentum = 0.5
    
    # 下载数据并对数据进行预处理
    transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5],[0.5])])
    train_dataset = mnist.MNIST('./data',train=True,transform=transform,download=True)
    test_dataset = mnist.MNIST('./data',train=True,transform=transform)
    train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
    
    # 3可视化源数据
    examples = enumerate(test_loader)
    batch_idx, (example_data, example_targets) = next(examples)
    fig = plt.figure()
    for i in range(6):
        plt.subplot(2,3,i+1)
        plt.tight_layout()
        plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
        plt.title("Ground Truth: {}".format(example_targets[i]))
        plt.xticks([])
        plt.yticks([])
    
    # 4 构建模型
    class Net(nn.Module):
        def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
            super(Net, self).__init__()
            self.layer1 = nn.Sequential(nn.Linear(in_dim, n_hidden_1),nn.BatchNorm1d(n_hidden_1))
            self.layer2 = nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2),nn.BatchNorm1d (n_hidden_2))
            self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
        def forward(self, x):
            x = F.relu(self.layer1(x))
            x = F.relu(self.layer2(x))
            x = self.layer3(x)
            return x
    # 4.2 实例化网络。
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") #实例化网络
    model = Net(28 * 28, 300, 100, 10)
    model.to(device)
    # 定义损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
    
    # 5 训练模型
    # 5.1 开始训练
    losses = []
    acces = []
    eval_losses = []
    eval_acces = []
    for epoch in range(num_epoches):
        train_loss = 0
        train_acc = 0
        model.train() #动态修改参数学习率
        if epoch%5==0:
            optimizer.param_groups[0]['lr'] *= 0.1
        for img, label in train_loader:
            img = img.to(device)
            label = label.to(device)
            img = img.view(img.size(0), -1)
            # 前向传播
            out = model(img)
            loss = criterion(out, label)
            # 反向传播
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # 记录误差
            train_loss += loss.item()
            # 计算分类的准确率
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            train_acc += acc
        losses.append(train_loss / len(train_loader))
        acces.append(train_acc / len(train_loader))
        # 在测试集上检验效果
        eval_loss = 0
        eval_acc = 0 # 将模型改为预测模式
        model.eval()
        for img, label in test_loader:
            img = img.to(device)
            label = label.to(device)
            img = img.view(img.size(0), -1)
            out = model(img)
            loss = criterion(out, label)
            # 记录误差
            eval_loss += loss.item()
            # 记录准确率
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            eval_acc += acc
        eval_losses.append(eval_loss / len(test_loader))
        eval_acces.append(eval_acc / len(test_loader))
        print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f},\
               Test Loss: {:.4f}, Test Acc: {:.4f}'
              .format(epoch, train_loss / len(train_loader), \
              train_acc / len(train_loader), eval_loss / len(test_loader),\
              eval_acc / len(test_loader)))
    # 5.2 可视化训练及测试损失值
    plt.title('trainloss')
    plt.plot(np.arange(len(losses)), losses)
    plt.legend(['Train Loss'], loc='upper right')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    调用model.train()会把所有的module 设置为训练模式。如果是测试或验证阶段,需要使模型处于验证阶段,即调用 model.eval(),调用model.eval()会把所有的training属性设置为False。

    1)nn.Xxx继承于nn.Module,nn.Xxx需要先实例化并传入参数,然后以函数调用的方 式调用实例化的对象并传入输入数据。它能够很好地与nn.Sequential结合使用,而 nn.functional.xxx无法与nn.Sequential结合使用。
    2)nn.Xxx不需要自己定义和管理weight、bias参数;而nn.functional.xxx需要自己定义 weight、bias参数,每次调用的时候都需要手动传入weight、bias等参数,不利于代码复 用。
    3)Dropout操作在训练和测试阶段是有区别的,使用nn.Xxx方式定义Dropout,在调 用model.eval()之后,自动实现状态的转换,而使用nn.functional.xxx却无此功能。
    总的来说,两种功能都是相同的,但PyTorch官方推荐:具有学习参数的(例如, conv2d,linear,batch_norm)采用nn.Xxx方式。没有学习参数的(例如,maxpool、loss func、 activation func)等根据个人选择使用nn.functional.xxx或者nn.Xxx方式。3.2小节中使用激 活层,我们采用F.relu来实现,即nn.functional.xxx方式。

    3.3 如何构建神经网络?

    3.3.1 构建网络层

    # 1)导入需要的模块
    
    import torch
    import torch.utils.data as Data
    import torch.nn.functional as F
    import matplotlib.pyplot as plt
    # 超参数
    LR = 0.01
    BATCH_SIZE = 32
    EPOCH = 12
    # 2)生成数据。
    # 生成训练数据
    # torch.unsqueeze() 的作用是将一维变二维,torch只能处理二维的数据
    x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)
    # 0.1 * torch.normal(x.size())增加噪点
    y = x.pow(2) + 0.1 * torch.normal(torch.zeros(*x.size()))
    torch_dataset = Data.TensorDataset(x,y)
    #得到一个代批量的生成器
    loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True)
    # 3)构建神经网络。
    
    class Net(torch.nn.Module): # 初始化
        def __init__(self):
            super(Net, self).__init__()
            self.hidden = torch.nn.Linear(1, 20)
            self.predict = torch.nn.Linear(20, 1)
    # 前向传递
    def forward(self, x):
        x = F.relu(self.hidden(x))
        x = self.predict(x)
        return x
    # 4)使用多种优化器。
    
    net_SGD = Net()
    net_Momentum = Net()
    net_RMSProp = Net()
    net_Adam = Net()
    nets = [net_SGD, net_Momentum, net_RMSProp, net_Adam]
    opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
    opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.9)
    opt_RMSProp = torch.optim.RMSprop(net_RMSProp.parameters(), lr=LR, alpha=0.9)
    opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
    optimizers = [opt_SGD, opt_Momentum, opt_RMSProp, opt_Adam]
    # 5)训练模型。
    
    loss_func = torch.nn.MSELoss()
    loss_his = [[], [], [], []] # 记录损失
    for epoch in range(EPOCH):
        for step, (batch_x, batch_y) in enumerate(loader):
            for net, opt,l_his in zip(nets, optimizers, loss_his):
                output = net(batch_x) # get output for every net
                loss = loss_func(output, batch_y) # compute loss for every net
                opt.zero_grad() # clear gradients for next train
                loss.backward() # backpropagation, compute gradients
                opt.step() # apply gradients
                l_his.append(loss.data.numpy()) # loss recoder
    labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
    # 6)可视化结果。
    
    for i, l_his in enumerate(loss_his):
        plt.plot(l_his, label=labels[i])
    plt.legend(loc='best')
    plt.xlabel('Steps')
    plt.ylabel('Loss')
    plt.ylim((0, 0.2))
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    4 PyTorch数据处理工具箱

    • 简单介绍PyTorch相关的数据处理工具箱。
    • utils.data简介。
    • torchvision简介。
    • tensorboardX简介及实例。

    4.1 数据处理工具箱概述

    1. torch.utils.data工具包包括以下4个类。

    1)Dataset:是一个抽象类,其他数据集需要继承这个类,并且覆写其中的两个方法
    (getitem_、len)。

    2)DataLoader:定义一个新的迭代器,实现批量(batch)读取,打乱数据
    (shuffle)并提供并行加速等功能。

    3)random_split:把数据集随机拆分为给定长度的非重叠的新数据集。

    4)*sampler:多种采样函数。

    1. torchvision包括4个类,各类的主要功能如下。

    1)datasets:提供常用的数据集加载,设计上都是继承自torch.utils.data.Dataset,主要
    包括MMIST、CIFAR10/100、ImageNet和COCO等。

    2)models:提供深度学习中各种经典的网络结构以及训练好的模型(如果选择
    pretrained=True),包括AlexNet、VGG系列、ResNet系列、Inception系列等。

    3)transforms:常用的数据预处理操作,主要包括对Tensor及PIL Image对象的操作。

    4)utils:含两个函数,一个是make_grid,它能将多张图片拼接在一个网格中;另一 个是save_img,它能将Tensor保存成图片。

    4.2 utils.data简介

    utils.data包括Dataset和DataLoader。torch.utils.data.Dataset为抽象类。自定义数据集需 要继承这个类,并实现两个函数,一个是__len__,另一个是__getitem__,前者提供数据 的大小(size),后者通过给定索引获取数据和标签。__getitem__一次只能获取一个数 据,所以需要通过torch.utils.data.DataLoader来定义一个新的迭代器,实现batch读取。首 先我们来定义一个简单的数据集,然后通过具体使用Dataset及DataLoader,给读者一个直 观的认识。

    import torch
    from torch.utils import data 
    import numpy as np
    
    # 2)定义获取数据集的类
    class TestDataset(data.Dataset):#继承Dataset 
        def __init__(self):
            self.Data=np.asarray([[1,2],[3,4],[2,1],[3,4],[4,5]])#一些由2维向量表示的数据集 self.Label=np.asarray([0,1,0,1,2])#这是数据集对应的标签
        def __getitem__(self, index): #把numpy转换为Tensor
            txt=torch.from_numpy(self.Data[index]) 
            label=torch.tensor(self.Label[index]) 
            return txt,label
        def __len__(self):
            return len(self.Data)
    # 3)获取数据集中数据
    Test=TestDataset()
    print(Test[2]) #相当于调用__getitem__(2) print(Test.__len__())
    #输出:
    #(tensor([2, 1]), tensor(0)) #5
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上数据以tuple返回,每次只返回一个样本。实际上,Dateset只负责数据的抽取,调 用一次__getitem__只返回一个样本。如果希望批量处理(batch),还要同时进行shuffle和 并行加速等操作,可选择DataLoader。DataLoader的格式为:

    data.DataLoader( dataset,
                    batch_size=1,
                    shuffle=False,
                    sampler=None,
                    batch_sampler=None,
                    num_workers=0,
                    collate_fn=<function default_collate at 0x7f108ee01620>,
                    pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None,
                    )
    主要参数说明:
    ·dataset:加载的数据集。
    ·batch_size:批大小。
    ·shuffle:是否将数据打乱。
    ·sampler:样本抽样。
    ·num_workers:使用多进程加载的进程数,0代表不使用多进程。
    ·collate_fn:如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可。
    ·pin_memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快 一些。
    ·drop_last:dataset中的数据个数可能不是batch_size的整数倍,drop_last为True会将多 出来不足一个batch的数据丢弃。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    test_loader = data.DataLoader(Test,batch_size=2,shuffle=False,num_workers=2) 
    for i,traindata in enumerate(test_loader):
       print('i:',i)
       Data,Label=traindata
       print('data:',Data)
       print('Label:',Label)
    dataiter=iter(test_loader)
    imgs,labels=next(dataiter)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    一般用data.Dataset处理同一个目录下的数据。如果数据在不同目录下,因为不同的目

    录代表不同类别(这种情况比较普遍),使用data.Dataset来处理就很不方便。不过,使用 PyTorch另一种可视化数据处理工具(即torchvision)就非常方便,不但可以自动获取标 签,还提供很多数据预处理、数据增强等转换函数。

    4.3 torchvision简介

    1 transforms

    transforms提供了对PIL Image对象和Tensor对象的常用操作。
    1)对PIL Image的常见操作如下。
    ·Scale/Resize:调整尺寸,长宽比保持不变。
    ·CenterCrop、RandomCrop、RandomSizedCrop:裁剪图片,CenterCrop和 RandomCrop在crop时是固定size,RandomResizedCrop则是random size的crop。
    ·Pad:填充。 
    ·ToTensor:把一个取值范围是[0,255]的PIL.Image转换成Tensor。形状为(H,W,C)的
    Numpy.ndarray转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloatTensor。 ·RandomHorizontalFlip:图像随机水平翻转,翻转概率为0.5。 
    ·RandomVerticalFlip:图像随机垂直翻转。 ·ColorJitter:修改亮度、对比度和饱和度。
    2)对Tensor的常见操作如下。
    ·Normalize:标准化,即,减均值,除以标准差。
    ·ToPILImage:将Tensor转为PIL Image。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果要对数据集进行多个操作,可通过Compose将这些操作像管道一样拼接起来,类 似于nn.Sequential。以下为示例代码:

    
    transforms.Compose([
    #将给定的 PIL.Image 进行中心切割,得到给定的 size,
    #size 可以是 tuple,(target_height, target_width)。
    #size 也可以是一个 Integer,在这种情况下,切出来的图片形状是正方形。 
    transforms.CenterCrop(10),
    #切割中心点的位置随机选取
    transforms.RandomCrop(20, padding=0),
    #把一个取值范围是 [0, 255] 的 PIL.Image 或者 shape 为 (H, W, C) 的 numpy.ndarray, #转换为形状为 (C, H, W),取值范围是 [0, 1] 的 torch.FloatTensor 
    transforms.ToTensor(),
    #规范化到[-1,1]
    transforms.Normalize(mean = (0.5, 0.5, 0.5), std = (0.5, 0.5, 0.5))
    ])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    还可以自己定义一个Python Lambda表达式,如将每个像素值加10,可表示为: transforms.Lambda(lambda x:x.add(10))。

    官网:https://PyTorch.org/docs/stable/torchvision/transforms.html。
    2 ImageFolder
    当文件依据标签处于不同文件下时。可以利用torchvision.datasets.ImageFolder来直接构造出dataset,代码如下:

    loader = datasets.ImageFolder(path)
    loader = data.DataLoader(dataset)
    
    • 1
    • 2

    ImageFolder会将目录中的文件夹名自动转化成序列,当DataLoader载入时,标签自动 就是整数序列了。
    下面我们利用ImageFolder读取不同目录下的图片数据,然后使用transforms进行图像 预处理,预处理有多个,我们用compose把这些操作拼接在一起。然后使用DataLoader加 载。
    对处理后的数据用torchvision.utils中的save_image保存为一个png格式文件,然后用 Image.open打开该png文件,详细代码如下:

    import torch
    from torchvision import transforms,utils,datasets
    import matplotlib.pyplot as plt
    
    my_trans = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor()
    ])
    train_data = datasets.ImageFolder('./data/torchvision_data',transforms=my_trans)
    train_loader = utils.data.DataLoader(train_data,batch_size = 8,shuffle=True)
    for i_batch, img in enumerate(train_loader):
        if i_batch == 0:
            print(img[1])
            fig = plt.figure()
            grid = utils.make_grid(img[0])  # 将多张图片拼接在一个网格中
            plt.imshow(grid.numpy().transpose((1, 2, 0)))
            plt.show()
            utils.save_image(grid,'test01.png')
            break
    
    from PIL import Image
    Image.open('test01.png')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.4 可视化工具

    4.4.1 tensorboardX简介

    from tensorboardX import SummaryWriter
    from tensorboardx import SummaryWriter
    writer = SummaryWriter(log_dir = 'logs') 
    SummaryWriter(log_dir=None, comment='', **kwargs)
    #其中comment在文件命名加上comment后缀
    # 如果不写log_dir,系统将在当前目录创建一个runs的目录。
    writer.add_xxX()  
    2)调用相应的API接口,接口一般格式为:
    # add_xxx(tag-name, object, iteration-number) #即add_xxx(标签,记录的对象,迭代次数)
    writer.close()
    
    ## 3)启动tensorboard服务:cd到logs目录所在的同级目录,在命令行输入如下命令,
    # logdir等式右边可以是相对路 径或绝对路径。
    
    # tensorboard --logdir=logs --port 6006 #如果是Windows环境,要注意路径解析,如
    #tensorboard --logdir=r'D:\myboard\test\logs' --port 6006
    
    # 4)web展示。在浏览器输入: http://服务器IP或名称:6006 #如果是本机,服务器名称可以使用localhost
    https://github.com/lanpa/tensorboardX。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2 用tensorboardX可视化神经网络

    '''用tensorboardX可视化神经网络'''
    # (1)导入需要的模块
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    import torchvision
    from tensboardX import SummaryWriter
    
    # (2)构建神经网络
    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
            self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
            self.conv2_drop = nn.Dropout2d()
            self.fc1 = nn.Linear(320, 50)
            self.fc2 = nn.Linear(50, 10)
            self.bn = nn.BatchNorm2d(20)
        def forward(self, x):
            x = F.max_pool2d(self.conv1(x), 2)
            x = F.relu(x) + F.relu(-x)
            x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
            x = self.bn(x)
            x = x.view(-1, 320)
            x = F.relu(self.fc1(x))
            x = F.dropout(x, training=self.training)
            x = self.fc2(x)
            x = F.softmax(x, dim=1)
            return x
    
    # (3)把模型保存为graph
    #定义输入
    input = torch.rand(32, 1, 28, 28)
    #实例化神经网络
    model = Net()
    out = model(input)
    print(out.shape)
    #将model保存为graph
    with SummaryWriter(log_dir='logs',comment='Net') as w:
         w.add_graph(model, (input, ))
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    
    
    • 1
    
    
    • 1

    3 用tensorboardX可视化损失值
    可视化损失值,需要使用add_scalar函数,这里利用一层全连接神经网络,训练一元
    二次函数的参数。

    import torch
    import numpy as np
    import torch.nn as nn
    from tensboardX import SummaryWriter
    
    input_size=1
    output_size=1
    learning_rate = 0.0001
    num_epoches = 1000
    dtype = torch.FloatTensor
    writer = SummaryWriter(log_dir='logs',comment='Linear')
    np.random.seed(100)
    x_train = np.linspace(-1, 1, 100).reshape(100,1)
    y_train = 3*np.power(x_train, 2) +2+ 0.2*np.random.rand(x_train.size).reshape(100,1)
    model = nn.Linear(input_size, output_size)
    criterion = nn.MSELoss()
    optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
    for epoch in range(num_epoches):
        inputs = torch.from_numpy(x_train).type(dtype)
        targets = torch.from_numpy(y_train).type(dtype)
        output = model(inputs)
        loss = criterion(output, targets)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 保存loss的数据与epoch数值 
        writer.add_scalar('训练损失值', loss, epoch)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4 用tensorboardX可视化特征图

    import torchvision.utils as vutils
    from tensboardX import SummaryWriter
    
    writer = SummaryWriter(log_dir='logs',comment='feature map')
    img_grid = vutils.make_grid(x, normalize=True, scale_each=True, nrow=2)
    net.eval()
    for name, layer in net._modules.items():
        # 为fc层预处理x
        x = x.view(x.size(0), -1) if "fc" in name else x
        print(x.size())
        x = layer(x)
        print(f'{name}')
    # 查看卷积层的特征图
        if 'layer' in name or 'conv' in name:
            x1 = x.transpose(0, 1) # C,B, H, W ---> B,C, H, W
            img_grid = vutils.make_grid(x1, normalize=True, scale_each=True, nrow=4) # normalize进行归一化处理 
            writer.add_image(f'{name}_feature_maps', img_grid, global_step=0)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5 机器学习

    1. 选择模型及损失函数
    二分类、单标签   有sigmoid  nn.BCELoss
    二分类、单标签   无sigmoid  nn.BCEWithLogistsLoss
    
    二分类、多标签   无激活函数  nn.SoftMarginLoss(target 1/-1)
    
    多分类、单标签   无softmax  nn.CrossEntropyLoss(target 为one_hot)
    多分类、单标签   有softmax  nn.NLLLoss
    
    多分类、多标签   无激活函数  nn.MultiLabelSoftMarginLoss
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 评估及优化模型
    ·留出法(Holdout):留出法的步骤相对简单,直接将数据集划分为两个互斥的集 合,其中一个集合作为训练集,另一个作为测试集。在训练集上训练出模型后,用测试集 来评估测试误差,作为泛化误差的估计。使用留出法,还可以优化出一种更好的方法,就 是把数据分成3部分:训练数据集、验证数据集、测试数据集。训练数据集用来训练模 型,验证数据集用来调优超参数,测试集则用来测试模型的泛化能力。数据量较大时可采 用这种方法。
    
    ·K折交叉验证:不重复地随机将训练数据集划分为k个,其中k-1个用于模型训练,剩 余的一个用于测试。
    
    ·重复的K折交叉验证:当数据量比较小,数据分布不很均匀时可以采用这种方法。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6 视觉处理基础

    • ·卷积神经网络简介。
    • ·卷积定义。
    • ·卷积运算。
    • ·卷积层。
    • ·池化层。
    • ·现代经典网络架构。
    • ·实例:用TensorFlow实现一个卷积神经网络。
    1. LeNet-5模型

    LeCun提出的LeNet,系统地提出了卷积层、池化层、全连 接层等概念.

    LeNet-5模型结构为输入层-卷积层-池化层-卷积层-池化层-全连接层-全连接层-输出。
    
    (2)模型特点 
    
    ·每个卷积层包含3个部分:卷积、池化和非线性激活函数。 
    
    ·使用卷积提取空间特征。 
    
    ·采用降采样(Subsample)的平均池化层(Average Pooling)。
    
    ·使用双曲正切(Tanh)的激活函数。 ·最后用MLP作为分类器。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. AlexNet模型

    AlexNet,提出一些训练深度网络的重要方 法或技巧,如Dropout、ReLu、GPU、数据增强方法等

    AlexNet为8层深度网络,其中5层卷积层和3层全连接层,不计LRN层和池化层。
    
    ·由5层卷积和3层全连接组成,输入图像为3通道224×224大小,网络规模远大于
    LeNet。
    
    ·使用ReLU激活函数。
    
    ·使用Dropout,可以作为正则项防止过拟合,提升模型鲁棒性。 
    
    ·具备一些很好的训练技巧,包括数据增广、学习率策略、Weight Decay等。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. VGG模型
    VGG可以看成是加深版本的AlexNet.都是 Conv Layer+FC layer,在当时看来这是一个非常深的网络了,层数高达1619层。
    
    • 1
    1. GoogleNet模型
    GG是增加网络的深度,但深度达到一个程度时,可能就成为瓶颈。GoogLeNet则从 另一个维度来增加网络能力,每单元有许多层并行计算,让网络更宽了。
    
    模型特点
    1)引入Inception结构,这是一种网中网(Network In Network)的结构。 通过网络的水平排布,可以用较浅的网络得到较好的模型能力,并进行多特征融合,
    同时更容易训练。另外,为了减少计算量,使用了1×1卷积来先对特征通道进行降维。
    
    2)采用全局平均池化层。
    将后面的全连接层全部替换为简单的全局平均池化,在最后参数会变得更少。GoogleNet移除全连接层,但并不会影响到结果的精度,在ImageNet中实现93.3%的精 度,而且要比VGG还快。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. ResNet模型
    模型特点:
    
    ·层数非常深,已经超过百层。
      
    ·引入残差单元来解决退化问题。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 显示参数:
    
    import collections
    import torch
    import torch.nn as nn
    def paras_summary(input_size, model):
        def register_hook(module):
            def hook(module, input, output):
                class_name = str(module.__class__).split('.')[-1].split("'")[0]
                module_idx = len(summary)
                m_key = '%s-%i' % (class_name, module_idx+1)
                summary[m_key] = collections.OrderedDict()
                summary[m_key]['input_shape'] = list(input[0].size())
                summary[m_key]['input_shape'][0] = -1
                summary[m_key]['output_shape'] = list(output.size())
                summary[m_key]['output_shape'][0] = -1
                params = 0
                if hasattr(module, 'weight'):
                    params += torch.prod(torch.LongTensor(list(module.weight.size())))
                    if module.weight.requires_grad:
                        summary[m_key]['trainable'] = True
                    else:
                        summary[m_key]['trainable'] = False
                if hasattr(module, 'bias'):
                    params += torch.prod(torch.LongTensor(list(module.bias.size())))
                summary[m_key]['nb_params'] = params
            if not isinstance(module, nn.Sequential) and \
                    not isinstance(module, nn.ModuleList) and \
                    not (module == model):
                hooks.append(module.register_forward_hook(hook))
        # check if there are multiple inputs to the network
        if isinstance(input_size[0], (list, tuple)):
            x = [torch.rand(1,*in_size) for in_size in input_size]
        else:
            x = torch.rand(1,*input_size)
        # create properties
        summary = collections.OrderedDict()
        hooks = []
        # register hook
        model.apply(register_hook)
        # make a forward pass
        model(x)
        # remove these hooks
        for h in hooks:
            h.remove()
        return summary
    
    
    import torch.nn as nn
    import torch.nn.functional as F
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(3, 16, 5)
            self.pool1 = nn.MaxPool2d(2, 2)
            self.conv2 = nn.Conv2d(16, 36, 5)
            self.fc1 = nn.Linear(16 * 5 * 5, 120)
            self.pool2 = nn.MaxPool2d(2, 2) #使用全局平均池化层
            self.aap=nn.AdaptiveAvgPool2d(1)
            self.fc3 = nn.Linear(36, 10)
        def forward(self, x):
            x = self.pool1(F.relu(self.conv1(x)))
            x = self.pool2(F.relu(self.conv2(x)))
            x = self.aap(x)
            x = x.view(x.shape[0], -1)
            x = self.fc3(x)
            return x
    net = Net()
    net=net.to(device)
    
    if __name__=='__main__':
        net = Net() #输入格式为[c,h,w]即通道数,图像的高级宽度
        input_size=[3,32,32]
        pa = paras_summary(input_size,net)
        for i ,j in zip(pa.keys(),pa.values()):
            print(f'k: {i},v:{j}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    1. 集成效果
    
    mlps=[net1.to(device),net2.to(device),net3.to(device)] 
    optimizer=torch.optim.Adam([{"params":mlp.parameters()} for mlp in mlps],lr=LR)
    loss_function=nn.CrossEntropyLoss()
    
    for ep in range(EPOCHES):
        for img,label in trainloader:
            img,label=img.to(device),label.to(device) optimizer.zero_grad()#10个网络清除梯度
            for mlp in mlps:
                mlp.train()
                out=mlp(img) loss=loss_function(out,label) loss.backward()#网络获得的梯度
            optimizer.step()
        pre=[]
        vote_correct=0
        mlps_correct=[0 for i in range(len(mlps))]
        for img,label in testloader:
            img,label=img.to(device),label.to(device) 
            for i, mlp in enumerate( mlps):
                mlp.eval() 
                out=mlp(img)
                _,prediction=torch.max(out,1) #按行取最大值 pre_num=prediction.cpu().numpy() mlps_correct[i]+=(pre_num==label.cpu().numpy()).sum()
                
                pre.append(pre_num)
            arr=np.array(pre)
            pre.clear()
            result=[Counter(arr[:,i]).most_common(1)[0][0] for i in range(BATCHSIZE)] 
            vote_correct+=(result == label.cpu().numpy()).sum()
        print("epoch:" + str(ep)+"集成模型的正确率"+str(vote_correct/len(testloader)))
        for idx, coreect in enumerate( mlps_correct): 
            print("模型"+str(idx)+"的正确率为:"+str(coreect/len(testloader)))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    6 相关的数学知识

    1. 标量Scalar:单独的一个数,维数为0。
    2. 向量Vector:一列按顺序排列的元素,维数为1。
    3. 矩阵Matrix:二维数组结构,用大写字母来表示。
    4. 张量Tensor: 数组的维度超过了二维的高维数组。
    import numpy as np
    scalar = np.array(3)
    vector = np.array([1,2,3])
    matrix = np.arange(9).reshape(3,3)
    tensor = np.arange(24).reshape(2,3,4)
    print(' shape ',' ndim ')
    print(scalar.shape,'       ',scalar.ndim)
    print(vector.shape,'     ',vector.ndim)
    print(matrix.shape,'   ',matrix.ndim)
    print(tensor.shape,'',tensor.ndim)
    '''
    shape    ndim 
    ()         0
    (3,)       1
    (3, 3)     2
    (2, 3, 4)  3
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 矩阵的转置
    a = np.random.randint(0,10,[3,4])
    c = np.zeros_like(a.T)
    print('a:\n',a)
    print('c: \n',c)
    for i in range(c.shape[0]):
        for j in range(c.shape[1]):
            c[i,j]= a[j,i]
    print('a.T:  \n',c)
    '''
    a:
     [[4 1 0 5]
     [2 8 5 6]
     [9 9 6 5]]
    c: 
     [[0 0 0]
     [0 0 0]
     [0 0 0]
     [0 0 0]]
    a.T:  
     [[4 2 9]
     [1 8 9]
     [0 5 6]
     [5 6 5]]
    
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    1. 矩阵加减乘
    import numpy as np
    a = np.random.randint(0,10,[3,4])
    b = np.random.randint(0,10,[5,3])
    def add(a,b):
        assert a.shape==b.shape
        h,w = a.shape
        c = np.zeros([h,w])
        for i in range(h):
            for j in range(w):
                c[i,j] = a[i,j]+b[i,j]
        return c
    
    def sub(a,b):
        assert a.shape==b.shape
        h,w = a.shape
        c = np.zeros([h,w])
        for i in range(h):
            for j in range(w):
                c[i,j] = a[i,j]-b[i,j]
        return c
    
    def mal(a,b):
        assert a.shape==b.shape
        h,w = a.shape
        c = np.zeros([h,w])
        for i in range(h):
            for j in range(w):
                c[i,j] = a[i,j]*b[i,j]
        return c
    
    def matrix(a,b):
        assert a.shape[1]==b.shape[0]
        h,w = a.shape[0],b.shape[1]
        c = np.zeros([h,w])
        for i in range(h):
            for j in range(w):
                for k in range(a.shape[1]):
                    c[i,j] += a[i,k]*b[k,j]
        return c
    
    if __name__ =='__main__':
        a = np.random.randint(0,10,[3,4])
        b = np.random.randint(0,10,a.shape)
        print('a:\n',a)
        print('b:\n',b)
        print('a+b:\n',add(a,b))
        print('a-b:\n',sub(a,b))
        print('a·b:\n',mal(a,b))
        print('a*b:\n',matrix(a,b.T))
    '''
    a:
     [[1 0 1 8]
     [3 6 1 8]
     [4 9 0 8]]
    b:
     [[3 3 9 7]
     [5 1 6 6]
     [8 6 3 9]]
    a+b:
     [[ 4.  3. 10. 15.]
     [ 8.  7.  7. 14.]
     [12. 15.  3. 17.]]
    a-b:
     [[-2. -3. -8.  1.]
     [-2.  5. -5.  2.]
     [-4.  3. -3. -1.]]
    a·b:
     [[ 3.  0.  9. 56.]
     [15.  6.  6. 48.]
     [32. 54.  0. 72.]]
    a*b:
     [[ 68.  59.  83.]
     [ 92.  75. 135.]
     [ 95.  77. 158.]]
     
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    1. 激活函数
    import numpy as np
    def sigmoid(x):
        return 1./(1.+np.exp(-x))
    
    def tanh(x):
        return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
    
    def ReLU(x):
        return np.maximum(0,x)
    
    def LeakReLU(x,a):
        return np.where(x>=0,np.maximum(0,x),a*x)
    
    if __name__ =='__main__':
        np.random.seed(1024)
        x = np.random.randn(6)
        print(sigmoid(x))
        print(tanh(x))
        print(ReLU(x))
        print(LeakReLU(x,a=0.1))
    '''
    [0.89325684 0.5628277  0.81064072 0.63858774 0.61259269 0.30803255]
    [ 0.97184215  0.24740444  0.89651607  0.51480076  0.42863533 -0.66922239]
    [2.12444863 0.25264613 1.45417876 0.56923979 0.45822365 0.        ]
    [ 2.12444863  0.25264613  1.45417876  0.56923979  0.45822365 -0.08093334]
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    (1) 自动求导

    from torch import nn
    import torch
    import torch.nn.functional as F
    from torch.autograd import Variable
    
    if __name__=='__main__':
        batch_n = 100
        input_data = 1000
        hidden_layer = 100
        output_data = 10
        epoch_n = 10001
        learning_rate = 1e-6
    
        x = Variable(torch.randn(batch_n,input_data),requires_grad=False)
        y = Variable(torch.randn(hidden_layer,output_data),requires_grad=False)
        w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad=True)
        w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad=True)
    
        for epoch in range(epoch_n):
            y_pred = x.mm(w1).clamp(min=0).mm(w2)
            loss = (y_pred-y).pow(2).sum()
            if epoch%500==0:
                print('Epoch:{}, Loss:{:.4f}'.format(epoch,loss.data))
    
            loss.backward()
            w1.data -= learning_rate*w1.grad.data
            w2.data -= learning_rate*w2.grad.data
    
            w1.grad.data.zero_()
            w2.grad.data.zero_()
    '''
    Epoch:0, Loss:46297568.0000
    Epoch:500, Loss:2064.5229
    Epoch:1000, Loss:332.0856
    Epoch:1500, Loss:92.1883
    Epoch:2000, Loss:34.2231
    Epoch:2500, Loss:15.4424
    Epoch:3000, Loss:8.0198
    Epoch:3500, Loss:4.5941
    Epoch:4000, Loss:2.8243
    Epoch:4500, Loss:1.8257
    Epoch:5000, Loss:1.2236
    Epoch:5500, Loss:0.8433
    Epoch:6000, Loss:0.5939
    Epoch:6500, Loss:0.4257
    Epoch:7000, Loss:0.3103
    Epoch:7500, Loss:0.2297
    Epoch:8000, Loss:0.1725
    Epoch:8500, Loss:0.1313
    Epoch:9000, Loss:0.1013
    Epoch:9500, Loss:0.0791
    Epoch:10000, Loss:0.0625
    
    Process finished with exit code 0
    
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    (2) 自定义传播函数

    from torch import nn
    import torch
    import torch.nn.functional as F
    from torch.autograd import Variable
    
    class Model(torch.nn.Module):
        def __init__(self):
            super(Model, self).__init__()
    
        def forward(self, input, w1, w2):
            x = torch.mm(input ,w1)
            x = torch.clamp(x,min=0)
            x = torch.mm(x,w2)
            return x
    
    
    if __name__=='__main__':
        batch_n = 100
        input_data = 1000
        hidden_layer = 100
        output_data = 10
        epoch_n = 10001
        learning_rate = 1e-6
    
        x = Variable(torch.randn(batch_n,input_data),requires_grad=False)
        y = Variable(torch.randn(hidden_layer,output_data),requires_grad=False)
        w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad=True)
        w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad=True)
    
        for epoch in range(epoch_n):
            y_pred = Model()(x,w1,w2)
            loss = (y_pred-y).pow(2).sum()
            if epoch%500==0:
                print('Epoch:{}, Loss:{:.4f}'.format(epoch,loss.data))
    
            loss.backward()
            w1.data -= learning_rate*w1.grad.data
            w2.data -= learning_rate*w2.grad.data
    
            w1.grad.data.zero_()
            w2.grad.data.zero_()
    '''
    Epoch:0, Loss:45101876.0000
    Epoch:500, Loss:2105.3201
    Epoch:1000, Loss:351.6454
    Epoch:1500, Loss:115.2642
    Epoch:2000, Loss:53.0504
    Epoch:2500, Loss:30.2131
    Epoch:3000, Loss:19.8175
    Epoch:3500, Loss:14.4892
    Epoch:4000, Loss:11.3584
    Epoch:4500, Loss:9.3450
    Epoch:5000, Loss:7.9523
    Epoch:5500, Loss:6.9340
    Epoch:6000, Loss:6.1545
    Epoch:6500, Loss:5.5373
    Epoch:7000, Loss:5.0327
    Epoch:7500, Loss:4.6104
    Epoch:8000, Loss:4.2507
    Epoch:8500, Loss:3.9397
    Epoch:9000, Loss:3.6656
    Epoch:9500, Loss:3.4207
    Epoch:10000, Loss:3.1994
    
    Process finished with exit code 0
    
    
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    (3) optimizer

    from torch import nn
    import torch
    import torch.nn.functional as F
    from torch.autograd import Variable
    
    class Model(torch.nn.Module):
        def __init__(self,input_data,hidden_layer,output_data):
            super(Model, self).__init__()
            self.model = torch.nn.Sequential(nn.Linear(input_data,hidden_layer),
                                          nn.ReLU(),
                                          nn.Linear(hidden_layer,output_data))
        def forward(self, x):
            x = self.model(x)
            return x
    
    if __name__=='__main__':
        batch_n = 100
        input_data = 1000
        hidden_layer = 100
        output_data = 10
        epoch_n = 1001
        learning_rate = 1e-6
    
        x = Variable(torch.randn(batch_n,input_data),requires_grad=False)
        y = Variable(torch.randn(hidden_layer,output_data),requires_grad=False)
        model = Model(input_data,hidden_layer,output_data)
        loss_fn = torch.nn.MSELoss()
        optimizer = torch.optim.Adam(model.parameters(),lr= learning_rate)
    
    
        for epoch in range(epoch_n):
            y_pred = model(x)
            loss = loss_fn (y_pred,y)
            if epoch%500==0:
                print('Epoch:{}, Loss:{:.4f}'.format(epoch,loss.data))
    
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
    
    
    '''
    Epoch:0, Loss:1.1027
    Epoch:500, Loss:0.9990
    Epoch:1000, Loss:0.9066
    
    Process finished with exit code 0
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    (4) No optimizer

    from torch import nn
    import torch
    import torch.nn.functional as F
    from torch.autograd import Variable
    
    class Model(torch.nn.Module):
        def __init__(self,input_data,hidden_layer,output_data):
            super(Model, self).__init__()
            self.model = torch.nn.Sequential(nn.Linear(input_data,hidden_layer),
                                          nn.ReLU(),
                                          nn.Linear(hidden_layer,output_data))
        def forward(self, x):
            x = self.model(x)
            return x
    
    if __name__=='__main__':
        batch_n = 100
        input_data = 1000
        hidden_layer = 100
        output_data = 10
        epoch_n = 1001
        learning_rate = 1e-6
    
        x = Variable(torch.randn(batch_n,input_data),requires_grad=False)
        y = Variable(torch.randn(hidden_layer,output_data),requires_grad=False)
        model = Model(input_data,hidden_layer,output_data)
        loss_fn = torch.nn.MSELoss()
    
        for epoch in range(epoch_n):
            y_pred = model(x)
            loss = loss_fn (y_pred,y)
            if epoch%500==0:
                print('Epoch:{}, Loss:{:.4f}'.format(epoch,loss.data))
    
            model.zero_grad()
            loss.backward()
            for param in model.parameters():
                param.data -= learning_rate * param.grad.data
    '''
    Epoch:0, Loss:1.0688
    Epoch:500, Loss:1.0684
    Epoch:1000, Loss:1.0680
    
    Process finished with exit code 0
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
  • 相关阅读:
    cors基础,响应头设置
    学习记忆——数学篇——代数——记忆宫殿——卧室
    LAMP环境部署:安装Discuz
    【C++深入浅出】类和对象下篇
    初识设计模式 - 命令模式
    【Redis】.net core Redis事件订阅与发布,基础篇
    【数据结构与算法】克鲁斯卡尔算法的介绍和公交站问题程序实现
    Springboot+vue的人事管理系统(有报告),Javaee项目,springboot vue前后端分离项目。
    带圆角的虚线边框?CSS 不在话下
    洛谷1631 序列合并(优先队列)
  • 原文地址:https://blog.csdn.net/qq_35732321/article/details/126544892