线性回归

线性模型

假设自变量x和因变量y的关系是线性的,则y可以表示为x的加权和

目的是求出合适的权重w和偏置b建立模型进行预测

在高维数据集中表示为

其中为y的估计值

使用向量表示wx为:

这里只需将w最后1列加入1 x中加入b 就可以将b合并进去得到:这在矩阵中也可以使用

损失函数

用于量化实际值与预测值之间的差距

常用平方误差函数: |添加系数 使求导后系数为1

度量模型在整个数据集的质量,提出损失均值:对上方n个样本的求和求均值:

展开求偏导:

为了最小化损失, 令偏导为0求最小值:

即:

小批量随机梯度下降

其中

线性回归 的简洁实现

获取数据集

假设数据集和真实的权重true_w和偏置true_b,用synthetic_data生成随机特征和对应的标签

#
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)

读取数据集

TensorDataset将features和labels组合起来的元组(features[i],label[i]),方便DataLoader()使用
由于样本数量较大, 这里使用DataLoader生成一个迭代器,每次返回批次大小batch_size的样本数量

from torch.utils import data
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)
for X,y in data_iter:
	print(X,y)

这里的data_iter是可迭代对象,可以使用iter(data_iter)转化为迭代器,使用next(iter(data_iter))依次获取1-10,11-20,21-30…的值

定义模型

在定义模型前先了解以下层和神经元

层和神经元

层是神经网络的基本构建块,从输入层输入数据,经过中间多个层,最后从输出层输出数据,每个层接受上一层的结果作为输入,输出的结果作为下一层的输入

  • 常见层类型
    • 全连接层(Fully Connected Layer / Linear Layer):每个输入与每个输出都有连接,通常用矩阵-向量乘法来表示。输入与权重矩阵相乘并加上偏置,最终经过激活函数输出。
    • 卷积层(Convolutional Layer):主要用于处理图像数据,通过卷积操作提取特征。
    • 池化层(Pooling Layer):用于减少数据的维度,通常跟在卷积层之后,降低计算复杂度并减少过拟合。
    • 激活层(Activation Layer):应用非线性函数(如 ReLU、Sigmoid 等)来引入非线性特性,使模型能够学习复杂的模式。
    • 归一化层(Normalization Layer):如批归一化(Batch Normalization),有助于提高训练稳定性,加速收敛。
    • 循环层(Recurrent Layer):用于处理序列数据,像 LSTM 和 GRU 这样的层能够处理时间序列数据。

每一层由多个神经元组成,每个神经元用于计算,将输入的数据进行加权和后输出
这里定义了一个Sequential,是层的集合,用于将层按顺序排列,自动按顺序执行每个层,

from torch import nn
net = nn.Sequential(nn.Linear(2, 1))#只有一层 输入两个特征,输出一个特征
#拓展
net1 = nn.Sequential(
					 nn.Linear(2, 3),#全连接层 输入两个特征,输出三个特征
					 nn.ReLU(), # 激活层,增加神经网络的非线性
					 nn.Linear(3, 1) #全连接层 输入三个特征,输出1个特征
)

初始化模型参数

这里的net[0]是神经网络的第一层,即nn.Linear(2, 1)
weight是他的权重,data是访问权重的原始数据,normal_是服从均值为0 标准差为0.01 的正态分布初始化weight,避免所有神经元的输出都相同,从而避免梯度消失或梯度爆炸的问题。
bias是偏置, fill_将偏置填充为0,偏置的作用是作为一个常数项,不需要特别的初始化,设为0是常见做法零初始化

  • 其他的初始化策略
    • 零初始化
    • 正态分布初始化
    • 均匀分布初始化
    • Xavier 初始化
    • He 初始化
    • LeCun 初始化
    • Orthogonal 初始化
    • 自适应初始化
    • 常数初始化
    • Sparse Initialization
    • Lecun Normal 和 Variance Scaling Initialization
    • Weight Norm Initialization
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

定义损失函数

MSELoss是一个用于回归任务的标准损失函数
计算均方误差使用的是MSELoss类(Mean Squared Error,MSE)
这里的loss_fn是生成了一个损失函数可以计算预测值与实际值的平方差的均值

  • 其他损失函数
    • 回归问题:使用 MSESmoothL1Huber Loss 等。
    • 分类问题:使用 Cross-Entropy LossBCE Loss 等。
    • 特殊任务:使用 KL Divergence LossCosine Embedding Loss
loss_fn = nn.MSELoss()
loss = loss_fn(output,labels)

定义优化器

小批量随机梯度下降算法是一种优化神经网络的标准工具
torch.optim.SGD:(Stochastic Gradient Descent, SGD)是pytorch中用于实现随机梯度下降的优化器
params: 是要优化的参数,通常传入net.parameters()作为参数
lr: (learning rate)学习率 用于控制每次更新的步长。较小的学习率可能导致收敛速度慢,而较大的学习率可能导致跳过最优解
momentum:动量w(Momentum),用于加速梯度下降,并避免在鞍点附近震荡。默认值为 0
dampening: 动量的衰减因子。默认值为 0
weight_decay: 权重衰减(L2 正则化),用于防止过拟合。默认值为 0
nesterov: 是否使用 Nesterov 动量,True 表示使用。默认值为 False

trainer = torch.optim.SGD(net.parameters(), lr=0.03)
#拓展
optimizer = torch.optim.SGD(params, lr=0.01, momentum=0, dampening=0, weight_decay=0, nesterov=False)
 

训练

num_epochs = 3 # 训练3轮
for epoch in range(num_epochs):
    for X, y in data_iter: # 获取特征和标签
        l = loss(net(X) ,y) # net(X)即y的预测值,计算损失,前向传播
        trainer.zero_grad() # 清除之前的梯度
        l.backward() # 反向传播,利用链式法则求损失对参数的梯度
        trainer.step() # 优化参数
    l = loss(net(features), labels) # 计算这次训练后整个数据集的损失
    print(f'epoch {epoch + 1}, loss {l:f}') # 展示优化进度

Softmax回归

在之前的线性回归中目的是预测一个线性的数值通常是标量,
softmax回归则是输出多个类别的logit 通过softmax函数转化为每个类别的概率,进行分类

softmax函数


其中
e保证函数可导且概率不为负,求和保证概率之和为1

小批量样本的矢量化

O=XW+b
X(B,D)B:batch_size;D:features_size;q:categories_size
W(D,q)
b(1,q)
这里XW+b使用了广播机制将(1,q)复制为(B,q)
O(B,q)

损失函数

softmax输出的是一个条件概率的向量:
y:=[ | P(y=j|x):在x发生的情况下是j的可能性是多少 ]

最大似然函数

X是输入特征集,Y是标签集:是独热编码
对于样本i,模型预测该样本属于标签的概率是P()
由于样本与样本之间独立,所以可以将概率相乘得到数据集的联合条件概率,即似然函数:
这里越接近1则说明模型预测的越好

最小负对数似然

由于上面的似然函数是多个概率的相乘,连乘会导致数值很小,不方便优化
使用对函数两边求对数,log(连乘)可以变成连加log(),
由于在log(x)当0<x<1时,函数为负数,为方便优化,我们计算负对数似然

从信息论基础到信息量\熵\交叉熵\交叉熵损失

信息量:=用来衡量一件事情有多”意外”,如果一件事情总是发生,则它没有带来信息量,概率越低,信息量越大:不常见的事情带来更大的信息量,反之也是
:,即信息量的加权平均,反映从一个分布中抽取一个事件所带来的信息量.可以想象为“知道真实概率的人所经历的惊异程度”
交叉熵:在知道真实分布为P(x),预测分布为Q(x)来编码信息时,所需要的平均信息量,在这里可以理解为对模型预测的惩罚,P(x)越接近Q(x),则交叉熵越小,反之越大,==可以想象为“主观概率为Q的观察者在看到根据概率P生成的数据时的预期惊异”==

交叉熵损失:由于这是个分类问题,真实分布只有一个为1,其余为0,例如
一个样本为[鸡,狗,羊]的真实概率为[0,1,0],即,该样本是狗.
那么该样本的求和项中的i=1,i=3项为0,只剩下i=2项,所以交叉熵损失函数可以简化为

图像分类数据集

使用torchvision下载Fashion-MNIST数据集

Fashion-MINST是有10个类别的 灰度图像数据集

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
#    将灰度图片的像素值从0-255归一化到0-1
#    改变数据的通道顺序(高度, 宽度, 通道)到(通道, 高度, 宽度)
trans = transforms.ToTensor()
# 获取训练集和测试集
#    FashionMINST较为常用,框架内置,导入其他数据集可以使用ImageFolder
#    root指定数据集所在的文件夹路径
#    transform指定预处理的方法
#    train指定训练集还是测试集
#    download设置数据是否下载,如果root中没有数据则自动下载,false则在没有数据时报错
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)
#该数据集包括10个类别,每个类别6000个训练集,1000个测试集
len(mnist_train), len(mnist_test)
#输出
(60000, 10000)
#这里获取第一个数据的第一个图片,[0][1]则为对应的label
mnist_train[0][0].shape
#输出
torch.Size([1, 28, 28])

由于类别以数字分类,这里使用一个函数获取对应数字的标签名称

# 输入[1,5]则输出['t-shirt','coat']
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]

使用Dataloader()从数据集随机抽取批量大小的数据

#num_worker设置使用的进程数
batch-size = 256
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                             num_workers=4)

SoftMax回归 的简洁实现

初始化参数

batch_size = 256  
num_inputs = 784  
num_outputs = 10
num_epochs = 20
def get_dataloader_workers():  
    """设置进程数"""  
    return 0

获取Fashion MINST 数据集

def load_data_fashion_mnist(batch_size, resize=None):  
    """下载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(batch_size)

创建神经网络

# 创建神经网络  
net = nn.Sequential(  
    nn.Flatten(),  
    nn.Linear(num_inputs, num_outputs),  
)

初始化权重参数

def init_weights(m):  
    """初始化权重"""  
    if type(m) == nn.Linear:  
        nn.init.normal_(m.weight, std=0.01)# 均值mean默认0.标准差0.01
  
# 应用权重  
net.apply(init_weights)

设置损失计算方法

# 使用交叉熵损失  
# reduction-->如何对损失值进行聚合 none sum meanloss
nn.CrossEntropyLoss(reduction='none')

设置优化方法

# 使用SGD梯度下降优化器 学习率为0.1  
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

定义评估方法

def evaluate_accuracy(net, data_iter):  
    # 模型切换到评估模式  
    net.eval()  
    acc, num_samples = 0, 0  
    with torch.no_grad():  # 禁用梯度计算  
        for X, y in data_iter:  
            X, y = X.to('cpu'), y.to('cpu')  # 改为 'cuda' 如果有 GPU            y_hat = net(X)  
            acc += (y_hat.argmax(dim=1) == y).sum().item()  
            num_samples += y.shape[0]  
    return acc / num_samples

定义训练方法

def train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer):  
    # 切换到训练模式,eval()为测试模式  
    net.train()  
    for epoch in range(num_epochs):  
        # 每个轮次开始时初始化数据  
        train_loss, train_acc, num_samples = 0, 0, 0  
        for X, y in train_iter:  
            # 将数据移动到 GPU(如果可用)  
            X, y = X.to('cpu'), y.to('cpu')  # 改为 'cuda' 如果你有 GPU  
            # 前向传播  
            y_hat = net(X)  
            # 计算损失  
            l = loss(y_hat, y).sum()  
  
            # 清除梯度  
            trainer.zero_grad()  
            # 反向传播  
            l.backward()  
            # 优化器更新参数  
            trainer.step()  
  
            # 累计训练损失和正确预测数量  
            train_loss += l.item()  
            train_acc += (y_hat.argmax(dim=1) == y).sum().item()  
            num_samples += y.shape[0]  
  
        # 计算测试集准确率  
        test_acc = evaluate_accuracy(net, test_iter)  
  
        print(f'epoch {epoch + 1}, loss {train_loss / num_samples:.4f}, '  
              f'train acc {train_acc / num_samples:.4f}, test acc {test_acc:.4f}')

开始训练

train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

输出

epoch 1, loss 91.0865, train acc 0.6838, test acc 0.7958
epoch 2, loss 48.5813, train acc 0.7607, test acc 0.8081
epoch 3, loss 46.8508, train acc 0.7708, test acc 0.8207
epoch 4, loss 43.4931, train acc 0.7793, test acc 0.8028
epoch 5, loss 39.7991, train acc 0.7850, test acc 0.8100
epoch 6, loss 41.8360, train acc 0.7812, test acc 0.7854
epoch 7, loss 38.8658, train acc 0.7934, test acc 0.8395
epoch 8, loss 36.4773, train acc 0.7935, test acc 0.7982
epoch 9, loss 38.6046, train acc 0.7927, test acc 0.8193
epoch 10, loss 36.8179, train acc 0.7929, test acc 0.8390
epoch 11, loss 35.7401, train acc 0.7977, test acc 0.8211
epoch 12, loss 37.6420, train acc 0.7981, test acc 0.8024
epoch 13, loss 35.7540, train acc 0.8003, test acc 0.8206
epoch 14, loss 35.4840, train acc 0.7990, test acc 0.8049
epoch 15, loss 36.7229, train acc 0.7934, test acc 0.8259
epoch 16, loss 34.4000, train acc 0.8032, test acc 0.8262
epoch 17, loss 37.9761, train acc 0.7995, test acc 0.8340
epoch 18, loss 34.0396, train acc 0.8033, test acc 0.7949
epoch 19, loss 35.5815, train acc 0.8002, test acc 0.8350
epoch 20, loss 35.2162, train acc 0.8036, test acc 0.8316