资讯详情

【李沐AI自学】线性神经网络

线性回归

在机器学习领域大多数任务通常与预测有关(prediction)有关。当我们想预测的时候,当我们想预测的时候回归问题将涉及到一个数值。常?的例?包括:预测价格(房屋、股票等)。),预测住院时间(住院病等)、预测需求(零售销量等)。但并非所有的预测都是回归问题。在后?在章节中,我们将介绍分类问题。分类问题标是预测数据属于?哪个组类别?个。

线性回归基本元素

为了解释线性回归,我们举起它个实际例子:我们希望根据房子积(平?英尺)和房龄(年)估算房价(美元)。为了开发?我们需要收集能够预测房价的模型真实数据集。该数据集包括房屋的销售价格积和房龄。在机器学习术语中,数据集称为训练数据集(training data set)或训练集(training set)。每?数据(?如?二次房屋交易对应的数据称为样本(sample),也可称为数据点(data point)或数据样本(data instance)。我们试图预测标(?如果预测房价,则称为标签(label)或?标(target)。预测所依据的预测变量(?积和房龄)称为特征(feature)或协变量(covariate)。

在这里插入图片描述 由于平误差函数中的次项,估计值y?(i)和观测值y(i)它们之间的差异会导致更多的损失。为了测量整个数据集中模型的质量,我们需要计算训练集n个样本中的平均损失(也等于和平)。

我们希望在训练模型中找到小组参数(w?, b?),这组参数可以最大化所有训练样本的总损失。 线性回归和前向传播是通过计算获得的y‘值,然后根据y’-y计算loss,然后反向传播,每个特征(相当于相邻矩阵的列)loss分别对w和b求偏导,得到每个特征的梯度,即3.1.为了更新w和b。

?量化加速

在训练我们的模型时,我们经常希望同时处理整个批样本。为了实现这一点,我们需要量化计算,而不是利润线性代数库Python编写费用昂for循环。

%matplotlib inline import math import time import numpy as np import torch from d2l import torch as d2l  n = 10000  a = torch.ones(n)  b = torch.ones(n) 

由于我们在这本书中经常进入运输时间的基准测试,我们定义了一个计时器:

class Timer: #@save """记录多次运输的时间"""  def __init__(self):   self.times = []   self.start()  def start(self): """启动计时器"""  self.tik = time.time()  def stop(self): """停止计时器,记录列表中的时间"""  self.times.append(time.time() - self.tik)   return self.times[-1]  def avg(self): """回到平均时间"""   return sum(self.times) / len(self.times)
	def sum(self):
"""返回时间总和"""
		return sum(self.times)
	def cumsum(self):
"""返回累计时间"""
		return np.array(self.times).cumsum().tolist()

现在我们可以对⼯作负载进⾏基准测试。 ⾸先,我们使⽤for循环,每次执⾏⼀位的加法。

	c = torch.zeros(n)
	timer = Timer()
	for i in range(n):
		c[i] = a[i] + b[i]
		f'{ 
          timer.stop():.5f} sec'
		'0.08976 sec'

或者,我们使⽤重载的+运算符来计算按元素的和。

timer.start()
d = a + b f'{ 
          timer.stop():.5f} sec'
'0.00027 sec'

正态分布和平方损失

接下来,我们通过对噪声分布的假设来解读平⽅损失⽬标函数。 正态分布和线性回归之间的关系很密切。正态分布(normal distribution),也称为⾼斯分布(Gaussian distribution),最早由德国数学家⾼斯(Gauss)应⽤于天⽂学研究。简单的说,若随机变量x具有均值µ和⽅差σ2(标准差σ),其正态分布概率密度函数如下: 下⾯我们定义⼀个Python函数来计算正态分布。

def normal(x, mu, sigma):
		p = 1 / math.sqrt(2 * math.pi * sigma**2)
		return p * np.exp(-0.5 / sigma**2 * (x - mu)**2)

我们现在可视化正态分布。

# 再次使⽤numpy进⾏可视化
x = np.arange(-7, 7, 0.01) # 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
ylabel='p(x)', figsize=(4.5, 2.5),
legend=[f'mean { 
          mu}, std { 
          sigma}' for mu, sigma in params])

线性回归的从零开始实现

⽣成数据集

为了简单起⻅,我们将根据带有噪声的线性模型构造⼀个⼈造数据集。我们的任务是使⽤这个有限样本的数据集来恢复这个模型的参数。我们将使⽤低维数据,这样可以很容易地将其可视化。在下⾯的代码中,我们⽣成⼀个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征。我们的合成数据集是⼀个矩阵X ∈ R1000×2。我们使⽤线性模型参数w = [2, −3.4]⊤、b = 4.2 和噪声项ϵ⽣成数据集及其标签: 你可以将ϵ视为模型预测和标签时的潜在观测误差。在这⾥我们认为标准假设成⽴,即ϵ服从均值为0的正态分布。为了简化问题,我们将标准差设为0.01。下⾯的代码⽣成合成数据集。

%matplotlib inline
import random
import torch
from d2l import torch as d21
def synthetic_data(w, b, num_examples): #@save
    """生成 y = Xw + b + 噪声。"""
    X=torch.normal(0,1,(num_examples,len(w)))
    y=torch.matmul(X,w)+b#matmul所以矩阵向量乘法都可以通过广播机制实现
    y+=torch.normal(0,0.01,y.shape)
    return X,y.reshape(-1,1)
torch.normal(2,3,size=(1,4))
# tensor([[-1.3987, -1.9544, 3.6048, 0.7909]])
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features,labels=synthetic_data(true_w,true_b,1000)
features,labels
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);

读取数据集

回想⼀下,训练模型时要对数据集进⾏遍历,每次抽取⼀⼩批量样本,并使⽤它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义⼀个函数,该函数能打乱数据集中的样本并以⼩批量⽅式获取数据。在下⾯的代码中,我们定义⼀个data_iter函数,该函数接收批量⼤⼩、特征矩阵和标签向量作为输⼊,⽣成⼤⼩为batch_size的⼩批量。每个⼩批量包含⼀组特征和标签。

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))#0-999的数组列表
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)#打乱顺序
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])#tensor([993, 616, 127, 509, 119, 167, 648, 486, 907, 27])前十个
        yield features[batch_indices], labels[batch_indices]
    #yield可以理解为一个return操作,但是和return又有很大的区别,执行完return,当前函数就终止了,函数内部的所有数据,所占的内存空间,全部都没有了。
    #而yield在返回数据的同时,还保存了当前的执行内容,当你再一次调用这个函数时,他会找到你在此函数中的yield关键字,然后从yield的下一句开始执行。

通常,我们利⽤GPU并⾏运算的优势,处理合理⼤⼩的“⼩批量”。每个样本都可以并⾏地进⾏模型计算,且每个样本损失函数的梯度也可以被并⾏计算。GPU可以在处理⼏百个样本时,所花费的时间不⽐处理⼀个样本时多太多。我们直观感受⼀下⼩批量运算:读取第⼀个⼩批量数据样本并打印。每个批量的特征维度显⽰批量⼤⼩和输⼊特征数。同样的,批量的标签形状与batch_size相等。

batch_size = 10
for X, y in data_iter(batch_size, features, labels):
	print(X, '\n', y)
	break

初始化模型参数

在我们开始⽤⼩批量随机梯度下降优化我们的模型参数之前,我们需要先有⼀些参数。在下⾯的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
 b = torch.zeros(1, requires_grad=True)

在初始化参数之后,我们的任务是更新这些参数,直到这些参数⾜够拟合我们的数据。每次更新都需要计算损失函数关于模型参数的梯度。有了这个梯度,我们就可以向减⼩损失的⽅向更新每个参数。因为⼿动计算梯度很枯燥⽽且容易出错,所以没有⼈会⼿动计算梯度。我们使⽤ 2.5节中引⼊的⾃动微分来计算梯度。 接下来,我们必须定义模型,将模型的输⼊和参数同模型的输出关联起来。回想⼀下,要计算线性模型的输出,我们只需计算输⼊特征X和模型权重w的矩阵-向量乘法后加上偏置b。注意,上⾯的Xw是⼀个向量,⽽b是⼀个标量。回想⼀下 2.1.3节中描述的⼴播机制:当我们⽤⼀个向量加⼀个标量时,标量会被加到向量的每个分量上。

def linreg(X, w, b): #@save
"""线性回归模型"""
	return torch.matmul(X, w) + b

因为需要计算损失函数的梯度,所以我们应该先定义损失函数。这⾥我们使⽤ 3.1节中描述的平⽅损失函数。在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

def squared_loss(y_hat, y): #@save
"""均⽅损失"""
	return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

正如我们在 3.1节中讨论的,线性回归有解析解。尽管线性回归有解析解,但本书中的其他模型却没有。这⾥我们介绍⼩批量随机梯度下降。在每⼀步中,使⽤从数据集中随机抽取的⼀个⼩批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的⽅向更新我们的参数。下⾯的函数实现⼩批量随机梯度下降更新。该函数接受模型参数集合、学习速率和批量⼤⼩作为输⼊。每⼀步更新的⼤⼩由学习速率lr决定。因为我们计算的损失是⼀个批量样本的总和,所以我们⽤批量⼤⼩(batch_size)来规范化步⻓,这样步⻓⼤⼩就不会取决于我们对批量⼤⼩的选择。

def sgd(params, lr, batch_size): #@save
"""⼩批量随机梯度下降"""
	with torch.no_grad():
		for param in params:
			param -= lr * param.grad / batch_size
			param.grad.zero_()

训练

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}')
epoch 1, loss 0.043118
epoch 2, loss 0.000173
epoch 3, loss 0.000051
print(f'w的估计误差: { 
          true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: { 
          true_b - b}')
w的估计误差: tensor([ 0.0005, -0.0004], grad_fn=<SubBackward0>) 
b的估计误差: tensor([0.0013], grad_fn=<RsubBackward1>)

线性回归简洁实现

生成数据集

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
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

读取数据集

我们可以调⽤框架中现有的API来读取数据。我们将features和labels作为API的参数传递,并通过数据 迭代器指定batch_size。此外,布尔值is_train表⽰是否希望数据迭代器对象在每个迭代周期内打乱数 据。

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 = 10
data_iter = load_array((features, labels), batch_size)

使⽤data_iter的⽅式与我们在 3.2节中使⽤data_iter函数的⽅式相同。为了验证是否正常⼯作,让我们 读取并打印第⼀个⼩批量样本。与 3.2节不同,这⾥我们使⽤iter构造Python迭代器,并使⽤next从迭代器中获取第⼀项。

next(iter(data_iter))

定义模型

对于标准深度学习模型,我们可以使⽤框架的预定义好的层。这使我们只需关注使⽤哪些层来构造模型,⽽不必关注层的实现细节。我们⾸先定义⼀个模型变量net,它是⼀个Sequential类的实例。Sequential类将多个层串联在⼀起。当给定输⼊数据时,Sequential实例将数据传⼊到第⼀层,然后将第⼀层的输出作为第⼆层的输⼊,以此类推。在下⾯的例⼦中,我们的模型只包含⼀个层,因此实际上不需要Sequential。但是由于以后⼏乎所有的模型都是多层的,在这⾥使⽤Sequential会让你熟悉“标准的流⽔线”。回顾 图3.1.2中的单层⽹络架构,这⼀单层被称为全连接层(fully-connected layer),因为它的每⼀个输⼊都通过矩阵-向量乘法得到它的每个输出。在PyTorch中,全连接层在Linear类中定义。值得注意的是,我们将两个参数传递到nn.Linear中。第⼀个指定输⼊特征形状,即2,第⼆个指定输出特征形状,输出特征形状为单个标量,因此为1。

# nn是神经⽹络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))

在使⽤net之前,我们需要初始化模型参数。如在线性回归模型中的权重和偏置。深度学习框架通常有预定义的⽅法来初始化参数。在这⾥,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。正如我们在构造nn.Linear时指定输⼊和输出尺⼨⼀样,现在我们能直接访问参数以设定它们的初始值。我们通过net[0]选择⽹络中的第⼀个图层,然后使⽤weight.data和bias.data⽅法访问参数。我们还可以使⽤替换⽅法normal_和fill_来重写参数值。

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

计算均⽅误差使⽤的是MSELoss类,也称为平⽅L2范数。默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

⼩批量随机梯度下降算法是⼀种优化神经⽹络的标准⼯具,PyTorch在optim模块中实现了该算法的许多变种。当我们实例化⼀个SGD实例时,我们要指定优化的参数(可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。⼩批量随机梯度下降只需要设置lr值,这⾥设置为0.03。

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

训练

通过深度学习框架的⾼级API来实现我们的模型只需要相对较少的代码。我们不必单独分配参数、不必定义我们的损失函数,也不必⼿动实现⼩批量随机梯度下降。当我们需要更复杂的模型时,⾼级API的优势将⼤⼤增加。当我们有了所有的基本组件,训练过程代码与我们从零开始实现时所做的⾮常相似。 回顾⼀下:在每个迭代周期⾥,我们将完整遍历⼀次数据集(train_data),不停地从中获取⼀个⼩批量的输⼊和相应的标签。对于每⼀个⼩批量,我们会进⾏以下步骤: • 通过调⽤net(X)⽣成预测并计算损失l(前向传播)。 • 通过进⾏反向传播来计算梯度。 • 通过调⽤优化器来更新模型参数。

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X) ,y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch { 
          epoch + 1}, loss { 
          l:f}')
epoch 1, loss 0.000392
epoch 2, loss 0.000104
epoch 3, loss 0.000104

下⾯我们⽐较⽣成数据集的真实参数和通过有限数据训练获得的模型参数。要访问参数,我们⾸先从net访问所需的层,然后读取该层的权重和偏置。正如在从零开始实现中⼀样,我们估计得到的参数与⽣成数据的真实参数⾮常接近。

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
w的估计误差: tensor([ 0.0002, -0.0002])
b的估计误差: tensor([-9.1553e-05])

softmax回归

分类问题

社会科学家邓肯·卢斯于1959年在选择模型(choice model)的理论基础上发明的softmax函数正是这样做的:softmax函数将未规范化的预测变换为⾮负并且总和为1,同时要求模型保持可导。我们⾸先对每个未规范化的预测求幂,这样可以确保输出⾮负。为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。如下式:

⼩批量样本的⽮量化

为了提⾼计算效率并且充分利⽤GPU,我们通常会针对⼩批量数据执⾏⽮量计算。假设我们读取了⼀个批量的样本X,其中特征维度(输⼊数量)为d,批量⼤⼩为n。此外,假设我们在输出中有q个类别。那么⼩批量特征为X ∈ Rn×d,权重为W ∈ Rd×q,偏置为b ∈ R1×q。softmax回归的⽮量计算表达式为: 相对于⼀次处理⼀个样本,⼩批量样本的⽮量化加快了X矩阵-向量乘法。由于X中的每⼀⾏代表⼀个数 据样本,那么softmax运算可以按⾏(rowwise)执⾏:对于O的每⼀⾏,我们先对所有项进⾏幂运算,然后通过求和对它们进⾏标准化。在 (3.4.5)中,XW + b的求和会使⽤⼴播,⼩批量的未规范化预测O和输出概率ˆY都是形状为n × q的矩阵。

图像分类数据集

MNIST数据集 [LeCun et al., 1998] 是图像分类中⼴泛使⽤的数据集之⼀,但作为基准数据集过于简单。我们将使⽤类似但更复杂的Fashion-MNIST数据集 [Xiao et al., 2017]。

%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()

读取数据集

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
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)
    
len(mnist_train), len(mnist_test)
mnist_train[0][0].shape
torch.Size([1, 28, 28])

Fashion-MNIST由10个类别的图像组成,每个类别由训练数据集(train dataset)中的6000张图像和测试数据集(test dataset)中的1000张图像组成。因此,训练集和测试集分别包含60000和10000张图像。测试数据集不会⽤于训练,只⽤于评估模型性能。 每个输⼊图像的⾼度和宽度均为28像素。数据集由灰度图像组成,其通道数为1。为了简洁起⻅,本书将⾼度h像素、宽度w像素图像的形状记为h × w或(h,w)。 Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤⼦)、pullover(套衫)、dress(连⾐裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。以下函数⽤于在数字标签索引及其⽂本名称之间进⾏转换。

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)
        
        标签: 2290连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台