読者です 読者をやめる 読者になる 読者になる

ウェアラブルデバイスのログで自己分析してみる

 2ヶ月ほど前に自分自身の行動ログを収集したいなと思い、フランスのWithings社が出している腕時計型のウェアラブルデバイスWithings Pulse O2を購入しました。以来、ほぼ毎日、お風呂と仕事中以外は腕に付け、歩数、心拍数、睡眠時間などのログを取っています。

今回はこのウェアラブルデバイスのデータをAPIで取得し、自分自身の行動を可視化してみました。


1. 準備

 WithingsのAPIを使うためには、まずここデベロッパー登録をする必要があります(Withingsのアカウントを持っていない場合はアカウントの作成から)。登録をすると、Consumer KeyとConsumer Secretが取得できるので、メモしておきましょう。
 次に、Withings API developer documentationで書かれてある通りにStep1から順に実行していき、ユーザーIDとAccess Token, Access Token Secretを取得し、こちらもメモしておきましょう。

とりあえずこれで、データにアクセスする準備は出来ました。


2. データを取得・前処理

 次に、実際にデータを取得し、Rで処理しやすいような形に整形します。 APIを叩く部分とデータの整形はRubyで実装します。Withings APIを使うための便利なgemがあるので、まずはsimplificator-withingsというgemをインストールしましょう。

> gem install simplificator-withings
> gem install ruby-hmac

そして、以下が記録されたデータを取得するサンプルコードです。

gistb48805bd5513093a8973

実行すると、以下のようなcsvファイルが作成されます。

f:id:hongo35:20140920212836p:plain

取得できるデータは、1日ごとの歩数(steps)、歩いた距離(distance)、消費カロリー(calories)、昇り降りの高低差(elevation)、軽めの運動をした時間(soft)、普通の運動をした時間(moderate)、激しい運動をした時間(intense)です。睡眠時間のデータも取得したかったのですが、まだできないようです。

ここまでで、必要なデータは揃ったので、後はExcelやRで見たいデータを可視化したり、統計量を求めたりしていきます。

3. Rで分析・可視化

 前述のように、さまざまなデータが取得できますが、今回はその中でも最も特徴が出やすいであろう歩数(steps)について見てみます。 Rのsummary()関数を使って、基本統計量を求めると、

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     88    3832    4800    5974    7170   14650

となりました。1日平均で約6,000歩しか歩いてません。目標にしている、1日平均1万歩にもっていくためには毎日今までの倍歩く必要があるようです。。 (最小値88は、ほぼ1日中つけ忘れていた日の値)

次に、日ごとの推移を見てみると、以下のようになりました。青の棒が土曜日、赤の棒が日曜日になっています。

f:id:hongo35:20140920223113p:plain

この図から、平日に比べ、土日(特に日曜日)によく運動していることがわかります。曜日による差が明らかになるように、曜日別に箱ひげ図で可視化した結果が以下です。

f:id:hongo35:20140920225514p:plain

明らかに平日に比べ土日の運動量が大きいです。原因は、スポーツクラブで運動しているからという単純な理由です。 そしてもうひとつ、月曜から金曜の部分も少しずつ違っていて、水曜、木曜の運動量が少なかったり、金曜が多かったりするので、なんとなく気分と相関してそうな気がしています。。

Rのコード

gist1ad54ce8cf217b62cda8

まとめ

 実は、Withingsが提供しているダッシュボードを見れば日々の活動が可視化されているので、 今回のようなことをしなくてもおおまかなことはわかります。 しかし、当然ダッシュボードでは詳しい分析はされていないので、より詳しく知りたい場合に自分でデータをいじる必要があるという意味で今回のような分析は有用なのではないかと思います。

 また、分析してみてわかったことは、運動量の大きい、つまりたくさん動いている日のほうが気分がいいし、それによって仕事の効率も良くなるのではないかということで、今後実践してみようと思いました。自分自身の気分と行動の関係などは自分自身でしかわからないので、分析してみるとおもしろいと思います。

ソーシャル×ECについて分析してみた

 昨年10月に発表されたYahoo!ショッピングのeコマース革命など、非常にホットで、これからますます成長していくであろうEC分野ですが、ソーシャルとも非常に相性が良いということでも知られています。TwitterFacebook上で友人・知人が話題にしている商品に反応し、思わず購入してしまった経験がある人も多いのではないでしょうか。特にTwitterは、ECにおいて商品選びに最も影響するSNSであると言われています。今回、Twitter上でどのような商品が最も話題になっているのか調べてみました。

f:id:hongo35:20121105005317j:plain

分析方法

 Amazon楽天Yahoo!ショッピングなどさまざまなECサイトがありますが、今回はAmazonの商品のみを対象としました。(1)ツイート本文中にアマゾンの短縮URL「amzn.to」(bit.lyの独自ドメイン利用)が含まれているツイートを収集し、(2)その短縮URLを展開、(3)そしてその展開したURLの中にアマゾンの商品コード(ASIN)が含まれていれば、それを集計するという流れで行いました。

(1) ツイートの収集
 API制限にかからないよう、Twitterのsearch APIを1回/minの間隔でcron実行して叩きました。

(2) 短縮URLの展開
 以下のようなスクリプトで展開し、展開したURLをDBに保存します。

gist11326277

(3) ASINの集計
 一般に、世の中で売られている各商品には商品識別コードEAN(JAN)が振られており、商品ページのURL中にそのコードが含まれている場合が多いのですが、Amazonの場合は、URL中にEANは含まれておらず、ASINと呼ばれるAmazon内部の独自コードが含まれています。したがって、展開したURLからASIN部分のみを抜き出し、同じ商品がどのくらいつぶやかれているのかを集計しました。

分析結果1(bot込み)

 分析対象日を2014年4月25日(金)として分析しました。まずは「amzn.to」を含むツイート件数の一分ごとの時系列推移です。

f:id:hongo35:20140429133842p:plain

ツイート総数: 172,251

 普通、ツイートの件数は昼に一度山があり、いったん下がって、夕方から夜にかけて再度件数が増えてくという曲線を描きますが、このグラフは1時間単位くらいの視点で見るとほとんど時間によって変動がありません。予想はしていましたが、Amazonの商品をおすすめしているのはほとんどがbotであるということがこのことからわかります。また、ある特定の時間だけツイート数が上昇していることからもbotが多いことが伺えます。
 実際にツイートをしているクライアントツール上位15位は以下のようになりました。

f:id:hongo35:20140429133900p:plain

 統計によると、普段使用されるクライアントツールの上位1,2,3位はTwitter for iPhone, Twitter for Android, webであるらしいので、それとはかなり違うことがわかります。
 そして、botによるツイートも含めて、ツイートされている商品の中で最もよく出現する商品(ASIN)のランキングは以下のようになりました。

1位.

手取り18万からの資産運用術

手取り18万からの資産運用術

2位. ええじゃないか【初回盤(WEST盤)】(CD+DVD)
3位. iPhone5s iPhone5 iPad mini 対応 充電 データ通信用ケーブル 巻き取り式
4位. ドラゴンエイジ:インクイジション デラックス エディション (限定版) (ダウンロードコード(スカイホールドの王座、赤角のハラ、沼のユニコーン、審問会の炎)、デジタルサウンドトラック、ボーナスデジタルコンテンツ 同梱)
5位. 確実に稼げる不動産投資 副業入門

いかにもbotがつぶやいていそうな商品が上位を占めています。。。
 これこそGIGO(Garbase in, Garbage out)で、ゴミをたくさん入れたらゴミな結果が出てきましたという感じなので、次にbotを除いたツイートのみで分析してみます。

分析結果2(人によるツイートのみ)

 人間によるつぶやきのみを抽出して分析します。人によるツイートの抽出方法としては、人間が使用するクライアントツールであるTwitter for iPhone, Twitter for Android, web, Keitai Web, Twitter for iPadからの投稿を、簡易的に人間がつぶやいたものと判断し、それらのツイートを分析しました。まずはツイート件数の時系列推移から見てみます。

f:id:hongo35:20140429183303p:plain

ツイート総数: 12,596

 分析結果1と比較すると件数が非常に少ないことがわかります。Twitter上に出回るAmazon商品は意外と少ないんですね。
 ツイートの中で最もよく出現する商品(ASIN)のランキングはこのようになりました。

1位.

2位. 幻想書庫 (ショートショート集?)
3位. WHITE ALBUM2 雪が紡ぐ旋律 5 (GA文庫)
4位. 妖怪ウォッチ お知らせブザー ジバニャン YW-01A
5位. 医療につける薬: 内田樹・鷲田清一に聞く (筑摩選書)

 こちらは、ランクインしている商品の商品ページを見てみると、最近話題になっているものや新着フラグがついているものなどが上位に来ていることから、その時話題になっている商品がきちんと抽出できているようです。アニメ系が多いのはTwitterAmazonのユーザー層からしてなんとなくわかりますね。。

まとめ

 Botによる投稿があまりにも多いので、実際に人がおすすめしている商品を抽出するには本分析の分析2のようなやり方をする必要があり、ノイズを取り除いた上でシグナルを見つけなければならないのだと思います。また、いくらECとソーシャルの相性が良いと言っても、これだけbotが多いと、本当の声がbotに打ち消されてしまう可能性が大きいよな、などと思ったりしました。

Chef Soloでelasticsearchの環境を作る

gemのバージョンはこんなかんじ。

berkshelf (2.0.10)
chef (11.8.2)
knife-solo (0.4.1)

複数台のサーバーにelasticsearchの環境を作ろうと思い、Chef Soloを使ってやってみた際のメモです。chefはあまり慣れてないので変なこと書いてるかもです。

リポジトリの作成

まずは、リポジトリを作成します。今回はchef-repoという名前のリポジトリにします。

$ knife solo init chef-repo
$ tree chef-repo
chef-repo
├── Berksfile
├── cookbooks
├── data_bags
├── environments
├── nodes
├── roles
└── site-cookbooks

Cookbookを作成

今回はBerkshelfを使って、サードパーティのcookbookを使いたいので、Berksfileにelasticsearchの記述を追記し、cookbooksディレクトリの下にインストールします。cookbookをダウンロードするためには、Opscode Communityにユーザー登録したり、いろいろ設定したり必要ですが、それに関してはChef Soloの正しい始め方などを参考にされるとよいかと思います。

site :opscode

cookbook ‘elasticsearch’
$ berks install -p cookbooks

roleを定義する

rolesディレクトリの下にelasticsearch.jsonファイルを作り、以下のように設定します。デフォルトでは、javaのバージョンが1.6.0_28、elasticsearchのバージョンが0.90.5でインストールされますが、より新しいバージョンのものを使いたいため、override_attributesでattributesを上書きし、javaは1.7系、elasticsearchは0.90.7をインストールするように書いています。また、elasticsearchで使用するkuromojiなどのpluginのインストールも同時に行うように、elasticsearch::pluginsというかたちで書きます。pluginインストールの設定については後述。

{
    “name”: “elasticsearch”,
    “default_attributes”: {},
    “override_attributes”: {
        “java”: {
            “install_flavor”: “openjdk”,
            “jdk_version”: “7”
        },
        “elasticsearch”: {
            “version”: “0.90.7”
        }
    },
    “json_class”: “Chef::Role”,
    “description”: “”,
    “chef_type”: “role”,
    “run_list”: [
        “recipe[java]”,
        “recipe[elasticsearch]”,
        “recipe[elasticsearch::plugins]”
    ]
}

plugin

インストールしたいelasticsearchのpluginに関しては、data_bagで設定してあげる必要があります。data_bags以下にelasticsearchディレクトリを掘り、その下にplugins.jsonファイルを作ります。elasticsearchのバージョンが0.90.7をインストールするので、kuromojiは、1.6.0を使います。また、私はいつもHQ pluginを使っているので、そちらも同時にインストールします。

{
    “id”: “plugins”,
    “_default”: {
        “plugins”: {
            “elasticsearch/elasticsearch-analysis-kuromoji”: { “version”: “1.6.0” },
            “royrusso/elasticsearch-HQ”: {}
        }
    }
}

cook

ここまでできたら、実際にcookしていきます。

$ knife solo prepare <host>

を実行し、nodes以下に作成された、\.jsonファイルのrun_listに、role[elasticsearch]を書き入れます。

{
    “run_list”: [
        “role[elasticsearch]”   
    ]
}

そして、cookします。

$ knife solo cook <host>

終了したら、ssh \でログインし、きちんとインストールできているか確認します。

$ java -version
java version "1.7.0_45"
OpenJDK Runtime Environment (rhel-2.4.3.4.el6_5-x86_64 u45-b15)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)
$ curl -XPUT ‘localhost:9200/test/‘ -d ‘
{ 
  “index”:{
    “analysis”: {
      “tokenizer”: {
        “kuromoji”: {
          “type”:”kuromoji_tokenizer”,
          “mode”:”search”
        }
      },
      “analyzer” : {
        “kuromoji_analyzer” : {
          “type” : “custom”,
          “tokenizer” : “kuromoji_tokenizer”
        }
      }
    }
  }
}'
{"ok":true,"acknowledged":true}

こんなかんじになれば、きちんとインストールできているかと思います。

$ curl -XGET 'localhost:9200/test/_analyze?analyzer=kuromoji_analyzer&pretty' -d '関西国際空港'
{
  "tokens" : [ {
    "token" : "関西",
    "start_offset" : 0,
    "end_offset" : 2,
    "type" : "word",
    "position" : 1
  }, {
    "token" : "関西国際空港",
    "start_offset" : 0,
    "end_offset" : 6,
    "type" : "word",
    "position" : 1
  }, {
    "token" : "国際",
    "start_offset" : 2,
    "end_offset" : 4,
    "type" : "word",
    "position" : 2
  }, {
    "token" : "空港",
    "start_offset" : 4,
    "end_offset" : 6,
    "type" : "word",
    "position" : 3
  } ]
}

分かち書きもきちんとできました。 いろいろと細かい設定もしたいところですが、今回はここまで。

参考

cookbook-elasticsearch
Chef Soloの正しい始め方
Berkshelfを試してみる

TwitterネットワークにPagerankを適用する

 前回の記事で、Pagerankを実装したので、今回はそのアルゴリズムを(自分の)Twitterのネットワークに適用してみました。

f:id:hongo35:20131208020538j:plain

 具体的な手順は、まず、Twitter APIを叩いて、対象アカウントがフォローしているユーザーのユーザーIDを取得します。このAPIは、一回のリクエストにつき最大5000件のIDを取得できます。それ以上フォローしている場合には、ページングして再度APIを叩き、次の5000件を取得します。また、API制限に引っかからないよう、sleepさせる処理が必要です。
 次に、取得したIDのユーザーそれぞれがフォローしているユーザーを同様に取得します(かなり時間かかる)。そして、そのリストの中から、対象アカウントがフォローしているユーザー同士のリンクのみを抽出し、フォロー関係がある場合には1、ない場合には0とした行列を作ります。
 そして最後に、出来上がった[フォロー数 × フォロー数]の大きさの行列にPagerankアルゴリズムを適用し、各ユーザーに対するPagerankの値を求めます。
 これらの処理をRubyで実装したものが以下です。一部、DBを使ってます。


Calculate pagerank of twitter follow network.

 API制限があることと、糞スパースな行列データにアルゴリズムを適用している影響で、フォロー人数が400人程度のアカウントで、処理が終わるまでに数時間かかりました。。。 (Pagerankの実装方法を違ったものにすればよかったと後悔)なので、nohupとかで実行するのがいいと思います。pagerank.csvとかのファイルに出力して、Rを使って可視化したものが以下です。


f:id:hongo35:20131207232712p:plain

 @hongo35がフォローしているアカウントの中で、ページランクの高かったアカウントTOP10は、こんなかんじになりました。

  1. @h_ototake
  2. @yukihiro_matz
  3. @KaworiM0531
  4. @sasakitoshinao
  5. @masason
  6. @AntiBayesian
  7. @t_wada
  8. @takapon_jp
  9. @meso
  10. @TechCrunch

すごく納得感があります。。
 データ収集と計算に多少時間がかかってしまいますが、どんなアカウントのフォローネットワークに対しても適用することができるので、実際にやってみるとおもしろいかと思います。

RubyでPageRankアルゴリズムを実装する


みなさんご存知、Webページ重要度の自動判定アルゴリズムであるPageRankですが、この記事

PageRankアルゴリズムを使った人事評価についての実験 | 株式会社サイバーエージェント

を読んで、PageRankアルゴリズムを身の回りのさまざまなリンク構造を持ったデータに対して適用してみたいなぁと思い、実装してみました。Rとかで既存のライブラリを使って、データを入れたら結果だけが返ってきて、中でどんな計算してるかは知らない、みたいなのは何も理解したことにならないので、実際にアルゴリズムを調べて実装し、どんな計算をしているのかまで理解するためにやりました。

f:id:hongo35:20131118025952p:plain


PageRankアルゴリズム

PageRankアルゴリズムは、「たくさんの良質なページからリンクされているようなページは、やはり良質なページである」という再帰的な関係をもとに、あらゆるページの重要度を計算したもので、引用に基づく学術論文の評価と似たアルゴリズムです。このアルゴリズムのポイントとしては、以下の3点が挙げられます。


1. 重要な論文はたくさんの論文から引用されるので、被引用数が多くなる

 → 重要なWebページはたくさんのページからリンクされるので、被リンク数が多くなる

2. 被引用数の多い論文から引用されている論文は、重要度が高い

 →被リンク数の多いページからのリンクは価値が高い

3. 多くの論文を引用している論文からの引用は価値が低い

 → リンク元ページのリンク数が多いページからのリンクは価値が低い

(ページランク - Wikipediaより引用)


数学的に注意しなければいけないことは、ランダムウォーク(マルコフ連鎖)がエルゴード性(集合平均と時間平均が一致するという性質)を満たす必要があるということです。 そのエルゴード性を満たすためにテレポーテーションを導入します。つまり、

・確率αで, 今いるページにあるリンクを一つ等確率で選ぶ

・確率1 - αで、でたらめなページ(等確率で)にテレポート

という実装をします。そして、Rubyで実装したものがこちら。

gist7513104

実行結果

----------
alpha: 1.0
[0.16666674613952637, 0.27777934074401855, 0.33333325386047363, 0.22222065925598145]
----------
alpha: 0.8
[0.1785715084624, 0.2704091523808, 0.3214284915376, 0.22959084761919998]
----------
alpha: 0.5
[0.20000008174320807, 0.26000069598110753, 0.29999991825679195, 0.23999930401889247]
----------
alpha: 0
[0.25, 0.25, 0.25, 0.25]

参考資料で実装されているものと結果が同じになったので、これでおkかと。 一般的には、ダンピングファクターの値(コード中のalpha)は0.85くらいにします。

あとはTwitterのフォロー・フォロワーネットワークとかから行列データを作って、PageRankを実行してみたいなと思ってます。 それについてはまたこんど。


参考資料

Google の秘密 - PageRank 徹底解説

リンク解析とか: 重要度尺度と von Neumann カーネル

リンク解析と周辺の話題

Rubyで生成したネットワークデータをd3.jsで可視化する

 学術分野としてはやや下火になっている分野ですが、私たちの友人ネットワークがどのように成長していくかをモデル化し、現実のソーシャルネットワークの形成過程を明らかにしようという研究があります。

 一般的に、「友達の友達とは友達になる可能性が高い」や「コミュニティがネットワーク形成に大きく関係している」などと言われていますが、本記事では、「友達の友達とは友達になる可能性が高い」という特性に着目したモデルである、CNN(Connecting Nearest Neighbor)モデルを使ってネットワークデータを作り、そのデータをd3.jsで可視化してみたいと思います。


CNNモデル

 CNN(Connecting Nearest Neighbor)モデルは、「友達の友達は友達」の関係に従ったネットワークモデルで、現実世界に存在するネットワークに共通する性質である「スケールフリー性」、「クラスター性」、「スモールワールド性」を満たすグラフを生成するアルゴリズムの一つとなっています。詳細アルゴリズムは、

(1) パラメータ u(0 \le u \le 1)を設定する。
(2) 各ステップにおいて、以下のアルゴリズムを実行する。

(a) 確率 uでネットワーク中に新しいノード v_iを追加し、すでにネットワーク中に存在するノード v_j(j \not= i)をランダムに選び、ノード v_iとの間にリンクを張る。さらに、ノード v_jの隣接ノードすべてに、ノードv_iとの潜在的なリンク(ポテンシャルリンク)を設定する。


(b) 確率 1 - uでランダムにポテンシャルリンクを選び、実際のリンクに変換する。

のようになっています。


ネットワークデータの準備

 まず、CNNモデルを使って、ノードとリンクのデータを生成します。パラメータPの値と繰り返し回数については適宜変更してください。ただしこの後、d3.jsで可視化する際の描画負荷を考えると、繰り返しはmaxで500くらいにした方がいいです。

ruby cnn.rb > network.json

を実行すると、CNNモデルによって生成されたネットワークデータのjsonファイルができます。


d3.jsで可視化

 ネットワークを可視化するにはgephiやcytoscapeなどのツールがありますが、今回はd3.jsを使って可視化してみたいと思います。
コードはすごく単純で、d3.jsのサイトにあるサンプルを基にしました。


結果

 パラメータPの違いによって、下図のように、ノード数は多いけど次数(各ノードから出ているリンクの数)は小さいグラフや、ノード数は少ないけど次数が大きいグラフが描けるのではないかと思います。


f:id:hongo35:20130808161602p:plain

 CNNモデルはすごく単純なモデルなので、もっといろいろなモデルを組み合わせたネットワークや、TwitterFacebookなどの実際のネットワークを可視化してみたいなーなんて思ったり(そのときはd3.jsじゃなくてgephiとかじゃないと無理かと...)。

RubyでSVMを使ってみる

CentOS6.4にlibsvmを入れて、rubyでそれを使うまでの導入記事です。 rubyのバージョンは、2.0.0p247を使っています。

まず、SVM(Support Vector Machine)について軽く触れておきます。SVMは、教師あり学習(正解データを学習させる)を用いた識別手法で、 多くある識別手法のなかで最も認識性能が優れた学習モデルの一つであると言われています。様々な場面で使うことができ、例えば、テキストの極性評価であったり、ジャンル分けなどに使えます。

libsvm

何はともあれ、libsvmをインストールしましょう。EPELレポジトリを追加していないとインストールできないので、こちらの記事を参考にレポジトリを追加します。追加できたら、

$ sudo yum --enablerepo=epel -y install libsvm libsvm-devel

を実行します。

libsvmは単体で、shellから使うこともできますが、pythonrubyバインディングさせて、スクリプトで実行することもできます。なので、今回はrubyからlibsvmを実行できるようにします。

libsvm-ruby-swig

rubyからlibsvmを実行するためのgemはいくつかあるみたいですが、今回はlibsvm-ruby-swigというgemを使います。普通に、

gem install libsvm-ruby-swig

を実行して、gemをインストールしてください。

これで準備は整いました。次に、これらを使って、SVMを実行してみたいと思います。

SVMは一番始めに述べたように、教師あり学習の一手法です。したがって、段階としては、まず始めに正解データを学習させる段階があり、その後、識別したいデータを食わせるという流れになります。下記のサンプルコードでは、trainメソッドで正解データを学習させています([1,0,1]の場合には1、[-1,0,-1]の場合には-1が正解になっている)。また、ここでカーネルの種類やペナルティパラメータを設定します。そして、学習が終わったら、識別モデルができるので、そのモデルを使って、識別したいデータを与えてあげます。

require 'svm'

class SvmKlass
  def train
    prob = Problem.new([1,-1],[[1,0,1],[-1,0,-1]])
    params = Parameter.new(:kernel_type => RBF, :C => 10)
    m = Model.new(prob,params)
    m.save("svm.model")
  end

  def test
    m = Model.new("svm.model")
    res = m.predict([0,0,-1])
    puts res
  end
end

svm = SvmKlass.new
svm.train
svm.test

以上のように、rubyでも簡単にSVMを使った識別ができます。あとは、学習させるデータを整理することと、カーネルの選択、パラメータの設定をし、より良い精度で判別できるようにします。カーネル選択、パラメータの設定については、こちらのスライドが参考になります。

参考