赞
踩
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)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。