工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

天気図画像データから、24時間以内の降雨の有無を予測する

概要

天気図画像データから、24時間以内の降雨の有無を予測してみた。



背景と目的

ある事情で、ディープラーニングを使ったモデルを作成する必要が出た。テーマは、データが集めやすそうな天気関連のものとして、記事タイトルにある内容で行うこととした。画像データから降雨に関わる重要な特徴を学習してうまく予測するモデルが作れるか試す。



詳細

1. 実施内容

予測対象地点は、さいたま市とした。


2. データの収集

  • 天気図データは、tenki.jpの過去の天気図データを用いる。2021年1月1日~2021年12月20日までの約1年分とする。Webスクレイピング等により収集した。3時間ごとに1枚の天気図がある。(ただし、0時はない)
https://tenki.jp/guide/chart/
  • 予測対象の降雨の有無は、さいたま市の過去のデータを以下の気象庁Webサイトからダウンロードした。
https://www.data.jma.go.jp/obd/stats/etrn/index.php


3. データセットの作成

3.1 天気図データ

今回使用する天気図データは、

  • 太平洋やロシアなど広範囲をカバーする
  • 地形や緯線、経線も含まれている

となっているが、

  • 一般的に、予測対象地点から大きく離れる場所や東側の大部分はあまり予測に重要でなさそうである
  • 同じ画角で切り出したとすると、予測対象地点は常に画像中の同じ位置だから、画像中の地形や緯線や経線は予測と無関係であり、天気図記号の位置関係のみが予測に関係する

と考えられた。そこで、

  • すべての画像から平均画像を作成し、各画像から減算して地形や緯度経度の線地形を消去
  • 予測対象地点がおおむね中心となるように日本付近を160 * 160pixelで切り出す
  • RGBチャンネルを分解してコントラストなどを調整
    • 青は寒冷前線マークの検出
    • 緑は天気図記号抽出がしにくいため不使用
    • 赤は等圧線、高気圧/低気圧マーク、温暖前線の検出
  • 青チャンネルと赤チャンネルを加算
  • 計算量削減のため天気図記号が認識可能な程度の80 * 80pixelに圧縮

といった方法で、画像を約2500枚作成した。すなわち、80pixel * 80pixel * 1channelのcreatedチャンネルが最終的に使用される画像である。なお、さらっと書いているが、実は天気図記号を消さずに地形や緯度経度の線を消すには結構試行錯誤した。データセット作成で苦労するのはよくある話だが。

f:id:dekuo-03:20220117233701p:plain

参考: 平均画像

f:id:dekuo-03:20220117231659j:plain


3.2 降雨データ

降雨データは、天気図の時刻を起点に、24時間以内の合計降雨量が1mmでもあれば降雨あり、0mmなら降雨なしとして、2次元ワンホットベクトル化した。データ全体における降雨なし/降雨ありの比率はおよそ68%である。


4. モデルの作成

畳み込み層と全結合層を組み合わせたニューラルネットワークとした。実装は、tensorflowを用いた。 層の深さ、チャンネル数、フィルタサイズ、正則化パラメータ等は、かなり試行錯誤して決めた。フィルタサイズやプーリングの有無は、天気図記号のピクセル数等を考慮した結果である。正則化は強すぎると学習しないし、弱ければ意味がない。

input = Input(shape=(H, W, 1), name="sma_input1")

x = Conv2D(64, 5, padding="same", activation="relu")(input)

x = MaxPooling2D()(x)

x = Conv2D(192, 3, padding="same", activation="relu", kernel_regularizer=regularizers.l2(0.005))(x)

x = MaxPooling2D()(x)

x = Conv2D(192, 3, padding="same", activation="relu", kernel_regularizer=regularizers.l2(0.005))(x)

x = MaxPooling2D(pool_size=(5, 5))(x)

x = Flatten()(x)

x = Dropout(rate=0.5)(x)

output = Dense(128, activation="softmax")(x)

x = Dropout(rate=0.5)(x)

output = Dense(2, activation="softmax")(x)

model = Model(inputs=input, outputs=output)

model.compile(
    loss="categorical_crossentropy", 
    optimizer='Adam',
    metrics=["accuracy"]
)

model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sma_input1 (InputLayer)      [(None, 80, 80, 1)]       0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 80, 80, 64)        1664      
_________________________________________________________________
max_pooling2d_9 (MaxPooling2 (None, 40, 40, 64)        0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 40, 40, 192)       110784    
_________________________________________________________________
max_pooling2d_10 (MaxPooling (None, 20, 20, 192)       0         
_________________________________________________________________
conv2d_11 (Conv2D)           (None, 20, 20, 192)       331968    
_________________________________________________________________
max_pooling2d_11 (MaxPooling (None, 4, 4, 192)         0         
_________________________________________________________________
flatten_3 (Flatten)          (None, 3072)              0         
_________________________________________________________________
dropout_6 (Dropout)          (None, 3072)              0         
_________________________________________________________________
dropout_7 (Dropout)          (None, 3072)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 2)                 6146
=================================================================
Total params: 450,562
Trainable params: 450,562
Non-trainable params: 0
_________________________________________________________________


5. 学習と評価

5.1 学習

  • ホールドアウト法で学習/検証/テストを行う
    • データセット全体の順序をシャッフル
    • 全体を0.75:0.25に分割し、学習/検証用とテスト用とした
    • 学習/検証用を0.75:0.25に分割し、学習用と検証用とした
  • バッチサイズは128
  • エポック数は60

以下が、60エポック完了後の損失と精度。精度は80%台前半。40-50エポックあたりで、検証データに対するロスが下がらなくなっているので、60エポックは少しやりすぎ。

f:id:dekuo-03:20220117233648p:plain


5.2 テストデータによる評価

検証精度とほぼ同様の81%となった。


6. 考察と改善案

6.1 考察

精度は80%台前半で、データ全体における降雨なし/降雨ありの比率はおよそ68%なので、このモデルは天気図から降雨に関連する何らかの特徴をある程度認識できていると考えられる。


6.2 改善案

今回使用したものは、基本的なCNNを使ったネットワーク構成なので、より発展的なネットワーク要素を取り入れることで、精度の向上が図れるのではないかと考える。また、入力がある時点の1枚の画像であるが、天気は時間経過とともに変化することを考えると、時系列性を考慮することも有効と考えられる。具体的には、

  • (案1)ResNetやDenseNetのような残差接続を導入する
  • (案2)時間的に並んだ3枚の天気図をまとめ、3チャンネルの入力データとする

などがある。時間があったらやってみたい。



まとめと今後の課題

天気図画像データから、24時間以内の降雨の有無を予測することにトライしてみた。