Mnist数据集分类任务试用

发布时间 2023-08-20 18:49:46作者: 王昱棋

学习方法

边用边学,torch只是个工具,用起来,查的过程才是学习的过程
直接上案例来学习,先跑起来,遇到问题就地解决

使用jupiter的方式,来实现

查看torch版本

import torch
print(torch.__version__)

1、拿到数据集

from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

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

URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"

if not (PATH / FILENAME).exists():
        content = requests.get(URL + FILENAME).content
        (PATH / FILENAME).open("wb").write(content)
import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
1.1查看数据样例
y_train[:10] # 打印前10个样本案例
array([5, 0, 4, 1, 9, 2, 1, 3, 1, 4], dtype=int64)
x_train.shape # 这里是一个矩阵,行是样本的个数,有5万个样本,每个样本有784个像素点(28*28)
(50000, 784)

x_train[0]# 选择其中的一个样本,都是0,是因为周围都是黑色的,没有特征,中间白色的的数据,表示数据 是一个一维数组

x_train[0].shape # 是784个点

from matplotlib import pyplot
import numpy as np
# 数据展示,多打印数据
# reshape 将 784个点转换成 28*28的矩阵
pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
#print(x_train.shape)
  • torch 在 GPU 中运行 tensor(张量,矩阵)
  • numpy 在 CPU 中运行 数组 ndarray
  • 数组的形式在torch中用不了
import torch
x_train_test, y_train_test, x_valid_test, y_valid_test = map( # map是一个映射 将数组的形式,转换成 tensor 的格式,
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
  • 查看数据
y_train_test[:10] # 看下 y_train_test的前10个数据,是一个tensor的格式
y_valid_test[0]
x_valid_test[10] # 都是tensor的格式的数据

2、将数据读入到内存中,并转成tensor

import torch

x_train, y_train, x_valid, y_valid = map( # map是一个映射 将数组的形式,转换成 tensor 的格式,
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape # n是样本的个数,像素点或者是特征的个数
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的格式
tensor([[0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.]]) tensor([5, 0, 4,  ..., 8, 4, 8])
torch.Size([50000, 784])
tensor(0) tensor(9)

torch.nn.functional 很多层和函数在这里都会见到

torch.nn.functional中有很多功能,后续会常用的。那什么时候使用nn.Module,什么时候使用nn.functional呢?一般情况下,如果模型有可学习的参数,最好用nn.Module,其他情况nn.functional相对更简单一些
import torch.nn.functional as F # nn.functional是测试使用,不适合做实际代码的训练,后续调用其他的模块

  • 需要传两个值,一个预测值,一个标签
import torch.nn.functional as F # nn.functional是测试使用,不适合做实际代码的训练,后续调用其他的模块
# 需要传两个值,一个预测值,一个标签

loss_func = F.cross_entropy # 现成的交叉熵损失函数 即分类损失函数

def model(xb): # 输入数据
    return xb.mm(weights) + bias
bs = 64 # 指定 batchsize 64 一次要训练多少样本
xb = x_train[0:bs]  # a mini-batch from x ,训练集取前64个
yb = y_train[0:bs] # 标签也是取前64个
# WX+B
# 权重参数都是随机初始化出来的
# 如果是 784 个特征做10分类的情况下:W1:784*10  B:10
# X 为 64 * 784
weights = torch.randn([784, 10], dtype = torch.float,  requires_grad = True) # w需要更新,需要梯度
bs = 64
bias = torch.zeros(10, requires_grad=True) # 偏置,b对结果的影响比较小

print(loss_func(model(xb), yb))
  • 打印其中的随机函数
    torch.randn([784, 10], dtype = torch.float, requires_grad = True) # 每次的结果不一样,随机的初始化

3、创建一个model来更简化代码(python类的形式来实现)

全连接的网络结构,
FC=WX+B 每一层都是这样
输入batch * 784
W=784* 128 ;b=128
第一层: 128个特征
w2=128*10 b=10
输出 10
并不是一个特定的值,属于各个类别的概率,各个值都有的,取类别概率最大的

  • 必须继承nn.Module且在其构造函数中需调用nn.Module的构造函数
  • 无需写反向传播函数,nn.Module能够利用autograd自动实现反向传播
  • Module中的可学习参数可以通过named_parameters()或者parameters()返回迭代器
from torch import nn # nn是神经网络的包

class Mnist_NN(nn.Module):#定义一个类,类的名字可以改,继承是不可以改的括号里面的东西
    def __init__(self): # 构造函数,需要提前定义好,现在比较简单,后续会比较复杂
        super().__init__()
        self.hidden1 = nn.Linear(784, 128) # 第一个隐层,wx+b,指定好输入784个像素点和输出128个特征
        self.hidden2 = nn.Linear(128, 256) # 第二个隐层,输入128个特征(就是第一个隐层的输出),和输出256个特征
        self.out  = nn.Linear(256, 10)# 全连接层:输入256个特征(就是第二个隐层的输出),和输出10个特征
        # drop out 为了过拟合的问题,只使用部分特征点,神经元个数过多,只使用部分神经元,随机杀死部分神经元,
        self.dropout = nn.Dropout(0.5) # 50%是比较常见的

    def forward(self, x):# 前向传播 需要自己定义,反向传播是自动的,输入是x,batch数据 64*784
        # 64*784 -h1、dropout->64*128-h2、dropout->64*256->256*10
        x = F.relu(self.hidden1(x))
        x = self.dropout(x)
        x = F.relu(self.hidden2(x))
        x = self.dropout(x)
        x = self.out(x)
        return x # 返回输出结果
        # 上面已经把权重参数设置好了
  • 查看网络结构的情况
net = Mnist_NN()
print(net)
Mnist_NN(
  (hidden1): Linear(in_features=784, out_features=128, bias=True)
  (hidden2): Linear(in_features=128, out_features=256, bias=True)
  (out): Linear(in_features=256, out_features=10, bias=True)
  (dropout): Dropout(p=0.5, inplace=False)
)
  • 可以打印我们定义好名字里的权重和偏置项
for name, parameter in net.named_parameters():# named_parameters 打印相关的信息
    print(name, parameter,parameter.size())# name 是哪一层,权重参数值,权重参数矩阵的大小

使用TensorDataset和DataLoader来简化

  • 测试、了解tensor
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
# minst 数据集中这样使用,其他的数据集中不会这样用,了解即可,后续搞自定义dataloader
# data 处理数据集,为进入GPU做准备,打好batch数据集

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True) # shuffle为了打乱顺序,
# train 训练数据集 需要打乱数据
# val 验证集:测试数据集 不需要打乱数据

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)
  • 转成tensor
def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )
  • 一般在训练模型时加上model.train(),这样会正常使用Batch Normalization和 Dropout
  • 测试的时候一般选择model.eval(),这样就不会使用Batch Normalization和 Dropout
import numpy as np

def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    # steps 数据集迭代多少次 ,model 构造一个model,loss_func,opt优化器,train_dl,valid_dl
    for step in range(steps):# epoch的概念,例子:10000数据,epoch有100iter(迭代),一个batch有100个数据,
        model.train()# 训练模式,更新每一层的权重和偏置w和b
        for xb, yb in train_dl: # 这是一个dataloader,一个一个的返回,里面是一个batch
            loss_batch(model, loss_func, xb, yb, opt)

        model.eval()# 验证模式,不更新w和b,只需要得到一个结果
        with torch.no_grad():# 不更新参数
            losses, nums = zip( # 将返回结果的k v解开,k为损失,v为样本数量
                *[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 torch import optim
def get_model():# 优化器
    model = Mnist_NN()
    return model, optim.Adam(model.parameters(), lr=0.001) # SGD与Adam,Adam的效果更好一点儿
# 第一个参数位置,要更新哪些参数,所有参数都要更新,
# 第二个参数,学习率,小一点儿,迭代次数多一点儿
  • 损失函数代码
# 计算损失
# 更新参数 w 和 b
def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)# 预测值与标签值比较

    if opt is not None:
        loss.backward() # 反向传播 每一组的权重参数就都算出来了,算梯度
        opt.step() # 更新权重参数
        opt.zero_grad() # 模型迭代多次,每次迭代之间有关系吗?没有,每次迭代都是独立的
        # torch 会对每次迭代的值进行记录,并累加 第一次 -0.1 第二次 0.3,会记录0.3-0.1=0.2,这样是不对的,需要把之前的梯度清空

    return loss.item(), len(xb) # len表示训练样本有多少个,
  • 将代码合并到一起
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)# 返回dataloader
model, opt = get_model()
fit(25, model, loss_func, opt, train_dl, valid_dl)# 需要迭代多少个epoch
  • 模型正确率代码
correct = 0
total = 0
for xb, yb in valid_dl: # 去验证集去出数据
    outputs = model(xb)
    _, predicted = torch.max(outputs.data, 1) # 最大的值和索引
    # 沿着哪个维度去做计算,对每个样本有10个类别的预测的概率值,想看每一个样本他在10个类别当中,哪个值预测的最大,就沿着哪个维度,沿着1这个维度,
    #要计算每个样本,要算概率值哪个大,而不是这10000个样本,对每个样本对应的概率值,每个样本概率值的维度,0是比较不同样本之间的东西
    total += yb.size(0)
    correct += (predicted == yb).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' %(
100*correct/total
))

优化器对比

  • 20次SGD 87%
  • 20次Adam 97%

zip用法 就是把数据打包配对到一起

a = [1,2,3]
b = [4,5,6]
zipped = zip(a,b)
print(list(zipped))
a2,b2 = zip(*zip(a, b))
print(a2)
print(b2)