生活デザインアプリコンテスト 募集カテゴリー2 応募 - lifedesign

神奈川工科大学主催
生活デザインアプリコンテスト
募集カテゴリー2
応募の手引き
生活デザインアプリコンテストにご興味をお持ちいただき、ありがとうございます。本ドキ
ュメントは、スマートメーター等から得られる電力ログデータを用い、近未来の電力使用予
測を行うプログラムソースコードを募集する、カテゴリー2への応募方法に関しての説明
になります。見える化をテーマとしたカテゴリー1の募集に関しては、コンテストの HP
(http://lifedesign-app.org/)をご参照ください。
概要
2016 年は電力小売り完全自由化が達成される予定になっています。電力自由化のもたらす
変化について本ドキュメントで詳説することは避けますが、身近な変化としてはスマート
メーターの全世帯導入があります。これまでは宅内の電力消費の総和を記録して表示する
ために、アナログ式の電力メーターが戸外に取り付けられていて、電力会社の検針員が時々
その値を記録しに訪れ、その値によって電気代が決められていました。
スマートメーターは通信機能を持った新しい電力メーターで、2024 年までに国内のすべて
の世帯に導入されることが決まっています。電力会社が変更業務を行うので、住人が付け替
えに対して支払いを要求されることはありません。このスマートメーターは、30 分間隔で
取得したリアルタイム値を直接電力会社に転送することができます。これによって、例えば
夏の暑い日などに電力が大量に使われることがわかると、それに応じて電気代を上昇させ、
経済原理に基づいて電力消費を抑えることができます。
このスマートメーターは、電力会社に情報を転送するだけでなく、HEMS ゲートウェイとい
う機器を追加で導入することにより、宅内のネットワークに電力情報を流すこともできま
す。HEMS ゲートウェイは現在ではトライアルやモニター募集などのキャンペーンにより無
料で利用できることもあるので※1、電力会社や販売会社に問い合わせてください。いずれ
にせよ、このスマートメーターから HEMS ゲートウェイへの通信路は「B ルート」と呼ば
れ、電力を利用した宅内サービスの基礎データとして大変注目されています。B ルート経由
では、30 分間隔の累積値に加え、瞬時値といってリアルタイムの電力消費値を得ることも
できます。より具体的に言えば、累積値は 30 分間の電力エネルギーの総和であり、単位は
通常 kWh(キロワット時)となります。それに対し、瞬時値は瞬間の電力であり、単位は W(ワ
ット)になります。数学的には瞬時値を積分すると累積値になるわけですが、B ルートの通
信は様々な要因からそれほど頻繁に値を得ることができるわけではないので、瞬時値と累
積値は関係はあるけれども計算式によって相互変換できるものではない、と考えてくださ
い。
いずれにせよ、B ルートから得られる電力の累積値と瞬時値が宅内電力消費の基礎データで
あり、ここから様々なサービスを導出するのがこれからの社会的課題になっています。
※1
例えば IIJ スマートメーターB ルート活用サービス トライアルプログラムなど。
コンテストの課題
さて、本コンテストのカテゴリー2では、これらのログデータ一か月分を用いて、直近の将
来一日分の累積値を推測するプログラムを組んでいただきます。30 分間隔の一日分ですの
で 0:00~23:30 までの 48 サンプルになります。入力として用いる過去データは一か月分
の累積値および瞬時値(多少のブレはありますが 30 秒間隔)です。図で書くと次のように
なります。
日付
1/1
1/2
1/3
1/4
1/5
1/6
瞬時
1/30
1/31
2/1
…
累積
この図で、
1/7
のデータを用いて
のデータを予測することになります。
この例では1月のデータを用いて 2/1 の累積値を予測するようになっていますが、コンテ
スト審査では 1/18~1/24 の期間中、それぞれ過去一か月のデータを用いて一日分の予測
をすることで予測精度(予測値と実測値の差分)を競うことになります。差分は、48 サン
プルの差分の二乗和(二乗誤差)によって求めます。応募作品のうち、最も二乗誤差が小さ
いものを表彰いたします。この実証実験には IIJ の社員が多数参加する予定です。
また、プログラムのチェック用、あるいは機械学習の教師データとして頂くために、実際の
スマートメーターログデータも、IIJ からコンテスト限定用途に限り提供されていますので
ご利用ください。以下のサイトから、規約にご同意の上ダウンロードしてください。
http://g.lifedesign-app.org/powerdata/
・フォーマット
応募に用いるプログラムは、上記データファイルの中に含まれている
sample_instantaneous.json
(瞬時値)
sample_forward_integral.json
(累積値)
の形式を入力フォーマットとし、出力として
sample_output_forward_integral.json (累積値48サンプルが含まれたもの)
の形式を規定のフォーマット(IIJ フォーマット)とします。
組んでいただくプログラムは単一のプログラムである必要はなく、別プログラムを使って
ファイルの前処理・後処理などを行って、所定のフォーマットに合わせるようにしても構い
ません。特に、累積値の生データは、スマートメーターが設置されてからの累積電力値が記
録されているので、常に単調増加な値を取ります。予測を行う場合は恐らく累積値ではなく、
その隣接サンプルの差分を取り、30 分区間で消費した電力量を予測に用いたほうが便利で
しょう。その後に累積値の IIJ フォーマットで出力するとよいかと思います。
本コンテストでは、実装に用いる言語は自由です。ただし、フィールド審査を行う都合上、
以下のものはご遠慮ください。
・有料であったり、非常に手に入りにくい、インストールしにくい、その他使用に制約のあ
るソフトウェアやリソースを用いること。
・実行時間があまりに長いもの
提出方法
コンテストに応募するには、以下のものを zip ファイルとしてまとめて
[email protected] までメールでご送付ください。
・プログラムソースコード。一か月分の電力データを入力とし、次の一日分の累積値(48
サンプル)を出力するもの。複数のプログラムに分かれていても構いません。
・以下の情報を含む Readme.txt
・運営側でプログラムを実行するための説明資料
・お名前、所属、連絡先メールアドレス(IIJ データダウンロード時に登録した情報と
同一のものをお書きください)
成果発表は 1/27-29 まで東京ビッグサイトで実施される ENEX 2016 の神奈川工科大学ブ
ースでなされるほか、2/1 から http://lifedesign-app.org/ でも発表されます。
実装例
それでは、簡単なアルゴリズムを用いた実装例を2つほど示します。実装例は、Samples フ
ォルダの下に入っています。
まず、アルゴリズムを書きやすくするために生データをより処理しやすい形に変更してお
きます。そのためのツールが Samples/JavaTools の下にあります。
前準備1:瞬時値ファイルを変換する
まず、Simplify_Instantaneous.class は、瞬時値の IIJ 形式 JSON ファイルを標準入力から
読み込み、一行に1サンプル:
UnixTime,瞬時値
という形式で出力する Java プログラムです。このツールを使って、サンプルプログラムの
入力である sample_instantaneous.json を処理しておきましょう。(Java はインストール
して、パスを通しておいてください)
$ java Simplify_Instantaneous sample_instantaneous.json > inst.txt
これで inst.txt というファイルができているはずです。inst.txt の中身が、
1443625202271,744.0
1443625232531,756.0
1443625262311,752.0
:
のようになっていれば成功です。
前準備2:累積値ファイルを変換する
次に、累積値ファイルも扱いやすい形式に変換しておきましょう。
これには Simplify_Integral.class というツールを用います。このツールは何をするかとい
うと、タイムスタンプを unixtime に変換するのに加え、累積値の差分を取ります。つまり、
元ファイルではスマートメーターを設置してからの電力使用量の総和が記録されているの
に対し、ツールを通したあとでは、各行には30分の間に使った電力量(kWh)が記録されて
います。これは単純に隣の値との差分をとっているだけなので、サンプル数が1減ってしま
うことにご留意ください。また、変換にともなう数値演算の誤差が発生します。
なお、unixtime として記録されている値は、時間フレームの最後の時間となります。つま
り、unixtime が 0:30 の時間であれば、その値は 0:00~0:30 の間に使った電力量になり
ます。
このプログラムを使って sample_forward_integral.json を処理し、integ.txt というファ
イル名にしておきましょう。コマンドラインは次のようになります。
$ java Simplify_Integral sample_forward_integral.json > integ.txt
すると、プログラムからのメッセージ(標準エラー出力)として、以下のようなものが表示
されていると思います。
Unsimplify : java Unsimplify_Integral 2147.59 1446303600000
これは、処理後のデータを用いて作られた予測結果のファイルを、元の IIJ 形式に復元する
際に必要なコマンドラインとなりますので、どこかに記録しておいてください。
いずれにせよ、瞬時値を変換した inst.txt と履歴を変換した integ.txt ができました。これ
を用いて予測を行います。
実装例1
まず最初に試みるアルゴリズムは、
「明日の電力消費の予測値は、今日と同じ可能性が高い」
と仮定して、データ内の最後の日のデータをそのまま出力するというものです。この予測は
乱暴に見えるかもしれませんが、もし電力消費が、例えば天気のような、比較的変化が遅い
ものから強い影響を受けているならばあながち間違った戦略ではないかもしれません。実
装も簡単そうです。このサンプルでは、あまり機械学習には使われないかもしれませんが、
プログラムの可読性の高さから Processing を用いて実装してみましょう。ソースコードは
次のようになります。
===============
String in_lines[] = loadStrings("integ.txt");
String out_lines[] = new String[48] ;
for( int i=0;i<48;++i ){
out_lines[i] = in_lines[ in_lines.length-48+i ].split(",")[1] ;
}
saveStrings("result.txt",out_lines) ;
===============
やっていることは単純で、integ.txt を行ごとに配列 in_lines[]に入れ、最後 48 行を取り
出し、カンマで二つに分けてその後者(累積値)をもう一つの配列 out_lines[]に格納し、
ファイルとして出力しています。 この例では瞬時値は 使っていません 。実行結果は
result.txt に格納されます。
result.txt の中身は
0.19000000000005457
0.18999999999959982
0.18000000000029104
:
のように、各行1つのデータが浮動小数値で入っています。これをもう一つのツール、
Unsimplify を使って IIJ 形式に戻しましょう。それには、Simplify_Integral を使った時に
出力された文字列から java 以降を用います。
java Unsimplify_Integral 2147.59 1446303600000
ですね。この標準入力として、result.txt を流し込み、ツールの出力をリダイレクトで
result.json に格納しましょう。
$ cat result.txt | java Unsimplify_Integral 2147.59 1446303600000 > result.json
※Windows のコマンドプロンプトの場合は、cat を type に変更してください。
結果の result.json は以下のように、JSON 形式の配列になっています。
[{"measured_at":"2015-1101T00:00:00.000+09:00","missing":false,"value":2147.78},{"measured_at":"201511-01T00:30:00.000+09:00","missing":false … }]
これで予測を行うことができました。
実装例2
次は、もう少し凝った予測を行いましょう。参照用のデータから、最後のデータ(今日のデ
ータ)に類似した部分を見つけ、その次のデータ 48 サンプル分を明日の予測値として出力
することを考えます。前の例では、生活パターンが大幅に変わるところ、例えば、金曜日か
ら土曜日に遷移するときなどに誤差が大きくなりそうですが、この戦略であれば、過去デー
タから金曜日が見つかったらより精度が高くなるかもしれません。もちろんならない可能
性もありますが、とりあえずやってみましょう。
前回と同じく Processing を用い、入力ファイルも integ.txt のみです(瞬時値は使いませ
ん)
。
プログラムは次のようになります。
===============
String in_lines[] = loadStrings("integ.txt");
// 入力文字列を数値に変換
double in_nums[] = new double[in_lines.length] ;
for( int i=0;i<in_lines.length;++i ){
in_nums[i] = Double.parseDouble(in_lines[i].split(",")[1] ) ;
}
int best_i = -1 ;
double best_diff = Double.MAX_VALUE ;
// 入力データの最初から、ラスト二日前までを比較対象にする
// (マッチングしたデータの先に、最低 48 サンプルが存在しないといけないため)
for( int i=0;i<in_nums.length-48*2;++i ){
// 48 サンプル分の比較開始
double diff = 0 ;
int k = in_nums.length-48 ;
for( int j=0;j<48;++j,++k ){
// 最後の日のデータとの差の二乗和をとる
diff += (in_nums[i+j]-in_nums[k])*(in_nums[i+j]-in_nums[k]) ;
}
// もし差分が過去の最も小さな差分よりも小さければマッチング候補とする
if( diff < best_diff ){
best_i = i ;
best_diff = diff ;
}
}
// マッチングした部分に続く 48 サンプルを文字列にして out_lines に格納
String out_lines[] = new String[48] ;
int j = best_i + 48 ;
for( int i=0;i<48;++i,++j ){
out_lines[i] = ""+in_nums[i] ;
}
// 出力
saveStrings("result.txt",out_lines) ;
===============
少し長くなりましたが、複雑なことをやっているわけではありません。1日目から昨日のデ
ータまで、今日のデータと似ているところを探します。最も似ているところがあったら、そ
の次 48 サンプルを予測値として出力しているだけです。日の変わり目を意識しているわけ
ではなく、単純に起点のサンプルをひとつづつづらしながらよいところを探しています。非
常にナイーブにループを回していますが、一か月しかデータがないため大きな問題ではな
さそうです。
結果の result.txt は一行に一つづつデータが並んだものになりますので、先ほどと同じよ
うに Unsimplify を用いて、IIJ 形式に変換することができます。
予測結果の検証
さて、折角予測を行ったので、最後に予測がどの程度正しいかを検証してみましょう。それ
には、一か月分のデータだけでは不十分です。幸い IIJ データセットには三か月分のデータ
が含まれているものが多いので、これを用いて予測精度を評価してみましょう。予測精度の
評価は、コンテストの実際の評価と同じように誤差の二乗和によって計算しましょう。これ
には、起点となる日を決めて、そこから一か月分のデータを用いて、その直後一日分の予測
を行います。予測された一日が、実際にはどうだったかというのはデータから得られますの
でその日について、予測と現実との二乗誤差を求めればいいわけです。
まず、3か月分の累積値をまとめて一つのデータにします。例えば
201508_forward_integral_7.json
201509_forward_integral_7.json
201510_forward_integral_7.json
は、世帯番号 7 の8月~10月のデータになりますので。これらをつなげて使うことにし
ましょう。これにも先ほど使った Simplify_Integral というツールを使うことができます。
このツールは、引数を増やすことで複数のファイルを読み込むことができるので、コマンド
ラインはこのようになります。
$ java Simplify_Integral 201508_forward_integral_7.json
201509_forward_integral_7.json 201510_forward_integral_7.json > integ.txt
※実際には改行を入れないでください
このデータを用いた場合、標準エラー出力には
Missing data found.48017
と表示されることと思います。これは、データに欠けがあることを示しています。欠けがあ
る場合、直前のデータがそのまま用いられます。
IIJ 形式に戻すためのコマンドとして、
Unsimplify : java Unsimplify_Integral 2083.65 1446303600000
とも表示されているはずです。しかし、今回は誤差を求めるだけで IIJ 形式に戻す必要はな
いので気にしないことにします。
こうしてできた integ.txt は、4415 サンプルを含んでいます。さきに述べたように、累積
値は入力データの差分を取って出力するため、出力データの個数は最初のサンプル数より
1つ少なくなります。その分を補って一日分のサンプル数すなわち 48 で割ると (4415+1)
÷48 = 92 すなわち、92 日分のデータが入っていることがわかります。
それでは、このデータと先ほど実装した2つのアルゴリズムを用いて3か月分の予測誤差
を求めてみましょう。1か月分のデータとして採用する部分をずらしながら予測を行い、ト
ータルで誤差がどの程度になるのかを計算します。これを Processing のソースコードで書
くと、以下のようになります。
===============
double[] integ_data ; // ファイルから読み込んだデータを格納しておく
double diff_total = 0 ;
for( int startday=0 ; ; ++startday ){
int endday = startday+31 ;
if( endday == 91 ) break ;
double[] prediction = new double[48] ;
// startday と endday-1 の間のデータ 31 日分を用いて、
// endday 日目のデータ 48 サンプルを予測し、prediction
// に入れる
for( int si=0;si<48;++si ){
diff_total += Math.pow(prediction[si]-integ_data[si + endday*48 ] ,2) ;
}
}
===============
このプログラムは、3か月分のデータを integ_data に格納しておき、その中で startday
から endday-1 までの一か月分のデータから次の endday 日の 48 サンプルを予測し、それ
と実際の値(同じく integ_data 内のデータ)との差の二乗和を計算しています。
一か月の長さを 31 日に固定してしまっていますが大目に見てください。
この中で
// startday と endday-1 の間のデータ 31 日分を用いて、
// endday 日目のデータ 48 サンプルを予測し、prediction
// に入れる
の部分に予測アルゴリズムを追加し、結果を prediction という配列に格納すれば予測と実
測値の誤差を計算します。
このロジックを使って、実装例1の予測誤差を計算してみましょう。動作するソースコード
は次のようになります。実際に integ_data をファイルから入力して設定する部分と、予測
を行う部分が追加されています。ただし、Simplify_Integral で前処理を行った際にサンプ
ル数が一つ減っており、ループを回しづらいので、最初にダミーデータ0を追加し、
integ_data は integ.txt のサンプル数よりも一つ多い要素数を持つようにしています。
===============
double[] integ_data ; // ファイルから読み込んだデータを格納しておく
double diff_total = 0 ;
{ // ファイルから読み込んで integ_data を設定
String in_lines[] = loadStrings("integ.txt");
// 差分を取った時に、サンプル数が一つ減るため、
// 最初にダミーのサンプルひとつを追加しておく。
integ_data = new double[in_lines.length+1] ;
integ_data[0] = 0 ;
for( int i=0;i<in_lines.length;++i ){
integ_data[i+1] = Double.parseDouble(in_lines[i].split(",")[1]) ;
}
}
for( int startday=0 ; ; ++startday ){
int endday = startday+31 ;
if( endday == 91 ) break ;
double[] prediction = new double[48] ;
// startday と endday-1 の間のデータ 31 日分を用いて、
// endday 日目のデータ 48 サンプルを予測し、prediction
// に入れる
for( int si=0;si<48;++si ){
prediction[si] = integ_data[ si + (endday-1)*48 ] ;
}
for( int si=0;si<48;++si ){
diff_total += Math.pow(prediction[si]-integ_data[si + endday*48 ] ,2) ;
}
}
println("Error : "+diff_total) ;
===============
予測をしている本体は赤字の部分のみです。要は、前日(endday-1)のデータをそのまま出
力しているわけです。
これを実行すると、出力として 179.2637000000005 という値が得られます。
これが前日のデータをそのまま予測値として出力した場合の誤差になります。
同様に、2番目の例の誤差も求めておきましょう。プログラムは、予測をしている部分のみ
の変更で大丈夫です。ソースコードを掲載します。
===============
double[] integ_data ; // ファイルから読み込んだデータを格納しておく
double diff_total = 0 ;
{
String in_lines[] = loadStrings("integ.txt");
// 差分を取った時に、サンプル数が一つ減るため、
// 最初にダミーのサンプルひとつを追加しておく。
integ_data = new double[in_lines.length+1] ;
integ_data[0] = 0 ;
for( int i=0;i<in_lines.length;++i ){
integ_data[i+1] = Double.parseDouble(in_lines[i].split(",")[1]) ;
}
}
for( int startday=0 ; ; ++startday ){
int endday = startday+31 ;
if( endday == (integ_data.length+1)/48 - 1 ) break ;
double[] prediction = new double[48] ;
// startday と endday-1 の間のデータ 31 日分を用いて、
// endday 日目のデータ 48 サンプルを予測し、prediction
// に入れる
{
int best_day = -1 ;
double best_diff = Double.MAX_VALUE ;
// 入力データの最初から、ラスト二日前までを比較対象にする
// (マッチングしたデータの先に、最低 48 サンプルが存在しないといけないため)
for( int day=startday ; day < endday-2 ; ++day ){
// 48 サンプル分の比較開始
double diff = 0 ;
for( int j=0;j<48;++j ){
// 最後の日のデータとの差の二乗和をとる
diff += Math.pow(integ_data[day*48+j]-integ_data[(endday-2)*48+j],2) ;
}
// もし差分が過去の最も小さな差分よりも小さければマッチング候補とする
if( diff < best_diff ){
best_day = day ;
best_diff = diff ;
}
}
for( int i=0;i<48;++i )
prediction[i] = integ_data[best_day*48+i] ;
}
for( int si=0;si<48;++si ){
diff_total += Math.pow(prediction[si]-integ_data[si + endday*48 ] ,2) ;
println((si + endday*48)+" : dif="+diff_total) ;
}
}
println("Error : "+diff_total) ;
===============
赤字になっている部分が、一つ目のアルゴリズムとの差分です。予測値は prediction[]に格
納します。
この実行結果は、145.3688999999992 となっています。1つ目のアルゴリズムでは予測
誤差は 179.2637000000005 でしたから、少し改善していることがわかります。
この2つの例では入力データの中を探索するだけで、所謂機械学習的な処理は行っていま
せん。また、例としてお見せしたものでは瞬時値は一切使っていません。IIJ データセット
をフルに用いれば、予測誤差を最小にするような機械学習プログラムも組むことができる
と思われますので、ぜひ挑戦していただければと思います。
長くなりましたが、説明は以上となります。なにかご質問がある場合は
[email protected]
までメールでご連絡ください。
多くの方々の投稿をお待ちしております。
大和田