【深度学习】(六)卷积神经网络 循环神经网络
一、卷积神经网络组成
一个典型的CNN通常由以下三种基本层交替堆叠而成:
1. 卷积层
- 利用多个可学习的卷积核在输入数据上滑动并进行点积运算,生成特征图
- 每个卷积核负责提取某种特定的局部模式(如边缘、纹理、形状部件)
- 局部连接:每个神经元仅与上一层的局部区域相连,大幅减少参数量
- 权值共享:同一卷积核在整个输入空间使用相同的参数,进一步降低模型复杂度
2. 池化层
- 对特征图进行下采样,通常采用最大池化或平均池化
- 作用:降低特征图的空间维度、扩大感受野、增强平移不变性、抑制过拟合
3. 全连接层
- 位于网络末端,将卷积层提取的高层特征展平后进行非线性组合
- 最终通过softmax或sigmoid输出分类或回归结果
二、卷积层
1 基础卷积运算
卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。



通过上面的卷积计算过程,最终的特征图比原始图像小很多,如果想要保持经过卷积后的图像大小不变, 可以在原图周围添加 Padding 来实现。
Padding(填充)操作是在输入特征图的边界周围添加额外像素(通常是0)。

Padding的主要作用:
- **保持空间维度:**如果不使用 padding,每次卷积操作后,特征图的尺寸都会缩小。多次卷积后,特征图会变得非常小,可能会丢失重要的边缘信息。Padding可以帮助维持输出特征图的尺寸与输入相同或接近相同。
- **保留边缘信息:**图像边缘的像素在卷积过程中参与的计算次数较少,这意味着边缘信息在特征提取过程中容易丢失。Padding通过在边缘添加额外的像素,增加了边缘像素的参与度,从而更好地保留了边缘信息。
- **提高性能:**Padding有助于避免由于特征图尺寸快速缩小而导致的信息丢失,从而提高模型的性能,尤其是在处理较小的图像或需要进行多层卷积时。
Padding的类型:
- Valid Padding (No Padding): 不进行任何填充。卷积核只在输入图像的有效区域内滑动。输出尺寸会缩小。
- Same Padding: 添加足够的填充,使得输出特征图的尺寸与输入相同。
- Full Padding: 尽可能多地添加填充,使得卷积核的每个元素都至少在输入图像上滑动一次。输出尺寸会增大。
**Padding的选择:**取决于具体的应用场景和网络架构
- Valid Padding: 适用于不需要保持输出尺寸的场景,或者输入图像足够大,边缘信息丢失不重要的情况。
- Same Padding: 广泛应用于各种CNN架构中,因为它可以保持特征图的尺寸,方便网络设计和计算。
- Full Padding: 较少使用,因为它会增加计算量,并且可能会在边缘引入一些伪影。
Stride(步长)指的是卷积核在图像上滑动时的步伐大小,即每次卷积时卷积核在图像中向右(或向下)移动的像素数。步长直接影响卷积操作后输出特征图的尺寸,以及计算量和模型的特征提取能力。


Stride的作用:
- **降低计算复杂度:**更大的步长意味着卷积核移动的次数更少,从而减少了计算量,并加快了训练和推理速度。
- 减1长越大,生成的特征图尺寸越小。这类似于池化的降维效果。
- **增大感受野:**虽然更大的步长会减小特征图的尺寸,但它同时也会增大每个神经元在输入数据上的感受野。这意味着每个神经元能够捕捉到更大范围的输入信息。
**Stride的选择:**取决于具体的应用场景和网络架构
- Stride = 1: 这是最常见的设置,尤其是在网络的早期层。它允许保留更多的空间细节。
- Stride > 1: 通常用于减小特征图的尺寸和增大感受野,例如在网络的后期层或需要进行快速降维时。 常见的设置包括 stride=2 或 stride=4。
2 多通道卷积运算

- 当输入有多个通道(Channel), 例如 RGB 三个通道, 此时要求卷积核需要拥有相同的通道数(图像有多少通道,每个卷积核就有多少通道).
- 每个卷积核通道与对应的输入图像的各个通道进行卷积.
- 将每个通道的卷积结果按位相加得到最终的特征图.
3 多卷积核运算

4 特征图大小
N=W−F+2ps+1 N = \frac{W-F+2p}{s} + 1 N=sW−F+2p+1
| 符号 | 含义 |
|---|---|
| $ N $ | 输出特征图的大小 |
| WWW | 输入特征图的大小 |
| $ s $ | 步长(stride) |
| $ p $ | 填充(padding) |
5 核心API
conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
"""
参数说明:
in_channels: 输入通道数,RGB图片一般是3
out_channels: 输出通道,也可以理解为卷积核kernel的数量
kernel_size:卷积核的高和宽设置,一般为3,5,7...
stride:卷积核移动的步长
整数stride:表示在所有维度上使用相同的步长 stride=2 表示在水平和垂直方向上每次移动2个像素
元组stride: 允许在不同维度上设置不同的步长 stride=(2, 1) 表示在水平方向上步长为2,在垂直方向上步长为1
padding:在四周加入padding的数量,默认补0
padding=0:不进行填充。
padding=1:在每个维度上填充 1 个像素(常用于保持输出尺寸与输入相同 padding=输入形状大小-输出形状大小)。
padding='same'(从 PyTorch 1.9+ 开始支持):让输出特征图的尺寸与输入保持一致。PyTorch会自动计算需要的填充量。stride必须等于1,不支持跨行,因为计算padding时可能出现小数
padding=kernel_size-1:Full Padding 完全填充
"""
示例:
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 对图像进行卷积
def cnn_demo():
# 读取图像
img1 = plt.imread('./data/img.png')
print('img1.shape', img1.shape) # 假设 HWC (640, 640, 4)
# 转换成 CHW,然后增加一个维度
img2 = torch.tensor(img1, dtype=torch.float)
img2 = img2.permute(2, 0, 1)
img3 = img2.unsqueeze(0)
print('img2.shape', img2.shape) # ([4, 640, 640])
print('img3.shape', img3.shape) # ([1, 4, 640, 640])
# 定义卷积层
conv = nn.Conv2d(in_channels=4, out_channels=6, kernel_size=3, stride=2, padding=0)
# 进行卷积运算
conv_img = conv(img3)
img4 = conv_img[0]
print('conv_img.shape', conv_img.shape) # ([1, 6, 319, 319])
print('img4.shape', img4.shape) # ([6, 319, 319])
# (H-F+2p)/s + 1 = (640-3+0)/2 + 1 = 319
# 转HWC
img5 = img4.permute(1, 2, 0)
print('img5.shape', img5.shape) # ([319, 319, 6])
# 可视化
plt.imshow(img5[:, :, 3].detach().numpy())
plt.show()
if __name__ == '__main__':
cnn_demo()
输出
img1.shape (640, 640, 4)
img2.shape torch.Size([4, 640, 640])
img3.shape torch.Size([1, 4, 640, 640])
conv_img.shape torch.Size([1, 6, 319, 319])
img4.shape torch.Size([6, 319, 319])
img5.shape torch.Size([319, 319, 6])
三、池化层
池化层是卷积神经网络中常见的操作,简单来说,它是一种降采样手段。
它的核心逻辑是:用一个固定大小的窗口(如 2×2),在输入数据上滑动,将窗口内的多个值浓缩成一个值输出。
根据浓缩方式的不同,主要分为两种:
- 最大池化:取窗口内的最大值。
- 特点:提取最显著的特征(如边缘、纹理)。这是最常用的池化方式。
- 效果:保留纹理信息,对噪声有一定的容忍度。
- 平均池化:取窗口内的平均值。
- 特点:保留背景信息,平滑图像。
- 效果:常用于网络深层,或者全局池化(将整个特征图压缩成一个点)时。
示例:
import torch
import torch.nn as nn
from torchvision.datasets import CIFAR10
from torchvision.transforms import ToTensor
import torch.optim as optim
from torch.utils.data import DataLoader
import time
import matplotlib.pyplot as plt
from torchsummary import summary
# 每批次样本数
BATCH_SIZE = 8
# CUDA加速
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def create_dataset():
"""
准备数据集
"""
# 参1: 数据集路径. 参2: 是否是训练集. 参3: 数据预处理 -> 张量数据. 参4: 是否联网下载
train_dataset = CIFAR10(root='./data', train=True, transform=ToTensor(), download=True)
test_dataset = CIFAR10(root='./data', train=False, transform=ToTensor(), download=True)
return train_dataset, test_dataset
class ImageModel(nn.Module):
"""
搭建神经网络
"""
def __init__(self):
super(ImageModel, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 3, 1, 0)
self.pool1 = nn.MaxPool2d(2, 2, 0)
self.conv2 = nn.Conv2d(6, 16, 3, 1, 0)
self.pool2 = nn.MaxPool2d(2, 2, 0)
self.linear1 = nn.Linear(576, 120)
self.linear2 = nn.Linear(120, 84)
self.output = nn.Linear(84, 10)
def forward(self, x):
x = self.pool1(torch.relu(self.conv1(x)))
x = self.pool2(torch.relu(self.conv2(x)))
x = x.reshape(x.size(0), -1) # 8行576列
x = torch.relu(self.linear1(x))
x = torch.relu(self.linear2(x))
return self.output(x)
def train(train_dataset):
"""
模型训练
"""
dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
model = ImageModel()
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-3)
epochs = 50
for epoch in range(epochs):
total_loss, total_samples, total_correct, start = 0.0, 0, 0, time.time()
for x, y in dataloader:
x, y = x.to(device), y.to(device)
model.train()
y_pred = model(x)
loss = criterion(y_pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_correct += (torch.argmax(y_pred, dim=-1) == y).sum()
total_loss += loss.item() * len(y)
total_samples += len(y)
print(f'epoch: {epoch + 1}, loss: {total_loss / total_samples:.5f}, acc:{total_correct / total_samples:.2f}, time:{time.time() - start:.2f}s')
torch.save(model.cpu().state_dict(), './model/image_model.pth')
model.to(device)
def evaluate(test_dataset):
"""
模型测试
"""
dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)
model = ImageModel()
model = model.to(device)
model.load_state_dict(torch.load('./model/image_model.pth')) # pickle文件
total_correct, total_samples = 0, 0
for x, y in dataloader:
x, y = x.to(device), y.to(device)
model.eval()
y_pred = model(x)
y_pred = torch.argmax(y_pred, dim=-1)
total_correct += (y_pred == y).sum()
total_samples += len(y)
print(f'Acc: {total_correct / total_samples:.2f}')
if __name__ == '__main__':
train_dataset, test_dataset = create_dataset()
print(f"数据集: {train_dataset.data.shape}") # (50000, 32, 32, 3)
print(f"测试集: {test_dataset.data.shape}") # (10000, 32, 32, 3)
print(f"特征类别: {train_dataset.class_to_idx}") # {'airplane': 0, 'automobile': 1, 'bird': 2, 'cat': 3, 'deer': 4, 'dog': 5, 'frog': 6, 'horse': 7, 'ship': 8, 'truck': 9}
train(train_dataset)
evaluate(test_dataset)
四、循环神经网络
循环神经网络是一类专门用于处理序列数据的神经网络。它的核心思想是:网络会对每一步处理的信息“记忆”下来,并用于当前步骤的计算。
简单来说,你可以把它想象成一个有“记忆力”的人:你在读一句话时,不是孤立地看每个字,而是带着对前面字的理解来读下一个字。

传统神经网络(如全连接网络)假设输入之间相互独立,但RNN通过在网络中引入循环来打破这一限制。
- 循环:网络在处理序列的下一个元素时,会将上一个元素的输出(或“状态”)作为输入的一部分。
- 隐藏状态:这是RNN的“记忆单元”。在每一步 ttt,隐藏状态 hth_tht 会同时接收当前输入 xtx_txt 和上一步的隐藏状态 ht−1h_{t−1}ht−1,并计算出新的状态。
如果展开这个循环,它就像是一个链条结构,多个相同的网络模块被串联起来,信息沿着链条一步步传递。
RNN 主要用于:
- 自然语言处理(NLP):文本生成、语言建模、机器翻译、情感分析等。
- 时间序列预测:股市预测、气象预测、传感器数据分析等。
- 语音识别:将语音信号转换为文字。
- 音乐生成:通过学习音乐的时序模式来生成新乐曲。
五、词嵌入层
词嵌入层的主要目的是将每个词映射为一个固定长度的向量(将文本转换为向量),这些向量能够捕捉词与词之间的语义关系。
传统的文本表示方法(如one-hot编码)无法反映单词之间的相似性,因为在one-hot编码中,每个单词都被表示为一个高维稀疏向量,而词嵌入通过低维稠密向量表示单词,能够更好地捕捉词汇之间的语义相似性。
词嵌入层首先会根据输入的词的数量构建一个词向量矩阵,例如: 我们有 100 个词,每个词希望转换成 128 维度的向量,那么构建的矩阵形状即为: 100*128,输入的每个词都对应了一个该矩阵中的一个向量。
词嵌入层工作流程
-
初始化词向量:词嵌入层的初始词向量通常会使用随机初始化或者通过加载预训练的词向量(如Word2Vec或GloVe)进行初始化。
-
输入索引:每个单词在词汇表中都有一个唯一的索引。输入文本(例如一个句子)会先被分词,然后每个单词会被转换为相应的索引。
-
查找词向量:词嵌入层将这些单词索引映射为对应的词向量。这些词向量是一个低维稠密向量,表示该词的语义。
-
输入到RNN:这些词向量作为RNN的输入,RNN处理它们并根据上下文生成一个序列的输出。
nn.Embedding(num_embeddings=10, embedding_dim=4)
# num_embeddings:词的数量
# embedding_dim:用多少维的向量来表示每个词
六、循环网络层
文本数据是具有序列特性的,例如: “我爱你”, 这串文本就是具有序列关系的,“爱” 需要在 “我” 之后,“你” 需要在 “爱” 之后, 如果颠倒了顺序,那么可能就会表达不同的意思。
为了表示出数据的序列关系,我们需要使用循环神经网络(Recurrent Nearal Networks, RNN) 来对数据进行建模,RNN 是一个具有记忆功能的网络,它作用于处理带有序列特点的样本数据。

上图中h表示隐藏状态,隐藏状态保存了序列数据中的历史信息,并将这些信息传递给下一个时间步,从而允许RNN处理和预测序列数据中的元素。
每一次的输入都会包含两个值:上一个时间步的隐藏状态、当前状态的输入值x
每一次的输出都会包含两个值:输出当前时间步的隐藏状态、当前时间步的预测结果y
隐藏状态作用:
-
记忆功能:隐藏状态就像RNN的记忆,它能够在不同的时间步之间传递信息。当一个新的输入进入网络时,当前的隐藏状态会结合这个新输入来生成新的隐藏状态。
-
上下文理解:由于隐藏状态携带了过去的信息,它可以用于理解和生成与上下文相关的输出。这对于语言模型、机器翻译等任务尤其重要。
-
连接不同时间步:隐藏状态通过网络内部的循环连接将各个时间步连接起来,使得网络可以处理变长的序列数据。
RNN = nn.RNN(input_size, hidden_size,num_layers)
# input_size:输入数据的维度,一般设为词向量的维度
# hidden_size:隐藏层h的维度,也是当前层神经元的输出维度
# num_layers: 隐藏层h的层数,默认为1
更多推荐


所有评论(0)