目录
一、什么是卷积?
二、卷积神经网络的组成
1. 卷积层
2. 池化层
3. 激活函数
4. 全连接层
三、卷积神经网络的构造
四、代码实现
1.数据预处理
2.创建卷积神经网络
3.创建训练集和测试集函数
4.创建损失函数和优化器并进行训练
一、什么是卷积?
对图像(不同的数据窗口数据)和卷积核(一组固定的权重:因为每个神经元的多个权重固定,所以又可以看做一个恒定的滤波器filter)做内积(逐个元素相乘再求和)的操作就是所谓的『卷积』操作,也是卷积神经网络的名字来源。
二、卷积神经网络的组成
1. 卷积层
- 功能:通过卷积操作提取特征。卷积核(滤波器)在输入图像上滑动,计算局部区域的加权和,生成特征图。
- 参数:卷积核的大小(如3x3、5x5)、步幅(每次滑动的像素数)和填充(为避免信息损失,通常在图像边缘添加零)。
2. 池化层
- 功能:降低特征图的空间维度,减少计算量和参数数量,防止过拟合。
-
类型:
- 最大池化:在每个窗口中取最大值。
- 平均池化:在每个窗口中取平均值。
- 参数:池化窗口的大小和步幅。
3. 激活函数
- 功能:引入非线性,帮助模型捕捉复杂的模式。
-
常用类型:
- ReLU(修正线性单元):输出为输入的正部分,负部分输出为0。
- Sigmoid:将输出限制在0到1之间,适用于二分类问题。
- Softmax:将输出转化为概率分布,适用于多分类问题。
4. 全连接层
- 功能:将卷积层和池化层提取的特征进行整合,用于最终的分类或回归。
- 结构:每个神经元与前一层的所有神经元相连接。
三、卷积神经网络的构造
- 输入层--卷积层--池化层--全连接层--输出层
四、代码实现
1.数据预处理
- 从本地读取图片数据并打包
- 将其裁剪成256*256大小并转换成tensor类型数据
- 将对应图片标签也转换成tensor类型数据
import torch
import numpy as np
from torch.utils.data import DataLoader, Dataset # 数据包管理工具,打包数据,
from torchvision import transforms
from PIL import Image
class food_dataset(Dataset):
def __init__(self, file_path, transform=None): # 类的初始化,解析数据文件txt
self.file_path = file_path
self.imgs = []
self.labels = []
self.transform = transform
with open(self.file_path) as f: # 是把train.txt文件中图片的路径保存在 self.imgs,train.txt文件中标签保存在self.label里
samples = [x.strip().split(' ') for x in f.readlines()] # 去掉首尾空格 再按空格分成两个元素
for img_path, label in samples:
self.imgs.append(img_path) # 图像的路径
self.labels.append(label) # 标签,还不是tensor
# 初始化:把图片目录加载到self
def __len__(self): # 类实例化对象后,可以使用len函数测量对象的个数
return len(self.imgs)
def __getitem__(self, idx): # 关键,可通过索引的形式获取每一个图片数据及标签
image = Image.open(self.imgs[idx]) # 读取到图片数据,还不是tensor
if self.transform:
# 将pil图像数据转换为tensor
image = self.transform(image) # 图像处理为256x256,转换为tenor
label = self.labels[idx] # label还不是tensor
label = torch.from_numpy(np.array(label, dtype=np.int64)) # label也转换为tensor
return image, label
data_transforms = {
'train':
transforms.Compose([
transforms.Resize([256, 256]),
transforms.ToTensor()
]),
'test':
transforms.Compose([
transforms.Resize([256, 256]),
transforms.ToTensor()
])
}
train_data = food_dataset(file_path=r'.\train.txt', transform=data_transforms['train']) # 64张图片为一个包 训练集60000张图片 打包成了938个包
test_data = food_dataset(file_path=r'.\test.txt', transform=data_transforms['test'])
train_dataloader = DataLoader(train_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
for x, y in test_dataloader:
print(f"shape of x [N ,C,H,W]:{x.shape}")
print(f"shape of y :{y.shape} {y.dtype}")
break
2.创建卷积神经网络
from torch import nn # 导入神经网络模块
class CNN(nn.Module): # Convolutional Neural Network
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Sequential( # 将多个层组合成一起。
nn.Conv2d( # 2d一般用于图像,3d用于视频数据(多一个时间维度),1d一般用于结构化的序列数据
in_channels=3, # 图像通道个数,1表示灰度图(确定了卷积核 组中的个数),
out_channels=16, # 要得到几多少个特征图,卷积核的个数
kernel_size=5, # 卷积核大小,5*5
stride=1, # 步长
padding=2 # 一般希望卷积核处理后的结果大小与处理前的数据大小相同,效果会比较好。那pading改如何设计呢?建议stride为1
),
nn.ReLU(), # relu层,不会改变特征图的大小
nn.MaxPool2d(kernel_size=2), # 进行池化操作(2x2 区域) 最大值池化
)
self.conv2 = nn.Sequential( # 但整个 nn.Sequential 可以视为一个卷积模块
nn.Conv2d(16, 32, 5, 1, 2),
nn.ReLU(),
nn.Conv2d(32, 32, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.conv3 = nn.Sequential(
nn.Conv2d(32, 128, 5, 1, 2),
nn.ReLU()
)
self.out = nn.Linear(128 * 64 * 64, 20) # 全连接层得到的结果
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = x.view(x.size(0), -1)
output = self.out(x)
return output
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_avaibale() else 'cpu'
model = CNN().to(device) # 把刚刚创建的模型传入到GPU
print(model)
输出:
- 可以看到该卷积神经网络有三个卷积模块
- 最后通过全连接层输出预测结果
CNN(
(conv1): Sequential(
(0): Conv2d(3, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(conv2): Sequential(
(0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(3): ReLU()
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(conv3): Sequential(
(0): Conv2d(32, 128, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
)
(out): Linear(in_features=524288, out_features=20, bias=True)
)
3.创建训练集和测试集函数
def train(dataloader, model, loss_fn, optimizer):
model.train() # 告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w.在训练过程中,w会被修改的
# pytorch提供2种方式来切换训练和测试的模式,分别是:model.train()和 model.eval().
# 一般用法是: 在训练开始之前写上model.trian(),在测试时写上model.eval().
batch_size_num = 1
for x, y in dataloader: #
x, y = x.to(device), y.to(device) # 把训练数据集和标签传入CPU或GPU
pred = model.forward(x) # 向前传播
loss = loss_fn(pred, y) # 通过交叉熵损失函数计算损失值loss
optimizer.zero_grad() # 梯度值清零
loss.backward() # 反向传播计算得到每个参数的梯度值w
optimizer.step() # 根据梯度更新网络w参数
loss_value = loss.item() # 从tensor数据中提取数据出来,tensor获取损失值
if batch_size_num % 2 == 0:
print(f"loss:{loss_value:>7f} [number:{batch_size_num}]")
batch_size_num += 1
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval() # 测试,w就不能再更新。
test_loss, correct = 0, 0
with torch.no_grad(): # 一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所占用的消耗
for x, y in dataloader:
x, y = x.to(device), y.to(device)
pred = model.forward(x)
test_loss += loss_fn(pred, y).item() # test loss是会自动累加每一个批次的损失值
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
a = (pred.argmax(1) == y) # dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
b = (pred.argmax(1) == y).type(torch.float)
test_loss /= num_batches # 能来衡量模型测试的好坏。
correct /= size # 平均的正确率
print(f"Test result: \n Accuracy: {(100 * correct)}%, Avg loss: {test_loss}")
4.创建损失函数和优化器并进行训练
- 创建处理多分类的损失函数
- 使用Adam优化器
loss_fn = nn.CrossEntropyLoss() # 处理多分类
optimizer = torch.optim.Adam(model.parameters(), lr=0.0002)
epochs = 20 # 到底选择多少呢?
for t in range(epochs):
print(f"Epoch {t + 1}\n--------------")
train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)
输出:
Epoch 20
--------------
loss:0.002920 [number:2]
loss:0.003376 [number:4]
Done!
Test result:
Accuracy: 41.02564102564102%, Avg loss: 5.77920389175415