引言
主要讲下alexnet里的几个方法,后面深度学习的代码部分应该都是借用别人的,整体安排是从简单到难。本篇借鉴Pytorch之AlexNet花朵分类_基于alexnet的花卉分类识别系统-CSDN博客,如果需要学习直接参考这篇就好了,本文只是作为本人复习记录。
正文
AlexNet 是深度学习时代的开端,它用一场决定性的胜利,证明了深度卷积神经网络在计算机视觉中的巨大潜力。
单层alexnet网络架构:
1. 输入层(Input Layer)
-
输入尺寸: 227 × 227 × 3
-
输入是一个彩色图像(RGB三个通道),尺寸为 227×227 像素。
2. 第一层卷积(CONV1)
-
卷积核: 11 × 11
-
步长(stride): 4
-
输出尺寸: 55 × 55 × 96
-
特征图数量为96,通过较大的卷积核快速提取低层特征。
→ 最大池化(Max Pool 1)
-
池化核: 3 × 3
-
步长: 2
-
输出尺寸: 27 × 27 × 96
3. 第二层卷积(CONV2)
-
卷积核: 5 × 5,使用“same”填充
-
输出尺寸: 27 × 27 × 256
→ 最大池化(Max Pool 2)
-
池化核: 3 × 3
-
步长: 2
-
输出尺寸: 13 × 13 × 256
4. 第三层卷积(CONV3)
-
卷积核: 3 × 3,使用“same”填充
-
输出尺寸: 13 × 13 × 384
5. 第四层卷积(CONV4)
-
卷积核: 3 × 3
-
输出尺寸: 13 × 13 × 384
6. 第五层卷积(CONV5)
-
卷积核: 3 × 3
-
输出尺寸: 13 × 13 × 256
→ 最大池化(Max Pool 3)
-
池化核: 3 × 3
-
步长: 2
-
输出尺寸: 6 × 6 × 256
7. 展平层(Flatten)
-
将 6 × 6 × 256 的特征图展平为一维向量,共 9216个节点
8. 全连接层(FC)
-
FC1: 9216 → 4096
-
FC2: 4096 → 4096
9. 输出层(Output Layer)
-
类别数: 1000(对应ImageNet的1000类)
-
激活函数: Softmax,用于分类
AlexNet创新点 :
1.更深的神经网络结构
AlexNet 是首个真正意义上的深度卷积神经网络,它的深度达到了当时先前神经网络的数倍。通过增加网络深度,AlexNet 能够更好地学习数据集的特征,从而提高了图像分类的精度。
2.ReLU激活函数的使用
AlexNet 首次使用了修正线性单元(ReLU)这一非线性激活函数。相比于传统的 sigmoid 和 tanh 函数,ReLU 能够在保持计算速度的同时,有效地解决了梯度消失问题,从而使得训练更加高效。
3.局部响应归一化(LRN)的使用
LRN是在卷积层和池化层之间添加的一种归一化操作。在卷积层中,每个卷积核都对应一个特征图(feature map),LRN就是对这些特征图进行归一化。具体来说,对于每个特征图上的每个位置,计算该位置周围的像素的平方和,然后将当前位置的像素值除以这个和。
LRN本质是抑制邻近神经元的响应,从而增强了神经元的较大响应。这种技术在一定程度上能够避免过拟合,并提高网络的泛化能力。
4.数据增强和Dropout
为了防止过拟合,AlexNet 引入了数据增强和 Dropout 技术。
数据增强可以通过对图像进行旋转、翻转、裁剪等变换,增加训练数据的多样性,提高模型的泛化能力。
Dropout 则是在训练过程中随机删除一定比例的神经元,强制网络学习多个互不相同的子网络,从而提高网络的泛化能力。Dropout简单来说就是在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。
5.大规模分布式训练
AlexNet在使用GPU进行训练时,可将卷积层和全连接层分别放到不同的GPU上进行并行计算,从而大大加快了训练速度。像这种大规模 GPU 集群进行分布式训练的方法在后来的深度学习中也得到了广泛的应用。
AlexNet实现
1.定义AlexNet网络模型
class My_AlexNet(nn.Module):
def __init__(self):
super(My_AlexNet, self).__init__()
# 特征提取
self.features = nn.Sequential(
# 输入通道数为3,因为图片为彩色,三通道
# 而输出96、卷积核为11*11,步长为4,是由AlexNet模型决定的,后面的都同理
nn.Conv2d(in_channels=3,out_channels=96,kernel_size=11,stride=4,padding=2),
nn.BatchNorm2d(96),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.BatchNorm2d(384),
nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.BatchNorm2d(384),
nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2)
)
self.classifier = nn.Sequential(
nn.Linear(256 * 6 * 6, 4096),
nn.BatchNorm1d(4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.BatchNorm1d(4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 5) # 输出5类,根据你自己定义
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
这里设计的是批量归一化,这是后面深度学习发展证明更有效的归一化方式。
2.加载数据集
# AlexNet 要求模型的输入大小为:227*227*3,因此需要下采样——裁剪
class My_Dataset(Dataset):
def __init__(self,filename,transform=None):
self.filename = filename # 文件路径
self.transform = transform # 是否对图片进行变化
self.image_name,self.label_image = self.operate_file()
def __len__(self):
return len(self.image_name)
def __getitem__(self,idx):
# 由路径打开图片
image = Image.open(self.image_name[idx])
# 下采样: 因为图片大小不同,需要下采样为227*227
trans = transforms.RandomResizedCrop(227)
image = trans(image)
# 获取标签值
label = self.label_image[idx]
# 是否需要处理
if self.transform:
image = self.transform(image)
# image = image.reshape(1,image.size(0),image.size(1),image.size(2))
# print('变换前',image.size())
# image = interpolate(image, size=(227, 227))
# image = image.reshape(image.size(1),image.size(2),image.size(3))
# print('变换后', image.size())
# 转为tensor对象
label = torch.from_numpy(np.array(label))
return image,label
def operate_file(self):
# 获取所有的文件夹路径 '../data/net_train_images'的文件夹
dir_list = os.listdir(self.filename)
# 拼凑出图片完整路径 '../data/net_train_images' + '/' + 'xxx.jpg'
full_path = [self.filename+'/'+name for name in dir_list]
# 获取里面的图片名字
name_list = []
for i,v in enumerate(full_path):
temp = os.listdir(v)
temp_list = [v+'/'+j for j in temp]
name_list.extend(temp_list)
# 由于一个文件夹的所有标签都是同一个值,而字符值必须转为数字值,因此我们使用数字0-4代替标签值
label_list = []
temp_list = np.array([0,1,2,3,4],dtype=np.int64) # 用数字代表不同类别
# 将标签每个复制200个
for j in range(5):
for i in range(200):
label_list.append(temp_list[j])
return name_list,label_list
class My_Dataset_test(My_Dataset):
def operate_file(self):
# 获取所有的文件夹路径
dir_list = os.listdir(self.filename)
full_path = [self.filename+'/'+name for name in dir_list]
# 获取里面的图片名字
name_list = []
for i,v in enumerate(full_path):
temp = os.listdir(v)
temp_list = [v+'/'+j for j in temp]
name_list.extend(temp_list)
# 将标签每个复制一百个
label_list = []
temp_list = np.array([0,1,2,3,4],dtype=np.int64) # 用数字代表不同类别
for j in range(5):
for i in range(100): # 只修改了这里
label_list.append(temp_list[j])
return name_list,label_list
3.训练模型
# 训练过程
def train():
batch_size = 32 # 批量训练大小
model = My_AlexNet() # 创建模型
# 将模型放入GPU中
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用的device是: {device}")
print("可用的 CUDA 设备数量:", torch.cuda.device_count())
print("当前 CUDA 设备 ID:", torch.cuda.current_device())
print("当前 CUDA 设备名称:", torch.cuda.get_device_name(torch.cuda.current_device()))
model.to(device)
# 定义损失函数
loss_func = nn.CrossEntropyLoss()
# 定义优化器
optimizer = optim.Adam(params=model.parameters(),lr=0.0002)
# 加载数据
train_set = My_Dataset('data/net_train_images',transform=transforms.ToTensor())
train_loader = DataLoader(train_set, batch_size, shuffle=True)
# 训练20次
for i in range(200):
model.train()
loss_temp = 0 # 临时变量
for j,(batch_data,batch_label) in enumerate(train_loader):
# 数据放入GPU中
batch_data,batch_label = batch_data.cuda(),batch_label.cuda()
# 梯度清零
optimizer.zero_grad()
# 模型训练
prediction = model(batch_data)
# 损失值
loss = loss_func(prediction,batch_label)
loss_temp += loss.item()
# 反向传播
loss.backward()
# 梯度更新
optimizer.step()
# 每25个批次打印一次损失值
print('[%d] loss: %.3f' % (i+1,loss_temp/len(train_loader)))
test(model)
4.测试模型
def test(model):
batch_size = 10
correct = 0
test_set = My_Dataset_test('data/net_test_images', transform=transforms.ToTensor())
test_loader = DataLoader(test_set, batch_size, shuffle=False)
model.eval()
with torch.no_grad(): # 禁用梯度,提高效率
for batch_data, batch_label in test_loader:
batch_data, batch_label = batch_data.cuda(), batch_label.cuda()
prediction = model(batch_data)
predicted = torch.max(prediction.data, 1)[1]
correct += (predicted == batch_label).sum().item()
print('准确率: %.2f %%' % (100 * correct / 500))
5.测试结果