pyfastnoisesimd を tf.data.Dataset 化して procedural noise でオーグメンテーションしたい

この記事は[創作+機械学習 Advent Calendar 2021](https://adventar.org/calendars/6442)の13日目に参加しているものです。

[コードはGistにアップロードしてあります](https://gist.github.com/YusukeSuzuki/6cd6a2703e3287d2462f572dcbac55e3)。

# 注記

- 厳密な理論に基づいた記事ではありません
- まともな実験すらできてません

# 何をするのか、なぜするのか

procedural noiseと呼ばれるノイズ画像のジャンルがありそれを無限に生成するデータセットを作ってオーグメンテーションに利用します。procedural noiseの生成にはpyfastnoisesimeライブラリを用い、それを tf.data.Dataset　でラップします。TensorFlowを使っていますがよく知られている通りPythonジェネレータとして扱えるためPyTorchでもJax/Flaxでも利用することができます。

発想としてはオーグメンテーションでuniformなりnormalなりのノイズを画像に加算するというのは伝統的に用いられるけれどもうちょっと形状を持ったノイズを乗せた方が性能がでるんじゃないかなというところです。mixup augmentationや[フラクタルでのプリトレーニング](https://hirokatsukataoka16.github.io/Pretraining-without-Natural-Images/)とかそこらへんの発想に近いです。また[PASS: Pictures without humAns for Self-Supervised Pretraining](https://github.com/yukimasano/PASS)も近いかなと思います。

「ターゲット（学習したいもの）が写っていないことを保証できるデータセットがあればオーグメンテーションに使い放題」みたいな感覚です。

普段は趣味でグラビア画像の生成モデルを作る遊びをしています（今年はちょっと停滞気味ですが）。celebaやfashion-mnistのように構図が整列されたデータではなくあーんなポーズやこーんなポーズが写っています。また風景や室内写真とことなりちょっとしたバランスの崩れで見た目の違和感が際立ちます。難易度の高いテーマだと考えています。まあ趣味なので半年以上取り組みが開いたりするんですが。

ここにおいてオーグメンテーションが結構重要で形状が豊富なノイズを入れたいなと思って作り始めました。

# procedural noise

[procedural texture](https://en.wikipedia.org/wiki/Procedural_texture)とも言われるようですしWikipediaだとこちらの語での解説しかありません。疑似乱数から諸々の数式を通してなんか形状があるけれどランダムな画像を生成する手法と理解しています。Perlin noiseという手法が代表のようですがフラクタルを利用したものとかいろんな手法があります。クリエイティブ領域でテクスチャを作ったりゲームのランダム地形生成に利用されたりするようです。PhotoshopやGimpにもフィルタとして実装されています。

実際のところ詳しいことを把握しきれないまま利用しています。

言葉だけだとわかりづらいですね。次に紹介するFastNoiseSIMDのテストツールの出力例を挙げておきます。

![FastNoise SIMD Preview1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25288/77f29dad-fe44-4d6b-d5af-0e4b394a6d53.png)


![FastNoise SIMD Preview2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25288/1e10fd26-8e10-dc70-94e9-39e7be2803e9.png)

# FastNoiseSIMD, pyfastnoisesimd

procedual noiseの生成は結構計算量が必要で手法の一つであるpink noiseのサンプルコードをPythonで書いてみたところちょっと使える速度とは言えない感じでした。

SIMDをバリバリ使った[FastNoiseSIMD](https://github.com/Auburn/FastNoiseSIMD)というライブラリが存在し、それのPythonパッケージ[pyfastnoisesimd](https://github.com/robbmcleod/pyfastnoisesimd)もあります（pipでインストールできます）。最適化が売りなだけあって実用的な速度で画像を生成してくれるのでこれを tf.data.Dataset でラップすることにしました。

ちなみにFastNoiseSIMDは現在はdeplecatedになっていて後継の[FastNoise2](https://github.com/Auburn/FastNoise2/)が出ています。pyfastnoisesimdがどこまで取り込んでいるかは把握できていません。

# tf.data.Dataset によるラップ

元々は地形生成などに使われるため1ch画像を出力するライブラリですが今回はカラー画像が欲しいのであれやこれやして3chにしています。

詳しくは[Gistにあるコード](https://gist.github.com/YusukeSuzuki/6cd6a2703e3287d2462f572dcbac55e3)を参照してください。

コードを1行ずつ解説するのが苦手なためポイントだけ。

pyfastnoisesimdの利用

- カラー画像を生成するため1つのパラメータで3倍のサイズのノイズ画像を生成してから3等分にして重ね合わせています
- ノイズ生成手法のランダム選択やパラメータのランダム指定はかなり試行錯誤しています。組み合わせによっては単一色ベタ塗りの画像が出てきたりします。

tf.data.Dataset化

- 当初はランダムシード値をルートのデータセットにして `Dataset.map` で画像を生成しようと思ったのですがうまくいきませんでした。 `Dataset.map` 内でtf以外の操作をするのが難しい。また `Dataset.map` の関数は状態を持てないのも不利です。
- そのため `Dataset.from_generator` を使うことになるのですがpyfastnoisesimdがマルチスレッドではないため単一のジェネレーターだと学習ループに間に合う生成速度が得られませんでした。
- そこで複数のジェネレータを `Dataset.from_generator` に通して複数のDatasetを作り次に `Dataset.zip` でまとめることでマルチスレッド動作をするようにしています。
- この時にジェネレータ内部で乱数シード値を決めようとすると全てのジェネレータで同じシード値になってしまうため外部から渡すようにしています。

ここらへんの工夫を経て [0., 1.] の3ch画像を生成するデータセットができました。

# 生成画像の例

目的がグラビア画像なので縦長で出しています。ボチボチいい感じで形状と多様性が出てます（そんな気がします（たぶん））。

![out_0000_05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25288/db48c2e9-324f-ddb8-e5b4-60bc1a4d5ed7.jpeg)

![out_0000_10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25288/c888757e-730c-10d4-7270-4ec403e5a5d0.jpeg)

![out_0000_00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25288/5fa2f310-c3ec-ea8d-10f5-f1a33d8423da.jpeg)

# 課題

- 上で書いた通り手法・パラメータの乱数の範囲は試行錯誤で決めています。ライブラリの作例を見るともっといろいろなノイズ画像を生成できるようなのでより多様でランダムな画像を生成できるパラメータ犯意を探っていく必要があります。
- 今回の手法に限ったことではないのですが「たまたまターゲットに見える画像が生成されちゃったらどうするの」みたいな疑問はこれ系ではいつも浮かびますね。
- あとは根本的に効果の測定をまじめにやらないと…

# そんで効果は？

きちんとした数値をだして実験はしていないのですがGANのオーグメンテーションに導入したところ学習が生成画像がひとつに収束してしまうのを回避するのが容易になった感触はあります。これからもっと試していきます。