第7回 時系列データの予測を行う深層学習(RNN)を作成してみよう(TensorFlow編):TensorFlow入門(1/2 ページ)
ディープラーニングの代表的手法「RNN」により時系列データの予測を行う機械学習モデルを構築してみる。RNNによる深層学習がどのようなものか体験しよう。
ご注意:本記事は、@IT/Deep Insider編集部(デジタルアドバンテージ社)が「deepinsider.jp」というサイトから、内容を改変することなく、そのまま「@IT」へと転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。
前回はRNNの概要を説明した。今回のベースとなる知識なので、本稿で作業を始める前に一読してほしい。今回はRNNを使って深層学習を試してみる。本稿のPythonコードは、Jupyter Notebook上で実行すればよい。
RNNの学習
モデル
今回は、tf.nn.rnn_cell.BasicRNNCellクラスを用いて、単純なRNNのモデルを作成する。
tf.nn.rnn_cell.BasicRNNCellクラスは、セルの内部構造が単層で全結合のニューラルネットワークを表すクラスである。インスタンス化の際にニューロンの数をnum_units引数に、活性化関数をactivation引数に指定すればよい。activation引数は省略した場合は、tanh関数という-1〜1の範囲をとる関数がデフォルトで設定される。特に理由がなければtanh関数のままでよい。この構造を表すコードについては後述する。
データセットの準備
データはUCI Machine Learning Repository*1よりAir Quality Data Set*2(センサーデバイスにより収集された大気質の時系列データセット)を用いる。
*1 Dua, D. and Graff, C. (2019). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science.
*2 S. De Vito, E. Massera, M. Piga, L. Martinotto, G. Di Francia, On field calibration of an electronic nose for benzene estimation in an urban pollution monitoring scenario, Sensors and Actuators B: Chemical, Volume 129, Issue 2, 22 February 2008, Pages 750-757, ISSN 0925-4005, [Web Link].
前回のMNISTデータのようにTensorFlowパッケージに含まれているわけではないので、手動でダウンロードする必要がある。Webブラウザーからダウンロードしてもよいが、Pythonでダウンロードする場合は次のようにできる。Jupyter Notebookで新規ノートを作成してリスト1のコードを実行すればよい。
import os
from urllib.request import urlretrieve
from urllib.parse import urlparse
from zipfile import ZipFile
def download_file(url, output_dir, overwrite=False):
# URLからファイル名を取得
parse_result = urlparse(url)
file_name = os.path.basename(parse_result.path)
# 出力先ファイルパス
destination = os.path.join(output_dir, file_name)
# 無意味なダウンロードを防ぐため、上書き(overwrite)の指定か未ダウンロードの場合のみダウンロードを実施する
if overwrite or not os.path.exists(destination):
# 出力先ディレクトリの作成
os.makedirs(output_dir)
# ダウンロード
urlretrieve(url, destination)
return destination
zip_file = download_file('https://archive.ics.uci.edu/ml/machine-learning-databases/00360/AirQualityUCI.zip', 'UCI_data/')
ダウンロードしたAirQualityUCI.zipファイル中にはAirQualityUCI.csvとAirQualityUCI.xlsxという2つのファイルが含まれているが、いずれも同じデータである。
TensorFlowで直接CSVを読み込んで利用する方法もあるが、本データには欠損値(missing data、NAと表すことが多い)が含まれていたり、CSVの数値の小数点がカンマであったりして取り扱いが複雑である。本稿では簡単のためにpandasパッケージ*3を利用して、Excel用の.xlsxファイルからデータをデータフレーム(DataFrame)形式(1行に各データポイントを含むデータセット)で読み込む。データフレームからテンソルへの変換は容易である。
pandasでExcelからデータを読み込むために、pandasパッケージとxlrdパッケージ*4のインストールが必要である。
*3 pandasは、データサイエンス/機械学習の分野で非常によく利用されるデータ解析用Pythonライブラリで、例えば時系列データなどの解析やグラフ図生成によるデータの可視化などが行える。
*4 xlrdは、Excelデータを読む込むためのPythonライブラリ。
$ pip install pandas xlrd
インストールが完了したら、Jupyter Notebook上でリスト3のコードを実行し、データフレームにデータを読み込んでほしい。このコードにより、先ほどダウンロードした.zipファイルからAirQualityUCI.xlsxファイルを抽出して読み込んでいる。
import pandas as pd
with ZipFile(zip_file) as z:
with z.open('AirQualityUCI.xlsx') as f:
air_quality = pd.read_excel(
f,
index_col=0, parse_dates={'DateTime': [0, 1]}, #(1)
na_values=[-200.0], #(2)
convert_float=False #(3)
)
read_excelメソッドでExcelファイル(f)からデータを読み込んでいる。
(1)データの最初の2列には日付(Date)と時刻(Time)のデータが含まれているので、引数parse_datesで日時データとして結合して[DateTime]列にまとめる。これをインデックスとして実際のデータポイントとは別に扱う。
(2)-200が欠損値(NA Values)として入力されているため、読み込み時にこれを指定する。
(3)Falseがされると、すべてのデータがfloat値のまま読み込まれる。デフォルトではTrueになっており、この場合はint値に変換されてしまうため。
【コラム】時系列データの可視化
pandasでデータフレームを読み込んだら、まず可視化を行うべきである。データの傾向をつかみ、どのような前処理を行い、どのような分析を行うか、といった道筋を立てることができるからだ。
このコラムでは、pandasのデータフレームの可視化に便利なCufflinksというパッケージを紹介する。
Cufflinksは、Plotlyというデータの可視化をサポートするプラットフォームおよびパッケージ(多言語対応)をバックエンドとしている。グラフ(※ここでは一般的な図のこと)を公開するためのSaaS(=オンライン)も提供しているが、Jupyter Notebookのようなオフラインでの利用もできる。Plotly単体でもデータフレームは可視化できるが、「データフレームを扱う」という処理のみにフォーカスすれば、Cufflinksを使うことで複雑なもろもろの処理を省くことができる。ちょうど前々回紹介したTensorFlowとKerasの関係に似ている。
Cufflinksパッケージのインストールはpipにより行える。このとき同時に依存するPlotlyパッケージもインストールされる。
$ pip install cufflinks
データを可視化することや可視化されたグラフのことをプロット(plot)と呼ぶ。Cufflinksでは、Jupyter Notebook上でプロットを行うために、pandasのデータフレーム(DataFrameクラス)でiplotという拡張メソッドを提供している。Jupyter Notebookのようなオフライン(=ローカル)でのプロットには、iplotメソッドのonline引数にFalseを与えるか、事前にgo_offlineメソッドを呼び出す必要がある(リスト5)。
Cufflinksを使ってAir Qualityデータセットを可視化するには、次のようにする。
import cufflinks as cf
cf.go_offline()
air_quality.iplot(
# 列ごとにグラフを縦に並べて描画する
subplots=True,
shape=(len(air_quality.columns), 1),
# グラフのx軸を共有する
shared_xaxes=True
)
iplotメソッドの引数の意味は、help(air_quality.iplot)を使って調べられる。もしくは「cufflinks/Cufflinks Tutorial - Plotly.ipynb at master · santosjorge/cufflinks」を参照してもよい。
列によりスケール(数値の大きさの程度)が大きく異なるため、それぞれの別のグラフとして表示した方が見やすい(subplots=Trueで別グラフとし、shape(<プロットの行数>,<列数>)でデータ項目数分を縦にグラフとして並べている。データフレームのcolumns属性でデータ項目を取得し、それをlen関数の引数に指定することでデータ項目の数を取得している。インデックスはデータ項目には含まれない)。
また、Plotlyはグラフの拡大・縮小を行えるが、可視化の際にすべてのグラフで同じ時間帯のグラフを表示できるように、x軸の共有を行っている(shared_xaxes=True)。
リスト5を実行すると図ex1のようなグラフが出力される。
プロットの見た目を洗練させるために、サイズや色の調整を行いたい場合は、iplotメソッドのasFigure引数にTrueを与え、その戻り値の辞書オブジェクトを修正し、その辞書オブジェクトに対してiplotメソッドを呼べばよい。次のコードは、背景色を白(カラーコードで#FFFFFF)にして縦幅を1600pxに設定する例だ。レイアウトの設定はPlotlyのLayoutを参照してほしい。図2はその結果である。
plot_data = air_quality.iplot(
asFigure=True,
subplots=True,
shared_xaxes=True,
shape=(len(air_quality.columns), 1)
)
plot_data['layout']['height'] = 1600
plot_data['layout']['paper_bgcolor'] = '#FFFFFF'
plot_data.iplot()
時系列データ、特にセンサーデータについては欠損値が含まれるのは一般的な事象であり、Air Qualityデータセットにも欠損値が含まれる。欠損値の統計上の取り扱いについては専門書が出版されるほどであるが、本稿でそれを説明するには記事が長くなりすぎる。ここでは簡単に、欠損値データについては学習に利用しないこととする。
また、本稿ではAir Qualityデータセット中の一部の列のみ利用する(表1)。
列 | 利用 | 内容 |
---|---|---|
CO(GT) | − | 一酸化炭素(CO)時間平均濃度[mg/m3] |
PT08.S1(CO) | ○ | 一酸化炭素(CO)に対する酸化スズの時間平均値 |
NMHC(GT) | − | 非メタン炭化水素(NMHC)時間平均濃度[microg/m3] |
C6H6(GT) | − | ベンゼン(C6H6)時間平均濃度[microg/m3] |
PT08.S2(NMHC) | ○ | 非メタン炭化水素(NMHC)に対する酸化チタンの時間平均値 |
NOx(GT) | − | 窒素酸化物(NOx)時間平均濃度[ppb] |
PT08.S3(NOx) | ○ | 窒素酸化物(NOx)に対する酸化タングステンの時間平均値 |
NO2(GT) | − | 二酸化窒素(NO2)時間平均濃度[microg/m3] |
PT08.S4(NO2) | ○ | 二酸化窒素(NO2)に対する酸化タングステンの時間平均値 |
PT08.S5(O3) | − | オゾン(O3)に対する酸化インジウムの時間平均値 |
T | ○ | 温度(Temperature)[℃] |
RH | − | 相対湿度(Relative Humidity)[%] |
AH | ○ | 絶対湿度(Absolute Humidity) |
一部の列のみをデータ項目として利用するには、リスト7のコードを記述すればよい。
# 不要列の除去
target_columns = ['T', 'AH', 'PT08.S1(CO)', 'PT08.S2(NMHC)', 'PT08.S3(NOx)', 'PT08.S4(NO2)']
air_quality = air_quality[target_columns]
前々回(CNN)で使ったMNISTデータセットのnext_batchメソッドのような機能を持つ、データセットを扱うための便利クラスを作成しておく(リスト8)。
時系列データの学習では、連続したデータポイントを訓練データとする。連続したデータポイントをランダムに切り出すためのメソッドとしてnext_batchを作成している。
next_batchメソッドは、length引数とbatch_size引数をとり、ランダムな「連続したlength分のデータポイント」および「その次のデータポイント」を返す。前述のように、本稿では欠損値は無視して扱う。すなわち、ランダムな連続データポイント中に欠損値が含まれていた場合は、再度、ランダムな連続データポイントを取得する*5。
*5 本稿の実装では、連続データポイント中に必ず欠損値が含まれてしまうような場合では、所望の連続データポイントが切り出せずに無限ループに陥る可能性がある。実際のデータセットの場合は、もう少しきちんとした処理を記述する必要がある。しかし本稿では、そのような処理は省略している。
import numpy as np
# 乱数シードの初期化(数値は何でもよい)
np.random.seed(12345)
# クラス定義
class TimeSeriesDataSet:
def __init__(self, dataframe):
self.feature_count = len(dataframe.columns)
self.series_length = len(dataframe)
self.series_data = dataframe.astype('float32')
def __getitem__(self, n):
return TimeSeriesDataSet(self.series_data[n])
def __len__(self):
return len(self.series_data)
@property
def times(self):
return self.series_data.index
def next_batch(self, length, batch_size):
"""
連続したlength時間のデータおよび1時間の誤差測定用データを取得する。
最後の1時間は最終出力データ。
"""
max_start_index = len(self) - length
design_matrix = []
expectation = []
while len(design_matrix) < batch_size:
start_index = np.random.choice(max_start_index)
end_index = start_index + length + 1
values = self.series_data[start_index:end_index]
if (values.count() == length + 1).all(): # 切り出したデータ中に欠損値がない
train_data = values[:-1]
true_value = values[-1:]
design_matrix.append(train_data.as_matrix())
expectation.append(np.reshape(true_value.as_matrix(), [self.feature_count]))
return np.stack(design_matrix), np.stack(expectation)
def append(self, data_point):
dataframe = pd.DataFrame(data_point, columns=self.series_data.columns)
self.series_data = self.series_data.append(dataframe)
def tail(self, n):
return TimeSeriesDataSet(self.series_data.tail(n))
def as_array(self):
return np.stack([self.series_data.as_matrix()])
Air Qualityデータセットは、2004年3月10日から2005年4月4日までの範囲である。本稿では2004年のデータのみを訓練(train)時に利用し、2005年のデータは予測の際にのみ利用する精度検証用のテスト(test)データとする(リスト9)。本稿ではCNNと同様に、訓練データから精度検証(validation)用データを切り分けずに、テストデータのみを精度検証に利用する。
dataset = TimeSeriesDataSet(air_quality)
train_dataset = dataset[dataset.times.year < 2005]
test_dataset = dataset[dataset.times.year >= 2005]
Copyright© Digital Advantage Corp. All Rights Reserved.