深度学习(12)--Mnist分类任务

一.Mnist分类任务流程详解

1.1.引入数据集

Mnist数据集是官方的数据集,比较特殊,可以直接通过%matplotlib inline自动下载,博主此处已经完成下载,从本地文件中引入数据集。

设置数据路径

from pathlib import Path

# 设置数据路径
# PATH = Path("data/minst")
DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

# PATH.mkdir(parents=True, exist_ok=True)  # 父目录不存在时创建父目录

'''
parents:如果父目录不存在,是否创建父目录。
exist_ok:只有在目录不存在时创建目录,目录已存在时不会抛出异常。
'''

读取数据

import pickle
import gzip

# 读取数据
'''
gzip.open的作用是解压gzip文件
with gzip.open(PATH.as_posix(), "rb") as f:
    ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
'''
# rb表示以二进制格式打开一个文件用于只读
# 打开PATH路径的文件用以接下来的操作
# 保存数据的文件类型为pickle,所以用pickle.load打开文件,文件此处设置的别名为f
with open(PATH.as_posix(), "rb") as f:
    ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")

'''
as.posix()的作用:
#返回使用斜杠(/)分割路径的字符串
#将所有连续的正斜杠、反斜杠,统一修改为单个正斜杠
#相对路径 './' 替换为空,'../' 则保持不变。
'''

测试引入数据集是否成功

from matplotlib import pyplot
import numpy as np

# 测试数据集是否导入成功
pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)

1.2.数据类型转换

数据需要转换成tensor类型才能参与后续建模训练

import torch

# 通过map映射,将x_train等数据全都转为torch.tensor类型。tensor类型才能参与后续建模训练
x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)

测试数据类型是否转换成功

n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())

数据均为tensor类型,转换成功

1.3.设置损失函数

import torch.nn.functional as F

# 设置损失函数,此处使用的损失函数为交叉熵
loss_func = F.cross_entropy 

测试损失函数

手动设置初始权重和偏置值进行测试,真实情况下系统会自动帮我们初始化

bs = 64
xb = x_train[0:bs]  # a mini-batch from x ,xb是x_train中0~64项的数
yb = y_train[0:bs]

# 实际操作中模型会自动定义权重参数,不用我们手动设置
# 因为输入数据是784x1个像素点,而最后得到的是十个类别,所以权重矩阵的大小为784x10。
weights = torch.randn([784, 10], dtype = torch.float,  requires_grad = True) 
bs = 64
# bias矩阵的大小取决于最后的类别数量,此处的bias矩阵为10x1
bias = torch.zeros(10, requires_grad=True)

def model(xb):
    return xb.mm(weights) + bias  # .mm()是矩阵相乘 .mul()则是对应位相乘

# 损失函数是用来度量模型的预测值f(x)与真实值Y的差异程度的运算函数,此处model(xb)得到的是经过权重计算的预测值,yb是真实值。给损失函数传入的参数即为预测值和真实值。
print(loss_func(model(xb), yb))

1.4.神经网络构造 

此处所选用的是传统神经网络完成Minist分类任务

from torch import nn

# 创建一个模型类,注意一定要继承于nn.Module(取决于你要创建的网络类型)
class Mnist_NN(nn.Module):
    def __init__(self):
        # 调用父类的构造函数
        super().__init__()
        # 创建两个隐层 由784->128->256->10
        self.hidden1 = nn.Linear(784, 128)
        self.hidden2 = nn.Linear(128, 256)
        # 创建输出层,由256->10,即最后输出十个类别
        self.out  = nn.Linear(256, 10)
        self.dropout = nn.Dropout(0.5)

    # torch框架需要自己定义前向传播,反向传播由框架自己实现
    # 传入的x是一个batch x 特征值
    def forward(self, x):
        # x经过第一个隐层
        x = F.relu(self.hidden1(x))
        # x经过dropout层
        x = self.dropout(x)
        # x经过第二个隐藏
        x = F.relu(self.hidden2(x))
        # x经过dropout层
        x = self.dropout(x)
        # x经过输出层
        x = self.out(x)
        return x
        

测试构造的神经网络

net = Mnist_NN()
print(net)

查看网络中构建好的权重和偏置项 

for name, parameter in net.named_parameters():
    print(name, parameter,parameter.size())

  

1.5.使用TensorDataset和DataLoader简化数据 

from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader

# TensorDataset获取数据,再由DataLoader打包数据传给GPU(包的大小位batch_size)
# 训练集一般打乱顺序,验证集不打乱顺序
train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)

def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )

1.6.模型训练

优化器设置

from torch import optim
def get_model():
    model = Mnist_NN() # 使用先前创建的类构造一个网络
    return model, optim.SGD(model.parameters(), lr=0.001) 
    # 返回值为模型和优化器
 # 优化器的设置,optim.SGD(),参数分别为:要优化的参数、学习率

def loss_batch(model, loss_func, xb, yb, opt=None):
    # 计算损失,参数为预测值和真实值
    loss = loss_func(model.forward(xb), yb)  # 预测值由定义的前向传播过程计算处

    # 如果存在优化器
    if opt is not None: 
        loss.backward()  # 反向传播,算出更新的权重参数
        opt.step()  # 执行backward()计算出的权重参数的更新
        opt.zero_grad()  # torch会进行迭代的累加,通过zero_grad()将之前的梯度清空(不同的迭代之间应当是没有关系的)

    return loss.item(), len(xb)

常用优化器:

  • 随机梯度下降(SGD, stochastic gradient descent)
  • SGDM(加入了一阶动量)
  • AdaGrad(加入了二阶动量)
  • RMSProp
  • Adam

模型训练

import numpy as np

#epoch和batch的关系,
#eg:有10000个数据,batch=100,则一个1epoch需要训练100个batch(1一个epoch就是训练整个数据一次)
# 定义训练函数,传入的参数分别为:迭代的次数、模型、损失函数、优化器、训练集、验证集
def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    for step in range(steps):
        # 训练模式:
        model.train()  
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)  # 得到由loss_batch更新的权重值
        # 验证模式:
        model.eval()   
        with torch.no_grad():  # 没有梯度,即不更新权重参数
            # zip将两个矩阵配对,例如两个一维矩阵配对成一个二维矩阵,下述情况中即为一个losses对应一个nums -> [(losses,nums)]。zip*是解包操作,即将二维矩阵又拆分成一维矩阵,并返回拆分得到的一维矩阵。
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)  # 计算平均损失:对应的损失值和样本数相乘的总和 / 总样本数
        print('当前step:'+str(step), '验证集损失:'+str(val_loss))

二.完整代码 

from pathlib import Path
import pickle
import gzip

from matplotlib import pyplot
import numpy as np

import torch

import torch.nn.functional as F

from torch import nn

from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader

from torch import optim

import numpy as np


# 设置数据路径
DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist.pkl"

# PATH.mkdir(parents=True, exist_ok=True)

'''
parents:如果父目录不存在,是否创建父目录。
exist_ok:只有在目录不存在时创建目录,目录已存在时不会抛出异常。
'''

# 读取数据
'''
gzip.open的作用是解压gzip文件
with gzip.open(PATH.as_posix(), "rb") as f:
    ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
'''
# rb表示以二进制格式打开一个文件用于只读
# 打开PATH路径的文件用以接下来的操作
# 保存数据的文件类型为pickle,所以用pickle.load打开文件,文件此处设置的别名为f
with open(PATH.as_posix(), "rb") as f:
    ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")

'''
as.posix()的作用:
#返回使用斜杠(/)分割路径的字符串
#将所有连续的正斜杠、反斜杠,统一修改为单个正斜杠
#相对路径 './' 替换为空,'../' 则保持不变。
'''

'''
# 测试数据集是否导入成功
pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)
'''

# 数据类型转换为torch

# 通过map映射,将x_train等数据全都转为torch.tensor类型。tensor类型才能参与后续建模训练
x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)



# 测试数据类型是否转换成功
'''
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())
'''

# 设置损失函数
loss_func = F.cross_entropy


bs = 64
xb = x_train[0:bs]  # a mini-batch from x ,xb是x_train中0~64项的数
yb = y_train[0:bs]

''' 
# 手动初始化进行测试

# 实际操作中模型会自动定义权重参数,不用我们手动设置
# 因为输入数据是784x1个像素点,而最后得到的是十个类别,所以权重矩阵的大小为784x10。
weights = torch.randn([784, 10], dtype = torch.float,  requires_grad = True)
bs = 64
# bias矩阵的大小取决于最后的类别数量,此处的bias矩阵为10x1
bias = torch.zeros(10, requires_grad=True)

def model(xb):
    return xb.mm(weights) + bias  # .mm()是矩阵相乘 .mul()则是对应位相乘

# 损失函数是用来度量模型的预测值f(x)与真实值Y的差异程度的运算函数,此处model(xb)得到的是经过权重计算的预测值,yb是真实值。给损失函数传入的参数即为预测值和真实值。
print(loss_func(model(xb), yb))
'''

# 创建一个模型类,注意一定要继承于nn.Module(取决于你要创建的网络类型)
class Mnist_NN(nn.Module):
    def __init__(self):
        # 调用父类的构造函数
        super().__init__()
        # 创建两个隐层 由784->128->256->10
        self.hidden1 = nn.Linear(784, 128)
        self.hidden2 = nn.Linear(128, 256)
        # 创建输出层,由256->10,即最后输出十个类别
        self.out = nn.Linear(256, 10)
        self.dropout = nn.Dropout(0.5)

    # torch框架需要自己定义前向传播,反向传播由框架自己实现
    # 传入的x是一个batch x 特征值
    def forward(self, x):
        # x经过第一个隐层
        x = F.relu(self.hidden1(x))
        # x经过dropout层
        x = self.dropout(x)
        # x经过第二个隐藏
        x = F.relu(self.hidden2(x))
        # x经过dropout层
        x = self.dropout(x)
        # x经过输出层
        x = self.out(x)
        return x


'''
# 测试构造的网络模型
net = Mnist_NN()
print(net)

打印权重和偏置项
for name, parameter in net.named_parameters():
    print(name, parameter,parameter.size())
'''

# TensorDataset获取数据,再由DataLoader打包数据传给GPU
# 训练集一般打乱顺序,验证集不打乱顺序
train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)

def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )


# 优化器设置
def get_model():
    model = Mnist_NN()
    return model, optim.SGD(model.parameters(), lr=0.001)
    # 优化器的设置,optim.SGD(),参数分别为:要优化的参数、学习率

def loss_batch(model, loss_func, xb, yb, opt=None):
    # 计算损失,参数为预测值和真实值
    loss = loss_func(model.forward(xb), yb)  # 预测值由定义的前向传播过程计算处

    # 如果存在优化器
    if opt is not None:
        loss.backward()  # 反向传播,算出更新的权重参数
        opt.step()  # 执行backward()计算出的权重参数的更新
        opt.zero_grad()  # torch会进行迭代的累加,通过zero_grad()将之前的梯度清空(不同的迭代之间应当是没有关系的)

    return loss.item(), len(xb)

# 模型训练
# epoch和batch的关系,
# eg:有10000个数据,batch=100,则一个1epoch需要训练100个batch(1一个epoch就是训练整个数据一次)
# 定义训练函数,传入的参数分别为:迭代的次数、模型、损失函数、优化器、训练集、验证集
def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    for step in range(steps):
        # 训练模式:
        model.train()
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)  # 得到由loss_batch更新的权重值
        # 验证模式:
        model.eval()
        with torch.no_grad():  # 没有梯度,即不更新权重参数
            # zip将两个矩阵配对,例如两个一维矩阵配对成一个二维矩阵,下述情况中即为一个losses对应一个nums -> [(losses,nums)]。zip*是解包操作,即将二维矩阵又拆分成一维矩阵,并返回拆分得到的一维矩阵。
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)  # 计算平均损失:对应的损失值和样本数相乘的总和 / 总样本数
        print('当前step:'+str(step), '验证集损失:'+str(val_loss))

# 输出
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(25, model, loss_func, opt, train_dl, valid_dl)

# 准确率的计算
correct = 0
total = 0
for xb,yb in valid_dl:
    outputs = model(xb)
    _, predicted = torch.max(outputs.data,1)  # 返回最大的值和对应的列索引(列索引在此处就是对应的类别)    (, 0)则返回行索引
    total += yb.size(0)  # yb的样本数
    correct += (predicted == yb).sum().item( ) # .sum()返回验证正确了的样本数,item()从tensor数据类型中取值,方便后续的画图等(tensor数据类型不好画图)

    print('Accuracy of the network on the 10000 test image: %d %%' %(100*correct/total))

三.输出结果

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-02-03 07:26:02       17 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-03 07:26:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-03 07:26:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-03 07:26:02       18 阅读

热门阅读

  1. 微信小程序如何控制元素的显示和隐藏

    2024-02-03 07:26:02       25 阅读
  2. Acwing---2816. 判断子序列

    2024-02-03 07:26:02       27 阅读
  3. Go语言中...(三个点)的使用几个常见情况

    2024-02-03 07:26:02       25 阅读
  4. qt编程---->qml

    2024-02-03 07:26:02       29 阅读
  5. node版本对应的npm版本

    2024-02-03 07:26:02       30 阅读
  6. PyQt子线程处理业务事件

    2024-02-03 07:26:02       31 阅读
  7. Ubuntu 安装 Docker 详细步骤

    2024-02-03 07:26:02       34 阅读
  8. nuxt.js中使用axios以及二次封装

    2024-02-03 07:26:02       29 阅读
  9. WPF简介

    WPF简介

    2024-02-03 07:26:02      30 阅读