Deep Learning初心者がKerasを使って気象衛星画像から天気予報をやってみた

# はじめに
Deep Learning初心者が天気予報にチャレンジしてみました。天気予報という題材を選んだ理由は、(1)気象衛星「ひまわり」の画像が利用可能であり、データ取得の負担が少ない、(2)問題設定が単純、などの理由から初心者が取り組みやすい題材だと思ったからです。

Deep LearningのライブラリはKeras + (Tensorflow)を選んでみました。世の中には様々なDeep Learningのライブラリがあふれていますが、Web上にあるサンプルコードを眺めているとKerasを使うのが最も簡単そうに思えたのでKerasでやってみることにしました。

# 環境設定
私が普段使っているPCにはWindows 7が入っています。まずはPythonを使えるようにするために[Anaconda3](https://www.continuum.io/downloads)を導入しました。

Anaconda3の導入が終わると「すべてのプログラム」➔「Anaconda3」から「Anaconda Prompt」を立ち上げてPythonを実行できるようになります。2017年5月24日時点だと、デフォルト環境ではPython3.6が使えるようになっているようです。現時点でTensorflowはPython3.6には対応していないようなので、まずはAnaconda PromptでPython3.5を使えるように設定します。

```shell-session
(C:\Users\IBM_ADMIN\Anaconda3) C:\Users\IBM_ADMIN>conda create -n py35 python=3.5 anaconda

(C:\Users\IBM_ADMIN\Anaconda3) C:\Users\IBM_ADMIN>activate py35

(py35) C:\Users\IBM_ADMIN>python --version
Python 3.5.2 :: Anaconda 4.3.1 (64-bit)
```
これでPython3.5が使えるようになりました。次に、KerasとTensorflowを導入します。

```shell-session
(py35) C:\Users\IBM_ADMIN>pip install tensorflow
```

```shell-session
(py35) C:\Users\IBM_ADMIN>pip install keras
```

Kerasのデフォルトの設定ではバックエンドがtensorflowになっていないので、Anaconda Promptのルートディレクトリにある「.keras/keras.json」の「backend」の値を下のように書き換えます。

```json
{
    "floatx": "float32",
    "image_data_format": "channels_last",
    "backend": "tensorflow",
    "epsilon": 1e-07
}
```

以上で、Kerasを使う準備は完了です。

# データの準備
気象衛星「ひまわり」の画像は[こちら](http://himawari8.nict.go.jp/ja/himawari8-image.htm)に公開されており、ダウンロード可能です。2015年7月からの現在までの気象衛星画像が利用可能となっています。

画像を入力として天気予報を行う場合、日中の画像でないと真っ暗な画像になってしまい、天気予報が成立しません。また、気象庁からは0時から3時間毎の天気の観測データが[公開](http://www.data.jma.go.jp/risk/obsdl/index.php)されています。そこで、私は9, 12, 15時の画像を各季節から万遍なくダウンロードして、学習データとして1095枚、テストデータとして90枚の画像を取得しました。例えば、ある日の日本列島の気象衛星画像は下のようになっています。
![hima820170617120000jp.png](https://qiita-image-store.s3.amazonaws.com/0/16430/06a0b60e-bbe9-d52d-3351-21bbe5dd48b0.png)


次に、学習データセットとテストデータセットを作成します。気象衛星の画像を利用して天気予報を行うには、ある時点の画像を入力したときに未来の天気を出力するようなアプリケーションを作る必要があります。そこで、私はある時点に日本列島を撮影した気象衛星画像を与えられたときに、24時間後の東京の天気を予想するようなアプリケーションを作ることに決め、各日時の気象衛星画像に対してその24時間後の天気の観測データをラベルとして与えることにしました。また、気象衛星画像から「曇」と「雨」を判別するのは難しいと考え、画像から「晴れ」と「それ以外」を予想することにしました。

仮に、2017年4月1日の9時からの気象衛星画像が得られているとします。これに対して、私が準備したラベルは下のようなフォーマットのCSVファイルです。「1.0」が晴れ、「0.0」がそれ以外の天気を表しています。

```text
年月日時,東京の天気
2017/4/2 9:00,1.0
2017/4/2 12:00,1.0
2017/4/2 15:00,1.0
2017/4/3 9:00,0.0
2017/4/3 12:00,0.0
```

このように学習データその正解ラベル、およびテストデータとその正解ラベルを準備したあと、下のようなプログラムを使って学習データセットとテストデータセットを作成しました。このプログラムを実行すると、学習データセットとテストデータセットが「data.pkl.gz」という名前のファイルとして学習データの正解ラベルを保存したディレクトリの下に作成されます。

```py3
import os
import numpy
import pandas
import pickle
from PIL import Image

train_data_src_dir = "学習データを保存したディレクトリの絶対パス"
train_label_csv = "CSV形式の学習データの正解ラベルの絶対パス"
test_data_src_dir = "テストデータを保存したディレクトリの絶対パス"
test_label_csv = "CSV形式のテストデータの正解ラベルの絶対パス"

def img2nparray(file):
    img = Image.open(file, "r")
    array = numpy.asarray(img, dtype="uint8")
    array = array.reshape(array.shape[0], array.shape[1], 1)
    return array

def get_label_set(file):
    labels = pandas.read_csv(file, encoding="shift-jis")
    labels = labels[labels["東京の天気"].notnull()]
    
    return labels["東京の天気"].as_matrix()
    

def generate_dataset():
    print("Generating train data set")
    master_dataset = []
    files = os.listdir(train_data_src_dir)
    for file in files:
        master_dataset.append(img2nparray(train_data_src_dir + file))
        
    master_dataset = numpy.array(master_dataset)
    train_label_set = get_label_set(train_label_csv)
    train_set = master_dataset, train_label_set
    
    print("Generating test data set")
    test_dataset = []
    files = os.listdir(test_data_src_dir)
    for file in files:
        test_dataset.append(img2nparray(test_data_src_dir + file))
        
    test_dataset = numpy.array(test_dataset)
    test_label_set = get_label_set(test_label_csv)
    test_set = test_dataset, test_label_set
    
    return (master_dataset, train_label_set), (test_dataset, test_label_set)

if __name__ == '__main__':
    dataset = generate_dataset()
    
    print("Creating pickle file")
    f = open(os.path.dirname(train_label_csv) + os.sep + 'data.pkl.gz', 'wb')
    binary = pickle.dump(dataset, f, protocol=2)
    f.close()
    print("Created")
```

以上で、データの準備は終了です。

# 気象衛星画像による天気予報の実践
では、準備したデータを使って天気予報を実行してみましょう。入力データが画像であり、手軽にDeep Learningを試してみたかったため、学習と天気用法は[こちら](https://github.com/fchollet/keras/blob/master/examples/mnist_cnn.py)の手書き文字判別用のプログラムをほぼそのまま利用させていただきました。一部、画像のサイズは私が用意したデータに合わせて変えています。また、「晴れ」と「それ以外」の二値の予想になるようにクラスの数を2に設定しました。さらに、MNISTのデータセットではなく私が作成したデータセットを読み込むような修正も加えています。

各季節から万遍なく学習データを取得し、上記リンク先のプログラムを使って学習させ、2017年4月1日から30日までの9, 12, 15時のテスト画像に対して天気予報を実践してみたところ、正答率は以下のように約65.5%となりました。

```text
1095 train samples
90 test samples
Train on 1095 samples, validate on 90 samples
Epoch 1/12
1095/1095 [==============================] - 112s - loss: 1.8521 - acc: 0.4922 - val_loss: 0.6720 - val_acc: 0.6000
Epoch 2/12
1095/1095 [==============================] - 110s - loss: 0.6840 - acc: 0.5096 - val_loss: 0.6604 - val_acc: 0.6000
Epoch 3/12
1095/1095 [==============================] - 109s - loss: 0.6809 - acc: 0.5187 - val_loss: 0.6559 - val_acc: 0.6000
Epoch 4/12
1095/1095 [==============================] - 107s - loss: 0.6748 - acc: 0.5187 - val_loss: 0.6839 - val_acc: 0.7222
Epoch 5/12
1095/1095 [==============================] - 112s - loss: 0.6731 - acc: 0.5836 - val_loss: 0.6461 - val_acc: 0.6000
Epoch 6/12
1095/1095 [==============================] - 108s - loss: 0.6705 - acc: 0.5845 - val_loss: 0.6390 - val_acc: 0.6000
Epoch 7/12
1095/1095 [==============================] - 108s - loss: 0.6705 - acc: 0.5817 - val_loss: 0.6478 - val_acc: 0.6000
Epoch 8/12
1095/1095 [==============================] - 110s - loss: 0.6646 - acc: 0.6027 - val_loss: 0.6317 - val_acc: 0.6000
Epoch 9/12
1095/1095 [==============================] - 113s - loss: 0.6744 - acc: 0.5863 - val_loss: 0.6483 - val_acc: 0.7778
Epoch 10/12
1095/1095 [==============================] - 118s - loss: 0.6617 - acc: 0.6192 - val_loss: 0.6183 - val_acc: 0.6222
Epoch 11/12
1095/1095 [==============================] - 114s - loss: 0.6630 - acc: 0.6046 - val_loss: 0.6149 - val_acc: 0.6222
Epoch 12/12
1095/1095 [==============================] - 104s - loss: 0.6549 - acc: 0.6046 - val_loss: 0.6170 - val_acc: 0.6556
Test loss: 0.616963325606
Test accuracy: 0.655555557542
```

# おわりに
今回は、Deep Learning初心者がDeep Learningの実行環境を用意して天気予報を試してみるところまでをまとめてみました。正答率は65.5%という微妙な結果であり、改善の余地がありそうです。たとえば、24時間前の画像だけを入力とするのではなく、より直近の画像も入力として与えることで、より精度を高められるかも知れません。

気象衛星画像を利用した天気予報は[他の方も実践済み](http://qiita.com/wbh/items/1984b582071ef60c92f3)なので、今後はこれらの情報から正答率改善のヒントを得たいと思います。