这是在kaggle上的一个练习比赛,使用的是ImageNet数据集的子集。

注意,mxnet版本要高于0.12.1b2017112

下载数据集。

1. 数据

1.1 整理数据

将解压后的数据整理成Gluon能够读取的形式,这里我直接使用了zh.gluon.ai教程上的代码

导入各种库

import math
import os
import shutil
from collections import Counter

设置一些变量

data_dir = './data'
label_file = 'labels.csv'
train_dir = 'train'
test_dir = 'test'
input_dir = 'train_valid_test'
batch_size = 128
valid_ratio = 0.1

定义整理数据函数

def reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir,
valid_ratio):
# 读取训练数据标签。
with open(os.path.join(data_dir, label_file), 'r') as f:
# 跳过文件头行(栏名称)。
lines = f.readlines()[1:]
tokens = [l.rstrip().split(',') for l in lines]
idx_label = dict(((idx, label) for idx, label in tokens))
labels = set(idx_label.values()) num_train = len(os.listdir(os.path.join(data_dir, train_dir)))
# 训练集中数量最少一类的狗的数量。
min_num_train_per_label = (
Counter(idx_label.values()).most_common()[:-2:-1][0][1])
# 验证集中每类狗的数量。
num_valid_per_label = math.floor(min_num_train_per_label * valid_ratio)
label_count = dict() def mkdir_if_not_exist(path):
if not os.path.exists(os.path.join(*path)):
os.makedirs(os.path.join(*path)) # 整理训练和验证集。
for train_file in os.listdir(os.path.join(data_dir, train_dir)):
idx = train_file.split('.')[0]
label = idx_label[idx]
mkdir_if_not_exist([data_dir, input_dir, 'train_valid', label])
shutil.copy(os.path.join(data_dir, train_dir, train_file),
os.path.join(data_dir, input_dir, 'train_valid', label))
if label not in label_count or label_count[label] < num_valid_per_label:
mkdir_if_not_exist([data_dir, input_dir, 'valid', label])
shutil.copy(os.path.join(data_dir, train_dir, train_file),
os.path.join(data_dir, input_dir, 'valid', label))
label_count[label] = label_count.get(label, 0) + 1
else:
mkdir_if_not_exist([data_dir, input_dir, 'train', label])
shutil.copy(os.path.join(data_dir, train_dir, train_file),
os.path.join(data_dir, input_dir, 'train', label)) # 整理测试集。
mkdir_if_not_exist([data_dir, input_dir, 'test', 'unknown'])
for test_file in os.listdir(os.path.join(data_dir, test_dir)):
shutil.copy(os.path.join(data_dir, test_dir, test_file),
os.path.join(data_dir, input_dir, 'test', 'unknown'))

调用这个函数整理数据集

reorg_dog_data(data_dir, label_file, train_dir, test_dir, input_dir,
valid_ratio)

1.2 载入数据

数据整理好之后,需要载入到gluon中,首先需要定义转换函数,因为需要模型融合,所以需要两个输入,分别经过两个不同的模型

导入各种包

from mxnet import gluon
from mxnet import image
import numpy as np
from mxnet import nd

在训练数据里,开启了一些数据增强,并且做了一些预处理。需要说明的是,这里的图像均值和方差是ImageNet数据集的。这是因为预训练模型的数据集是ImageNet,那么我们就必须按照模型训练的时候处理的方式来处理我们的数据,这样才能保证最好的效果。图像尺寸同样,这里使用两个不同的尺寸就是为了两个不同的网络准备的。

def transform_train(data, label):
im1 = image.imresize(data.astype('float32') / 255, 224, 224)
im2 = image.imresize(data.astype('float32') / 255, 299, 299)
auglist1 = image.CreateAugmenter(data_shape=(3, 224, 224), resize=0,
rand_crop=False, rand_resize=False, rand_mirror=True,
mean=np.array([0.485, 0.456, 0.406]), std=np.array([0.229, 0.224, 0.225]),
brightness=0, contrast=0,
saturation=0, hue=0,
pca_noise=0, rand_gray=0, inter_method=2)
auglist2 = image.CreateAugmenter(data_shape=(3, 299, 299), resize=0,
rand_crop=False, rand_resize=False, rand_mirror=True,
mean=np.array([0.485, 0.456, 0.406]), std=np.array([0.229, 0.224, 0.225]),
brightness=0, contrast=0,
saturation=0, hue=0,
pca_noise=0, rand_gray=0, inter_method=2)
for aug in auglist1:
im1 = aug(im1)
for aug in auglist2:
im2 = aug(im2)
# 将数据格式从"高*宽*通道"改为"通道*高*宽"。
im1 = nd.transpose(im1, (2,0,1))
im2 = nd.transpose(im2, (2,0,1))
return (im1,im2, nd.array([label]).asscalar().astype('float32')) def transform_test(data, label):
im1 = image.imresize(data.astype('float32') / 255, 224, 224)
im2 = image.imresize(data.astype('float32') / 255, 299, 299)
auglist1 = image.CreateAugmenter(data_shape=(3, 224, 224),
mean=np.array([0.485, 0.456, 0.406]),
std=np.array([0.229, 0.224, 0.225]))
auglist2 = image.CreateAugmenter(data_shape=(3, 299, 299),
mean=np.array([0.485, 0.456, 0.406]),
std=np.array([0.229, 0.224, 0.225]))
for aug in auglist1:
im1 = aug(im1)
for aug in auglist2:
im2 = aug(im2)
# 将数据格式从"高*宽*通道"改为"通道*高*宽"。
im1 = nd.transpose(im1, (2,0,1))
im2 = nd.transpose(im2, (2,0,1))
return (im1,im2, nd.array([label]).asscalar().astype('float32'))

转换函数定义好之后,就可以载入到gluon里了

batch_size = 32

train_ds = gluon.data.vision.ImageFolderDataset(input_str + train_dir, flag=1,
transform=transform_train)
valid_ds = gluon.data.vision.ImageFolderDataset(input_str + valid_dir, flag=1,
transform=transform_test)
train_valid_ds = gluon.data.vision.ImageFolderDataset(input_str + train_valid_dir,
flag=1, transform=transform_train)
test_ds = gluon.data.vision.ImageFolderDataset(input_str + test_dir, flag=1,
transform=transform_test) loader = gluon.data.DataLoader
train_data = loader(train_ds, batch_size, shuffle=True, last_batch='keep')
valid_data = loader(valid_ds, batch_size, shuffle=True, last_batch='keep')
train_valid_data = loader(train_valid_ds, batch_size, shuffle=True,
last_batch='keep')

2. 设计网络

这里为了得到一个更好的名次,并且减少我们的工作量,使用了迁移学习加模型融合。迁移学习就是使用预训练模型里的特征层,然后再自己填补后面的输出层。因为与训练模型都是老司机了,见多识广,所以这些数据也不在话下。

模型融合这里使用两个模型,分别是resnet152_v1inception_v3

导入各种包

from mxnet import init
from mxnet.gluon import nn

2.1 双模型合并

为了让两个网络合并,就需要自定义一个合并两个网络的层。

在这里,我为每个网络添加了一个GlobalAvgPool2D层,这是为了让两个网络输出的尺寸可以合并。

class  ConcatNet(nn.HybridBlock):
def __init__(self,net1,net2,**kwargs):
super(ConcatNet,self).__init__(**kwargs)
self.net1 = nn.HybridSequential()
self.net1.add(net1)
self.net1.add(nn.GlobalAvgPool2D())
self.net2 = nn.HybridSequential()
self.net2.add(net2)
self.net2.add(nn.GlobalAvgPool2D())
def hybrid_forward(self,F,x1,x2):
return F.concat(*[self.net1(x1),self.net2(x2)])

这样就可以构造出一个特征提取层

def get_features2(ctx):
resnet = gluon.model_zoo.vision.inception_v3(pretrained=True,ctx=ctx)
return resnet.features def get_features1(ctx):
resnet = gluon.model_zoo.vision.resnet152_v1(pretrained=True,ctx=ctx)
return resnet.features def get_features(ctx):
features1 = get_features1(ctx)
features2 = get_features2(ctx)
net = ConcatNet(features1,features2)
return net

2.3 输出层

输出层比较简单,两层全链接,中间加了一层Dropout

def get_output(ctx,ParamsName=None):
net = nn.HybridSequential()
with net.name_scope():
net.add(nn.Dense(256, activation="relu"))
net.add(nn.Dropout(.7))
net.add(nn.Dense(120))
if ParamsName is not None:
net.collect_params().load(ParamsName,ctx)
else:
net.initialize(init = init.Xavier(),ctx=ctx)
return net

2.4 连接成一个网络

有了可以合并两个网络的层,还有一个输出层,那么我们需要将这两个网络连接起来。

class  OneNet(nn.HybridBlock):
def __init__(self,features,output,**kwargs):
super(OneNet,self).__init__(**kwargs)
self.features = features
self.output = output
def hybrid_forward(self,F,x1,x2):
return self.output(self.features(x1,x2))

这样就可以构造出一个完整的网络

def get_net(ParamsName,ctx):
output = get_output(ctx,ParamsName)
features = get_features(ctx)
net = OneNet(features,output)
return net

3. 训练

有着前面的准备,就可以开始干活了。首先第一步是提取特征,因为是迁移学习,会锁定特征层。那干脆让所有训练数据都过一遍特征网络,这样既节约时间,有节省显存。何乐而不为。

导入各种包

from tqdm import tqdm
import datetime
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
from mxnet import autograd
import mxnet as mx
import pickle

3.1 提取特征

提取特征我们使用上面定义好的特征提取网络

net = get_features(mx.gpu())
net.hybridize() def SaveNd(data,net,name):
x =[]
y =[]
print('提取特征 %s' % name)
for fear1,fear2,label in tqdm(data):
fear1 = fear1.as_in_context(mx.gpu())
fear2 = fear2.as_in_context(mx.gpu())
out = net(fear1,fear2).as_in_context(mx.cpu())
x.append(out)
y.append(label)
x = nd.concat(*x,dim=0)
y = nd.concat(*y,dim=0)
print('保存特征 %s' % name)
nd.save(name,[x,y]) SaveNd(train_data,net,'train_r152i3.nd')
SaveNd(valid_data,net,'valid_r152i3.nd')
SaveNd(train_valid_data,net,'input_r152i3.nd')

为了最后输出提交文件做准备,保存一下需要的东西

ids = ids = sorted(os.listdir(os.path.join(data_dir, input_dir, 'test/unknown')))
synsets = train_valid_ds.synsets
f = open('ids_synsets','wb')
pickle.dump([ids,synsets],f)
f.close()

3.2 载入预训练后的数据

3.3 训练模型

训练之前先把各种参数设置一下

num_epochs = 100
batch_size = 128
learning_rate = 1e-4
weight_decay = 1e-4
pngname='train.png'
modelparams='r152i3.params'

然后载入特征提取后的数据

train_nd = nd.load('train_r152i3.nd')
valid_nd = nd.load('valid_r152i3.nd')
input_nd = nd.load('input_r152i3.nd')
f = open('ids_synsets','rb')
ids_synsets = pickle.load(f)
f.close() train_data = gluon.data.DataLoader(gluon.data.ArrayDataset(train_nd[0],train_nd[1]), batch_size=batch_size,shuffle=True)
valid_data = gluon.data.DataLoader(gluon.data.ArrayDataset(valid_nd[0],valid_nd[1]), batch_size=batch_size,shuffle=True)
input_data = gluon.data.DataLoader(gluon.data.ArrayDataset(input_nd[0],input_nd[1]), batch_size=batch_size,shuffle=True)

设置训练函数和loss函数

def get_loss(data, net, ctx):
loss = 0.0
for feas, label in data:
label = label.as_in_context(ctx)
output = net(feas.as_in_context(ctx))
cross_entropy = softmax_cross_entropy(output, label)
loss += nd.mean(cross_entropy).asscalar()
return loss / len(data) def train(net, train_data, valid_data, num_epochs, lr, wd, ctx):
trainer = gluon.Trainer(
net.collect_params(), 'adam', {'learning_rate': lr, 'wd': wd})
train_loss = []
if valid_data is not None:
test_loss = [] prev_time = datetime.datetime.now()
for epoch in range(num_epochs):
_loss = 0.
for data, label in train_data:
label = label.as_in_context(ctx)
with autograd.record():
output = net(data.as_in_context(ctx))
loss = softmax_cross_entropy(output, label)
loss.backward()
trainer.step(batch_size)
_loss += nd.mean(loss).asscalar()
cur_time = datetime.datetime.now()
h, remainder = divmod((cur_time - prev_time).seconds, 3600)
m, s = divmod(remainder, 60)
time_str = "Time %02d:%02d:%02d" % (h, m, s)
__loss = _loss/len(train_data)
train_loss.append(__loss) if valid_data is not None:
valid_loss = get_loss(valid_data, net, ctx)
epoch_str = ("Epoch %d. Train loss: %f, Valid loss %f, "
% (epoch,__loss , valid_loss))
test_loss.append(valid_loss)
else:
epoch_str = ("Epoch %d. Train loss: %f, "
% (epoch, __loss)) prev_time = cur_time
print(epoch_str + time_str + ', lr ' + str(trainer.learning_rate)) plt.plot(train_loss, 'r')
if valid_data is not None:
plt.plot(test_loss, 'g')
plt.legend(['Train_Loss', 'Test_Loss'], loc=2) plt.savefig(pngname, dpi=1000)
net.collect_params().save(modelparams)

接下来就可以训练了

softmax_cross_entropy = gluon.loss.SoftmaxCrossEntropyLoss()
ctx = mx.gpu()
net = get_output(ctx)
net.hybridize() train(net, train_data,valid_data, num_epochs, learning_rate, weight_decay, ctx)

输出测试结果

训练之后,就可以把测试集的数据跑出来了

首先定义一些变量

netparams = 'r152i3.params'
csvname = 'kaggle.csv'
ids_synsets_name = 'ids_synsets'
f = open(ids_synsets_name,'rb')
ids_synsets = pickle.load(f)
f.close()

从原始图像载入数据,并定义测试输出函数

test_ds = vision.ImageFolderDataset(input_str + test_dir, flag=1,
transform=transform_test)
def SaveTest(test_data,net,ctx,name,ids,synsets):
outputs = []
for data1,data2, label in tqdm(test_data):
data1 =data1.as_in_context(ctx)
data2 =data2.as_in_context(ctx)
output = nd.softmax(net(data1,data2))
outputs.extend(output.asnumpy())
with open(name, 'w') as f:
f.write('id,' + ','.join(synsets) + '\n')
for i, output in zip(ids, outputs):
f.write(i.split('.')[0] + ',' + ','.join(
[str(num) for num in output]) + '\n')

开跑

net = get_net(netparams,mx.gpu())
net.hybridize()
SaveTest(test_data,net,mx.gpu(),csvname,ids_synsets[0],ids_synsets[1])

最后就可以把输出的csv文件提交到kaggle上了。

使用kaggle提供的数据,最后拿到了0.27760的分数。如果更进一步,那就是用Stanford dogs dataset数据集。

感悟

首先这次的kaggle比赛算是我第一次结束正式的图像分类比赛,在Gluon论坛里也学到了好多东西。

使用迁移学习的话,那前期就先把数据过一遍特征网络,省时省力。如需要训练前面特征网络的时候,再连起来训练就可以了。

大多数图像分类都可以使用预训练模型进行迁移训练,因为经过ImageNet的模型都是老司机了,见多识广。

使用预训练模型进行迁移学习,那么数据处理要和原模型的一致,比如图像尺寸,归一化等。

最后感谢沐神的直播课程,和论坛里的大神杨培文提供的思路和借鉴代码。

完整代码: https://github.com/fierceX/Dog-Breed-Identification-Gluon

Gluon炼丹(Kaggle 120种狗分类,迁移学习加双模型融合)的更多相关文章

  1. 修改pytorch官方实例适用于自己的二分类迁移学习项目

    本demo从pytorch官方的迁移学习示例修改而来,增加了以下功能: 根据AUC来迭代最优参数: 五折交叉验证: 输出验证集错误分类图片: 输出分类报告并保存AUC结果图片. import os i ...

  2. 使用PyTorch进行迁移学习

    概述 迁移学习可以改变你建立机器学习和深度学习模型的方式 了解如何使用PyTorch进行迁移学习,以及如何将其与使用预训练的模型联系起来 我们将使用真实世界的数据集,并比较使用卷积神经网络(CNNs) ...

  3. TensorFlow从1到2(九)迁移学习

    迁移学习基本概念 迁移学习是这两年比较火的一个话题,主要原因是在当前的机器学习中,样本数据的获取是成本最高的一块.而迁移学习可以有效的把原有的学习经验(对于模型就是模型本身及其训练好的权重值)带入到新 ...

  4. 基于深度学习和迁移学习的识花实践——利用 VGG16 的深度网络结构中的五轮卷积网络层和池化层,对每张图片得到一个 4096 维的特征向量,然后我们直接用这个特征向量替代原来的图片,再加若干层全连接的神经网络,对花朵数据集进行训练(属于模型迁移)

    基于深度学习和迁移学习的识花实践(转)   深度学习是人工智能领域近年来最火热的话题之一,但是对于个人来说,以往想要玩转深度学习除了要具备高超的编程技巧,还需要有海量的数据和强劲的硬件.不过 Tens ...

  5. 【转载】 迁移学习简介(tranfer learning)

    原文地址: https://blog.csdn.net/qq_33414271/article/details/78756366 土豆洋芋山药蛋 --------------------------- ...

  6. 用tensorlayer导入Slim模型迁移学习

    上一篇博客[用tensorflow迁移学习猫狗分类]笔者讲到用tensorlayer的[VGG16模型]迁移学习图像分类,那麽问题来了,tensorlayer没提供的模型怎么办呢?别担心,tensor ...

  7. 【转载】 第四范式首席科学家杨强:AlphaGo的弱点及迁移学习的应对(附视频)

    原文地址: https://www.jiqizhixin.com/articles/2017-06-02-2 ============================================= ...

  8. 《A Survey on Transfer Learning》迁移学习研究综述 翻译

    迁移学习研究综述 Sinno Jialin Pan and Qiang Yang,Fellow, IEEE 摘要:   在许多机器学习和数据挖掘算法中,一个重要的假设就是目前的训练数据和将来的训练数据 ...

  9. PyTorch基础——迁移学习

    一.介绍 内容 使机器能够"举一反三"的能力 知识点 使用 PyTorch 的数据集套件从本地加载数据的方法 迁移训练好的大型神经网络模型到自己模型中的方法 迁移学习与普通深度学习 ...

随机推荐

  1. 常用的HTTP状态码

    成功的状态码: 200 – 服务器成功返回网页 304 – 未修改 失败的状态码: 404 – 请求的网页不存在 503 – 服务器暂时不可用 500 – 服务器内部错误 下面的不是很常用,记住上面那 ...

  2. kettle系列一之eclipse开发

    1.引言 最近公司开始一个etl项目,底层结合开源的kettle进行开发.那么学习kettle势在必行,kettle的使用在这里就不用介绍了,网上有很多的资料.例如:kettle中文社区,我们在这里主 ...

  3. Linux入门(17)——Ubuntu16.04显示内存CPU网速等(System Monitor)

    终端查看内存状况有两个命令 top htop 如果系统没有安装htop的话,那就安装一下. 然而这样比较麻烦,System Monitor是个不错的选择,System Monitor可以显示网速,内存 ...

  4. MySQL数据库主从复制实践

        MySQL 主从(MySQL Replication),主要用于 MySQL 的实时备份.高可用HA.读写分离.在配置主从复制之前需要先准备 2 台 MySQL 服务器. 一.MySQL主从原 ...

  5. Debian GNU/Linux 8.4 (jessie)编译安装php.md

    编译遇到的问题很多.网上的文章往往是记录遇到的报错,贴上对应的解决. 而实际的环境,如操作系统,安装的软件必然有差异,所以,更重要的是,如何找到解决方法(不担保按步骤做可以编译成功),并将过程自动化. ...

  6. base64减少图片请求

    1. 使用base64减少 a)            2. 页面解析 CSS 生成的 CSSOM 时间增加 Base64 跟 CSS 混在一起,大大增加了浏览器需要解析CSS树的耗时.其实解析CSS ...

  7. 如何内网搭建NuGet服务器

    NuGet 是.NET程序员熟知的给.NET项目自动配置安装library的工具,它可以直接安装开源社区中的各个公用组件,可以说是非常方便.不过,有些时候,公司内部的公用的基础类库,各个项目都有引用, ...

  8. Java基础——字符串构建器

    StringBuilder类: 可以将许多小段的字符串构建一个字符串. StringBuilder builder = new StringBuilder(); //构造一个空的字符串构建器 buil ...

  9. js实现强大功能

    作者:知乎用户链接:https://www.zhihu.com/question/48187821/answer/110002647来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  10. 关于01背包求第k优解

    引用:http://szy961124.blog.163.com/blog/static/132346674201092775320970/ 求次优解.第K优解 对于求次优解.第K优解类的问题,如果相 ...