线性回归

线性回归(linear regression)能够追溯到19世纪初,它在回归的各种规范⼯具中最简略且最流⾏。

线性回归基于几个简略的假定:⾸先,假定⾃变量x和因变量y之间的联系是线性的,即y能够表⽰为x中元素的加权和,这⾥一般允许包括观测值的⼀些噪声;其次,假定任何噪声都比较正常,如噪声遵循正态分布。

假如咱们期望依据房子的⾯积(平⽅英尺)和房龄(年)来估算房子价格(美元)。为了开发⼀个能猜测房价的模型,咱们需求收集⼀个实在的数据集。这个数据集包括了房子的销售价格、⾯积和房龄。在机器学习的术语中,该数据集称为练习数据集(training dataset)或练习集(training set)。每⾏数据(比方⼀次房子交易相对应的数据)称为样本(sample),也能够称为数据点(datapoint)或数据样本(data instance)。咱们把企图猜测的⽬标(比方猜测房子价格)称为标签(label)或⽬标(target)。猜测所依据的⾃变量(⾯积和房龄)称为特征(feature)或协变量(covariate)。

一般,运用n来表明数据集中的样本数。对索引为i的样本,其输入表⽰为x(i)=[x1(i),x2(i)]⊤\mathbf{x}^{(i)}=\left[x_{1}^{(i)}, x_{2}^{(i)}\right]^{\top},对应的标签是y(i)y^{(i)}

线性模型

线性假定是指⽬标(房子价格)能够表⽰为特征(⾯积和房龄)的加权和

price=warea⋅area+wage⋅age+bprice =w_{\text {area }} \cdot area +w_{\text {age }} \cdot age +b

权重决议了每个特征对猜测值的影响。b称为偏置(bias)、偏移量(offset)或截距(intercept)。偏置是指当一切特征都取值为0时,猜测值应该为多少。即使现实中不会有任何房⼦的⾯积是0或房龄正好是0年,依然需求偏置项。假如没有偏置项,模型的表达能⼒将受到限制。

在机器学习领域一般运用的是⾼维数据集,建模时采用线性代数表⽰法会比较⽅便。当输入包括d个特征时,将猜测结果y\hat{y} 表⽰为

y=w1x1+…+wdxd+b\hat{y}=w_{1} x_{1}+\ldots+w_{d} x_{d}+b

将权重运用特征矩阵表明:

y=w⊤x+b.\hat{y}=\mathbf{w}^{\top} \mathbf{x}+b.

线性回归的⽬标是找到⼀组权重向量w和偏置b:当给定从X的同分布中取样的新样本特征时,这组权重向量和偏置能够使得新样本猜测标签的差错尽或许小。

线性回归是⼀个单层神经⽹络

pytorch-简单神经网络

torch自界说简略线性回归

运用torch完成一个简略的线性回归,首先假定有一个已经实在存在的线性函数,运用线性模型参数w=[2,−3.4]Tw=[2, −3.4]^T、b = 4.2 和噪声项⽣成数据集及其标签:

y=Xw+b+\mathbf{y}=\mathbf{X} \mathbf{w}+b+\epsilon

依据此函数生成一个1000样本的数据集

import torch
def synthetic_data(w, b, num_examples):
    """⽣成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w))) # 生成契合正态分布的矩阵
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)
    return X, y.reshape((-1, 1))

X,y组成一个完好数据集,X是特征,y是标签

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

对数据集进行可视化调查,特征与目标值之间的联系

import matplotlib.pyplot as plt
plt.scatter(features[:, (0)].detach().numpy(), labels.detach().numpy(),1)
plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(),1)

pytorch-简单神经网络

练习神经网络之前需求随机初始化权重并将偏置初始化为0。

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
w
tensor([[ 0.0092],
        [-0.0167]], requires_grad=True)

在初始化参数之后,练习模型使命便是更新这些参数,直到这些参数⾜够拟合数据。每次更新都需求核算丢失函数关于模型参数的梯度。有了这个梯度,就能够向减小丢失的⽅向更新每个参数。

接下来,必须界说模型,将模型的输入和参数同模型的输出关联起来,在这个简略的线性模型中,只需求输入特征矩阵X,传入要练习的权重矩阵w与偏置b即可

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

由于需求核算丢失函数的梯度,所以应该先界说丢失函数。这儿运用平方丢失函数。

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

终究界说优化算法更新权重,这儿运用随机梯度下降sgd

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

现在已经预备好了模型练习一切需求的要素,能够完成主要的练习过程部分了。在每次迭代中,读取⼀小批量练习样本,并经过模型来取得⼀组猜测。核算完丢失后开始反向传达,存储每个参数的梯度。终究,调用优化算法sgd来更新模型参数。

  1. 初始化参数
  2. 每个迭代周期(epoch)重复以下练习,直到完成
  • 核算梯度
  • 更新参数(w, b) ← (w, b)

练习模型时要对数据集进行遍历,每次epoch 对悉数样本随机抽取批量样本,并运用它们来更新模型

import random
def data_iter(batch_size, features, labels):
    """随机抽取批次样本"""
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

之后就能够练习模型,挑选模型,界说超参数:学习率,批次,丢失函数,优化器,批次随机抽取样本巨细(batch_size)等

batch_size = 10
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs): # 迭代批次(epoch)
    for X, y in data_iter(batch_size, features, labels): # 每次迭代依据batch巨细随机练习多轮数据
        l = loss(net(X, w, b), y) # 核算丢失函数,X和y的小批量丢失
        l.sum().backward() # 由于l形状是(batch_size,1),⽽不是⼀个标量。l中的一切元素被加到⼀起,并以此核算关于[w,b]的梯度
        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 14.806926
epoch 1, loss 13.858301
...
epoch 3, loss 0.000052
epoch 3, loss 0.000051

能够看到经过多批次(epoch),每批次随机抽取样本进行多轮练习,终究丢失函数收敛到必定值

由于运用的是自己合成的数据集,所以知道真正的参数是什么。因而能够经过比较实在参数和经过练习学到的参数来评估练习的成功程度

print(f'w的估量差错: {true_w - w.reshape(true_w.shape)}')
print(f'b的估量差错: {true_b - b}')
w的估量差错: tensor([ 0.0010, -0.0014], grad_fn=<SubBackward0>)
b的估量差错: tensor([0.0002], grad_fn=<RsubBackward1>)

torch简略线性回归

运用torch的API完成上述简略的线性回归,首先加载数据迭代器

from torch.utils import data
def load_array(data_arrays, batch_size, is_train=True):
    """结构⼀个PyTorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

界说模型

对于规范深度学习模型,能够运用结构的预界说好的层。这使咱们只需重视运用哪些层来结构模型,⽽不必重视层的完成细节。 ⾸先界说⼀个模型变量net,它是⼀个Sequential类的实例。Sequential类将多个层串联在⼀起。当给定输入数据时,Sequential实例将数据传入到第⼀层,然后将第⼀层的输出作为第⼆层的输入,以此类推。

在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.databias.data⽅法拜访参数。

深度学习结构一般有预界说的方法来初始化参数,所以一般不需求手动随机初始化模型参数

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

界说丢失函数

核算均方差错运用的是MSELoss类,也称为平方L2范数。默认情况下,它回来一切样本丢失的平均值。

loss = nn.MSELoss()

界说优化算法

小批量随机梯度下降算法是⼀种优化神经网络的规范⼯具,PyTorchoptim模块中完成了该算法的许多变种。当实例化⼀个SGD实例时,指定优化的参数(可经过net.parameters()从模型中取得)以及优化算法所需的超参数字典。小批量随机梯度下降只需求设置lr值,这⾥设置为0.03。

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

练习模型

之后练习神经网络,对于已界说好的模型一般直接传入模型参数,丢失函数、优化器等超参数直接练习即可

for epoch in range(num_epochs):
    for X, y in load_array((features, labels), batch_size):
        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.000418
epoch 2, loss 0.000102
epoch 3, loss 0.000103

评估练习差错

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.0017, -0.0006])
b的估量差错: tensor([-0.0004])

分类问题

从⼀个图画分类问题开始。假定每次输入是⼀个2 2的灰度图画。咱们能够用⼀个标量表⽰每个像素值,每个图画对应四个特征x1, x2, x3, x4。此外,假定每个图画归于类别“猫”,“鸡”和“狗”中的⼀个。接下来,咱们要挑选如何表⽰标签。有两个显着的挑选:最直接的主意是挑选y ∈ {1, 2, 3},其中整数别离代表{狗, 猫, 鸡}。这是在核算机上存储此类信息的有用⽅法。

假如类别间有⼀些⾃然顺序,⽐如说咱们企图猜测{婴儿, 儿童, ⻘少年, ⻘年人, 中年人, ⽼年人},那么将这个问题转变为回归问题,并且保留这种格式是有意义的。但是⼀般的分类问题并不与类别之间的⾃然顺序有关。幸运的是,统计学家很早以前就发明晰⼀种表⽰分类数据的简略⽅法:独热编码(one-hot encoding)。

独热编码

独热编码是⼀个向量,它的重量和类别⼀样多。类别对应的重量设置为1,其他一切重量设置为0。在上述例⼦中,标签y将是⼀个三维向量,其中(1, 0, 0)对应于“猫”、(0, 1, 0)对应于“鸡”、(0, 0, 1)对应于“狗”:

y∈(1,0,0),(0,1,0),(0,0,1)y ∈ {(1, 0, 0),(0, 1, 0),(0, 0, 1)}

线性模型分类

为了估量一切或许类别的条件概率,需求⼀个有多个输出的模型,每个类别对应⼀个输出。为了处理线性模型的分类问题,需求和输出⼀样多的仿射函数(affine function)。每个输出对应于它⾃⼰的仿射函数。在上述例⼦中,由于有4个特征和3个或许的输出类别,需求12个标量来表⽰权重(带下标的w),3个标量来表⽰偏置(带下标的b)

o1=x1w11+x2w12+x3w13+x4w14+b1,o2=x1w21+x2w22+x3w23+x4,w24+b2o3=x1w31+x2w32+x3w33+x4w34+b3.o_{1}=x_{1} w_{11}+x_{2} w_{12}+x_{3} w_{13}+x_{4} w_{14}+b_{1},\\o_{2}=x_{1} w_{21}+x_{2} w_{22}+x_{3} w_{23}+x_{4},w_{24}+b_{2}\\o_{3}=x_{1} w_{31}+x_{2} w_{32}+x_{3} w_{33}+x_{4} w_{34}+b_{3}.

pytorch-简单神经网络

torch简略Minist分类

MNIST数据集 [LeCun et al., 1998] 是图画分类中⼴泛运用的数据集之⼀,但作为基准数据集过于简略。这儿运用类似但更复杂的Fashion-MNIST数据集 [Xiao et al., 2017]。Fashion-MNIST由10个类别的图画组成,每个类别由练习数据集(train dataset)中的6000张图画和测验数据 集(test dataset)中的1000张图画组成。因而,练习集和测验集别离包括60000和10000张图画。

import torchvision
from torchvision import transforms
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../datasets/minist", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../datasets/minist", train=False, transform=trans, download=True)
len(mnist_train), len(mnist_test)
# (60000, 10000)
mnist_train[0][0].shape
# torch.Size([1, 28, 28])

Fashion-MNIST中包括的10个类别,别离为t-shirt( T恤)、trouser(裤⼦)、pullover(套衫)、dress(连⾐ 裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。

def get_fashion_mnist_labels(labels):
    """回来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):
    """绘制图画列表"""
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = 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)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));

pytorch-简单神经网络

在每次迭代中,数据加载器每次都会读取⼀小批量数据,⼤小为batch_size。经过内置数据迭代器随机打乱了一切样本,从⽽⽆偏⻅地读取⼩批量。

batch_size = 256
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,num_workers=4)
test_iter  = data.DataLoader(mnist_test, batch_size, shuffle=True,num_workers=4)

界说模型

只需在Sequential中增加⼀个带有10个输出的全衔接层,在这⾥Sequential并不是必要的,但它是完成深度模型的根底。咱们依然以均值0和规范差0.01随机初始化权重。

# PyTorch不会隐式地调整输入的形状。因而,在线性层前界说了展平层(flatten),来调整⽹络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
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')

挑选优化器

运用学习率为0.1的⼩批量随机梯度下降作为优化算法

optimizer = torch.optim.SGD(net.parameters(), lr=0.1)

练习模型

界说模型点评目标并练习模型

def train_minist(model, train_iter, loss, num_epochs, optimizer):
    """练习模型"""
    for epoch in range(num_epochs):
        model.train() # 将模型设置为练习形式
        for X, y in train_iter:
            y_predict = model(X) # 核算梯度并更新参数
            l = loss(y_predict, y) # 核算丢失函数
            optimizer.zero_grad() # 梯度清零
            l.mean().backward() # 反向传达
            optimizer.step() # 梯度更新
num_epochs = 10
train_minist(net, train_iter, loss, num_epochs, optimizer)    
from sklearn.metrics import accuracy_score
trues = []
preds = []
for X, y in test_iter:
    trues.extend(get_fashion_mnist_labels(y))
    preds.extend(get_fashion_mnist_labels(net(X).argmax(axis=1)))
print(accuracy_score(trues,preds))
0.8315

一个简略模型就有0.8315准确率

Reference

  1. zh-v2.d2l.ai/chapter_lin…
  2. gitee.com/LSLWIND/mac…