任务分析
问题分类:回归是连续问题,这里房价数据集,MSE作为损失函数 数据处理: 训练集,测试集拆分; 归一化; 转dataset 模型定义:一层全连接 学习训练: 损失函数MSE,优化器:SGD ; 输入训练集,循环训练; 中间输出损失函数大小,精度等以便观察 预测处理: 使用测试集进行预测, 模型的输出就是房价,不需要额外处理
房价数据集
from ai.datasets import load_boston # 加载数据 X_train, y_train, X_test, y_test = load_boston() shape:(430, 13), type:class 'numpy.ndarray', X_train shape:(430,), type:class 'numpy.ndarray', y_train
以向量,向量组的角度看数据集
学术界默认所有向量为列向量 训练集是一个shape为(430,13)的矩阵 从数据集的角度看,整个数据集有13个向量,每个向量430维, 每个维数430的向量都是房价一个维度的数据,可计算 均值,标准差 向量维数430,向量组个数为13, 数据处理的时候,要从这个角度看数据集
一个样本表示的向量
通常使用小写的x表示一个样本向量,如果按数学理论,它是一个列向量 这里13个维度表示一个样本,在数学理论上差不多是这个样子 [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ] 在落地计算的时候,列向量与行向量无任何区别, 实际上常用的样本是按行写的,它是下面这个样本 x=[1,2,3,4,5,6,7,8,9,10,11,12,13] 这二者容易混淆,区分方法有二: 1. 看场景,如果你是在看书,请把所有向量默认按列向量看 2. 如果你在敲击键盘,请把数据集的一个样本看成行向量 下面是全连接的计算公式
x就是一个样本,这里的x是行向量, [1,13]@[13,1]= [1] 把一个13维的向量变换成1维,因为这里是回归问题 不要太在意公式怎么写,关键是shape对的上就可以了 全连接公式写经常写成 xw + b = y 意为把x变换到y,y与标签同维
标签shape处理
# 标签shape处理,一个标签为一个向量 y_train = y_train.reshape((-1, 1)) y_test = y_test.reshape((-1, 1)) y_test.shape (76, 1) 模型输出,标签 会作为损失函数的参数,通常情况下它们同shape 连续问题,模型使用全连接, 输入数据的维度是13, 输出为1维,本意是输出一个数就是行, 但深度学习中的维度转换,也就是线性变换,数据全是有维度的,至少1维 在pytorch中,单个数字没有维度或维度为0,1维就是1维数组/列表 模型从13维转换到1维,其一个样本对应的结果就是[1]这样的格式 后面会展示模型的输出 所以,这里将标签的维度从0提升到1 这一步,要求心中对 模型维度转换,损失函数参数,标签维度 三者shape了然于胸, 是整体打通流程后,第二遍回来看时,才能做到的, 第一遍知道有这个事就行了
均值
取均值处理,以测试集为例,数据有76行,13列, X_test.shape (76, 13) 归一化,是要对一列做处理,是要取一列的均值, 即要对76这个数字所在的维度,就是dim=0这一维度取均值 就2维矩阵,dim=0表示列方向, 因此均值为 X_test.mean(axis=0,keepdims=False).shape (13,) 有13列,每列一个均值,有13个均值 xx_: 单个末尾下划线(后缀)是一个约定,用来避免与Python关键字产生命名冲突;
标准化公式
(数据 - 均值)/ 标准差 这里数据的shape为(76,13),是二维 均值的shape为(13,),是一维 一个二维矩阵 减 一维矩阵,首先会对一维矩阵升维,升到与二维同shape, 也就是说,会将(13,)变成(76,13) 会将(13,)这样的向量,复制76份, 数据[76,13] - 均值(13,) = 数据[76,13] - 均值(76,13) = 新数据[76,13] 后面是除法,这个是重点,除法并不在指定的向量线性运算中, 走的更不是矩阵运算,即不是向量内积 走的是,向量位乘,按位运算 同样先将标准差的维度从(13,)升到(76,13) 然后按位操作,相同位置上的前后两个矩阵上的元素,分别相除 运算后的矩阵shape不变
标准化的目的
(数据-均值)/标准差 标准差就是方差的开平方, 是一个数据分布内部离散量的累加再求平均, 可 近似看作 整体数据离“数据中心点”的距离的平均值 这么一系列操作后,得到的是啥? 将数据转化为均值为0,标准差为1 的正态分布,即标准正态分布 为什么要这么做?为什么呢? 因为数据处理时,处理的多个数据分布,要对比就得有个统一的量纲, 处理方法就是,不管你原来是什么分布,数据的均值多少,离散程度怎么样, 我统一给你转成均值为1方差为1的标准正态分布 这是能够多列一起处理的 基石/起点/前提 注意: 让均值为0,标准差为1,就是所有数据离中心点平均距离为1, 并不意味着各个数据分布会变得一模一样 他们的形态仍然是千差万别 比如,在一个平面坐标系上,给你两个数,可正可负, 只要求,他们的均值为0,到原点距离为1, 以原点为圆心,取直径与圆相交的两个点,为一对数据, 那么,符合要求的数据可以取无数对, 他们都符合,均值为0,到均值0的平均距离为1, 这才是两个数据点中一种特殊的数据分布... 所以,让数据分布的均值为0,标准差为1, 并没有让所有数据分布变得一模一样, 只是统一了量纲,有了比较/计算的前提
数据预处理
# 数据预处理 mean_ = X_train.mean(axis=0) std_ = X_train.std(axis=0) X_train = (X_train - mean_) / std_ X_test = (X_test - mean_) / std_ 四行代码,但你的脑海中要闪现出以下内容: 每列都是一个分布 求分布均值 求分布标准差 将每列化为正态分布, 这里使用是z-score,归一化中的标准化, 将分布转化为一个均值为0,标准差为1的正态分布, 即标准正态分布 矩阵shape的变换 有向量线性运算, 也有向量位乘 但真的没有矩阵乘法运算,即没有向量内积
批次加载
import torch from torch import nn # 数据打包 from torch.utils.data import Dataset from torch.utils.data import DataLoader class MyDataset(Dataset): """自定义数据集 """ def __init__(self, X, y): """超参 """ self.X = X self.y = y def __getitem__(self, idx): """根据索引返回一对数据 (x, y) """ return torch.tensor(data=self.X[idx]).float(), torch.tensor(data=self.y[idx]).float() def __len__(self): """数据总数 """ return len(self.X) # 训练集加载器 train_dataset = MyDataset(X=X_train, y=y_train) train_dataloader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True) train_dataset[0] (tensor([-0.3971, -0.4946, -0.3633, -0.2689, -0.2868, -0.6296, 0.8480, -0.7475, -0.5148, -0.1354, 1.1539, 0.4311, 0.8525]), tensor([19.5000])) len(train_dataset) 430 for x,y in train_dataloader: print(x.shape,y.shape) break torch.Size([32, 13]) torch.Size([32, 1]) # 测试集加载器,不需要shuffle test_dataset = MyDataset(X=X_test, y=y_test) test_dataloader = DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)
从数据到标签的shape变换
torch.Size([32, 13]) torch.Size([32, 1]) 数据:32一个批次,每个样本13维 标签:32一个批次,每个样本1维 特征变换:将13变为1,这就是模型要做的
nn.Linear()
nn.Linear( in_features: int, out_features: int, bias: bool = True, device=None, dtype=None, ) -> None y = xA^T + b import torch from torch import nn X = torch.randn(32,13) # 先定义一个类模板,并生成一个模板对象,这个过程会初始化一组参数矩阵A与b line_layer = nn.Linear(in_features=13,out_features=1) # 调用该对象 y_out = line_layer(X) y_out.shape torch.Size([32, 1]) for param in line_layer.parameters(): print(param) Parameter containing: tensor([[-0.2485, 0.0003, -0.2262, -0.2724, -0.2261, -0.2296, 0.0148, -0.1113, 0.0652, 0.1598, 0.2040, -0.0722, -0.1204]], requires_grad=True) Parameter containing: tensor([-0.1196], requires_grad=True)
模型定义
class LinearRegression(nn.Module): """模型定义 """ def __init__(self, in_features, out_features): """参数网络设计 - 总体来说,做的事件是将数据从一个维度转换到另外一个维度 """ super().__init__() self.linear = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, X): """正向传播 - 调用定义的参数网络 - 让数据流过参数网络,常量数据流过不过的参数产生不同的值 - 这个过程参数本身不会变 - 让参数变化的是后面的优化器 """ out = self.linear(X) return out model = LinearRegression(in_features=13, out_features=1) model LinearRegression( (linear): Linear(in_features=13, out_features=1, bias=True) ) 该自定义模型中只有一个线性模型Linear 其参数也只有线性模型的参数, 在init方法中完成Linear模板类的初始化,也就完成了参数的初始化 for param in model.parameters(): print(param) Parameter containing: tensor([[-0.2292, -0.1510, 0.2504, 0.0500, -0.2287, 0.1542, -0.1364, -0.1282, -0.2764, -0.0922, 0.1389, 0.0320, 0.0816]], requires_grad=True) Parameter containing: tensor([-0.0371], requires_grad=True) forward方法 输入为批次数据,调用参数网络进行计算,然后输出结果
模型验证
X = torch.randn(30,13) y_out = model(X) y_out.shape torch.Size([30, 1]) 使用随机数验证一下模型是否与预期相符, 数据分批后,最后一批未必刚好就是32,大概率不是
损失函数
# 损失函数 loss_fn = nn.MSELoss()
where :math:`N` is the batch size. If :attr:`reduction` is not ``'none'``(default ``'mean'``), then:
MSE损失计算
每个样本与标签之间都有个损失,一个批次有n个损失, 默认求均值,因此批次损失的结果是一个标量, 另外,损失函数的结果是一个批次的均值, 在pytorch中,任何数都是一个tensor,这个均值也是如此, tensor对象上还附加有一系列方法,比如反向传播 X = torch.randn(30,13) y_out = model(X) y = torch.randn(30,1) # 损失函数 loss_fn = nn.MSELoss() loss = loss_fn(y_out, y) loss tensor(1.4462, grad_fn=MseLossBackward0)
损失函数反向传播
loss.backward() 损失函数以 模型输出和标签 为参数,这才是整个工程中的总函数 loss_fn = nn.MSELoss() 损失函数中的常量与变量各是什么? 常量就是数据,输入的数据自来业务,是不会变的 变量就是参数,参数才是变量,类似下面的公式 y = 3w + b 3在前,w在后,写成数据的样本,就是 y_out = xw + b 数学公式通常将常量写在变量的前面, 书上经常是这么写的y=3x+1,导致我们潜意识中总是认为x是变量 其实谁是变量主要看业务逻辑,x本身就是一个字母符号, 在AI中,x代表的是数据中的一个样本,是常量,w才是那个变量 反向传播,是一种逆向求导的过程,就简单看作求导吧 y = 3w + b 若对w求偏导,那么函数y的导数恒为3, MSE计算的是差的平方,包括了平方计算, 那么它的导函数就是一个关于w的一次函数 pytorch在损失函数反向传播,也就是求导时, 将变量w在x处的梯度存入w.grad.data中, 并且是累加 for param in model.parameters(): print(param.grad.data) tensor([[ 0.0172, -1.7931, 1.6512, 0.3874, 0.2149, -0.3993, -1.0178, -1.1852, -2.4712, -0.1330, 1.1420, -1.2974, -0.1388]]) tensor([1.0219]) 到这里变参本身本身尚无变化,只是所在对象tensor.gard.data被放了历史累计的梯度 for param in model.parameters(): print(param) Parameter containing: tensor([[-0.2292, -0.1510, 0.2504, 0.0500, -0.2287, 0.1542, -0.1364, -0.1282, -0.2764, -0.0922, 0.1389, 0.0320, 0.0816]], requires_grad=True) Parameter containing: tensor([-0.0371], requires_grad=True)
重全局而弱个体
损失函数的公式中,损失的计算是批次的均值 实际上每个样本与标签都有一个损失,有的损失大有的损失小 一个批次中那个损失小的参数是否值得保留? 不着急回答这个问题,先来看看概率: 概率不同的频率,频率是统计结果,一个频率是与一个样本绑定的, 而概率具有普遍意义,比如抛一次硬币出现正面的概率是二分之一, 这种先验概率可以代入数学公式进行推理,而频率达不到这种程度 但 ... 虽说抛一次硬币出现正面的概率是二分之一,你抛两次就敢说一定出正面吗? 不敢 飞行员跳伞用的那个伞,最初时,人跳下去伞打不开的概率是百分之一,你敢用吗? 不敢 概率,对个体而言它很不准,只有数据量达到一定规模时,才具有“统计”意义 即使损失函数计算的不是概率,但仍在统计学的范畴,仍有数理统计的规律在其中, 所以,统计损失时,不是按个体进行统计,而是批次, 这也意味着,批次通常不要太小, 要能一定程度上 代表/反映 全体数据集的性质才行 另外,计算机中为什么要用批次?批次在落地计算时的意义何在? 那是因为,如果代入全体数据集,计算机可能计算不动, 无奈才小批次计算... 如果条件允许,批次要尽量大一些
模型参数更新:优化器
训练的目的:期望得到一组符合要求的参数 这个工作由优化器来处理 原理:梯度下降法 w = w - 学习率*w.grad.data 学习率是一个小于1的数, 目的在于控制每次参数的改变量, 防止它一次变化太大,导致跨过最优解 w.grad.data,就是梯度,导数, 也就是梯度下降法,最核心的点在哪里? 在于它有正负 如果只有一个变量,随便给个足够小的数,一点点改变,也能找到最优解 但现在问题是变量有N多个,每个变量的正负变化方向不一样, 而梯度,也就是导数, 小于0表示下降,大于0表示在上升,能准确在表示每个变参的增减 它可以让每个变参都向着各自维度上最低点走去, 当所有变参都走到自己的最低点附近时,就是整个损失函数的最低点 # 优化器 optimizer = torch.optim.SGD(params=lr.parameters(), lr=1e-2) SGD是随机梯度下降,常用的还有自适应torch.optim.Adam, torch.optim包下还有很多其他优化器,可自行研究
# 优化一次 optimizer.step() # 然后模型参数就会变化一次 for param in model.parameters(): print(param) Parameter containing: tensor([[-0.2248, -0.1468, 0.2516, 0.0496, -0.2243, 0.1527, -0.1342, -0.1232, -0.2775, -0.0989, 0.1355, 0.0304, 0.0789]], requires_grad=True) Parameter containing: tensor([-0.0366], requires_grad=True)
数据被使用一次,参数就被更新一次 但一次更新,未必会达到损失函数梯度的最低点的附近,可能还有很远的距离 如此,数据就可以循环/重复使用
一轮训练
# 模型对象 model = LinearRegression(in_features=13, out_features=1) # 损失函数 loss_fn = nn.MSELoss() # 优化器 optim = torch.optim.SGD(params=model.parameters(), lr=1e-2) # 一轮训练 for X,y in train_dataloader: y_out = model(X) loss = loss_fn(y_out, y) # 求当前参数在当前数据处的梯度 loss.backward() print(loss.item()) # 下降一次,就是朝着最优解的方向前进一步 optim.step() optim.zero_grad() 563.1500854492188 529.769287109375 552.975830078125 569.2049560546875 466.73284912109375 469.0701599121094 452.25543212890625 445.3332824707031 385.3167419433594 512.1915893554688 481.2800598144531 455.5694274902344 265.6980895996094 331.1587219238281
多轮训练
def trans(epoch, batch_size=32, dataset=train_dataset, model=model, loss_fn=loss_fn, optim=optim): """多轮训练 """ train_dataloader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) for i in range(1,epoch+1): print(f"...第{i}轮训练开始....\n") # 一轮训练 for X,y in train_dataloader: y_out = model(X) loss = loss_fn(y_out, y) # 求当前参数在当前数据处的梯度 loss.backward() print(loss.item()) # 下降一次,就是朝着最优解的方向前进一步 optim.step() optim.zero_grad() print("-------------------------\n") trans(epoch=2) ...第1轮训练开始.... 599.859130859375 689.5206298828125 517.9083251953125 453.3288269042969 506.0191650390625 585.3128662109375 398.93548583984375 541.7645874023438 385.9604797363281 442.7764892578125 336.8048095703125 380.02935791015625 464.0787353515625 381.2884216308594 ------------------------- ...第2轮训练开始.... 281.2172546386719 411.74871826171875 367.2362365722656 283.3430480957031 376.3662109375 297.74468994140625 197.08645629882812 222.76084899902344 338.5317077636719 229.46780395507812 197.31729125976562 235.1333770751953 195.02389526367188 184.9523162841797 -------------------------
损失监控
import numpy as np test_dataset = MyDataset(X=X_test, y=y_test) def get_loss(dataset, model=model, loss_fn=loss_fn, batch_size=128): """以整个数据集为基的损失评估 """ dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=False) with torch.no_grad(): batch_loss = [] for X, y in dataloader: y_out = model(X) loss = loss_fn(y_out, y) batch_loss.append(loss.item()) mean_loss = np.array(batch_loss).mean() return mean_loss.round(4) def trans(epoch, batch_size=32, train_dataset=train_dataset, test_dataset=test_dataset, model=model, loss_fn=loss_fn, optim=optim): """多轮训练 """ train_dataloader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) for i in range(1,epoch+1): print(f"...第{i}轮训练开始....\n") # 一轮训练 count = 0 for X,y in train_dataloader: count = count+1 y_out = model(X) loss = loss_fn(y_out, y) # 求当前参数在当前数据处的梯度 loss.backward() if count%7==6: print(round(loss.item(),4)) # 下降一次,就是朝着最优解的方向前进一步 optim.step() optim.zero_grad() epoch_loss_train = get_loss(dataset=train_dataset, model=model, loss_fn=loss_fn) epoch_loss_test = get_loss(dataset=test_dataset, model=model, loss_fn=loss_fn) print(f"第{i}轮训练,模型输出与训练集偏差:{epoch_loss_train},与测试集偏差:{epoch_loss_test}") print("-------------------------\n") trans(epoch=2) ...第1轮训练开始.... 497.1451 385.3959 第1轮训练,模型输出与训练集偏差:362.3831,与测试集偏差:260.6301 ------------------------- ...第2轮训练开始.... 362.2549 215.1881 第2轮训练,模型输出与训练集偏差:217.4191,与测试集偏差:145.0836 -------------------------
精度监控
多跑几个批次
trans(epoch=20) ...第7轮训练开始.... 35.5381 21.4963 第7轮训练,模型输出与训练集偏差:37.6273,与测试集偏差:18.4797 ------------------------- ...第8轮训练开始.... 29.2791 29.9498 第8轮训练,模型输出与训练集偏差:32.6934,与测试集偏差:16.8172 ------------------------- ...第9轮训练开始.... 25.7375 45.8575 第9轮训练,模型输出与训练集偏差:29.5915,与测试集偏差:16.5234 ------------------------- ...第10轮训练开始.... 47.0189 21.1816 第10轮训练,模型输出与训练集偏差:27.9589,与测试集偏差:16.5747 ------------------------- ...第11轮训练开始.... 22.6338 36.2648 第11轮训练,模型输出与训练集偏差:26.9055,与测试集偏差:16.8762 ------------------------- ...第12轮训练开始.... 19.1486 31.5116 第12轮训练,模型输出与训练集偏差:26.2493,与测试集偏差:17.2252 ------------------------- ...第13轮训练开始.... 11.7521 21.8944 第13轮训练,模型输出与训练集偏差:25.7086,与测试集偏差:17.5082 ------------------------- ...第14轮训练开始.... 12.7284 21.849 第14轮训练,模型输出与训练集偏差:25.4166,与测试集偏差:17.7007 ------------------------- ...第15轮训练开始.... 10.8912 29.8508 第15轮训练,模型输出与训练集偏差:25.226,与测试集偏差:17.7393 ------------------------- ...第16轮训练开始.... 21.1807 22.1289 第16轮训练,模型输出与训练集偏差:25.0299,与测试集偏差:18.0463 ------------------------- 从第11轮开始,损失开始慢慢变大了...
调用模型输出即预测
def predict(X,model): """预测 """ with torch.no_grad(): y_out = model(X) return y_out
test_dataloader = DataLoader(dataset=test_dataset, batch_size=128, shuffle=False) for X, y in test_dataloader: y_pred = predict(X=X, model=model) print(y_pred[:5],y[:5]) print(((y_pred - y) ** 2).mean()) break tensor([[17.5243], [24.8794], [36.0807], [18.9943], [20.5612]]) tensor([[17.4000], [23.9000], [36.0000], [12.1000], [19.6000]]) tensor(18.5860)
训练模型传参是引用
def trans(epoch, batch_size=32, train_dataset=train_dataset, test_dataset=test_dataset, model=model, loss_fn=loss_fn, optim=optim): model对象传入trans方法,是以引用的方式传入的 在trans方法修改了model对象的参数 trans方法结束后,model对象的参数永久地发生了变化 故后续可直接使用model对象进行预测
from ai.datasets import load_boston # 加载数据 X_train, y_train, X_test, y_test = load_boston() # 标签shape处理,一个标签为一个向量 y_train = y_train.reshape((-1, 1)) y_test = y_test.reshape((-1, 1)) # 数据预处理 mean_ = X_train.mean(axis=0) std_ = X_train.std(axis=0) X_train = (X_train - mean_) / std_ X_test = (X_test - mean_) / std_ import torch from torch import nn # 数据打包 from torch.utils.data import Dataset from torch.utils.data import DataLoader class MyDataset(Dataset): """自定义数据集 """ def __init__(self, X, y): """超参 """ self.X = X self.y = y def __getitem__(self, idx): """根据索引返回一对数据 (x, y) """ return torch.tensor(data=self.X[idx]).float(), torch.tensor(data=self.y[idx]).float() def __len__(self): """数据总数 """ return len(self.X) # 训练集加载器 train_dataset = MyDataset(X=X_train, y=y_train) # train_dataloader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True) class LinearRegression(nn.Module): """模型定义 """ def __init__(self, in_features, out_features): """参数网络设计 - 总体来说,做的事件是将数据从一个维度转换到另外一个维度 """ super().__init__() self.linear = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, X): """正向传播 - 调用定义的参数网络 - 让数据流过参数网络,常量数据流过不过的参数产生不同的值 - 这个过程参数本身不会变 - 让参数变化的是后面的优化器 """ out = self.linear(X) return out # 模型对象 model = LinearRegression(in_features=13, out_features=1) # 损失函数 loss_fn = nn.MSELoss() # 优化器 optim = torch.optim.SGD(params=model.parameters(), lr=1e-2) import numpy as np test_dataset = MyDataset(X=X_test, y=y_test) def get_loss(dataset, model=model, loss_fn=loss_fn, batch_size=128): """以整个数据集为基的损失评估 """ dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=False) with torch.no_grad(): batch_loss = [] for X, y in dataloader: y_out = model(X) loss = loss_fn(y_out, y) batch_loss.append(loss.item()) mean_loss = np.array(batch_loss).mean() return mean_loss.round(4) def trans(epoch, batch_size=32, train_dataset=train_dataset, test_dataset=test_dataset, model=model, loss_fn=loss_fn, optim=optim): """多轮训练 """ train_dataloader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) for i in range(1,epoch+1): print(f"...第{i}轮训练开始....\n") # 一轮训练 count = 0 for X,y in train_dataloader: count = count+1 y_out = model(X) loss = loss_fn(y_out, y) # 求当前参数在当前数据处的梯度 loss.backward() if count%7==6: print(round(loss.item(),4)) # 下降一次,就是朝着最优解的方向前进一步 optim.step() optim.zero_grad() epoch_loss_train = get_loss(dataset=train_dataset, model=model, loss_fn=loss_fn) epoch_loss_test = get_loss(dataset=test_dataset, model=model, loss_fn=loss_fn) print(f"第{i}轮训练,模型输出与训练集偏差:{epoch_loss_train},与测试集偏差:{epoch_loss_test}") print("-------------------------\n") trans(epoch=20) def predict(X,model): """预测 """ with torch.no_grad(): y_out = model(X) return y_out test_dataloader = DataLoader(dataset=test_dataset, batch_size=128, shuffle=False) for X, y in test_dataloader: y_pred = predict(X=X, model=model) print(y_pred[:5],y[:5]) print(((y_pred - y) ** 2).mean()) break
模型改变,nn.Parameter加载参数,其他不变 import torch from torch import nn class LinearRegression(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = nn.Parameter(torch.ones(in_features,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = LinearRegression(in_features=3,out_features=1) for param in model.parameters(): print(param) Parameter containing: tensor([[1.], [1.], [1.]], requires_grad=True) Parameter containing: tensor([1.], requires_grad=True) 参数加入nn.Parameter后,就可以在model.parameters()中查看了 |
import torch from torch import nn # 定义模型 class Model(nn.Module): def __init__(self, in_features=3, out_features=1): super(Model, self).__init__() self.linear1 = nn.Linear(in_features=in_features, out_features=out_features) def forward(self, x): out = self.linear1(x) return out model = Model(in_features=3,out_features=1) model.parameters() generator object Module.parameters at 0x7f31a1de5ee0 model.parameters bound method Module.parameters of Model( (linear1): Linear(in_features=3, out_features=1, bias=True) ) for param in model.parameters(): print(param) Parameter containing: tensor([[ 0.3679, -0.2549, -0.5131]], requires_grad=True) Parameter containing: tensor([0.1661], requires_grad=True) 参数加入 |
模型改变,梯度下降法计算参数,其他不变 import torch from torch import nn class LinearRegression(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = torch.ones(in_features,1, dtype=torch.float32,requires_grad=True) self.b = torch.ones(1, dtype=torch.float32,requires_grad=True) def forward(self,X): x = X@self.w + self.b return x def parameters(self): params = [self.w,self.b] return params 这种写法更原始一些,没有将参数加入nn.Parameter 这意味着 优化器 无法用了,因为model.parameters()没有参数了 optim = torch.optim.SGD(params=model.parameters(), lr=1e-2) 需要自定义一个优化器来更新参数 自定义模型参数与优化器验证 import torch from torch import nn class LinearRegression(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = torch.ones(in_features,1, dtype=torch.float32,requires_grad=True) self.b = torch.ones(1, dtype=torch.float32,requires_grad=True) def forward(self,X): x = X@self.w + self.b return x def parameters(self): params = [self.w,self.b] return params # 模型对象 model = LinearRegression(in_features=2, out_features=1) # 损失函数 loss_fn = nn.MSELoss() class Optim(): def __init__(self,params,lr): self.params = params self.lr = lr def step(self): # 更新参数 for param in self.params: param.data -= self.lr * param.grad.data def zero_grad(self): """清空梯度""" for param in self.params: param.grad.data.zero_() # model.parameters()不是简单类型,传参的方式依然是引用 # 所以才能做到,修改它就是修改model中参数的效果,因为它们一个地址 optim = Optim(params=model.parameters(), lr=1e-2) X = torch.tensor([[0.1,0.1]],dtype=torch.float32) y = torch.tensor([[1]],dtype=torch.float32) y_out = model(X) loss_fn = nn.L1Loss() loss = loss_fn(y_out,y) loss.backward() for param in model.parameters(): print(param) """ tensor([[1.], [1.]], requires_grad=True) tensor([1.], requires_grad=True) """ optim.step() for param in model.parameters(): print(param) """ tensor([[0.9990], [0.9990]], requires_grad=True) tensor([0.9900], requires_grad=True) """ for param in model.parameters(): print(param.grad.data) """ tensor([[0.1000], [0.1000]]) tensor([1.]) """ optim.zero_grad() for param in model.parameters(): print(param.grad.data) """ tensor([[0.], [0.]]) tensor([0.]) """ |
代码:更换了模型与优化器 from ai.datasets import load_boston # 加载数据 X_train, y_train, X_test, y_test = load_boston() # 标签shape处理,一个标签为一个向量 y_train = y_train.reshape((-1, 1)) y_test = y_test.reshape((-1, 1)) # 数据预处理 mean_ = X_train.mean(axis=0) std_ = X_train.std(axis=0) X_train = (X_train - mean_) / std_ X_test = (X_test - mean_) / std_ import torch from torch import nn # 数据打包 from torch.utils.data import Dataset from torch.utils.data import DataLoader class MyDataset(Dataset): """自定义数据集 """ def __init__(self, X, y): """超参 """ self.X = X self.y = y def __getitem__(self, idx): """根据索引返回一对数据 (x, y) """ return torch.tensor(data=self.X[idx]).float(), torch.tensor(data=self.y[idx]).float() def __len__(self): """数据总数 """ return len(self.X) # 训练集加载器 train_dataset = MyDataset(X=X_train, y=y_train) # train_dataloader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True) import torch from torch import nn class LinearRegression(nn.Module): def __init__(self,in_features, out_features): super().__init__() self.w = torch.ones(in_features,1, dtype=torch.float32,requires_grad=True) self.b = torch.ones(1, dtype=torch.float32,requires_grad=True) def forward(self,X): x = X@self.w + self.b return x def parameters(self): params = [self.w,self.b] return params # 模型对象 model = LinearRegression(in_features=13, out_features=1) # 损失函数 loss_fn = nn.MSELoss() class Optim(): def __init__(self,params,lr): self.params = params self.lr = lr def step(self): # 更新参数 for param in self.params: param.data -= self.lr * param.grad.data def zero_grad(self): """清空梯度""" for param in self.params: param.grad.data.zero_() # 优化器 # optim = torch.optim.SGD(params=model.parameters(), lr=1e-2) optim = Optim(params=model.parameters(), lr=1e-2) import numpy as np test_dataset = MyDataset(X=X_test, y=y_test) def get_loss(dataset, model=model, loss_fn=loss_fn, batch_size=128): """以整个数据集为基的损失评估 """ dataloader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=False) with torch.no_grad(): batch_loss = [] for X, y in dataloader: y_out = model(X) loss = loss_fn(y_out, y) batch_loss.append(loss.item()) mean_loss = np.array(batch_loss).mean() return mean_loss.round(4) def trans(epoch, batch_size=32, train_dataset=train_dataset, test_dataset=test_dataset, model=model, loss_fn=loss_fn, optim=optim): """多轮训练 """ train_dataloader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) for i in range(1,epoch+1): print(f"...第{i}轮训练开始....\n") # 一轮训练 count = 0 for X,y in train_dataloader: count = count+1 y_out = model(X) loss = loss_fn(y_out, y) # 求当前参数在当前数据处的梯度 loss.backward() if count%7==6: print(round(loss.item(),4)) # 下降一次,就是朝着最优解的方向前进一步 optim.step() optim.zero_grad() epoch_loss_train = get_loss(dataset=train_dataset, model=model, loss_fn=loss_fn) epoch_loss_test = get_loss(dataset=test_dataset, model=model, loss_fn=loss_fn) print(f"第{i}轮训练,模型输出与训练集偏差:{epoch_loss_train},与测试集偏差:{epoch_loss_test}") print("-------------------------\n") trans(epoch=20) def predict(X,model): """预测 """ with torch.no_grad(): y_out = model(X) return y_out test_dataloader = DataLoader(dataset=test_dataset, batch_size=128, shuffle=False) for X, y in test_dataloader: y_pred = predict(X=X, model=model) print(y_pred[:5],y[:5]) print(((y_pred - y) ** 2).mean()) break 输出 ...第20轮训练开始.... 26.8791 22.784 第20轮训练,模型输出与训练集偏差:25.0181,与测试集偏差:18.7029 ------------------------- tensor([[17.6071], [25.1191], [36.2995], [18.5979], [20.5758]]) tensor([[17.4000], [23.9000], [36.0000], [12.1000], [19.6000]]) tensor(18.7029) 预测效果 预测效果,与前面的两种方式在一个水平级别上 |
线性回归的输出是将一个维度的数据化为一个数,就是降了1维,因此输出维度是固定的 import torch from torch import nn class LinearRegression(nn.Module): def __init__(self,in_features): super().__init__() self.w = nn.Parameter(torch.ones(in_features,1, dtype=torch.float32),requires_grad=True) self.b = nn.Parameter(torch.ones(1, dtype=torch.float32),requires_grad=True) def forward(self,X): x = X@self.w + self.b return x model = LinearRegression(in_features=512) a = torch.randn(32,512) model(a).shape #torch.Size([32, 1]) 它的效果等同于一层线性连接 linear = nn.Linear(in_features=512,out_features=1,bias=True) linear(a).shape #torch.Size([32, 1]) |
|
|
标签没有做归一化处理
数据做了归一化,目的何在? 因为数据要代入模型,进行一层层神经网络的计算, 这个神经网络,目前根据无法处理大量的大于1的数据, 因此矩阵乘法具体到向量实际上是向量内积 向量内积是相乘再相加,如果数据都大于1, 乘法会让这些数据迅速变大,计算机就无法处理了
标签没有做归一化,为什么结果还算可以? 换句讲,标签为什么可以不做归一化? 因为标签代入的是损失函数, 它没有流入一层层的神经网络, 另外,它本身也不需要与谁相乘, 模型输出跟它比的是差异, 所以它不会产生什么数据爆炸 所以它不用做归一化
数据归一化后小于1,但训练后的参数未必全小于1
未必标签做归一化,那么标签又是连续值,是几十几百的数, 训练前随机初始化的模型参数几乎都是小于1的, 现在来看看训练后模型的参数 for param in model.parameters(): print(param) Parameter containing: tensor([[-7.4591e-01, 6.8696e-01, -2.9462e-01, 1.0525e+00, -1.1340e+00, 3.1784e+00, -2.3888e-03, -2.3702e+00, 1.1473e+00, -3.9150e-01, -1.7936e+00, 9.1111e-01, -3.7042e+00]], requires_grad=True) Parameter containing: tensor([22.8020], requires_grad=True) 有正有负,有小于1的,也有大于1的,比如 3.1784e+00,...,-3.7042e+00, 偏置就更不用说了,是 22.8020 这也是偏置不需要归一化的另外一个原因, 并且标签未必就非得是One Hot编码那种非0即1的情况, 也可以是几十几百的数... 模型训练时,自会调整参数,使其逼近标签!
关键的两个环节/步骤
全连接,更确切地说是线性变换, 以近似等价的方式 模拟/或者说是给出了 事物从一个状态到另外一个状态的公式 梯度下降法: w = w - learning_rate*w.grad 以原函数的导函数可反映 其正负变化趋势 的特点/性质, 又加入一定微分/积分的思想,增加了学习率这个参数learning_rate=0.001 再加上计算机强大的遍历计算的能力, 才有了现在的效果,不完美,但能解决一些实际问题 事物转换有其本身的结构/规律,或者不止一个, 但我们是一个都不知道... 线性变换是能近似地描述事物两个状态之间的规律, 但也不能所有规律/结构,都用一个全连接来表示,这太粗糙了... 猜不道事物规律的全部,可以局部猜个大概结构, 比如盲人摸象,摸着腿像柱子,摸着肚子像墙, 就可以建立一个更适合描述柱子的线性变换,和一个更贴切描述墙的线性变换 要比直接一个线性变换去表示大象更好一些
框架的核心
就是pytorch框架工作的关键点:loss.backward() 从损失函数开始对模型参数求导并计算梯度... 这个功能是无法重写的,这是框架的意义所在,或者说是不可替代的环节,其他的都可用python+numpy实现