【动手学深度学习笔记】之实现softmax回归模型

时间:2022-07-23
本文章向大家介绍【动手学深度学习笔记】之实现softmax回归模型,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1.实现softmax回归模型

首先还是导入需要的包

import torchimport torchvisionimport sysimport numpy as np#替代d2l库的库from IPython import displayimport torchvision.transforms as transforms

1.1获取和读取数据

设置小批量数目为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)

1.2初始化模型参数

由输入的数据可知:每个图像都是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)

1.3实现softmax运算

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	#这部分用了广播机制

1.4定义模型

将第二步做的和第三步做的合起来。

def net(X):	return softmax(torch.mm(X.view((-1,num_inputs)), W) +b)	#第一步:首先将X换形成28*28的张量,然后用.mm函数将换形后的张量与权重参数相乘,最后加偏差参数    #第二步:对第一步进行softmax运算

1.5定义损失函数

首先介绍一下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)))

1.6计算分类准确率

计算准确率的原理:

我们把预测概率最大的类别作为输出类别,如果它与真实类别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

1.7优化算法

softmax回归应用的优化算法同样使用小批量随机梯度下降算法。

def sgd(params,lr,batch_size):    #lr:学习率,params:权重参数和偏差参数    for param in params:        param.data -= lr*param.grad/batch_size        #.data是对数据备份进行操作,不改变数据本身。

1.8训练模型

在训练模型时,迭代周期数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)

1.9预测

做一个模型的最终目的当然不是训练了,所以来预测一下试试。

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])