深度学习9. 全连接及Dropout概念

一、基本概念

1. 什么是全连接层

全连接层是神经网络中的一种常见的层类型,也称为密集连接层(Dense Layer)或者全连接层(Fully Connected Layer)。全连接层可以将输入特征与每个神经元之间的连接权重进行矩阵乘法和偏置加法操作,从而得到输出结果。

在全连接层中,每个神经元都与上一层的所有神经元相连,每个输入特征都与每个神经元之间都存在一定的连接权重。在训练过程中,神经网络通过反向传播算法来优化每个神经元的权重和偏置,从而使得输出结果能够更好地拟合训练数据。

2. 全连接层的作用

全连接层的作用是将输入特征映射到输出结果,通常在神经网络的最后一层使用,用于分类、回归等任务。全连接层的输出结果可以看作是对输入特征的一种非线性变换,这种变换可以将输入特征空间映射到输出结果空间,从而实现模型的复杂性和非线性拟合能力。

需要注意的是,全连接层的参数量非常大,因此容易出现过拟合的情况。为了避免过拟合,可以使用一些正则化方法,比如Dropout、L1/L2正则化等。

3. 过拟合的概念

过拟合(overfitting)是指机器学习模型在训练数据集上表现得很好,但是在新的、未见过的数据集上表现得很差的现象。

过拟合的原因一般是模型过于复杂,导致模型在训练数据上过于准确地拟合了噪声和细节,从而失去了泛化能力。

过拟合的表现包括模型在训练集上的损失(或错误率)很小,但在测试集或验证集上的损失很大,或者模型在训练集上的预测效果很好,但是在测试集或验证集上的预测效果很差。

解决过拟合的方法包括:

  • 简化模型结构,如减少模型参数、降低网络层数等;
  • 增加数据量,如采集更多的数据或使用数据增强技术;
  • 添加正则化,如L1、L2正则化、dropout等;
  • 早停法(early stopping),即在模型在验证集上表现开始下降时停止训练。

4. Dropout介绍

Dropout是一种常用的正则化方法,用于减少神经网络的过拟合现象。它的基本思想是在训练神经网络的过程中,随机地将一部分神经元的输出值置为0,从而使得神经网络的结构变得不稳定,从而强制网络学习到更加鲁棒的特征表示。

具体来说,Dropout在训练过程中,对于每个神经元以一定的概率进行保留或者丢弃。通常情况下,保留概率为 p p p,丢弃概率为 1 − p 1-p 1p,可以通过一些随机采样的方法来实现。在前向传播过程中,被丢弃的神经元的输出值会被置为0;在反向传播过程中,被丢弃的神经元也不参与误差反向传播,从而减少神经网络的参数量和模型复杂度,进而避免过拟合。

Dropout的好处是可以让神经网络学习到更加鲁棒的特征表示,从而提高模型的泛化能力。同时,Dropout还可以起到一定的正则化作用,使得神经网络中的权重参数更加平滑,进一步减少过拟合的风险。

需要注意的是,Dropout只在训练过程中使用,在测试过程中应该关闭Dropout,以便得到更加准确的输出结果。

5. L1/L2正则化简介

(1)L1

L1(也称为Lasso)正则化是指在神经网络的损失函数中添加一个L1范数惩罚项,用来惩罚模型权重参数中的大值,使得模型权重变得更加稀疏。L1正则化的效果是使得模型中的一些无关紧要的特征的权重变为0,从而减小模型的复杂度。
L1正则化的公式为: L 1 ( w ) = ∑ i = 1 n ∣ w i ∣ L1(w) = \sum_{i=1}^{n}|w_i| L1(w)=i=1nwi

(2)L2

L2正则化是指在神经网络的损失函数中添加一个L2范数惩罚项,用来惩罚模型权重参数的平方和。L2正则化的效果是让所有的权重都尽可能小,从而减小模型的复杂度。

L2正则化的公式为: L 2 ( w ) = ∑ i = 1 n ∣ w i ∣ 2 L2(w) = \sum_{i=1}^{n}|w_i|^2 L2(w)=i=1nwi2

在实际应用中,L1和L2正则化可以分别通过在损失函数中添加对应的惩罚项来实现。在优化过程中,加上正则化项对应的梯度会影响权重的更新,使得权重不会过大。同时,正则化项的权重需要根据实际情况进行调整,以充分发挥正则化的作用。

二、Python简单实现

1. Python实现全连接层

下面的代码里实现了一个简单的全连接层,以更容易理解全连接层。

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt


class DenseLayer:
    def __init__(self, input_size, output_size):
        self.weights = np.random.randn(input_size, output_size)
        self.bias = np.zeros((1, output_size))
        self.inputs = None
        self.outputs = None

    def forward(self, inputs):
        self.inputs = inputs
        self.outputs = np.dot(self.inputs, self.weights) + self.bias
        return self.outputs


# 定义一个两层的神经网络模型
input_size = 784 * 3
hidden_size = 100
output_size = 10

layer1 = DenseLayer(input_size, hidden_size)
layer2 = DenseLayer(hidden_size, output_size)

# 读取一张图片
img = Image.open('example_pool.jpg').convert('RGB')
img = img.resize((28, 28))
# 将 PIL.Image 对象转换成 numpy 数组,并且将像素值缩放到 [0, 1] 的范围内
data = np.array(img, dtype=np.float32).reshape((1, 784*3)) / 255.0

# 前向传播
h = layer1.forward(data)
y = layer2.forward(h)

# 使用softmax函数将输出的数值转化为概率
y = np.exp(y - np.max(y, axis=1, keepdims=True))
y = y / np.sum(y, axis=1, keepdims=True)

# 将输出结果绘制成条形图
result = np.argmax(y)
labels = [str(i) for i in range(10)]
plt.bar(labels, y.flatten())
plt.title('Output from Neural Network')
plt.xlabel('Digit')
plt.ylabel('Output Probability')
plt.show()


这里实现的简单的全连接,只写了前向传播,没有定义后向传播。
从前向传播的构造方法中,可以看出使用了一个单层感知机,是一个线性的神经元。

2. Python实现 Dropout

下面定义的 Dropout 类,其构造函数接收一个 dropout_rate 参数表示要 dropout 的比例。
forward 方法接收一个输入 x,如果是训练状态,则生成一个掩码 mask 并将其应用到输入上,输出结果。如果是测试状态,则直接输出输入结果。

backward 方法接收一个梯度 dout,将其乘以之前生成的掩码 mask 后输出。本程序没有使用反向传播。

np.random.binomial 用于生成一个二项分布的数组,值为 0 或 1。
其中的参数 1 - self.dropout_rate 表示成功概率为 1 - dropout_rate。数组中的每个元素代表输入中对应位置的值是否应该被保留。最后除以 (1 - self.dropout_rate) 是为了保证训练时输出结果的期望与测试时相同。

import numpy as np

# 定义 Dropout 类
class Dropout:
    def __init__(self, dropout_rate=0.5):
        self.dropout_rate = dropout_rate
        self.mask = None

    def forward(self, x, is_train=True):
        if is_train:
            # 生成掩码
            self.mask = np.random.binomial(1, self.dropout_rate, size=x.shape)
            return x * self.mask / self.dropout_rate
        else:
            return x

    def backward(self, dout):
        return dout * self.mask / self.dropout_rate

# 使用 Dropout 的示例
dropout_rate = 0.5
x = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0]])
dropout = Dropout(dropout_rate)

# 训练阶段使用 Dropout
x_train = dropout.forward(x, is_train=True)
print('Training output:', x_train)

# 推理阶段不使用 Dropout
x_test = dropout.forward(x, is_train=False)
print('Test output:', x_test)


运行示例:

Training output: [[ 0.  0.  0.  8.]
 [10. 12.  0.  0.]]
Test output: [[1. 2. 3. 4.]
 [5. 6. 7. 8.]]

可以看到在训练阶段,部分元素赋了0,而在测试阶段,输出与输入相同。

三、使用PyTorch

在使用全连接层时,需要将每个样本的输入数据展平为一维向量,以便于输入到全连接层中进行处理。

在PyTorch中,每个batch的数据的形状为(batch_size, channels, height, width),需要使用view()方法将其转化为(batch_size, input_size),其中input_size表示展平后的向量长度,即channels * height * width。

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor
from tqdm import tqdm


# 定义全连接神经网络模型
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, 10)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x


# 加载 MNIST 数据集
train_dataset = MNIST(root='data/', train=True, transform=ToTensor(), download=True)
test_dataset = MNIST(root='data/', train=False, transform=ToTensor(), download=True)

# 创建数据加载器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# 创建模型、损失函数和优化器
model = Net()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

# 训练模型
num_epochs = 10
for epoch in range(num_epochs):
    for images, labels in tqdm(train_loader):
        # 将输入数据拉平为一维向量
        images = images.view(images.size(0), -1)

        # 向前传播、计算损失和梯度
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()

        # 更新参数
        optimizer.step()
        optimizer.zero_grad()

    # 在测试集上评估模型性能
    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.view(images.size(0), -1)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    accuracy = correct / total
    print('Epoch [{}/{}], Accuracy: {:.2f}%'.format(epoch+1, num_epochs, accuracy*100))

print('Training finished.')

代码要点:

  • 上面示例使用了MNIST数据集;
  • 训练每个批次大小64;
  • tqdm是一个在命令行显示训练进度的库;
  • 每个批次的数据 , images是一个大小为(batch_size, 1, 28, 28)的张量,labels是一个大小为(batch_size,)的张量,表示对应的图像的标签;
  • images.view(images.size(0), -1):将images变换为(batch_size, input_size)的形状。其中,images.size(0)表示batch_size的值,而-1表示剩下的维度根据原有数据的形状自动计算得出。

程序运行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xundh/article/details/129578302