【PyTorch入门】之一文看懂如何用softmax回归模型识别图像

先打个广告哈。我自己做的公众号【拇指笔记】,每天写写我自己学习中记下的笔记,欢迎各位大神关注一下~

本篇文章完整程序可以在我的公众号后台回复"softmax"获取,欢迎各位关注!

1.实现softmax回归模型

首先还是导入需要的包

import torch
import torchvision
import sys
import numpy as np

from IPython import display
from numpy import argmax
import torchvision.transforms as transforms
from time import time
import matplotlib.pyplot as plt

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 = 784
num_outputs = 10

#初始化参数与线性回归也类似,权重参数设置为均值为0 标准差为0.01的正态分布;偏差设置为0
W = 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;输出为C
B中每个元素分别为b(0,0)=0,b(0,1)=0
			  b(1,0)=1,b(1,1)=0
    
如果dim=0(列)
则取B中元素的列号,如:b(0,1)1
b(0,1)=0,所以C中的c(0,1)=输入的(0,1)处元素2

如果dim=1(行)
则取B中元素的列号,如:b(0,1)0
b(0,1)=0,所以C中的c(0,1)=输入的(0,0)处元素1

总结如下:
输出  元素  在  输入张量  中的位置为:
输出元素位置取决与同位置的index元素
dim=1时,取同位置的index元素的行号做行号,该位置处index元素做列号
dim=0时,取同位置的index元素的列号做列号,该位置处index元素做行号。

最后根据得到的索引在输入中取值

index类型必须为LongTensor
gather最终的输出变量与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参数类型必须为LongTensor
y_hat.gather(1,y.vies(-1,1))	
#如果y不是列向量,则需要将变量y换形为列向量。选取第一维度(行)。
#套用上述公式可知,输出为0.1,0.1就是真是类别0的概率。

有了上述理论基础,并根据交叉熵函数的公式
H ( y ( i ) , y ^ ( i ) ) = l o g   y ^ j ( i ) H(y^{(i)},\hat{y}^{(i)}) =- log\ \hat{y}^{(i)}_j
我们可以得到最终的损失函数。

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.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 acc %.3f, test acc %.3f' % (epoch + 1, train_l_sum / n, train_right_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。


欢迎各位关注【拇指笔记】,每天更新我的学习笔记~
在这里插入图片描述

发布了31 篇原创文章 · 获赞 72 · 访问量 4953

猜你喜欢

转载自blog.csdn.net/weixin_44610644/article/details/104671610