文章目录
深度学习Week6——VGG-16算法实现人脸识别
一、前言
二、我的环境
三、前期工作
1、配置环境
2、导入数据
3、划分数据集
四、调用官方的VGG-16模型
五、训练模型
1、编写训练函数
2、编写测试函数
3、设置动态学习率
4、正式训练
六、结果可视化
1、Loss图和Accuracy图
2、指定图片识别
3、模型评估
七、我的收获与疑惑
一、前言
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
二、我的环境
- 电脑系统:Windows 10
- 语言环境:Python 3.11.3
- 编译器:Pycharm2023.2.3
深度学习环境:Pytorch
显卡及显存:RTX 3060 8G
三、前期工作
1、导入库并配置环境
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
import os,PIL,random,pathlib,warnings
warnings.filterwarnings("ignore") #忽略警告信息
device = torch.device("cuda")
device
输出:
device(type='cuda')
至此,我们的环境已经配置完成。
2、 导入数据
我们下载好文件到自己的文件夹目录中,复制文件地址备用,注意这里要将[str(path).split("\\")[] for path in data_paths]
中[]
里的数字。
第一步:使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。
第二步:使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。
第三步:通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames中
第四步:打印classeNames列表,显示每个文件所属的类别名称。
data_dir = 'Data/Week6/48-data'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[3] for path in data_paths]
classeNames
输出:
['Angelina Jolie',
'Brad Pitt',
'Denzel Washington',
'Hugh Jackman',
'Jennifer Lawrence',
'Johnny Depp',
'Kate Winslet',
'Leonardo DiCaprio',
'Megan Fox',
'Natalie Portman',
'Nicole Kidman',
'Robert Downey Jr',
'Sandra Bullock',
'Scarlett Johansson',
'Tom Cruise',
'Tom Hanks',
'Will Smith']
快速的导入数据,构建模型
data_dir = 'Data/Week6/48-data'
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*/*.jpg')))
print("训练图片总数为:",image_count)
输出:
训练图片总数为: 1800
然后我们可以指定第一张照片作为测试是否导入数据成功。
roses = list(data_dir.glob('Angelina Jolie/*.jpg'))
PIL.Image.open(str(roses[0]))
# 关于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] 从数据集中随机抽样计算得到的。
])
total_data = datasets.ImageFolder("Data/Week6/48-data",transform=train_transforms)
total_data
输出:
Dataset ImageFolder
Number of datapoints: 1800
Root location: Data/Week6/48-data
StandardTransform
Transform: Compose(
Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=True)
ToTensor()
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
)
total_data.class_to_idx
{'Angelina Jolie': 0,
'Brad Pitt': 1,
'Denzel Washington': 2,
'Hugh Jackman': 3,
'Jennifer Lawrence': 4,
'Johnny Depp': 5,
'Kate Winslet': 6,
'Leonardo DiCaprio': 7,
'Megan Fox': 8,
'Natalie Portman': 9,
'Nicole Kidman': 10,
'Robert Downey Jr': 11,
'Sandra Bullock': 12,
'Scarlett Johansson': 13,
'Tom Cruise': 14,
'Tom Hanks': 15,
'Will Smith': 16}
3、划分训练集
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])
train_dataset, test_dataset
输出:
(<torch.utils.data.dataset.Subset at 0x20cb7153f90>,
<torch.utils.data.dataset.Subset at 0x20cb64d04d0>)
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=1)
test_dl = torch.utils.data.DataLoader(test_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=1)
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
Shape of X [N, C, H, W]: torch.Size([32, 3, 224, 224])
Shape of y: torch.Size([32]) torch.int64
四 、调用官方的VGG-16模型
VGG-16结构说明:
- 13个卷积层(Convolutional Layer),分别用blockX_convX表示;
- 3个全连接层(Fully connected Layer),用classifier表示;
- 5个池化层(Pool layer)。
from torchvision.models import vgg16
device = "cuda"
print("Using {} device".format(device))
# 加载预训练模型,并且对模型进行微调
model = vgg16(pretrained = True).to(device) # 加载预训练的vgg16模型
for param in model.parameters():
param.requires_grad = False # 冻结模型的参数,这样子在训练的时候只训练最后一层的参数
# 修改classifier模块的第6层(即:(6): Linear(in_features=4096, out_features=2, bias=True))
# 注意查看我们下方打印出来的模型
model.classifier._modules['6'] = nn.Linear(4096,len(classeNames)) # 修改vgg16模型中最后一层全连接层,输出目标类别个数
model.to(device)
model
输出:
Using cuda device
Downloading: "https://download.pytorch.org/models/vgg16-397923af.pth" to C:\Users\Han-official/.cache\torch\hub\checkpoints\vgg16-397923af.pth
100.0%
VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace=True)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace=True)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace=True)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace=True)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace=True)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace=True)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace=True)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace=True)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace=True)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace=True)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
(2): Dropout(p=0.5, inplace=False)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace=True)
(5): Dropout(p=0.5, inplace=False)
(6): Linear(in_features=4096, out_features=17, bias=True)
)
)
这里是K同学啊对于VGG-16网络的动画演示
五、训练模型
1、编写训练函数
其核心思想是在每个训练迭代中,它从数据加载器中获取一个小批量的训练数据(包含图像和标签),将数据传递给模型进行前向传播,计算损失,然后通过反向传播更新模型的参数。最后,它记录并返回训练的准确率和损失。
简要概括:
- 从数据加载器中获取小批量训练数据。
- 将数据传递给模型进行前向传播,计算预测值。
- 计算预测值与真实标签之间的损失。
- 使用反向传播更新模型参数。
- 记录并返回训练准确率和损失。
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset) # 训练集的大小
num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
for X, y in dataloader: # 获取图片及其标签
X, y = X.to(device), y.to(device)
# 计算预测误差
pred = model(X) # 网络输出
loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
# 反向传播
optimizer.zero_grad() # grad属性归零
loss.backward() # 反向传播
optimizer.step() # 每一步自动更新
# 记录acc与loss
train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
train_loss += loss.item()
train_acc /= size
train_loss /= num_batches
return train_acc, train_loss
2、编写测试函数
下面这段代码主要是用于测试(评估)深度学习模型性能的函数。在每个测试迭代中,它从测试数据加载器中获取一个小批量的数据(包含图像和标签),通过模型进行前向传播,计算损失,然后记录测试准确率和损失。
简要概括:
- 从测试数据加载器中获取小批量测试数据。
- 将数据传递给模型进行前向传播,得到预测值。
- 计算预测值与真实标签之间的损失。
- 记录并返回测试准确率和损失。
测试函数和训练函数大致相同,但是由于不进行梯度下降对网络权重进行更新,所以不需要传入优化器
# 测试函数
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
3、设置动态学习率
def adjust_learning_rate(optimizer, epoch, start_lr):
# 每 2 个epoch衰减到原来的 0.98
lr = start_lr * (0.92 ** (epoch // 2))
for param_group in optimizer.param_groups:
param_group['lr'] = lr
learn_rate = 1e-4 # 初始学习率
optimizer = torch.optim.SGD(model.parameters(), lr = learn_rate)
4、正式训练
主要是一个简单的训练循环,用于训练和评估深度学习模型多个epochs。在每个epoch中,它分别进行训练和测试,并记录训练集和测试集的准确率以及损失。最后,将每个epoch的结果打印出来。
import copy
loss_fn = nn.CrossEntropyLoss() # 创建损失函数
epochs = 40
train_loss = []
train_acc = []
test_loss = []
test_acc = []
best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标
for epoch in range(epochs):
# 更新学习率(使用自定义学习率时使用)
# adjust_learning_rate(optimizer, epoch, learn_rate)
model.train()
epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
# scheduler.step() # 更新学习率(调用官方动态学习率接口时使用)
model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
# 保存最佳模型到 best_model
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 = optimizer.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))
# 保存最佳模型到文件中
PATH = './best_model.pth' # 保存的参数文件名
torch.save(model.state_dict(), PATH)
print('Done')
输出:
Epoch: 1, Train_acc:5.6%, Train_loss:2.957, Test_acc:5.6%, Test_loss:2.876, Lr:1.00E-04
Epoch: 2, Train_acc:6.9%, Train_loss:2.907, Test_acc:6.7%, Test_loss:2.847, Lr:1.00E-04
Epoch: 3, Train_acc:7.3%, Train_loss:2.864, Test_acc:8.9%, Test_loss:2.798, Lr:1.00E-04
…
Epoch:38, Train_acc:23.8%, Train_loss:2.367, Test_acc:18.9%, Test_loss:2.362, Lr:1.00E-04
Epoch:39, Train_acc:22.8%, Train_loss:2.353, Test_acc:19.4%, Test_loss:2.378, Lr:1.00E-04
Epoch:40, Train_acc:21.7%, Train_loss:2.359, Test_acc:19.7%, Test_loss:2.372, Lr:1.00E-04
Done
六、结果可视化
1、Loss和Accuracy图
import matplotlib.pyplot as plt
#隐藏警告
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 # 因为我是用的Jupyter,因此不用plt.show()
结果:
2、指定图片识别
from PIL import Image
classes = list(total_data.class_to_idx)
def predict_one_image(image_path, model, transform, classes):
test_img = Image.open(image_path).convert('RGB')
plt.imshow(test_img) # 展示预测的图片
test_img = transform(test_img)
img = test_img.to(device).unsqueeze(0)
model.eval()
output = model(img)
_,pred = torch.max(output,1)
pred_class = classes[pred]
print(f'预测结果是:{pred_class}')
# 预测训练集中的某张照片
predict_one_image(image_path = 'Data/Week6/48-data/Sandra Bullock/008_f9ffe0c5.jpg',
model = model,
transform = train_transforms,
classes = classes)
输出:
预测结果是:Scarlett Johansson
3、模型评估
# 模型保存
best_model.eval()
epoch_test_acc, epoch_test_loss = test(test_dl, best_model, loss_fn)
# 将参数加载到model当中
epoch_test_acc, epoch_test_loss
(0.19722222222222222, 2.377724568049113)
# 查看是否与我们记录的最高准确率一致
epoch_test_acc
0.19722222222222222
七、我的疑问与收获
通过本周的学习,我学会了如何调用官方的VGG-16模型,并且仿造K同学保存训练过程中的最佳模型权重的方式保存了我的最佳模型,对此还是比较满意的,但也存在一些问题;
由于自己基础的薄弱,拔高的内容已经无法尝试,或许从下周开始,我的选题会更基础简单一些,工欲善其事,必先利其器!