用Gluon炼丹体验

"纸上得来终觉浅,绝知此事要躬行。"这是老祖宗传下来的一句话,与其对应的一句英文便是"Get your hands dirty",他们都表达同样一个意思,任何事情,只有实践才能够真正出真知,如果只懂嘴上功夫,那么永远无法真正成为大师。老祖宗的智慧是非常大的,这时刻提醒着我们需要亲自动手,要自己踩过一些坑,才能够明白一些原理。为什么要在开篇要说这个呢?因为这正是我用Gluon炼丹中得到的一点启发,我想这也是沐神开设动手学深度学习课程的初衷之一,理论不仅要看,也要自己动手写代码,调参,这样才能真正理解deep learning。

介绍

学习了一段时间沐神的课程,动手学深度学习,也动手调了一下cifar10数据集,这里有一个简单的结果展示,确实感受到了调参的魔力。这里要强烈安利一下gluon的论坛,里面的小伙伴都非常棒,而且aws还为大家参加比赛提供计算支持,这里有cifar10的奖金情况,可以看到基本上参与的人都拿到了至少50刀的aws credit,如果稍微调一下,就能够拿到至少100刀的奖励,也就是说只要不管你有没有GPU,你都可以轻松地玩转kaggle,体会深度学习的魅力,是不是特别棒呢?如果错过了cifar10的比赛,没有关系,现在又开始了新的比赛,ImageNet的子集比赛120狗分类,看到了大家的奖金是不是特别心动呢?赶快来参加吧,既可以玩深度学习,还能顺便赚点aws credit花,何乐而不为呢。

广告说完了,接下来正式进入到炼丹环节。这次要分享的炼丹过程是ai challenger比赛场景分类项目,总的图片是80000张,一共是80个场景分类,70%用于训练集,10%作为验证集,20%作为测试集A和B。

我们可视化其中一张图片如下,大小是(531, 800)。

image

数据预处理

均值和方差的计算

在定义网络之前,首先我们需要进行数据预处理,首先想到的就是需要做标准化,也就是减去均值除以标准差,所以我们首先要在训练集上进行均值和方差的计算,方法非常简单,遍历每一张图片,然后计算每个channel上的均值和方差即可。

r = 0 # r mean
g = 0 # g mean
b = 0 # b mean

r_2 = 0 # r^2 
g_2 = 0 # g^2
b_2 = 0 # b^2

total = 0
for img_name in img_list:
    img = mx.image.imread(path + img_name) # ndarray, width x height x 3
    img = img.astype('float32') / 255.
    total += img.shape[0] * img.shape[1]
    
    r += img[:, :, 0].sum().asscalar()
    g += img[:, :, 1].sum().asscalar()
    b += img[:, :, 2].sum().asscalar()
    
    r_2 += (img[:, :, 0]**2).sum().asscalar()
    g_2 += (img[:, :, 1]**2).sum().asscalar()
    b_2 += (img[:, :, 2]**2).sum().asscalar()

r_mean = r / total
g_mean = g / total
b_mean = b / total

r_var = r_2 / total - r_mean ** 2
g_var = g_2 / total - g_mean ** 2
b_var = b_2 / total - b_mean ** 2

数据增强

一个非常好的处理过拟合的方法就是数据增强,这里对训练集使用数据增强分为以下几步。首先我们随机将图片较短的边按比例resize到[256, 480]之间的一个整数,然后在resize之后的图片上做随机crop到(224, 224)的大小,然后在按0.5的概率做随机翻转。对于验证集,我们就简单地将数据resize到(224, 224)。

def transform_train(img):
    '''
    img is the mx.image.imread object
    '''
    img = img.astype('float32') / 255
    random_shape = int(np.random.uniform() * 224 + 256)  
    # random samplely in [256, 480]
    aug_list = mx.image.CreateAugmenter(
        data_shape=(3, 224, 224), resize=random_shape,
        rand_mirror=True, rand_crop=True, 
        mean=np.array([0.4960, 0.4781, 0.4477]),                               
        std=np.array([0.2915, 0.2864, 0.2981]))
    
    for aug in aug_list:
        img = aug(img)
    img = nd.transpose(img, (2, 0, 1))
    return img
    
def transform_valid(img):
    img = img.astype('float32') / 255.
    aug_list = mx.image.CreateAugmenter(
        data_shape=(3, 224, 224), 
        mean=np.array([0.4960, 0.4781, 0.4477]),
        std=np.array([0.2915, 0.2864, 0.2981]))
    
    for aug in aug_list:
        img = aug(img)
    img = nd.transpose(img, (2, 0, 1))
    return img

数据读入

接下来需要写数据读入,这里gluon和pytorch几乎是一样的,只需要定义一个dataset就好了,比赛的数据集label是放在一个json文件中的,打开之后大概是这样

image

image_id是图片名字,url就是图片的网站,不用管,label_id就是图片的label,知道了这些我们就能够写一个自定义的dataset来读入数据集。

class SceneDataSet(gl.data.Dataset):
    def __init__(self, json_file, img_path, transform):
        self._img_path = img_path
        self._transform = transform
        with open(json_file, 'r') as f:
            annotation_list = json.load(f)
        self._img_list = [[i['image_id'], i['label_id']]
                          for i in annotation_list]

    def __getitem__(self, idx):
        img_name = self._img_list[idx][0]
        label = np.float32(self._img_list[idx][1])
        img = mx.image.imread(os.path.join(self._img_path, img_name))
        img = self._transform(img)
        return img, label

    def __len__(self):
        return len(self._img_list)

然后我们可以使用gluon中的DataLoader来构成一个迭代器。

train_data = gl.data.DataLoader(train_set, batch_size=64, shuffle=True, last_batch='keep')

模型训练

定义好了数据预处理和数据读入之后,我们可以定义模型,然后定义好loss函数,epoch数目,学习率,权重衰减等参数就可以开始训练了。

超参数的定义如下。

ctx = mx.gpu(0)
num_epochs = 10
lr = 0.1
wd = 1e-4
lr_decay = 0.1

loss和优化函数定义如下。

criterion = gl.loss.SoftmaxCrossEntropyLoss()
trainer = gl.Trainer(
        net.collect_params(), 'sgd', {'learning_rate': lr, 'momentum': 0.9, 'wd': wd})

这里的模型使用了gluon model zoo里面的resnet50。

net = gl.model_zoo.vision.resnet50_v2(classes=80)
net.initialize(init=mx.init.Xavier(), ctx=ctx)
net.hybridize()

初始化参数使用Xavier方法,net.hybridize()是gluon特有的,可以将动态图转换成静态图加快训练速度。

接着我们就可以开始训练了,训练的主体如下,跟pytorch很类似。

for epoch in range(num_epochs):
    for data, label in train_data:
        bs = data.shape[0]
        data = data.as_in_context(ctx)
        label = label.as_in_context(ctx)
        with mx.autograd.record():
            output = net(data)
            loss = criterion(output, label)
        loss.backward()
        trainer.step(bs)

在每个epoch中进行数据迭代,然后使用as_in_context将data放到gpu上,然后在mx.autograd.record()中建立计算图,loss.backward()反向传播,计算梯度,最后使用trainer.step(bs)更新参数。

我的显卡是titan x,训练一个epoch大概需要13分钟,我就随便跑了100次作为baseline,没有调过learning rate decay,最后的训练记录结果如下。

image

可以看到train loss还是很大的,并没有经过充分训练,训练完成之后使用net.save_params('./net.params')保存模型。

提交结果

我们需要对测试集进行预测,然后提交top3的结果。这里我们采取的策略是取图片四个角和正中心的patch以及他们的镜面对称,一共构成10个patch,每个patch大小都是224,对这10个patch进行预测,然后取10个结果的softmax求和作为最后的结果,完整的代码在文章最后的github地址中。

最后我们将结果提交到官网上就能看到我们的排名了。

image

因为我们就是简单地跑一下baseline,所以得到的结果并不是特别好,如果想得到更好的结果,可以训练更长的时间,同时使用多个模型做ensemble。

以上就是初步对gluon炼丹体验的小结,得到的结果并不算太好,抛砖引玉,希望大家使用gluon能够在深度学习的世界里面玩得开心。


完整代码

欢迎查看我的知乎专栏,深度炼丹

欢迎访问我的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,561评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,218评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,162评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,470评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,550评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,806评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,951评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,712评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,166评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,510评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,643评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,306评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,930评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,745评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,983评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,351评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,509评论 2 348

推荐阅读更多精彩内容