前面我们讲了这么多理论知识,下面我们来简单的来一个实战学习,利用全连接神经网络实现 手写数字分类。 数据集是一个手写体数据集,包括 0~9 这 10 个数字,图片大小是 28 x 28 的灰度图。数据集由四部分组成,分别是:
数据处理
在开始训练网络之前,良好的数据预处理和参数初始化可以使训练效率更高,效果更优 。如果有了解机器学习的童鞋,对这些肯定不陌生,机器学习在处理数据时,也会先将数据标准化后再进行特征的操作,和深度学习是大同小异的。常用有以下几种处理方法:
中心化
中心化为将每个特征维度都减去相应的均值,这样可以使得数据变成 0 均值,特别对于一些图像数据,为 了方便我们将所有的数据都减去一个相同的值 。
标准化
在使得数据都变成 0 均值之后,还需要使用标准化的做出让数据不同的特征维度都有着相同的规模。有两种常用的方法:一种是除以标准差,这样可以使得新数据的分布接近标准高斯分布;还有一种做法就是让每个特征维度的最大值和最小值按比例缩放到 -1 ~1 之间。
PCA 会将数据中心化,然后计算数据的协方差矩阵,通过这个协方差矩阵来进行奇异值分解 (
),然后对数据进行
去相关性,将其投影到一个特征空间,我们取一些较大的、主要的特征向量来降低数据的维数,去掉一些没有方差的维度。这个操作对于一些线性模型和神经网络,都能取得良好的效果。
白噪声
白噪声首先会跟 一样将数据投影到一个特征空间,然后每个维度除以特征值来标准化这些数据,直观上就是一个多元高斯分布转化到了一个 0 均值,协方差矩阵为 1 的多元高斯分布。
数据处理
pytorch中使用的数据处理的函数为 ,它提供了很多图片预处理的方法 。
from torchvision import transforms
from torchvision.datasets import mnist # 导入内置的 mnist 数据
transform = transforms.Compose([
transforms.ToTensor(), # 数据转为tensor
transforms.Normalize([0.5], [0.5]) # 正则化,均值: 0.5 方差: 0.5
])
train_set = mnist.MNIST('./data', train=True, transform=transform, download=False)
test_set = mnist.MNIST('./data', train=False, transform=transform, download=False)
我们的图片是黑白色,所以图片只有1通道,
只需要使用
,如果是彩色图片,那么就有三通道,需要使用
来表示每个通道对应的均值与方差。
参数解释:
:
训练,
无需训练;
:数据处理;
:
下载,
无需下载。我这里已经下载过了,所以使用的
。
数据加载之后,我们可以看看数据信息:
a_data, a_label = train_set[0]
a_data.shape
可以看到第一张图片的大小为:torch.Size([1, 28, 28])
import matplotlib.pyplot as plt
print(train_set.train_data.size()) # 训练集
print(train_set.train_labels.size()) # 训练集标签数量
plt.imshow(train_set.train_data[0].numpy(), cmap='gray')
plt.title('%i' % train_set.train_labels[0])
plt.show()
我们一次有这么多数据,训练会很繁琐, 提供了一个 的封装接口将数据按照一定大小封装成 ,这样我们可以按批次训练,提高效率。
train_data = DataLoader(train_set, batch_size=64, shuffle=True)
test_data = DataLoader(test_set, batch_size=128, shuffle=False)
表示每次取数据数量, 表示数据是否打乱, 打乱。
网络模型
我这里简单的定义一个四层的全连接神经网络,参数我直接带上了,也可以定义为变量,在引用模型的时候直接使用。
import torch
from torch import nn
class fnn(nn.Module):
def __init__(self):
super(fnn, self).__init__()
self.layer1 = nn.Sequential( # 全连接层 [1, 28, 28]
nn.Linear(784, 400), # 输入维度,输出维度
nn.BatchNorm1d(400), # 批标准化,加快收敛,可不需要
nn.ReLU() # 激活函数
)
self.layer2 = nn.Sequential(
nn.Linear(400, 200),
nn.BatchNorm1d(200),
nn.ReLU()
)
self.layer3 = nn.Sequential( # 全连接层
nn.Linear(200, 100),
nn.BatchNorm1d(100),
nn.ReLU()
)
self.layer4 = nn.Sequential( # 最后一层为实际输出,不需要激活函数,因为有 10 个数字,所以输出维度为 10,表示10 类
nn.Linear(100, 10),
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
output = self.layer4(x)
return output
导入网络:
net = fnn() # 导入网络
net.parameters # 查看网络参数
我们可以看到网络每层的参数信息
接着定义损失函数和优化函数:
criterion = nn.CrossEntropyLoss() # 损失函数
optimizer = torch.optim.Adam(net.parameters(), 1e-1)
网络训练
from torch.autograd import Variable
for epoch in range(20):
train_loss = 0
train_acc = 0
for im, label in train_data:
im = Variable(im.view(im.size(0), -1))
label = Variable(label)
out = net(im) # 加载模型
loss = criterion(out, label) # 计算误差
optimizer.zero_grad() # 梯度归零
loss.backward() # 反向传播
optimizer.step() # 参数更新
train_loss += loss.data
# 计算分类的准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item() # 预测正确数目
acc = num_correct / im.shape[0] # 正确率
train_acc += acc
print('epoch: {}, Train Loss: {:.6f}, Train Acc: {:.6f}'.format(epoch, train_loss/len(train_data), train_acc/len(train_data)))
结果测试
网络训练好之后,我们从训练结果可以看到,正确率可以高达 98.6%,使用测试集在看看效果。
eval_loss = 0
eval_acc = 0
net.eval() # 将模型改为预测模式
for im, label in test_data:
im = Variable(im.view(im.size(0), -1))
label = Variable(label)
out = net(im)
loss = criterion(out, label)
eval_loss += loss.data
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / im.shape[0]
eval_acc += acc
eval_losses.append(eval_loss / len(test_data))
eval_acces.append(eval_acc / len(test_data))
print('epoch: {}, Eval Loss: {:.6f}, Eval Acc: {:.6f}'.format(e, eval_loss/len(test_data), eval_acc/len(test_data)))
import torchvision
from torchvision.transforms import ToPILImage
show = ToPILImage()
classes = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
dataiter = iter(test_data) # 显示4张图片
images, labels = dataiter.next()
print("实际类别: ", " ".join("%11s"%classes[labels[j]] for j in range(8)))
show(torchvision.utils.make_grid((images+1)/2)).resize((1000, 1000)) # resize: 图片缩放 make_grid:将多个图片拼到一个网格
images = Variable(images.view(images.size(0), -1))
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
print("预测类别: ", " ".join("%11s"%classes[predicted[j]] for j in range(8)))