CIFAR-10数据集包含airplane、automobile、bird、cat、deer、dog、frog、horse、ship、truck十个类别、其中每个类别有6000个彩色图像。数据集中共有50000张训练图像和10000张测试图像且每个图片尺寸为3×32×32。
分类问题就是寻找到输入和输出之间的关系判断输入数据属于的类别(离散值),可以是二分类问题、也可以是多分类问题。
输出值不是简单地0、1、2、3、4、5、6、7、8、9,因为这些类别之间没有实数空间中数值大小的含义。输出的是概率P(0)、P(1)、…P(9)。概率值最大的项就是预测的结果。
分类问题的求解过程如下图:
在利用pytorch进行模型构造与训练时,总体思路分为四个阶段:
数据集准备、用类设计模型、构造损失函数和优化器、训练模型+测试。
如下图所示
我们使用逻辑回归模型,与线性回归模型类似,使用nn.Linear加载模型。
#导入包
import torch
import torchvision
from torch import nn
from torchvision.transforms import transforms
from torch.utils.data.dataloader import DataLoader
import numpy as np
#数据集导入
batch_size=64
#transforms 定义了一系列数据转化形式,并对数据进行预处理。
import torchvision.transforms as transforms
transform=transforms.Compose([transforms.ToTensor(), #传入数据转化成张量形式
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]) #均值 方差为0.5
#dataset下载数据集
trainset=torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transforms.ToTensor())
# root(string) 数据集的根目录在哪里
# train(bool, optional): 如果为True,则创建数据集training.pt,否则创建数据集test.pt。
# download(bool,optional): 如果为True,则从Internet下载数据集并将其放在根目录中,如果已下载数据集,则不会再次下载。
# transform(callable, optional): 一个函数/转化,它接受PIL图像并返回转换后的版本
# target_transform(callable, optional): 接受目标并对其进行转换的函数
#dataloader将下载好的数据集分块作为模型的输入
trainloader=torch.utils.data.DataLoader(trainset, batch_size, shuffle=True, num_workers=1)
#dataset(dataset): 输入的数据类型
#batch_size(int): 每次输入数据的行数,默认为1
#shuffle (bool): 洗牌,默认设置为False。在每次迭代训练时是否将数据洗牌,默认设置为False,
#将输入数据的顺序打乱,是为了使数据更有独立性。
#num_workers(int): 工作者数量,默认为0。使用多少个子进程来导入数据。
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transforms.ToTensor())
testloader=torch.utils.data.DataLoader(testset, batch_size, shuffle=False, num_workers=1)
# 定义线性模型结构
num_inputs, num_outputs = 3*32*32, 10 # 输入:三通道28*28的灰度图像, 输出:10个分类对应十个输出
class linearNet(nn.Module):
def __init__(self, num_inputs, num_outputs):
#对模型结构的进行定义,包括都有哪些层以及每层具有的功能。其中参数包括输入维度和输出维度,在我们的模型中,第一个输入是三通道 28*28 的灰度图像,故维度是 3*28*28,最终的输出的维度是 10,分别表示 10 个类别的概率值大小。
super(linearNet, self).__init__()
self.linear = nn.Linear(num_inputs, num_outputs)
def forward(self, x):
#根据__init__()中的定义决定了模型/网络的执行顺序
y = self.linear(x.view(x.shape[0], -1))
return y
linearnet = linearNet(num_inputs, num_outputs)
device = torch.device("cuda:0"if torch.cuda.is_available() else"cpu") #使用GPU加快训练速度
linearnet.to(device)
print(linearnet)
#损失函数和优化器
import torch.nn.functional as F
import torch.optim as optim
learning_rate=0.001
loss = nn.CrossEntropyLoss() #定义交叉熵损失函数, 这个方法集成了softmax计算
optimizer = torch.optim.SGD(linearnet.parameters(),lr=learning_rate) #SGD优化器
#测试集数据评估
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for data in data_iter:
X, y=data
X, y=X.to(device),y.to(device)
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
#定义训练过程
epochs = 200
def train_model(optimizer):
for epoch in range(epochs):
train_loss_sum, train_acc_sum, n = 0.0, 0.0, 0
for data in trainloader:
inputs, y=data
inputs, y=inputs.to(device),y.to(device)
#print(inputs)
y_hat = linearnet(inputs)
cost = loss(y_hat, y).sum()
optimizer.zero_grad()
cost.backward()
optimizer.step()
train_loss_sum += cost.item()
train_acc_sum += (torch.argmax(y_hat, dim=1) == y).float().sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(testloader, linearnet)
print('epoch %d, loss %.4f, train acc %.4f, test acc %.4f' % (epoch + 1, train_loss_sum / n, train_acc_sum / n, test_acc))
#调用函数训练数据
train_model(optimizer)
#结果可视化
#准确度绘制
import matplotlib.pyplot as plt
# 绘制训练%验证的精度
Trainacc=[] #请输入上述训练过程中保存的数据
Testacc=[] ##请输入上述训练过程中保存的数据
plt.plot(Trainacc)
plt.plot(Testacc)
plt.title('qijiajing-Residual Block Model accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend(['Train acc', 'Test acc'], loc='upper left')
plt.show()
#loss绘制
import matplotlib.pyplot as plt
loss=[]
plt.plot(loss)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('qijiajing-Model accuracy')
运行结果:
我们进行了250次迭代,最终训练集精度稳定在43.5%左右,测试集精度稳定在41%左右,是随机猜测10%的四倍。这说明模型学习到了一些规律,然而由于超参数的选取、模型初始化参数的选取和输入特征较多等限制了线性模型对此类数据集处理的准确度。
人工神经网络将多个神经元连接到一起,类比与人的大脑,人工神经网络能够自动学习输入特征和目标属性之间的复杂关系。一个神经元的输出是另一个神经元的输入,通过反向传播不断学习和调整参数来学习、优化模型。所有的神经元属于输入层、隐藏层或输出层。
我们开始训练网络时使用较小尺寸的隐藏层,观察精度结果慢慢增加。隐藏层更多更大分类器准确性不一定越高,因为这意味着参数也随之增多。容易使得模型过拟合,并且训练时间随之增加。
我们最终采用一个全连接的具有两层隐藏层的神经网络,输入层有33232个节点,第一个隐藏层有60个节点,第二个隐藏层有40个节点,输出层有10个节点,分别表示10个类别,每一层的激活函数都是sigmoid函数。
Sigmoid函数和网络层在__init__()函数中定义,在forward()函数中顺序调用。
#导入包
import torch
import torchvision
import torchvision.transforms as transforms
#matplotlib inline
#导入数据集并使用transform进行数据(归一化)处理 0.5表示均值 0.1表示方差 (可以自己换个数)
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.1,0.1,0.1))])
batch_size=16
trainset= torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
trainloader=torch.utils.data.DataLoader(trainset, batch_size, shuffle=True, num_workers=1)
#测试数据集
testset=torchvision.datasets.CIFAR10(root='./data',train=False,download=True,transform=transform)
testloader=torch.utils.data.DataLoader(testset, batch_size, shuffle=False, num_workers=1)
#定义模型
import torch.nn as nn
import torch.nn.functional as F
class FCNet(nn.Module):
def __init__(self):
super(FCNet, self).__init__()
self.fc1 = nn.Linear(3*32*32, 60)
self.fc2 = nn.Linear(60,40)
self.fc3 = nn.Linear(40,10)
#self.dropout = nn.Dropout(p=0.4)
self.sigmoid=torch.nn.Sigmoid()
def forward(self, x):
x = x.view(x.shape[0],-1) #改变x的形状
x = self.sigmoid(self.fc1(x))
#x = self.dropout(x)
x = self.sigmoid(self.fc2(x))
#x = self.dropout(x)
x = self.sigmoid(self.fc3(x))
return x
#打印神经网络
fcnet=FCNet()
print(fcnet)
device = torch.device("cuda:0"if torch.cuda.is_available() else"cpu")
fcnet.to(device)
# 假设在一台CUDA机器上运行,那么这里将输出一个CUDA设备号:
print(device)
#定义损失函数和优化器
import torch.optim as optim
loss=nn.CrossEntropyLoss()
optimizer= optim.SGD(cnnnet.parameters(),lr=0.001,momentum=0.99)
#测试集数据评估(计算准确度)
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for data in data_iter:
X, y=data
X, y=X.to(device),y.to(device)
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
#定义模型训练函数
epochs=150 #定义迭代次数
def train_model(optimizer):
for epoch in range(epochs):
train_loss_sum, train_acc_sum, n = 0.0, 0.0, 0 #训练集损失,训练集精度
for data in trainloader:
inputs, y=data
inputs, y=inputs.to(device),y.to(device)
#print(inputs)
y_hat = fcnet(inputs)
cost = loss(y_hat, y).sum()
#反馈过程
optimizer.zero_grad()
cost.backward()
optimizer.step()
train_loss_sum += cost.item()
train_acc_sum += (torch.argmax(y_hat, dim=1) == y).float().sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(testloader, fcnet)
print('epoch %d, loss %.4f, train acc %.4f, test acc %.4f' % (epoch + 1, train_loss_sum / n, train_acc_sum / n, test_acc)) #分别打印训练集损失、测试集精度、训练集精度。
#调用函数进行模型的训练
train_model(optimizer)
隐藏层的节点数分别为60、40,batch_size=16,使用SGD优化器,其中网络初始参数随机设定,学习率为0.01(对比于0.001训练效果更好),momentum=0.8。
相比于线性分类器,全连接神经网络分类器最终精度收敛到46%左右。但全连接神经网络分类器也有局限性,在全连接神经网络中,我们将图像化为一个一维向量,丧失了一些原有的空间结构信息,故模型学习不到这些空间结构信息。下面我们引入卷积神经网络,它是先将图像直接按照原始的空间结构进行保存、输入数据,从而保留原始的空间信息再进行神经网络的训练。