LoginSignup
2
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-05

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

前回(第3回)では気象データをもとに「天気図っぽい前線」を機械学習で描いてみる(3) として、教師画像を作成するために「速報天気図」から前線要素を切り抜く話を投稿しました。

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

白黒版「速報天気図」のカラー化

なぜカラー化か

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

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

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

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

 (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を用いていますので、ピクセル値として教師画像に寄せるような学習です。

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

出力となる教師画像はこんなもの。
SPAS_COLOR_201809302100.v8.512.png

学習結果

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

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

out2w_SPAS_COLOR_2018093021.v8.512.png

予測結果

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

spas_cmp_2017070109.png

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

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

EF883BE9-9D9F-431C-A83A-8550432756A3.jpeg

遊び

手書きでイタズラしてみました(笑)。
こちらは私の作成。
25DE47A0-A84F-4793-970F-A6AD4989BF5A.jpeg

子供にも描かせてみました。
AD1C083D-A87A-4DED-9FDB-58FDF19D4A13.jpeg

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

まとめ

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

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

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3