一、完整代码
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,pathlib,warnings
import matplotlib.pyplot as plt
import copy
import torch.nn.functional as F
from PIL import Image
#隐藏警告
import warnings
warnings.filterwarnings("ignore")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
#导入数据
import os,PIL,random,pathlib
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
data_dir = r'D:\编程文件\数据库\J1ResNet-50\第8天\bird_photos'
data_dir = pathlib.Path(data_dir)
#查看数据
image_count=len(list(data_dir.glob('*/*')))
print("图片数为:",image_count)
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[6] for path in data_paths]
print(classeNames)
#加载数据
batch_size = 8
# 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
train_transforms = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
# transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
test_transform = transforms.Compose([
transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
total_data = datasets.ImageFolder("D:\编程文件\数据库\P2天气识别\weather_photos",transform=train_transforms)
print(total_data)
print(total_data.class_to_idx)
#划分数据集
train_size = int(0.8 * len(total_data))
test_size = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
print(train_dataset, test_dataset)
train_dl = torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=0)
test_dl = torch.utils.data.DataLoader(test_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=0)
#检查数据
if __name__ == '__main__':
for X, y in test_dl:
print("Shape of X [N, C, H, W]: ", X.shape)
print("Shape of y: ", y.shape, y.dtype)
break
image_folder = r'D:\编程文件\数据库\J1ResNet-50\第8天\bird_photos\Cockatoo' # 指定图像文件夹路径
image_files = [f for f in os.listdir(image_folder) if f.endswith((".jpg", ".png", ".jpeg"))]
fig, axes = plt.subplots(2, 4, figsize=(16, 6))
for ax, img_file in zip(axes.flat, image_files):
img_path = os.path.join(image_folder, img_file)
img = Image.open(img_path)
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()
#构建残差模型
# 构造ResNet50模型
class ResNetblock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super(ResNetblock, self).__init__()
self.blockconv = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride),
nn.BatchNorm2d(out_channels),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(out_channels),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels * 4, kernel_size=1, stride=1),
nn.BatchNorm2d(out_channels * 4)
)
if stride != 1 or in_channels != out_channels * 4:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels * 4, kernel_size=1, stride=stride),
nn.BatchNorm2d(out_channels * 4)
)
def forward(self, x):
residual = x
out = self.blockconv(x)
if hasattr(self, 'shortcut'): # 如果self中含有shortcut属性
residual = self.shortcut(x)
out += residual
out = F.relu(out)
return out
class ResNet50(nn.Module):
def __init__(self, block, num_classes=1000):
super(ResNet50, self).__init__()
self.conv1 = nn.Sequential(
nn.ZeroPad2d(3),
nn.Conv2d(3, 64, kernel_size=7, stride=2),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d((3, 3), stride=2)
)
self.in_channels = 64
# ResNet50中的四大层,每大层都是由ConvBlock与IdentityBlock堆叠而成
self.layer1 = self.make_layer(ResNetblock, 64, 3, stride=1)
self.layer2 = self.make_layer(ResNetblock, 128, 4, stride=2)
self.layer3 = self.make_layer(ResNetblock, 256, 6, stride=2)
self.layer4 = self.make_layer(ResNetblock, 512, 3, stride=2)
self.avgpool = nn.AvgPool2d((7, 7))
self.fc = nn.Linear(512 * 4, num_classes)
# 每个大层的定义函数
def make_layer(self, block, channels, num_blocks, stride=1):
strides = [stride] + [1] * (num_blocks - 1)
layers = []
for stride in strides:
layers.append(block(self.in_channels, channels, stride))
self.in_channels = channels * 4
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv1(x)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avgpool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
model = ResNet50(block=ResNetblock, num_classes=len(classeNames)).to(device)
print(model)
#编写训练函数
def train(dataloader, model, optimizer, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
train_acc, train_loss = 0, 0
for X, y in dataloader:
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss /= num_batches
train_acc /= size
return train_acc, train_loss
#编写测试函数
def test(dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小
num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
test_loss, test_acc = 0, 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
# 计算loss
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss
#设置损失函数和学习率
loss_fn = nn.CrossEntropyLoss() #交叉熵函数
learn_rate = 1e-3
opt = torch.optim.Adam(model.parameters(), lr=learn_rate)
#正式训练
epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []
best_acc = 0
# 开始训练
for epoch in range(epochs):
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, opt, loss_fn)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
if epoch_test_acc > best_acc:
best_acc = epoch_test_acc
best_model = copy.deepcopy(model)
train_acc.append(epoch_train_acc)
train_loss.append(epoch_train_loss)
test_acc.append(epoch_test_acc)
test_loss.append(epoch_test_loss)
lr = opt.state_dict()['param_groups'][0]['lr']
template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss,
epoch_test_acc * 100, epoch_test_loss, lr))
print('Done')
#结果可视化
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100 # 分辨率
epochs_range = range(epochs)
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
二、理论知识
1. 经典卷积神经网络
卷积神经网络(CNN)模型在图像识别和处理领域有着非常重要的地位。
-
LeNet-5:这是最早的卷积神经网络之一,由Yann LeCun等人在1998年提出,主要用于手写数字识别。
-
AlexNet: 由Alex Krizhevsky等人在2012年提出,它在ImageNet竞赛中取得了突破性的成绩,推动了深度学习的发展。AlexNet是一个深度CNN,包含5个卷积层和3个全连接层。
-
VGGNet:由牛津大学的视觉几何组(Visual Geometry Group)在2014年提出。VGGNet通过使用更小的卷积核(3x3)和更深的网络结构来提高性能。
-
Inception Network (GoogLeNet):由Google的研究团队在2014年提出,它引入了Inception模块,可以在同一层内并行地应用不同大小的卷积核,以捕获不同尺度的特征。
-
ResNet: 由Kaiming He等人在2015年提出,引入了残差连接来解决深度网络训练中的退化问题,允许训练更深的网络。
-
DenseNet:由Gao Huang等人在2016年提出,它通过连接每个层到前面所有层来加强特征传播,从而提高性能和参数效率。
-
MobileNet:由Andrew G.Howard等人在2017年提出,专为移动和嵌入式视觉应用设计,通过使用深度可分离卷积来减少计算量和模型大小。
-
EfficientNet: 由Mingxing Tan和Quoc V. Le在2019年提出,它引入了一种新的缩放方法,通过同时扩展深度、宽度和分辨率来提高模型的效率和准确性。
2.RestNet
2.1 RestNet分类
ResNet,即残差网络(Residual Networks),是一类深度学习卷积神经网络的总称,它们通过引入残差学习解决了随着网络深度增加而出现的梯度消失问题,使得训练更深的网络成为可能。ResNet有多种不同的架构,主要根据网络的深度和使用的残差模块类型进行分类。
-
ResNet-18 和 ResNet-34:这两种架构使用了名为Basic Block的残差模块,适用于较浅的网络结构。Basic Block由两个3x3的卷积层构成,其中第一个卷积层可能包含步长为2的卷积以实现降采样 。
-
ResNet-50、ResNet-101 和 ResNet-152:这些更深的网络架构使用了Bottleneck Block作为残差模块,以减少计算量和参数数量。Bottleneck Block由三个卷积层构成:首先是1x1的卷积层用于降维,然后是3x3的卷积层,最后是1x1的卷积层用于升维 。
-
ResNet-50:作为ResNet系列中的一个中间深度模型,ResNet-50包含50层网络结构,具体来说,它由4组残差块组成,每组分别包含3、4、6、3个残差块,加上最开始的一个单独的卷积层,以及最后的全连接层,总共达到50层。
ResNet-50在设计上有一些细节,比如在每个残差块中,第一个卷积层后通常会跟一个批量归一化(Batch Normalization)和ReLU激活函数,而在后续的卷积层中,只有批量归一化,没有激活函数,直到残差块的最后才会再次使用ReLU激活函数。
- ResNet v2:ResNet的改进版本,ResNet v2 对残差学习单元进行了优化,将ReLU激活函数放置在残差块的shortcut连接之后,并且每个卷积层后面都跟随一个Batch Normalization层 。
2.2 RestNet的创新之处
- 每种ResNet模型都可以通过调整残差块的数量和类型来适应不同的任务和数据集。
例如,ResNet-50模型在CIFAR-10数据集上进行图像分类任务时,可以通过修改全连接层的输出大小来适配不同数量的分类类别 。此外,ResNet模型通常使用全局平均池化层和全连接层作为网络的最后几个层,用于分类或其他任务 。 - ResNet的创新之处在于其残差结构,允许网络学习输入和输出之间的残差,而不是直接学习映射关系,这使得网络能够通过增加深度来提高性能,而不会出现梯度消失或过拟合的问题 。
ResNet系列模型因其卓越的性能和灵活性,在计算机视觉领域得到了广泛的应用。
2.3 Pytorch和tensorflow框架对比
与TensorFlow相比,PyTorch通常在代码简洁性方面有优势,会需要较少的代码量。
- PyTorch以其动态计算图而闻名,允许在运行时修改模型,这使得模型构建更加直观和灵活。
- TensorFlow 2.x引入了Eager Execution,提供了更动态的执行模式,改善了易用性。
2.4 总结
为了提高模型的训练效果,我们可以:
- 根据任务选择合适的模型:例如图像分类可能使用CNN,序列任务可能使用RNN或Transformer。
- 应用正则化技术:L1、L2正则化或Dropout来减少过拟合。
- 使用批量归一化:来加速训练并提高模型稳定性。
- 模型集成:训练多个模型并将其集成起来,以提高整体性能。
- 多任务学习:如果可能,使用多任务学习同时训练模型完成多个相关任务。
- 损失函数选择:根据具体任务选择合适的损失函数,如均方误差、交叉熵等。