第五周周报

摘要

  本周使用softmax多分类回归对MNIST图片数据集进行了分类预测、感知机多类感知机的概念,与softmax回归的关系、用代码实现多层感知机的训练过程(不使用框架和使用框架),以及模型选择,其中遇到的过拟合和欠拟合的问题,并用代码实现一个(一项多项式、三次多项式、十九次多项式)多项式拟合来表现过拟合、欠拟合的问题。
   This week, I used softmax multi-class regression to perform classification predictions on the MNIST image dataset.I explored the concept of multi-class perceptron and its relationship with softmax regression.I implemented the training process for a multi-layer perceptron using code, both without using any frameworks and with the use of frameworks.I encountered issues related to overfitting and underfitting during the process of model selection.I also used code to implement polynomial fitting with different degrees (linear, cubic, and nineteenth-degree) to demonstrate issues related to overfitting and underfitting.

深度学习

softmax回归代码实现

  用softmax回归实现了一个对图像进行分类预测。
  程序的步骤是数据初始化-小批量数据读取-定义初始化模型参数-定义模型-定义损失函数-定义SGD(优化算法)-训练。

import sys
import os
import torch
import torchvision
from torchvision import transforms
from torch.utils import data
from d2l import torch as d2l
import d2lutil.common as common


os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"  #使用多个python虚拟环境时,可能会出现重复链接的情况,因此设置KMP_DUPLICATE_LIB_OK为true,控制是否允许重复链接。

## 读取小批量数据
batch_size = 256
trans = transforms.ToTensor()
mnist_train  = torchvision.datasets.FashionMNIST(
    root="../data", train=True, transform=trans, download=True) # 这里数据集会自动下载到项目跟目录的/data目录下
mnist_test  = torchvision.datasets.FashionMNIST(
    root="../data", train=False, transform=trans, download=True) # 这里数据集会自动下载到项目跟目录的/data目录下
print(len(mnist_train))  # train_iter的长度是235;说明数据被分成了234组大小为256的数据加上最后一组大小不足256的数据
print('11111111')


## 展示部分数据
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_fashion_mnist(images, labels):
    d2l.use_svg_display()
    # 这里的_表示我们忽略(不使用)的变量
    _, figs = plt.subplots(1, len(images), figsize=(12, 12))
    for f, img, lbl in zip(figs, images, labels):
        f.imshow(img.view((28, 28)).numpy())
        f.set_title(lbl)
        f.axes.get_xaxis().set_visible(False)
        f.axes.get_yaxis().set_visible(False)
    plt.show()


train_data, train_targets = next(iter(data.DataLoader(mnist_train, batch_size=18)))
#展示部分训练数据
show_fashion_mnist(train_data[0:10], train_targets[0:10])

# 初始化模型参数
num_inputs = 784
num_outputs = 10

W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)  #b就是输出维数


# 定义模型
def softmax(X):
    X_exp = X.exp()
    partition = X_exp.sum(dim=1, keepdim=True)
    return X_exp / partition  # 这里应用了广播机制


def net(X):
    return softmax(torch.matmul(X.reshape(-1, num_inputs), W) + b)  #加上b =》广播机制实现偏移


# 定义损失函数
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y = torch.LongTensor([0, 2])
y_hat.gather(1, y.view(-1, 1))


def cross_entropy(y_hat, y):
    return - torch.log(y_hat.gather(1, y.view(-1, 1)))


# 计算分类准确率
def accuracy(y_hat, y):
    return (y_hat.argmax(dim=1) == y).float().mean().item()


# 计算这个训练集的准确率
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
        n += y.shape[0]
    return acc_sum / n


num_epochs, lr = 10, 0.1


# 本函数已保存在d2lzh包中方便以后使用
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
              params=None, lr=None, optimizer=None):
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
        for X, y in train_iter:
            y_hat = net(X)
            l = loss(y_hat, y).sum()

            # 梯度清零
            if params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()

            l.backward()
            # 执行优化方法
            if optimizer is not None:
                optimizer.step()
            else:
                d2l.sgd(params, lr, batch_size)

            train_l_sum += l.item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
            n += y.shape[0]
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
              % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))


# 训练模型
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)

# 预测模型
for X, y in test_iter:
    break
true_labels = get_fashion_mnist_labels(y.numpy())
pred_labels = get_fashion_mnist_labels(net(X).argmax(dim=1).numpy())
titles = [true + '\n' + pred for true, pred in zip(true_labels, pred_labels)]
show_fashion_mnist(X[0:9], titles[0:9])

在这里插入图片描述

在这里插入图片描述

感知机

在这里插入图片描述
  感知机在1957年由Rosenblatt提出,是支持向量机和神经网络的基础。感知机是一种二类分类的线性分类模型,输入为实例的特征向量,输出为实例的类别,正类取1,负类取-1。感知机是一种判别模型,其目标是求得一个能够将数据集中的正实例点和负实例点完全分开的分离超平面。如果数据不是线性可分的,则最后无法获得分离超平面。
如图所示,输入x,w,b,如果对应的函数<w,x>+b的值是正数,则感知机就输出1,否则输出0.
在这里插入图片描述
  训练感知机的算法过程:
最开始声明w和b的值都为0;然后用yi(表示数据的标识,也就是真实值)乘以函数<w,x>+b的值,如果小于等于0,则表示判断错误。因此就需要修正参数w和参数b。
修正w:w=w+yixi,修正b:b=b+yi。
这个过程等价于使用批量大小为1的梯度下降,并使用如下的loss函数:
在这里插入图片描述
  这个函数的意思是如果y<w,x>的值是正数,说明就是判断正确,那么取反,在max中,0就是最大数,返回0,损失函数的值就为0.说明没有损失;而如果y<w,x>的值是负数;则说明判断错误,取反值为正数。max返回loss函数的值就为正数。说明存在损失。

收敛定理
   
假设数据在一个半径r的区域内,又假设有一个余量ρ,使得在这个区域内存在一个分界面
在这里插入图片描述

并且这个分界面对于数据是完全分类正确的,且这个ρ是有一定余量的。由图所示:

在这里插入图片描述
感知机保证在r ^2 +1 /ρ^2步骤后收敛。

收敛定理证明:

    所谓感知机算法的收敛性,就是说只要给定而分类问题是线性可分的,那么按上述感知机算法进行操作,在有限次迭代后一定可以找到一个可将样本完全分类正确的超平面。

首先,为了方便推导,我们将偏置b并入权重向量w,记作w = (wt,b)t,同样将输入向量加以扩充,记作x=(x^t, 1)^t,显然                      
                        在这里插入图片描述

既然样本是线性可分的,我们可以假设有一个满足条件的超平面Wopt将样本点完全正确分开,且存在r>0,对所有i=1,2,…,N,有:
                        在这里插入图片描述

令                         在这里插入图片描述
,则感知机算法在训练集上误分类次数满足:            
                          在这里插入图片描述
证明:
请添加图片描述

XOR函数
在这里插入图片描述
在这里插入图片描述

即异或问题可以分为根据输出可以分为两类,显示在二维坐标系中如上图(右)所示:其中输出结果为1对应右图中红色的十字架,输出为0对应右图中蓝色的圆圈,我们可以发现对于这种情况无法找到一条直线将两类结果分开。
感知机是一种线性分类模型,属于判别模型。而异或问题是线性不可分的,在异或问题的图示中,我们可以发现无法找到一条直线将两类结果分开,即无法找到一个线性模型将其进行划分,所以感知机无法解决异或问题。所以它也导致了AI的第一次寒冬。

多层感知机

为了解决能对异或问题进行分类,出现了多层感知机。比如下图一个模型:4个点。
在这里插入图片描述
对其分类就是先用蓝色平面将13和24分开,取13为‘+’,取23为‘﹣’,然后在用橙色平面分割12和34,取12为‘+’,34为‘﹣’。得到一个表:
在这里插入图片描述
最后的结果是把每一列对应的行的符号做异或运算,相同为‘true’,相异为‘flase’。最后完成分类。
而多层感知机的定义是:
通过在网络中 加入一个或多个隐藏层 来克服线性模型的限制, 使其能处理更普遍的函数关系类型。 要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 我们可以把前 L-1 层看作表示,把最后一层看作线性预测器。 这种架构通常称为多层感知机(multilayer perceptron),通常缩写为MLP。
在这里插入图片描述
其中隐藏层是超参数,因为输入和输出都是由数据或需要拟合的需求决定的,唯一能改变的就是隐藏层的参数。
输入x,x是一个n维的向量。
隐藏层的权重矩阵W1是m*n的矩阵,偏移b1也是一个m维向量。
输出层的权重w2是一个m维的向量。偏移b2是一个实数。
函数h=σ (w1x+b1)
其中σ是按元素的激活函数。
输出函数o = (w2)^t *h+b2
如果没有激活函数的话,从输出到输入,最后的关系始终是线性关系。

sigmod函数
Sigmoid函数将输入值映射到一个范围为(0, 1)的区间,具有平滑的S形曲线。
在这里插入图片描述

sigmoid ⁡ ( x ) = 1 1 + exp ⁡ ( − x ) \operatorname{sigmoid}(x)=\frac{1}{1+\exp (-x)} sigmoid(x)=1+exp(x)1
在这里插入图片描述

tanh函数
tanh函数将输入值映射到一个范围为(-1, 1)的区间,也具有S形曲线。
tanh ⁡ ( x ) = exp ⁡ ( x ) − exp ⁡ ( − x ) exp ⁡ ( x ) + exp ⁡ ( − x ) = 1 − exp ⁡ ( − 2 x ) 1 + exp ⁡ ( − 2 x ) \tanh (x)=\frac{\exp (x)-\exp (-x)}{\exp (x)+\exp (-x)}=\frac{1-\exp (-2 x)}{1+\exp (-2 x)} tanh(x)=exp(x)+exp(x)exp(x)exp(x)=1+exp(2x)1exp(2x)
在这里插入图片描述
弊端:与Sigmoid函数类似,Tanh函数在两端也存在梯度饱和和输出非零均值的问题,会影响神经网络的训练效果。

ReLU函数
函数在输入大于0时返回该输入值,小于等于0时返回0
ReLU ⁡ ( x ) = max ⁡ ( 0 , x ) \operatorname{ReLU}(\mathrm{x})=\max (0, \mathrm{x}) ReLU(x)=max(0,x)
相比 sigmiod和tanh函数,不需做指数运算,节省了运算成本。同时ReLU函数解决了Sigmoid和Tanh函数存在的梯度饱和问题,激活函数的导数在正数区域始终为常数1,可以有效传递梯度。

在这里插入图片描述
多类分类
就在softmax回归的基础上,新增加了隐藏层的概念。在这里插入图片描述
多隐藏层
在这里插入图片描述
对于多隐藏层,其超参数为:1.隐藏层的层数。2.每层隐藏层的大小。多层感知机使用隐藏层和激活函数来得到非线性模型。常使用的激活函数是Sigmoid,Tanh,Relu。对于多分类问题使用softmax来处理。

代码实现


import torch
from torch import nn
from d2l import torch as d2l

batch_size = 256  
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
#小批量导入测试集的数据到迭代器中,批量大小为256维

# 实现一个具有单隐藏层的多层感知机,它包含256个隐藏单元
num_inputs, num_outputs, num_hiddens = 784, 10, 256 # 输入、输出是数据决定的,256是调参自己决定的
W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True))
#randn的意思是随机一个num_inputs*num_hiddens的服从正态分布的矩阵
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True))
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [W1,b1,W2,b2]  # 参数矩阵

# 实现 ReLu 激活函数
def relu(X):
    a = torch.zeros_like(X) # 数据类型、形状都一样,但是值全为 0
    return torch.max(X,a)

# 实现模型
def net(X):
    #print("X.shape:",X.shape)
    X = X.reshape((-1, num_inputs)) # -1为自适应的批量大小,拉成一个二维矩阵
    #print("X.shape:",X.shape)
    H = relu(X @ W1 + b1)
    #@是使用广播机制的乘法  ==matmul()
    #print("H.shape:",H.shape)
    #print("W2.shape:",W2.shape)
    return (H @ W2 + b2)

# 损失
loss = nn.CrossEntropyLoss() # 交叉熵损失

# 多层感知机的训练过程与softmax回归的训练过程完全一样
num_epochs ,lr = 30, 0.1  #循环30次  学习率为0.1
updater = torch.optim.SGD(params, lr=lr)  #传入参数矩阵 和学习率,梯度下降参数
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

输出结果为:
在这里插入图片描述
使用框架实现:

import torch
from torch import nn
from d2l import torch as d2l

# 隐藏层包含256个隐藏单元,并使用了ReLU激活函数
net = nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight,std=0,)
        
net.apply(init_weights)

# 训练过程
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(), lr=lr)

train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

模型选择

模型必须是能够在在先前未观测到的新输入上表现良好,而不只是在训练集上表现良好。在先前未观测到的输入上表现良好的能力被称为泛化。
训练误差:模型在训练数据集上计算得到的误差。
泛化误差:模型在一个新数据(从未见过的数据)上的损失。
通常,我们只关注泛化误差,而不是训练误差;因为泛化误差可以衡量模型在新数据上的泛化能力。
最重要的一点就是:一定要将训练数据集和验证数据集分开。测试数据集理论上只能使用一次,不能使用测试数据集来进行调整参数。

但在实际情况中,没有足够多的数据使用。因此使用k-则交叉验证的算法。
在这里插入图片描述
K折交叉验证,初始采样分割成K个子样本,一个单独的子样本被保留作为验证模型的数据,其他K-1个样本用来训练。交叉验证重复K次,每个子样本验证一次,平均K次的结果,最终得到一个单一估测。「一般 k 为 5或者10」. 损失为 这 k 个的损失之和求平均值。

过拟合和欠拟合

underfitting欠拟合
欠拟合是指模型不能在训练集上获得足够低的误差,更不能很好地泛化新数据的现象。

overfitting过拟合
过拟合是训练误差和测试误差之间的差距太大。
在这里插入图片描述

模型容量是指一个机器学习模型能够拟合复杂函数的能力。它取决于模型所包含的参数数量以及模型的结构。具有更多参数和更复杂结构的模型通常具有更高的容量,可以更好地适应训练数据。

模型容量过低:当模型容量不足以表示数据中的复杂模式时,模型会出现欠拟合现象,无法很好地拟合训练数据。
模型容量过高:当模型容量过高时,模型可能过度拟合训练数据,记住了数据中的噪声和细节,而无法泛化到新数据。

在这里插入图片描述当模型容量比较低的时候,训练损失、泛化误差比较高;随着模型容量的增加,训练损失在不断减少。但是并不是模型容量越高越好,越高意味着复杂度越高,意味着记住更多的信息,意味着记录大量的噪音,这会导致模型去拟合噪音,导致泛化误差变大,需要把握这个度。
代码实现:

# 通过多项式拟合来交互地探索这些概念
import math
import numpy as np
import torch
from torch import nn
from d2l import torch as d2l

max_degree = 20 # 特征为20就是每一个样本是一个[20,1]的tensor
n_train, n_test = 100, 100 # 100个测试样本、100验证样本
true_w = np.zeros(max_degree)
true_w[0:4] = np.array([5,1.2,-3.4,5.6]) # 真实标号为5

features = np.random.normal(size=(n_train+n_test,1))
print(features.shape)
np.random.shuffle(features)
print(np.arange(max_degree))
print(np.arange(max_degree).reshape(1,-1))
print(np.power([[10,20]],[[1,2]]))
poly_features = np.power(features, np.arange(max_degree).reshape(1,-1)) # 对第所有维的特征取0次方、1次方、2次方...19次方  
for i in range(max_degree):
    poly_features[:,i] /= math.gamma(i+1) # i次方的特征除以(i+1)阶乘
labels = np.dot(poly_features,true_w) # 根据多项式生成y,即生成真实的labels
labels += np.random.normal(scale=0.1,size=labels.shape) # 对真实labels加噪音进去

#看一下前两个样本
true_w, features, poly_features, labels = [torch.tensor(x,dtype=torch.float32) for x in [true_w, features, poly_features, labels]]                 
print(features[:2]) # 前两个样本的x
print(poly_features[:2,:]) # 前两个样本的x的所有次方
print(labels[:2])  # 前两个样本的x对应的y

# 实现一个函数来评估模型在给定数据集上的损失
def evaluate_loss(net, data_iter, loss):
    """评估给定数据集上模型的损失"""
    metric = d2l.Accumulator(2) # 两个数的累加器
    for X, y in data_iter: # 从迭代器中拿出对应特征和标签
        out = net(X)
        y = y.reshape(out.shape) # 将真实标签改为网络输出标签的形式,统一形式
        l = loss(out, y) # 计算网络输出的预测值与真实值之间的损失差值
        metric.add(l.sum(), l.numel()) # 总量除以个数,等于平均
    return metric[0] / metric[1] # 返回数据集的平均损失

# 定义训练函数
def train(train_features, test_features, train_labels, test_labels, num_epochs=400):
    loss = nn.MSELoss()
    input_shape = train_features.shape[-1]
    net = nn.Sequential(nn.Linear(input_shape, 1, bias=False)) # 单层线性回归
    batch_size = min(10,train_labels.shape[0])
    train_iter = d2l.load_array((train_features,train_labels.reshape(-1,1)),batch_size)
    test_iter = d2l.load_array((test_features,test_labels.reshape(-1,1)),batch_size,is_train=False)    
    trainer = torch.optim.SGD(net.parameters(),lr=0.01)
    animator = d2l.Animator(xlabel='epoch',ylabel='loss',yscale='log',xlim=[1,num_epochs],ylim=[1e-3,1e2],legend=['train','test'])                   
    for epoch in range(num_epochs):
        d2l.train_epoch_ch3(net, train_iter, loss, trainer)
        if epoch == 0 or (epoch + 1) % 20 == 0:
            animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss), evaluate_loss(net,test_iter,loss)))
    print('weight',net[0].weight.data.numpy()) # 训练完后打印,打印最终学到的weight值  

三阶多项式函数拟合

# 三阶多项式函数拟合(正态)
train(poly_features[:n_train,:4],poly_features[n_train:,:4],labels[:n_train],labels[n_train:])  # 最后返回的weig

一阶多项式函数拟合(欠拟合了)

# 这里相当于用一阶多项式拟合真实的三阶多项式,欠拟合了,损失很高,根本就没降
train(poly_features[:n_train,:2],poly_features[n_train:,:2],labels[:n_train],labels[n_train:])

十九阶多项式函数拟合(过拟合)

# 十九阶多项式函数拟合(过拟合)
# 这里相当于用十九阶多项式拟合真实的三阶多项式,过拟合了
train(poly_features[:n_train,:],poly_features[n_train:,:],labels[:n_train],labels[n_train:])

总结

   本周学习重心放在对softmax、多层感知机、过拟合的代码实现上,其实训练用的代码,步骤都是先进行数据初始化、小批量数据读取-定义初始化模型参数、定义模型、定义损失函数、定义SGD(优化算法)最后训练。但是在pytorch的基础上仍然有太多的缺漏,在学习深度学习的时候,需要同时跟进pytorch、matplotlib、nn、d2l相关库函数的学习。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值