首先还是导入需要的包
import torchimport torchvisionimport sysimport numpy as np#替代d2l库的库from IPython import displayimport torchvision.transforms as transforms
设置小批量数目为256。这一部分与之前的线性回归的读取数据大同小异,都是转换类型-->生成迭代器。
batch_size = 256
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=True,download=True,transform=transforms.ToTensor())#获取训练集mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=False,download=True,transform=transforms.ToTensor())#获取测试集(这两个数据集在已存在的情况下不会被再次下载)
#生成迭代器(调用一次返回一次数据)train_iter = torch.utils.data.DataLoader(mnist_train,batch_size=batch_size,shuffle = True,num_workers = 0)
test_iter = torch.utils.data.DataLoader(mnist_test,batch_size = batch_size,shuffle=False,num_workers=0)
由输入的数据可知:每个图像都是28*28像素,也就是说每个图像都有28*28=784个特征值。由于图像有10个类别,所以这个网络一共有10个输出。共计存在:784*10个权重参数和10个偏差参数。
num_inputs = 784num_outputs = 10
#初始化参数与线性回归也类似,权重参数设置为均值为0 标准差为0.01的正态分布;偏差设置为0W = torch.tensor(np.random.normal(0,0.01,(num_inputs,num_outouts)),dtype = torch.float)b = torch.zeros(num_outputs,dtype=torch.float32)
#同样的,开启模型参数梯度W.requires_grad_(requires_grad=True)b.requires_grad_(requires_grad=True)
softmax运算本质就是将每个元素变成非负数,且每一行和为1。
首先回顾一下tensor的按维度操作。
X = torch.tensor([[1,2,3],[4,5,6]])
#dim=0表示对列求和。keepdim表示是否在结果中保留行和列这两个维度X.sum(dim=0,keepdim=True)X.sum(dim=1,keepdim=True)
然后定义一下softmax运算:softmax运算会先对每个元素做指数运算,再对exp矩阵同行元素求和,最后令矩阵每行各元素与该行元素之和相除,最终得到的矩阵每行元素和为1且非负数。
def softmax(X): X_exp = X.exp() partition = X_exp.sum(dim=1,keepdim=True) return X_exp/partition #这部分用了广播机制
将第二步做的和第三步做的合起来。
def net(X): return softmax(torch.mm(X.view((-1,num_inputs)), W) +b) #第一步:首先将X换形成28*28的张量,然后用.mm函数将换形后的张量与权重参数相乘,最后加偏差参数 #第二步:对第一步进行softmax运算
首先介绍一下torch.gather函数
#gather函数的定义torch.gather(input,dim,index,out=None) → Tensor#gather的作用是这样的,index实际上是索引,具体是行(dim=1)还是列(dim=0)的索引要看前面dim 的指定,输出的大小由index决定
这个函数的原理我归结如下
假设输入与上同;index=B;输出为CB中每个元素分别为b(0,0)=0,b(0,1)=0 b(1,0)=1,b(1,1)=0 如果dim=0(列)则取B中元素的列号,如:b(0,1)的1b(0,1)=0,所以C中的c(0,1)=输入的(0,1)处元素2
如果dim=1(行)则取B中元素的列号,如:b(0,1)的0b(0,1)=0,所以C中的c(0,1)=输入的(0,0)处元素1
总结如下:输出 元素 在 输入张量 中的位置为:输出元素位置取决于同位置的index元素dim=1时,取同位置的index元素的行号做行号,该位置处index元素做列号dim=0时,取同位置的index元素的列号做列号,该位置处index元素做行号。
最后根据得到的索引在输入中取值
index类型必须为LongTensorgather最终的输出变量与index同形。
例子如下:
import torch
a = torch.Tensor([[1,2], [3,4]])
b = torch.gather(a,1,torch.LongTensor([[0,0],[1,0]]))#1. 取各个元素行号:[(0,y)(0,y)][(1,y)(1,y)]#2. 取各个元素值做行号:[(0,0)(0,0)][(1,1)(1,0)]#3. 根据得到的索引在输入中取值#[1,1],[4,3]
c = torch.gather(a,0,torch.LongTensor([[0,0],[1,0]]))#1. 取各个元素列号:[(x,0)(x,1)][(x,0)(x,1)]#2. 取各个元素值做行号:[(0,0)(0,1)][(1,0)(0,1)]#3. 根据得到的索引在输入中取值#[1,2],[3,2]
因为softmax回归模型得到的结果可能是多个标签对应的概率,为了得到与真实标签之间的损失值,我们需要使用gather函数提取出在结果中提取出真实标签对应的概率。
假设y_hat是1个样本在3个类别中的预测概率(其余七个为0),y是这个样本的真实标签(数字0-9表示)。
y_hat = torch.tensor([0.1,0.3,0.6])y = torch.LongTensor([0]) #gather函数中的index参数类型必须为LongTensory_hat.gather(1,y.vies(-1,1)) #如果y不是列向量,则需要将变量y换形为列向量。选取第一维度(行)。#套用上述公式可知,输出为0.1,0.1就是真是类别0的概率。
有了上述理论基础,并根据交叉熵函数的公式
我们可以得到最终的损失函数。
def cross_entropy(y_hat,y): return -torch.log(y_hat.gather(1,y.view(-1,1)))
计算准确率的原理:
我们把预测概率最大的类别作为输出类别,如果它与真实类别y一致,说明预测正确。分类准确率就是正确预测数量与总预测数量之比
首先我们需要得到预测的结果。
从一组预测概率(变量y_hat)中找出最大的概率对应的索引(索引即代表了类别)
#argmax(f(x))函数,对f(x)求最大值所对应的点x。我们令f(x)= dim=1,即可实现求所有行上的最大值对应的索引。A = y_hat.argmax(dim=1) #最终输出结果为一个行数与y_hat相同的列向量
然后我们需要将得到的最大概率对应的类别与真实类别(y)比较,判断预测是否是正确的
B = (y_hat.argmax(dim=1)==y).float()#由于y_hat.argmax(dim=1)==y得到的是ByteTensor型数据,所以我们通过.float()将其转换为浮点型Tensor()
最后我们需要计算分类准确率
我们知道y_hat的行数就对应着样本总数,所以,对B求平均值得到的就是分类准确率
(y_hat.argmax(dim=1)==y).float().mean()
上一步最终得到的数据为tensor(x)的形式,为了得到最终的pytorch number,需要对其进行下一步操作
(y_hat.argmax(dim=1)==y).float().mean().item()#pytorch number的获取统一通过.item()实现
整理一下,得到计算分类准确率函数
def accuracy(y_hat,y): return (y_hat.argmax(dim=1).float().mean().item())
作为推广,该函数还可以评价模型net在数据集data_iter上的准确率。
def net_accurary(data_iter,net): right_sum,n = 0.0,0 for X,y in data_iter: #从迭代器data_iter中获取X和y right_sum += (net(X).argmax(dim=1)==y).float().sum().item() #计算准确判断的数量 n +=y.shape[0] #通过shape[0]获取y的零维度(列)的元素数量 return right_sum/n
softmax回归应用的优化算法同样使用小批量随机梯度下降算法。
def sgd(params,lr,batch_size): #lr:学习率,params:权重参数和偏差参数 for param in params: param.data -= lr*param.grad/batch_size #.data是对数据备份进行操作,不改变数据本身。
在训练模型时,迭代周期数num_epochs和学习率lr都是可以调节的超参数,通过调节超参数的值可以获得分类更准确的模型。
num_epochs,lr = 5,0.1
def train_softmax(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr ,optimizer): for epoch in range(num_epochs): #损失值、正确数量、总数 初始化。 train_l_sum,train_right_sum,n= 0.0,0.0,0 for X,y in train_iter: y_hat = net(X) l = loss(y_hat,y).sum() #数据集损失函数的值=每个样本的损失函数值的和。 if optimizer is not None: optimizer.zero_grad() #对优化函数梯度清零 elif params is not None and params[0].grad is not None: for param in params: param.grad.data.zero_() l.backgrad() #对损失函数求梯度 optimzer(params,lr,batch_size) train_l_sum += l.item() train_right_sum += (y_hat.argmax(dim=1) == y).sum().item() n += y.shape[0] test_acc = net_accuracy(test_iter, net) #测试集的准确率 print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f' % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc)) train_softmax(net,train_iter,test_iter,cross_entropy,num_epochs,batch_size,[W,b],lr,sgd)
做一个模型的最终目的当然不是训练了,所以来预测一下试试。
def get_Fashion_MNIST_labels(labels): text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [text_labels[int(i)] for i in labels] #labels是一个列表,所以有了for循环获取这个列表对应的文本列表
def show_fashion_mnist(images,labels): display.set_matplotlib_formats('svg') #绘制矢量图 _,figs = plt.subplots(1,len(images),figsize=(12,12)) #设置添加子图的数量、大小 for f,img,lbl in zip(figs,images,labels): f.imshow(img.view(28,28).numpy()) f.set_title(lbl) f.axes.get_xaxis().set_visible(False) f.axes.get_yaxis().set_visible(False) plt.show()
X, y = iter(test_iter).next()
true_labels = get_Fashion_MNIST_labels(y.numpy())pred_labels = get_Fashion_MNIST_labels(net(X).argmax(dim=1).numpy())titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:9], titles[0:9])
由于训练比较耗时,我只训练了五次,可以看出,随着训练次数的增加,损失值从0.7854减少到0.4846;准确率从0.785提升到0.826。
#实现softmax回归import torchimport torchvisionimport sysimport numpy as np
from IPython import displayfrom numpy import argmaximport torchvision.transforms as transformsfrom time import timeimport matplotlib.pyplot as plt
batch_size =256num_inputs = 784num_outputs = 10num_epochs,lr = 5,0.1
mnist_train = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=True,download=True,transform=transforms.ToTensor())#获取训练集mnist_test = torchvision.datasets.FashionMNIST(root='~/Datasets/FashionMNIST',train=False,download=True,transform=transforms.ToTensor())#获取测试集(这两个数据集在已存在的情况下不会被再次下载)
#生成迭代器(调用一次返回一次数据)train_iter = torch.utils.data.DataLoader(mnist_train,batch_size=batch_size,shuffle = True,num_workers = 0)
test_iter = torch.utils.data.DataLoader(mnist_test,batch_size = batch_size,shuffle=False,num_workers=0)
#初始化参数与线性回归也类似,权重参数设置为均值为0 标准差为0.01的正态分布;偏差设置为0W = torch.tensor(np.random.normal(0,0.01,(num_inputs,num_outputs)),dtype = torch.float)b = torch.zeros(num_outputs,dtype=torch.float32)
#同样的,开启模型参数梯度W.requires_grad_(requires_grad=True)b.requires_grad_(requires_grad=True)
def softmax(X): X_exp = X.exp() partition = X_exp.sum(dim=1,keepdim=True) return X_exp/partition #这部分用了广播机制
def net(X): return softmax(torch.mm(X.view((-1,num_inputs)), W) +b)
def cross_entropy(y_hat,y): return -torch.log(y_hat.gather(1,y.view(-1,1)))
def accuracy(y_hat,y): return (y_hat.argmax(dim=1).float().mean().item())
def net_accurary(data_iter,net): right_sum,n = 0.0,0 for X,y in data_iter: #从迭代器data_iter中获取X和y right_sum += (net(X).argmax(dim=1)==y).float().sum().item() #计算准确判断的数量 n +=y.shape[0] #通过shape[0]获取y的零维度(列)的元素数量 return right_sum/n
def sgd(params,lr,batch_size): #lr:学习率,params:权重参数和偏差参数 for param in params: param.data -= lr*param.grad/batch_size
def train_softmax(net,train_iter,test_iter,loss,num_epochs,batch_size,params,lr ,optimizer,net_accuracy): for epoch in range(num_epochs): #损失值、正确数量、总数 初始化。 train_l_sum,train_right_sum,n= 0.0,0.0,0 for X,y in train_iter: y_hat = net(X) l = loss(y_hat,y).sum() #数据集损失函数的值=每个样本的损失函数值的和。
if params is not None and params[0].grad is not None: for param in params: param.grad.data.zero_() l.backward() #对损失函数求梯度 optimizer(params,lr,batch_size) train_l_sum += l.item() train_right_sum += (y_hat.argmax(dim=1) == y).sum().item() n += y.shape[0] test_acc = net_accurary(test_iter, net) #测试集的准确率 print('epoch %d, loss %.4f, train right %.3f, test right %.3f' % (epoch + 1, train_l_sum / n, train_right_sum / n, test_acc))
def get_Fashion_MNIST_labels(labels): text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [text_labels[int(i)] for i in labels] #labels是一个列表,所以有了for循环获取这个列表对应的文本列表
def show_fashion_mnist(images,labels): display.set_matplotlib_formats('svg') #绘制矢量图 _,figs = plt.subplots(1,len(images),figsize=(12,12)) #设置添加子图的数量、大小 for f,img,lbl in zip(figs,images,labels): f.imshow(img.view(28,28).numpy()) f.set_title(lbl) f.axes.get_xaxis().set_visible(False) f.axes.get_yaxis().set_visible(False) plt.show()
time1 = time() train_softmax(net,train_iter,test_iter,cross_entropy,num_epochs,batch_size,[W,b],lr,sgd,net_accurary)print('\n',time()-time1,'s')
X, y = iter(test_iter).next()
true_labels = get_Fashion_MNIST_labels(y.numpy())pred_labels = get_Fashion_MNIST_labels(net(X).argmax(dim=1).numpy())titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:9], titles[0:9])