数字识别
以下是个图片数字识别的例子,数据集来自torchvision的MNIST,使用交叉熵计算损失,使用两层的全连接进行训练。
1. 数据集的获取
获取MNIST的数据集:
dset.MNIST(root, train=True, transform=None, target_transform=None, download=False)
参数说明: - root : processed/training.pt
和 processed/test.pt
的主目录 - train : True
= 训练集, False
= 测试集 - download : True
= 从互联网上下载数据集,并把数据集放在root
目录下. 如果数据集之前下载过,将处理过的数据(minist.py中有相关函数)放在processed
文件夹下。
所有代码演示
def get_dataloader(train= True, batch_size=BATCH_SIZE):
transformFn = Compose([
ToTensor(),
Normalize(mean=(0.1307,), std=(0.0381,))
])
dataset = MNIST(root="./dataset", train=train, transform=transformFn)
data_loader = DataLoader(dataset, batch_size=batch_size, shuffle= True) ##将获取的数据集实例化一个数据加载器
return data_loader
dataloader = get_dataloader()
2. 模型的损失函数
当输出只有2个分类时,我们使用sigmoid计算对数似然损失
- 在2分类时只有正类和负类,正类概率为P(x) = 1 1 + e − x \frac {1} {1+e^{-x}} 1+e−x1,那负类概率即为1-P(x)
- 接下来通过 $-\sum yln(P(x)) $(交叉熵,经常用于评估分类的预测效果)
当输出为多分类时:
- 跟2分类唯一区别就是不能用sigoid函数计算样本的概率,这时候改为用softmax函数。
- softmax需要计算每类样本的概率,而sigmoid只需要一次
softmax的公式为:
P ( x i ) = e x i ∑ e x j , j = 1 , 2... n P(x_i) = \frac {e^{x_i}}{\sum e^{x_j}}, j=1,2...n P(xi)=∑exjexi,j=1,2...n
对于softmax的结果,属于[0, 1]区间
从而通过交叉熵函数计算损失
l
o
s
s
=
−
∑
Y
l
o
g
(
P
)
,
P
=
∑
i
=
1
n
P
(
x
i
)
loss = -\sum Ylog(P)\quad ,P=\sum_{i=1}^n P(x_i) \\
loss=−∑Ylog(P),P=i=1∑nP(xi)
实现交叉熵损失的函数方式:
## 1. 直接创建交叉熵损失计算器
criterion = nn.CrossEntropyLoss()
loss = criterion(input, target)
## 2. 直接自己计算
## 计算softmax并取对数
output = F.log_softmax(x, dim=-1)
# 计算loss带全损失
# F.nll_loss(x, y) = -∑ yixi,此处也即是将log(P)作为xi,真实值当做yi
loss = F.nll_loss(output, target)
3. 构建模型
class MnistMdl(nn.Module):
def __init__(self):
super().__init__()
self.md1 = nn.Linear(1*28*28, 28)
# self.bn1 = nn.BatchNorm1d(28)
self.md2 = nn.Linear(28, 10)
def forward(self, input):
"""
:param input: [batch_size, 1, 28, 28]
:return:
"""
# 1.修改输入的形状
x = input.view([-1, 1*28*28])
# 2.进行全连接
x = self.md1(x)
# x = self.bn1(x)
# 3.激活函数处理
x = F.relu(x)
# 4.输出层
out = self.md2(x)
return F.log_softmax(out, -1) # out为128*10的矩阵, dim=-1即对10列的数据进行概率计算(详见下的损失函数)
4. 模型的保存与加载
模型的加载
if os.path.exists("./dataset/module/model.pkl"):
print("loading the history file!!!")
model.load_state_dict(torch.load("./dataset/module/model.pkl"))
optimizer.load_state_dict(torch.load("./dataset/module/optimizer.pkl"))
模型的保存
torch.save(model.state_dict(), "./dataset/module/model.pkl")
torch.save(optimizer.state_dict(), "./dataset/module/optimizer.pkl")
5. 模型的测试与评估
-
测试无需对算式进行追踪:with torch.no_grad():
-
准确率 :
a. 获取预测值:tensor.max(dim=-1)[-1]
b. tensor.eq(tensor2).float().mean()
6. 程序代码演示:
from torch.optim import Adam
import numpy as np
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import Compose, ToTensor, Normalize
from torch import nn
import torch.nn.functional as F
import torch
import os
BATCH_SIZE = 128
TEST_SIZE = 1000
# 1.获取数据集
def get_dataloader(train= True, batch_size=BATCH_SIZE):
transformFn = Compose([
ToTensor(),
Normalize(mean=(0.1307,), std=(0.0381,))
])
dataset = MNIST(root="./dataset", train=train, transform=transformFn)
data_loader = DataLoader(dataset, batch_size=batch_size, shuffle= True)
return data_loader
dataloader = get_dataloader()
# 1. 构建模型
class MnistMdl(nn.Module):
def __init__(self):
super().__init__()
self.md1 = nn.Linear(1*28*28, 28)
# self.bn1 = nn.BatchNorm1d(28)
self.md2 = nn.Linear(28, 10)
def forward(self, input):
"""
:param input: [batch_size, 1, 28, 28]
:return:
"""
# 1.修改输入的形状
x = input.view([-1, 1*28*28])
# 2.进行全连接
x = self.md1(x)
# x = self.bn1(x)
# 3.激活函数处理
x = F.relu(x)
# 4.输出层
out = self.md2(x)
return F.log_softmax(out, -1) # out为128*10的矩阵, dim=-1即对10列的数据进行概率计算
model = MnistMdl()
optimizer = Adam(model.parameters(), lr=0.001)
if os.path.exists("./dataset/module/model.pkl"):
print("loading the history file!!!")
model.load_state_dict(torch.load("./dataset/module/model.pkl"))
optimizer.load_state_dict(torch.load("./dataset/module/optimizer.pkl"))
def train(epoch):
"""实现训练的过程"""
dataloader = get_dataloader()
for idx, (input, target) in enumerate(dataloader):
optimizer.zero_grad() # 梯度清零
output = model(input)
loss = F.nll_loss(output, target) # 加权平均获得损失
loss.backward()
optimizer.step()
if idx%50 == 0:
# print(epoch, idx, loss.data)
print("Train Epoch : ", epoch, ", 已完成 ", idx,
"(",'%.2f'%(100.*idx/len(dataloader)),"%)",
" ,损失为 : " , '%.3f'%loss.item())
if idx%100 == 0:
torch.save(model.state_dict(), "./dataset/module/model.pkl")
torch.save(optimizer.state_dict(), "./dataset/module/optimizer.pkl")
def test():
""" 测试的过程"""
test_dataloader = get_dataloader(False, TEST_SIZE)
loss_list = []
acc_list = []
for idx, (input, target) in enumerate(test_dataloader):
with torch.no_grad():
output = model(input)
# 计算交叉熵损失
loss = F.nll_loss(output, target)
loss_list.append(loss)
# 计算准确率
# output:[batch_size, 10] target:[batch_size]
y_pred = output.max(dim=-1)[-1] # -1取该数字的索引也就是位置
acc = y_pred.eq(target).float().mean()
acc_list.append(acc)
print("平均精确率为 : ", np.mean(acc_list)," ,而平均损失为: ", np.mean(loss_list) )
if __name__ == '__main__':
test()
for i in range(3):
train(i)
test()