ChainerのTrainerを使ってFineTuning
どうもこんにちは
chainerなんか早くなったらしいですね。
とても素晴らしい技術力だと思いますが、個人的には高速化よりも詳しい使い方が書いたドキュメントや日本語ドキュメントの充実に力を入れてほしい気がします。
(GPU128枚も持ってないし…)
128枚もGPUを使って何かするようなところって大きな企業かでかい研究機関しかないと思うし、そういったところが使うかと言うと使わない気もする。
PyTorchとかchainerに似ているフレームワークも出てきていて正直chainerどうなるんだろうという感じはしていますが、PaintsChainerとかあったり一応日本では流行ってるみたいなので使ってみました。
Trainer
Trainerってなんだって話ですが、今まではバッチ処理を自分で書いたりしなければならなかったのがTrainerによって抽象化されて書く必要がなくなりました。 いまどれだけの進捗でどれくらいのlossになっててAccuracyはどれくらいだと言うことも割りと簡単にできるようになったみたいなので今回はこのTrainerを使ってみたいと思います。 学習させるところまでできたので記録としてかいてあります。 間違ってるところとか、こういうふうに書いたほうがかっこいいぜみたいなのあったら教えていただけるとありがたいです。
使い方
上のサンプルコードを参考に書いていきます。詳しいことはこのサンプルとドキュメントを見るとわかりやすいかと思います。 まず、事前にやっておくこととして、FineTuningをしたいのでモデルの重みを取ってきておきます。 modelはこちらにたくさんあるので好きなのを選びましょう。
caffeのモデルですが、chainerではcaffeのモデルをロードできるような仕組みがCaffefunctionと言うかたちで実装されています。 caffefunctionの詳しい使い方は今回書きませんが、caffeのモデルを読み込むのはすごく時間がかかるので、一度読み込んだらPickleなどでdumpしておきましょう。 重みの読み込みは以下ののコードの場合load_model.pyということに書いてあります。重みの読み込みはこちらで書いていることと同じことを行っています。
手順としては、
重みを読み込む→トレーニングセットと、バリデーションセットに分ける→optimizer,updaterの設定をする→trainerでトレーニング
と言った感じです。
Trainerで学習させるときにGPUを使う場合はモデルにたいしてto_gpu()
を行う必要があります。ここで結構ハマりました。
import chainer import pickle import chainer.links as L from chainer import training from chainer.training import extensions from preprocess import ImagePreprocess import vgg16 from load_model import load_caffe_model import os import pandas as pd def trainer(csv_file, root, batch_size, lorderjob, image_size, gpu): # load model model = vgg16.Vgg16() model_weight = pickle.load(open('../data/models/vgg16model', 'rb')) load_caffe_model(model_weight, model) model = L.Classifier(model) print('finish loading model') if gpu >= 0: chainer.cuda.get_device(0).use() model.to_gpu() # create train set and validation set csv_data = pd.read_csv('../data/train.tsv', delimiter='\t') dataset = [] for r in csv_data.iterrows(): dataset.append((r[1][0], r[1][1])) train, val = chainer.datasets.split_dataset_random(dataset, 9000) train_data = ImagePreprocess(train, root, image_size) val_data = ImagePreprocess(val, root, image_size, False) train_iter = chainer.iterators.MultiprocessIterator( train_data, batch_size, n_processes=lorderjob ) val_iter = chainer.iterators.MultiprocessIterator( val_data, batch_size, repeat=False, n_processes=lorderjob ) # setup optimizer optimizer = chainer.optimizers.SGD() optimizer.setup(model) # setup updater and trainer updater = training.StandardUpdater(train_iter, optimizer, device=0) trainer = training.Trainer(updater, (10, 'epoch'), out='result') trainer.extend(extensions.Evaluator(val_iter, model, device=0)) trainer.extend(extensions.dump_graph('main/loss')) trainer.extend(extensions.snapshot(), trigger=(5, 'epoch')) trainer.extend(extensions.snapshot_object( optimizer, 'optimizer_iter_{.updater.iteration}'), trigger=(5, 'epoch')) trainer.extend(extensions.LogReport()) trainer.extend(extensions.PrintReport( ['epoch', 'main/loss', 'validation/main/loss', 'main/accuracy', 'validation/main/accuracy'] ), trigger=(1, 'epoch')) trainer.extend(extensions.ProgressBar(update_interval=10)) trainer.run() print('Training is finished!')
DataMixinの拡張をして、画像をミニバッチごとに読み込む
import chainer import random import cv2 class ImagePreprocess(chainer.dataset.DatasetMixin): def __init__(self, dataset, root, img_size, random=True): self.base = chainer.datasets.LabeledImageDataset(dataset, root) self.img_size = img_size self.random = random def __len__(self): return len(self.base) def get_example(self, i): img_size = self.img_size image, label = self.base[i] image = image.transpose(1, 2, 0) image = cv2.resize(image, (img_size, img_size)) image = image.transpose(2, 0, 1) _, h, w = image.shape if self.random: # Randomly imga region and flip the image if random.randint(0, 1): image = image[:, :, ::-1] image *= (1.0 / 255.0) # Scale to [0, 1] return image, label
preprocess.py
というファイルにDatasetMixin
クラスを拡張したものを作っています。このクラスを拡張して、get_exampleをいい感じにいじるとミニバッチごとに画像を読み込んでくれるのでメモリの節約をすることができます。
DataAugmentationもこれを使うことでできます。上のコードではエポックごとに画像をランダムでフリップさせることによって、画像のデータを増やすといったことをしています。
またこのクラスのオブジェクトをMultiprocessIterator
に渡すことでCPUでミニバッチ分の画像を読み込み、GPUで学習といったことができるようになります。
以上のようにすれば学習が進んでいくかと思います。 Trainerは抽象化されているので理解するのに少し時間がかかるかと思いますが、使えるようになると便利なので使っていくといいのではないでしょうか。 特に、CPUで画像読み込みながらGPUで学習するとかいったコードを自分で書くのは大変そうなのでそのあたりもTrainer使うと便利そうです。