深度学习原理搭建
在学习深度学习时经常发现自己的代码书写规范和大神的代码差距很大,所以专门学习了一下如何正确的书写深度学习的书写规范
导入所需包
import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
# PyTorch三件套
import torch
from torch import nn # 层:参数定义+处理逻辑包装在一起
from torch.nn import functional as F # 单个处理逻辑,可以自定义参数
# 数据打包
from torch.utils.data import Dataset # 将数据通过中括号的方法(索引)取出来 ,
from torch.utils.data import DataLoader # 以Dataset为参数批量化打包
- torch.nn:在PyTorch中指层,包括但不限于将参数定义、处理逻辑封装在一起
- torch.nn.functional:将nn中处理逻辑单独拿出,包含大量 loss 和 activation function
nn函数文档中第一行的 Init signature(初始化函数签名) 代表其是一个类
Applies a linear transformation to the incoming data代表会对数据进行线性变换公式是 y = xA^T + b
一、数据处理
1.数据集预处理
# 加载数据
X,y = load_boston(return_X_y=True)
# 切分数据
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2 ,random_state=1)
# 标签改为列向量
y_train = y_train.reshape((-1,1))
y_test = y_test.reshape((-1,1))
"""
数据预处理
- .mean(axis=0):按列计算均值均值,结果为(13,)
- .std(axis=0):按列计算标准差,结果为(13,)
"""
mean_ = X_train.mean(axis=0)
std_ = X_train.std(axis=0)
"""
数据标准化:
- 经过处理的数据符合标准正态分布。
- 好处:使网络更容易收敛, 更快收敛,对识别率有好处。
"""
X_train = (X_train - mean_) / std_
X_test = (X_test - mean_) / std_
2.数据打包
"""
数据打包
"""
class MyDataset(Dataset):
"""
定义Dataset
"""
def __init__(self, X, y):
"""
超参
"""
self.X = X
self.y = y
def __getitem__(self, idx):
"""
根据索引返回一对数据
"""
return torch.tensor(data = self.X[idx]).float(), torch.tensor(data = self.y[idx]).float() # 相当于元组
def __len__(self):
return len(self.X)
3.定义训练集数据加载器
"""
定义训练集数据加载器
- batch_size = 32:将数据批量处理,每次32个
- shuffle=True 打乱
"""
train_dataset = MyDataset(X=X_train,y=y_train)
train_dataloader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
"""
定义测试集的加载器
- 使用测试集时不用构建特征图,没有反向传播,所以不需要打乱
"""
test_dataset = MyDataset(X=X_test,y=y_test)
test_dataloader = DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)
二、模型定义
1.定义模型
"""
定义模型
"""
class LinearRegression(nn.Module):
"""
"""
def __init__(self, in_features, out_features):
"""
接收超参和定义层
"""
super(LinearRegression, self).__init__() # 一个子类继承了一个父类,那么子类在构建时应该先构建父类
self.linear = nn.Linear(in_features=in_features, out_features=out_features) # 13列进来 1列出去
def forward(self, x):
"""
定义正向传播过程
"""
out = self.linear(x)
return out
这里的书写逻辑有点不同,定义模型时,在__init__()初始化阶段定义模型,再通过forward()函数中对象调用函数的方法调用模型
# 模型定义
lr = LinearRegression(in_features=13, out_features=1)
2.定义损失函数与优化器
# 定义损失函数 对象当成函数使用的风格
loss_fn = nn.MSELoss()
# 定义优化器 梯度下降
optimizer = torch.optim.SGD(params=lr.parameters(),lr=1e-2) # 只要是需要梯度下降的(True),都抠出来交给他(params)托管
3.过程监控
"""
过程监控
"""
def get_loss(dataloader=train_dataloader, model=lr, loss_fn=loss_fn):
with torch.no_grad(): # 不记录图(不计算梯度)
lossses = []
for X, y in dataloader:
y_pred = model(X)
loss = loss_fn(y_pred, y)
lossses.append(loss.item())
return np.array(lossses).mean()
三、模型训练&预测
"""
训练过程
"""
def train(modle=lr, dataloader=train_dataloader, optimizer=optimizer, loss_fn=loss_fn, epochs = 20):
"""
定义训练过程
由于数据量巨大,花费时间成本和金钱成本巨大,所以深度学习将数据分为多个banch,
所有banch都训练一遍称为epoch
"""
# 训练多少轮
for epoch in range(1,epochs+1):
# 每一轮按批次进行训练
for X,y in train_dataloader:
# 正向传播
y_pred = modle(X)
# 计算损失
loss = loss_fn(y_pred, y)
# 清空梯度
optimizer.zero_grad()
# 反向传播
loss.backward()
# 优化梯度
optimizer.step()
print(f"当前是第 {epoch} 轮,训练集的误差为: {get_loss(dataloader=train_dataloader)}, 测试集的误差为:{get_loss(dataloader=test_dataloader)}")
train()
def predict(model, X):
"""
模型的预测
"""
with torch.no_grad():
y_pred = model(X)
return y_pred
for X, y in test_dataloader:
y_pred = predict(model=lr, X=X)
# print(y_pred)
# print(y)
print(((y_pred - y) ** 2).mean())
break
四、模型保存
# 保存模型
lr.state_dict() # 状态字典:有顺序的字典
# 序列化
torch.save(obj=lr.state_dict(),f="lr.aura")
# 反序列化
torch.load(f="lr.aura")
# 替换状态字典
model.load_state_dict(state_dict=torch.load(f="lr.aura"))
# <All keys matched successfully>代表所有参数已经加载且对准
五、其他
Q:为什么不对标签做归一化?
A:
- 对特征归一化的原因是
- 特征需要参与模型计算,如果数据太大,框架底层可能溢出,所以归一化将数据集中在0附近
- 统一量纲
- 标签不做模型计算,而且如果标签归一化需要反解结果(标签其实就是结果,需要是人能看懂的数据)
一些概念理解
- AI数据必须时批量化的,因为工程上数据量非常大,所以需要数据加载器
- 在定义模型时,要将模型封装成对象,成为层
- 面向对象的思想:同一个类中有静态属性,有动态属性(fn)
- 深度学习层的概念:把变量的定义的处理逻辑封装在一起
- 损失函数的选取上,当数据是连续数据时,Loss首选MSE ,当数据是离散时首选交叉熵 ,注意在编写代码时损失函数也要封装起来
- 优化器中主要内容是参数的更新和梯度的清空
框架中的矩阵操作
AI框架中对矩阵相乘的类型要求非常严格,默认为Float类型,当两种类型不相同的矩阵相乘时会发生报错,原因是框架为了提高速度和效率,底层使用的都是C/C++语言,是静态语言
m1 = torch.randint(low = 1, high= 10, size = (2,5))
m2 = torch.randint(low = 1, high= 10, size = (5,3)).float
m1 @ m2