【PyTorch学习笔记】开始MNIST手写识别数字实战

分类问题

图像表示

  • 每一张手写数字的图片在MNIST中图片大小是28×28的单通道图片

  • 我们可以把这个图片打平成一个 长 度 为 784 长度为784 784的数组,也就是忽略它的二维之间的相关性。

  • 然后在前面插入一个维度就变成了一个 [ 1 , 784 ] [1,784] [1,784]的一个矩阵。

单个线性模型无法解决这个问题,所以目前使用三个线性模型进行嵌套

X = [ v 1 , v 2 , . . . , v 784 ] X=[v_1,v_2,...,v_{784}] X=[v1,v2,...,v784]

  • x : [ 1 , 784 ] x:[1,784] x:[1,784]

H 1 = X W 1 + b 1 H_1=XW_1+b_1 H1=XW1+b1

  • 为了满足矩阵相乘,相乘过程: [ 1 , 784 ] × [ 784 , d 1 ] + [ d 1 ] [1,784]×[784,d_1]+[d_1] [1,784]×[784,d1]+[d1]–> [ 1 , d 1 ] + [ d 1 ] [1,d_1]+[d_1] [1,d1]+[d1] --> [ 1 , d 1 ] [1,d_1] [1,d1]
  • W 1 : [ d 1 , 784 ] W_1:[d_1,784] W1:[d1,784] W 1 T : [ 784 , d 1 ] W_1^T:[784,d_1] W1T:[784,d1] b 1 : [ d 1 ] b_1:[d_1] b1:[d1]
  • H 1 H_1 H1的维度就是: [ 1 , d 1 ] [1,d_1] [1,d1]

H 2 = H 1 W 2 + b 2 H_2=H_1W_2+b_2 H2=H1W2+b2

  • 相乘过程: [ 1 , d 1 ] × [ d 1 , d 2 ] + [ d 2 ] [1,d_1]×[d_1,d_2] + [d_2] [1,d1]×[d1,d2]+[d2]–> [ 1 , d 2 ] + [ d 2 ] [1,d_2]+[d_2] [1,d2]+[d2]–> [ 1 , d 2 ] [1,d_2] [1,d2]
  • W 2 : [ d 2 , d 1 ] W_2:[d_2,d_1] W2:[d2,d1] W 2 T : [ d 1 , d 2 ] W_2^T:[d_1,d_2] W2T:[d1,d2] b 2 : [ d 2 ] b_2:[d_2] b2:[d2]
  • H 2 H_2 H2的维度就是: [ 1 , d 2 ] [1,d_2] [1,d2]

H 3 = H 2 W 3 + b 3 H_3=H_2W_3+b_3 H3=H2W3+b3

  • 相乘过程: [ 1 , d 2 ] × [ d 2 , d 3 ] + [ d 3 ] [1,d_2]×[d_2,d_3]+[ d_3] [1,d2]×[d2,d3]+[d3]–> [ 1 , d 3 ] [1,d_3] [1,d3]
  • W 3 : [ d 3 , d 2 ] W_3:[d_3,d_2] W3:[d3,d2] W 3 T : [ d 2 , d 3 ] W_3^T:[d_2,d_3] W3T:[d2,d3] b 3 : [ d 3 ] b_3:[d_3] b3:[d3]
  • H 3 H_3 H3的维度是: [ 1 , d 3 ] [1,d_3] [1,d3]

也就是可以理解为通过不断地矩阵乘法,改变图像的维度,最后将需要的特征输出出来。

最后输出的 H 3 H_3 H3中第一个维度表示图片的数量,第二个维度表示 0 − 9 0-9 09对应的那个数字

loss求解

图像分类最后得到的结果是一个以独热编码的一个矩阵,然后通过对该矩阵进行解码就可以得到对应分类的类别了。

独热编码(One-Hot Encoding)

为样本特征的每个值建立一个由一个1和若干个0组成的序列,用该序列对所有的特征值进行编码。

两个数   三个数	四个数
1		3		2
7		5		4
1		8		6  
7		3		9
为每一个数字进行独热编码:
1-10    3-100	2-1000
7-01    5-010   4-0100
        8-001   6-0010
                9-0001
编码完毕后得到最终经过独热编码后的样本矩阵:
101001000
010100100
100010010
011000001

使用场景: 计算相似度

战狼2,    吴京,  吴京,       动作|战争|爱国
我不是药神,徐峥,  徐峥|王传君, 喜剧|剧情|社会
战狼,     吴京,  吴京,       动作|战争

战狼2,    01    001    111000
我不是药神,10    110    000111
战狼,     01    001    110000

比如:一个是1的一个图像,被展成一个 1 × 10 1×10 1×10的矩阵(这里的10是取决于有多少类别)

  • e.g: 1 =>[0,1,0,0,0,0,0,0,0,0]

  • e.g: 2 =>[0,0,0,3,0,0,0,0,0,0]

使用这样的编码就不会在数字之间彼此产生关系,就不会出现大小关系了。就非常适合你表达哪一类的属性。

那么,我们要比较他们之间的差距通常就是对两个矩阵做差再平方(欧氏距离算法)公式为:

l o s s = ( H 3 p r e d − Y r e a l ) 2 loss = (H_{3pred}-Y_{real})^2 loss=(H3predYreal)2

根据上面的公式我们得到预测的结果的公式就为:

p r e d = W 3 ∗ { W 2 [ W 1 X + b 1 ] + b 2 } + b 3 pred = W_3 * \{W_2[W_1X+b_1]+b_2\}+b_3 pred=W3{W2[W1X+b1]+b2}+b3

非线性模型

但上面的都是基于线性进行计算和检测的,但生活中遇到的很多东西都是非线性的,所以我们需要让模型具有非线性的表达能力。(我们人脑也是如此)

我们根据生物的神经元,神经元有多个输入,输出只有一个,但会有一个阈值,会让数字不会很大,过大会处于一个饱和的状态,过小就会趋近于0(sigmoid)。而在我们人工的神经网络之中x通常使用的是ReLU函数。

ReLU函数特性:梯度容易计算,只存在0和x的部分很好地避免了梯度弥散。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PQjiR6tZ-1591843167595)(E:\Programing Project\Git\Summary\pytorch-note\回归问题\imgs\3.png)]

我们就可以通过使用ReLU函数对 H 1 H_1 H1进行一个非线性化。公式如下:

H 1 = R e L U ( X W 1 + b 1 ) H_1=ReLU(XW_1+b_1) H1=ReLU(XW1+b1)

H 2 = R e L U ( H 1 W 2 + b 2 ) H_2=ReLU(H_1W_2+b_2) H2=ReLU(H1W2+b2)

H 3 = R e L U ( H 2 W 3 + b 3 ) H_3=ReLU(H_2W_3+b_3) H3=ReLU(H2W3+b3)

进行这样的操作之后再进行迭代嵌套。

p r e d = W 3 ∗ { W 2 [ W 1 X + b 1 ] + b 2 } + b 3 pred = W_3 * \{W_2[W_1X+b_1]+b_2\}+b_3 pred=W3{W2[W1X+b1]+b2}+b3

预测

输入一个新的 X X X然后进行 p r e d = W 3 ∗ { W 2 [ W 1 X + b 1 ] + b 2 } + b 3 pred = W_3 * \{W_2[W_1X+b_1]+b_2\}+b_3 pred=W3{W2[W1X+b1]+b2}+b3(每次线性运算都进行一次激活函数)的公式运算,最后得到的矩阵是一个 [ 10 , 1 ] [10,1] [101]的一个矩阵表示为:

[ 0.1 0.8 0.01 . . . ] \begin{bmatrix}0.1\\0.8\\0.01\\...\end{bmatrix} 0.10.80.01...

P ( 0 ∣ x ) = 0.1 P ( 1 ∣ x ) = 0.8 . . . P(0|x)=0.1 \\P(1|x)=0.8 \\ ... P(0x)=0.1P(1x)=0.8...

其中每一个元素都对应了0-9这10个数字的概率,然后我们在找到在这个矩阵里面最大的那个元素对应的数字就知道了这个识别的结果是啥。

实战

为什么经过三次线性运算就可以达到分类的效果?

高维矩阵求导理解不了,可以说是经验之谈。

搭建流程:

  1. 加载数据
  2. 构造模型
  3. 训练
  4. 测试

代码如下:

  • utils.py
import  torch
from    matplotlib import pyplot as plt


def plot_curve(data):
    fig = plt.figure()
    plt.plot(range(len(data)), data, color='blue')
    plt.legend(['value'], loc='upper right')
    plt.xlabel('step')
    plt.ylabel('value')
    plt.show()



def plot_image(img, label, name):

    fig = plt.figure()
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        plt.tight_layout()
        plt.imshow(img[i][0]*0.3081+0.1307, cmap='gray', interpolation='none')
        plt.title("{}: {}".format(name, label[i].item()))
        plt.xticks([])
        plt.yticks([])
    plt.show()


def one_hot(label, depth=10):
    out = torch.zeros(label.size(0), depth)
    idx = torch.LongTensor(label).view(-1, 1)
    out.scatter_(dim=1, index=idx, value=1)
    return out
  • train.py
import torch
import torchvision
import numpy as np
from utils import *
from torch.autograd import Variable
import torch.nn as nn
from torch import optim
import matplotlib.pyplot as plt
from torch.nn import functional as f

learning_rate = 0.01
batch_size = 512

transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor(),
                                             torchvision.transforms.Normalize((0.1307,), (0.3081,))])

train_data = torchvision.datasets.MNIST('./data', train=True, transform=transforms, download=True)
test_data = torchvision.datasets.MNIST('./data', train=False, transform=transforms, download=True)

train_dataloader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)
test_dataloader = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False)


# x, y = next(iter(train_dataloader))
# plot_image(x,y,"train")
# print(x.shape, y.shape)

# torch.Size([512, 1, 28, 28]) torch.Size([512])  size中第一个维度是图片的数量,第二个维度是通道数,后面两个维度是通道大小

class LinearNet(nn.Module):

    def __init__(self):
        super(LinearNet, self).__init__()

        # x@w1 + b1
        self.fc1 = nn.Linear(28 * 28, 256)  # 256由经验决定
        self.fc2 = nn.Linear(256, 64)  # 64也是经验决定
        self.fc3 = nn.Linear(64, 10)  # 第二个参数是识别类别的数量

    def forward(self, x):
        # x: [b, 1, 28, 28] 有b张图片
        # h1 = relu(xw1+b1)
        x = f.relu(self.fc1(x))
        # h2 = relu(h1w2+b2)
        x = f.relu(self.fc2(x))
        # h3 = h2w3+b3
        x = self.fc3(x)

        return x


linearnet = LinearNet()


# train code
# 逻辑:每一次求导,然后再更新数值
def train():
    # [w1,b1,w2,b2,w3,b3]
    optimizer = optim.SGD(linearnet.parameters(), lr=learning_rate, momentum=0.9)
    train_loss = []
    for epoch in range(1, 4):
        for batch, (x, y) in enumerate(train_dataloader):
            # x: img y: label
            # x: [img_num, 1, 28, 28], y:[img_num]

            # 网络只接受[img_num, 特征通道数]大小的
            # [img_num, 1, 28, 28]=>[img_num, 特征通道数]
            x = Variable(x)
            y = Variable(y)
            x = x.view(x.size(0), 28 * 28)
            # =>[img_num, 10]
            out = linearnet(x)
            y_onehot = one_hot(y, depth=10)
            # loss = mse(out, y_onehot)
            loss = f.mse_loss(out, y_onehot)

            # 清零梯度
            optimizer.zero_grad()
            # 计算梯度
            loss.backward()
            # w' = w - lr*grad
            optimizer.step()
            train_loss.append(loss.item())

            if batch % 10 == 0:
                print("Epoch: %d\tBatch: %d\tLoss: %f" % (epoch, batch, loss.item()))
    # 得到w1,b1,w2,b2,w3,b3
    plot_curve(train_loss)
    torch.save(linearnet, 'model_.pkl')


train()
# 准确度测试
total_correct = 0
model = torch.load('model_.pkl')
for x, y in test_dataloader:
    x = x.view(x.size(0), 28 * 28)
    out = linearnet(x)
    # out :[img_num, 10]=> pred:[img_num]
    pred = out.argmax(dim=1)
    correct = pred.eq(y).sum().float().item()
    total_correct += correct

total_num = len(test_dataloader.dataset)
acc = total_correct / total_num
print("test acc: %f" % acc)

# 输出结果
x, y = next(iter(test_dataloader))
x = x.view(x.size(0), 28 * 28)
out = linearnet(x)
pred = out.argmax(dim=1)
# plot_image(x, pred, "real")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Delv_Peter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值