Edited at

【Caffe】HDF5 Data Layer向けのHDF5データを作ってみる

More than 1 year has passed since last update.


動機

Caffeでは度々HDF5 Data Layerを使うことがある.例えば以下のようなとき.


  • 回帰をするとき,LEVELDB等ではラベルをint型にしかできないのを,float型/double型にしたい

  • 3次元(時間軸方向込)に画像を格納したデータを作りたい

しかしHDF5データ作成のサンプルコードが無いということで,作ってみました.


float/double型のラベルデータ作成

以下を実行することで,"hdf5"フォルダを作成し,その階層下に***.hdf5, ***_path.txtを作ってくれます.

但し,***.hdf5の名前に「train」「test」「val」のいずれかの名前が入っていないとダメな仕様にしています.

python label_hdf5.py [leveldbかlmdbを作成するときのテキストファイル名] [hdf5データ保存ファイル名].hdf5

[train/test/val]_path.txtに,作成したhdf5のファイル名のパスが書き込まれます.


label_hdf5.py

# coding:utf-8

import h5py
import sys
import os

def save_hdf5(filename, labels):
with h5py.File(filename, 'w') as f:
f['label'] = labels

def make_pathfile(savefile):
typename = get_type(savefile)
if typename == "":
print "include 'train/test/val' in the name of savefile"
return -1
f = open('hdf5/' + typename + '_label_path.txt', 'w')
f.write(savefile)
f.close()
return 0

def get_type(filename):
if 'train' in filename:
return 'train'
elif 'test' in filename:
return 'test'
elif 'val' in filename:
return 'val'
else:
return ''

def main():
if not os.path.exists("hdf5"):
os.mkdir("hdf5")

text_file = sys.argv[1]
save_file = 'hdf5/' + sys.argv[2]

if len(sys.argv) != 3:
print "Usage: python label_hdf5.py text_file save_file"
return
res = make_pathfile(save_file)
if res < 0:
return

label = []
for line in open(text_file, 'r'):
label.append(float(line.split(' ')[1])) # doubleにしたいなら,floatをdoubleに差し替えてください

save_hdf5(save_file, label)

if __name__ == "__main__":
main()



HDF5 Data Layerの使い方

Data Layerでtop: "label"をコメントアウトします(LEVELDBに格納したlabelデータを使わないという意味です.こちらはint型)

新たにHDF5DataLayerを追加します.こちらでtop: "label"は名前を間違えないように.

hdf5_data_paramsourceに,先ほどのhdf5/***_path.txtの名前を指定します.

batch_sizeはDataLayerのそれと一致させます.

LossLayerのtop: "label"の箇所も名前を間違えないこと.


train_test.prototxt

name: "NN"

layer {
name: "datas"
type: "Data"
top: "data"
# top: "label"
include {
phase: TRAIN
}
data_param {
source: "train_leveldb"
backend: LEVELDB
batch_size: 64
}
transform_param {
scale: 0.00390625
mean_file: "train_mean.binaryproto"
}
}
layer {
name: "label_data"
type: "HDF5Data"
top: "label"
include {
phase: TRAIN
}
hdf5_data_param {
source: "hdf5/train_path.txt"
batch_size: 64
}
}

...

layer {
name: "loss"
type: "EuclideanLoss"
bottom: "prob"
bottom: "label"
top: "loss"
}



3次元画像(=映像)データ作成

Caffeは3DPoolingに対応していないので,果たしてやる意味あるのかという感じですが,いつか誰かが実装してくれると信じて...


注意

HDF5 Data Layerには平均画像(mean_file)を差し引く機能はありません

0~255→0~1のスケール変換機能もありません.

クソすぎますが,負けじと作ります.


3ddata_hdf5.py

import h5py

import numpy as np
import random
import sys
import os
import glob
from PIL import Image

def save_hdf5(filename, inputs, labels):
with h5py.File(filename, 'w') as f:
f['data'] = inputs
f['label'] = labels

def make_pathfile(savefile):
typename = get_type(savefile)
if typename == "":
print "include 'train/test/val' in the name of savefile"
return -1
f = open('hdf5/' + typename + '_path.txt', 'w')
f.write(savefile)
f.close()
return 0

def get_type(filename):
if 'train' in filename:
return 'train'
elif 'test' in filename:
return 'test'
elif 'val' in filename:
return 'val'
else:
return ''

def count(file):
num = 0
for line in open(file, 'r'):
num += 1
return num

def main():
if not os.path.exists("hdf5"):
os.mkdir("hdf5")

if len(sys.argv) < 7:
print "Usage: python 3ddata_hdf5.py text_file save_file channel(int) width(int) height(int) depth(int)"
return

# dir_name = sys.argv[1]
text_file = sys.argv[1]
save_file = sys.argv[2]
channels = int(sys.argv[3])
width = int(sys.argv[4])
height = int(sys.argv[5])
depth = int(sys.argv[6])
scale = 0.00390625
num = count(text_file)

res = make_pathfile(save_file)
if res < 0:
return

label = []
inputs = []
data = np.zeros(channels * width * height * depth)
data = data.reshape([channels, depth, width, height])
mean = np.zeros(channels * width * height * depth)
mean = data.reshape([channels, depth, width, height])
n = 0
for line in open(text_file, 'r'):
if n % 100 == 0:
print str(n) + " is processing..."
n += 1
label.append(float(line.split(' ')[1]))
imgname = sorted(glob.glob(line.split(' ')[0] + "/*"))
for i in range(0, depth):
data[0:, i, 0:, 0:] = np.array(Image.open(imgname[i])).reshape([channels, width, height]) * scale
mean += data / num
inputs.append(data)
inputs = np.array(inputs, dtype=np.float32)
inputs = inputs - mean

save_hdf5("hdf5/" + save_file, inputs, label)

if __name__ == "__main__":
main()



使い方

データセットは,あるフォルダ(今回はtrain/とする)階層下に各データのフォルダがあり,そのまた下に各々のデータの画像群が入っているものとします.

train/0/0.jpg

train/0/1.jpg
train/0/2.jpg
...
train/0/20.jpg
train/1/0.jpg
train/1/1.jpg
train/1/2.jpg
...
train/1/20.jpg
...

といった形です.これに対応し,各データとラベルを対応づけるテキストファイルtrain.txtは


train.txt

train/0 0

train/1 2
train/2 1
...

と,1列目にフォルダ名,2列目にラベルを書いてください.

コマンドラインで以下のようにして使用します.

python 3ddata_hdf5.py text_file save_file channel(int) width(int) height(int) depth(int)

channel(int) 以降は,用いるデータの各画像のチャネル数,大きさと,時間軸方向の大きさに合わせて書いてください.(間違えると使えない)

例えば,画像が3チャンネル50*50px,20フレーム分,

各データとラベルが対応づいたテキストファイルがtrain.txt,

保存先がtrain.hdf5としたい場合

python 3ddata_hdf5.py train.txt train.hdf5 3 50 50 20

となります.HDF5データはhdf5/フォルダ内に作られます.


注意

データが大きくなりすぎたとき,平均画像を引くinputs = inputs - meanが上手くいかないことがあります.

これはtrain用に作りましたが,test用にはtrainで作った平均画像を引く必要があります(testデータの平均画像で引いてはダメ).

mean.npyとかを作らなきゃいけないと思いますが,今回は割愛(すみません)

HDF5のデータファイルはとにかく重いです.元データ群より重いこともあります.


終わりに

PythonLayerでもいけそう,いつか実装したい