! [ -e /content ] && pip install -Uqq fastai # 在Colab上升级fastai计算机视觉简介
在计算机视觉中使用 fastai 库。
from fastai.vision.all import *本教程重点介绍如何快速构建一个 Learner 并对预训练模型进行微调,以应对大多数计算机视觉任务。
单标签分类
在这个任务中,我们将使用牛津-印度理工大学宠物数据集,该数据集包含37种不同品种的猫和狗的图像。我们将首先展示如何构建一个简单的猫狗分类器,然后是一个稍微复杂一点的模型,可以分类所有品种。
可以使用以下代码下载并解压数据集:
path = untar_data(URLs.PETS)它只会下载一次,并返回解压缩归档文件的位置。我们可以使用 .ls() 方法检查里面的内容。
path.ls()(#2) [Path('/home/jhoward/.fastai/data/oxford-iiit-pet/images'),Path('/home/jhoward/.fastai/data/oxford-iiit-pet/annotations')]
我们暂时忽略注释文件夹,专注于图像文件夹。get_image_files 是一个 fastai 函数,它帮助我们快速获取一个文件夹中所有的图像文件(递归)。
files = get_image_files(path/"images")
len(files)7390
猫与狗
为了给我们的猫与狗问题标记数据,我们需要知道哪些文件名是狗的图片,哪些是猫的图片。区分这两者的简单方法是:文件名以大写字母开头表示猫,以小写字母开头表示狗。
files[0],files[6](Path('/home/jhoward/.fastai/data/oxford-iiit-pet/images/basset_hound_181.jpg'),
Path('/home/jhoward/.fastai/data/oxford-iiit-pet/images/beagle_128.jpg'))
我们可以定义一个简单的标签函数:
def label_func(f): return f[0].isupper()为了使我们的数据准备好供模型使用,我们需要将其放入一个 DataLoaders 对象中。这里我们有一个使用文件名进行标记的函数,因此我们将使用 ImageDataLoaders.from_name_func。还有其他的 ImageDataLoaders 工厂方法可能更适合您的问题,因此请确保在 vision.data 中检查它们所有。
dls = ImageDataLoaders.from_name_func(path, files, label_func, item_tfms=Resize(224))我们已经将当前工作目录、我们抓取的files、我们的label_func 和最后一个参数item_tfms传递给了这个函数:item_tfms是应用于我们数据集中所有项目的Transform,它会将每张图片调整为224 x 224的大小,通过对最大维度进行随机裁剪使其变为正方形,然后再调整为224 x 224。如果我们不传递这个参数,后面会出现错误,因为无法将项目一起打包。
我们可以使用show_batch方法检查一切是否正常(True表示猫,False表示狗):
dls.show_batch()
然后我们可以创建一个 Learner,这是一个 fastai 对象,它将数据和模型结合起来进行训练,并使用迁移学习在仅仅两行代码中微调预训练模型:
learn = vision_learner(dls, resnet34, metrics=error_rate)
learn.fine_tune(1)| epoch | train_loss | valid_loss | error_rate | time |
|---|---|---|---|---|
| 0 | 0.150819 | 0.023647 | 0.007442 | 00:09 |
| epoch | train_loss | valid_loss | error_rate | time |
|---|---|---|---|---|
| 0 | 0.046232 | 0.011466 | 0.004736 | 00:10 |
第一行下载了一个名为 ResNet34 的模型,该模型在 ImageNet 上进行了预训练,并将其调整为我们的特定问题。然后对该模型进行了微调,并在相对较短的时间内,我们得到了一个错误率远低于 1% 的模型……真是令人惊叹!
如果您想对新图像进行预测,可以使用 learn.predict:
learn.predict(files[0])('False', TensorImage(0), TensorImage([9.9998e-01, 2.0999e-05]))
predict 方法返回三个内容:解码后的预测(这里是 False 表示不是狗),预测类的索引以及所有类的概率张量,按照它们的索引标签顺序(在这种情况下,模型对这是狗的判断非常有信心)。此方法接受一个文件名、一个 PIL 图像或在这种情况下直接接受一个张量。 我们还可以通过 show_results 方法查看一些预测结果:
learn.show_results()
查看本教程中涉及的其他应用程序,如文本或表格,您会发现它们都有一个一致的API,用于收集数据并进行查看,创建一个Learner,训练模型并查看一些预测结果。
分类品种
为了使用品种名称标记我们的数据,我们将使用正则表达式从文件名中提取它。回顾一下文件名,我们有:
files[0].name'great_pyrenees_173.jpg'
所以类名是最后一个 _ 后面跟一些数字之前的所有内容。一个可以捕捉到这个名称的正则表达式是:
pat = r'^(.*)_\d+.jpg'由于使用正则表达式对数据进行标记非常普遍(通常,标签隐藏在文件名中),因此有一个工厂方法可以做到这一点:
dls = ImageDataLoaders.from_name_re(path, files, pat, item_tfms=Resize(224))如之前所示,我们可以使用 show_batch 来查看我们的数据:
dls.show_batch()
由于在37种不同品种中准确分类猫或狗的确切品种是一个更困难的问题,我们将稍微修改DataLoaders的定义,以使用数据增强:
dls = ImageDataLoaders.from_name_re(path, files, pat, item_tfms=Resize(460),
batch_tfms=aug_transforms(size=224))这次我们在分批处理之前将大小调整为更大,并添加了 batch_tfms。 aug_transforms 是一个提供数据增强转换集合的函数,其默认值在许多数据集上表现良好。您可以通过向 aug_transforms 传递适当的参数来定制这些转换。
dls.show_batch()
我们可以像之前一样创建我们的 Learner 并训练我们的模型。
learn = vision_learner(dls, resnet34, metrics=error_rate)我们之前使用了默认的学习率,但我们可能想要找到最佳的学习率。为此,我们可以使用学习率查找器:
learn.lr_find()SuggestedLRs(lr_min=0.010000000149011612, lr_steep=0.0063095735386013985)

它绘制了学习率查找器的图形,并给出两个建议(最小值除以10和最陡的梯度)。我们将在这里使用 3e-3。我们还将进行更多的训练周期:
learn.fine_tune(2, 3e-3)| epoch | train_loss | valid_loss | error_rate | time |
|---|---|---|---|---|
| 0 | 1.270041 | 0.308686 | 0.109608 | 00:16 |
| epoch | train_loss | valid_loss | error_rate | time |
|---|---|---|---|---|
| 0 | 0.468626 | 0.355379 | 0.117050 | 00:21 |
| 1 | 0.418402 | 0.384385 | 0.110961 | 00:20 |
| 2 | 0.267954 | 0.220428 | 0.075778 | 00:21 |
| 3 | 0.143201 | 0.203174 | 0.064953 | 00:20 |
我们可以再次通过show_results查看一些预测:
learn.show_results()
另一个有用的东西是解释对象,它可以向我们显示模型在哪些地方做出了最差的预测:
interp = Interpretation.from_learner(learn)interp.plot_top_losses(9, figsize=(15,10))
单标签分类 - 使用数据块 API
我们还可以使用数据块API将数据放入DataLoaders中。这有点高级,如果您对学习新的API还不感到舒适,可以随意跳过这一部分。
数据块通过给fastai库提供一系列信息来构建:
- 通过一个名为
blocks的参数指定使用的类型:在这里我们有图像和类别,所以我们传递ImageBlock和CategoryBlock。 - 如何获取原始项,在这里是我们的函数
get_image_files。 - 如何标记这些项,这里使用与之前相同的正则表达式。
- 如何将这些项进行分割,这里使用随机分割器。
- 像以前一样的
item_tfms和batch_tfms。
pets = DataBlock(blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=RandomSplitter(),
get_y=using_attr(RegexLabeller(r'(.+)_\d+.jpg$'), 'name'),
item_tfms=Resize(460),
batch_tfms=aug_transforms(size=224))pets对象本身是空的:它只包含将帮助我们收集数据的函数。我们必须调用dataloaders方法以获取DataLoaders。我们传递给它数据的来源:
dls = pets.dataloaders(untar_data(URLs.PETS)/"images")然后我们可以使用 dls.show_batch() 查看我们的图片。
dls.show_batch(max_n=9)
多标签分类
对于这个任务,我们将使用Pascal数据集,该数据集包含不同种类的物体/人物的图像。它最初是一个用于物体检测的数据集,意味着任务不仅仅是检测图像中是否存在某一类别的实例,而是还要在其周围绘制一个边界框。在这里,我们将尝试预测给定图像中的所有类别。
多标签分类与之前的不同之处在于,每张图像并不属于一个类别。例如,一张图像可能同时包含一个人和一匹马。或者没有我们研究的任何类别。
和之前一样,我们可以相当容易地下载这个数据集:
path = untar_data(URLs.PASCAL_2007)
path.ls()(#9) [Path('/home/jhoward/.fastai/data/pascal_2007/valid.json'),Path('/home/jhoward/.fastai/data/pascal_2007/test.json'),Path('/home/jhoward/.fastai/data/pascal_2007/test'),Path('/home/jhoward/.fastai/data/pascal_2007/train.json'),Path('/home/jhoward/.fastai/data/pascal_2007/test.csv'),Path('/home/jhoward/.fastai/data/pascal_2007/models'),Path('/home/jhoward/.fastai/data/pascal_2007/segmentation'),Path('/home/jhoward/.fastai/data/pascal_2007/train.csv'),Path('/home/jhoward/.fastai/data/pascal_2007/train')]
每张图像的标签信息存储在名为 train.csv 的文件中。我们使用 pandas 加载它:
df = pd.read_csv(path/'train.csv')
df.head()| fname | labels | is_valid | |
|---|---|---|---|
| 0 | 000005.jpg | chair | True |
| 1 | 000007.jpg | car | True |
| 2 | 000009.jpg | horse person | True |
| 3 | 000012.jpg | car | False |
| 4 | 000016.jpg | bicycle | True |
多标签分类 - 使用高级API
这很简单:对于每个文件名,我们获取不同的标签(用空格分隔),最后一列指示它是否在验证集中。为了快速将其放入DataLoaders中,我们有一个工厂方法from_df。我们可以指定所有图像的基础路径,一个额外的文件夹(在基础路径和文件名之间添加,这里是train),用于考虑验证集的valid_col(如果不指定,我们将取一个随机子集),一个用于分割标签的label_delim,以及之前的item_tfms和batch_tfms。
请注意,我们不必指定fn_col和label_col,因为它们默认分别是第一列和第二列。
dls = ImageDataLoaders.from_df(df, path, folder='train', valid_col='is_valid', label_delim=' ',
item_tfms=Resize(460), batch_tfms=aug_transforms(size=224))与之前一样,我们可以使用 show_batch 方法查看数据。
dls.show_batch()
训练模型与以前一样简单:相同的函数可以应用,fastai库将自动检测到我们处于多标签问题,因此选择正确的损失函数。唯一不同的是我们传递的指标:error_rate 对于多标签问题无效,但我们可以使用 accuracy_thresh 和 F1ScoreMulti。我们还可以更改指标的默认名称,例如,我们可能希望看到使用 macro 和 samples 平均的F1分数。
f1_macro = F1ScoreMulti(thresh=0.5, average='macro')
f1_macro.name = 'F1(macro)'
f1_samples = F1ScoreMulti(thresh=0.5, average='samples')
f1_samples.name = 'F1(samples)'
learn = vision_learner(dls, resnet50, metrics=[partial(accuracy_multi, thresh=0.5), f1_macro, f1_samples])和之前一样,我们可以使用 learn.lr_find 来选择一个合适的学习率:
learn.lr_find()SuggestedLRs(lr_min=0.025118863582611083, lr_steep=0.03981071710586548)

我们可以选择建议的学习率,并对我们的预训练模型进行微调:
learn.fine_tune(2, 3e-2)| epoch | train_loss | valid_loss | accuracy_multi | time |
|---|---|---|---|---|
| 0 | 0.437855 | 0.136942 | 0.954801 | 00:17 |
| epoch | train_loss | valid_loss | accuracy_multi | time |
|---|---|---|---|---|
| 0 | 0.156202 | 0.465557 | 0.914801 | 00:20 |
| 1 | 0.179814 | 0.382907 | 0.930040 | 00:20 |
| 2 | 0.157007 | 0.129412 | 0.953924 | 00:20 |
| 3 | 0.125787 | 0.109033 | 0.960856 | 00:19 |
和以前一样,我们可以轻松查看结果:
learn.show_results()
或者获取给定图像的预测:
learn.predict(path/'train/000005.jpg')((#2) ['chair','diningtable'],
TensorImage([False, False, False, False, False, False, False, False, True, False,
True, False, False, False, False, False, False, False, False, False]),
TensorImage([1.6750e-03, 5.3663e-03, 1.6378e-03, 2.2269e-03, 5.8645e-02, 6.3422e-03,
5.6991e-03, 1.3682e-02, 8.6864e-01, 9.7093e-04, 6.4747e-01, 4.1217e-03,
1.2410e-03, 2.9412e-03, 4.7769e-01, 9.9664e-02, 4.5190e-04, 6.3532e-02,
6.4487e-03, 1.6339e-01]))
对于单个分类预测,我们获得了三样东西。最后一个是模型对每个类别的预测(范围从0到1)。倒数第二个对应于一个独热编码的目标(对于所有预测的类别,概率大于0.5的类别为True),第一个是解码后的可读版本。
和之前一样,我们可以检查模型表现最差的地方:
interp = Interpretation.from_learner(learn)
interp.plot_top_losses(9)| target | predicted | probabilities | loss | |
|---|---|---|---|---|
| 0 | car;person;tvmonitor | car | tensor([7.2388e-12, 5.9609e-06, 1.7054e-11, 3.8985e-09, 7.7078e-12, 3.4044e-07,\n 9.9999e-01, 7.2118e-12, 1.0105e-05, 3.1035e-09, 2.3334e-09, 9.1077e-09,\n 1.6201e-09, 1.1083e-08, 1.0809e-02, 2.1072e-07, 9.5961e-16, 5.0478e-07,\n 4.4531e-10, 9.6444e-12]) | 1.494603157043457 |
| 1 | boat | car | tensor([8.3430e-06, 1.9416e-03, 6.9865e-06, 1.2985e-04, 1.6142e-06, 8.2200e-05,\n 9.9698e-01, 1.3143e-06, 1.0047e-03, 4.9794e-05, 1.9155e-05, 4.7409e-05,\n 7.5056e-05, 1.6572e-05, 3.4760e-02, 6.9266e-04, 1.3006e-07, 6.0702e-04,\n 1.5781e-05, 1.9860e-06]) | 0.7395917773246765 |
| 2 | bus;car | car | tensor([2.2509e-11, 1.0772e-05, 6.0177e-11, 4.8728e-09, 1.7920e-11, 4.8695e-07,\n 9.9999e-01, 9.0638e-12, 1.9819e-05, 8.8023e-09, 5.1272e-09, 2.3535e-08,\n 6.0401e-09, 7.2609e-09, 4.4117e-03, 4.8268e-07, 1.2528e-14, 1.2667e-06,\n 8.2282e-10, 1.6300e-11]) | 0.7269787192344666 |
| 3 | chair;diningtable;person | person;train | tensor([1.6638e-03, 2.0881e-02, 4.7525e-03, 2.6422e-02, 6.2972e-04, 4.7170e-02,\n 1.2263e-01, 2.9744e-03, 5.5352e-03, 7.1830e-03, 1.0062e-03, 2.6123e-03,\n 1.8208e-02, 5.9618e-02, 7.6859e-01, 3.3504e-03, 1.1324e-03, 2.3881e-03,\n 6.5440e-01, 1.7040e-03]) | 0.6879587769508362 |
| 4 | boat;chair;diningtable;person | person | tensor([0.0058, 0.0461, 0.0068, 0.1083, 0.0094, 0.0212, 0.4400, 0.0047, 0.0166,\n 0.0054, 0.0030, 0.0258, 0.0020, 0.0800, 0.5880, 0.0147, 0.0026, 0.1440,\n 0.0219, 0.0166]) | 0.6826764941215515 |
| 5 | bicycle;car;person | car | tensor([3.6825e-09, 7.3755e-05, 1.7181e-08, 4.5056e-07, 3.5667e-09, 1.0882e-05,\n 9.9939e-01, 6.0704e-09, 5.7179e-05, 3.8519e-07, 9.3825e-08, 6.1463e-07,\n 3.9191e-07, 2.6800e-06, 3.3091e-02, 3.1972e-06, 2.6873e-11, 1.1967e-05,\n 1.1480e-07, 3.3320e-09]) | 0.6461981534957886 |
| 6 | bottle;cow;person | chair;person;sofa | tensor([5.4520e-04, 4.2805e-03, 2.3828e-03, 1.4127e-03, 4.5856e-02, 3.5540e-03,\n 9.1525e-03, 2.9113e-02, 6.9326e-01, 1.0407e-03, 7.0658e-02, 3.1101e-02,\n 2.4843e-03, 2.9908e-03, 8.8695e-01, 2.2719e-01, 1.0283e-03, 6.0414e-01,\n 1.3598e-03, 5.7382e-02]) | 0.6329519152641296 |
| 7 | chair;dog;person | cat | tensor([3.4073e-05, 1.3574e-03, 7.0516e-04, 1.9189e-04, 6.0819e-03, 4.7242e-05,\n 9.6424e-04, 9.3669e-01, 9.0736e-02, 8.1472e-04, 1.1019e-02, 5.4633e-02,\n 2.6190e-04, 1.4943e-04, 1.2755e-02, 1.7530e-02, 2.2532e-03, 2.2129e-02,\n 1.5532e-04, 6.6390e-03]) | 0.6249645352363586 |
| 8 | car;person;pottedplant | car | tensor([1.3978e-06, 2.1693e-03, 2.2698e-07, 7.5037e-05, 9.4007e-07, 1.2369e-03,\n 9.9919e-01, 1.0879e-07, 3.1837e-04, 1.8340e-05, 7.5422e-06, 2.3891e-05,\n 2.5957e-05, 3.0890e-05, 8.4529e-02, 2.0280e-04, 4.1234e-09, 1.7978e-04,\n 2.3258e-05, 6.0897e-07]) | 0.5489450693130493 |

多标签分类 - 使用数据块 API
我们也可以使用数据块 API 将数据加载到 DataLoaders 中。如前所述,如果您尚未准备好学习新的 API,可以随意跳过这一部分。
请记住我们的数据框中的数据结构:
df.head()| fname | labels | is_valid | |
|---|---|---|---|
| 0 | 000005.jpg | chair | True |
| 1 | 000007.jpg | car | True |
| 2 | 000009.jpg | horse person | True |
| 3 | 000012.jpg | car | False |
| 4 | 000016.jpg | bicycle | True |
在这种情况下,我们通过提供以下内容来构建数据块:
- 使用的类型:
ImageBlock和MultiCategoryBlock。 - 如何从我们的数据框中获取输入项:这里我们读取列
fname,并需要在开头添加path/train/以获取正确的文件名。 - 如何从我们的数据框中获取目标:这里我们读取列
labels,并需要通过空格进行分割。 - 如何拆分项目,这里通过使用列
is_valid。 - 像之前一样的
item_tfms和batch_tfms。
pascal = DataBlock(blocks=(ImageBlock, MultiCategoryBlock),
splitter=ColSplitter('is_valid'),
get_x=ColReader('fname', pref=str(path/'train') + os.path.sep),
get_y=ColReader('labels', label_delim=' '),
item_tfms = Resize(460),
batch_tfms=aug_transforms(size=224))这个块与之前略有不同:我们不需要传递一个函数来收集我们的所有项目,因为我们提供的数据框架已经包含了所有的项目。然而,我们确实需要对该数据框中的行进行预处理,以获取我们的输入,这就是我们传递 get_x 的原因。它默认使用 fastai 的 noop 函数,这就是我们之前不需要传递它的原因。
与之前一样,pascal 只是一个蓝图。我们需要将数据源传递给它,以便能够获取 DataLoaders:
dls = pascal.dataloaders(df)然后我们可以使用 dls.show_batch() 查看我们的一些图片。
dls.show_batch(max_n=9)
分割
分割是一个问题,我们需要为图像的每个像素预测一个类别。对于这个任务,我们将使用Camvid数据集,这是一个来自汽车摄像头的屏幕截图数据集。图像的每个像素都有一个标签,例如“道路”、“汽车”或“行人”。
和往常一样,我们可以使用我们的untar_data函数来下载数据。
path = untar_data(URLs.CAMVID_TINY)
path.ls()(#3) [Path('/home/jhoward/.fastai/data/camvid_tiny/codes.txt'),Path('/home/jhoward/.fastai/data/camvid_tiny/images'),Path('/home/jhoward/.fastai/data/camvid_tiny/labels')]
images 文件夹包含图像,相应的标签分割掩码位于 labels 文件夹中。codes 文件包含对应类的整数(掩码为每个像素分配一个整数值)。
codes = np.loadtxt(path/'codes.txt', dtype=str)
codesarray(['Animal', 'Archway', 'Bicyclist', 'Bridge', 'Building', 'Car',
'CartLuggagePram', 'Child', 'Column_Pole', 'Fence', 'LaneMkgsDriv',
'LaneMkgsNonDriv', 'Misc_Text', 'MotorcycleScooter', 'OtherMoving',
'ParkingBlock', 'Pedestrian', 'Road', 'RoadShoulder', 'Sidewalk',
'SignSymbol', 'Sky', 'SUVPickupTruck', 'TrafficCone',
'TrafficLight', 'Train', 'Tree', 'Truck_Bus', 'Tunnel',
'VegetationMisc', 'Void', 'Wall'], dtype='<U17')
分割 - 使用高级API
与之前一样,get_image_files 函数帮助我们获取所有图像文件名:
fnames = get_image_files(path/"images")
fnames[0]Path('/home/jhoward/.fastai/data/camvid_tiny/images/0006R0_f02910.png')
让我们来看看标签文件夹:
(path/"labels").ls()[0]Path('/home/jhoward/.fastai/data/camvid_tiny/labels/0016E5_08137_P.png')
看起来分割掩码与图像具有相同的基本名称,但多了一个 _P,因此我们可以定义一个标签函数:
def label_func(fn): return path/"labels"/f"{fn.stem}_P{fn.suffix}"我们可以使用 SegmentationDataLoaders 来收集我们的数据:
dls = SegmentationDataLoaders.from_label_func(
path, bs=8, fnames = fnames, label_func = label_func, codes = codes
)我们在这里不需要传递 item_tfms 来调整图像的大小,因为它们已经都是相同的尺寸。
和往常一样,我们可以使用 show_batch 方法查看我们的数据。在这个例子中,fastai 库使用每个像素特定颜色的方式叠加了掩码:
dls.show_batch(max_n=6)
传统的卷积神经网络(CNN)不适用于分割任务,我们必须使用一种特殊的模型,称为UNet,因此我们使用unet_learner来定义我们的Learner:
learn = unet_learner(dls, resnet34)
learn.fine_tune(6)| epoch | train_loss | valid_loss | time |
|---|---|---|---|
| 0 | 2.802264 | 2.476579 | 00:03 |
| epoch | train_loss | valid_loss | time |
|---|---|---|---|
| 0 | 1.664625 | 1.525224 | 00:03 |
| 1 | 1.440311 | 1.271917 | 00:02 |
| 2 | 1.339473 | 1.123384 | 00:03 |
| 3 | 1.233049 | 0.988725 | 00:03 |
| 4 | 1.110815 | 0.805028 | 00:02 |
| 5 | 1.008600 | 0.815411 | 00:03 |
| 6 | 0.924937 | 0.755052 | 00:02 |
| 7 | 0.857789 | 0.769288 | 00:03 |
和之前一样,我们可以通过 show_results 获取一些预测结果的概念。
learn.show_results(max_n=6, figsize=(7,8))
我们还可以使用 SegmentationInterpretation 类对验证集上的模型错误进行排序,然后绘制对验证损失贡献最大的 k 个实例。
interp = SegmentationInterpretation.from_learner(learn)
interp.plot_top_losses(k=3)
分割 - 使用数据块API
我们也可以使用数据块 API 将数据放入 DataLoaders 中。正如之前所说的,如果您对学习新的 API 还不太熟悉,请随意跳过这部分。
在这种情况下,我们通过提供以下内容来构建数据块:
- 使用的类型:
ImageBlock和MaskBlock。我们为MaskBlock提供codes,因为无法从数据中推测它们。 - 如何收集我们的项,这里使用
get_image_files。 - 如何从我们的项目中获取目标:通过使用
label_func。 - 如何拆分项目,这里是随机拆分。
batch_tfms用于数据增强。
camvid = DataBlock(blocks=(ImageBlock, MaskBlock(codes)),
get_items = get_image_files,
get_y = label_func,
splitter=RandomSplitter(),
batch_tfms=aug_transforms(size=(120,160)))dls = camvid.dataloaders(path/"images", path=path, bs=8)dls.show_batch(max_n=6)
点数
本节使用数据块 API,因此如果您之前跳过了它,我们建议您也跳过本节。
现在我们来看一个任务,我们想要预测图片中的点。为此,我们将使用 Biwi Kinect 头部姿态数据集。首先,我们像往常一样开始下载数据集。
path = untar_data(URLs.BIWI_HEAD_POSE)让我们看看我们有什么!
path.ls()(#50) [Path('/home/sgugger/.fastai/data/biwi_head_pose/01.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/18.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/04'),Path('/home/sgugger/.fastai/data/biwi_head_pose/10.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/24'),Path('/home/sgugger/.fastai/data/biwi_head_pose/14.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/20.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/11.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/02.obj'),Path('/home/sgugger/.fastai/data/biwi_head_pose/07')...]
有24个目录,编号从01到24(它们对应于被拍摄的不同人员),以及一个相应的.obj文件(我们在这里不需要它们)。我们将查看其中一个目录的内容:
(path/'01').ls()(#1000) [Path('01/frame_00087_pose.txt'),Path('01/frame_00079_pose.txt'),Path('01/frame_00114_pose.txt'),Path('01/frame_00084_rgb.jpg'),Path('01/frame_00433_pose.txt'),Path('01/frame_00323_rgb.jpg'),Path('01/frame_00428_rgb.jpg'),Path('01/frame_00373_pose.txt'),Path('01/frame_00188_rgb.jpg'),Path('01/frame_00354_rgb.jpg')...]
在子目录中,我们有不同的帧,每个帧都有一张图像 (\_rgb.jpg) 和一个姿态文件 (\_pose.txt)。我们可以轻松地通过 get_image_files 递归获取所有图像文件,然后编写一个函数,将图像文件名转换为其关联的姿态文件。
img_files = get_image_files(path)
def img2pose(x): return Path(f'{str(x)[:-7]}pose.txt')
img2pose(img_files[0])Path('04/frame_00084_pose.txt')
我们可以看看我们的第一张图片:
im = PILImage.create(img_files[0])
im.shape(480, 640)
im.to_thumb(160)
Biwi数据集网站解释了与每个图像关联的姿势文本文件的格式,该文件显示了头部中心的位置。对于我们的目的,这些细节并不重要,因此我们只展示用于提取头部中心点的函数:
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([372.4046, 245.8602])
我们可以将这个函数作为get_y传递给DataBlock,因为它负责给每个项目标记。我们将把图像调整为输入大小的一半,以加快训练速度。
需要注意的一点是,我们不应仅仅使用随机分割。这样做的原因是,在这个数据集中,同一个人出现在多个图像中——但我们希望确保我们的模型能够推广到它尚未见过的人。数据集中的每个文件夹包含一个人的图像。因此,我们可以创建一个分割函数,该函数仅对一个人返回true,从而产生一个仅包含该人图像的验证集。
与之前的数据块示例的唯一区别是第二个块是一个PointBlock。这是必要的,以便fastai知道标签表示坐标;这样,它知道在进行数据增强时,应对这些坐标执行与图像相同的增强。
biwi = DataBlock(
blocks=(ImageBlock, PointBlock),
get_items=get_image_files,
get_y=get_ctr,
splitter=FuncSplitter(lambda o: o.parent.name=='13'),
batch_tfms=[*aug_transforms(size=(240,320)),
Normalize.from_stats(*imagenet_stats)]
)dls = biwi.dataloaders(path)
dls.show_batch(max_n=9, figsize=(8,6))
现在我们已经整理好我们的数据,我们可以像往常一样使用fastai API的其他部分。vision_learner 在这种情况下表现得非常好,库会从数据中推断出合适的损失函数:
learn = vision_learner(dls, resnet18, y_range=(-1,1))learn.lr_find()
然后我们可以训练我们的模型:
learn.fine_tune(1, 5e-3)| epoch | train_loss | valid_loss | time |
|---|---|---|---|
| 0 | 0.057434 | 0.002171 | 00:31 |
| epoch | train_loss | valid_loss | time |
|---|---|---|---|
| 0 | 0.005320 | 0.005426 | 00:39 |
| 1 | 0.003624 | 0.000698 | 00:39 |
| 2 | 0.002163 | 0.000099 | 00:39 |
| 3 | 0.001325 | 0.000233 | 00:39 |
损失是均方误差,这意味着我们平均会犯一个错误。
math.sqrt(0.0001)0.01
预测我们得分时的百分比!我们可以像往常一样查看这些结果:
learn.show_results()