回归
理解数据块API
这里考虑图像回归问题,人们常把它看成一个独立的任务,但是,也可以把它当作另一个基于数据块API的CNN。
关键点检测模型。关键点
是指图像中的特定位置。本例为人的图像找到每张图像中人脸的中心点,即实际上要为每张图像预测两个值:人脸中心点的行坐标和列坐标。
配置数据
path = untar_data(URLs.BIWI_HEAD_POSE)
# 查看数据
# Path.BASE_PATH = path
# path.ls().sorted()
# (path/'01').ls().sorted()
img_files = get_image_files(path)
def img2pose(x): return Path(f'{str(x)[:-7]}pose.txt')
img2pose(img_files[0])
im = PILImage.create(img_files[0])
# im.shape
im.to_thumb(160)
理解相机校准参数文件depth.cal
575.816 0 320
0 575.816 240
0 0 1
//相机内参矩阵,3x3的相机内参矩阵,用于将3D点投影到2D图像平面。它包含了焦距(f_x, f_y)和主点(c_x, c_y)的坐标:f_x = 575.816 和 f_y = 575.816 是x轴和y轴的焦距,单位通常是像素。c_x = 320 和 c_y = 240 是图像的光学中心(也称为主点)的坐标,即图像平面上与相机光轴交点的坐标。
0 0 0 0
//畸变系数,用于校正相机镜头引起的图像畸变。在这个文件中,所有的畸变系数都是0,意味着假设没有畸变或畸变已经被其他方式校正。
1 0 0
0 1 0
0 0 1
//3x3的单位矩阵,代表旋转矩阵。在这个上下文中,它可能表示相机相对于某个参考坐标系的旋转。单位矩阵意味着没有旋转。
0 0 0
//平移向量,用于描述相机相对于参考坐标系的平移。这里的0值意味着没有平移。
640 480
//图像的分辨率,即图像的宽度和高度。在这个例子中,图像的宽度是640像素,高度是480像素。
这些参数通常用于3D重建、图像校正和深度感知等计算机视觉应用中。
# 提取头部中心点
cal = np.genfromtxt(path/'01'/'rgb.cal', skip_footer=6)
def get_ctr(f):
ctr = np.genfromtxt(img2pose(f), skip_header=3)
c1 = ctr[0] * cal[0][0]/ctr[2] + cal[0][2]
c2 = ctr[1] * cal[1][1]/ctr[2] + cal[1][2]
return tensor([c1,c2])#返回校正后的坐标
get_ctr(img_files[0])
# tensor([338.8099, 284.0216])
构造数据块
步骤 1: 定义 get_y
函数
get_y
函数用于为每个项目(在这个场景中是图像)提供标签。用来提取图像中心点的坐标。
步骤 2: 使用 DataBlock
API
使用 DataBlock
API 来定义数据的加载方式,包括输入和标签的类型、如何分割数据集、如何获取输入和标签等。
步骤 3: 定义分割函数
由于数据集中同一个人可能出现在多张图像中,为了确保模型能够泛化到未见过的人,需要特别注意数据的分割方式。可以创建一个分割函数,这个函数基于文件夹名(每个人的图像存储在单独的文件夹中)来决定哪些图像用于训练,哪些用于验证。
步骤 4: 调整图像大小
为了加速训练过程,可以在数据块中加入一个步骤来将图像大小调整为原始大小的一半。
步骤 5: 使用 PointBlock
由于标签代表坐标,需要告诉 fastai
这一点,这样在进行数据增强时,fastai
会相应地增强这些坐标。PointBlock
用于指定标签是坐标点。
biwi = DataBlock(
blocks=(ImageBlock, PointBlock),
get_items=get_image_files,
get_y=get_ctr,
splitter=FuncSplitter(lambda o: o.parent.name=='13'),#这里使用了 FuncSplitter 并传入了一个匿名函数(lambda 函数)。这个函数检查每个项目的父目录名是否等于 '13'。如果等于,这意味着这个项目(图像)属于验证集;如果不等于,这意味着这个项目属于训练集。
batch_tfms=aug_transforms(size=(240,320)),
)
点与数据增强:fastai特有的能自动并准确地将数据增强应用到坐标上的库
dls = biwi.dataloaders(path)
dls.show_batch(max_n=9, figsize=(8,6)) # 先查看一下数据
xb,yb = dls.one_batch()
xb.shape,yb.shape
# (torch.Size([64, 3, 240, 320]), torch.Size([64, 1, 2]))
#64个图像,3个颜色通道,340*320
#64个样本,每个样本的标签是一个1x2的张量
yb[0]
#TensorPoint([[-0.1604, 0.1157]], device='cuda:0')
# 从 DataLoader 中获取一个批次的数据。这个方法非常有用,特别是当你想快速检查数据批次的内容或者格式时。执行这个方法会返回两个元素:xb 和 yb。
#xb 代表输入数据的批次(例如图像),它是一个张量(Tensor)。
#yb 代表与 xb 对应的标签或目标的批次,它也是一个张量
在使用
fastai
库进行图像回归任务时,其强大之处在于你不需要专门为图像回归设计一个独立的应用程序。相反,你只需正确标注数据,并告诉fastai
独立变量(输入)和因变量(标签)分别代表什么类型的数据。这种灵活性和简便性使得fastai
成为处理各种机器学习任务的强大工具。
创建Learner,训练模型
learn = vision_learner(dls, resnet18, y_range=(-1,1)) #坐标被缩放到-1到1区间
# fastai中,y_range通过使用sigmoid_range来实现
def sigmoid_range(x, lo, hi): return torch.sigmoid(x) * (hi-lo) + lo
对于
y_range=(-1,1)
,这意味着模型的输出被限制在 -1 到 1 之间。这通常通过在模型的最后添加一个 Sigmoid 或 Tanh 激活函数来实现,然后将输出缩放到指定的范围。在图像回归任务中,这可以帮助模型学习预测特定范围内的值,例如预测图像中某个点的归一化坐标。使用
y_range
的原因包括:
- 改善学习稳定性:限制输出范围有助于防止模型在训练初期做出极端预测,这可以提高训练过程的稳定性。
- 匹配预期输出:如果你知道目标变量的范围,设置
y_range
可以确保模型的输出始终在这个范围内,这对于提高模型性能很有帮助。- 简化模型的最后一层:通过在最后一层自动应用适当的激活函数和缩放,
y_range
简化了模型架构的设计,使得模型更容易训练和调整。
plot_function(partial(sigmoid_range,lo=-1,hi=1), min=-4, max=4)
查看一下默认使用的损失函数
dls.loss_func
# FlattenedLoss of MSELoss()
使用学习率查找器找到一个好的学习率
learn.lr_find()
# 使用学习率为3e-3
lr = 3e-3
learn.fit_one_cycle(5, lr)
查看训练结果
learn.show_results(ds_idx=1, max_n=3, figsize=(6,8))
# learn.show_results(ds_idx=1, nrows=3, figsize=(6,8))
# ds_idx=1:指定显示结果的数据集索引。在 fastai 中,0 通常代表训练集,1 代表验证集。因此,这里表示从验证集中显示结果。
# max_n 直接控制显示的结果数量,而 nrows 可能用于控制显示结果的布局行数。
# figsize=(6,8):指定显示结果的图形大小,宽度为6英寸,高度为8英寸。
- 灵活的API:强调了使用灵活、强大的编程接口对于快速开发和实验的重要性。
- 迁移学习:指的是利用一个在某任务上训练好的模型,并将其应用到另一个相关但不同的任务上。这种方法可以显著减少训练时间和所需的数据量。
- 图像分类到图像回归的迁移:展示了迁移学习的灵活性,即使是在任务性质有显著差异的情况下(从分类到回归),也能有效地迁移和应用模型。
在看似完全不同的问题(单标签分类、多标签分类和回归)中,我们最终使用的是相同的模型,只是输出的数量不同。变化的是损失函数,这就是为什么重要的是要仔细检查您是否为您的问题使用了正确的损失函数。
如果使用的是 fastai,它会自动尝试从您构建的数据中选择正确的损失函数。但如果使用纯 PyTorch 来构建您的 DataLoaders,确保在决定损失函数选择时仔细思考。通常情况下,可能会需要:
- 单标签分类:使用
nn.CrossEntropyLoss
- 多标签分类:使用
nn.BCEWithLogitsLoss
- 回归:使用
nn.MSELoss
比较fine_tune和fit_one_cycle
在 fastai
库中,fine_tune
和 fit_one_cycle
是两种用于模型训练的方法,它们各自有不同的用途和背景。
fine_tune
fine_tune
方法通常用于迁移学习场景,其中一个预训练模型被调整(fine-tuned)以适应一个新的、但相关的任务。fine_tune
方法的工作流程通常包括两个阶段:
- 冻结阶段:在第一个阶段,模型的主体(通常是预训练的部分)被冻结,只有最后几层(通常是添加的自定义层)被训练。这样做是为了快速适应新任务,同时避免破坏预训练模型的特征提取能力。
- 解冻并微调阶段:在第二个阶段,整个模型(包括预训练的部分)被解冻,并使用较小的学习率进行训练。这一步骤旨在微调模型的所有权重,以更好地适应新任务。
fit_one_cycle
fit_one_cycle
方法基于一种称为“one cycle policy”的训练策略。这种策略涉及在训练过程中只使用一个学习率循环,其中学习率首先增加到某个最大值,然后再减少。这种方法旨在帮助快速收敛,同时减少训练时间和防止过拟合。
fit_one_cycle
方法适用于从头开始训练模型或在迁移学习中微调整个模型时。它通过动态调整学习率(和可能的动量),在训练过程中帮助模型更有效地找到最优解。
总结
fine_tune
是为迁移学习设计的,自动包含了冻结预训练层、训练新层和解冻所有层进行微调的步骤。fit_one_cycle
是一种通用的训练方法,基于“one cycle policy”策略,通过动态调整学习率来加速训练并提高模型性能,适用于各种训练场景。