気象データをもとに「天気図っぽい前線」を機械学習で描いてみる（4）

#気象データをもとに「天気図っぽい前線」を機械学習で描いてみる（4）　白黒天気図をカラー化してみた

前回（第3回）では[気象データをもとに「天気図っぽい前線」を機械学習で描いてみる（3）](https://qiita.com/m-taque/items/4d5bb45e6b5dc42dc833)  として、教師画像を作成するために「速報天気図」から前線要素を切り抜く話を投稿しました。

今回の話は、（まったく個人的事情ですが）前線要素を切り抜くためのカラー版「速報天気図」の入手可能な対象期間が少なかったため、従来から蓄積していた白黒版「速報天気図」をカラー化してみた、というものです。

## 白黒版「速報天気図」のカラー化
### なぜカラー化か

最近、白黒写真やフィルムを機械学習でカラー化するという話を聞きます。
過去の、どこか現実感がなかった戦時中の映像など、圧倒的に現実感が増して、同時代感まで匂わせて迫ってくるように感じます。

今回のカラー化はそういう話とは全く関係なく、前線要素抽出のアルゴリズム事情だけです。

白黒版の天気図ですと、当然のことながら、前線と、経度緯度線・地図線・等圧線すべてが同じ黒色です。
ですから、前線だけそこから切り出すうまい方法が思いつかず、カラー化する必要が生じました。

というか、ホントはやったことを時系列でいうと、

　(1) 出力画像として、前線要素を切り出すとかせず、速報天気図そのものを教師画像として学習するニューラルネットワークを最初に作った。うまく前線が学習されなかった。
　(2) 学習対象を前線に絞ろうと考えて、前線要素を抜き出す方法を考えていた時に、カラー版「速報天気図」の存在に気づいた。
　(3) 入手可能なカラー版「速報天気図」をダウンロードし、色をベースに前線画像を抜き出すことができた。
　(4) 前線だけとした教師画像で学習させたところ、ある程度うまく行きそうとわかったので、もっと天気図例を増やそうと思った。この時点ではカラー版は半年分くらいしか入手できなかった。
　(5) このころ作っていたニューラルネットワークは画像から画像を生成するものなので、ひょっとしたら白黒天気図からカラー天気図に変換することもできるのでは？と思いいたった。

というものでした。
画像から画像のニューラルネットワークがある程度動き始めていたので、応用できるかな、と思ったのでした。

結果として、これは思った以上にうまくいきました。

半年ほどしかなかった教師画像を、いっきに2年分以上に増やすことができました。
春夏秋冬を2まわり分得られることで、教師画像のバリエーションも増えました。






## 作成したニューラルネットワーク
### ハードウェア、ソフトウェア環境

Mac mini(2018)
　プロセッサ　3.2GHz 6コア Intel Core i7
　メモリ　　　32GB 2667 MHz DDR4


OS macOS Catalina
python3.7
Keras 2.0


を用いております。

### CNNの構造
作成していたネットワークはかなりシンプルな構造です。Sequential構造で、1チャネルのグレースケール画像を、Conv2Dによって特徴量を抽出して、最下層で4段のCNNを通した後に、Conv2DTransposeによって戻しながら3チャネルのカラー画像にするというネットワークです。

入力画像は256x256ですが、Conv2Dのstridesを4回かけて小さくし、16x16 にした後でさらにConv2Dを4回通し、
その後Conv2DTransposeによって元のサイズに戻すというもの。

学習は`mean_squared_error`を用いていますので、ピクセル値として教師画像に寄せるような学習です。


```Python:network.py

# parameter settings

num_hidden1 = 32
num_hidden2 = 64
num_hidden3 = 128
num_hidden4 = 64
num_hidden5 = 32
num_hidden6 = 32

######### start of network definition
        
NN_1 = Sequential()

#--- encode start

NN_1.add(Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', input_shape=(in_ch, i_dmlat, i_dmlon), padding='same'))
NN_1.add(Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), activation='relu', input_shape=(in_ch, i_dmlat, i_dmlon), padding='same'))
NN_1.add(Conv2D(num_hidden1, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', input_shape=(in_ch, i_dmlat, i_dmlon), padding='same'))

NN_1.add(Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden2, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))

#--- encode out
NN_1.add(Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))

#--- decode start

NN_1.add(Conv2DTranspose(num_hidden4, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))
NN_1.add(Conv2DTranspose(num_hidden4, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))

NN_1.add(Conv2DTranspose(num_hidden5, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))
NN_1.add(Conv2DTranspose(num_hidden5, data_format='channels_first', kernel_size=(3,3), strides=(2,2), activation='relu', padding='same'))

NN_1.add(Conv2D(num_hidden5, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))
NN_1.add(Conv2D(num_hidden6, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))

#--- back to 3 channel

NN_1.add(Conv2D(3, data_format='channels_first', kernel_size=(3,3), activation='relu', padding='same'))

####### end of network definition

# compile network
NN_1.compile(optimizer='adam', loss='mean_squared_error' , metrics=['accuracy'])

# do training
NN_1.fit(np_i_data_train_tr, np_t_data_train_tr, epochs=num_itter , callbacks=cbks, batch_size=8 , validation_split=0.2 )

# Save model and weights
json_string = NN_1.to_json()
open(os.path.join(paramfiledir, 'cnnSPAStoColSPAS2_011_model.json'),'w').write(json_string)

NN_1.save_weights(os.path.join(paramfiledir, 'cnnSPAStoColSPAS2_011_weight.hdf5'))

```


## 学習と変換

### 入力と出力

入力画像は例えばこんなもの（速報天気図 2018/9/30 21UTC)。
![spas.u.2018093021.v8.512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/bc6ae2e4-d258-4a8c-25b5-b0f32003514d.png)

出力となる教師画像はこんなもの。
![SPAS_COLOR_201809302100.v8.512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/9d366cfb-7244-ce9d-3b8e-89cf3589f5e6.png)



### 学習結果

半年分のデータのうち、２ヶ月分を評価用にしようとして、およそ4ヶ月分（１日に６枚あるので、700枚強）を用いて白黒版とカラー版を学習させました。

学習データはわりと早いうちにこのような画像に収束するようになります。
地図や経度緯度線、日付はあっという間に過学習していくのでしょう。

![out2w_SPAS_COLOR_2018093021.v8.512.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/b2ef794a-8f00-622a-b4c0-31b7800a7675.png)

### 予測結果

学習したネットワークを用いて、実際の白黒天気図をカラー化してみました。
右が元となった白黒天気図、左がカラー化された天気図です。
色をベースに前線要素を切り抜く目的としては、十分なカラー化だと思います。

![spas_cmp_2017070109.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/3bc37a37-c6bb-763e-8a28-df9e6ed4b581.png)

細かい話ですが、左上の年月日時刻が記載されているボックスも画像として学習・予測した結果です。
天気図の中に出てくる数値（気圧とか、移動速度）は素直に予測しているように見えるのに、
「平成29年7月」はガンとして「平成30年1月」としか予測していません。
学習に用いているデータが平成30年以降のもので、かつ7月が含まれていないので、
「入力にないものは予測できません」
といっている感じです。

同じことは代変わりの令和の文字列についても、ガンとして平成と変換していました。
ここは何が来ても平成になるように、重みはゼロでバイアスだけ残ったような学習になったのでしょう。

![EF883BE9-9D9F-431C-A83A-8550432756A3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/700dd7c3-56cd-7eb6-28db-9b7c6f090548.jpeg)


### 遊び

手書きでイタズラしてみました（笑）。
こちらは私の作成。
![25DE47A0-A84F-4793-970F-A6AD4989BF5A.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/baab8eb1-2ad2-a407-d824-f6290f91b774.jpeg)

子供にも描かせてみました。
![AD1C083D-A87A-4DED-9FDB-58FDF19D4A13.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411112/b3fd18ce-ae54-acf4-b7af-ef26344755b9.jpeg)

前線記号の手書きは私の方が一日の長があるようです。


## まとめ

今回は白黒天気図をカラー化するという話についてまとめました。これにより前線切り抜き画像が大幅に増え、最終目的の「天気図っぽい前線」の描画学習の精度が上がっていきました。

次回は最終回として、前線描画のニューラルネットワーク[気象データをもとに「天気図っぽい前線」を機械学習で描いてみる（5）機械学習編 Automatic Front Detection in Weather Data](https://qiita.com/m-taque/items/2788f623365418db4078)について投稿予定です。
2020.2.9現在 Qiitaサイトへの画像投稿容量の月間制限に達してしまって最終回は3月に持ち越します。