Bootstrap

深度学习4

一、手动构建模型

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)

        

;