fast.ai 深度学习笔记(七)(3)

简介: fast.ai 深度学习笔记(七)

fast.ai 深度学习笔记(七)(2)https://developer.aliyun.com/article/1482689

分割[1:29:13]

最后,让我们谈谈分割。这来自著名的 CamVid 数据集,这是一个学术分割数据集的经典示例。基本上你可以看到我们的做法是从一幅图片开始(实际上在这个数据集中是视频帧),我们有一些标签,它们实际上不是颜色 - 每个标签都有一个 ID,这些 ID 映射到颜色。所以红色可能是 1,紫色可能是 2,浅粉色可能是 3,等等。所以所有建筑物属于一类,所有汽车属于另一类,所有人属于另一类,所有道路属于另一类,依此类推。所以我们实际上在这里为每个像素进行多类分类。你可以看到,有时多类分类确实非常棘手 - 就像这些分支。尽管有时标签实际上并不是那么好。正如你所看到的,这非常粗糙。这就是我们要做的。

我们将进行分割,所以这很像边界框。但与其只是找到每个物体周围的框,我们实际上要为每个像素标记其类别。实际上,这实际上要容易得多,因为它非常适合我们的 CNN 风格,我们可以创建任何输出为 N 乘以 M 网格的 CNN,其中包含从 0 到 C 的整数,其中 C 是类别数。然后我们可以使用 softmax 激活的交叉熵损失,然后就完成了。我实际上可以在这里停止课程,你可以使用在第 1 和第 2 课中学到的完全相同的方法,你会得到一个完全可以接受的结果。所以首先要说的是,这实际上并不是一件非常困难的事情。但我们将尽力做得更好。

以简单的方式进行[1:31:26]

笔记本

让我们从最简单的方式开始。我们将使用 Kaggle Carvana比赛,你可以像往常一样使用 Kaggle API 下载它。

%matplotlib inline
%reload_ext autoreload
%autoreload 2
from fastai.conv_learner import *
from fastai.dataset import *
from pathlib import Path
import json

设置

有一个包含一堆图像的训练文件夹,这是自变量,还有一个 train_masks 文件夹,这是因变量,它们看起来像下面这样。

在这种情况下,就像猫和狗一样,我们选择简单的方式,而不是进行多类分类,我们将进行二元分类。但当然,多类分类只是更一般的版本 - 分类交叉熵或二元分类熵。在概念上没有区别,因此因变量只是零和一,而自变量是常规图像。

为了做到这一点,真的很有帮助知道汽车是什么样子的。因为我们真正想做的是弄清楚这是一辆车,以及它的方向,并根据图片和他们对汽车外观的理解,在我们期望汽车出现的地方放置白色像素。

PATH = Path('data/carvana')
list(PATH.iterdir())
'''
[PosixPath('data/carvana/train_masks.csv'),
 PosixPath('data/carvana/train_masks-128'),
 PosixPath('data/carvana/sample_submission.csv'),
 PosixPath('data/carvana/train_masks_png'),
 PosixPath('data/carvana/train.csv'),
 PosixPath('data/carvana/train-128'),
 PosixPath('data/carvana/train'),
 PosixPath('data/carvana/metadata.csv'),
 PosixPath('data/carvana/tmp'),
 PosixPath('data/carvana/models'),
 PosixPath('data/carvana/train_masks')]
'''
MASKS_FN = 'train_masks.csv'
META_FN = 'metadata.csv'
TRAIN_DN = 'train'
MASKS_DN = 'train_masks'
masks_csv = pd.read_csv(PATH/MASKS_FN)
masks_csv.head()

原始数据集还附带了这些 CSV 文件[1:32:44]。我实际上并没有用它们做很多其他事情,只是从中获取图像列表。

meta_csv = pd.read_csv(PATH/META_FN)
meta_csv.head()

def show_img(im, figsize=None, ax=None, alpha=None):
    if not ax: 
        fig,ax = plt.subplots(figsize=figsize)
    ax.imshow(im, alpha=alpha)
    ax.set_axis_off()
    return axCAR_ID = '00087a6bd4dc'
list((PATH/TRAIN_DN).iterdir())[:5]
'''
[PosixPath('data/carvana/train/5ab34f0e3ea5_15.jpg'),
 PosixPath('data/carvana/train/de3ca5ec1e59_07.jpg'),
 PosixPath('data/carvana/train/28d9a149cb02_13.jpg'),
 PosixPath('data/carvana/train/36a3f7f77e85_12.jpg'),
 PosixPath('data/carvana/train/843763f47895_08.jpg')]
'''
Image.open(PATH/TRAIN_DN/f'{CAR_ID}_01.jpg').resize((300,200))

list((PATH/MASKS_DN).iterdir())[:5]
'''
[PosixPath('data/carvana/train_masks/6c0cd487abcd_03_mask.gif'),
 PosixPath('data/carvana/train_masks/351c583eabd6_01_mask.gif'),
 PosixPath('data/carvana/train_masks/90fdd8932877_02_mask.gif'),
 PosixPath('data/carvana/train_masks/28d9a149cb02_10_mask.gif'),
 PosixPath('data/carvana/train_masks/88bc32b9e1d9_14_mask.gif')]
'''
Image.open(PATH/MASKS_DN/f'{CAR_ID}_01_mask.gif').resize((300,200))

每张图片在车辆 ID 之后都有一个 01、02 等,我已经打印出其中一个车辆的所有 16 个方向,正如你所看到的,基本上这些数字是一个车辆的 16 个方向[1:32:58]。我认为在这个比赛中没有人实际上使用这些方向信息。我相信他们都保留了车辆的图像,只是单独处理它们。

ims = [
    open_image(PATH/TRAIN_DN/f'{CAR_ID}_{i+1:02d}.jpg') 
    for i in range(16)
]
fig, axes = plt.subplots(4, 4, figsize=(9, 6))
for i,ax in enumerate(axes.flat): 
    show_img(ims[i], ax=ax)
plt.tight_layout(pad=0.1)

调整大小和转换[1:33:27]

这些图像非常大 - 大小超过 1000 乘以 1000,只是打开 JPEG 并调整它们的大小很慢。所以我对它们进行了处理。此外,OpenCV 无法处理 GIF 文件,因此我对它们进行了转换。

问题:有人最初如何获得这些用于训练的蒙版?Mechanical turk或其他什么[1:33:48]?是的,只是很多无聊的工作。可能有一些工具可以帮助你进行一些边缘捕捉,这样人类可以粗略地完成,然后只需微调它错误的部分。这种标签是昂贵的。所以我真正想要做的事情之一是增强深度学习交互式标注工具,因为这显然是可以帮助很多人的事情。

我这里有一个小节,如果你想的话可以运行。你可能想要。它将 GIF 转换为 PNG,所以只需用 PIL 打开它,然后保存为 PNG,因为 OpenCV 不支持 GIF。像往常一样,对于这种类型的东西,我使用线程池,这样我就可以利用并行处理。然后创建一个单独的目录train-128train_masks-128,其中包含它们的 128x128 调整大小版本。

这是在过程早期进行的工作,可以让你保持理智的工作。所以每当你获得新的数据集时,认真考虑创建一个较小的版本以加快速度。每当你发现自己在电脑上等待时,尝试想出一种创建较小版本的方法。

(PATH/'train_masks_png').mkdir(exist_ok=True)
def convert_img(fn):
    fn = fn.name
    Image.open(PATH/'train_masks'/fn).save(
        PATH/'train_masks_png'/f'{fn[:-4]}.png'
    )
    files = list((PATH/'train_masks').iterdir())
with ThreadPoolExecutor(8) as e: 
    e.map(convert_img, files)
    (PATH/'train_masks-128').mkdir(exist_ok=True)
def resize_mask(fn):
    Image.open(fn).resize((128,128)).save(
        (fn.parent.parent)/'train_masks-128'/fn.name
    )
files = list((PATH/'train_masks_png').iterdir())
with ThreadPoolExecutor(8) as e: 
    e.map(resize_img, files)
    (PATH/'train-128').mkdir(exist_ok=True)
def resize_img(fn):
    Image.open(fn).resize((128,128)).save(
        (fn.parent.parent)/'train-128'/fn.name
    )
files = list((PATH/'train').iterdir())
with ThreadPoolExecutor(8) as e: 
    e.map(resize_img, files)

所以在你从 Kaggle 获取它之后,你可能想要运行这些东西,离开,吃午餐,回来时,当你完成时,你将拥有这些较小的目录,我们将从 128x128 开始使用。

数据集[1:35:33]

TRAIN_DN = 'train-128'
MASKS_DN = 'train_masks-128'
sz = 128
bs = 64
ims = [
    open_image(PATH/TRAIN_DN/f'{CAR_ID}_{i+1:02d}.jpg') 
    for i in range(16)
]
im_masks = [
    open_image(PATH/MASKS_DN/f'{CAR_ID}_{i+1:02d}_mask.png') 
    for i in range(16)
]

这里有一个很酷的技巧。如果你使用相同的轴对象(ax)两次绘制图像,第二次使用 alpha,你可能知道在计算机视觉世界中意味着透明度,那么你实际上可以在照片的顶部绘制蒙版。这是一个很好的方法,可以看到所有车辆组中所有照片顶部的所有蒙版。

fig, axes = plt.subplots(4, 4, figsize=(9, 6))
for i,ax in enumerate(axes.flat):
    ax = show_img(ims[i], ax=ax)
    show_img(im_masks[i][...,0], ax=ax, alpha=0.5)
plt.tight_layout(pad=0.1)

这是我们已经看过两次的相同的 MatchedFilesDataset。这是相同的代码。这里有一些重要的东西。如果我们在训练集中有左边的图像,然后验证集中有右边的图像,那将是一种作弊,因为它是相同的车辆。

class MatchedFilesDataset(FilesDataset):
    def __init__(self, fnames, y, transform, path):
        self.y=y
        assert(len(fnames)==len(y))
        super().__init__(fnames, transform, path)
    def get_y(self, i): 
        return open_image(os.path.join(self.path, self.y[i]))
    def get_c(self): 
        return 0
x_names = np.array([Path(TRAIN_DN)/o for o in masks_csv['img']])
y_names = np.array([
    Path(MASKS_DN)/f'{o[:-4]}_mask.png' 
     o in masks_csv['img']
])
len(x_names)//16//5*16
'''
1008
'''

所以我们使用一系列连续的汽车 ID,由于每个集合是一组 16 个,我们确保可以被 16 整除。因此,我们确保我们的验证集包含与训练集不同的汽车 ID。这是你必须小心的事情。在 Kaggle 上,情况并不那么糟糕 - 你会知道,因为你会提交你的结果,你的排行榜上的结果会与你的验证集有很大不同。但在现实世界中,你不会知道,直到你投入生产并让公司破产并失去工作。所以在这种情况下,你可能需要仔细考虑你的验证集。

val_idxs = list(range(1008))
((val_x,trn_x),(val_y,trn_y)) = split_by_idx(val_idxs, x_names, y_names)
len(val_x),len(trn_x)
'''
(1008, 4080)
'''

在这里,我们将使用转换类型分类(TfmType.CLASS)[1:37:03]。这基本上与转换类型像素(TfmType.PIXEL)相同,但是如果你考虑一下,对于像素版本,如果我们旋转一点,那么我们可能希望在两者之间平均像素,但是分类,显然我们不需要。我们使用最近邻。所以这里有一点不同。此外,对于分类,光照不起作用,归一化不起作用于因变量。

aug_tfms = [
    RandomRotate(4, tfm_y=TfmType.CLASS),
    RandomFlip(tfm_y=TfmType.CLASS),
    RandomLighting(0.05, 0.05)
]
# aug_tfms = []

它们已经是方形图像,所以我们不必进行任何裁剪。

tfms = tfms_from_model(
    resnet34, sz, 
    crop_type=CropType.NO, 
    tfm_y=TfmType.CLASS, 
    aug_tfms=aug_tfms
)
datasets = ImageData.get_ds(
    MatchedFilesDataset, 
    (trn_x,trn_y), 
    (val_x,val_y), 
    tfms, 
    path=PATH
)
md = ImageData(
    PATH, datasets, bs, 
    num_workers=8, 
    classes=None
)
denorm = md.trn_ds.denorm
x,y = next(iter(md.aug_dl))
x = denorm(x)

所以在这里你可以看到增强图像的不同版本 - 它们在移动一点,旋转一点,等等。

fig, axes = plt.subplots(5, 6, figsize=(12, 10))
for i,ax in enumerate(axes.flat):
    ax=show_img(x[i], ax=ax)
    show_img(y[i], ax=ax, alpha=0.5)
plt.tight_layout(pad=0.1)

在我们的学习小组中,我经常被问到如何调试和修复不起作用的东西。我从来没有一个很好的答案,除了每次我解决问题都是因为我经常做这样的事情。我总是在进行过程中打印出所有内容,然后我搞砸的那一件事总是最后发现是我忘记检查的那一件事。你能做这种事情的越多越好。如果你不看所有的中间结果,你会遇到麻烦。

模型[1:38:30]

class Empty(nn.Module): 
    def forward(self,x): 
        return x
models = ConvnetBuilder(resnet34, 0, 0, 0, custom_head=Empty())
learn = ConvLearner(md, models)
learn.summary()
class StdUpsample(nn.Module):
    def __init__(self, nin, nout):
        super().__init__()
        self.conv = nn.ConvTranspose2d(nin, nout, 2, stride=2)
        self.bn = nn.BatchNorm2d(nout)
    def forward(self, x): 
        return self.bn(F.relu(self.conv(x)))
flatten_channel = Lambda(lambda x: x[:,0])
simple_up = nn.Sequential(
    nn.ReLU(),
    StdUpsample(512,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    nn.ConvTranspose2d(256, 1, 2, stride=2),
    flatten_channel
)

考虑到我们想要一个知道汽车长什么样的东西,我们可能想要从一个预训练的 ImageNet 网络开始。所以我们将从 ResNet34 开始。使用ConvnetBuilder,我们可以获取我们的 ResNet34 并添加一个自定义头部。自定义头部将是一些上采样的东西,现在我们将做一些非常愚蠢的事情,就是我们只是做一个 ConvTranspose2d,批量规范化,ReLU。

这就是我说的 - 任何人都可以在不看任何笔记本的情况下构建这个,或者至少你有来自以前课程的信息。这里没有任何新东西。所以最后,我们有一个单一的过滤器。现在这将给我们一个批量大小为 1 乘以 128 乘以 128。但我们想要的是批量大小为 128 乘以 128。所以我们必须去掉那个单元轴,所以我在这里有一个 lambda 层。Lambda 层非常有帮助,因为没有这个 lambda 层,它只是通过索引 0 来删除那个单元轴,没有 lambda 层,我将不得不创建一个自定义类,具有自定义的前向方法等等。但通过创建一个 lambda 层来执行一个自定义操作,我现在可以将其放入 Sequential 中,这样就更容易了。

PyTorch 的人们对这种方法有点傲慢。Lambda 层实际上是 fastai 库的一部分,而不是 PyTorch 库的一部分。而且 PyTorch 讨论板上的人们说“是的,我们可以给人们这个”,“是的,这只是一行代码”,但他们从不鼓励他们过于频繁地使用 Sequential。所以你看。

这是我们的自定义头部[1:40:36]。所以我们将有一个 ResNet 34 进行下采样,然后一个非常简单的自定义头部,非常快速地上采样,希望这样做一些事情。我们将使用阈值为 0.5 的准确度并打印出指标。

models = ConvnetBuilder(resnet34, 0, 0, 0, custom_head=simple_up)
learn = ConvLearner(md, models)
learn.opt_fn=optim.Adam
learn.crit=nn.BCEWithLogitsLoss()
learn.metrics=[accuracy_thresh(0.5)
learn.lr_find()
learn.sched.plot()
'''
94%|█████████▍| 30/32 [00:05<00:00,  5.48it/s, loss=10.6]
'''

lr=4e-2
learn.fit(lr,1,cycle_len=5,use_clr=(20,5))
'''
epoch      trn_loss   val_loss   <lambda>                  
    0      0.124078   0.133566   0.945951  
    1      0.111241   0.112318   0.954912                  
    2      0.099743   0.09817    0.957507                   
    3      0.090651   0.092375   0.958117                   
    4      0.084031   0.086026   0.963243
[0.086025625, 0.96324310824275017]
'''

经过几个时代,我们得到了 96%的准确率。这好吗[1:40:56]?96%的准确率好吗?希望对这个问题的答案是取决于。这是为了什么?答案是 Carvana 想要这个,因为他们想要能够拍摄他们的汽车图像并将它们剪切并粘贴到异国情调的蒙特卡洛背景或其他地方(这是蒙特卡洛的地方,而不是模拟)。为了做到这一点,你需要一个非常好的蒙版。你不想留下后视镜,缺少一个车轮,或者包括一点背景之类的东西。那看起来很愚蠢。所以你需要一些非常好的东西。所以只有 96%的像素正确并不听起来很好。但我们真的不知道直到我们看到它。所以让我们看看。

learn.save('tmp')
learn.load('tmp')
py,ay = learn.predict_with_targs()
ay.shape
'''
(1008, 128, 128)
'''

所以这是我们想要剪切的正确版本[1:41:54]

show_img(ay[0]);

这是 96%准确的版本。所以当你看到它时,你会意识到“哦,是的,准确地获取 96%的像素实际上很容易,因为所有外部部分都不是汽车,所有内部部分都是汽车,而真正有趣的部分是边缘。所以我们需要做得更好。

show_img(py[0]>0);

让我们解冻,因为到目前为止我们只训练了自定义头部。让我们做更多。

learn.unfreeze()
learn.bn_freeze(True)
lrs = np.array([lr/100,lr/10,lr])/4
learn.fit(lrs,1,cycle_len=20,use_clr=(20,10))
'''
epoch      trn_loss   val_loss   <lambda>                   
    0      0.06577    0.053292   0.972977  
    1      0.049475   0.043025   0.982559                   
    2      0.039146   0.035927   0.98337                    
    3      0.03405    0.031903   0.986982                   
    4      0.029788   0.029065   0.987944                   
    5      0.027374   0.027752   0.988029                   
    6      0.026041   0.026718   0.988226                   
    7      0.024302   0.025927   0.989512                   
    8      0.022921   0.026102   0.988276                   
    9      0.021944   0.024714   0.989537                   
    10     0.021135   0.0241     0.990628                   
    11     0.020494   0.023367   0.990652                   
    12     0.01988    0.022961   0.990989                   
    13     0.019241   0.022498   0.991014                   
    14     0.018697   0.022492   0.990571                   
    15     0.01812    0.021771   0.99105                    
    16     0.017597   0.02183    0.991365                   
    17     0.017192   0.021434   0.991364                   
    18     0.016768   0.021383   0.991643                   
    19     0.016418   0.021114   0.99173
[0.021113895, 0.99172959849238396]
'''

再经过一段时间,我们得到了 99.1%。这好吗?我不知道。让我们看看。

learn.save('0')
x,y = next(iter(md.val_dl))
py = to_np(learn.model(V(x)))

实际上不是。它完全错过了左侧的后视镜,右侧也错过了很多。底部的边缘明显错了。当我们尝试剪裁时,这些事情完全会影响到,所以还不够好。

ax = show_img(denorm(x)[0])
show_img(py[0]>0, ax=ax, alpha=0.5);

ax = show_img(denorm(x)[0])
show_img(y[0], ax=ax, alpha=0.5);

512x512

让我们尝试放大。很好的一点是,当我们将其放大到 512x512 时(确保减少批量大小,因为你会耗尽内存),有更多的信息供其使用,因此我们的准确性提高到 99.4%,事情一直在变得更好。

TRAIN_DN = 'train'
MASKS_DN = 'train_masks_png'
sz = 512
bs = 16
x_names = np.array([Path(TRAIN_DN)/o for o in masks_csv['img']])
y_names = np.array([
    Path(MASKS_DN)/f'**{o[:-4]}**_mask.png' 
    for o in masks_csv['img']
])
((val_x,trn_x),(val_y,trn_y)) = split_by_idx(val_idxs, x_names, y_names)
len(val_x),len(trn_x)
'''
(1008, 4080)
'''
tfms = tfms_from_model(
    resnet34, sz, 
    crop_type=CropType.NO,
    tfm_y=TfmType.CLASS, 
    aug_tfms=aug_tfms
)
datasets = ImageData.get_ds(
    MatchedFilesDataset, 
    (trn_x,trn_y),
    (val_x,val_y), 
    tfms, 
    path=PATH
)
md = ImageData(
    PATH, datasets, bs, 
    num_workers=8, 
    classes=None
)
denorm = md.trn_ds.denorm
x,y = next(iter(md.aug_dl))
x = denorm(x)

这是真实的。

fig, axes = plt.subplots(4, 4, figsize=(10, 10))
for i,ax in enumerate(axes.flat):
    ax=show_img(x[i], ax=ax)
    show_img(y[i], ax=ax, alpha=0.5)
plt.tight_layout(pad=0.1)


simple_up = nn.Sequential(
    nn.ReLU(),
    StdUpsample(512,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    nn.ConvTranspose2d(256, 1, 2, stride=2),
    flatten_channel
)
models = ConvnetBuilder(resnet34, 0, 0, 0, custom_head=simple_up)
learn = ConvLearner(md, models)
learn.opt_fn=optim.Adam
learn.crit=nn.BCEWithLogitsLoss()
learn.metrics=[accuracy_thresh(0.5)]
learn.load('0')
learn.lr_find()
learn.sched.plot()
'''
85%|████████▌ | 218/255 [02:12<00:22,  1.64it/s, loss=8.91]
'''

lr=4e-2
learn.fit(lr,1,cycle_len=5,use_clr=(20,5))
'''
epoch      trn_loss   val_loss   <lambda>                     
    0      0.02178    0.020653   0.991708  
    1      0.017927   0.020653   0.990241                     
    2      0.015958   0.016115   0.993394                     
    3      0.015172   0.015143   0.993696                     
    4      0.014315   0.014679   0.99388
[0.014679321, 0.99388032489352751]
'''
learn.save('tmp')
learn.load('tmp')
learn.unfreeze()
learn.bn_freeze(True)
lrs = np.array([lr/100,lr/10,lr])/4
learn.fit(lrs,1,cycle_len=8,use_clr=(20,8))
'''
epoch      trn_loss   val_loss   mask_acc                     
    0      0.038687   0.018685   0.992782  
    1      0.024906   0.014355   0.994933                     
    2      0.025055   0.014737   0.995526                     
    3      0.024155   0.014083   0.995708                     
    4      0.013446   0.010564   0.996166                     
    5      0.01607    0.010555   0.996096                     
    6      0.019197   0.010883   0.99621                      
    7      0.016157   0.00998    0.996393
[0.0099797687, 0.99639255659920833]
'''
learn.save('512')
x,y = next(iter(md.val_dl))
py = to_np(learn.model(V(x)))
ax = show_img(denorm(x)[0])
show_img(py[0]>0, ax=ax, alpha=0.5);

ax = show_img(denorm(x)[0])
show_img(y[0], ax=ax, alpha=0.5);

事情一直在变得更好,但我们仍然有一些小黑色块状物。所以让我们调整到 1024x1024。

1024x1024

所以让我们调整到 1024x1024,批量大小减少到 4。现在这是相当高分辨率的了,再训练一段时间,99.6%,99.8%!

sz = 1024
bs = 4
tfms = tfms_from_model(
    resnet34, sz, 
    crop_type=CropType.NO,
    tfm_y=TfmType.CLASS, 
    aug_tfms=aug_tfms
)
datasets = ImageData.get_ds(
    MatchedFilesDataset, 
    (trn_x,trn_y), 
    (val_x,val_y), 
    tfms, 
    path=PATH
)
md = ImageData(
    PATH, datasets, bs, 
    num_workers=8, 
    classes=None
)
denorm = md.trn_ds.denorm
x,y = next(iter(md.aug_dl))
x = denorm(x)
y = to_np(y)
fig, axes = plt.subplots(2, 2, figsize=(8, 8))
for i,ax in enumerate(axes.flat):
    show_img(x[i], ax=ax)
    show_img(y[i], ax=ax, alpha=0.5)
plt.tight_layout(pad=0.1)

simple_up = nn.Sequential(
    nn.ReLU(),
    StdUpsample(512,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    StdUpsample(256,256),
    nn.ConvTranspose2d(256, 1, 2, stride=2),
    flatten_channel,
)
models = ConvnetBuilder(resnet34, 0, 0, 0, custom_head=simple_up)
learn = ConvLearner(md, models)
learn.opt_fn=optim.Adam
learn.crit=nn.BCEWithLogitsLoss()
learn.metrics=[accuracy_thresh(0.5)]
learn.load('512')learn.lr_find()
learn.sched.plot()
'''
85%|████████▌ | 218/255 [02:12<00:22,  1.64it/s, loss=8.91]
'''

lr=4e-2
learn.fit(lr,1,cycle_len=2,use_clr=(20,4))
'''
epoch      trn_loss   val_loss   <lambda>                       
    0      0.01066    0.011119   0.996227  
    1      0.009357   0.009696   0.996553
[0.0096957013, 0.99655332546385511]
'''
learn.save('tmp')
learn.load('tmp')
learn.unfreeze()
learn.bn_freeze(True)
lrs = np.array([lr/100,lr/10,lr])/8
learn.fit(lrs,1,cycle_len=40,use_clr=(20,10))
'''
epoch      trn_loss   val_loss   mask_acc                       
    0      0.015565   0.007449   0.997661  
    1      0.01979    0.008376   0.997542                       
    2      0.014874   0.007826   0.997736                       
    3      0.016104   0.007854   0.997347                       
    4      0.023386   0.009745   0.997218                       
    5      0.018972   0.008453   0.997588                       
    6      0.013184   0.007612   0.997588                       
    7      0.010686   0.006775   0.997688                       
    8      0.0293     0.015299   0.995782                       
    9      0.018713   0.00763    0.997638                       
    10     0.015432   0.006575   0.9978                         
    11     0.110205   0.060062   0.979043                      
    12     0.014374   0.007753   0.997451                       
    13     0.022286   0.010282   0.997587                       
    14     0.015645   0.00739    0.997776                       
    15     0.013821   0.00692    0.997869                       
    16     0.022389   0.008632   0.997696                       
    17     0.014607   0.00677    0.997837                       
    18     0.018748   0.008194   0.997657                       
    19     0.016447   0.007237   0.997899                       
    20     0.023596   0.008211   0.997918                       
    21     0.015721   0.00674    0.997848                       
    22     0.01572    0.006415   0.998006                       
    23     0.019519   0.007591   0.997876                       
    24     0.011159   0.005998   0.998053                       
    25     0.010291   0.005806   0.998012                       
    26     0.010893   0.005755   0.998046                       
    27     0.014534   0.006313   0.997901                       
    28     0.020971   0.006855   0.998018                       
    29     0.014074   0.006107   0.998053                       
    30     0.01782    0.006561   0.998114                       
    31     0.01742    0.006414   0.997942                       
    32     0.016829   0.006514   0.9981                         
    33     0.013148   0.005819   0.998033                       
    34     0.023495   0.006261   0.997856                       
    35     0.010931   0.005516   0.99812                        
    36     0.015798   0.006176   0.998126                       
    37     0.021636   0.005931   0.998067                       
    38     0.012133   0.005496   0.998158                       
    39     0.012562   0.005678   0.998172
[0.0056782686, 0.99817223208291195]
'''
learn.save('1024')
x,y = next(iter(md.val_dl))
py = to_np(learn.model(V(x)))
ax = show_img(denorm(x)[0])
show_img(py[0][0]>0, ax=ax, alpha=0.5);

ax = show_img(denorm(x)[0])
show_img(y[0,...,-1], ax=ax, alpha=0.5);

show_img(py[0][0]>0);

show_img(y[0,...,-1]);

现在如果我们看一下掩模,它们实际上看起来不错。这看起来相当不错。那么我们能做得更好吗?答案是肯定的。

U-Net

笔记本 / 论文

U-Net 网络非常了不起。使用之前的方法,我们的预训练 ImageNet 网络被压缩到 7x7,然后再扩展到 224x224(1024 被压缩到比 7x7 大得多)。然后再次扩展出来,这意味着它必须以某种方式在小版本中存储关于更大版本的所有信息。实际上,关于更大版本的大部分信息实际上已经在原始图片中。因此,这种压缩和解压似乎不是一个很好的方法。

因此,U-Net 的想法来自于这篇出色的论文,在这篇论文中,它实际上是在生物医学图像分割这个非常特定的领域中发明的。但事实上,基本上每一个与分割有关的 Kaggle 获胜者最终都使用了 U-Net。这是每个 Kaggle 参与者都知道的最佳实践之一,但在更多的学术圈中,这已经存在至少几年了,很多人仍然没有意识到这是迄今为止最好的方法。

这里是基本的想法。在左侧是向下路径,我们从 572x572 开始,然后将网格大小减半 4 次,然后在右侧是向上路径,我们将网格大小扩大 4 次。但我们还做的一件事是,在每个减半网格大小的点,我们实际上将这些激活复制到向上路径,并将它们连接在一起。

在右下角可以看到,这些红色箭头是最大池化操作,这些绿色箭头是向上采样,然后这些灰色箭头是复制。所以我们复制并连接。换句话说,经过几次卷积后的输入图像被复制到输出中,连接在一起,现在我们可以使用所有经过所有向下和向上的信息,还有输入像素的略微修改版本。以及输入像素的略微修改版本,因为它们是通过这里上来的。所以我们拥有所有向下和向上的丰富性,但也有一个略微不那么粗糙的版本,然后是一个略微不那么粗糙的版本,然后是一个真正简单的版本,它们都可以组合在一起。这就是 U-Net。这是一个很酷的想法。

我们在 carvana-unet 笔记本中。所有这些与之前的代码相同。

%matplotlib inline
%reload_ext autoreload
%autoreload 2
from fastai.conv_learner import *
from fastai.dataset import *
from fastai.models.resnet import vgg_resnet50
import jsontorch.backends.cudnn.benchmark=True

fast.ai 深度学习笔记(七)(4)https://developer.aliyun.com/article/1482692

相关文章
|
14天前
|
机器学习/深度学习 自然语言处理 PyTorch
fast.ai 深度学习笔记(三)(4)
fast.ai 深度学习笔记(三)(4)
22 0
|
14天前
|
机器学习/深度学习 算法 PyTorch
fast.ai 深度学习笔记(三)(3)
fast.ai 深度学习笔记(三)(3)
30 0
|
14天前
|
机器学习/深度学习 编解码 自然语言处理
fast.ai 深度学习笔记(三)(2)
fast.ai 深度学习笔记(三)(2)
32 0
|
14天前
|
机器学习/深度学习 PyTorch 算法框架/工具
fast.ai 深度学习笔记(三)(1)
fast.ai 深度学习笔记(三)(1)
35 0
|
索引 机器学习/深度学习 计算机视觉
fast.ai 深度学习笔记(四)(3)
fast.ai 深度学习笔记(四)
40 0
|
15天前
|
机器学习/深度学习 固态存储 Python
fast.ai 深度学习笔记(四)(2)
fast.ai 深度学习笔记(四)
50 3
fast.ai 深度学习笔记(四)(2)
|
15天前
|
API 机器学习/深度学习 Python
fast.ai 深度学习笔记(四)(1)
fast.ai 深度学习笔记(四)
62 3
fast.ai 深度学习笔记(四)(1)
|
15天前
|
机器学习/深度学习 算法框架/工具 PyTorch
fast.ai 深度学习笔记(五)(4)
fast.ai 深度学习笔记(五)
71 3
fast.ai 深度学习笔记(五)(4)
|
机器学习/深度学习 自然语言处理 Web App开发
fast.ai 深度学习笔记(五)(3)
fast.ai 深度学习笔记(五)
119 2
fast.ai 深度学习笔记(五)(3)
|
15天前
|
机器学习/深度学习 自然语言处理 Python
fast.ai 深度学习笔记(五)(2)
fast.ai 深度学习笔记(五)
105 4
fast.ai 深度学习笔记(五)(2)