masato-ka's diary

日々思ったこととか、やったことの備忘録。

PythonでJubatusのregressionクライアントのサンプルコードを作ってみた。

Jubatusnoのregressionのサンプルコードを作ってみました。基本的には公式サンプルのclassifierをベースに書いてるので、対比して読みやすいかと思います。例によってPythonです。

regression(回帰)

regression(回帰)とは独立変数と従属変数のデータセットからその関係性を求めます。未知の値の独立変数を入力したときに、その独立変数に対する従属変数の値を推定できます。
簡単に説明すると、最小二乗法を考えるといいのですが、X(独立変数)とY(従属変数)のデータの集まりから、Y=b+aXのa とbを求めます。こうすることで、未知のXが来た際もYの値を推定することができます。もちろん実際は独立変数がX1 X2 X3....と複数存在したり、推定すべき式の形(モデル)もn次関数であったり、複雑です。また、これらを求める手法としては前述の最小二乗法だけでなくベイズ理論を用いた方法などが存在します。JubatusではPAと呼ばれるアルゴリズムを用いたPA-regressionというものを用いています。
回帰ができると、株価の予想や、気温と消費電力の関係、市場予測など様々な分野に応用できそうですね。センサーの値と事象を学習させて未知の事象をセンサーが観測してもそれに対する正しい出力を予想できそうです。

サンプルコード解説

細かいことはどうでもいいのでサンプルコードは以下のようになっています。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from jubatus.regression import client
from jubatus.regression import types

def parse_args():
    from optparse import OptionParser, OptionValueError
    p = OptionParser()
    p.add_option('-s', '--server_ip', action='store',
                 dest='server_ip', type='string', default='127.0.0.1')
    p.add_option('-p', '--server_port', action='store',
                 dest='server_port', type='int', default='9199')
    p.add_option('-n', '--name', action='store',
                 dest='name', type='string', default='tutorial3')
    p.add_option('-a', '--algo', action='store',
                 dest='algo', type='string', default="PA")
    return p.parse_args()

if __name__ == '__main__':
    options, remainder = parse_args()

    regression = client.regression(options.server_ip,options.server_port)

    str_fil_types = {}
    str_fil_rules = []
    num_fil_types = {}
    num_fil_rules = []
    str_type= {}
    str_rules = []
    num_type = {}
    num_rules = [types.num_rule("value","num")]
    
    converter = types.converter_config(str_fil_types, str_fil_rules, num_fil_types, num_fil_rules,
                                       str_type, str_rules, num_type, num_rules)
    config = types.config_data(options.algo, converter);

    pname = options.name
    
    regression.set_config(pname,config)
    
    print regression.get_config(pname)
    print regression.get_status(pname)
    
    with open(".//train.csv") as f:
        rows = f.read().split("\r")
        power_list = [row.split(",") for row in rows]

    for p in power_list:
        print p
            
    train_data=[(float(list[0].replace(":",".")),types.datum([],[["value", float(list[1])]])) for list in power_list]
    for t in train_data:
        regression.train(pname,[t])
    regression.get_status(pname)

    regression.save(pname, "tutorial3")
    regression.load(pname, "tutorial3")
    
    regression.set_config(pname, config)
    regression.get_config(pname)

    with open(".//test.csv") as f:
        rows = f.read().split("\r")
        power_list = [row.split(",") for row in rows]

    for p in power_list:
        datum = types.datum(  [], [["value",float(p[1])]] )
        ans = regression.estimate(pname,[(datum)])
        print "estimate:%s actual:%s" %(ans[0],p[0]) 

以下かいつまんで説明します。

import モジュール

次のモジュールをインポートします。

from jubatus.regression import client
from jubatus.regression import types
regressionのクライアントインスタンス
regression = client.regression(options.server_ip,options.server_port)

classifierとは違って今回はregressionです。お間違いのないよう。引数はclassifierのサンプルと同じになっています。

フィルタの記述
    str_fil_types = {}
    str_fil_rules = []
    num_fil_types = {}
    num_fil_rules = []
    str_type= {}
    str_rules = []
    num_type = {}
    num_rules = [types.num_rule("value","num")]

filterの記述方法についてはJubatusの公式ページにリファレンスが公開されているので、詳しくはそちらを参照いただければと思います。今回は読み込ませるデータが数値だったため、num_rulesのみ追加しています。valueの値をそのまま使うという意味です。numのほかにも値の対数値を使うlogという指定があります。フィルターは利用しません。

テストデータの読み込み
 with open(".//train.csv") as f:
        rows = f.read().split("\r")
        power_list = [row.split(",") for row in rows]

  for p in power_list:
        print p

Jubatusと関係ないところですが、trac.csvというファイルからcsv形式のデータをリスト形式で読み込んでいます。今回使用したデータは[時間,東京の消費電力]というものです。元データは東京電力の電気予報のサイトから入手できます。http://www.tepco.co.jp/forecast/html/images/juyo-2011.csv:東京電力公開2011年の消費電力
このデータからヘッダ情報、年月日を取り除き、時間と消費電力のデータのみを利用しています。

データの作成と学習
train_data=[(float(list[0].replace(":",".")),types.datum([],[["value", float(list[1])]])) for list in power_list]
for t in train_data:
    regression.train(pname,[t])
    regression.get_status(pname)

train_dataが(ラベル,datumオブジェクトのリスト)です。datumオブジェクトは次のように定義しています。[ [], [ ["value",float型の数値] ] ]

Jubatusでは学習データや結果をdatumというオブジェクトで表現しています。datumは以下の用になっています。

( [ ["user/id", "ippy"],
    ["user/name", "Loren Ipsum"],
    ["message", "<H>Hello World</H>"] ],
  [ ["user/age", 29] ,
    ["user/income", 100000] ] )

データはkeyとvalueのペアになっています。このkeyとvalueはリストの要素になっており、[key,value]という形で表します。また、前半のリストはvalueが文字列のもの。後半のリストはvalueが数値になっているものです。datum[ svkey,value, nv[key,value]]という形になっています。

まとめると、[(label,datum),(label,datum)....]という形式のデータをregression.train()に与えています。なおregressionではlabelもdatumで与える数値もfloat型でないとだめなようです。ちなみにlabelが従属変数、datumの中の値が独立変数となります。

推定
with open(".//test.csv") as f:
    rows = f.read().split("\r")
    power_list = [row.split(",") for row in rows]

for p in power_list:
    datum = types.datum(  [], [["value",float(p[1])]] )
    ans = regression.estimate(pname,[(datum)])
    print "estimate:%s actual:%s" %(ans[0],p[0]) 

テストデータを読み込んで未知データを与えてみます。結果では推定値と実際の値を表示させます。ちなみに、電力と時間の関係では期待通りの結果を得ることができませんでした。消費電力と時間ではあまり相関がないと思うので当たり前かもしれませんが。気温と時間と消費電力だとかわってくるかもしれませんね。

まとめ

regressionは基本的にclassifierとそんなに違いがないので取っ付きやすいかもしれません。また、Jubatusのリファレンスもしっかりしてるので困らないと思います。
かなりおおざっぱで汚いコードですが、ご参考にしていただければ幸いです。もし、わかりづらい箇所があればコメントなどを残していただくとできる限り対応したいと思います。regressionできると応用範囲がさらに広がりそう!