每一张手写数字的图片在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]
H 1 = X W 1 + b 1 H_1=XW_1+b_1 H1=XW1+b1
H 2 = H 1 W 2 + b 2 H_2=H_1W_2+b_2 H2=H1W2+b2
H 3 = H 2 W 3 + b 3 H_3=H_2W_3+b_3 H3=H2W3+b3
也就是可以理解为通过不断地矩阵乘法,改变图像的维度,最后将需要的特征输出出来。
最后输出的 H 3 H_3 H3中第一个维度表示图片的数量,第二个维度表示 0 − 9 0-9 0−9对应的那个数字
图像分类最后得到的结果是一个以独热编码的一个矩阵,然后通过对该矩阵进行解码就可以得到对应分类的类别了。
独热编码(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=(H3pred−Yreal)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的部分很好地避免了梯度弥散。
我们就可以通过使用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] [10,1]的一个矩阵表示为:
[ 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(0∣x)=0.1P(1∣x)=0.8...
其中每一个元素都对应了0-9这10个数字的概率,然后我们在找到在这个矩阵里面最大的那个元素对应的数字就知道了这个识别的结果是啥。
为什么经过三次线性运算就可以达到分类的效果?
高维矩阵求导理解不了,可以说是经验之谈。
搭建流程:
代码如下:
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
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")