3.1. 线性回归 — 动手学深度学习 2.0.0-beta1 documentation
目录
3.1. 线性回归 — 动手学深度学习 2.0.0-beta1 documentation
3.1 线性回归
3.1.1 线性回归的基本元素
训练数据集(training data set)或训练集(training set):
想要开发一个具有一定功能的模型,必须要有大量的相关事例作为数据集,这些事例就是训练集。有了这一些事例,就可以根据自变量和因变量之间的关系来作为模型预测的依据。
样本(sample):
每组数据就是样本,也可以称为数据点(data point)或数据样本(data instance)。
标签(label)或目标(target):试图预测的目标。
特征(feature)或协变量(covariate):预测所依据的自变量。
3.1.1.1 线性回归模型
b称为偏置(bias)、偏移量(offset)或截距(intercept) 不可缺少的
w称为权重(weight)
数据集的意义是帮我们找到相对合适的b和w
即使确信特征与标签的潜在关系是线性的, 我们也会加入一个噪声项来考虑观测误差带来的影响。
3.1.1.2 损失函数
3.1.1.3 解析解
一点点高数和线代知识
对以上求导,由于凸函数的性质,当导数为0时,损失最小,解的为
3.1.1.4 随机梯度下降
当没有解析解(显示解)时的优化方法
步长怎么找?
没看懂
3.1.2 矢量化加速
3.2 线性回归从零开始实现
生成数据集
X = torch.normal(0, 1, (num_examples, len(w)))
均值为0,方差为1,n个样本,列数为w
y += torch.normal(0, 0.01, y.shape)
同理,均值为0,方差为0.01,形状同y的长度
这里加的就是随机噪音,这段代码每次运行的结果都不一样
输出第一个样本
detach作用是把数据从计算图里面拎出来,然后再转numpy
读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features) #确定样本数量
indices = list(range(num_examples)) #range()表示从0 - n-1,list()表示转成list格式
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices) #把下标随机打乱
for i in range(0, num_examples, batch_size): #表示从0到n,每次跳batch_size个大小
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices] #每次产生随机特征和标号
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
初始化模型参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) #requires_grad计算梯度
b = torch.zeros(1, requires_grad=True) #1表示直接就是个标量,一个数,同样计算梯度
定义模型
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
定义损失函数
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
定义优化算法
def sgd(params, lr, batch_size): #params给定的所有参数,lr学习率,batch_size指的是批每次跳过的量
"""小批量随机梯度下降"""
with torch.no_grad(): #更新的时候不需要计算梯度
for param in params:
param -= lr * param.grad / batch_size #自动求导
param.grad.zero_()
训练
lr = 0.03 #学习率
num_epochs = 3 #整个数据扫3遍
net = linreg #之前定义的模型linreg,便于以后更换模型
loss = squared_loss #均方损失
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward() #求和之后算梯度
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
#每一遍扫完后损失就会越来越小
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
学习率小还能多跑几遍,学习率过大直接超出浮点运算范围了(nan=not a number)
3.3 线性回归的简洁实现
生成数据集
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2 #形成真实的w和b
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
读取数据集
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
#每次从中挑选batch_size个数据出来,shuffle表示需不需要打乱顺序,is_train则表示需要
batch_size = 10
data_iter = load_array((features, labels), batch_size)
#如果已经有了features, labels,把它做成list,传到dataset
next(iter(data_iter)) #通过next函数得到x和y
定义模型
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
#nn.Linear(2, 1)指的是输入维度为2,输出维度为1
#Sequential是一个容器,用来装层(层数多时非常有用)
初始化模型参数
net[0].weight.data.normal_(0, 0.01) #normal指使用正态分布替换掉data的值
net[0].bias.data.fill_(0) #bias指偏差,这里直接置零
定义损失函数
loss = nn.MSELoss() #MSELoss类
定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
#net.parameters()包括所有参数,这里包括w和b
训练
num_epochs = 3 #迭代3个周期
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad() #trainer预设器梯度清零
l.backward() #计算backward
trainer.step() #调用step函数来更新模型
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
3.4 softmax回归
分类和回归
回归是一个连续值(房价分析)
分类预测一个类别(手写数字识别)
分类输出的个数等于类别的个数
独热编码(one-hot encoding):
独热编码是一个向量,它的分量和类别一样多。 类别对应的分量设置为1,其他所有分量设置为0。 在我们的例子中,标签y将是一个三维向量, 其中(1,0,0)对应于“猫”、(0,1,0)对应于“鸡”、(0,0,1)对应于“狗”:
置信度:
【看完这篇就够了!!!通俗易懂】置信度理解(95%的置信度、置信区间)_醪糟小丸子的博客-CSDN博客_95%置信度
置信度:以测量值为中心,在一定范围内,真值出现在该范围内的几率。一般设定95%,是通常情况下置信度(置信水平)的设定值。
置信区间:在某一置信度下,以测量值为中心,真值出现的范围。一定概率下真值的取值范围(可靠范围)称为置信区间。其概率称为置信概率或置信度(置信水平)。
我的理解是
样本数目不变的情况下,做一百次试验,有95个置信区间包含了总体真值。置信度为95%
损失函数
L2 loss(均方损失)
正好在求导后抵消
L1 loss
预测值和真实值较远时,不管多远,梯度都是常数+/- 1
优点:稳定性好
缺点:零点是尖点,不可导。 优化到末期,结果会变得不稳定
Huber 's Robust Loss
预测值和真实值差距大时,绝对值大于1时,为绝对值误差,梯度为常数
预测值和真实值差距小时,误差函数像高斯函数,是线性的
这样兼具了L1 L2的优点
大概理解就是这样了
这里没怎么看懂,应该是基础知识欠缺的原因,等我学了概率论再来看
softmax:
想要使得输出是一个概率
y和^y的区别作为损失,此时所有都是概率
指数的好处在于无论是什么数都能变成非负
个人理解:softmax算出来的就是置信度
交叉熵:
没懂为啥这么算
一文搞懂交叉熵在机器学习中的使用,透彻理解交叉熵背后的直觉_史丹利复合田的博客-CSDN博客
额,还是没懂
3.5 图像分类数据集
MNIST数据集
Fashion-MNIST数据集
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display() #李沐老师写的包,用svg画图
读取数据集
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor() #把图片转成张量
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
#把数据集下载到上一级目录的data里面,train=True表示下载的是训练数据集
#transform=trans表示图片拿出来要是个pytorch的tensor
#download=True表示默认从网上下载
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
#mnist_test测试数据集,用来验证模型好坏
#train=False 这就是说不下载训练数据集
len(mnist_train), len(mnist_test) #图片数量
mnist_train[0][0].shape
#mnist_train[][].shape 第一个[]表示第一个例子,第二个[]表示第一个图片
#这里试了一下发现好像都是黑白图片
def get_fashion_mnist_labels(labels): #@save
"""返回Fashion-MNIST数据集的文本标签"""
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): #@save
"""绘制图像列表"""
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
axes = axes.flatten()
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
else:
# PIL图片
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
#构建python的iterater(迭代器),用next取小批量
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));
#2,9表示画两行,每行9张图片
读取小批量
训练前要先看读取有多块,以读取速度来决定训练速度
读取速度要比训练速度快很多
batch_size = 256
def get_dataloader_workers(): #@save
"""使用4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
timer = d2l.Timer() #定义timer函数来测量速度
for X, y in train_iter:
continue
f'{timer.stop():.2f} sec'
整合所有组件
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()))
train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
print(X.shape, X.dtype, y.shape, y.dtype)
break
3.6 softmax回归的从零开始实现
import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
#训练集和测试集的迭代器
初始化模型参数
num_inputs = 784 #先粗暴的把图片信息拉成向量28*28=784,损失掉的信息要用之后的神经网络
num_outputs = 10 #模型有10个类,所以输出维度为10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
#size=(num_inputs, num_outputs)形状,行数为num_inputs 784,列数num_outputs 10
b = torch.zeros(num_outputs, requires_grad=True)
#对于每一个输出都有偏移,长为num_outputs的向量
定义softmax操作
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
#2*3 的矩阵
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
#维度为0,列向量求和,得到行向量1*3;维度为1,行向量求和,得到列向量2*1
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
#此时做一些验证
X = torch.normal(0, 1, (2, 5)) #两行五列的矩阵,且所有值都是正的
X_prob = softmax(X)
X_prob, X_prob.sum(1) #按行来加,每行和为1
定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
#-1表示自己算一下这里批量大小,之前设定过256,W.shape[0]=784
#加偏移b是通过广播机制
定义损失函数
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
#[0.1, 0.3, 0.6]为y0,[0.3, 0.2, 0.5]为y1
y_hat[[0, 1], y] #对第0样本拿出y0对应的元素,第1样本拿出y1对应的元素
#所以最后输出值为0.1和0.5
#y0拿出0对应的0.1,y1拿出2对应的0.5
感谢b站大学
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
#生成长度n的向量,拿出真实标号的预测值
cross_entropy(y_hat, y) #这里求交叉熵
#y_hat是2*3的预测值,y是长为2的向量
分类精度
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1) #每行元素值最大的那个下标存y_hat里
cmp = y_hat.type(y.dtype) == y
#把y_hat转成y的数据类型,然后作比较,变成bool型tensor
return float(cmp.type(y.dtype).sum())
accuracy(y_hat, y) / len(y)
#找出来预测正确的样本数,除以y的长度,求出预测正确的概率
#这里结果是0.5,因为都是随机生成的
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:
metric.add(accuracy(net(X), y), y.numel())
#每次先把x放到net里算出评测值
#accuracy(net(X), y)预测正确的样本数
#这里求了总样本数
return metric[0] / metric[1]
#得到精度
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]
evaluate_accuracy(net, test_iter)
由于都是随机生成,开始设置有10个类别,所以结果接近于0.1
训练
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train() #告诉pytorch,要计算梯度
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3) #长度为3的迭代器
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y) #交叉熵损失函数
if isinstance(updater, torch.optim.Optimizer):
#如果updater是pytorch中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]
#这里主要是画图,没看懂,再学学
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 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)
#train_metrics训练误差
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
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
#训练十个迭代周期
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
预测
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])
predict_ch3(net, test_iter)
准确率挺高,