《PyTorch深度学习实践》学习笔记:多分类问题


1. 输出多个类别

多分类问题不同,以MNIST数据集为例,需要计算出每个方框属于不同数字的10个概率,这10个概率需要满足:
在这里插入图片描述
这样才能满足多分类的离散分布;而对于二分类问题,我们只需要求出P(y=1)的概率就可以,因为P(y=0)的概率我们可以通过1-(y=1)求得。

在这里插入图片描述
输出之间要有竞争性且满足分布的要求。

对于解决多分类问题,我们不在采样sigmoid作为最后一层,因为sigmoid层不能满足上面的两个条件,而这里采用了softmax层。
在这里插入图片描述
softmax层能解决多分类问题,它主要解决的问题就是使得前面输出的正值和负值都统一成正值,此外可以使得多个输出的和为1。

2. softmax层

softmax满足两个要求:

  1. 让每一个输出都大于0
  2. 让k个分类的和加起来等于1

softmax的公式:
在这里插入图片描述
softmax的过程:
在这里插入图片描述

3. loss计算

在这里插入图片描述
对于多分类问题,输入的一个对象他只有一个标签是1,其余的都是0,这样0*logY就没有了,只剩下标签1对应的logY,也就是loss值。

整个过程的计算如下**(NLLLoss损失)**:
在这里插入图片描述
其实在实际计算过程种,我们一般直接找到标签为1的,然后直接乘以它的log预测值。

import numpy as np
y = np.array([1, 0, 0])
z = np.array([0.2, 0.1, -0.1])
y_pred = np.exp(z) / np.exp(z).sum()
loss = (- y * np.log(y_pred)).sum()
print(loss)

pytorch提供了多分类softmax以及loss的整个计算(交叉熵损失):
在这里插入图片描述
最后一层是线性层,后面直接跟torch.nn.CrossEntropyLoss()计算交叉熵损失,其中y是长整形的tensor张量,代表输入属于第几个分类,上图代表y属于第一个分类。z代表最后线性层的输出。
在这里插入图片描述
Y代表真实的属于哪一个类,下面的两个为预测的结果,最后线性层的输出。

import torch
y = torch.LongTensor([0])
z = torch.Tensor([[0.2, 0.1, -0.1]])
criterion = torch.nn.CrossEntropyLoss()
loss = criterion(z, y)
print(loss)

注意CrossEntropyLoss()和NLLLoss()的区别:

CrossEntropyLoss损失函数是将Softmax层和NLLLoss损失函数整合在一。

注意:使用CrossEntropyLoss损失函数时,神经网络的最后一层不需要做激活,(也就是不用经过Softmax层的计算),直接输入到CrossEntropyLoss损失函数中就可以,因为CrossEntropyLoss损失函数包含的Sofrmax层。

CrossEntropyLoss()
在这里插入图片描述
NLLLoss()
在这里插入图片描述

4. softmax和交叉熵来处理MNIST数据集

MNIST数据集:
在这里插入图片描述
手写数字数据集,其中包含0-9数字,也就是十分类问题。

我们之前学习的案例中,输入x都是一个向量;在MNIST数据集中,我们需要输入的是一个图像,怎样,图像怎么才能输入到模型中进行训练呢?一种方法是我们可以把图像映射成一个矩向量,再输入到模型中进行训练。怎样将一个图像映射成一个向量?

如图所示是MNIST数据集中一个方格的图像,它是由28x28=784个像素组成,其中越深的地方数值越接近0,越亮的地方数值越接近1。
在这里插入图片描述
图像的像素一般分布在0-255之间,我们通过映射使其分布在0-1之间。

3.1 准备数据集

图像张量:灰度图(黑白图像)就是一个单通道的图像,彩色图像是多通道的图像(分别是R,G,B三个通道),一个通道具有高度——H,宽度——W,通道由——C表示。
在这里插入图片描述
transform作用:在pytorch中读取图像时使用的是python的PIL,而由PIL读取的图像一般是由W x H x C组成,而在pytorch中为了进行更高效的图像处理,需要图像由C x W x H这样的顺序组成,因此transform的作用就是将PIL读取的图像顺序转换成C x W x H,同时pytorch图像的类型为tensor类型;像素值从0-255转换成0-1。
在这里插入图片描述

Normalize作用:归一化或标准化,提供的参数为均值和标准差。

举例四六级成绩的计算:(你的分数-均值)/标准差*70% + 500
在这里插入图片描述
这符合正太分布了 amazing~
在这里插入图片描述
对于MNIST数据集,这个均值0.1307和标准差0.3018是根据整个数据集计算得来的一种经验值,不是随便取得。

神经网络喜欢0-1分布的数据,这种对于神经网络是最好的。
在这里插入图片描述
对于其他的数据集,我们可以通过numpy包来计算均值和标准差。

MNIST数据集相对于糖尿病数据集他占内存大,所以对于图像的读取更倾向于在getitem()实现时候调用transform,这个具体可以看数据集的源码。

关于归一化和标准化的细节理解:数据归一化处理transforms.Normalize()

# 准备数据集
batch_size = 64
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.1307,],std=[0.3081,])
])
train_dataset = datasets.MNIST(root='../dataset/mnist/',train=True,
                               transform=transform,download=True)
train_dataloader = DataLoader(dataset=train_dataset,shuffle=True,batch_size=batch_size)

test_dataset = datasets.MNIST(root='../dataset/mnist/',train=False,transform=transform,download=False)
test_dataloader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=True)

3.2 设计模型

全连接网络中,要求输入的是一个矩阵,因此需要将1x28x28的这个三阶的张量变成一个一阶的向量,因此将图像的每一行的向量横着拼起来变成一串,这样就变成了一个维度为1x784的向量,一共输入N个手写数图,因此,输入矩阵维度为(N,784)。这样就可以设计我们的模型,如下图所示:
在这里插入图片描述
最后一层是线性层,不做激活,后面接交叉熵损失。另外,模型采用的激活函数是Relu激活函数。N代表样本数量,batch_size。

# 模型
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.linear1 = nn.Linear(784,512)
        self.linear2 = nn.Linear(512,256)
        self.linear3 = nn.Linear(256,128)
        self.linear4 = nn.Linear(128,64)
        self.linear5 = nn.Linear(64,10)

    def forward(self,x):
        x = x.view(-1,784)
        x = f.relu(self.linear1(x))
        x = f.relu(self.linear2(x))
        x = f.relu(self.linear3(x))
        x = f.relu(self.linear4(x))
        x = self.linear5(x)           # 最后一层不接激活
        return x

model = Model()

3.3 选择恰当的损失函数和优化器

在这里插入图片描述
模型相对之前比较大了,选择了带冲量的优化器。

冲量的原理:冲量(momentum)的原理

# 损失函数和优化器
creartion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(),lr=0.01,momentum=0.5)

3.4 训练模型以及测试模型

为了方便,这里我们将一轮训练循环封装成函数;

test函数不需要反向传播,只用计算正向的就可以;

torch.max的返回值有两个,第一个是每一行的最大值是多少,第二个是每一行最大值的下标(索引)是多少;具体细节可以参考:PyTorch系列 | _, predicted = torch.max(outputs.data, 1)的理解

torch.argmax只返回一个值即每一行最大值的下标(索引)是多少;

torch.no_grad() :不需要计算梯度;

torch.size(0),torch.size(1)中0和1的意思,参考链接:torch.size()的理解

训练代码

# 训练
# 训练
def train(epoch):
    runing_loss = 0.0
    for i,data in enumerate(train_dataloader,0):
        inputs,target = data
        optimizer.zero_grad()
        # 前馈计算
        y_pred = model(inputs)
        # 计算loss
        loss = creartion(y_pred,target)
        loss.backward()
        optimizer.step()

        runing_loss += loss.item() # 取出值,否则会构建计算图
        if i % 300 == 299:
            print("{} {} loss: {:.3f}".format(epoch+1,i+1,runing_loss/300))
            runing_loss = 0.0
            

测试代码
在这里插入图片描述
在这里插入图片描述

# 测试
def test():
    correct = 0
    total = 0
    with torch.no_grad():     # 测试是不需要计算梯度的,不需要反向传播
        for data in train_dataloader:
            inputs,traget=data
            outputs = model(inputs)
            # 找出预测中最大值的下标是多少
            _,predicted = torch.max(outputs.data,dim=1) # dim=1取每行的最大值下标,dim=0取每列的最大值下标
            # predicted = torch.argmax(outputs,dim=1)
            total += traget.size(0) # target是[NX1]的,所以我们取0维的代表数量
            correct += (predicted==traget).sum().item()
        print("Accuracy on test set:{:.2f} %".format(100*correct/total))

5.完整代码

# 导入工具包
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as f
import torch.optim as optim

# 准备数据集
batch_size = 64
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.1307,],std=[0.3081,])
])
train_dataset = datasets.MNIST(root='../dataset/mnist/',train=True,
                               transform=transform,download=True)
train_dataloader = DataLoader(dataset=train_dataset,shuffle=True,batch_size=batch_size)

test_dataset = datasets.MNIST(root='../dataset/mnist/',train=False,transform=transform,download=False)
test_dataloader = DataLoader(dataset=test_dataset,batch_size=batch_size,shuffle=True)

# 模型
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.linear1 = nn.Linear(784,512)
        self.linear2 = nn.Linear(512,256)
        self.linear3 = nn.Linear(256,128)
        self.linear4 = nn.Linear(128,64)
        self.linear5 = nn.Linear(64,10)

    def forward(self,x):
        x = x.view(-1,784)
        x = f.relu(self.linear1(x))
        x = f.relu(self.linear2(x))
        x = f.relu(self.linear3(x))
        x = f.relu(self.linear4(x))
        x = self.linear5(x)           # 最后一层不接激活
        return x

model = Model()

# 损失函数和优化器
creartion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(),lr=0.01,momentum=0.5)

# 训练
def train(epoch):
    runing_loss = 0.0
    for i,data in enumerate(train_dataloader,0):
        inputs,target = data
        optimizer.zero_grad()
        # 前馈计算
        y_pred = model(inputs)
        # 计算loss
        loss = creartion(y_pred,target)
        loss.backward()
        optimizer.step()

        runing_loss += loss.item() # 取出值,否则会构建计算图
        if i % 300 == 299:
            print("{} {} loss: {:.3f}".format(epoch+1,i+1,runing_loss/300))
            runing_loss = 0.0

# 测试
def test():
    correct = 0
    total = 0
    with torch.no_grad():     # 测试是不需要计算梯度的,不需要反向传播
        for data in train_dataloader:
            inputs,traget=data
            outputs = model(inputs)
            # 找出预测中最大值的下标是多少
            _,predicted = torch.max(outputs.data,dim=1)
            # predicted = torch.argmax(outputs,dim=0)
            total += traget.size(0) # target是[NX1]的,所以我们取0维的代表数量
            correct += (predicted==traget).sum().item()
        print("Accuracy on test set:{:.2f} %".format(100*correct/total))


if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test()

输出结果:
在这里插入图片描述


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值