文章目录
1. 输出多个类别
多分类问题不同,以MNIST数据集为例,需要计算出每个方框属于不同数字的10个概率,这10个概率需要满足:
这样才能满足多分类的离散分布;而对于二分类问题,我们只需要求出P(y=1)的概率就可以,因为P(y=0)的概率我们可以通过1-(y=1)求得。
输出之间要有竞争性且满足分布的要求。
对于解决多分类问题,我们不在采样sigmoid作为最后一层,因为sigmoid层不能满足上面的两个条件,而这里采用了softmax层。
softmax层能解决多分类问题,它主要解决的问题就是使得前面输出的正值和负值都统一成正值,此外可以使得多个输出的和为1。
2. softmax层
softmax满足两个要求:
- 让每一个输出都大于0
- 让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层。
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()
输出结果: