Mnist手写数字识别

手撕代码001-----Mnist手写数字识别

from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"  # 斜杠 / 操作符用于拼接路径,比如创建子路径
# Path.mkdir 创建目录
# mkdir(parents=True, exist_ok=True)
# parents:如果父目录不存在,是否创建父目录。
# exist_ok:只有在目录不存在时创建目录,目录已存在时不会抛出异常
PATH.mkdir(parents=True, exist_ok=True)

URL = "http://deeplearning.net/data/mnist/"   # 下载 mnist 数据集的地址
Filename = "mnist.pkl.gz"

if not (PATH / Filename).exists(): # 如果"mnist.pkl.gz"文件不存在
        req = requests.get(URL + Filename)  # 请求下载数据集地址的Filename文件
        # 把Reponse对象的内容以二进制数据的形式返回
        # .content 返回的是可以自由加工的(以你想要的编码格式来进行编码)的字节串,
        # 是只高于二进制数据的一种数据存储单位。
        pic = req.content
        # (PATH / Filename) :是你下载的好的数据,存储在PATH(date/mnist路径下的文件1=="mnist.pkl.gz")
        (PATH / Filename).open("wb").write(pic)
        # 文件1 "mnist.pkl.gz",打开
        # Path.open("wb")  w表示写入
        # w:open for writing写入, truncating the file first【截断】
        # b:binary mode 二进制方式
        # write(pic):写入pic


import pickle
import gzip

# from pathlib import Path
# 路径分隔符表示函数: Path.as_posix()
# 作用:将 Windows目录中的路径分隔符 ‘\’ 改为 Unix 样式 ‘/’。
# 将所有连续的正斜杠、反斜杠,统一修改为单个正斜杠
with gzip.open((PATH / Filename).as_posix(), "rb") as f:
# 该 pickle 模块实现了用于序列化和反序列化 Python 对象结构的二进制协议
# pickle.load 反序列化
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
# x_train.shape (50000, 784)
# y_train.shape (50000,)
# x_valid.shape (10000, 784)
# y_valid.shape (10000, )
from matplotlib import pyplot as plt
import numpy as np
# .reshape 将其形成一个28*28数组
# x_train[0].shape = (784, ) 784是mnist数据集每个样本的像素点个数
plt.imshow(x_train[0].reshape((28, 28)), cmap="gray")
plt.show()
print(x_train.shape)

import torch
# 注意数据需转换成tensor才能参与后续建模训练
# pytorch 的 map()函数接收两个参数,一个是函数,一个是Iterable,【可以进行数据格式转换】
# map将传入的函数【数据转换成torch.tensor】依次作用到序列的每个元素,并把结果作为新的Iterator返回
x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape  # n=50000 c= 784
#  x_train.shape == torch.Size([50000, 784])
x_train, x_train.shape, y_train.min(), y_train.max()
# y_train.min()==tensor(0)  y_train.max()==tensor(9)
# print(x_train, y_train)
# print(x_train.shape)
# print(y_train.min(), y_train.max())

# 那什么时候使用nn.Module,什么时候使用nn.functional呢?
# 一般情况下,如果模型有可学习的参数,最好用nn.Module,其他情况nn.functional相对更简单一些
import torch.nn.functional as F
# 包含 torch.nn 库中所有函数
# 同时包含大量 loss 和 activation function
loss_func = F.cross_entropy

def model(xb):
    return xb.mm(weights) + bias
# weights.shape [784, 10] 分成10类,总共784个像素点【特征点】

bs = 64  # batch_size
# xb.shape = torch.Size([64, 784])
xb = x_train[0:bs]  # a mini-batch from x
yb = y_train[0:bs]
# 权重随机初始化   weights 需要对权重更新的,追踪该参数, 要自动求梯度的
# dtype = torch.float:返回张量所需的数据类型
# requires_grad=True  autograd是否应该记录对返回张量的操作(说明当前量是否需要在计算中保留对应的梯度信息)
# 返回一个符合均值为0,方差为1的正态分布(标准正态分布)中填充随机数的张量
weights = torch.randn([784, 10], dtype = torch.float,  requires_grad = True)
bs = 64
# torch.zeros 张量全部赋值0,且 权重需要更新的,追踪该参数,要自动求梯度的
bias = torch.zeros(10, requires_grad=True)

print(loss_func(model(xb), yb))

# 无需写反向传播函数,nn.Module能够利用autograd自动实现反向传播
# torch.nn 实现一个简单的线性回归模型,
# 主要是根据 nn.Module 和 torch.nn 中的 Linear 模型, 来创建一个自己的线性模型.
from torch import nn
# 可以通过继承 nn.Module(它本身是一个类并且能够跟踪状态)建立神经网络。
# 我们想要建立一个包含权重、偏置和前向传播的方法的类

class Mnist_NN(nn.Module):
    # 创建Mnist_NN类  子类:Mnist_NN 父类:nn.Module
    def __init__(self):
        # 初始化属性hidden1、hidden2、out
        super(Mnist_NN,self).__init__()
        # super().__init__() 可行
        # 这里__init__()函数里面的值要写,不然就被父类的默认值覆盖了!
        self.hidden1 = nn.Linear(784, 128)  # 两层全连接神经网络
        self.hidden2 = nn.Linear(128, 256)
        self.out = nn.Linear(256, 10)
# class 子类(父类):
# 	def 子类的方法(self, 参数):
# 		父类.父类的方法(self, 参数)

    def forward(self, x):
        x = F.relu(self.hidden1(x))  # 前向传播的激活函数relu
        x = F.relu(self.hidden2(x))
        x = self.out(x)
        return x  # 传出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)
 )
'''
# net.named_parameters() 可以查看神经网络的参数信息,用于更新参数,或者用于模型的保存
# list(net.named_parameters()) 查看神经网络的参数信息
# 'hidden1.weight' 'hidden1.bias'
# 'hidden2.weight' 'hidden2.bias'
# 'out.weight' 'out.bias'  以上6组
# name, parameter 名字 参数tensor值
# 'hidden1.weight' 以及对应的tensor值
for name, parameter in net.named_parameters():
    print(name, parameter,parameter.size())

# 使用TensorDataset和DataLoader来简化
# torch.utils.data.DataLoader(): 构建可迭代的数据装载器, 我们在训练的时候,每一个for循环,
# 每一次iteration,就是从DataLoader中获取一个batch_size大小的数据的
from torch.utils.data import TensorDataset  # 数据预处理
from torch.utils.data import DataLoader  # DataLoader :(构建可迭代的数据装载器)

train_ds = TensorDataset(x_train, y_train)  # 把数据放在数据库中
# 从dataset数据库中每次抽出batch_size = bs = 64个数据
train_dl = DataLoader(train_ds,  # 决定数据从哪读取以及如何读取
                      batch_size=bs,
                      shuffle=True  # 每个epoch 将数据打乱
                      )

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)  # 为什么2倍的batch

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

from torch import optim

def get_model():
    model = Mnist_NN()
    return model, optim.SGD(model.parameters(), lr=0.001)
# optim.SGD “随机梯度下降”,但是本质上还是还是实现的批量梯度下降,即用全部样本梯度的均值更新可学习参数

'''
- 一般在训练模型时加上model.train(),这样会正常使用Batch Normalization和 Dropout
- 测试的时候一般选择model.eval(),这样就不会使用Batch Normalization和 Dropout    '''
import numpy as np

# 定义更新权值的训练函数
# 把整个训练循环封装成fit()函数,以便后续再次调用它
def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    for step in range(steps):  # 开始step=0
        # 训练开始之前写上 model.train() ,在测试时写上 model.eval()
        # 训练过程中会在程序上方添加一句model.train(),作用是:启用 batch normalization 和 dropout
        model.train()
        # xb.shape = torch.Size([64, 784])  yb.shape =torch.Size([64])
        for xb, yb in train_dl:  # 对当前batch进行损失率及参数更新
            loss_batch(model, loss_func, xb, yb, opt)  # 计算损失率以及进行参数更新
        # 一个step【或epoch】,就是把所有的训练或验证的数据用一遍

        # 加入验证集
        model.eval()  # 评估模型
        # 使用with torch.no_grad():
        # 表明当前计算不需要反向传播,所有计算得出的tensor的requires_grad都自动设置为False
        with torch.no_grad():
            # len(muns)=79 用*号操作符,可以将list unzip(解压)
            # nums =(128,128,...,128,16) 总共10000个验证集 10000=128*78+16
            # losses= (2.279... , ...... , 2.275...)
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        # np.sum() 求和
        # 矩阵点乘——对应元素相乘:np.sum(np.multiply(a,b))=a*b = 128*2.279..+...+16*2.275..
        # np.sum(nums)= 10000
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)
        print('当前step:'+str(step), '验证集损失:'+str(val_loss))
# loss_func = F.cross_entropy
# steps=要迭代多少次
# model模型
# loss_func损失函数
# opt优化器
# train_dl训练数据
# valid_dl验证数据
def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)
    # 因为训练的过程通常使用mini-batch方法,所以如果不将梯度清零的话,
    # 梯度会与上一个batch的数据相关,因此该函数要写在反向传播和梯度下降之前。
    # 每次迭代之后,需要将梯度还原为零,否则loss.backward() 将梯度增加到已经存在的值上,而不是替代它
    if opt is not None:
        # compute gradient and do SGD step
        loss.backward()  # 反向传播计算得到每个参数的梯度值(loss.backward())
        opt.step()       # 通过梯度下降执行一步参数更新(optimizer.step())
        opt.zero_grad()  # 将梯度归零(optimizer.zero_grad())
    return loss.item(), len(xb)  # item():取出张量具体位置的元素元素值  len(xb)= 64
# 获取训练的x,y数据的时候,一次抓取batch=64 和 获取验证的x,y 的数据数据的时候,一次抓取batch=128
# valid_dl.batch_size= 2*bs = 128
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)

model, opt = get_model()

fit(30, model, loss_func, opt, train_dl, valid_dl)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值