一、手动构建模型
epoch 一次完整数据的训练过程(可细分多次训练),称为 一代训练
Batch 小部分样本对权重的更新,称为 一批数据
iteration 使用一个 Batch 的过程,称为 一次训练
步骤:
1、生成 x,y 的数据
2、初始化 w,b 的值
3、拆分 x,y 的数据
4、传入小批量数据到一个函数, 函数为关系表达式(y = x@w+b),(得到一个损失函数值,表达式)
5、根据真实损失值和表达式结果得到均方差(显示结果为值传递,隐藏函数表达式的传递,为梯度下降使用)
6、对损失函数反向传递(第五步的结果),对 w,b 求梯度值
7、更新梯度值到小变化
1、基础数据 x,y
使用 sklearn 的数据集方法 make_regression
from sklearn.datasets import make_regression
make_regression :创建一个具有线性关系的数据集,包含一定的噪声和异常值
参数:n_samples 样本数量,默认100
n_features 每个样本的特征数量,默认100
n_targets 回归目标的数量,默认1
bais 偏置,默认0.0
coef 系数值,设置为True,则返回数据的真实系数
shuffle 打乱样本
noise 高斯噪声的标准差,默认0.0
属性:x 样本形状,
y 实际目标值
coef 真实系数
import torch
import sklearn.datasets import make_regression
def data_build():
noise = 14.6
n_sample=1000 # 样本数量
x,y,coef = make_regression(n_samples=n_sample,n_features=4,coef=True,bias=15.9,n_informative=1,noise=noise,random_state=666)
x = torch.tensor(x, dtype=torch.float32, requires_grad=True) # 根据make_regression函数生成的数据,x的shape为(n_sample,n_features),需要梯度更新
y = torch.tensor(y, dtype=torch.float32) # 函数生成特征值 n_features
return x,y,coef # coef 为真实的函数系数
2、初始化 w,b
也就是随机产生两个tensor值,requires_grad =True 可梯度更新
import torch
# 初始化 w,b
def initialize(n_features):
torch.manual_seed(666) # 保证每一次初始化的值都一样,验证模型训练
w = torch.randn(n_features,requires_grad=True)
b = torch.tensor(1.,requires_grad=True)
return w,b
3、拆分x,y的数据
设定多代训练(完整数据),每代多次训练(小批量)
import torch
def data_loader(x,y):
"""
description: 数据加载器
param {type}
"""
# 配置参数
batch_size = 16 # 设定一次为16个数据
n_samples = x.shape[0] # 根据x的形状得到总数据量
n_batch = math.ceil(n_samples / batch_size) # 总数据量除以批次大小得到总共需要多少次训练完整个数据
indexs = [i for i in range(x.shape[0])] # 生成总样本的数据量,为打乱数据准备
random.shuffle(indexs) # 打乱下标数据
for i in range(0, n_batch, batch_size): # 循环训练次数,步长为设定的批次大小
index = indexs[i*batch_size: (i+1)*batch_size] # 根据打乱的下标得到数据打乱效果
yield x[index],y[index] # yeild 生成器 获得数据,与训练模型搭配
4、损失函数
传递为是损失函数表达式,损失结果值;计算预测结果
import torch
# 损失函数表达式
def myresreser(x,w,b):
return x@w+b # 一个容器中装了每一条样本的预测值
5、计算均方差
根据预测值和前面生成的真实值得到均方差
import torch
# 根据预测值和真实值得到均方差,损失函数结果
def mse(y_pred,y_true):
return torch.mean((y_true-y_pred)**2) # 求误差均值
6、更新(优化)
根据传递grad梯度,修改 w,b的值
import torch
w.data = w.data - lr * w.grad
b.data = b.data - lr * b.grad
7、训练
调用数据生成、数据划分、初始值w,b、损失函数、均方差、更新的函数;
定义epoch 完成多代训练、batch 单词训练数据大小、iteration 完整训练次数
import torch
# 训练函数
def train():
# 调用生成数据函数产生 x,y 和真实的系数
x,y,coef = data_build()
# 初始化参数 w,b
w,b = initialize(x.shape[1])
# 训练参数
lr = 0.01 # 学习率
epoch = 100 # 训练次数
for i in range(epoch): # 循环训练次数(完整数据为一代)
for batch_x,y_true in data_loader(x,y): # 调用数据加载器得到一次小训练的数据
y_pred = myresreser(batch_x,w,b) # 根据表达式函数船射出预测值 ,返回值是一个tensor
y_true = torch.tensor(y_true,requires_grad=True) # 跟据真实值创建一个 tensor
loss = mse(y_pred,y_true) # 根据真实值和预测值得到损失函数
# 判断第一次不需要清零,后面需要更新梯度,所以需要梯度清零
if w.grad is not None:
w.grad.data.zero_()
if b.grad is not None:
b.grad.data.zero_()
# 反向传播
loss.backward()
# 更新梯度
w.data = w.data - lr * w.grad
b.data = b.data - lr * b.grad
完整流程:
from sklearn.datasets import make_regression
import torch
import math
import random
# 创建 x,y 的数据 返回一个数据真实的系数
def data_build():
noise = 14.6
n_sample=1000 # 样本数量
x,y,coef = make_regression(n_samples=n_sample,n_features=4,coef=True,bias=15.9,n_informative=1,noise=noise,random_state=666)
x = torch.tensor(x, dtype=torch.float32, requires_grad=True) # 根据make_regression函数生成的数据,x的shape为(n_sample,n_features),需要梯度更新
y = torch.tensor(y, dtype=torch.float32) # 函数生成特征值 n_features
return x,y,coef # coef 为真实的函数系数
# 初始化 w,b
def initialize(n_features):
torch.manual_seed(666)
w = torch.randn(n_features,requires_grad=True)
b = torch.tensor(1.,requires_grad=True)
return w,b
# 加载 x,y 数据,进行划分为 明确批量大小和次数的多个小批量数据
def data_loader(x,y):
"""
description: 数据加载器
param {type}
"""
# 配置参数
batch_size = 16 # 设定一次为16个数据
n_samples = x.shape[0] # 根据x的形状得到总数据量
n_batch = math.ceil(n_samples / batch_size) # 总数据量除以批次大小得到总共需要多少次训练完整个数据
indexs = [i for i in range(x.shape[0])] # 生成总样本的数据量,为打乱数据准备
random.shuffle(indexs) # 打乱下标数据
for i in range(0, n_batch, batch_size): # 循环训练次数,步长为设定的批次大小
index = indexs[i*batch_size: (i+1)*batch_size] # 根据打乱的下标得到数据打乱效果
yield x[index],y[index] # yeild 生成器 获得数据,与训练模型搭配
# 损失函数表达式
def myresreser(x,w,b):
return x@w+b # 一个容器中装了每一条样本的预测值
# 根据预测值和真实值得到均方差,损失函数结果
def mse(y_pred,y_true):
return torch.mean((y_true-y_pred)**2) # 求误差均值
# 训练函数
def train():
# 调用生成数据函数产生 x,y 和真实的系数
x,y,coef = data_build()
# 初始化参数 w,b
w,b = initialize(x.shape[1])
# 训练参数
lr = 0.01 # 学习率
epoch = 100 # 训练次数
for i in range(epoch): # 循环训练次数(完整数据为一代)
e = 0
count=0
for batch_x,y_true in data_loader(x,y): # 调用数据加载器得到一次小训练的数据
y_pred = myresreser(batch_x,w,b) # 根据表达式函数船射出预测值 ,返回值是一个tensor
y_true = torch.tensor(y_true,requires_grad=True) # 跟据真实值创建一个 tensor
loss = mse(y_pred,y_true) # 根据真实值和预测值得到损失函数
e+=loss
count +=1
# 判断第一次不需要清零,后面需要更新梯度,所以需要梯度清零
if w.grad is not None:
w.grad.data.zero_()
if b.grad is not None:
b.grad.data.zero_()
# 反向传播
loss.backward()
print(w.grad)
print(b.grad)
# 更新梯度
w.data = w.data - lr * w.grad
b.data = b.data - lr * b.grad
train()
print()
二、模型组件
就是将第一点所用的函数内容封装为官方组件,使用时直接调用,无需再次定义。
1、基础组件
1.1、线性层组件
import torch.nn as nn
nn.Linear :根据参数内容常见线性对象,在对象传入样本数据,返回预测结果
参数:in_features:输入特征数量
out_features:输出特征数量
bais:是否使用偏置,默认True
属性:weights:权重
bais:偏置
import torch
import torch.nn as nn
# x 数据
x = torch.tensor([[1,2,3,4]],dtype=torch.float32)
model = nn.Linear(4,1) # 创建一个线性对象,输入特征值与x保持一致,输出一个特征
print("Weights:", model.weight) # 权重
print("Bias:", model.bias) # 偏置
y = model(x) # 使用线性对象预测 y
print(y)
1.2、损失函数组件
import torch.nn as nn
nn.MSELoss:可无参数使用,返回一个损失函数对象,在对象中传入预测值和真实值(由于求均方差,故顺序不重要)
import torch
import torch.nn as nn
model = nn.MSELoss()
y_true = torch.randn(5, 3)
y_pred = torch.randn(5, 3, requires_grad=True)
print(model(y_true, y_pred))
1.3、优化器组件
import torch.optim as optim
optim.SGD():根据传入的线性对象、学习率,实现随机梯度下降,配合循环使用
参数:params:需要进行优化的模型,一般使用线性对象
lr:学习率,默认0.1
weight_decay:权重衰减(L2正则化),放置过拟合,默认0
属性:optimizer.zero_grad() 清零梯度,不需要进行空值判断
optimizer.step() 更新参数 相当于 w.data = w.data- lr*w.grad
import torch
import torch.optim as optim
x = torch.randint(1,10,(400,5)).type(torch.float32) # 创建x值
target = torch.randint(1,10,(400,1)).type(torch.float32) # 创建实际y值
model = nn.Linear(5, 1) # 线性对象,5个传入特征,1个输出特征
# 优化器对象
sgd = optim.SGD(model.parameters(), lr=0.01) # 学习率为0.01
y_pred = model(x) # 预测值
loss_fn = nn.MSELoss() # 创建损失函数 均方差 对象
loss = loss_fn(y_pred,target) # 使用 预测值 和真实值 计算损失
print(loss)
sgd.zero_grad() # 梯度清零
loss.backward() # 反向传播
sgd.step() # 更新参数
import torch
import torch.optim as optim
x = torch.randint(1,10,(400,5)).type(torch.float32) # 创建x值
target = torch.randint(1,10,(400,1)).type(torch.float32) # 创建实际y值
# 线性对象,5个传入特征,1个输出特征
model = nn.Linear(5, 1)
# 优化器对象
sgd = optim.SGD(model.parameters(), lr=0.001) # 学习率为0.01
# 创建损失函数 均方差 对象
loss_fn = nn.MSELoss()
# 创建循环训练
batch = 50
for j in range(int(400/batch)):
x_batch = x[j*batch:(j+1)*batch]
target_batch = target[j*batch:(j+1)*batch]
y_pred = model(x_batch) # 预测值
loss = loss_fn(y_pred,target_batch) # 使用 预测值 和真实值 计算损失
sgd.zero_grad() # 梯度清零
loss.backward() # 反向传播
sgd.step() # 更新参数
print("损失函数值的变化:",loss)
2、数据加载器
2.1、构建数据和加载数据
构建自定义数据加载类通常需要继承 torch.utils.data.Dataset ,实现以下三个方法:
__init__(self,data,lables): data 表示数据(x),lables表示特征(y)
__len__(self):获取自定义数据集大小的方法
__getitem__(self,index):index表示下标,根据下标获取data 或 lables的数据
# 数据加载器 数据集和加载器
import torch
from torch.utils.data import Dataset,DataLoader
class my_dataset(Dataset): # 继承Datasets
def __init__(self,x,y):
super(my_dataset,self).__init__()
self.data = x # 实例化对象传入的x数据
self.label = y # 实例化对象传入的特征数据
def __getitem__(self, index):
return self.data[index], self.label[index]
def __len__(self):
return len(self.data)
# 随机创建 x,y 的数据
x = torch.randn(100, 3)
y = torch.randn(100, 1)
# 实例化对象 返回数据类
data = my_dataset(x,y)
# 使用其方法
print("len:",len(data))
print("getitem:",data[1])
# 创建一个数据加载对象 loader batch_size=16 每次训练16个数据,随机打乱
loader = DataLoader(data, batch_size=16, shuffle=True)
for x, y in loader:
print(x.shape,y.shape)
2.2、数据集加载-excel
加载本地案例
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd
class my_excel_dataset(Dataset):
def __init__(self, path):
super(my_excel_dataset,self).__init__()
"""
把excel文件读取,数据放入data,目标值放入labels
"""
data_pd = pd.read_excel(path) # 读取数据,使用pandas优化
data_pd.dropna(axis=1, how='all')
# 对每一类加上特征描述,方便后续使用
data_pd.columns = ['zubie','st_id','st_name','fengong','expresion','ppt_make','answer','code_show','score','demo']
# 删除无用数据列
data_pd = data_pd.drop(['zubie','st_id','st_name','fengong','score'], axis=1)
# 创建 dada 和 lables
self.data = torch.tensor(data_pd.iloc[:,:-1].to_numpy(), dtype=torch.float32)
self.lables = torch.tensor(data_pd.iloc[:,:-1].to_numpy(), dtype=torch.float32)
def __getitem__(self, index):
return self.data[index],self.lables[index]
def __len__(self):
return len(self.data)
path = f"../../data/21级大数据答辩成绩表.xlsx"
data = my_excel_dataset(path) # 通过本地文件实例化数据类
datasets = DataLoader(data, batch_size=10,shuffle=True) # 加载数据
for x,y in datasets:
print(x,y)
2.4、数据集加载-图片
需要使用os的API:
# 目录,该目录下的根文件夹(如存在,一层目录结束后进入),该目录下的根文件
for root, dir, files in os.walk("../../data"):
pass
# 拼接
path = os.path.join("../../data","1.png")
print(path)
# 取出完整文件名,默认分割最后一个斜杠
str = os.path.split("../../data/1.png")
print(str)
# 取出完整文件后缀名
str = os.path.splitext("../../data/1.png")
print(str)
import os
from torch.utils.data import Dataset,DataLoader
import cv2
import torch
class My_Image_Dataset(Dataset):
def __init__(self,path):
self.path = path
self.data = [] # 存放图片路径
self.lables =[] # 存放图片标签
self.classname =[] # 存放图片标签名称
for roots , dirs, files in os.walk(path):
if roots == path: # 判断是根目录
self.classname = dirs # 获取所有文件夹名称
else:
for file in files: # 进入内层目录后遍历所有文件
file_path = os.path.join(roots,file) # 拼接该文件所在目录和文件名称,得到完整路径
self.data.append(file_path) # 添加到data
class_id = self.classname.index(os.path.split(roots)[1]) # 拆分目录得到该文件所在文件夹名称(标签),并在列表中返回对应下标
self.lables.append(class_id) # 将下表添加到lables ,这样labels就可以和data一一对应
def __len__(self):
return len(self.lables) # 根据标签得到总共数据长度
def __getitem__(self, index):
img_path = self.data[index] # 根据下标返回图片路径
lable = self.lables[index] # 根据下标返回标签
img = cv2.imread(img_path) # 根据路径获取图片数据
cv2.imshow("img",img) # 展示图片,保证获取数据无问题
cv2.waitKey(0)
img = cv2.resize(img,(336,336)) # 调整图片大小为统一格式
img = torch.from_numpy(img) # 转化为numpy格式
img = img.permute(2,0,1) # 进行通道调整,维度个数交换
return img,lable # 返回图片数据,标签
path = f"../../data/animal" # 数据集路径
my_image_dataset = My_Image_Dataset(path) # 实例化数据集对象
print(len(my_image_dataset)) # 数据集大小
print(my_image_dataset[1]) # 根据下标1 返回对应图片数据和标签
print(my_image_dataset.classname) # 输出所有类别名称
2.5、数据集加载-官方
官方地址:Datasets — Torchvision 0.20 documentation
from torchvision import transforms,datasets
使用 transforms 对数据进行调整
使用 datasets 加载数据
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
# 创建一个数据转换器,将numpy或图片转成为tensor,Compose为组合器
transform = transforms.Compose([
transforms.ToTensor()
])
# 加载 MNIST 数据集到 data地址
data = datasets.MNIST(root='../../data', # 数据集的存储路径
train=True, #是否加载训练数据集
transform=transform, # 加载数据转化器
download=True # 是否从互联网下载数据集。如果设置为 True,并且数据集不存在于指定路径,将会自动下载数据集。
)
for (x,y) in DataLoader(data, batch_size=16,shuffle=True): # 循环数据加载器,循环返回16个数据
print(x.shape, y.shape)