线性回归模型:\(y = Xw + b + \epsilon\)
1、如何衡量模型质量?loss function损失函数——量化实际值和预测值之间的差距
可证:在高斯噪声的假设下,线性模型的最大似然估计 等价于 最小化均方误差(MSE)。证明在另一篇里写过:https://www.cnblogs.com/xjl-ultrasound/p/18305000
平方误差:
\(l^{(i)}(w,b) = \frac{1}{2}(\hat{y}^{(i)} - y^{(i)})^2\)
均方误差(在n个样本上的损失均值):
\(L(w,b) = \frac{1}{n}\sum_{i=1}^{n}l^{(i)}(w,b)\)
2、如何更新参数以提升模型质量?一步步去更新参数以降低loss,最后,找到一组参数,使loss最小化
常用的为“梯度下降”:
(1)即在loss function递减的方向上更新参数,以降低loss。已知梯度是函数值增加最快的方向,那么负梯度方向则是函数值下降最快的方向。所以,可沿着loss function的负梯度方向更新参数,使loss降低
(2)这里面每次更新参数时都涉及求loss function的梯度,如果每次都要遍历数据集,会很慢,常用小批量随机梯度下降(minibatch SGD),每次更新参数,随机抽取一个小批量B,去计算小批量的损失均值关于模型参数的导数,用原参数减去(学习率*导数)
(3)公式:\((w,b) \leftarrow (w,b) - \lambda\frac{\sum_{i\in{B}}\partial_{(w,b)}l^{(i)}(w,b)}{|B|}\)
(4)学习率lr和批量大小batch_size两个超参数:
lr太小——算太多次梯度,贵!
lr太大——容易走过头,不断震荡但loss未有效下降
这里小批量随机梯度下降,相当于每次用小批量的损失均值去近似整体的损失均值,以更新参数。但是,反直觉的是,batch_size小点反而利于收敛
batch_size小:1-noise大,对神经网络是好事,不易走偏,使得模型容忍度↑,更鲁棒,防过拟合;2-但若batch_size过小,每次计算量太小,不适合并行以最大化利用资源
batch_size大:1-太大的话可能模型不那么鲁棒;2-内存消耗大;3-一个batch内可能包含太多相似的样本,浪费计算
线性模型实现
不管是复杂还是简单的神经网络,实现过程都是模板化的:
1、数据集获取&读取
2、定义模型
3、初始化模型参数
4、定义损失函数
5、定义优化算法
6、定义评估指标
7、训练
8、预测、评估
第一步:数据集获取和读取
这一步是模板化的,以下给出的不是线性回归所用的数据集,线性回归用的数据是自己生成的
公共数据集+DataLoader读取,如下:
def load_data_fashion_mnist(batch_size, resize=None): #@save
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0, transforms.Resize(resize))
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers()),
data.DataLoader(mnist_test, batch_size, shuffle=False,
num_workers=get_dataloader_workers()))
私有数据集(Dataset类)+DataLoader读取,如下:
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import os
from PIL import Image
batch_size = 128
train_trans = transforms.Compose([
transforms.ToPILImage(), # 如果用Image.open打开(如下),这行不需要
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(15),
transforms.ToTensor(),])
test_trans = transforms.Compose([
transforms.ToPILImage(), # 如果用Image.open打开(如下),这行不需要
transforms.ToTensor(),])
# 测试集不需要翻转或旋转图片
# 注意ToTensor会把channel提到第一维
# 还可按需加入Resize
class Mydata(Dataset):
def __init__(self, root_dir, label_dir, trans = None):
self.root_dir = root_dir
self.label_dir = label_dir
self.path = os.path.join(self.root_dir, self.label_dir)
self.img_path = os.listdir(self.path) # 获取path路径下所有子文件(图像)的名称
self.trans = trans
def __getitem__(self, idx):
img_name = self.img_path[idx]
img_item_path = os.path.join(self.root_dir, self.label_dir, img_name)
img = Image.open(img_item_path) # Image.open返回PIL类型,还可以用cv2.imread(),返回ndarray类型
if self.transform is not None:
img = self.transform(img)
label = self.label_dir
return img, label
def __len__(self):
return len(self.img_path)
root_tr = r'D:\ai-learning\pytorch\hymenoptera_data\train' # train文件夹
benign_label_dir = "benign"
malignant_label_dir = "malignant"
benign_tr = Mydata(root_tr, benign_label_dir, trans=train_trans)
malignant_tr = Mydata(root_tr, malignant_label_dir, trans=train_trans)
tr_dataset = benign_tr + malignant_tr
# 同理可得te_dataset
tr_iter = DataLoader(tr_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
# te_iter = DataLoader(te_dataset, batch_size=batch_size, shuffle=False, num_workers=0)
Image.open和cv2.imread区别:https://www.cnblogs.com/ElaineTiger/p/18138084
第二步:定义线性模型
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
# nn.Sequential理解为list of layers
第三步:初始化模型参数
net[0].weight.data.normal_(0, 0.01) # 访问net中第一层的参数weight,进行初始化
net[0].bias.data.fill_(0)
第四步:定义损失函数
loss = nn.MSELoss()
第五步:定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
训练
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter: # 每个batch的数据拿出来
l = loss(net(X) ,y) # net(X)算的预测值y'
trainer.zero_grad() # 先把trainer(SGD)的梯度清零
l.backward() # 算loss(损失均值)关于参数的梯度
trainer.step() # 更新模型参数
l = loss(net(features), labels) # X=features, y=labels
print(f'epoch {epoch + 1}, loss {l:f}')
李沐老师的线性回归部分QA总结(QA绝对是精华):
一、求梯度时为什么是求损失均值关于模型参数的导数,即为什么损失要求均值?
使得梯度大小不受batch_size影响,这样调学习率的时候也不受batch_size影响。此外,因为可能出现最后一个batch内数据量不足batch_size的情况,SGD里是会自动帮你除以当前batch的size,而不是预设好的batch_size
二、调学习率的办法:
1、用对学习率没那么敏感的方法,如Adam,比较smooth
2、合理的参数初始化,使得学习率简单调调就行,,,
3、gridsearch
三、二阶导(牛顿法)收敛更快,为什么不用二阶导而用一阶导?
1、未必都能求二阶导
2、二阶导,计算量大,贵;易陷入局部最优
*真正的牛顿法做不了,但有的方法可以近似
3、我们不太关心收敛得快不快,更关心收敛得准不准
还有另一个思想:模型都是错的,但是有用。意思是,我们不可能得到精确的模型,这时候,什么最优解意义不大,快速准确的求解模型可能还不如大方向是正确的随机计算(粗糙拟合)好。。。比如用梯度解析解(只有线性模型有解析解)的泛化性不如SGD。。。
四、load数据时,如果数据很大,全部load进来,内存爆炸?
可以把数据放在硬盘里,每次取一个batch左右的数据
五:随机取batch,其实是把数据下标随机打乱,按顺序抽,能够保证一个epoch下来,取完全部的数据
六、数据总量不是batch_size大小的整数倍,怎么办?1-不咋办,就这样,最后一个batch小一点;2-最后一个小一点的batch直接扔掉;3-从下一个epoch采点数据,补全最后一个小一点的batch
七、怎么判断收敛?关系到epoch的大小
1、两个目标之间变化不大时
2、用一个验证集,其预测精度变化不大时
3、一般更常用的是,先凭直觉设置epoch大小,比如epoch=100,画学习曲线,再定epoch。从而对不同数据集形成经验
iter和for循环:
for循环的本质就是先通过__iter__这个方法获取可迭代对象的迭代器,然后对获取的迭代器进行不断调用__next_方法,来获取下一个值,并将其赋值给临时变量i(in前面的那个临时变量),当遇到StopIteration异常对象则终止循环。参考:https://blog.csdn.net/abraham_ly/article/details/107874466
——————————————————————————————————————
Softmax回归模型:即Softmax输出层
用于:多类别分类问题——只有1个正确答案——互斥输出
真实值(标签):one-hot encoding,一个向量,分量数=类别数,仅正确类别的分量为1,其余为0
预测值(预测概率和预测类别):全连接层(input_dim=上一层输出的feature数,output_dim=类别数)+ softmax(保证1-输出为概率,范围0-1;2-输出之和为1;3-模型保持可导),这样我们取概率最大的分量对应的类别为预测类别
softmax公式:\(\hat{y} = softmax(o)\),其中,\(\hat{y_i} = \frac{exp(o_j)}{\sum_{k}\exp(o_k)}\)
损失函数:真实值和预测值均为概率模型,损失函数衡量两个概率模型之间的差异——交叉熵损失\(l(y,\hat{y}) = -\sum_{j=1}^{q}y_{j}log\hat{y_j}\)
——————————————————————————————————————
信息量、熵、KL散度、交叉熵:这部分参考了b站up主王木头学科学https://space.bilibili.com/504715181
1、信息量
可以看出信息量公式需要满足:
(1)概率相乘变相加——需要log运算
(2)发生概率越小,获得信息量越大——加负号
(3)以几为底?无所谓,先以2为底(这样单位是比特)。如果以e为底,单位是纳特
即:
\(f(x) = -log_{2}x\)
信息量:一件事从不确定变为确定的难度。原来的概率x越小,难度越大,包含的信息量f(x)就越大
2、熵
熵:一个系统(中的所有事件)从不确定变为确定的难度。即所有事件信息量的加权和(期望),权重即为本事件发生的概率
因此,熵可以衡量一个系统(一个概率模型)的混乱程度=不确定程度
概率模型P的熵:\(H(P) = \sum_{i=1}^{m}p_{i}f(p_{i}) = \sum_{i=1}^{m}p_{i}(-log_{2}p_i)\)
3、KL散度、交叉熵
KL散度衡量两个概率模型之间的差距。比如\(D_{KL}(P\|{Q})\)以概率模型P为基准(谁写在前,以谁为基准),衡量概率模型P和Q之间的差距
\(D_{KL}(P\|{Q}) = \sum_{i=1}^{m}p_{i}(f_{Q}(q_i)-f_{P}(p_i)) = \sum_{i=1}^{m}p_{i}(-logq_i) - \sum_{i=1}^{m}p_{i}(-logp_i)\)
计算内容为 事件在Q中的信息量与在P中的信息量的差值的加权和(期望),权重为对应事件在P中的概率。最后得到,以概率模型P为基准时,概率模型P和Q之间的KL散度=交叉熵-在P系统的熵
4、把交叉熵作为损失函数的合理性
吉布斯不等式证明了KL散度一定≥0。当P和Q一样时,为0;不一样时,大于0
让模型P(真实模型)和模型Q(预测模型)尽可能接近 —— 让\(D_{KL}(P\|{Q})\)尽可能小(KL散度=交叉熵-在P系统的熵,而后一项已知不变)—— 让交叉熵尽可能小
因此,交叉熵可作为损失函数,衡量两个概率模型的接近程度。最小化交叉熵,可使真实模型和预测模型尽可能接近
5、任何指数族分布模型均满足:交叉熵的梯度 = 预测概率 - 真实概率
一方面,我们可以看到,梯度为0,交叉熵取最小值时,确实让预测概率和真实概率尽可能接近(相等)
另一方面,这个性质使得求解梯度变得简单
——————————————————————————————————————
代码实现:
import torch
from torch import nn
from d2l import torch as d2l
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
class Animator: #@save
"""在动画中绘制数据"""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# 增量地绘制多条线
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 使用lambda函数捕获参数
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# 向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # 检查 y_hat 是否是一个二维张量,即有多个类的概率分布
y_hat = y_hat.argmax(axis=1) # 找到每个样本预测概率最大的类别索引,y_hat 将变成一个一维张量,表示每个样本的预测类别
cmp = y_hat.type(y.dtype) == y # y_hat和y相同位置元素比较,若一样,在cmp对应位置的元素为True,否则为False
return float(cmp.type(y.dtype).sum())
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式,只做前向传递,不做梯度(反向传播)
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter: # 累加器用于累加一个epoch的结果
metric.add(accuracy(net(X), y), y.numel()) # 分类正确的样本数、总样本数;在下一句两者相除,得到精度
return metric[0] / metric[1]
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期"""
if isinstance(net, torch.nn.Module):
net.train() # 将模型设置为训练模式,要计算梯度
# 训练损失总和、训练准确度总和、样本数;因此用长度为3的迭代器累计信息
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 如果使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
else:
# 如果使用自定义的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater) # 训练
test_acc = evaluate_accuracy(net, test_iter) # 测试
animator.add(epoch + 1, train_metrics + (test_acc,)) # 可视化
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签(定义见第3章)"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
# 读取数据
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 定义模型
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
# nn.Flatten用于将多维张量展平为二维张量(即,保持批量维度不变,将其余维度展平为一个维度)。常用于在卷积层和全连接层之间的转换
#初始化权重
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
# 在交叉熵损失函数中传递未规范化的预测,并同时计算softmax及其对数,这样做可以防止溢出
loss = nn.CrossEntropyLoss(reduction='none')
# nn.CrossEntropyLoss的reduction参数:1-默认为mean,对n样本的loss求均值;2-sum,对n样本的loss求和;3-none,直接返回n样本的loss
# 如果这里不使用默认的mean,则在d2l.train_ch3的设置里求梯度之前,要先求loss的mean,即l.mean().backward()
#优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
#训练
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
#预测
predict_ch3(net, test_iter)
标签:loss,self,batch,train,softmax,P8,net,回归,size
From: https://www.cnblogs.com/xjl-ultrasound/p/18345729