ずぼらな人間が機械学習の実験データを管理する方法

最近 [DVC(Data Version Control)](https://dvc.org/)について関心を持っています。
DVC と cloud storageとの組み合わせについてもご検討ください。
DVCのサイトには、解説動画がありますので、それを見ると理解がすすみます。

DVC については、以下のメモを作成しました。
[Git LFS からDVC(Data Version Control) に移行した話](https://qiita.com/nonbiri15/items/54c1fac76a8af5e51fdf)

---

世の中、ビッグデータだとか深層学習だとか賑やかだ。
どちらも共通していることは、たくさんのデータが必要になるということだ。
この記事の中では、ずぼらな人間がたどりついた、現時点での実験データの管理の方法を述べたいと思う。
以下の事例では、組織の中の個人、もしくは小規模のチームの中での話であり、組織化されたチームやデータの運営に関わる話は含まれていない。

（git lfs を活用している人、それ以上に効率的な実験データの管理ができている人には、有用なことは書かれていません。）

#### この記事を書いた後に気づいたこと
1. データの管理には２通りある。
　　- 機械学習の実行の時点でのデータの管理
　　- 上記のデータを用意するための個別の撮影ごとのデータの管理
　　この２つは、データの管理という点ではいっけん同じようひびくが、かなり別物で合うということ。
2. 上記のうち、機械学習の実行の時点でのデータの管理の部分には[DVC(= Data Version Control
)](https://dvc.org/)がかなり普及しだしていること。
----

### 前置き

深層学習のアルゴリズムは、今までに多くの実装例がある。
ソフトウェア環境を構築さえできれば、深層学習を既存のフレームワークにしたがって再現することは、とてもあっけなく終わるだろう。（実際には、深層学習で使われるGPUの環境構築や、必要なライブラリのバージョンをそろえることで四苦八苦することも多い。）
それぞれのフレームワークでは例題を用意してあり、READMEに書かれた手順にしたがって操作すれば、「これで学習できて、その結果の評価ができたの。」と思うほどだ。しかし、それはうまくいくように既に十分データが用意されているからだ。


　だから、自分の実現しようとすることの最初の糸口としては、それらの中の1例の学習データを、自分の目的のデータで置き換えることから始めることが多いだろう。
　しかし、うまくいかないことに気づく。自分が用意したデータの数が少なすぎたり、一般的な学習をするには、データがかたよりすぎているものだ。

つまり、自分の目的のための学習データを集めることが、一番の律速（＝ボトルネック）になっている。
解決できるかどうかは、自分の目的にそったデータをどれだけきちんと集められるかにかかっている。
しかもデータをきちんと管理することが必要だ。

大概の場合、データの管理をするのに専任の作業者が用意されているわけではない。
大元のデータの取得、データの加工、データの利用（アルゴリズムの開発など）、結果のドキュメンテーション、それらを行う片手間としてデータの管理を開発者自らがすることになってしまう。


そのような状況の中で、実験データを管理する方法を、個人的な経験に基づいて述べてみようと思う。

### データの管理に必要な条件

- **マシンのHDDを破損でデータを失わないこと**。
    - データはバックアップをとっておかないと失われてしまいます。
    - そのデータは業務として費用をかけて収集したものです。そのデータを失うと必要な開発を完了できなくなってしまいます。
- **同じ内容のデータを必要な関係者がネットワークで共有できること。**
    - バージョン管理をしていないデータを共有すると、必要な関係者がそれぞれ同じファイル名のデータを使っているのに、それぞれのファイルの中身が違っているということが簡単に発生してしまいます。そのようなときに、それぞれの場所での計算結果が一致しないのは当然です。
    - そのようなトラブルを予防することができる仕組みが必要です。
    - 余分な手間なしに簡単に最新の状況にそろえることができること。
- **バイナリデータを含むデータをバージョン管理できること。**

    - データファイルのファイル名は、最初から一貫性があって管理できるのがのぞましい。しかしそうできないこともある。その時ファイル名やディレクトリ構成を変えていったときに、効果的にバイナリファイルをバージョン管理できるツールが必要だ。間違った操作（必要なファイルを消してしまうことや、画像の補間方法が最近傍補間のように画質の劣化を生じやすい方法を用いてしまうことのようなものまで）をしてしまう危険から、データを守れることが大切です。
   
- **データへのアクセス管理をできること**

    - まだ加工中で配布できないデータと、必要な関係先に配布して共有することのできるデータとは別に管理しなくてはなりません。
    - データへのアクセス管理が細やかにできることも必要な条件です。
    - アクセスログを残せるほうがのぞましい。

- **データを追加するだけで済むなら話は単純にでくるのだが** 

 取得したデータをリポジトリに追加するだけで済むのが一番単純化できる。
しかし、実際には、データを加工しないと使えるデータにならないことが多い。
　データのクレンジング。
　画像の場合：
　適切な大きさの画像に加工するという前処置が必要なことが多い。
　目的にそった部分の画像だけにするように画像をトリミングする必要があったりする。
　最近は、画像を一部マスクする必要があったりする。
　そのため、データの加工という作業から避けて通ることができない。

　
　

### 使えなかった構成
#### NFSマウントしているWSに接続してあるHDDのテープへのバックアップ

WSのHDDをテープにバックアップするのが手間だった。
2周間に1度もバックアップがとれれば良い方だった。
トラブルを生じてしまうと、その2週間前を復元するのがやっとだった。
ファイルの共有もNFSやsambaで共有できている範囲に限られていた。

テキストファイルでの実験データを管理するには、テキストファイルであるソースコード・スクリプトの管理と同様にRCSを使っていることが多かった。

#### PCの外付けHDDをHDDまるごとコピー

PCに山ほど外付けHDDを使う状況に突入した。内蔵するHDDでは足らずに、外付けHDDを複数用いていた。しかし、外付けHDDは、物理的な衝撃に弱かった。不用意にカタとぶつけてしまうと、HDDは物理的に死んでしまって復元できなかった。
データを失う危険を減らすために、HDDから別のHDDにまるごとデータをコピーしておくことで、バックアップをとることにしていた。

HDDのディレクトリ構造まとめて状況をコピーするのに、FastCopyでディスクのディレクトリのミラーリングを定期的に行なった。


複数のディスクで自動でミラーリングするという選択もありえたはずだが、そうはしなかった。


通常使っているHDDでファイルを消してしまうと、自動でミラーリング先でも消えてしまうのは、バックアップとしてどうだろうかと思っていたからだ。
山ほどあるデータの部分と、ソースコード・スクリプトの部分とは保存する先を別にしておいて、ソースコード・スクリプトの部分から、バージョン管理をはじめていった。

#### 個人では導入しきれなかったRAID
日常の開発業務をしている中で、なかなかRAIDを導入して運用することまで及ばなかった。RAIDにするとハードディスクが物理的に破壊することからのリスクを減らせるのには違いないのだが、導入コストが高いこと、RAIDでの復旧の手順とかを考えると簡単ではなかった。
（githubなどのサービスが使える今の状況においては、ローカルにRAIDを導入するメリットはほぼない。ディスクが物理的に失われることへの対策を、自分たちが行う労力を行う必要がなくなる。自前でRAIDをくんで、社内のネットワークで共有できるようにし、アクセス管理を行うこと、そういったこことの全てを自前で行おうとすると、本来の開発業務を行うための時間と体力はどれだけ残るだろう。）

#### 何もないよりましのRCS
１つのファイルであっても、大切なコードはバージョン管理をしていた。
ソースコードに改変した際に、劣化させてしまって、前のファイルにもどりたくなることを日常的に経験していたからだ。
RCSのci コマンド co コマンドを使ってファイル単位でバージョン管理していた。しかし、テキストファイルをバージョン管理するツールであり、テキストファイルの実験データは管理できたが、バイナリデータは管理できなかった。
　幸いにも、この当時、管理する必要があった実験データはテキストファイル化可能な数値データだった。


#### ソースコードはCVSでバージョン管理

ソースコードをCVSを使ってバージョン管理していた。
CVSは、ディレクトリ構成を反映してテキストファイルを管理できるようになった分だけ、RCSよりも良くなっていた。
しかしながら、CVSの[「バイナリーファイルの扱いが下手で、リポジトリサイズの増大につながる。」という欠点](
https://ja.wikipedia.org/wiki/Concurrent_Versions_System#%E6%AC%A0%E7%82%B9)
は無視できなかった。

また、CVSでは[ファイル名の変更削除、ディレクトリ名の変更削除をうまく扱えない。](https://ja.wikipedia.org/wiki/Concurrent_Versions_System) ことも、テキストファイルのデータであっても実験データの管理に適さない状況であった。
そのため、バイナリファイルを本格的にバージョン管理することがなかった。

この状況は課題を抱えたままたっだ。

- バイナリファイルのバージョン管理ができていなかった。
- CVSでバージョン管理しているといっても、その状況を複数のメンバーで共有して開発することができていなかった。
- ましてや、ローカルなネットワークを超えた先の関係者と共有することができなかった。


###  Subversion 時代

複数のメンバーで同一のソースコードを開発する状況では、Subverionにバージョン管理ツールを移行していた。

しかしながら、多数のバイナリファイルをバージョン管理することはできなかった。

社内のサーバマシンにおいてNFSマウントやSMBで利用するという状況だった。組織をまたぐようなデータの共有はできなかった。

多数のバイナリデータがある状況だとSubversionではそれがうまく管理できなかった。

バイナリデータのバージョン管理に対しては、手を抜いていた状況だった。

### バイナリデータのバージョン管理を手抜きしていると生じる問題点

- バイナリデータの管理ができず、元データと加工データの区別がつかずに間違った判断をしてしまうことが起こる。
    - 例：解像度の異なる画像に対して同一のファイル名をつけてしまい、処理結果がどちらの解像度で評価したものなのかがわからなくなってしまう。
    - 例：画像を加工する際の加工スクリプトを改定していくことで、同一のファイル名の処理済みファイルが変わっていく。それがどのように変わってのかの履歴がとれない。

- 過去の処理済ファイルをHDD上に残しておくと、利用しているHDDの中に無駄にファイル数が増えてしまって、収集がつかなくなる。
    - 見えるべき必要があるものだけ見えて、それ以外のものは表には見えてこない管理が必要だ。
- 共有すべきデータを利用する側には、ほんとうに知る必要のある部分だけ見えればよいのであって、それ以外のものが見えることはうれしくない。
- バイナリデータをバージョン管理しておかないと、データを失う危険に対して 無防備なままになっている。
- ヘマをしでかしたときに取り戻しが利くデータ管理をしたい。
    - ファイルを壊してしまう。
    - よくないファイル操作をしてしまっているのを、よりよい処理の結果に置き換えたい。
    - 例：補間処理をNEARESTではなく、BILINEARでの処理に置き換える。元データをバージョン管理してあれえば、元データを復元し、それをBILINEARで補間処理しなおす。

- データを必要な範囲で加工しておくのを安心して行いたい。
    - 例：過度に画素数の多い画像は、機械学習をさせる目的では、いたずらに必要なメモリ量・CPU性能の要求水準を高めてしまう。
    - バージョン管理なしにやってしまうと、間違えた後にやりなおしが効かなくなってしまう。


### Google Drive やMicroSoft OneDriveでは満たされないこと

ファイルを共有する仕組みにGoogle Driveや OneDriveがあるが、
ローカルPCでそのデータを気軽に扱えるようにはなっていない。

ローカルPCの特定のディレクトリにあるデータと、ネットワークドライブにあるデータの比較や同期などのしかたに制限がありすぎるためだ。


以上のようなデータ管理の時代を経て、今はgitとgit lfs を用いている。

今までのバイナリデータの管理の中でgit lfs は一番使い勝手がよい状況だと感じている。


## gitと git lfs 時代
Subversionからgitに乗り換えてどれだけ恩恵を受けたかについては、gitのユーザーの方々に今更いう必要もないだろう。

バイナリデータをバージョン管理するにはgit lfsを使っている。

- [Git LFS で大きめのバイナリファイルも Git で管理する](https://qiita.com/msh5/items/582c086311d3630563bc)

バイナリファイルをgit lfs で管理して、だいぶ楽になった。

#### 楽になった点：
　バージョン管理してあるので、ファイルを失う心配が大幅に低下した。
　git lfsではファイル名やディレクトリ名を変更しても、効率的にファイル管理してくれる。

　元データから最終データに加工する中で生じる中間データをローカルのHDDの中に残しておく必要がない。
　どのような操作で中間データや最終データを加工したのかをコミットの際に記しておけば、データをどう作ったのかを辿れるようになる。


## 留意点：
git lfs を使うことで、バイナリな実験データを含む場合でも処理がしやすくなった。
しかし、何も考えずに作業をすると、作業の効率を低下させてしまう。
そうさせないためには、何かしらの注意をもって利用するのがいいようだ。


### データの数が１桁ふえるとやり方を考える必要がある。
データの数が1桁増えると、１時間で終る処理が１０時間かかる処理に変わってしまう。
データの数が2桁増えると、１時間で終る処理が１００時間かかる処理、13日の作業になってしまう。
データの数が桁で増えたときには、それ以前と同じやり方ができなくなる。
そのことを考えて実験データを管理しなくてはならない。

### リポジトリを大きくし過ぎないこと。
　
  目的が違うデータは、リポジトリを別にする。
　リポジトリを git clone する際に消費するディスクの容量を無駄に大きくしない。
  
#### git status で差分が生じなければ、手元に残しておく必要がない。
ローカルPCの貴重なディスクスペース（特にノートパソコンのSSDの場合にはディスクスペースが少ない）を使っていないタイミングのデータで無駄に消費することを避けます。

#### リポジトリが別ならば、分業がしやすい。
リポジトリが同じだと、pushする前にpullしなければなりません。リポジトリが別ならば、そもそもそのような必要がありません。もちろん、複数の人間が作業しても、conflictを生じる可能性は減らせます。


### 複数のファイルを単一のアーカイブにしてバージョン管理をしないこと
####　例：画像データに対するアノテーションデータの場合は、画像ごとにアノテーション結果が発生します。
　ある一枚の画像のアノテーションを修正するときに、発生する差分が小さく、どのファイルに変更を生じたのかわかりやすいほうが、管理がしやすくなります。変化を生じたアノテーションファイルのファイル名がわかれば、どの画像ファイルへのアノテーションなのか、画像ファイルごとの分業が簡単になります。
　
####　もし、アノテーションファイルを複数の画像ファイルを含むものにしてしまうと
どの画像のアノテーションを生じても、共通のアノテーションファイルに変更を生じてしまいます。
複数人でアノテーションを分業するときに、複数人のアノテーション作業の結果を、そのままコピーすればよいという訳にいかなくなる。マージ作業を生じてしまう。運悪くデータフォーマットが改行コードが少ないファイルフォーマットの場合には、差分を表示させるのも困難なことになる。


 複数のステップを経て生成させるデータがあっても、すべての中間ファイルを一時的にコミットしては、次の段階で削除するなどとものは減らそう。

　完全にすべての操作をバージョン管理しきれるとは考えない場合もあるのではなかろうか？
　(ゴミとなるようなデータを含む中から、ほんとうに必要なデータを発掘するような作業の場合）


#### 例：画像の場合、利用の目的に必要十分な画素数にまで、範囲を切り取るとか、画像を縮小する。
こうするとで、管理する画像データのディスク容量が減る。画像の辺を1/4倍になるように縮小すると、画素数は1/16になり、リポジトリのサイスも1/16になる。いたずらに解像度を高いまま処理をしようとするのはやめよう。
撮影時点で解像度を減らしておくのが処理量が減る。

#### 例：動画の場合、動き差分が一定量以上になったときだげ画像を保存する。

データの目的によっては、動画を全フレームで必要なわけではない。データを保存する時点で、必要なフレームについてだけ保存するようにしておけば、動画データの量を減らすことができる。
　目的によっては、動き差分が少なく、似た画像を多数集めても意味がないからだ

#### 例：大きくなりすぎたリポジトリから一部だけ取り出す。
以下の記事を参考にしてください。

[git sparse checkout で clone せずに一部のサブディレクトリだけを pull/checkout する](https://mseeeen.msen.jp/git-sparse-checkout/)


### リポジトリにデータを登録したら、データ取得に使用したマシンからはデータを削除しよう

レポジトリにデータを登録する際には、ネーミングルールで登録していることだろう。
そうしたときに、変更前の別メディアにあるファイルは削除しておこう。
そうしないと未登録のデータがあったと、別なタイミングに誤解してしまう。

#### データが複数の場所にあるのは悪夢
どっちが正解なのか、
どちらも保存が必要なのかを考えるのが悪夢


### 全部を処理しようとするな。
　必要なのは統計的に意味のあるデータ。
　統計的に意味があるようにデータをサンプリングして処理する
  例：データを処理するときに、データの集合をマージして、シャッフルしてからデータを選び直す。
　例：代表的なシーンについて、シャフルして選び出す枚数をそろえて、特定のシーンのデータの比率を多くしすぎるのを抑制する。
　選択したデータについてだけ、面倒な処理をすればよく、その分だけ処理量が減って楽になる。

### git はcloneした人すべてのディスクを浪費する。
だから、gitで管理するひとつのリポジトリの中のデータの数は抑制しよう。

### コミットし終えたリポジトリは、ローカルなコピーを削除できる。
データを`git commit` 、`git push` 　し終えたあとで、ローカルなディレクトリにコミットすべき残りのデータがなくなったら、その`git clone` したディレクトリをローカルのPC上からきれいさっぱり削除できる。そうすることで、自分の荷を解放することができる。

SSDをディスクとしているノートPCの場合だと、大容量の実験データで、ディスクを圧迫することは避けなくてはならない。
 

### 見えなくていいはずのデータが見えているのも悪夢
データが見えていると、そのデータは何なんだろうとなるし、使ってほしくない版のデータを使われてしまうということも生じてしまう。
だから、ディレクトリの構造は簡単にして、ほんとうに必要なファイルだけが残っている状態にする。

世の中で構築されている機械学習用のデータは、使いやすいように前もって処理をされている。でも配布されているデータは、使いやすいように処理済みのデータです。もし中間的な処理の状況を含めて配布されていたら、使う側が混乱してしまうでしょう。


### データ数が多いときのディレクトリの比較は効率化しよう。
バージョン管理を使っていると、ディレクトリを比較してまとめるのに、次のような操作をしてしまいがちだ。
新しい修正が入っているかもしれないものがあるときには、それをバージョン管理しているディレクトリに重ね書きして、バージョン管理システムでdiffをとるという操作だ。
しかし、重ね書きしてdiffをチェックするというのは、データが多い時にすべきことではない。
データのdiffをとるのに時間がかかりすぎて、貴重な時間を無駄にしてしまう。
データの数が大きいとき、データのサイズが大きい時には、それまでのやり方では大変になってくる。

編集が加わったファイルとそうでないファイルの比較を、もっと楽にチェックすることだ。
そして、編集が加わったデータだけ処理するようにして、貴重な時間を無駄にすることをなくしたい。

##### 例：編集が加わったファイルかどうかは、日付をみて確認しよう。
　編集したあとの状況で、編集を加えた後の日時をチェックして、それより古い時刻のファイルは、削除しよう。
　その上で、ローカルのリポジトリに中に重ね書きしよう。そうすれば、変更のないファイルのdiffをとるという無駄を省くことができる。

#### 例：リポジトリのフォルダ名は、どこに何を入れればよいのかわかりやすく

リファクタリングで名前が重要なように、リポジトリの名前、サブフォルダの名前がわかりやすくすること。
意図がわかりやすい構成にしておくことで、データを追加する他のメンバーでもメンテナンスしやすいものになっていく。

もちろん、最初からデータのディレクトリ構造を考えておいて、作りなおしが生じないほうがはるかによい。


処理が全部自動化できていればいいが、
データによって手動で処理をしなければならないのが悪夢

処理のアトミック性を保つこと。




一度に似たことを多数やろうとする混乱して間違える罠
　１つずつ処理すれば間違えないものを、複数同時に効率よくやろうとして間違える罠。
ファイルの移動を、同時に複数走らせると、処理の状況を人が適切に判断できなくなることが増える。


画像の表示にはコストがかかることを意識しよう。

１つのフォルダに含める画像の数に上限を設定しよう。


ファイルのブラウザでファイルを目視しながら作業しなければならないことが多くなるので、１つのフォルダの中に含まれる画像ファイルの数を制限する。
　
---
付記：
ここでの事例にあげているのは、個人レベルのデータ管理です。
チームとして仕事をする上で望ましい事例ではありません。
機械学習の開発者の工数は貴重です。
データの取得、データの加工、データの管理に、機械学習の開発者の工数を割きすぎることのないように運営してください。
もちろん、機械学習の開発者は、次のような形で関わる必要なあります。

・どのようなデータを取得する必要があるのか示すこと
・取得したデータをどのように加工するのか示すこと。
・どのようなデータが利用できるように保守されている必要があるか示すこと。

さらには、機械学習を利用するシステムを作り上げる人(機械学習の開発者にとっては、開発の依頼主）は

達成する目標がなんであり、どのような状況においてどのような結果を出せれば十分とするのかを
明示する必要があります。


#### 目的の違うデータはリポジトリを別にしよう

#### 利用制限の異なるデータもリポジトリを別にしよう

#### 継続的にデータが増え続ける性質のデータの場合、データ取得時期によってリポジトリを別にしよう

#### オプション：本当に不要になったファイルは、gitの履歴から削除しよう。
gitで管理するリポジトリが大きくなりすぎると、git のリモートのディスクの消費量が大きくなりすぎます。
[Git リポジトリに上がっているファイルを履歴ごと消すには？](https://qiita.com/go_astrayer/items/6e39d3ab16ae8094496c)

#### git submodule を活用しよう。
複数のリポジトリを連携して使う場合には、git submodule を使ったり、symbolic link を使ったりすることを考えたときがいい場合がある。

次の記事を書き起こしました(2021.03)。
[規模の大きな学習用データセットには git submoduleを使う]
(https://qiita.com/nonbiri15/items/e46e17d8b17706c87480)

---
### 追記

機械学習を使ったシステムを開発することになった場合、既存の機械学習結果をそのまま使うのではなければ、実験データを管理することは必須の作業になります。
また、既存の機械学習のシステムが目的の性能を達しているかどうかを判断するためにも、実験データを管理することは必須です。
ある程度、機械学習の枠組みができている場合には、データの改良やデータの積み増しこそが主要な作業の１つになる場合が多くなります。



[Revisiting Unreasonable Effectiveness of Data in Deep Learning Era](https://arxiv.org/pdf/1707.02968.pdf)

[機械学習論文読み：Revisiting Unreasonable Effectiveness of Data in Deep Learning Era](https://qiita.com/ikeyasu/items/aefa15e8c329b599fdb2)