深度学习实现——softmax回归

从⼀个图像分类问题开始。

假设每次输入是⼀个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个特征  x=[x_1,x_2,x_3,x_4],3种分类方法  o=[o_1,o_2,o_3]

o_1=w_{11}x_1+w_{12}x_2+w_{13}x_3+w_{14}x_4+b_1

o_2=w_{21}x_1+w_{22}x_2+w_{23}x_3+w_{24}x_4+b_2

o_3=w_{31}x_1+w_{32}x_2+w_{33}x_3+w_{34}x_4+b_3

每一种类别都与4个特征有关,所以输出层为全连接层;此外,不存在其他的隐藏层,所以softmax回归也是一个单层神经网络。

2、softmax回归的基本原理

希望模型的输出是属于各个类别的概率,然后选择具有最大输出值的类别作为最终预测值。例如,o=[o_1,o_2,o_3]=[0.1,0.8,0.1],那么模型预测的类别就是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    #平均样本损失  

每个样本的损失是:-y_{i}log(\hat{y_{i}})

所有样本的损失和是:loss=\sum -y_{i}log(\hat{y_{i}})=-log(\hat{y_{y}}),即所对应类别的概率的负对数值。

损失函数CEEloss定义样本的平均损失:loss=\sum -y_{i}log(\hat{y_{i}})/n=-log(\hat{y_{y}})/n

定义优化算法

#定义优化算法
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])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值