2023年12月30日土曜日

シミュレーションゲームを使った行動分析

Twineというツールがある.もともとこのツール,「対話的非線形物語(interactive, nonlinear stories)」あるいは「あなた自身の冒険を作成(create-your-own-adventure)」と呼ばれる,オンライン版のテキストアドベンチャーゲームを作成するツールである.

このツールを用いて行動分析できないかと今年の卒研生が考えた.彼らは「避難行動のシミュレーションゲーム」を作成し,いま,その分析をしながら卒論を書いている.

もとより簡単な行動ログを記録する機能が用意されていたが,より簡便かつ確実に,詳細な行動記録を取れるように拡張してみた.本稿ではその概要を説明する.

Snowmanの利用

まず,Twine上で動作させるstory formatとしてSnowmanと呼ばれるものを利用する.Twineでは,いくつかのスクリプティングシステムを切り替えて活用できる.そのスクリプト言語のことをstory formatと呼んでいる.

デフォルトではHarloweと呼ばれるものが組み込まれているが,これを,Snowmanと呼ばれるstory formatに切り替える.Snowmanは,CSSやJavaScriptをある程度理解している人向けに用意されているもので,PHPやERB(Embedded Ruby)のように,<% …… %>や<%= …… %>といったタグを用いてJavaScriptをそのまま埋め込められる.

なお,Snowmanへの切り替えは,Twine編集画面のStoryタブにあるDefaultsメニューから行う.

Story JavaScriptの設定

Story formatをSnowmanに切り替えたら,まず,StoryタブのJavaScriptメニューを開き,Story JavaScriptに以下のコードを記載する.

window.story.state.hash = {};


// create a unique ID

window.story.state.uid = Date.now().toString(16) +

            Math.floor(1000 * Math.random()).toString(16);


$(window).on('sm.passage.shown', 

             function (eventObject, passageObject) {

  window.story.state.

    hash[passageObject.passage.name] = eventObject.timeStamp


  // disable the browser's back button

  history.pushState(null, null, null);

  return;

});


// to show the alert to quit the game

$(window).on('beforeunload', function (event) {

  event.preventDefault();

  return;

});

このコードは,ストーリーが起動されたときに最初に走るJavaScriptのプログラムである.なお,Snowmanでは,JavaScriptの動作スコープは各passage(ページのこと)内に閉じられており,グローバル変数をwindow.story以外に持たない.したがって,ストーリー全体でデータをやりとりしたい場合には,基本的にはwindow.story.state変数にいろいろと付け加えていくしかない.

そのような理由により,まずは,window.story.state.hashという連想配列を用意し,動作状況はそこに記録していくことにする.

なお,もうひとつ,window.story.state.uidというユーザIDを記録する変数も用意し,そこに一意な文字列として作成するIDを格納する.

Snowmanでは,passage(ページ)遷移時にsm.passage.shownというイベントを発火する.各passageのアクセス記録を取りたければ,そのイベントを受けて,先の連想配列に,passage名をキー,その時点でのタイムスタンプ(Unixエポック)をバリューとして,記録するようにしておけばよい.passage名はpassageObject.passage.nameとして,タイムスタンプはeventObject.timestampとして与えられるので,それを連想配列に記録する.

残りのコードは,ブラウザの「戻る」ボタンを無効にしたり,途中でやめようとしたときにアラートを発したりという設定を行うものである.

記録のテスト

簡単なストーリーを用意した.次の図は,四つのpassageから成るストーリーである.Story JavaScriptには先のコードが入れられており,最後のページには,記録されたユーザIDとJSON形式で提示されるアクセス一覧を表示するコードが埋め込まれている.

このストーリーを実行して最後のページ(The last page)まで遷移すると,次の画面が現れる.

一意なユーザIDと,JSON形式で表現されたアクセスログが表示されていることを確認できる.

ところで,ここまで注意深く読んできたひとは,"The last page"が記録されていないじゃないか?ということに気付いただろう.そのとおり.残念ながら最後のページの記録が残らない.これは,どうも,sm.passage.shownというイベントが,ページのレンダリングの「後で」発火するから,という仕様によるものらしい.

なお,ドキュメントにはsm.passage.hideやsm.passage.showというイベントもあると書いてあり,shownではなくshowを使えば最後のページも記録できそうなものだが,現時点で利用できるTwine 2.8.0とSnowman 2.0.2の組合せではsm.passage.shownしか使えなかった.残念だが,致し方ないところである.最後まで記録するためには,最後にもうひとつ,ダミーのページを用意して対処する必要がある.この点には注意しなければならない.

サーバへ送信

さて,ここまで確認できれば,あとは,最後のpassageにサーバへ記録を送信するコードを埋め込み,サーバ側ではその送信を受け取るコードを用意すればよいだけである.

<%

  $.ajax({

    url: 'https://your.server.domain/php/tlog.php',

    type: 'POST',

    data: { 'uid': window.story.state.uid,

            'log': JSON.stringify(window.story.state.hash) },

    dataType: 'json'

  });

%>

このコードはyour.server.domainというサーバに結果を送信するAJAXのコードである.ユーザIDとJSON形式で表現したアクセスログを送っている.

なお,アクセスログのデータは ... 'log': JSON.stringify(window.story.state.hash) }ではなく... 'log': window.story.state.hash }としてもそのままJSONデータとして送信できるが,サーバ側での処理を簡易化するため,この段階で文字列データ化して送信している点に注意.

サーバ側の処理

サーバ側のプログラムは,/var/www/html/php/tlog.phpというファイルにおいて,次のように記述する(サーバ側はUbuntu 20.04, PHP 7.4.3 および SQLite3 3.31.1を使用).

<?php

  $uid = $_POST['uid'];

  $log = $_POST['log'];


  $dbfile = './tlog_data.sqlite3';

  $conn = new PDO('sqlite:' . $dbfile);


  // create table

  $ddl = 'CREATE TABLE IF NOT EXISTS userdata(

             id integer primary key autoincrement, 

             uid string, log text)';

  $conn->query($ddl);


  // insert posted data

  $sql = 'INSERT INTO userdata(uid, log) 

          VALUES (:uid, :log)';

  $stmt = $conn->prepare($sql);

  $stmt->execute(array(':uid' => $uid, ':log' => $log));

?>

以上の比較的単純なコードで,ユーザ行動を記録できる.次のスクリーンショットは,記録の例である.左からID,ユーザID,アクセスログが記録されている状況を確認できよう.

今回は単純な線形のストーリーであるため,全て同じ行動でタイムスタンプが違うだけだが,複雑な非線形ストーリーを用意すれば,全ての選択行動をデータベースに記録でき,その結果を分析すればユーザの判断行動もわかるという仕組みである.

行動分析の例

最後に,行動分析の事例を紹介しておこう.いま卒論に取り組んでいる学生たちが用意したストーリーで得られた,ユーザの選択行動データである.テーマは「避難行動」,大地震に遭遇したときの避難状況において,ユーザはどういった判断をするのかという分析である.

次の図は,アクセスログから解析した行動状況を示すものである.ノードは各passageを示し,ノードに付記されている数値は,そのpassageを通過したユーザの数を表す.また,ノードの色は滞在時間の平均値から計算されたものであり,寒色系は短い時間,暖色系は長い時間を表している.白は誰も通過しなかったノード,および,最後のノードである(最後のノードはそもそも滞在時間を計測できないため).

この図は,アクセスログと,Twineから出力される.tweeファイルに基づき,PythonとGraphvizを用いて作成した.アクセスログだけでなく.tweeファイルも必要な理由は,誰も通過しなかったノードはアクセスログから掘り起こせないからである(なお,その作業を行ったことにより,どこからも参照され得ない不適切なノードの存在も発覚した.そのような不適切なノードは分析作業の対象外として,先の図には表していない).

この結果から,いくつか意義のある分析結果が得られている.ここでは,ひとつの興味深い事例を紹介しよう.


「道が分かれている」というpassageでは,ハザードマップを見たことがあるか否かの質問を投げかけている.「はい」を選んだグループと「いいえ」を選んだグループでは,明らかに,その後に投げかけられた「どの場所を通っていきますか?」という設問の回答に差が出ている.

見ただけで,ハザードマップを見たことがあるグループは大通りを選ぶ傾向がある一方で,見たことがないグループはそうでもないことがわかる.実は,ハザードマップには大通りを選ぶべきであるヒントが記されていた.したがって,素直に解釈すればハザードマップを見たグループは大通りを選ぶ一択になるはずなのである.この結果に対してカイ二乗検定を実施したところ,1%水準で有意差が示された.

2023年12月9日土曜日

悪質タクシーにご用心

ゆうべ,こんなことがあった.

昨日はサイバーワールド研究会が三田のNECで開催されたため,夜はNEC芝倶楽部で懇親会をした.懇親会を早い時間から始めたので,お開きになったのは20時頃.そして昨夜は,たまたまMRIの元同僚の皆さんが赤坂で飲んでいるとかでそちらからも声がかかっており,三田から赤坂に移動することにした.

三田で飲んでいたOさんと二人で赤坂に向かうことになったが,すでにかなり酔っ払っていて田町から赤坂までの最適な移動方法がすぐに思い浮かばない.大柄なOさんということもあって「面倒だからタクシーで行っちゃおう」と,OさんがタクシーGOというアプリで車を呼んだ(※ここ重要なので覚えといてね).

しばらくして個人タクシーが一台やってきて,我々はそれに乗車,赤坂に向かった.なお,座席は私が奥で運転手の後ろ,Oさんは入り口側である.タクシーに乗るのはずいぶんと久しぶりで,(初乗り500円だけど,100円ずつ,けっこういい調子でメーターが上がっているなあ)などと酔った頭でボーッと見ていた.

よく喋る運転手で,Oさんと運転手はずいぶんいろいろ話をしていた.タクシーやるなら個人タクシー,リスクもあるけど儲かる,なんて話.私はなんとなく億劫になっていて,それを聞くともなく,外を見たりメーターを見たりと所在無げにしていた.

さて,赤坂に近づき「どのへんでお降りになります?」と尋ねられたので「TBSのあたりで」と答えた.赤坂の手前,溜池山王を過ぎて日枝神社の前を赤坂に曲がる交差点でちょうど赤になったので,「あ,いまちょうど赤信号だから」と,我々はそこで降りることにした.

メーターを見たら2,400円.そんなもんかなと,財布から1,000円札を2枚出す.ポッケのなかの小銭を探していたら「あ,2,000円でいいですよ」と運転手.「えらいまあ気前のいい運転手だなあ,メーターがやたらと早く回るタクシーだから,それ差っ引いてそういうサービスなのかなあ」などと酔った頭で考えつつ,千円札2枚,2,000円を支払って車を降りた.

さて,先に降りていたOさんと歩道で歩きながら,その件に付いて話し出すと……

「え?お金払っちゃったの?タクシーGOで呼んでるから,2,500円,俺のクレジットカードから支払われるんだよ?」と,アプリの画面に大きく2,500円の文字が出ているのを見せるOさん.

おいおい,さっきの2,000円,いったい何だったんだ?

幸にして,ちょうど信号が変わった直後のタイミングだったので,左に曲がろうとしていたタクシーに追いついた.窓を叩いたら,運転手が窓を開けて「これでしょ」と2,000円を返してきた.

_人人人人人人人人人人人人人人人人_
> 「これでしょ」じゃねーだろ! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

わかってて,しれっとカネ受け取りやがったな.

アプリで呼んだので,素性はバレている.車のなかで「タクシーセンターに苦情を3回入れられると営業できなくなる」なんて話をしていた.さすがに苦情入れられるのが怖くなったかな?

タクシーGOの仕組みを知らなかった私もうっかりしていたが,忘年会,新年会で酔っ払ってまともな判断力を失いそうになるこの季節,皆さんも.お気をつけあれ.



2023年12月2日土曜日

名前なんて飾りです偉いひとには(以下略

「飯尾」という名前,とくにありふれているわけでもなく,かといってさほど珍しいわけでもなく,なんとなく中途半端な位置付けにある.全国には1万人弱くらいの飯尾さんが居て,その順位は1,600番目くらいだそうだ.最近ではお笑い芸人のコンビ「ずん」の飯尾がTVで多少は活躍しているようで,それなりに認知度が上がってきたか?という感じ.

1994年,三菱総研に入社したときに,1,000人に満たない規模の会社にもかかわらず別の飯尾さんがいらっしゃり,社員名簿に「飯尾(淳)」とカッコ書きで下の名前が付記された.学生時代には経験したことがなかった体験だったので,そのときは少し興奮した.なお,その後,同様の経験はなく,しばらくして先輩の飯尾さんは定年退社したため,カッコ書きは取れてしまった.今ではそもそも,社員名簿そのものが作られないご時世だ.

そんな飯尾という名前に多少の愛着はないこともないが,困るのが海外に行くとしばしば Lio にされてしまうことである.子音が一切なく母音だけ3文字という名前は奇異にみえるらしく,最初のI(アイ)を小文字のl(エル)だと勝手に判断された結果,Lioにされてしまう.

その件で初めて困ったのは,2007年にドイツに出張したときのこと.日本からの飛行機は,夕方,フランクフルトに着陸し,私は空港から電車を乗り継いで,その郊外にあるダルムシュタットという街に赴いた.翌日の予定として,その街にあるフラウンフォーファー研究所への訪問が目的だったが,とにかく,ダルムシュタットの街中にある予約してあったホテルに着いたのは,もう日も落ちて20時を超えた時間だった.

小雨模様でうら寂しい状況であり,かつ,季節は初冬でたいへん寒い.駅からタクシーでホテルの前まで送ってもらい,ホテルを見つけたときには,とにかく落ち着けそうだと安堵した.しかし,その宿はずいぶんと小さな宿で,夜になっていたので通りに面した入り口はすでにシャッターが降りていた.せっかく到着したのに入れないじゃないか.

とにかく部屋に入りたいとあたりを見回してみると,傍に2階へ上がる階段があり,その入り口にキーのディスペンサーが設置してあった.インストラクションを読むと「If you have a reservation, enter your surname as the password to deliver the key for your room.(予約がある方は,姓を部屋の鍵のパスワードとして入力してください)」とある.

そこで何度も I, I, O と入力してみたのだが,キーは出てこない.このときはまだ Lio に間違えられた経験も少なく,まさか Lio として登録されているなどとは思いもよらなかった.

予約がなくとも,部屋が空いていればクレジットカードで部屋を確保できるよとの指示があり,クレジットカードで部屋を押さえることができた.ひと安心して部屋に入ると,部屋の電話がすぐに鳴り「もうひと部屋必要になったのか?」と宿のマスターが尋ねてきた.

そうではないと断り,その夜はそれで一件落着となった.Lio で登録されていたからキーが出てこなかったのだと知ったのは翌朝,食堂でマスターと話をして正確な状況を確認できたときである.

このときの出張はドイツ,イタリア,フランス,イギリスと1週間で4国を回る弾丸出張で,結局のところ,泊まった4箇所のホテルのうち3箇所で Lio と間違えられていたことが判明した.なんてこったい.ちなみに,最後に訪問したイギリスでは,いつ帰る?と入国審査官に尋ねられ,「tomorrow(明日)」と答えたら,「ワーカホリックの日本人よ,もっとグレートブリテン訪問を楽しみなさい,次に来るときには絶対に1週間以上滞在すること!」と叱られた.余計なお世話じゃ.

なお,JALやらANAやら,日系の航空会社に乗ると「イトー」様と呼ばれることが頻繁にある.彼ら彼女らは Iio ではなく Ito に見慣れているからだ.「伊藤様,いつもご利用ありがとうございます」と何度言われたことか.こちらもすでに慣れっこであり,心はすでに伊藤淳である.なお,大学時代に同じ学科で学んだ同級生に伊藤淳史という友人がいる.彼の名は「いとうあつし」.同姓同名である俳優の伊藤淳史は我が家の近くに住んでいたはずで,彼のご子息がうちの息子と同級生だか一つ下だったか,というのは完全に余計な話.

IT業界には(もうすでにお亡くなりになっているが)itojun氏という有名な方がいて,Googleなど,iiojun でエゴサーチすると「もしかして itojun」などと言ってくる.全く,失礼千万である.名前なんて飾りだとはわかっていても,これはちょいとばかり酷くない?

さて,だいぶ話が脱線してしまったので,元の話に戻そう.今回参加した国際会議もネームプレートには Lio と記されていた.こちらも逆手にとって「外国の皆さんからするとヘンな名前だってことは知ってるから,驚かない.Junって呼んでね」と自己紹介のネタにする.全国の飯尾さんにおかれましては,国際的な自己紹介に使えるネタですよ?

最後に,「脚なんて飾りです,偉い人には……」という某アニメの有名なセリフを借りて,名前なんて飾りにすぎないとあらためて主張したい.やれハシゴ高だツチ吉だとこだわる人をたまに見かけるが,中身で勝負せいと言うのはお節介か.

ところで,うちに「昔の漢字コードだと対応する字がないのではてなマークにされちゃうんです.だからカタカナ表記にしています」という学生が一人いる.それはさすがに可哀想すぎる.聞けば,成績証明書の発行システムだかなんだかが対応していないらしい.Unicodeだと対応できているようなので,古いシステムを使っている組織は早いとこ新しいシステムにリプレイスしてあげてほしい.