第P6周:VGG-16算法-Pytorch实现人脸识别
- 🍨 本文为🔗365天深度学习训练营中的学习记录博客
- 🍖 原作者:
本周是学习深度学习的第6周。编译器使用的是vscode,安装的是CPU版PyTorch:
torch 2.12.0+cpu。
本周的学习目标:
-
保存训练过程中的最佳模型权
-
调用官方的VGG-16网络框架
1 数据集
1.1 数据集导入
第一步:使用pathlib.Path()函数将字符串类型的文件夹路径转换为pathlib.Path对象。
第二步:使用glob()方法获取data_dir路径下的所有文件路径,并以列表形式存储在data_paths中。
第三步:通过split()函数对data_paths中的每个文件路径执行分割操作,获得各个文件所属的类别名称,并存储在classeNames中
第四步:打印classeNames列表,显示每个文件所属的类别名称。
data_dir = './6-data/'
data_dir = pathlib.Path(data_dir)
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[1] for path in data_paths]
print(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']
1.2 图像预处理
-
transforms.Compose:将多个图像变换组合成一个处理流水线:- Resize:把图片统一缩放到 224×224 像素(ResNet 等经典模型的输入尺寸)。
- ToTensor:将 PIL 图片转成 PyTorch 张量,同时像素值从 0-255 归一化到 0-1。
- Normalize:用 ImageNet 数据集的均值和标准差做标准化,让数据分布接近标准正态分布,帮助模型更快收敛。
-
datasets.ImageFolder:从./6-data/目录加载数据集,自动按子文件夹名分配标签,并应用上述预处理。 -
class_to_idx:打印类别名到索引的映射(如{'cat': 0, 'dog': 1})。
# 关于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("./6-data/",transform=train_transforms)
print(total_data)
print(total_data.class_to_idx)
显示结果为:
Dataset ImageFolder
Number of datapoints: 1800
Root location: ./6-data/
StandardTransform
Transform: Compose(
Resize(size=[224, 224], interpolation=bilinear, max_size=None, antialias=None)
ToTensor()
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
)
{'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}
1.3 数据集划分
按 8:2 比例随机拆分数据集,random_split 会随机打乱并分配每张图片到训练集或测试集。batch_size=32:每次喂给模型 32 张图片(一个批次)。shuffle=True:每个 epoch 随机打乱数据顺序,防止模型记住样本顺序。num_workers=1:用 1 个子进程并行加载数据,加快读取速度。
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)
print(test_dataset)
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
显示结果为:
(<torch.utils.data.dataset.Subset at 0x2570a8b6680>,
<torch.utils.data.dataset.Subset at 0x2570a8b67a0>)
Shape of X [N, C, H, W]: torch.Size([32, 3, 224, 224])
Shape of y: torch.Size([32]) torch.int64
2 VGG-16模型
VGG-16(Visual Geometry Group-16)是由牛津大学视觉几何组(Visual Geometry Group)提出的一种深度卷积神经网络架构,用于图像分类和对象识别任务。VGG-16在2014年被提出,是VGG系列中的一种。VGG-16之所以备受关注,是因为它在ImageNet图像识别竞赛中取得了很好的成绩,展示了其在大规模图像识别任务中的有效性。
VGG-16结构说明:
- 13个卷积层(Convolutional Layer),分别用
blockX_convX表示; - 3个全连接层(Fully connected Layer),用
classifier表示; - 5个池化层(Pool layer)。
device = "cuda" if torch.cuda.is_available() else "cpu"
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)
print(model)
显示结果为:
Using cpu device
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)
)
)
3 模型训练与可视化
3.1 模型训练
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
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
2.动态学习率算法:
torch.optim.lr_scheduler.StepLR:等间隔动态调整方法,每经过step_size个epoch ,做一次学习率decay,以gamma值为缩小倍数。
lr_scheduler.LambdaLR:根据自己定义的函数更新学习率。
lr_scheduler.MultiStepLR:在特定的 epoch 中调整学习率。
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)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=2, gamma=0.92)
3 正式训练
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(best_model.state_dict(), PATH)
print('Done')
3.2 可视化
Epoch: 1, Train_acc:7.4%, Train_loss:2.895, Test_acc:15.0%, Test_loss:2.774, Lr:1.00E-04
Epoch: 2, Train_acc:8.3%, Train_loss:2.873, Test_acc:15.3%, Test_loss:2.757, Lr:1.00E-04
Epoch: 3, Train_acc:11.4%, Train_loss:2.822, Test_acc:16.4%, Test_loss:2.730, Lr:1.00E-04
Epoch: 4, Train_acc:12.5%, Train_loss:2.789, Test_acc:15.6%, Test_loss:2.705, Lr:1.00E-04
Epoch: 5, Train_acc:13.5%, Train_loss:2.757, Test_acc:16.7%, Test_loss:2.685, Lr:1.00E-04
Epoch: 6, Train_acc:12.2%, Train_loss:2.738, Test_acc:17.8%, Test_loss:2.661, Lr:1.00E-04
Epoch: 7, Train_acc:14.8%, Train_loss:2.711, Test_acc:17.8%, Test_loss:2.645, Lr:1.00E-04
Epoch: 8, Train_acc:13.6%, Train_loss:2.703, Test_acc:18.1%, Test_loss:2.620, Lr:1.00E-04
Epoch: 9, Train_acc:14.6%, Train_loss:2.686, Test_acc:18.3%, Test_loss:2.620, Lr:1.00E-04
Epoch:10, Train_acc:14.0%, Train_loss:2.664, Test_acc:18.6%, Test_loss:2.599, Lr:5.00E-05
Epoch:11, Train_acc:15.2%, Train_loss:2.655, Test_acc:18.6%, Test_loss:2.587, Lr:5.00E-05
Epoch:12, Train_acc:15.2%, Train_loss:2.648, Test_acc:18.1%, Test_loss:2.585, Lr:5.00E-05
Epoch:13, Train_acc:14.3%, Train_loss:2.640, Test_acc:18.3%, Test_loss:2.582, Lr:5.00E-05
Epoch:14, Train_acc:16.0%, Train_loss:2.624, Test_acc:18.9%, Test_loss:2.560, Lr:5.00E-05
Epoch:15, Train_acc:16.2%, Train_loss:2.620, Test_acc:19.2%, Test_loss:2.555, Lr:5.00E-05
Epoch:16, Train_acc:16.5%, Train_loss:2.609, Test_acc:19.4%, Test_loss:2.550, Lr:5.00E-05
Epoch:17, Train_acc:15.7%, Train_loss:2.603, Test_acc:19.2%, Test_loss:2.523, Lr:5.00E-05
Epoch:18, Train_acc:17.2%, Train_loss:2.580, Test_acc:19.2%, Test_loss:2.528, Lr:5.00E-05
Epoch:19, Train_acc:17.2%, Train_loss:2.590, Test_acc:19.4%, Test_loss:2.529, Lr:5.00E-05
Epoch:20, Train_acc:18.3%, Train_loss:2.556, Test_acc:19.4%, Test_loss:2.510, Lr:2.50E-05
Epoch:21, Train_acc:16.4%, Train_loss:2.573, Test_acc:19.4%, Test_loss:2.520, Lr:2.50E-05
Epoch:22, Train_acc:17.6%, Train_loss:2.575, Test_acc:19.4%, Test_loss:2.521, Lr:2.50E-05
Epoch:23, Train_acc:17.6%, Train_loss:2.563, Test_acc:20.0%, Test_loss:2.513, Lr:2.50E-05
Epoch:24, Train_acc:15.8%, Train_loss:2.559, Test_acc:20.0%, Test_loss:2.490, Lr:2.50E-05
Epoch:25, Train_acc:18.7%, Train_loss:2.544, Test_acc:20.0%, Test_loss:2.515, Lr:2.50E-05
Epoch:26, Train_acc:19.7%, Train_loss:2.532, Test_acc:19.7%, Test_loss:2.502, Lr:2.50E-05
Epoch:27, Train_acc:17.7%, Train_loss:2.543, Test_acc:20.3%, Test_loss:2.497, Lr:2.50E-05
Epoch:28, Train_acc:17.9%, Train_loss:2.544, Test_acc:20.3%, Test_loss:2.503, Lr:2.50E-05
Epoch:29, Train_acc:18.1%, Train_loss:2.550, Test_acc:20.3%, Test_loss:2.487, Lr:2.50E-05
Epoch:30, Train_acc:18.7%, Train_loss:2.540, Test_acc:20.3%, Test_loss:2.490, Lr:1.25E-05
Epoch:31, Train_acc:16.8%, Train_loss:2.538, Test_acc:20.3%, Test_loss:2.491, Lr:1.25E-05
Epoch:32, Train_acc:17.4%, Train_loss:2.536, Test_acc:20.3%, Test_loss:2.492, Lr:1.25E-05
Epoch:33, Train_acc:17.4%, Train_loss:2.553, Test_acc:20.0%, Test_loss:2.484, Lr:1.25E-05
Epoch:34, Train_acc:18.7%, Train_loss:2.541, Test_acc:20.3%, Test_loss:2.458, Lr:1.25E-05
Epoch:35, Train_acc:20.1%, Train_loss:2.527, Test_acc:20.3%, Test_loss:2.493, Lr:1.25E-05
Epoch:36, Train_acc:18.7%, Train_loss:2.518, Test_acc:20.3%, Test_loss:2.494, Lr:1.25E-05
Epoch:37, Train_acc:17.4%, Train_loss:2.532, Test_acc:20.3%, Test_loss:2.477, Lr:1.25E-05
Epoch:38, Train_acc:17.4%, Train_loss:2.532, Test_acc:20.3%, Test_loss:2.475, Lr:1.25E-05
Epoch:39, Train_acc:19.0%, Train_loss:2.506, Test_acc:20.3%, Test_loss:2.481, Lr:1.25E-05
Epoch:40, Train_acc:18.3%, Train_loss:2.519, Test_acc:20.3%, Test_loss:2.465, Lr:6.25E-06
Done
为了直观表示训练的结果,编写图片曲线可视化的部分:
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 #分辨率
from datetime import datetime
current_time = datetime.now() # 获取当前时间
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.xlabel(current_time)
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()
结果为:

3.3 图片预测
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='./6-data/Angelina Jolie/001_fe3347c0.jpg',
model=model,
transform=train_transforms,
classes=classes)
结果为:
预测结果是:Angelina Jolie

4 个人总结
本次学习使用VGG-16进行人脸识别,加载 ImageNet 预训练的 VGG-16,冻结卷积层、只替换最后一层全连接层来适配自己的人脸分类任务,数据预处理必须和预训练时保持一致(224×224 + ImageNet 的 mean/std),训练时搭配学习率衰减并在每个 epoch 后用 model.eval() 评估测试集,只保留准确率最高的模型。
但是由于VGG-16由16个卷积层和3个全连接层组成,因此具有相对较深的网络结构,计算量特别大,造成了训练时间非常长。
更多推荐




所有评论(0)