微调
(1)在源数据集(例如 ImageNet 数据集)上预训练一个神经网络模型,即源模型。
(2)创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。
(3)为目标模型添加一个输出大小为目标数据集类别个数的输出层,并随机初始化该层的模型参数。
(4)在目标数据集(例如椅子数据集)上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。
为什么要微调
卷积神经网络的核心是:
(1)浅层卷积层提取基础特征,比如边缘,轮廓等基础特征。
(2)深层卷积层提取抽象特征,比如整个脸型。
(3)全连接层根据特征组合进行评分分类。
普通预训练模型的特点是:用了大型数据集做训练,已经具备了提取浅层基础特征和深层抽象特征的能力。
不做微调:
(1)从头开始训练,需要大量的数据,计算时间和计算资源。
(2)存在模型不收敛,参数不够优化,准确率低,模型泛化能力低,容易过拟合等风险。
使用微调:有效避免了上述可能存在的问题。
什么情况下使用微调
(1) 要使用的数据集和预训练模型的数据集相似。如果不太相似,比如你用的预训练的参数是自然景物的图片,你却要做人脸的识别,效果可能就没有那么好了,因为人脸的特征和自然景物的特征提取是不同的,所以相应的参数训练后也是不同的。
(2) 自己搭建或者使用的CNN模型正确率太低。
(3)数据集相似,但数据集数量太少。 (4)计算资源太少。
只有源数据集几十倍或者几百倍于目标数据集,才有效果,不然重新训练一个神经网络即可
正样本:属于目标的样本
负样本:不属于目标的样本
代码:
%matplotlib inline
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
#加载数据集
#@save
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL+'hotdog.zip','fba480ffa8aa7e0febbb511d181409f899b9baa5')
data_dir=d2l.download_extract('hotdog')
train_imgs=torchvision.datasets.ImageFolder(os.path.join(data_dir,'train'))
test_imgs=torchvision.datasets.ImageFolder(os.path.join(data_dir,'test'))
#展示样本,前8个是正类样本 后8个是负类样本
hotdogs=[train_imgs[i][0] for i in range(8)]
not_hotdogs=[train_imgs[-i-1][0] for i in range(8)]
d2l.show_images(hotdogs+not_hotdogs,2,8,scale=1.4);
#进行数据增广
normalize=torchvision.transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
#使用rgb通道的均值和标准差,以标准化每个通道
train_augs=torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(224),torchvision.transforms.RandomHorizontalFlip(),torchvision.transforms.ToTensor(),normalize])
#随机裁剪大小成224*224的图片 水平翻转
test_augs=torchvision.transforms.Compose([
torchvision.transforms.Resize([256,256]),torchvision.transforms.CenterCrop(224),torchvision.transforms.ToTensor(),normalize])
#缩放为256*256,裁剪中央224*224大小
#定义预训练模型
pretrained_net=torchvision.models.resnet18(pretrained=True)
#展示模型顶层(输出层)
pretrained_net.fc
#定义目标模型
finetune_net=torchvision.models.resnet18(pretrained=True)
finetune_net.fc=nn.Linear(finetune_net.fc.in_features,2)
nn.init.xavier_uniform_(finetune_net.fc.weight)
#定义训练函数
def train_fine_tuning(net,learning_rate,batch_size=128,epochs=5,param_group=True):
train_iter = torch.utils.data.DataLoader(dataset=torchvision.datasets.ImageFolder(os.path.join(data_dir,'train'),transform=train_augs),
batch_size=batch_size,shuffle=True)
test_iter = torch.utils.data.DataLoader(dataset=torchvision.datasets.ImageFolder(os.path.join(data_dir,'test'),transform=test_augs),
batch_size=batch_size,shuffle=False)
#reduction='none'表示直接求出loss,然后再求和,得出样本总loss大小,不是求样本平均loss,因此loss值会与样本数有直接线性关系,从而当batch_size批量样本数目变多时,一般都会增加学习率。样本平均loss一般与样本数目关系不大,当batch_size批量样本数目变多时,一般学习率不会增加
devices = d2l.try_all_gpus()
loss = nn.CrossEntropyLoss(reduction='none')
if param_group:
#param_group=True表示使用微调
#表示除最后一层外前面所有层的参数使用微调,最后一层参数学习率是前面所有层参数学习率的10倍
param_conv2d = [param for name,param in net.named_parameters() if name not in ['fc.weight','fc.bias']]
#如果不是最后一层fc的话,直接用预训练模型
optim = torch.optim.SGD([{'params':param_conv2d},
{'params':net.fc.parameters(),
'lr':learning_rate*10}],
lr=learning_rate,weight_decay=0.001)
else:
# param_group=False表示不使用微调,直接使用当前数据集对目标模型从零开始进行训练
optim = torch.optim.SGD(net.parameters(),lr=learning_rate,weight_decay=0.001)
d2l.train_ch13(net,train_iter,test_iter,loss,optim,epochs,devices)
#开始训练
#使用预训练的模型对目标模型进行训练,使用较小的学习率5e-5
train_fine_tuning(finetune_net,learning_rate=5e-5)
训练结果
物体检测的边缘框
x[m,n]是通过numpy库引用数组或矩阵中的某一段数据集的一种写法,
m代表第m维,n代表m维中取第几段特征数据。
通常用法:
x[:,n]或者x[n,:]
x[:,n]表示在全部数组(维)中取第n个数据,直观来说,x[:,n]就是取所有集合的第n个数据,
%matplotlib inline
import torch
from d2l import torch as d2l
#获取图片
d2l.set_figsize()
img=d2l.plt.imread("C:\\Users\\13930\\Pictures\\Saved Pictures\\猫狗.jpg")
d2l.plt.imshow(img)
#@save
def box_corner_to_center(boxes):
#从(左下,右下)转换到(中间,宽度,高度)
x1,y1,x2,y2=boxes[:,0],boxes[:,1],boxes[:,2],boxes[:,3]
cx=(x1+x2)/2
cy=(y1+y2)/2
w=x2-x1#宽
h=y2-y1#高
boxes=torch.stack((cx,cy,w,h),axis=-1)
#(cx,cy)是中心点的坐标
return boxes
#@save
def box_center_to_corner(boxes):
#从(中间,宽度,高度)转换到(左上,右下)
cx,cy,w,h=boxes[:,0],boxes[:,1],boxes[:,2],boxes[:,3]
x1=cx-0.5*w
y1=cy-0.5*h
x2=cx+0.5*w
y2=cy+0.5*h
boxes=torch.stack((x1,y1,x2,y2),axis=-1)
#(x1,y1)是左上左边,(x2,y2)是右下坐标
return boxes
#边缘框位置(左上和右下)
dog_bbox,cat_bbox=[50.0,45.0,150.0,300.0],[130.0,100.0,290.0,300.0]
#根据给定的坐标点,生成边框的样子
#@save
def bbox_to_rect(bbox,color):
return d2l.plt.Rectangle(
xy=(bbox[0],bbox[1]),width=bbox[2]-bbox[0],height=bbox[3]-bbox[1],fill=False,edgecolor=color,linewidth=2)
#描绘边框
fig=d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox,'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox,'red'))
描绘结果
加载一个小的数据集,里面有香蕉的边框位置
import torch
import os
import pandas as pd
import torchvision
import d2l.torch
#@save
d2l.torch.DATA_HUB['banana-detection'] = (
d2l.torch.DATA_URL + 'banana-detection.zip',
'5de26c8fce5ccdea9f91267273464dc968d20d72')
def read_data_bananas(is_train=True):
data_dir = d2l.torch.download_extract('banana-detection')
print(data_dir)
csv_fpath = os.path.join(data_dir, 'bananas_train' if is_train else 'bananas_val', 'label.csv')
csv_file = pd.read_csv(csv_fpath)
csv_file = csv_file.set_index('img_name')
images, targets = [], []
for image_name, target in csv_file.iterrows():
images.append(torchvision.io.read_image(
path=os.path.join(data_dir, 'bananas_train' if is_train else 'bananas_val', 'images', f'{image_name}')))
targets.append(list(target))
return images, torch.tensor(targets).unsqueeze(1) / 256
images, targets = read_data_bananas()
targets[0]
targets.shape
"""一个用于加载香蕉检测数据集的自定义数据集"""
class BananasDataset(torch.utils.data.Dataset):
def __init__(self, is_train):
self.features, self.labels = read_data_bananas(is_train)
def __getitem__(self, item):
return (self.features[item].float(), self.labels[item])
def __len__(self):
return len(self.features)
"""加载香蕉检测数据集"""
def load_data_bananas(batch_size):
train_iter = torch.utils.data.DataLoader(BananasDataset(is_train=True), batch_size, shuffle=True)
val_iter = torch.utils.data.DataLoader(BananasDataset(is_train=False), batch_size, shuffle=False)
return train_iter, val_iter
batch_size, edge_size = 32, 256
train_iter, valid_iter = load_data_bananas(batch_size)
batch = next(iter(train_iter))
batch[0].shape, batch[1].shape
#permute()函数可以同时多次交换tensor的维度,如:b = a.permute(0,2 ,1) 将a的维度索引1和维度索引2交换位置
imgs = (batch[0][0:10].permute(0,2,3,1))/255 #除以255是为了对图片中每一个像素进行标准化
axes = d2l.torch.show_images(imgs,2,5,scale=2)
for axe,label in zip(axes,batch[1][0:10]):
d2l.torch.show_bboxes(axe,bboxes=[label[0][1:5]*256],colors=['w']) #label[0][1:5]*256乘256是因为加载数据集时bounding box边缘框除以256,256是图片的高和宽
x_range = torch.arange(0, 2)
y_range = torch.arange(0, 4)
x, y = torch.meshgrid(y_range, x_range)
print(x)
print(y)
x, y = torch.meshgrid(x_range, y_range)
print(x)
print(y)
锚框
生成大量锚框,找到还有关注物体的锚框,调整其边框位置,至真实的边框