データセットを管理する Revision の紹介

当記事は、[SENSY Advent Calendar 2017](https://qiita.com/advent-calendar/2017/sensy)の10日目の記事です。

今回は、データセットを管理する [Revision](https://pypi.python.org/pypi/revision) というライブラリを会社の時間を使って作ったので、紹介したいと思います(ライセンスやリポジトリは会社名の表記にしていますが、メインの開発は僕が個人的に行っております)。

## なぜ作ったか

機械学習の開発をする際、複数のデータセットや元のデータセットに手を加えたような様々な状態のデータセットが手元にあり、開発中に使用します。
例になりますが、データセットに手を加えたが、データセットを以前の状態に戻したい場合が多々あります。その時に版管理できていると、ある時点の状態に戻すのが簡単になると思い、作ろうと思いました。

また、データセットはGitで管理するにはサイズが大きすぎる場合があるので、クラウド等の保存先を選べるようにしました。

(ちなみに、外に公開するライブラリとして初めてのものなので、僕にとって、**とても感慨深い大事なライブラリ**です。)

## 機能

Revision は版管理をするための基本的な機能のみ実装していて、Google Cloud StorageやAmazon S3等の各クライアント毎の実装はエクステンションとして動作するようにしています。

Revision が提供する機能:

* 特定のパスにあるファイル群を含むディレクトリをZip圧縮したり、特定のパスに展開する
* エクステンションで実装しないといけない機能を定義して(インターフェースを)提供する
* 版管理するための機能や、それらの機能を使うためのコマンドラインを提供する

各エクステンションが提供する機能:

* アップロード・ダウンロード処理を行う
* エクステンション自身の情報を Revision に提供する

Revision の簡単な説明は以上になります。
次に具体的な使い方を以下紹介していきます。
また、説明用に[サンプルのコード](https://github.com/tokuda109/revision-sample)を作っているので、このコードを元に説明していきます。

## プロジェクトのダウンロード

```sh
$ git clone git@github.com:tokuda109/revision-sample.git
```

グローバルの環境を汚さないために仮想環境を作ります(該当しない場合やここでのやり方ではない方法で準備したい方は、このステップは飛ばして下さい)。

```sh
$ pyenv virtualenv 2.7.13 revision-sample
New python executable in /Users/tokuda109/.pyenv/versions/2.7.13/envs/revision-sample/bin/python2.7
Also creating executable in /Users/tokuda109/.pyenv/versions/2.7.13/envs/revision-sample/bin/python
Installing setuptools, pip, wheel...done.
Requirement already satisfied: setuptools in /Users/tokuda109/.pyenv/versions/2.7.13/envs/revision-sample/lib/python2.7/site-packages
Requirement already satisfied: pip in /Users/tokuda109/.pyenv/versions/2.7.13/envs/revision-sample/lib/python2.7/site-packages
```

```sh
$ pyenv activate revision-sample
```

## インストール

パッケージをインストールします。
現在、SENSY Inc.ではGoogle Cloud Storageをメインのストレージとして使っているので[GCSクライアント](https://pypi.python.org/pypi/revision-gcs)のみがエクステンションとして公開されています。(S3とかはもう少し待ってくださいm(＿ ＿)m)

```sh
$ pip install -r requirements.txt
```

## プロジェクトの開始

対象のプロジェクトのディレクトリで、以下を実行すると版管理をするためのプロジェクトが開始されます。  

```sh
$ revision init
INFO .revision.json file is created.
```

現在のディレクトリに `.revision.json` というファイルが作成されます。
このファイルの中身は以下のようになっています。

```json
{
  "clients": []
}
```

`clients` は配列なので、複数のクライアントを定義することができるようになっています。
ここに `Revision for GCS` で管理するディレクトリを指定してみます。

```json
{
  "clients": [
    {
      "key": "sample",
      "module": "revision_gcs.GCSClient",
      "local_path": "data",
      "remote_path": "gs://revision-sample",
      "revision_file": "CHANGELOG.md",
      "options": {
        "key_file": "gcs-key-file.json"
      }
    }
  ]
}
```

キー | 説明
-----|--------------------
key | 対象とするディレクトリ毎に指定するキーで、ユニークである必要があります(必須項目)
module | 読み込むエクステンションのモジュールパスを指定します(これは将来的になんとかしたい)(必須項目)
local_path | 作成したプロジェクトのルートからの相対パスを指定します(必須項目)
remote_path | (Revision for GCSの場合は) `gs://` のプリフィックスの後にバケット名を指定します(必須項目)
revision_file | 履歴を書き込むためのファイル名を指定します(任意でデフォルトは`CHANGELOG.md`)
options | 各クライアント毎で柔軟に設定を定義できるようにするためで、`Revision for GCS` ではサービスアカウントのキーファイルへのパスを指定します。

## コミット

コンフィグの `local_path` で指定したディレクトリの現在のファイル群をコミットするには以下のように実行します。

```sh
$ revision sample commit
```

注意点として、CLIのサブコマンドとして、コンフィグで定義したクライアントのキーを指定して下さい。
実行すると、エディター画面が起動するので `[DESCRIPTION]` と `[MESSAGE]` の部分を変更内容に修正して保存・終了します。今回の例では以下のように修正します。

```
# Please enter the commit message for your changes.

sample description

sample message
```

## アップロード

コミット内容をクライアントのストレージにアップロードするには以下のようにします。

```sh
$ revision sample push
Archive CHANGELOG.md
Archive black_100_100.png
Uploading sample-d03cc6c040049a6fc93d7ea7a0b08d2477d9199c.zip (0.00MB)  [####################################]  100%
```

処理の内容としては、`CHANGELOG.md` にコミット概要やメッセージを書き込んで、対象ディレクトリをZIP圧縮してアップロードしています。GCSを見てみるとZIPファイルが追加されているのを確認することができます。

<img width="1440" alt="fig_01.png" src="https://qiita-image-store.s3.amazonaws.com/0/1876/57ed06c6-271f-6b7b-e365-20ecbf0a28c5.png">

## ダウンロード

次に、クライアントに対して版管理した最新のファイルをダウンロードします。
今回はきちんとダウンロードできたかを確認するために一度ファイルを削除します。

![fig_02.png](https://qiita-image-store.s3.amazonaws.com/0/1876/0d81c350-39f0-99fc-af1e-cfcf39cc7017.png)

削除してから以下の実行すると、上の画像のようにダウンロードしてきたZIPファイルを展開して元の通りになります。

```sh
$ revision sample pull
Downloading sample-d03cc6c040049a6fc93d7ea7a0b08d2477d9199c.zip (0.00MB)  [####################################]  100%
```

## 動作風景

今回たくさん説明してきましたが、全体の流れをざっと、スクリーンキャストにしましたので、是非見てみて下さい。細かい内容は、現バージョンと異なるところもありますが、全体の流れはこの動画で確認することができます。(あと、ちょっと字が小さいかもしれないですが、スイマセン。)

[![asciicast](https://asciinema.org/a/140624.png)](https://asciinema.org/a/140624)

## TODO

また、現在実装済みの機能は、やりたいことの大部分がまだ実装できておらず、直近すぐにやる必要がある作業として以下のものがあります。

* ドキュメントを書く
* 全体的な機能として使いやすいかをもう一度確認して、機能のリファクタリングを行う
* 状態を保持する機能のリファクタリング
* GCSクライアント以外のエクステンションも作る (S3とかスプレッドシートとか)
* 個人的にメンテナンスしている[TensorFlow Docker Skeleton](https://github.com/tokuda109/tensorflow-docker-skeleton)に組み込む

現状、業務が忙しいので、次に紹介する[おのみち.py](https://onopy.connpass.com)に参加して、おのPYドリブンでほそぼそとデベロップメントしています。THE **ODD**!!

## おのみち.py

おのみち.py は、同僚の @rhoboro さんが主催しているPythonもくもく会です。  
場所は尾道の[ONOMICHI SHARE](http://onomichi-share.com/)で開催されることが多く？？、Pythonや尾道に興味を持った方は気軽に参加してみてください。広島の方のコミュニティともコラボしたりしているみたいです。その近くにお住まいの方は是非！リモートでも参加できまよ！