本项目参考 猫狗大战
猫狗大战来源于Kaggle上的一个热门比赛,传送门:Dogs vs. Cats 该项目包括25000张图片的训练集和12500张图片的测试集,训练集中,猫狗图片各占一半;
-
由于本文使用Keras 的 ImageDataGenerator,因此在进行数据预处理时,会把训练集中的猫狗图片分开,并将测试数据集再额外放置于一个文件夹中(套一层‘外套’)
-
在开始训练之前,我先切分出了10%的图片为验证集,比起在Keras中使用valid_split, 这样做的好处是数据集结构比较清晰,且在开始时就打乱了数据集,不会出现在训练时忘了打乱验证集的情况:
How is the validation split computed?
If you set the validation_split argument in model.fit to e.g. 0.1, then the validation data used will be the last 10% of the data. If you set it to 0.25, it will be the last 25% of the data, etc. Note that the data isn't shuffled before extracting the validation split, so the validation is literally just the last x% of samples in the input you passed.
- 在进行数据集文件夹分类时,我采用了python中的 shutil.move 函数,可以自动地将不同图片文件分类放置,eg:
os.makedirs('test3'+'/test')
for file in os.listdir('test'):
shutil.move('test/' + file,'test3/test/' + file)
最终获得数据集文件结构为:
├── train.zip
├── test.zip
├── test [0 images]
├── test3
│ └── test [12500 images]
├── train [0 images]
├── train3 [total 22500 images]
│ ├── cat
│ └── dog
└── valid3 [total 2500 images]
├── cat
└── dog
在项目中,我先采用普通的Transfor Learning并进行fine tune,在这里,我使用Keras中的预训练模型:Resnet50,Inception V3,Xception,并使用预训练权重,最终取得的结果为:
Model Name | LogLoss score |
---|---|
Resnet50 | 0.09039 |
Inception V3 | 0.08367 |
Xception | 0.08866 |
Xception with data augmentation | 0.07847 |
具体可以参阅同名notebook(eg. Xception.ipynb)
在完成以上工作后,我参照模型融合的**,将Keras提供的预训练模型都使用了起来,具体包括5个模型:Resnet50,Inception V3,Xception,DenseNet201,InceptionResnetV2; 也就是说,使用这5个模型从原图片里提取猫狗的“特征”,再把这些特征作为输入,进行模型预测;
这样做的优势:
- logloss 显著降低,相当于将几个很强的模型“并联”,实现 stacking
- 预训练模型向量在训练完后可以保存,只需要再构建一个很小的网络,就可以使用这些向量权重,迅速进行预测
最终将预训练获得的特征保存为h5文件,每个h5文件5 文件包括五个 numpy 数组:
- train (22500, features)
- valid(2500,features)
- test (12500, features)
- train_label (22500,)
- valid_label (2500,)
其中features为该模型提取出来的特征;对于Resnet50,Inception V3,Xception,特征值为2048,对于DenseNet201,该值为1920,对于InceptionResnetV2,为1536;
将提取出的特征融合,变为新的模型输入:
X_train = []
X_valid = []
X_test = []
for filename in ["pre_ResNet50s.h5", "pre_Xceptions.h5", "pre_InceptionV3s.h5","pre_InceptionResNetV2s.h5","pre_DenseNet201s.h5"]:
with h5py.File(filename, 'r') as h:
X_train.append(np.array(h['train']))
X_valid.append(np.array(h['valid']))
X_test.append(np.array(h['test']))
y_train = np.array(h['train_label'])
y_valid = np.array(h['valid_label'])
X_train = np.concatenate(X_train, axis=1)
X_valid = np.concatenate(X_valid, axis=1)
X_test = np.concatenate(X_test, axis=1)
X_train, y_train = shuffle(X_train, y_train)
然后构建一个两层的简单的预测模型:
input_tensor = Input(X_train.shape[1:])
x = input_tensor
x = BatchNormalization(axis=1)(x)
x = Dense(1, activation='sigmoid')(x)
model = Model(input_tensor, x)
在构建模型时,我使用了BN层,对前面各个模型提取出的features进行Normalization;
另外,在最终预测结果时,我参照原例,使用了clip方法,将预测输出限定在[0.005,0.995]内,事实证明,clip方法对降低Logloss确实有作用。
最终结果(2 epoch):
Model Name | LogLoss score |
---|---|
Stacking model | 0.03941 |
在运行预测代码时,我发现模型在4epoch 就会产生过拟合,导致Logloss上升,而2 epoch 和3 epoch 获得的valid Loss 差不多,由此,我将2 epoch 和 3 epoch的结果进行加权:
df_a = pd.read_csv("epoch2.csv")
df_b = pd.read_csv("epoch3.csv")
pred_mix= np.zeros(12500)
for i,a,b in zip(df_a['id'],df_a['label'],df_b['label']):
if a>=0.5 and b>=0.5:
pred_mix[i-1]=np.max((a,b))
elif a<0.5 and b<0.5:
pred_mix[i-1]=np.min((a,b))
else:
pred_mix[i-1]=np.mean((a,b))
df_a.set_value(i-1,'label',pred_mix[i-1])
df_a.to_csv('pred_mix.csv',index=None)
df_a.head(10)
结果有所提升,Logloss 有所下降:
Model Name | LogLoss score |
---|---|
Stacking model-average | 0.03840 |
该结果位于Leardboard的12名,进入了前1%;
如果要进一步提升,可以增加数据增强,或者在stacking模型的预训练模型中放开更多的层。