[シェル芸] ワンライナーでData Augmentation

この記事は[eeic Advent Calender 2017](https://qiita.com/advent-calendar/2017/eeic2)の7日目の記事です。

# はじめに
## シェル芸とは
シェルなどで１行コードを書いて処理を終えることをワンライナーと言ったりします。
シェル芸については深くは知らないのですが、おそらくワンライナーで面白いことをするもの、という認識で良いのでしょう。
これはシェル芸の入門記事ではないので、シェル芸に興味のある人は自分で色々調べてみてください。

## Data Augmentation とは
機械学習の分野で学習に使うデータが少ない時にデータを水増しすることをData Augmentationと言います。
特に画像処理の分野だと、画像を左右反転させたり一部分をクロップしたり回転させたりしてデータを増やすことがよく行われるそうです。
これによって過学習を抑制できるし万々歳らしいです。

## この記事は
つまり、Data Augmentationでシェル芸をしようという試みです。
大学でディープラーニングで何かやってみようという実験があり、その時の知見をまとめておこうということです。ちなみに大学ではシェル芸の実験はやってないです。

Data AugmentationはPythonでやればいい、とか前もって処理せずに実行時に画像処理すればいい、というツッコミがありそうですが、まあシェルの芸だと思って聞いてください。

# やってみる
## ImageMagickのconvertコマンド
ImageMagickのconvertコマンドがLinuxだとだいたい使えて、コマンドラインから画像処理ができます。
一番良く使われるであろう使い道はフォーマットの変換で、

```bash
$ convert hoge.jpg hoge.png
```

とやるとjpgからpngに変換してくれたりしてとても便利です。
他にも色々なことができて、[ImageMagick コマンドリファレンス](http://image-magick.com)に使い方がよくまとまっています。

## フォーマットの変換
先程も紹介しましたが、convertコマンドでフォーマットの変換が簡単にできます。
一括で変換したいような場合はmogrifyコマンドを使うことができ、

```bash
$ mogrify -format png *.jpg
```

とすれば任意のjpgファイルをpngに変換できます。
宗教上の理由か何かでconvertしか使えなくなってしまった人は

```bash
$ ls *.jpg | sed 's/\.jpg$//g' | xargs -I FILE convert FILE.jpg FILE.png
```

などとすれば変換できます。
やっていることとしては、

* `ls *.jpg` : 任意のjpgファイルの名前を出力
* `sed 's/\.jpg$//g'` : `.jpg`で終わる部分を削除（"hoge.jpg"->"hoge"になる)
* `xargs -I FILE convert FILE.jpg FILE.png` : パイプで渡された引数をFILEと名前をつけて、それぞれに対して`convert FILE.jpg FILE.png`を実行

こんな感じです。xargsは以下でもまた出てきます。

## 画像のフリップ（左右反転）
画像の左右反転を行うことをフリップって言ったりしますが、ImageMagickでは左右反転はflopなので注意が必要です。flipしようとすると上下反転されて出てきます。

```bash
$ mogrify -flop *.png
```
とすれば任意のpng画像をフリップできるのですが、mogrifyを使うと元画像が上書きされてしまいます。
しかしこれではデータがいつまで経っても増えないので、convertを使います。

```bash
$ ls *.png | xargs -I FILE convert -flop FILE flip_FILE
```

などとすれば"hoge.png"をフリップして"filp_hoge.png"の名前で保存することができます。

## リサイズ
これも同様にして

```bash
$ ls *.png | xargs -I FILE convert -resize 64x64! FILE resize_FILE
```

とすれば64x64の画像に変換できます。
"64x64!"に"!"が付いているのは、比を無視して64x64にリサイズするためです。
"!"をつけないと縦横の小さい方の長さが64になるように比を維持して変換されます。

## クロップ（切り抜き）

Data Augmentationでは窓の位置を少しずつずらしながらクロップしていくことが有効です。
座標(1,1)から(9,9)までを左上の座標とし、64x64にクロップするようなコマンドはこうなります。

```bash
$ ls *.png | xargs -I FILE bash -c 'for i in {1..9}; do for j in {1..9}; do convert -crop 64x64+${i}+${j} FILE crop_${i}_${j}_FILE; done; done'
```

いよいよ楽しくなってきた感じがあります。
少し見にくいので改行しましょう。

```bash
$ ls *.png | xargs -I FILE bash -c '
    for i in {1..9}; do
        for j in {1..9}; do
            convert -crop 64x64+${i}+${j} FILE crop_${i}_${j}_FILE;
        done;
    done'
```

xargsに`bash -c 'コマンド'`を渡しているのは、xargsではそのままだと複数コマンドが実行できないためです。
`bash -c 'コマンド'`は指定した'コマンド'を実行する１つのコマンドとして処理されるので、xargsでパイプで繋がれた処理を行いたい時にもよくこういう書き方をします。

`bash -c`で渡したコマンドについてはforループを2重にしてcropの処理を行っている、というだけです。
`64x64+${i}+${j}`は、`(${i},${j})`を左上として64x64の大きさでクロップをするという指定です。

注意が必要なのは、これはシングルクォーテーション(')で囲まなければならないという点です。
ダブルクォーテーション(")で囲んだ場合は挙動が異なり、`${i}`などの変数が置き換わって渡されてしまいます。
今回の場合では、実行前に変数`${i}`は未定義のはずなので、`${i}`の記述が虚無に書き換わって`bash -c`に渡されてしまい、正しい挙動となりません。

# xargs -P で並列化

ここまで`ls`で表示して`xargs`に渡す、ということをしてきましたが、これの利点として並列化が容易なことが挙げられます。
xargsには`-P`オプションがあり、並列に実行する最大数を指定できます。
8スレッドのマシンの場合、

```bash
$ ls *.png | xargs -P 8 -I FILE convert -flop FILE flip_FILE
```

としてフリップすると理想的には8倍の速度でフリップが進みます。
このようにして手軽に並列化ができるのもシェルの魅力であると言えます。

# おわりに
シェル芸は遊び的な側面も強いですが、芸は身を助けると言うように思わぬところで役に立ったりします。
はたしてシェル芸でData Augmentationをすることが役に立つことなのかどうかは分かりませんが、xargsの使い方などを知っておけばきっとどこかで役に立つでしょう。

もっと良い書き方あるよ〜とかあったらぜひ教えてください。