まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:insights

IBM が提唱する新しい人工知能である「コグニティブコンピューティング」、その柱となる Watson アナリティクスの API を使った PHP プログラムを作ってみます。API そのものは REST のウェブ API なので、PHP 以外の別言語であっても簡単に移植できると思います(PHP だと記述がシンプル&見やすい、という理由で選んでいます)。


まず最初にこのサービスの内容を簡単に紹介します。まずはこちらのデモサイトにアクセスしてみてください:
https://watson-pi-demo.mybluemix.net/

2015060801


画面左のテキストエリアに長文が入力されています。これはサンプルで、ある一人の人が誰かに宛てて書いたメールの本文だと思ってください。そして、この Personality Insights API はそのメール本文で使われている単語や文法を元に、書いた人の性格を分析して、その結果を返してくれる、というものです。実際に分析を実行するには "Analyze" ボタンをクリックします。

すると画面右に分析結果が一覧表示されます:
2015060802


分析結果はカテゴライズされていて、例えば "Openness(社交性)" が 94%、その Openness を更に細分化すると、Adventurousness(冒険性) が 72%、Artistic interests(芸術への興味) が16%、・・・ といった感じになっています。

また、これは人工知能とは異なりますが、オプションとしてこの結果を視覚化する機能も提供されています(deprecated)。画面を下にスクロールするとグラフ化された結果が確認できます:
2015060803


Watson Personal Insights API は、このように「入力したテキスト」を元に「性格分析結果の JSON」を返す API です。最新の API 詳細についてはリファレンスを参照ください:
http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/personality-insights

このリファレンスにも記述されていますが、性格分析を行う機能は /v2/profile という API で提供されています。このエンドポイントに対して、入力テキストをそのまま POST すればよい、という簡単な API のようです。


というわけで、実際にこのサービスを使って性格分析を行う、という内容のシンプルなプログラムを PHP で記述してみます。 

なお、この API は現在対応言語が英語のみとなっているため、ポストする入力テキストは英語で記述されている必要があり、かつある程度の精度を確保する目的で 100 単語以上をポストする必要があります。場合によっては事前に翻訳してから実行する、という処理が必要になることをご了承ください。


IBM Bluemix にログインし、(PHP の)ランタイムを1つ作成してください。ここまでの手順がよく分からない場合はこちらのエントリを参照してください:
http://dotnsf.blog.jp/archives/1000961115.html

この例では dotnsf-watsonpi という名前の PHP ランタイムを1つ作成しています。ここで「サービスまたは API の追加」をクリックします:
2015060901


サービスの一覧が表示されます。非常に多くのサービスが並んでいますが、Watson カテゴリ内の "Personality Insights" サービスをクリックして選びます:
2015060902


サービス内容を確認します。この Personality Insights API は有料サービスであり、API を月に100回実行するまでは無料です。それ以降は1回の実行につき63円かかります(Bluemix の30日間無料トライアル期間中であれば101回目以降も無料です)。この価格に関しては今後変更される可能性があるので、最新の情報についてはその都度確認してください。最後に先程作成したランタイムの名前(この例では dotnsf-pi)が選択されていることを確認して「作成」ボタンをクリックします。更に再ステージングについて問われたら「再ステージ」をクリック:
2015060903


これで PHP のランタイムに Watson Personality Insights サービスがバインドされました。この時点で API を利用するための資格情報を確認できます。資格情報を確認するには Personality Insights サービスアイコンの「資格情報の表示」と書かれた部分をクリックします:
2015060904


画面が縦に広がって、このような情報が出力されます:
2015060905


出力された中身はこのような内容の JSON テキストになっています。ここに API を実行するために不可欠な情報(特に赤字部分)が書かれています。なお username と password はこの API を実行する際に指定するユーザー名とパスワードで、実際にはランダムに生成された文字列になっています:
{
  "personality_insights": [
    {
      "name": "Personality Insights-74",
      "label": "personality_insights",
      "plan": "IBM Watson Personality Insights Monthly Plan",
      "credentials": {
        "url": "https://gateway.watsonplatform.net/personality-insights/api",
        "username": "(username)",
        "password": "(password)"
      }
    }
  ]
}

この url の情報と、上記の API リファレンスを組み合わせると API が実行できます。例えば上記デモサイトで行ったような、あるテキストメッセージを書いた人の性格を API で問い合わせる場合を調べてみます。

リファレンスによると "/v2/profile" という関数が目的の検索機能であり、ここに POST メソッドの本文としてメール本文の内容を付与して実行すればいい、ということが書かれています:
2015060906


実際に API を実行するプログラムを記述する際の API エンドポイントは上記 JSON に書かれていた url 値である "https://gateway.watsonplatform.net/personality-insights/api" に、リファレンスの内容を足した "https://gateway.watsonplatform.net/personality-insights/api/v2/profile" になります。この URL に JSON 内の username および password の情報を使って Basic アクセスをして、性格を解析したいテキスト本文を POST する、という処理内容を記述することになります。

では書かれている通りの内容で PHP プログラム(wpi.php とします)を作ってみると、こんな感じになりますかね。エラーハンドリングなどは省略して、なるべくシンプルにしています。TIPS として触れておくと HTTP リクエストヘッダに "Content-Type: text/plain" の指定が必須です:
<?php
// エンドポイント
$url = 'https://gateway.watsonplatform.net/personality-insights/api/v2/profile';

// 認証
$username = '(username)'; // JSON に書かれていた内容を指定
$password = '(password)'; // JSON に書かれていた内容を指定

// 本文(198単語)
$data = "Call me Ishmael. Some years ago-never mind how long precisely-having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people's hats off-then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.";

// POST で API 実行
$headers = array(
    'Authorization: Basic '.base64_encode($username.':'.$password),
    'Content-Type: text/plain' // 必須指定
);
$options = array('http' => array(
    'method' => 'POST',
    'content' => $data,
    'header' => implode("\r\n", $headers)
));
$result = file_get_contents($url, false, stream_context_create($options));

// 結果を出力
echo $result;
?>

このプログラム(wpi.php)を実行すると、こんな感じの結果が出力されます(見やすいように改行と赤字コメントを加えています。ただ性格分析は僕の専門ではないので、おかしな翻訳やコメントになっていたらごめんなさい):
$php -f wpi.php

{
 "id":"*UNKNOWN*",
 "source":"*UNKNOWN*",
 "word_count":198, 単語数
 "word_count_message":"There were 198 significant words in the text, we recommend text with at least 100 (and preferably 2,000) words",
 "tree":{ 分析結果のツリーはここから
  "id":"r",
  "name":"root",
  "children":[
   {
    "id":"personality",
    "name":"Big 5 ", ビッグ5と呼ばれる性格分析結果
    "children":[
     {
      "id":"Neuroticism_parent",
      "name":"Emotional range", 感受性
      "category":"personality",
      "percentage":0.8918895452867617, 89%
      "children":[ 感受性の子要素
       {
        "id":"Openness",
        "name":"Openness", 社交性
        "category":"personality",
        "percentage":0.715912798590218, 72%
        "sampling_error":0.28262000000000004, そのサンプリングエラー率 28%
        "children":[ 社交性の子要素
         {
          "id":"Adventurousness",
          "name":"Adventurousness", 冒険心
          "category":"personality",
          "percentage":0.24200589364589645, 24%
          "sampling_error":0.2349836 そのサンプリングエラー率 23%
         },
         {
          "id":"Artistic interests",
          "name":"Artistic interests", 芸術への興味
          "category":"personality",
          "percentage":0.1779719351186918, 18%
          "sampling_error":0.47357392 そのサンプリングエラー率 47%
         },
         {
          "id":"Emotionality",
          "name":"Emotionality",
          "category":"personality","percentage":0.8348632312299376,
          "sampling_error":0.26339703999999997
         },
         {
          "id":"Imagination",
          "name":"Imagination",
          "category":"personality",
          "percentage":0.40246782944324716,
          "sampling_error":0.31812512000000004
         },
         {
          "id":"Intellect",
          "name":"Intellect",
          "category":"personality",
          "percentage":0.5337358423377544,
          "sampling_error":0.28229272
         },
         {
          "id":"Liberalism",
          "name":"Authority-challenging",
          "category":"personality",
          "percentage":0.9036798826551202,
          "sampling_error":0.36567567999999995
         }
        ]
       },       {
        "id":"Conscientiousness",
        "name":"Conscientiousness",
        "category":"personality",
        "percentage":0.5445535670078625,
        "sampling_error":0.35129408,
        "children":[
         {
          "id":"Achievement striving",
          "name":"Achievement striving",
          "category":"personality",
          "percentage":0.2700341317349067,
          "sampling_error":0.32485944
         },
         {
          "id":"Cautiousness",
          "name":"Cautiousness",
          "category":"personality",
          "percentage":0.742529850394309,
          "sampling_error":0.36339328000000004
         },
         {
          "id":"Dutifulness",
          "name":"Dutifulness",
          "category":"personality",
          "percentage":0.3009058388302808,
          "sampling_error":0.44760968
         },
         {
          "id":"Orderliness",
          "name":"Orderliness",
          "category":"personality",
          "percentage":0.9511955761089055,
          "sampling_error":0.294814
         },
         {
          "id":"Self-discipline",
          "name":"Self-discipline",
          "category":"personality",
          "percentage":0.2560312591429979,
          "sampling_error":0.38256207999999997
         },
         {
          "id":"Self-efficacy",
          "name":"Self-efficacy",
          "category":"personality",
          "percentage":0.6787093695767238,
          "sampling_error":0.41188784
         }
        ]
       },
       {
        "id":"Extraversion",
        "name":"Extraversion",
        "category":"personality",
        "percentage":0.14544123271874,
        "sampling_error":0.38825928,
        "children":[
         {
          "id":"Activity level",
          "name":"Activity level",
          "category":"personality",
          "percentage":0.032364142051857475,
          "sampling_error":0.48726208000000004
         },
         {
          "id":"Assertiveness",
          "name":"Assertiveness",
          "category":"personality",
          "percentage":0.1717311752830918,
          "sampling_error":0.4465296
         },
         {
          "id":"Cheerfulness",
          "name":"Cheerfulness",
          "category":"personality",
          "percentage":0.1353039029992872,
          "sampling_error":0.34023712
         },
         {
          "id":"Excitement-seeking",
          "name":"Excitement-seeking",
          "category":"personality",
          "percentage":0.031036118864266764,
          "sampling_error":0.36140536
         },
         {
          "id":"Friendliness",
          "name":"Outgoing",
          "category":"personality",
          "percentage":0.04360600032167501,
          "sampling_error":0.41489215999999995
         },
         {
          "id":"Gregariousness",
          "name":"Gregariousness",
          "category":"personality",
          "percentage":0.00993996956776937,
          "sampling_error":0.42565192
         }
        ]
       },
       {
        "id":"Agreeableness",
        "name":"Agreeableness",
        "category":"personality",
        "percentage":0.41731474192057555,
        "sampling_error":0.37315656,
        "children":[
         {
          "id":"Altruism",
          "name":"Altruism",
          "category":"personality",
          "percentage":0.1893141565019656,
          "sampling_error":0.4574676
         },
         {
          "id":"Cooperation",
          "name":"Cooperation",
          "category":"personality",
          "percentage":0.7569715715364396,
          "sampling_error":0.42429936
         },
         {
          "id":"Modesty",
          "name":"Modesty",
          "category":"personality",
          "percentage":0.05410449484777291,
          "sampling_error":0.43126808000000005
         },
         {
          "id":"Morality",
          "name":"Uncompromising",
          "category":"personality",
          "percentage":0.6031683897259557,
          "sampling_error":0.40826744000000004
         },
         {
          "id":"Sympathy",
          "name":"Sympathy",
          "category":"personality",
          "percentage":1.0,
          "sampling_error":0.47322736
         },
         {
          "id":"Trust",
          "name":"Trust",
          "category":"personality",
          "percentage":0.18556199834989792,
          "sampling_error":0.4375552
         }
        ]
       },
       {
        "id":"Neuroticism",
        "name":"Emotional range",
        "category":"personality",
        "percentage":0.8918895452867617,
        "sampling_error":0.28468672,
        "children":[
         {
          "id":"Anger",
          "name":"Fiery",
          "category":"personality",
          "percentage":0.8060263824156899,
          "sampling_error":0.25980440000000005
         },
         {
          "id":"Anxiety",
          "name":"Prone to worry",
          "category":"personality",
          "percentage":0.7723866874361663,
          "sampling_error":0.29556752
         },
         {
          "id":"Depression",
          "name":"Melancholy",
          "category":"personality",
          "percentage":0.5106478670677633,
          "sampling_error":0.3757664
         },
         {
          "id":"Immoderation",
          "name":"Immoderation",
          "category":"personality",
          "percentage":0.6744415372949466,
          "sampling_error":0.26551199999999997
         },
         {
          "id":"Self-consciousness",
          "name":"Self-consciousness",
          "category":"personality",
          "percentage":0.7924144626881476,
          "sampling_error":0.4019884
         },
         {
          "id":"Vulnerability",
          "name":"Susceptible to stress",
          "category":"personality",
          "percentage":0.9138950516717607,
          "sampling_error":0.29872272
         }
        ]
       }
      ]
     }
    ]
   },
   {
    "id":"needs",
    "name":"Needs",
    "children":[
     {
      "id":"Closeness_parent",
      "name":"Closeness",
      "category":"needs",
      "percentage":1.0,
      "children":[
       {
        "id":"Challenge",
        "name":"Challenge",
        "category":"needs",
        "percentage":0.9821912864702969,
        "sampling_error":0.81476168
       },
       {
        "id":"Closeness",
        "name":"Closeness",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.8723808799999999
       },
       {
        "id":"Curiosity",
        "name":"Curiosity",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.78142888
       },
       {
        "id":"Excitement",
        "name":"Excitement",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.7837571999999999
       },
       {
        "id":"Harmony",
        "name":"Harmony",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.82233096
       },
       {
        "id":"Ideal",
        "name":"Ideal",
        "category":"needs",
        "percentage":0.04112302144450648,
        "sampling_error":0.77215992
       },
       {
        "id":"Liberty",
        "name":"Liberty",
        "category":"needs",
        "percentage":0.0033214357670770067,
        "sampling_error":0.81046376
       },
       {
        "id":"Love",
        "name":"Love",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.85773144
       },
       {
        "id":"Practicality",
        "name":"Practicality",
        "category":"needs",
        "percentage":1.0,
        "sampling_error":0.83880064
       },
       {
        "id":"Self-expression",
        "name":"Self-expression",
        "category":"needs",
        "percentage":0.0068972518833839295,
        "sampling_error":0.79480384
       },
       {
        "id":"Stability",
        "name":"Stability",
        "category":"needs",
        "percentage":0.07939535553110567,
        "sampling_error":0.80823464
       },
       {
        "id":"Structure",
        "name":"Structure",
        "category":"needs",
        "percentage":0.02549425311812723,
        "sampling_error":0.06421888
       }
      ]
     }
    ]
   },
   {
    "id":"values",
    "name":"Values",
    "children":[
     {
      "id":"Conservation_parent",
      "name":"Conservation",
      "category":"values",
      "percentage":0.00995039863428774,
      "children":[
       {
        "id":"Conservation",
        "name":"Conservation",
        "category":"values",
        "percentage":0.00995039863428774,
        "sampling_error":0.4966044
       },
       {
        "id":"Openness to change",
        "name":"Openness to change",
        "category":"values",
        "percentage":0.9711138591560894,
        "sampling_error":0.5254172
       },
       {
        "id":"Hedonism",
        "name":"Hedonism",
        "category":"values",
        "percentage":0.9245332640747028,
        "sampling_error":0.51724872
       },
       {
        "id":"Self-enhancement",
        "name":"Self-enhancement",
        "category":"values",
        "percentage":0.3464581176518462,
        "sampling_error":0.50278936
       },
       {
        "id":"Self-transcendence",
        "name":"Self-transcendence",
        "category":"values",
        "percentage":0.8998366368666929,
        "sampling_error":0.47211168
       }
      ]
     }
    ]
   }
  ]
 }
}

JSON が複雑な親子関係になってはいますが、基本的にはデモサイトの画面右側に出力されていたものと同じ構成の結果が返ってきている、ということが分かると思います。更によく見ると、各項目ごとの結果には "percentage" という数値結果に加えて、"sampling_error" という数字があることに気付きます。これはデモサイトの結果には表示されていない情報です。

この sampling_error(サンプリングエラー率)とは、いわゆる「誤差」のことです。つまり本来の値と、今回の入力に使ったテキストだけから得られた結果との間にどれだけの誤差が生じている可能性があるか、という数値です。要はこの数値が小さい項目ほど結果が正しい可能性が高くなる、という数値です。デモサイトだけを使っているとこのサンプリングエラーを意識することはありませんが、実はこういった誤差率までが取得できる API が用意されている、ということになります。


で、後は得られたこの結果をどうやって使うのか、そもそも入力テキストをどこからどうやって取得するのか、、、 それは開発者のアイデア次第です! ということで(笑)

 

IBM Bluemix から提供されている各種サービスについて、開発者視点から実際の使い方を紹介してみようと思います。

ここで紹介するサンプルを開発する時に使う言語は迷いましたが、PHP を使うことにします。
最初にお断りしておくと、IBM Bluemix の API サービスは REST で提供されているものがほとんどで、原則的には(Get や Post などの HTTP アクセスが記述できれば)どの言語でも実現できます。ただ PHP だと(Java でいうところの JAR ファイルとか)拡張ライブラリなどが不要で素のまま HTTP アクセスが使えるということと、取得した結果の JSON テキストも素の PHP のままでデコードして使えるということと、実際にプログラミングとして記述した時に非常にシンプルに書ける、というメリットがあります。 といった観点で PHP を使って紹介しますが、(HTTP アクセスして、結果を取得して、JSON をデコードして、、という)同様の処理を実装すれば他の言語にも応用できるはずです。参考程度ですが、自分が業務用のデモを作る時はほぼ Java で書いてます。


記念すべき第一回目のテーマは IBM Insights for Twitter サービスにします:
https://www.ng.bluemix.net/docs/#services/Twitter/index.html#twitter

IBM が Twitter 社との提携によって decahose と呼ばれるツイート情報を提供されるようになりました。そのツイート情報に IBM のインサイトを加えてデータベース化した上で検索 API を提供しているのが、このサービスです。

このサービスを実際に使って動くデモアプリがこちらに用意されています:
https://cdetestapp.mybluemix.net/
2015060201


テキストフィールドに検索キーワードを入れて "Twitter Search" ボタンをクリックすると、そのキーワードで Twitter の検索(正確には Twitter から提供された情報を元に作られた IBM のデータベース内の検索)結果が表示されます。この例では "IBM" という検索キーワードに対して 333957 件ヒットし、その一部が画面に表示されています。ぶっちゃけ、ここまでは普通の Twitter API でもできることです:
2015060202


次に検索キーワードに "IBM sentiment:positive" と入力してみます。これは「IBM というキーワードを含み、かつ内容がポジティブなもの」を検索しています。結果として 53199 件見つかり、その一部が表示されています:
2015060203


この「内容がポジティブなもの」という検索方法に注目してください。少なくとも Twitter の標準 API ではこのような検索はできません。

IBM が Twitter からツイートの情報を提供されていることは上記で述べましたが、IBM はその内容を自分達のデータベースとして保存する際にストリーミング処理を施し、ツイートの内容やツイートの作者の情報に基づいた情報を独自に付加してデータベース化しています。その付加情報の中に内容のネガ・ポジ情報も含まれているため、このような検索が可能になっています。同様にして「IBM というキーワードを含み、かつ内容がネガティブなもの」という検索("IBM sentiment:negative")も可能です:
2015060204


ネガ・ポジ以外の付加情報も検索キーとして使えます。例えばこの例では「IBM というキーワードを含み、かつ内容がネガティブで、かつ子供がいる人からツイート」という検索("IBM sentiment:negative has:children")をして、その結果を表示しています。なんとなく推測できたかもしれませんが、スペースを付けて複数の条件を指定するとアンド検索になります:
2015060205


なお、検索はここのツイート情報まで表示させることもできますが、単にヒット数だけを調べることもできます。"Twitter Search" ボタンではなく "Twitter Count" ボタンで検索すると、個々のツイート情報は取得せず、数だけを調べることができます。当然ですがこちらの方が軽く動作します:
2015060206



以上、ざっくりとして紹介でしたがなんとなく使い道がわかったでしょうか? 繰り返しますが、IBM Insights for Twitter サービスはこのデモサイトのことではなく、あくまで「このデモサイトの実装に使われている APIです。極端な言い方をすれば、皆さんがこのデモサイトと同じようなサービスを作ることも可能な、そんな REST API が IBM Insights for Twitter サービスです。なお、この API サービスの関数リファレンスについてはこちらを参照ください。ま、要は「一定のルールで書かれた検索文字列を渡せば、その結果を JSON で返してくれる」というウェブ API です:
https://cdeservice.mybluemix.net/rest-api/#!/ibm_twitter_insights


では、実際にこのサービスを PHP から使って、プログラムを作ってみましょう! IBM Bluemix にログインし、(PHP でもそれ以外でもいいのですが)ランタイムを1つ作成します。ここまでの手順がよく分からない場合はこのエントリ等を参考にしてください:
http://dotnsf.blog.jp/archives/1000961115.html

この例では "dotnsf-twitter" という名前の PHP ランタイムを1つ作成しています。ここで「サービスまたは API の追加」をクリックします:
2015060207


サービスの一覧が表示されます。非常に多くのサービスが並んでいますが、「ビッグデータ」カテゴリ内に今回の目的である "Insights for Twitter" サービスが見つかります。これをクリックします:
2015060208



サービス内容を確認します(無料利用の条件などが書かれています)。そして「作成」ボタンをクリックすると、PHP ランタイムにこのサービスを追加します。更に再ステージングについて問われたら「再ステージ」を選択:
2015060209


これで PHP ランタイムに IBM Insights for Twitter サービスがバインドされました。これで API を利用するための情報が確認できます。API 利用情報を確認するには Insights for Twitter サービスアイコンの「資格情報の表示」と書かれた部分をクリックします:
2015060210

 
このような情報が出力されます:
2015060211

 
このような JSON フォーマットのテキストが書かれているはずです。ここに API を実行するために不可欠な情報(特に赤字部分)が書かれています。なお username と password はこの API を実行する際に指定するユーザー名とパスワードで、実際にはランダムに生成された文字列になっています:
{
  "twitterinsights": [
    {
      "name": "Insights for Twitter-8p",
      "label": "twitterinsights",
      "plan": "Free",
      "credentials": {
        "port": "433",
        "username": "(username)",
        "host": "cdeservice.mybluemix.net",
        "password": "(password)",
        "url": "https://(username):(password)@cdeservice.mybluemix.net"
      }
    }
  ]
}

この url の情報と、上記の API リファレンスを組み合わせると API が実行できます。例えば上記デモサイトでも行った「"IBM" というキーワードでツイートを検索」を API で問い合わせる場合を調べてみます。

リファレンスによると "/v1/messages/search" という関数が目的の検索機能であり、ここに "q=IBM" といった具合でパラメータを付与して GET メソッドで実行すればいい、ということが書かれています:
2015060212


これに上記 JSON に書かれたホスト名とユーザー名、パスワードを使って、試しにブラウザでアクセスしてみます。url 値は "https://(username):(password)@cdeservice.mybluemix.net" でした。API は /api パス以下にあるので、ウェブブラウザのアドレス欄に
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM
と入力してみます(username と password は実際に JSON に書かれているものを指定してください):
2015060213

成功すると↑のような JSON テキストが取得できます。これが IBM での検索結果です。もう少し見やすい形に整形するとこのような結果になっていることが確認できます(赤字はコメント):
{
 "search":{
  "results":334805, ヒットしたレコード数
  "current":100
 },
 "tweets":[ 個別のレコード(配列)
  {
   "message":{ ツイッターからの情報は message として取得できる
    "body":"*******", 本文
    "favoritesCount":0, お気に入り登録数
    "link":"http://twitter.com/****", 個別ツイートへのリンク
    "retweetCount":1, リツイート回数
    "twitter_lang":"en",
    "postedTime":"2013-11-15T16:45:53.000Z",
    "provider":{ ***** },
    "actor":{ ***** },
      :
   },
   "cde":{ IBMが付加した情報は cde として取得できる
    "content":{
     "sentiment":{"polarity":"NEUTRAL", "evidence":[] } 感情情報
    },
    "author":{
     "location":{"state":"North Carolina","country":"United States","city":""},
     "parenthood":{"evidence":"","isParent":"unknown"}, 親かどうかの情報
     "gender":"male", 性別
     "marialStatus":{"isMarried":"unknown","evidence":""} 結婚しているかどうかの情報
    }
   }
  },
  {
   "message":{
      :
  }
 ],
 "related":{
"next":{"href":"https://cdeservice.mybluemix.net/api/v1/messages/search?q=IBM&from=100&size=100"}
} }

詳しくはリファレンス内を参照していただきたいのですが、上記のような JSON フォーマットで検索結果が取得できています。

ちなみに検索文字列を "IBM sentiment:positive" にする場合は URL エンコードする必要があるので、
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM%20sentiment%3apositive
を指定することになります:
2015060201


余計な情報かもしれませんが、デフォルトではヒットしたレコードのうち、最初の100件だけを取り出します。この数を変えるには URL オプション size で指定します(例 size=200)。また100件目から150件目までの50件を取り出すには URL オプション from を使って from=100&size=50 のように指定します。

なお、個別のツイートは不要でヒットするレコード数だけを検索したい場合は search ではなく count を実行します:
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM
2015060202


この場合の JSON はツイート情報を含まないため、かなりシンプルになります。こちらなら一見して分かりますね:
{
 "search":{"results":334805},
 "related":{"search":{"href":"https://cdeservice.mybluemix.net/api/v1/messages/search?q=IBM"}}
}


さて、ここまで分かれば後は今のオペレーションを PHP で(或いは別の言語で)実装するだけです。例えばこんな感じの内容(test1.php)にしてみます:
<html>
<head>
<title>Test 1</title>
</head>
<body>

<?php
// URLパラメータ q が指定されている場合のみ API を実行する if( isset( $_GET['q'] ) ){ $q = $_GET['q']; if( $q ){ ?> <h1><?php echo( $q ); ?></h1> <table border="1"> <tr> <th>Sentiment</th><th>Body</th><th>DateTime</th> </tr> <?php
// (username) と (password) は実際に有効なものを指定する $url = "https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=" . $q;
// PHP の file_get_contents 関数で実行
$text = file_get_contents( $url );
// JSON デコード $json = json_decode( $text ); $results = $json->search->results; // ヒット数 $tweets = $json->tweets;
// 各ツイート情報ごとにテーブルの1行を作る foreach( $tweets as $tweet ){ $message = $tweet->message; $cde = $tweet->cde; $body = $message->body; // 本文 $link = $message->link; // リンク $postedTime = $message->postedTime; // 日時 $polarity = $cde->content->sentiment->polarity; // 感情 ?> <tr> <td><?php echo( $polarity ); ?></td> <td><a target="_blank" href="<?php echo( $link ); ?>"><?php echo( $body ); ?></a></td> <td><?php echo( $postedTime ); ?></td> </tr> <?php } ?> </table> (#<?php echo( $results ); ?>) <?php } } ?> </body> </html>

これを動かしてみます。HTTP サーバー上にこのファイルを置いて、test1.php?q=IBM のような感じで、検索ワードを指定してアクセスすると、このような検索結果の画面になります。一列目に検索結果の各ツイートの感情が表示されます:
2015060203


一方、今度は指定したキーワードの感情ごとのヒット数を一覧にしてみます。以下の様な内容の test2.php を用意します:
<html>
<head>
<title>Test 2</title>
</head>
<body>

<?php
if( isset( $_GET['q'] ) ){
  $q = $_GET['q'];
  if( $q ){
?>
<h1><?php echo( $q ); ?></h1>
<table border="1">
 <tr>
  <th>Sentiment</th><th>#</th>
 </tr>
<?php
    $sentiments = array( 'positive', 'neutral', 'ambivalent', 'negative' );
    $url0 = "https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/count?q=" . $q . "%20sentiment%3a";
    foreach( $sentiments as $sentiment ){
      $url = $url0 . $sentiment;
      $text = file_get_contents( $url );
      $json = json_decode( $text );

      $results = $json->search->results;
?>
 <tr>
  <td><?php echo( $sentiment ); ?></td>
  <td><?php echo( $results ); ?></td>
 </tr>
<?php
    }
?>
</table>
<?php
  }
}
?>

</body>
</html>
これも動かしてみます。HTTP サーバー上にこのファイルを置いて、test2.php?q=IBM のような感じで、検索ワードを指定してアクセスすると、このキーワードを positive(ポジティブ) / neutral(中間) / ambivalent(混合) / negative(ネガティブ) の4つの感情ごとにヒット数を求めて、一覧表にして表示する、という内容になっています:

2015060204


このくらいになると「指定したキーワードが Twitter 上でどういう感情割合で使われているか」を調べるツールとして使えそうです。後はもう少し視覚的に工夫すれば、もう少し実用的になれそうですよね。


今回は PHP でのサンプルを紹介しました。とはいえ、PHP に依存しているのはこのエントリでも最後の部分だけです(PHP は file_get_contents 関数が使えるのですごく楽です)。他の言語でも同様の REST API を実行するだけですので、移植にも是非チャレンジしてみてください。



 

先日、このブログでも紹介した、IBM Bluemix から提供されている性格分析の人工知能 API "Personality Insights" に関する新着情報です:
IBM Bluemix で性格分析サービスを使う


Bluemix から提供されているコグニティブ(学習型人工知能)サービスの先陣を切って、この Personality Insights API がベータ版を卒業し、正式サービスとしての運用が開始されました:
2015032000
↑「IBM ベータ」の「ベータ」が取れました。


そして正式公開に併せて価格も変更されました。これまではベータ版だったため、制限なく無料で利用できていましたが、今後は1回のコールにつき 63 円かかります。ただし月間 100 コールまでは無料枠として実行できるようになっています:
2015032001

今後、この API を利用する場合は料金にもご注意ください。なお、現時点では他のサービスはまだベータ版扱いとなっています。

(2015/03/21 補足)
この正式サービスインされた Personality Insights サービスも無料トライアル30日期間中は制限なく無料で利用できます。






 

IBM Bluemix から提供されているコグニティブ(Cognitive : 学習型人工知能)サービスの1つ、Personality Insights サービスを紹介します:
2015031601


このサービスは IBM が研究開発しているコグニティブエンジンの1つである WATSON サービスの1つで、与えられたテキスト情報(メール本文など、1人の誰かが書いたテキスト)だけを元に、その人の性格を分析する、というサービスです。残念ながらまだ日本語テキストには対応していませんが、英語メッセージであれば、ベータ版の現在は無料でこの機能を利用することが可能です。


このサービスの API が IBM Bluemix を通じて提供されます。つまり IBM Bluemix をお使いのユーザーであれば誰でもこの API を利用することができることになります。人工知能やその学習、ビッグデータ解析といった複雑な部分は全てブラックボックス化されており、単純にテキストをポストすれば結果を JSON フォーマットで取得できます。

このサービスはデモサイトが用意されています。API を使う前に、まずはこのサービスがどのようなものかをデモサイトを体験することで理解してみましょう:
Personal Insights Demonstration


上記ページを開くと、画面左ペインに英文テキストが表示されています。この中に解析したいテキスト(メール本文など)をコピー&ペーストなどで入力します。最初はデフォルト状態で入力されているテキストをそのまま使っても構いません:
2015031602


なお解析を行うには 100 単語以上からなるテキストを入力する必要があります(短すぎると解析できません):
2015031603


"Analyze" ボタンをクリックすると、このテキストが Personal Insights API に送られ、性格分析が行われます。その結果が画面右側に表示されます:
2015031604


更に下部には結果を視覚化したグラフが表示されています。この視覚化機能も現在は API の一部として提供されていますが、現在では deprecated 扱いになっており、将来的には使えなくなる(D3.js など別途視覚化ライブラリを使ってグラフ化する必要がある)予定になっています:
2015031606


上記のように、現在では与えられたテキストから、その著者の性格を分析して JSON フォーマットで返すという API と、その結果を更に視覚化する、という API の、2つの API が提供されています。ただし後者については将来的に廃止予定となっている、という状況です。まあ視覚化のための要素は JSON で取得できるため、後は何らかのライブラリを使って表示すればいいのだと思っています。グラフ化はあまりコグニティブな要素もないためだと思われます。


実際の使い方は API Reference を参照いただきたいのですが、Personality Insights プロファイル API のエンドポイントである https://gateway.watsonplatform.net/personality-insights/api/v2/profile (実際にはこの値も環境変数から取得できます) に対して、認証情報と解析したいメッセージ本文をプレーンテキストで POST すると、解析結果が以下のような、サンプリング誤差率※も含めたツリー型の JSON フォーマットで取得できる、というものです(赤字はコメント):
{
 "id":"*UNKNOWN*",
 "source":"*UNKNOWN*",
 "word_count":2196,  単語数
 "tree":{
  "id":"r",
  "name":"root",
  "children":[
   {
    "id":"personality",  性格
    "name":"Big 5 ",  性格の5大要素
    "children":[
     {
      "id":"Openness_parent",
      "name":"Openness",  社交性
      "category":"personality",
      "percentage":0.9493716242287923,  94.94%
      "children":[
       {
        "id":"Openness",
        "name":"Openness",  社交性
        "category":"personality",
        "percentage":0.9493716242287923,  94.94%
        "sampling_error":0.14430105599999998,  サンプリング誤差率 14.43%
        "children":[
         {
          "id":"Adventurousness",
          "name":"Adventurousness",  冒険心
          "category":"personality",
          "percentage":0.7224550516937974,  72.25%
          "sampling_error":0.11646272  サンプリング誤差率 11.65%
         },
         {
          "id":"Artistic interests",
          "name":"Artistic interests",  芸術性への興味
          "category":"personality",
          "percentage":0.16797400413558944,  16.80%
          "sampling_error":0.22150304  サンプリング誤差率 22.15%
         },
         {
             :
         }
       },
       {
         :
       }
      ]
     },
     {
       :
     }
   }
  ]
 }
}
※サンプルを元に推定した結果と、実際の値との誤差。この値が小さいほど精度が高い推測値、ということになる

後はアプリケーション側で、この得られた JSON テキストを解析して、一定の誤差率を下回るものだけを対象にするなどして性格分析結果を可視化する、といった処理をすることになると思います。


この Personality Insights API に関しては、解析結果に確実性だけでなくサンプリング誤差率まで含めて提供されるので、アプリケーション側での扱いにも自由度というか、独自性を出しやすい API だと感じました。




 

このページのトップヘ