从⼀个图像分类问题开始。
假设每次输入是⼀个1*2*2(即通道数*图像长度*图像宽度)的灰度图像,每个图像对应四个特征x1, x2, x3, x4(即像素总数2*2),整理为一个特征向量。
假设每个图像属于类别“猫”,“鸡”和“狗”中的⼀个。有两种表示方法:
第一种:自然顺序码 y ∈ {1, 2, 3},其中整数分别代表{狗, 猫, 鸡}。但是计算机在处理这类数据时会认为是连续且可以计算的,有大小,并且有着可以相加相乘的联系。
第二种:独热编码(one-hot encoding)。(1, 0, 0)对应于 “猫”、(0, 1, 0)对应于“鸡”、(0, 0, 1)对应于“狗”。这也是常用的编码形式。
一、softmax回归介绍
1、分类问题的网络架构
同样引用上述案例,一共有4个特征 ,3种分类方法
。
每一种类别都与4个特征有关,所以输出层为全连接层;此外,不存在其他的隐藏层,所以softmax回归也是一个单层神经网络。
2、softmax回归的基本原理
希望模型的输出是属于各个类别的概率,然后选择具有最大输出值的类别作为最终预测值。例如,,那么模型预测的类别就是2,在上述例子中代表“鸡”。
softmax的计算公式为
使用exp(z)使输出值之间的差距拉大,大的更大,小的更小,能更好区分各个类别,最后中心化使其分布在[0,1]之间,各个概率之和为1。
二、介绍常用的分类数据集FashionMNIST
Fashion MNIST/服饰数据集包含70000张灰度图像,其中包含60,000个示例的训练集和10,000个示例的测试集,每个示例都是一个28x28灰度图像。
接下来进行该数据集的导入和可视化
1、导入所需要的包
%matplotlib inline
import torch
import torchvision #包含数据集
import matplotlib.pyplot as plt
from torch.utils import data
from torchvision import transforms #改变变量格式
2、加载数据集
mnist_train = torchvision.dataset.FashionMNIST(
root='.../data',transform=transforms.ToTensor(),download=True,train=True)
mnist_test = torchvision.dataset.FashionMNIST(
root='.../data',transform=transforms.ToTensor(),download=True,train=False)
#参数root:下载路径,transform:调整格式为张量,
#download:True表示下载文件,train:是否加载训练集
'''transforms.ToTensor()可以将图像数据转变成32位浮点数个数,并除以255使得所有像素的均值在0-1之间
3、定义绘图函数
#定义函数,可以根据最大序号获取类别名称
def get_name(labels):
text_labels = ['t_shirt','trouser','pullover','dress','coat','sandal','shirt','sneaker','bag','ankle boot']
#一共有十类
return [text_labels[i] for i in labels]
#定义函数,用来绘制图像
def show_image(imgs,num_rows,num_cols,titles=None,scale=1.5):
#fig是整个图形的容器。
#axs 是一个形状为 (num_rows,num_cols) 的二维数组,其中每个元素都是一个Axes 对象。
fig,axes = plt.subplot(num_rows,num_cols,figsize=(num_cols*scale,num_rows*scale))
axes = axes.Flatten() #变成num_rows*num_cols的一维数组,方便后续循环
for i,(img,ax) in enumerate(zip(imgs,axes)):
if torch_istensor(img):
ax.imshow(img.numpy())
else:
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False) #设置横纵轴不可见
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(title[i]) #获取标题
retuen axes
'''
假设我们有如下两个列表:
axes = [ax1, ax2, ax3]
imgs = [img1, img2, img3]
那么 enumerate(zip(axes, imgs)) 会产生这样的输出:
(0, (ax1, img1))
(1, (ax2, img2))
(2, (ax3, img3))
'''
4、绘制图像
#获取数据
train_iter = data.DataLoader(mnist_train,batch_size=18)
x,y = next(iter(train_iter)
#调用函数
show_image(x,2,9,titles=get_label(y))
三、softmax回归从零开始实现
不借助Torch中已经定义好的softmax函数,能更好地理解模型运行过程。
1、导入所需要的库
import torch
import torchvision
from torch.utils import data
from torch import transforms
2、读取小批量数据
定义一个数据导入函数。输入需要的批量大小和数据格式,即可得到相应的小批量训练集和测试集。
def load_data(batchsize,resize=None):
trans = [transforms.ToTensor()] #初始定义一个转化方式
if resize:
trans = trans.insert(0,transforms.Resize(resize))
trans = transforms.Compose(trans)
#trans是一个列表,trans.insert(插入位置,插入的元素)表示在列表的指定位置插入新元素
#transforms.Resize:用于调整图像的大小。可以接受一个或两个整数参数,分别代表目标图像的宽和高。
#这句的目的是图像数据经过后续之前,先调整大小
#transforms.Compose()整合所有的操作
mnist_train = torchvision.dataset.FashionMNIST(root='.../data',train=True,download=False,transform=trans)
mnist_test = torchvision.dataset.FashionMNIST(root='.../data',train=False,download=False,transform=trans)
#前面已经下载过数据,故download=False
return data.DataLoader(mnist_train,batchsize,shuffle=True,num_workers=4),data.DataLoader(mnist_test,batchsize,shuffle=True,num_workers=4)
#num_workers=4表示分配4个进程用于读取数据
在这里,我们获得256个样本的数据,数据格式不变。所以每次输出的一组训练集的大小为[256,1,28,28],测试集的大小为[256]
batch_size = 256
train_iter,test_iter = load_data(batchsize)
3、函数定义
softmax回归的损失函数使用交叉熵损失函数,优化算法仍使用小批量梯度下降算法,详情可以浏览深度学习实现——线性回归-CSDN博客,深度学习的两大问题概述——分类和回归-CSDN博客
定义初始参数
num_input = 784
num_output = 10
w = torch.data.normal(0,0.01,size=(num_input,num_output),require_grad=True)
b = torch.zero(num_output,require_grad=True)
定义回归模型
#定义softmax函数,得到每个类别的概率值
def softmax(z):
z_exp = torch.exp(z)
sum_z = torch.sum(1,keepdim=True)
return z_exp/sum_z
#定义回归模型,y_pred=net(x)
def net(x):
return softmax(torch.matmul(x.reshape(-1,num_input),w)+b)
定义损失函数
#定义损失函数
def CEEloss(y_pred,y,batch_size):
l = y_pred[range(len(y_pred)),y] #长度为[256]的张量,获得对应分类的概率值
l_row = -torch.log(l) #得到每个样本的损失
return torch.sum(l_row)/batch_size #平均样本损失
每个样本的损失是:
所有样本的损失和是:,即所对应类别的概率的负对数值。
损失函数CEEloss定义样本的平均损失:
定义优化算法
#定义优化算法
def sgd(lr,params):
with torch.no_grad():
for param in params:
param -= lr*param.grad()
param.grad_zero_()
定义模型评估指标
def accuracy(y_pred,y):
catagory = y_pred.argmax(axis=1)
num = []
for i in range(len(catagory)):
if catogory[i] == y[i]: #预测正确
num.append(1)
else: #预测错误
num.append(0)
return sum(num) #返回预测正确的数量
定义预测精确度
def pred(test_iter):
metric = Accumulator(2)
for x, y in test_iter:
y_pred = net(x)
metric.add(accuracy(y_pred, y), y.numel())
return metric
定义一个类,用于存储数据
class Accumulator:
def __init__(self,n):
self.data = [0.0]*n #初始定义为长为n的列表
def add(self,*arg):
self.data = [a+ float(b) for a,b in zip(self.data,arg)] #原始数据与输入数据对应相加
def reset(self):
self.data = [0.0]*len(self.data) #清空数据
def __getitem__(self,idx):
return self.data[idx] #可以索引数据
4、训练模型并进行预测
num_epochs = 10
lr = 0.01
metric = Accumulator(3)
train_loss = []
train_acc = []
for epoch in len(num_epochs):
for x,y in train_iter: #对60000个数据每次拿出256个数据进行训练优化
y_pred = net(x)
loss = CEEloss(y_pred,y,batch_size)
loss.backward() #loss在前面定义为样本平均损失
sgd([w,b],lr)
metric.add(float(loss.sum()),accuracy(y_pred,y),y.numel())
#遍历结束60000个数据,总的损失=每组样本平均损失之和/组数,总的精确度=预测正确的数量/样本总数
train_loss.append(metric[0]/int(60000/batch_size))
train_acc.append(metric[1]/meric[2])
#测试集精确度
test = pred(test_iter)
test_acc.append(test[0]/test[1])
print("epoch:{}".format(epoch+1)+' '+'loss:{}'.format(metric[0]/int(60000/batch_size)) + ' '+ 'acc:{}'.format(metric[1]/metric[2]))
epoch:1 loss:1.3769495204473152 acc:0.6339666666666667 epoch:2 loss:0.9205031445902637 acc:0.7139333333333333 epoch:3 loss:0.8057272392205703 acc:0.7478 epoch:4 loss:0.7450133630225801 acc:0.7662 epoch:5 loss:0.7052615309755007 acc:0.7792166666666667 epoch:6 loss:0.6762602594163682 acc:0.78685 epoch:7 loss:0.6538072010008698 acc:0.7937 epoch:8 loss:0.6357285793011005 acc:0.7994166666666667 epoch:9 loss:0.6207016917248057 acc:0.803 epoch:10 loss:0.6080135073289912 acc:0.8063
5、预测结果可视化输出
train_iter_,test_iter_ = load_mnist_data(6)
for x, y in test_iter_:
trues = get_labels(y)
preds = get_labels(net(x).argmax(axis=1))
t = [true +'\n' + pred for true, pred in zip(trues, preds)]
show_image(x.reshape(6,28,28),1,6,title=t)
break
plt.figure(figsize=[5,4])
plt.plot(range(0,10),train_loss,linestyle='-',label='loss')
plt.plot(range(0,10),train_acc,linestyle='--',label='accuracy')
plt.plot(range(0,10),test_acc,linestyle='--',label='test_loss')
plt.legend()
plt.show()
四、softmax的简洁实现
前两步与三相同,从第三步开始
3、模型初始化
from torch import nn
#定义回归函数
net = nn.Sequential(nn.Flatten(),nn.Linear(num_input,num_output))
#把数据拉直,即第二步中resize的作用
#初始化参数
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight,std=0.01)
net.apply(init_weights)
#定义损失函数
loss = nn.CrossEntropy(reduction='none)
#定义优化器
trainer = torch.optim.SGD(net.parameters(),lr=0.01)
4、训练模型并预测
num_epochs = 10
train_loss = []
train_acc = []
for epoch in range(num_epochs):
metric = Accumulator(3)
for x, y in train_iter:
# 计算梯度并更新参数
y_hat = net(x)
l = loss(y_hat, y)
#使⽤定制的优化器和损失函数
trainer.zero_grad()
l.mean().backward()
trainer.step()
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
train_loss.append(metric[0] / metric[2])
train_acc.append(metric[1] / metric[2])
test = pred(test_iter)
test_acc.append(test[0]/test[1])
print("epoch:{}".format(epoch+1)+' '+'loss:{}'.format(metric[0] / metric[2]) + ' '+ 'acc:{}'.format(metric[1] / metric[2])