Example #1
0
class Trainer(object):

    def __init__(self, config, debug=True):
        self.config = config
        self.debug = debug
        self.logger = LogAPI.create_logger(self.__class__.__name__, self.debug)

        self.data_processor = DataProcessor(config)
        self.data_processor.prepare()
        n_vocab = len(self.data_processor.vocab)
        self.model = L.Classifier(Img2Seq(n_vocab, config))
        self.model.compute_accuracy = False  # I want loss, not accuracy
        self.optimizer = chainer.optimizers.Adam()
        self.optimizer.setup(self.model)

        if self.config['use_gpu'] >= 0:
            chainer.cuda.get_device(self.config['use_gpu']).use()
            self.model.to_gpu()
            self.xp = cuda.cupy
        else:
            self.xp = np

        # パラメータ・実験結果を保存しておくdict
        self.result_storage = {}
        self.result_storage["result"] = {
            "total_loss": [], "average_loss": [], "time_taken": [], "hyper_params": config}

    def _calc_loss(self, batch):
        boke, img = batch
        boke = self.xp.asarray(boke, dtype=np.int32)
        img = self.xp.asarray(img, dtype=np.float32)

        # 1. ベクトル化してある画像をCNNに入れて特徴ベクトルにする
        img_vec = self.model.predictor.encode_image(img)
        # この時点で画像は入力(word embedding)と同じ次元
        # もしかして: これLinkの外に出したほうがいいか?

        # 3. bokeをデコードするように学習
        accum_loss = 0
        n = 0
        for curr_words, next_words in zip(boke.T, boke[:, 1:].T):
            if n == 0:
                accum_loss += self.model(curr_words, img_vec, next_words)
            else:
                accum_loss += self.model(curr_words, next_words)
            n += 1
        return accum_loss

    def run(self):
        for epoch in xrange(self.config["total_epoch"]):
            self.logger.info("Currently at Epoch #{}".format(epoch + 1))
            self.epoch_start_time = datetime.now()

            for batch in self.data_processor.batch_iter():
                loss = self._calc_loss(batch)
                self.optimizer.target.zerograds()
                loss.backward()
                self.optimizer.update()

            self.epoch_end_time = datetime.now()
            if (epoch + 1) % 3 == 0:
                self._take_snapshot(epoch)
                self.evaluate(epoch)

    def evaluate(self, epoch):
        self.logger.info("Processing Validation...")
        accum_loss = 0
        n_samples = 0

        for batch in self.data_processor.batch_iter(kind="dev"):
            accum_loss += cuda.to_cpu(self._calc_loss(batch).data)
            n_samples += len(batch[0])  # batchsize

        # epoch単位の実行時間の計算
        td = self.epoch_end_time - self.epoch_start_time  # td: timedelta
        hours, remainder = divmod(td.seconds, 3600)
        minutes, seconds = divmod(remainder, 60)
        time_taken = "{hours:02d}:{minutes:02d}".format(
            hours=hours, minutes=minutes)

        total_loss = float(accum_loss)
        average_loss = total_loss / n_samples
        self.logger.info("Epoch: #{epoch}\tTotal Loss:{total:.4f}\tAverage Loss:{average:.4f}\tTime Taken:{time_taken}\n".format(
            epoch=epoch,
            total=total_loss,
            average=average_loss,
            time_taken=time_taken
        ))

        # accuracy/total loss/average lossをそれぞれ格納
        self.result_storage["result"]["total_loss"].append(total_loss)
        self.result_storage["result"]["average_loss"].append(average_loss)
        self.result_storage["result"]["time_taken"].append(time_taken)
        # 格納したstatsをファイルに保存
        self._save_stats()

    def _take_snapshot(self, epoch):
        """
        訓練したモデルをepochごとに保存していくメソッド
        """
        path = "{}/{:02d}.npz".format(self.config["model_path"], epoch)
        S.save_npz(path, self.model)

    def _save_stats(self):
        """
        stats.json (accuracy/average loss/total lossを保存するjson) を
        epochごとに書き換えるメソッド
        """
        with open(self.config["log_path"], "w") as result:
            result.write(json.dumps(self.result_storage))
            result.flush()