自分の行動履歴からID行列を作成する

概要

  • (日数, 1日をN等分するN) のサイズの行列データを作りたい
  • 自然言語処理 (CBoWとか) のそれと同じような感覚
  • python manage.py のコマンド → CSVファイルに書き込み、という流れとする
  • 50 日分くらいのデータが溜まった
  • Apple Home まだか

イメージ

ニューラルネットワークによる自然言語処理の入力データ。単語 <=> ID (インデックス) は一意に対応していさえすれば、学習に用いることができるのであった。つまり ID 自体に (一意であること以外の) 意味はなくともよい。

以下では、「食事をした」→ 2、「眠気を感じた」 → 3, ... などというように、生活における事象の発生と任意のインデックスを対応付けている。

以下のようなモデルを考える

f:id:zdassen:20180407205658j:plain

EventType モデル

from app_name.models import EventType


event_types = EventType.objects.filter(user=user)    # User は取得済み
",".join([event_type.name for event_type in event_types])
# '眠気を感じた,食事をとった,コーヒーを飲んだ,飲酒した,トイレ,筋トレをした'

python manage.py dump_event [username] とできるようにする

python manage.py で使えるコマンドを定義する。
対象のユーザーを切り替えられるように、ユーザー名を引数に指定させる。

# management/commands/dump_event.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User    # 今回は素の User とする
from ...models import Event, EventType

import numpy as np
import pandas as pd


class Command(BaseCommand):

    # ヘルプメッセージ
    help = "Save event data to csv file"

    def add_arguments(self, parser):
        """コマンドライン引数を追加する"""

        # ユーザー名 (についてのデータを保存する)
        # 引数は 1 個、文字列として解釈する
        parser.add_argument("username",
            nargs=1, type=str)

def handle(self, *args, **options):
        """コマンド実行時に呼び出されるメソッド"""

        # 指定されたユーザー名でユーザーを取得
        username = options["username"][0]

        # QuerySet が取得されることに注意
        user = User.objects.filter(username=username)[0]

        # イベントタイプも取得
        # ID の設定に使用する
        event_types = EventType.objects.filter(user=user)

        # イベントタイプ → ID、に変換する対応表
        event2id = {}
        gap = 2    # ID を 2 からスタートさせる
        for event_type in event_types:
            if not event_type in event2id:

                # Sleep が 1 なので
                # ダブりを防ぐための len(event2id)
                event2id[event_type.name] = len(event2id) + gap
        
        # 指定されたユーザーのイベントデータを取得
        event_list = Event.objects.filter(user=user)

        # DataFrame のインデックスを作成
        start = event_list[0].at    # 開始日
        finish = event_list[len(event_list) - 1].at    # 最新の日時 (Negative index が使えない)
        date_range = pd.date_range(
            reset(start),
            reset(finish)
        )

        # DataFrame を作成
        # 1 日 ( 00:00:00 ~ 23:59:59 ) を 96 等分するイメージ
        # つまり 1 マス当たり 15 分に相当する行列
        # N は引数としてもいいかもしれない
        N = 96    
        df = pd.DataFrame(
            np.zeros((len(date_range), N)).astype("int"),
            index=date_range
        )

        # DataFrame に イベントID をセットする
        for event in event_list:

            # イベント名 → イベントID
            event_id = event2id[event.event_type.name]

            # イベントの時刻 (時/分/秒) → 
            # 1 日を N 等分した時のインデックス
            at = event.at
            ei = int(to_nth(at) * N)

            # イベントID をセットする
            df.loc[str(at.date())][ei] += event_id

        # CSV ファイルに書き込む
        df.to_csv("./events.csv")


def reset(dtm):
    """時/分/秒/マイクロ秒をリセットする"""
    return dtm.replace(
        hour=0,
        minute=0,
        second=0,
        microsecond=0
    )


def to_nth(dtmobj):
    """1日における時間の経過割合に変換する (0.0~1.0)"""

    seconds = dtmobj.hour * 60 * 60 + \
        dtmobj.minute * 60 + \
        dtmobj.second
    if seconds == 0:
        return 0.0
    else:
        return seconds / 86400.0

CSV データを読み込んで画像として表示してみる

import pandas as pd
from matplotlib import pyplot as plt

# データを読み込む
df = pd.read_csv("./events/csv")

# 1 列目が日付時刻となるので除外
mat = df.values[:, 1:]

# 画像として表示してみる
mat = mat.astype("float")
plt.imshow(mat)    # 色関係は無視する
plt.show()

f:id:zdassen:20180401111847j:plain

考察と感想と余談

  • ※個人の生活データなので逆解析はお辞めください
  • Apple Home ができて、データを自動採取してくれるまでの辛抱 (現在はフォームで手入力)
  • プログラマーの仕事を作っているのはつくづくセンサーなんだなぁと思った
  • EventType の「眠気を感じた」を教師データとして、生活パターンから眠くなる 30 分前に教えてくれるようにできないか etc.. (ちなみにコーヒーを飲んでカフェインが摂取されるまでのおおよその時間が 30 分とされているらしい、眠くなる 30 分前にコーヒーを飲むことができれば眠くなくなる (はず..) ).また、それによって実際に眠気を回避できた場合以降で採取されるデータの質 (パターン) も変化するのであり、その場合にどういう対処を行うべきか体験してみたい
  • 他にも気象データ (気温、湿度等)、睡眠データとマージしてみる.個人データなので量が少ないが、用いるべき特徴は「普段の生活上の経験」からある程度導き出せるので、適切な特徴を選び出して数を増やすことである程度対応できないか、とは思う
  • 個人の体調というものはある程度連続性がある (例: 夏バテ、残業続きで最近だるい) ので (広義の?) RNN で学習させることにはそれなりの意味がありそう

弱点

  • データが溜まるのが遅く、量が少ない
  • 個人の (パーソナルな) AI にしかならない