2018年3月30日金曜日

人狼知能開発のPythonAgent接続(AutoStarterバージョン)

https://qiita.com/aves/items/afefaa0d61fb9e21e2d4
にGUIを使ったPythonエージェントのつなぎ方が書いてありました.
@avesさん,ありがとうございます.

せっかくなので,AutoStarter.bat(AutoStarter.sh)でのつなぎ方についても説明をしておきます.

AutoStarter.iniで,エージェント数を指定して,それよりも少ない数のエージェントしか登録しなければ, 接続待ちの状態で止まりますので,その間にPythonエージェントをしていたポートにつなげれば,サーバにPythonエージェントを接続することが出来ます. AutoStarter.iniを以下のように書き直します.
  1. AutoStarter.iniを以下のように書き直す.
    lib=./
    log=./log/
    port=10000
    game=10
    view=true
    setting=./SampleSetting.cfg
    agent=5
    Sample1,org.aiwolf.sample.player.SampleRoleAssignPlayer,WEREWOLF
    Sample2,org.aiwolf.sample.player.SampleRoleAssignPlayer,SEER
    Sample3,org.aiwolf.sample.player.SampleRoleAssignPlayer
    Sample4,org.aiwolf.sample.player.SampleRoleAssignPlayer
    #Sample5,org.aiwolf.sample.player.SampleRoleAssignPlayer ←コメントアウト
  2. AutoStarter.bat(sh)を実行する
  3. Pythonエージェントをポート10000につなげる
以上です.

2018年3月14日水曜日

強いAI弱いAI

學鐙 第115巻 第1号(2018年春号)に安西祐一郎先生が「強いAI弱いAI」の書評を書いてくださいました.
安西先生らしいちょっと捻った面白いレビューです.

出版社・安西先生の使用許諾を得て公開しますので,ぜひご覧ください. 「強い AI・弱い AI ─研究者に聞く人工知能の実像」書評

書評を見て面白そうだなと思ったら,本書の方もぜひご覧ください.




2017年5月24日水曜日

第31回人工知能学会全国大会で発表数が多い研究者をランキングしてみた

昨日から第31回人工知能学会全国大会が始まっています.
人工知能に注目が集まっている昨今,どんな研究者がこの分野で特に活躍しているのでしょうか.
シンプルに発表件数が多い順にランキングしてみました.
なお,ランキングの論文数はプログラムからカウントし,同姓同名は同じ人物として扱っています.

第9位

まずは,同率9位. 名古屋大学の古橋 武先生(名古屋大学)と栗原 聡先生(電気通信大学),池上 高志先生(東京大学),坂田 一郎先生(東京大学)です.
いずれの先生も色々な研究を行っています.古橋先生は,ロボットから感性まで扱っていますし,栗原先生は群知能,センサネットワーク,深層学習とテーマが多彩です.

第3位

第3位は同率で6名の方がランクインしています.
森 純一郎先生(東京大学), 長井 隆行先生(電気通信大学), 白松 俊先生(名古屋工業大学), 吉川 大弘先生(名古屋大学), 福田 直樹先生(静岡大学), 伊藤 孝行先生(名古屋工業大学)です.
それぞれ10件のご発表があります.
開催地が名古屋だからか,東海周辺の先生方が多いですね.
やはり研究テーマは皆さん多彩です.たとえば,伊藤先生,白松先生,福田先生の市民共創知研究会の構想:グローバルで持続可能な地域創生活動支援の仕組みなどは一見人工知能とは無関係のようにも見える研究ですが,こういった広い分野を受け入れるのも人工知能学会の懐の広さではないでしょうか. 深層学習だけが人工知能ではありません.

第2位

鳥海 不二夫(東京大学)です. なんというか,ごめんなさい.
近未来チャレンジセッション「NFC (卒業)異種協調型災害情報支援システム実現に向けた基盤技術」人狼知能で学ぶAIプログラミングをよろしくお願いします.

第1位

栄えある第一位はやはり松尾 豊先生(東京大学)でした.なんと20件もの論文があります.
今をときめくディープラーニングを牽引する松尾先生が発表数第一位というは極めて納得いく結果です.うん,なんだかんだ言ってやっぱり深層学習は熱いですね.

というわけで,トップ10ランキング表を付けておきます.

順位研究者名所属論文数
1松尾 豊東京大学20
2鳥海 不二夫東京大学11
3森 純一郎東京大学10
3長井 隆行電気通信大学10
3白松 俊名古屋工業大学10
3吉川 大弘名古屋大学10
3福田 直樹静岡大学10
3伊藤 孝行名古屋工業大学10
9古橋 武名古屋大学9
9栗原 聡電気通信大学9
9池上 高志東京大学9
9坂田 一郎東京大学9
敬称略


2017年度論文数ベスト100研究者

ついでに,10位以降のベスト100のランキングは以下のようになります.皆さんは何位でしたか?
順位研究者名論文数
13荒井 幸代8
13中村 友昭8
13小林 一郎8
13岡田 真人8
13藤田 桂英8
18井上 克巳7
18鹿島 久嗣7
18岡 瑞起7
18加藤 昇平7
22稲邑 哲也6
22平嶋 宗6
22石川 翔吾6
22矢入 郁子6
22岡 夏樹6
22日和 悟6
22馬場 雪乃6
22大塚 孝信6
22和泉 潔6
22竹林 洋一6
22横尾 真6
22福井 健一6
22橋本 康弘6
22廣安 知之6
22仙石 晃久6
22宇津呂 武仁6
38大知 正直5
38水山 元5
38大森 隆司5
38羽室 行信5
38平田 圭二5
38浅谷 公威5
38榊 剛史5
38本村 陽一5
38中山 英樹5
38野中 朋美5
38伊藤 孝紀5
38高間 康史5
38鷲尾 隆5
38ジメネス フェリックス5
38中野 有紀子5
38五十嵐 康彦5
38上野山 勝也5
55高瀬 裕4
55清水 美穂4
55櫻井 祐子4
55河原 吉伸4
55萩原 良信4
55松下 光範4
55瀧川 一学4
55武藤 敦子4
55跡見 順子4
55杉山 弘晃4
55金子 正秀4
55小野 智司4
55今井 倫太4
55中村 剛士4
55古崎 晃司4
55大澤 博隆4
55跡見 友章4
55松井 孝典4
55西村 拓一4
55林 雄介4
55東中 竜一郎4
55小方 孝4
55山下 和也4
55森山 甲一4
55大武 美保子4
55吉田 光男4
55市瀬 龍太郎4
55岡田 将吾4
55田中 文英4
55工藤 卓4
55谷口 忠大4
55犬塚 信博4
55加納 政芳4
88松尾 義博3
88武田 英明3
88田和辻 可昌3
88内田 智之3
88三輪 誠3
88砂山 渡3
88渥美 雅保3
88沖本 天太3
88駒谷 和範3
88畑中 裕司3
88北原 鉄朗3
88佐々木 裕3
88秀島 栄三3
88長谷川 克也3
88稲葉 雅幸3
88松村 真宏3
88宇野 毅明3
88藤井 秀樹3
88池田 圭佑3
88中村 哲3
88荒牧 英治3
88松本 和幸3
88戸田 浩之3
88山田 誠二3
88岩澤 有祐3
88林 佑樹3
88尾形 哲也3
88相澤 清晴3
88沼尾 正行3
88河瀬 諭3
88穴田 一3
88高野 雅典3
88日永田 智絵3
88岩橋 直人3
88柳井 孝介3
88宮原 哲浩3
88下田 和3
88松居 辰則3
88吉田 智一3
88新田 克己3
88北 研二3
88福田 一郎3
88尾崎 知伸3
88森田 武史3
88番原 睦則3
88芦原 佑太3
88堂坂 浩二3
88永田 賢二3
88町村 尚3
88西田 知史3
88吉村 忍3
88岡田 慧3
88寺野 隆雄3
88石黒 浩3
88若宮 翔子3
88柳瀬 利彦3
88宮澤 和貴3
88岡本 洋3
88麻生 英樹3
88山田 クリス孝介3
88西本 伸志3
88早川 博章3
88山崎 俊彦3
88那須野 薫3
88鈴木 祐介3
88坪内 孝太3
88山下 達雄3
88吉田 稔3
88岡田 克彦3
88岩沼 宏治3
88青木 達哉3
88和田 健太郎3
88諏訪 正樹3
88中山 浩太郎3
88小郷原 一智3
88松井 藤五郎3
88伊藤 貴之3
以上敬称略


なお,ランキングが高い研究者同士の関係を知りたい方は,第31回人工知能学会全国大会の発表から人工知能研究者のネットワークを作ってみたをご覧ください.


2017年5月23日火曜日

第31回人工知能学会全国大会の発表から人工知能研究者のネットワークを作ってみた

5月23日から26日の間名古屋で第31回人工知能学会全国大会が開かれています.
とりあえず,せっかく人工知能研究者が一同に介す場なので,人間関係を調べてみようということで, 人工知能研究者のネットワークを作ってみました.
共著が一つでもある人をリンクでつないだ図になります.


とりあえず,全体像はこんな感じ.
いくつか大きなコンポーネントに分かれています.
人工知能なんという小さな分野でも一枚岩じゃないですねw
とはいえこれを見ても,何も情報が無いので,上位5コンポーネントについてどんな研究グループがあるのかを見てみましょう.

抽出された最大コンポーネントがこちら.
西村先生,本村先生という産総研人工知能研究センターのメンバーと電通大長井先生,中村先生,立命館谷口先生,玉川大の大森先生などが入っています.機械学習,認知,サービス工学とかその当たりが中心でしょうか.
いくつかのグループがブリッジとなる人を介して一つのコンポーネントになっていることが分かります.


次に大きかったコンポーネントはこちら.これは見るからにソーシャルウェブ関係の人達が中心ですね.インタラクションや科学技術社会論が繋がっているのがなかなか興味深いですね.
確かに,その辺と近い気がする・・・


ここに出ている右側のグループは伊藤先生を中心としたマルチエージェント系のグループですね.左側は音楽情報系のグループのようです.一見大分違うグループにも見えますが,北原先生がコネクションとなっているようです.


こちらは大阪大学の沼尾先生らを中心とした研究グループと関連するところでしょうか.データマイニングとか知識アーキテクチャの研究家と思います.

 こちらは明らかに松尾先生を中心とした深層学習グループでしょうね.
ディープラーニングブームを考えると,ここが一番大きいかなと思ったのですが,意外とそんなことはありませんでした.

というわけで,人工知能学会全国大会における共著関係から,どんな研究グループがあるのかを見てみました.
とはいえ,2000人近く参加している学会のため,私自身もあんまり研究内容をよく知らない先生も多いため,見当違いのことを書いているかもしれません.
間違いがあったら,メールまたは,twitter(@toritorix)までご連絡ください.


せっかくデータを作ったので,ネットワークデータは公開しておきます.
ネットワークデータのダウンロード
間違いを発見したら教えてください.

なお,データはJSAIのプログラムページからぶっこ抜いて処理しています.
同姓同名の人は同一人物として扱われているので,もしかしたらその辺で間違いはあるかもしれません.

こちらもどうぞ.
第31回人工知能学会全国大会で発表数が多い研究者をランキングしてみた.

2016年12月1日木曜日

WindowsでDeepLearning・その2

さて,昨日の段階で,

    **************************************************
    *** WARNING: Include files not found: ['cudnn.h']
    *** WARNING: Skip installing cudnn support
    *** WARNING: Check your CFLAGS environment variable
    **************************************************
というエラーが出て終わっていたので,FIXを目指す. https://github.com/pfnet/chainer/issues/1699 を見てみたけど,どうも最後まで解決はしていないらしい.

とにかく問題はcuDNNがうまくインストールされていないことらしいということで, あたりがついてきたので,その辺を調べてみる.

どうやらcuDNNは中身を C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0 以下に上書きする必要があったようだ. http://qiita.com/okuta/items/f985b9da6de33a016a75

cuDNNを無事ダウンロードできた人は、展開したファイルをC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v7.5に上書きしてください。
cuDNNv3の場合、cudnn64_70.dllがCUDA\v7.5\binフォルダに、cudnn.hがCUDA\v7.5\includeフォルダにcudnn.libがCUDA\v7.5\lib\x64に入っていれば大丈夫です。

これで再び

>pip install chainer --no-cache-dir -vvvv
でインストールすると,エラーは発生せずに無事インストールできた.

が,実行してみると

>python train_mnist.py --gpu 0

GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Traceback (most recent call last):
  File "C:\usr\Python35\lib\site-packages\cupy\cuda\compiler.py", line 49, in _run_nvcc
    return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT)
  File "C:\usr\Python35\lib\subprocess.py", line 626, in check_output
    **kwargs).stdout
  File "C:\usr\Python35\lib\subprocess.py", line 708, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['nvcc', '--preprocess', '-Xcompiler', '/wd 4819', '-m64', 'C:\\Users\\***\\AppData\\Local\\Temp\\tmp_ak8290o\\kern.cu']' returned non-zero exit status 2

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "train_mnist.py", line 107, in 
    main()
  File "train_mnist.py", line 104, in main
    trainer.run()
  File "C:\usr\Python35\lib\site-packages\chainer\training\trainer.py", line 292, in run
    entry.extension(self)
  File "C:\usr\Python35\lib\contextlib.py", line 77, in __exit__
    self.gen.throw(type, value, traceback)
  File "C:\usr\Python35\lib\site-packages\chainer\reporter.py", line 90, in scope
    yield
  File "C:\usr\Python35\lib\site-packages\chainer\training\trainer.py", line 289, in run
    update()
  File "C:\usr\Python35\lib\site-packages\chainer\training\updater.py", line 170, in update
    self.update_core()
  File "C:\usr\Python35\lib\site-packages\chainer\training\updater.py", line 182, in update_core
    optimizer.update(loss_func, *in_vars)
  File "C:\usr\Python35\lib\site-packages\chainer\optimizer.py", line 392, in update
    loss = lossfun(*args, **kwds)
  File "C:\usr\Python35\lib\site-packages\chainer\links\model\classifier.py", line 67, in __call__
    self.y = self.predictor(*x)
  File "train_mnist.py", line 24, in __call__
    h1 = F.relu(self.l1(x))
  File "C:\usr\Python35\lib\site-packages\chainer\links\connection\linear.py", line 91, in __call__
    self._initialize_params(x.size // x.shape[0])
  File "C:\usr\Python35\lib\site-packages\chainer\links\connection\linear.py", line 77, in _initialize_params
    initializer=self._W_initializer)
  File "C:\usr\Python35\lib\site-packages\chainer\link.py", line 180, in add_param
    data = initializers.generate_array(initializer, shape, self.xp)
  File "C:\usr\Python35\lib\site-packages\chainer\initializers\__init__.py", line 45, in generate_array
    initializer(array)
  File "C:\usr\Python35\lib\site-packages\chainer\initializers\normal.py", line 94, in __call__
    Normal(s)(array)
  File "C:\usr\Python35\lib\site-packages\chainer\initializers\normal.py", line 31, in __call__
    loc=0.0, scale=self.scale, size=array.shape)
  File "cupy/core/core.pyx", line 1107, in cupy.core.core.ndarray.__setitem__ (cupy\core\core.cpp:22267)
  File "cupy/core/core.pyx", line 1336, in cupy.core.core.elementwise_copy (cupy\core\core.cpp:49642)
  File "cupy/core/elementwise.pxi", line 771, in cupy.core.core.ufunc.__call__ (cupy\core\core.cpp:40576)
  File "cupy/util.pyx", line 35, in cupy.util.memoize.decorator.ret (cupy\util.cpp:1261)
  File "cupy/core/elementwise.pxi", line 579, in cupy.core.core._get_ufunc_kernel (cupy\core\core.cpp:37190)
  File "cupy/core/elementwise.pxi", line 32, in cupy.core.core._get_simple_elementwise_kernel (cupy\core\core.cpp:27744)
  File "cupy/core/carray.pxi", line 87, in cupy.core.core.compile_with_cache (cupy\core\core.cpp:27431)
  File "C:\usr\Python35\lib\site-packages\cupy\cuda\compiler.py", line 131, in compile_with_cache
    base = _empty_file_preprocess_cache[env] = preprocess('', options)
  File "C:\usr\Python35\lib\site-packages\cupy\cuda\compiler.py", line 94, in preprocess
    pp_src = _run_nvcc(cmd, root_dir)
  File "C:\usr\Python35\lib\site-packages\cupy\cuda\compiler.py", line 56, in _run_nvcc
    raise RuntimeError(msg)
RuntimeError: `nvcc` command returns non-zero exit status.
command: ['nvcc', '--preprocess', '-Xcompiler', '/wd 4819', '-m64', 'C:\\Users\\*****\\AppData\\Local\\Temp\\tmp_ak8290o\\kern.cu']
return-code: 2
というわけで,実行時エラー.
さて,ここからどうするか・・・

2016年11月30日水曜日

WindowsでDeepLearning・その1

Windows10でDeepLearningを目指す.
基本的に,
http://qiita.com/akrian/items/953082aa8f00479dbb01
を参考にする.

まずは,Python3.x系のインストール.
2.7系とどちらを入れるべきか悩んだけど,参考ページが3.5なので同じものを.

https://www.python.org/downloads/
から,Python3.5.2 Windows x86-64 web-based installerを選択してDL.
そして,インストール.





Visual Studio CommunityをDL
https://www.visualstudio.com/ja/downloads/
最新版でいいのかな.
さすがMicrosoft.いきなりInstallが始まったぞ.



かなり長時間のインストール後,再起動.

続いてCudaを導入.
https://developer.nvidia.com/cuda-downloads



cuDNNをインストール

Pycudaをインストール

Windows環境変数を追加

Chainerのインストール

と調子よくインストールが完了していったが,
・Chainerのサンプルファイルを実行
でこけた.

GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20

Traceback (most recent call last):
  File ".\train_mnist.py", line 107, in <module>
    main()
  File ".\train_mnist.py", line 56, in main
    chainer.cuda.get_device(args.gpu).use()  # Make a specified GPU current
  File "C:\usr\Python35\lib\site-packages\chainer\cuda.py", line 180, in get_device
    check_cuda_available()
  File "C:\usr\Python35\lib\site-packages\chainer\cuda.py", line 85, in check_cuda_available
    raise RuntimeError(msg)
RuntimeError: CUDA environment is not correctly set up
(see https://github.com/pfnet/chainer#installation).CuPy is not correctly installed. Please check your environment, unin
stall Chainer and reinstall it with `pip install chainer --no-cache-dir -vvvv`.
だそうで.

試しに,
> pip install chainer --no-cache-dir -vvvv
としてみたけど,案の定ダメ.

http://hurikake.hatenadiary.jp/entry/2016/09/26/232246
を参考にして,
いろいろ試した結果,pathが有効になっていなかった.
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\bin
へのPathが有効になっていなかった.

ログインしなおして再チャレンジ.
今度は,
distutils raises an error: Unable to find vcvarsall.bat
だそうな.
http://www.regentechlog.com/2014/04/13/build-python-package-on-windows/
によると,
C/C++などのビルドが必要なファイルを含むPythonパッケージをWindows環境下でインストールしようとすると「Unable to find vcvarsall.bat」とエラーがでることがあります。

Pythonパッケージにビルドが必要なファイル(pure Python以外のC/C++など)が指定されている場合はインストール時にコンパイルが必要ですが、Windows環境ではデフォルトでPython本体をコンパイルしたVC++と同じバージョンのVC++を呼ぼうとします。そのため、Python本体をコンパイルしたバージョンのVC++がないとエラーになります。
なるほど.
調べてみると,
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version
'3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)]'
>>>
ということで,MSC v.1900->VS13でコンパイルしたことが分かる.
・・・と思ったけど,
http://stackoverflow.com/questions/33323172/vcvarsall-bat-needed-for-python-to-compile-missing-from-visual-studio-2015-v-1
によると, vcvarsall.bat がないことがあるらしい.
調べてみたら,確かに
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\vcvarsall.bat 
はない.
http://stackoverflow.com/questions/33323172/vcvarsall-bat-needed-for-python-to-compile-missing-from-visual-studio-2015-v-1#comment63185046_35243904
を参考に,
c:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\vsvars32.batをコピーして,vcvarsall.batとリネームしてみる.

とりあえず動いたけど,
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20

C:\usr\Python35\lib\site-packages\chainer\cuda.py:90: UserWarning: cuDNN is not enabled.
Please reinstall chainer after you install cudnn
(see https://github.com/pfnet/chainer#installation).
  'cuDNN is not enabled.\n'
というわけで,cuDNNは使えていないらしい.
そのうえ,実行も失敗している.

う~む・・・
いずれにせよ,今日は時間がないので,ここまで.


2016年2月1日月曜日

2006年の節分まで恵方巻なんてなかった!?クックパッドデータを使って調べてみた.

節分が近づいてきました.
で,最近は恵方巻きとか太巻きとか節分に食べる訳ですが,子供の頃そんな風習無かったよな~としか思えない.

マスコミに踊らされているとか,コンビニ戦略だ,とか色々言われているけど,一体いつから恵方巻きとか太巻きとかが節分に食べられるようになったのか気になる.
というわけで,クックパッドとNIIによって提供されているクックパッドレシピデータを使って調べてみた.データは1998年から2014年まである.

とりあえず,レシピのタイトルに「恵方巻き」「太巻き」が入っているレシピを検索し,いつ投稿されたのかを調べる.ただし,全体の投稿数と比較しないと何とも言えないので,
「一日に投稿されたレシピの内,恵方巻き・太巻きが含まれている割合」
を各日ごとに検出してみた.



ちゃんとピークは2月ごとに訪れているらしい.
んでもって,最初のピークは2003年2月.

う~ん,2003年より前はクックパッドのデータにどれほど統計的意味があるか分からないレベルなので,何とも言えないかな.
ただ,別途調べた「雑煮」だと2000年からピークが出ているので,それに比べるとやはり節分に太巻きという風習は2000年代初頭はそれほどメジャーなイベントでは無かったのかも知れない.

なお,恵方巻のみだと2006年が初出.恵方巻きという言葉自体は2006年くらいから使われ始めたもののようだ.
それまでは一件もレシピがないみたいだけど、誰がこの名前つけたんだろう?




というわけで,思ったより太巻きを食べる風習というのは昔からあったみたいだけど,恵方巻きと言う言葉は最近メジャーになったものらしいということが分かりました.

クックパッドのデータ,近年の食文化の変化を見るには適しているのかも.
論文にはなりづらそうだけど.


2015年7月15日水曜日

学会タイマーオンライン再び

GoogleAppEngineの仕様変更に伴って
学会タイマーオンラインのメンテナンスができなくなったので,別の場所に再設置.

プレゼンタイマーオンライン

MacでもWindowsでもAndroidでも,アプリのインストール無しに使えます.

2015年3月7日土曜日

人狼知能エージェントの作成・その6 ver0.1.16

人狼知能サーバのバージョンが0.1.16に上がりました.

バージョン0.1.16での変更点として大きいものとして,
RoleAsignPlayerに追加する各役職とのPlayerが,Playerクラスを継承しなくなりました.

これまでは.村人であればAbstractVillagerPlayerというクラスを継承したクラスを使って,
行動を規定していました.
たとえば,
class MyVillagerPlayer extends AbstractVillagerPlayer{
}

しかしながら,AbstractVillagerPlayerはPlayerを実装していたのですが,
Playerを実装する意味がないということから,
AbstractRoleクラスを継承したAbstractVillagerをいうクラスを継承する事になりました.
たとえば,
class MyVillager extends AbstractVillager{
}

同様に,AbstractSeer,AbstractMediumなどのクラスを継承してMySeer,MyMediumなどを作るようにしてください.
基本的には,extendsするクラスとAbstractVillagerPlayerなどからAbstractVillagerに変更すればOkです.

今後も0系統は大きな変更がある可能性がありますが,ご了承ください.



2014年12月11日木曜日

Pythonを使ってTwitterでOAuthのログインをするためのクラスを作る

TwitterのOAuthでログインできるようにしておくと色々便利なのはAppEngineで経験済みなので,そのためのライブラリを作成してみる.

基本的にはこちらのページを参考にこしらえた.
pythonでoauth認証
事前準備は,
さくらインターネットでPython2.7を使ってTwitterAPIを叩くを参考のこと.
なお,Twitterとの接続に使うのは,python-twitter

まず,
http://syncer.jp/twitter-api-create-application
を参考に,Twitterのアプリを作成しておく.
ただし,アプリ作成の場所が,分かりづらくなったので,直接こちらにアクセスした方がよい.
https://apps.twitter.com/
Twitter社はアプリ作らせる気がないのか?と思うくらい分かりづらい.

このとき,CallBackURLにアプリのCGIのURLを指定しておくことを忘れずに.
後から変えられるけど.

次に,アプリ作成に入る.
まず,sqliteを使うので,oauth.dbというファイルを作っておいて,
$ sqlite3 oauth.db
sqlite> CREATE TABLE oauth (oauth_token text primary key,oauth_token_secret text);
sqlite> CREATE TABLE user (id integer primary key, consumer_key text, consumer_secret text, access_token text, access_token_secret text, screen_name text, image_url text, time datetime);
sqlite> .exit
として,データベースを作成しておく. 次に,TwitterLogin.pyを作成.
ここが今日のハイライト.


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

import site
site.addsitedir('/home/username/lib/python')

import os
import cgi
import cgitb
import oauth2 as oauth
import sqlite3
import twitter
import datetime
from Cookie import SimpleCookie
from twitter import TwitterError

request_token_url = 'https://api.twitter.com/oauth/request_token'
access_token_url = 'https://api.twitter.com/oauth/access_token'
authenticate_url = 'https://api.twitter.com/oauth/authorize'

class TwitterLogin:
    def __init__(self, db_name, consumer_key, consumer_secret):
        self.db_name = db_name
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret

    def getOAuth(self):
        consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
        client = oauth.Client(consumer)
        # reqest_token    
        resp, content = client.request(request_token_url, 'GET')
        request_token = dict(self.parse_qsl(content))
       
        #call_back="http://torix.sakura.ne.jp/twitter/signin.cgi"
   
        # request_token    
        con = sqlite3.connect(self.db_name)
        con.execute(u'insert into oauth values (?, ?)', (request_token['oauth_token'], request_token['oauth_token_secret']))
        con.commit()
        con.close()

        #
        url = '%s?oauth_token=%s' % (authenticate_url, request_token['oauth_token'])
        print 'Content-type: text/html; charset: utf-8\n\n'
        print
        print '' % url


    def getApi(self):
        access_token, access_token_secret, screen_name, image_url = self.loadAccessToken()
        self.screen_name = screen_name
        self.image_url = image_url
        if not access_token == None:
            # Cookieを使った認証
            api = twitter.Api(consumer_key=self.consumer_key,
                              consumer_secret=self.consumer_secret,
                              access_token_key=access_token,
                              access_token_secret=access_token_secret)
            return api
        elif 'QUERY_STRING' in os.environ:
            # CallBackを使った認証
            query = cgi.parse_qs(os.environ['QUERY_STRING'])
            if len(query) == 0:
                return None
            # oauth_token_secret を取得
            con = sqlite3.connect(self.db_name)
            oauth_token_secret = con.execute(
                u'select oauth_token_secret from oauth where oauth_token = ?'
                , [query['oauth_token'][0]]).fetchone()[0]
            con.close()

            if oauth_token_secret == None:
                return None

            # Access_token と access_token_secret を取得
            consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
            token = oauth.Token(query['oauth_token'][0], query['oauth_verifier'][0])
            client = oauth.Client(consumer, token)
            resp, content = client.request(
                access_token_url, "POST", body="oauth_verifier=%s" % query['oauth_verifier'][0])
            access_token = dict(self.parse_qsl(content))

            # access_tokenの保存
            api = twitter.Api(consumer_key=self.consumer_key,
                        consumer_secret=self.consumer_secret,
                        access_token_key=access_token['oauth_token'],
                        access_token_secret=access_token['oauth_token_secret'])
            try:
                user = api.VerifyCredentials()
                uid = user.GetId()
                self.screen_name = user.screen_name
                self.image_url = user.profile_image_url
                self.saveAccessToken(uid, access_token['oauth_token'], access_token['oauth_token_secret'], self.screen_name, self.image_url)
            except TwitterError:
                return None
            return api
        else:
            return None

    #save access token to db
    def saveAccessToken(self, uid, access_token, access_token_secret, screen_name, image_url):

        con = sqlite3.connect(self.db_name)
        c = con.cursor()
        #print datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        c.execute(u"replace into user (id, consumer_key, consumer_secret, access_token, access_token_secret, screen_name, image_url, time) values (?, ?, ?, ?, ?, ?, ?, datetime(?))", [uid, self.consumer_key, self.consumer_secret, access_token, access_token_secret, screen_name, image_url, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
        con.commit()
        c.close()

        ck = SimpleCookie()
        ck.load(os.environ.get("HTTP_COOKIE",""))
        ck["id"] = uid
        ck["token"] = access_token
       
        now_date = datetime.datetime.now()
        expires = now_date
        expires = expires + datetime.timedelta(seconds=+(60*60*24))
        expires = expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
           
        ck["id"]["expires"] = expires
        ck["token"]["expires"] = expires
        print ck.output(["id", "expires"])
        print ck.output(["token", "expires"])

    #load data from data base
    def loadAccessToken(self):
        ck = SimpleCookie()
        ck.load(os.environ.get("HTTP_COOKIE",""))
        if ck.has_key("id") and ck.has_key("token"):
            id = int(ck["id"].value)
            token = ck["token"].value
            access_token = None

            con = sqlite3.connect(self.db_name)
            c = con.cursor()
            c.execute(u'select access_token, access_token_secret, screen_name, image_url from user where id=%d'% id)
            for row in c:
                access_token = row[0]
                access_token_secret = row[1]
                self.screen_name = row[2]
                self.image_url = row[3]
            con.close()

            if not access_token == None and access_token == token:
                return access_token, access_token_secret, self.screen_name, self.image_url
            else:
                return None, None, None, None
        else:
            return None, None, None, None



    def parse_qsl(url):
        param = {}
        for i in url.split('&'):
            _p = i.split('=')
            param.update({_p[0]: _p[1]})
        return param

    def parse_qsl(self, url):
        param = {}
        for i in url.split('&'):
            _p = i.split('=')
            param.update({_p[0]: _p[1]})
        return param

getOAuthで,OAuth認証を行うページに飛ばして,callbackで戻ってきたときにgetApiで捕まえる.
それ以降は,UserIDとAccessTokenをCookieに保存することでSession管理をすることにする.
本当は独自のsessionIDを発行した方がいいのかもしれない.

次に,実際にログインするところを作ってみよう. 使い方は以下の通り.
なお,consumer_keyとconsumer_secretはTwitterで登録したアプリのものをつかう.
callback urlがhttp://xxx.xx.xx/twitter/login.cgi
そのURLでアクセスできるcgiを以下のように作成する.

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

import site
site.addsitedir('/home/username/lib/python')
from TwitterLogin import TwitterLogin

consumer_key = 'consumer_key'
consumer_secret = 'consumer_secret'

if __name__ == '__main__':
    twitter = TwitterLogin('oauth.db', consumer_key, consumer_secret)
    api = twitter.getApi()
    print 'Content-type: text/html; charset: utf-8\n\n'
    if api == None:
        print 'Fail to login'
    else:
        user = api.VerifyCredentials()
        print user.screen_name

これで,認証したアカウントのスクリーンネームが表示されるはず.

APIの使い方は,以下を参照.
https://python-twitter.googlecode.com/hg/doc/twitter.html

色々問題はあるかもしれないけど,とりあえず動いたので,満足.




2014年12月8日月曜日

さくらインターネットでPython2.7を使ってTwitterAPIを叩く

タイトル長いな.
世界の中心で愛を叫ぶみたいになってもうた.

さて,TwitterAPIを使ったツイート収集だけど,最近検索APIで1週間分なら無制限にとれるっぽいことが分かったので,これを使って遊んでみよう計画.

最近Pythonも気になるので,Pythonでさくらサーバ使って遊んでみる.


PythonでTwitterのAPIをたたいてみよう-セットアップ偏
を参考にしてみる.

なお,Twitterとの接続に使うのは,python-twitter

$ setenv PYTHONPATH /home/username/lib/python

$ wget --no-check-certificate github.com/simplegeo/python-oauth2/archive/master.zip
$ unzip master.zip
$ cd  python-oauth2-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://github.com/bear/python-twitter/archive/master.zip
$ unzip master.zip
$ cd  python-twitter-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://github.com/jcgregorio/httplib2/archive/master.zip
$ unzip master.zip
$ cd  httplib2-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://pypi.python.org/packages/source/s/simplejson/simplejson-3.6.5.tar.gz
$ tar zxvf simplejson-3.6.5.tar.gz
$ cd simplejson-3.6.5/
$ ./setup.py install --home=~

これで必要なファイルはダウンロード完了.
ここで適当なアプリを作成しておく.

ConsumerKeyとConsumerSecret,AccessToken, AccessTokenSecretを獲得して,
以下のようなcgiを作成.

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


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


import site
site.addsitedir('/home/username/lib/python')

import sys
import twitter

print ('Content-type: text/html; charset=UTF-8\n\n')

CONSUMER_KEY = ''                             # Consumer Key
CONSUMER_SECRET = ''         # Consumer Secret
ACCESS_TOKEN = '' # Access Token
ACCESS_TOKEN_SECRET = ''         # Accesss Token Secert

api = twitter.Api(consumer_key=CONSUMER_KEY,
                      consumer_secret=CONSUMER_SECRET,
                      access_token_key=ACCESS_TOKEN,
                      access_token_secret=ACCESS_TOKEN_SECRET)


tlAry = api.GetSearch(term="keyword",count=100)
tweetlist=[]
for s in tlAry:
    if not s.text.startswith('RT @'):
        tweetlist.append(s.text)
        print s.text.encode('utf-8')


これで,keywordを含むツイートを取得可能.
ポイントの一つが,
import site
site.addsitedir('/home/username/lib/python')
の部分.
これを書いておかないとライブラリを読みに行ってくれないので注意.
さくらインターネットでは,ライブラリはローカルに置くしかないので,この作業をやらないと,twitterライブラリがimportできません.




2014年12月2日火曜日

Windows7にCDH5.1.3でHadoop・断念編

というわけで,前回のエントリーでHDInsightでHadoopをと思ったけど,
その後どうやってもEclipseでは起動しない.
どうもライブラリその他の関係でうまくいかないようだ.

色々試した結果,EclipsePluginがHadoop2.x系には対応していないと言う結論に.
結局利用可能なのは,Hadoop0.21系まで.

ひとまずHadoop2.x系のPluginがリリースされるまでは,Hadoop0.21系を利用することに.
こちらのエントリーに書いた方法で概ねインストール可能.
変更点は,
hadoop-env.shが
hadoop/bin/hadoop-env.sh
から
hadoop/conf/hadoop-env.sh
へ移動になったことくらい.

結局2年前のHadoopを使うことになるとは.とほほ.


2014年11月25日火曜日

Windows7にCDH5.1.3でHadoop・その2 HDInsightインストール編

というわけで,ここを参考にHDInsightでWindows7にHadoopを導入してみようとトライ.

まずは,HDInsight Previewsのページへ・・・行ってみるとすでに404.

嫌な予感をはらみつつ,Microsoft Web Platform Installerからの導入を目指す.
直接Microsoft Web Platform InstallerをDLすると,Microsoft Web Platform Installer5.0となっている.
説明のページが4.0だったので,バージョンが上がっている.

ここで,

  1. インストーラー(wpilauncher.exe)を実行
  2. Microsoft HDInsight for Windows Server Community Technology Previewを選択

というのが流れのはずなのだが,Microsoft HDInsight for Windows Server Community Technology Previewがない.
あるのは,Microsoft HDInsight Emulator for Windows Azureのみ.
for Windows Azureというあたりに駄目そうな予感を感じながら,とりあえずダウンロードしてみる.

やたら長いインストールが終わると,C:\直下に大量のディレクトリができる.
C直下は綺麗にしておきたい身としては,勝手にフォルダ作りまくっていらっとするけど,しょうがない.

C:\Azul
C:\hadoop
C:\HadoopInstallFiles
C:\hdp
C:\hdpdata

が作成されたようだ.
このうち,C:\hdpがHadoopの各種ライブラリがインストールされているフォルダとなる.
各種アプリがすでにインストールされているが,
C:\hdp\hadoop-2.4.0.2.1.3.0-1981以下にあるのがHadoop本体.
とりあえず,C:\hdp\hadoop-2.4.0.2.1.3.0-1981でコマンドプロンプトを開き,
C:\hdp\hadoop-2.4.0.2.1.3.0-1981>bin\hadoop version
Hadoop 2.4.0.2.1.3.0-1981
Subversion git@github.com:hortonworks/hadoop-monarch.git -r 3c481b5d3c117181c4be
3d3a25697e2e0574f972
Compiled by jenkins on 2014-07-15T14:23Z
Compiled with protoc 2.5.0
From source with checksum 8a8b83aa11aad760f2a019f4cea188c
This command was run using /C:/hdp/hadoop-2.4.0.2.1.3.0-1981/share/hadoop/common
/hadoop-common-2.4.0.2.1.3.0-1981.jar

というわけで,hadoopコマンドが動くことは確認.
さて,これをどうやってEclipseから呼び出すか,それが問題だ.

ちなみに,参考ページにはProgram Filesの問題からJAVA_HOMEをHDInsightでインストールした物にしないと駄目だ,と書いてあるが,特に問題なくインストール完了.
Microsoft Web Platform Installer5.0になって,改良されたのかしらん?


Windows7にCDH5.1.3でHadoop・その1失敗編

Hadoopプログラミングをする上で,いちいちサーバにjarを持って行ってテストをするのは大変すぎるので,Windows上のEclipseでHadoopプログラムをデバッグしていたんだけど,新しいHadoopをCDH5.1.3で入れたので,新しいバージョン.
以前の情報はこちら

インストール

まず,
https://ccp.cloudera.com/display/SUPPORT/CDH+Downloads
で,CDH5.1.3のtarを取得.
CDH5.1.3なのはサーバとバージョンを併せるため.

とりあえず,
hadoop-2.3.0-cdh5.1.3.tar.gz
をダウンロード.
Cygwinの/usr/local/libに展開.

$ cd /usr/local/lib
$ tar zxvf hadoop-2.3.0-cdh5.1.3.tar.gz
$ ln -s hadoop-2.3.0-cdh5.1.3 hadoop
$ ln -s /cygdrive/c/Program\ Files\ /Java/jdk1.7.0_60/ jdk1.7
$ ln -s jdk1.7 java

Windowsの環境変数にHADOOP_HOMEを追加. Pathも追加.
HADOOP_HOME:C:\usr\cygwin\usr\local\lib\hadoop
PATH:...;%HADOOP_HOME%\bin

hadoop/bin-mapreduce1/hadoop-config.shに以下を追記
# the root of the Hadoop installation
if [ -z "$HADOOP_HOME" ]; then
  export HADOOP_HOME=`dirname "$this"`/..
else
  export HADOOP_HOME=$(cygpath -u "$HADOOP_HOME")
fi
hadoopにも以下を追記.
#cygwin=false
cygwin=true
# some Java parameters
export JAVA_HOME=/usr/local/lib/java

Windowsではローカルモードでしか起動しないので,以下のように設定する.
etc/hadoop/core-site.xml
<configuration>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>C:\tmp\hadoop</value>
    </property>
</configuration>
etc/hadoop/mapred-site.xml
<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>local</value>
    </property>
    <property>
         <name>dfs.replication</name>
         <value>1</value>
    </property>
</configuration>

Eclipseプラグイン

ここまで来て問題発生.
ここにあるとおり,プラグインの公式な物がHadoop2.x系では出ていないとのこと.
ということで,どうしようかと考えていたら.
Windows用HadoopHDInsightの記事を発見.
とりあえずこれを試してみることにする.
というところで,続きは次回.

2014年11月14日金曜日

人狼知能エージェントの作成・その5 Talk編

発話の生成

人狼知能ver0.1.x系では,コミュニケーションはかなり制限されています.
 話すことが出来るのは,

  1. カミングアウト
  2. 占い結果
  3. 霊媒結果
  4. 護衛先の報告
  5. 他人の職業の予想
  6. 他人の意見への同意
  7. 他人の意見への反対
  8. 投票先の宣言
  9. 襲撃先の宣言(人狼のみ)
  10. パス
の10種類となります.
そのため,それ以外の発話を行わないように,対話生成クラスを使って発話内容を作成することが推奨されています.
発話生成には,TemplateTalkFactoryを,人狼同士の囁きの生成には,TemplateWhisperFactoryを使います.
たとえば,占い師をカミングアウトしたい場合は,
String contents = TemplateTalkFactory.comingout(getMe(), Role.SEER);
// contents = "COMINGOUT Agent[00] SEER"
とします.
これによって,占い師であることや,霊媒師であることを宣言することが可能です.

以下,他のテンプレートによる発話の例を示します.
//カミングアウト
//「私は占い師です」
String comingout = TemplateTalkFactory.comingout(mySelf, Role.SEER);
// comingout = "COMINGOUT Agent[07] SEER"

//占い結果の報告
// 「targetは人狼だった」
String divine = TemplateTalkFactory.divined(target, Species.WEREWOLF);
// divine = "DIVINED Agent[01] WEREWOLF"

//霊媒結果の報告
//「targetは人間だった」
String inquest = TemplateTalkFactory.inquested(target, Species.HUMAN);
// inquest = "INQUESTED Agent[01] HUMAN"

//護衛先の報告
//「targetを護衛した」
String guard = TemplateTalkFactory.guarded(target);
// guard = "GUARDED Agent[01]"

//予想
//「targetは村人だと思う」
String estimate = TemplateTalkFactory.estimate(target, Role.VILLAGER);
// estimate = "ESTIMATE Agent[01] VILLAGER"

//投票先の宣言
//「targetに投票する」
String vote = TemplateTalkFactory.vote(target);
// vote = "VOTE Agent[01]"

//同意
//「1日目の発言001に賛成」
String agree = TemplateTalkFactory.agree(TalkType.TALK, 1, 1);
// agree = "AGREE TALK day1 ID:1"

//反対
//「4日目の発言020に反対」
String disagree = TemplateTalkFactory.disagree(TalkType.TALK, 4, 20);
// disagree = "DISAGREE TALK day4 ID:20"

//襲撃先の宣言(人狼のみ)  
//「targetを襲撃先にする」
String attack = TemplateWhisperFactory.attack(target);
// attack = "ATTACK Agent[03]"

//一回パス(他に発言者がいなくても,次の順番で発言を行う)
String skip = TemplateTalkFactory.skip();
//skip = "Skip"

//パス(他に発言者がいなければその日の発言は終了)
String over = TemplateTalkFactory.over();
// over = "Over"
なお,通常の会話に利用する発話は,
TemplateTalkFactory
を利用し,人狼が囁きを行う場合は,
TemplateWhisperFactory
を利用します.

発話の理解

自分を含めた他のエージェントの発話は,Talk型として渡されます. その具体的な中身は,
Talk talk = talkList.get(0);
String contents = talk.getContents();
として獲得できますが,contentsの中身は先に見たTemplateTalkFactoryで作成した文字列がそのまま獲得されるだけのため,何らかの形で文字列が表す意味を理解しなければいけません.
そこで,定型文を簡単に理解できるようにParserが用意されています.
たとえば,カミングアウトした発話のみを見つけて,カミングアウトしたエージェントとその職業を表示するには以下のようにします.
List talkList = gameInfo.getTalkList();
for(Talk talk:talkList){
 Utterance utterance = new Utterance(talk.getContent());
 Topic topic = utterance.getTopic();
 if(topic == Topic.COMINGOUT){
  Agent target = utterance.getTarget();
  Role role = utterance.getRole();
  System.out.println(target+" comingouts as "+role);
 }
}
もしエージェント10が占い師としてカミングアウトしていたら,コンソールに以下のように表示されます.
Agent[10] comingouts as SEER
Utteranceから抽出できるTopicとそれに付随する変数の一覧を以下に示します.
Topic内容利用可能な情報
ESTIMATE予想getTarget(), getRole()
COMINGOUTカミングアウトgetTarget(), getRole()
DIVINED占い結果getTarget(), getResult()
INQUESTED霊媒結果getTarget(), getResult()
GUARDED護衛対象getTarget()
VOTE投票宣言getTarget()
AGREE意見に賛成getTalkType(), getTalkDay(), getTalkId()
DISAGREE意見に反対getTalkType(), getTalkDay(), getTalkId()
ATTACK襲撃対象(人狼のみ)getTarget()
OVER現段階では話すことは無い
SKIP今回は発話しない
TemplateTalkFactory,TemplateWhisperFactory,Utteranceクラスを利用することで, 他のエージェントに発話を文字列として扱うこと無く人狼知能を構築することが出来ます.

今回まで5回にわたって人狼知能作成方法について書いてきました.
途中長い中断があったために,バージョンが変わって,若干仕様の変更がありましたが,おおむねこれで人狼知能が作成できるようになったはずです
次回は,自分が作った人狼知能と人間が対戦する方法について解説します.

2014年11月3日月曜日

人狼知能エージェントの作成・その4 番外編・人狼知能ver0.1.1xの変更点

人狼知能ver0.1.11にバージョンアップされました.
ダウンロードはこちらから.
ver0.1.1xから,以降からいくつか大きな変更がありました.
ここでは主な変更点についてお知らせします.

RoleBasePlayerからAbstarctRoleAssignPlayerへ変更

主な変更として, RoleBasePlayerのクラス名が AbstarctRoleAssignPlayer と変更になりました.
これに伴って, 人狼知能エージェントの作成・その1で紹介したMyPlayerは以下のようになります.
package org.aiwolf.tori.player;

import org.aiwolf.client.base.player.AbstarctRoleAssignPlayer;

public class MyPlayer extends AbstarctRoleAssignPlayer{
 
 public MyPlayer() {
  setVillagerPlayer(new MyVillagerPlayer());
 }

 @Override
 public String getName() {
  return MyPlayer.class.getSimpleName();
 }

}
となります.

enum型の変更

ver0.1.9までは,enum型の名称が大文字小文字混じりでしたが, すべて大文字で表記するように変更されました.
たとえば,
Status.ALIVE;
Status.DEAD;
の様に表記します.

人狼プロトコルパーサの改訂

人狼プロトコルのパーサの仕様が変更されました. 人狼知能エージェントの作成・その2 Talk編 ではProtocolクラスなどを利用していましたが, ver0.1.1xからはUtteranceクラスで一括管理するようになりました.
これについては,次回詳細を説明します.

その他,細かい変更点がありますが主な変更点は以上です. 対話部分については早めにブログでサンプルをお見せするようにいたします. ご迷惑おかけしますが,よろしくお願いします.

実行クラスの変更(2014-11-14追記)

実行クラスのパッケージが変更されました. RoleRequestStarterで開始する場合,
org.aiwolf.server.bin.RoleRequestStarter
をメインクラスとして指定してください.

2014年6月27日金曜日

人狼知能エージェントの作成・その3 スタート編

2015/02/09変更
サーバのバージョンの変更に従って,一部ソースコードが変更されています.
古いバージョンをお使いの方はver0.1.15以上にアップデートしてください.

さて,今日は人狼知能Talkする編を書こうと思ったけど,
その前に「テスト用に,自分で繰り返し実行する方法が知りたい」という意見があったので,
先にそちらを書こうと思う.

ゲーム実行のためのメインクラス

AIWolfのゲームを実行するには,

  • AIWolfGame:ゲーム実行サーバ本体クラス
  • GameSetting:ゲームの設定クラス
  • GameServer:エージェントとゲームサーバとの通信用クラス
の三つのクラスと,エージェントクラスを利用する.
以下は実行サンプルのソースコードである.
ただし,このソースコードは,人狼知能エージェントの作成・その1-ver0.1.15対応にある BasePlayerをつかっている.ので,作成していない場合は先にこちらを参照して作成して欲しい.

package org.aiwolf.player.bin;

import java.io.File;
import java.io.IOException;

import java.util.HashMap;
import java.util.Map;

import org.aiwolf.client.base.smpl.SampleRoleAssignPlayer;
import org.aiwolf.common.data.Player;
import org.aiwolf.common.data.Role;
import org.aiwolf.common.data.Team;
import org.aiwolf.common.net.GameSetting;
import org.aiwolf.player.BasePlayer;
import org.aiwolf.server.AIWolfGame;
import org.aiwolf.server.GameData;
import org.aiwolf.server.net.DirectConnectServer;



/**
 * テスト用に人狼をスタートするためのMainクラス
 * @author tori
 *
 */
public class TestStarter {

 /**
  * 他のエージェントの数
  */
 static protected int PLAYER_NUM = 12;
 
 /**
  * @param args
  * @throws IOException 
  */
 public static void main(String[] args) throws IOException {
  

  Map playerMap = new HashMap();//・・・(1)
  //BasePlayerはVillagerに固定
  playerMap.put(new BasePlayer(), Role.VILLAGER);
  for(int i = 0; i < PLAYER_NUM-1; i++){
   //そのほかのエージェントは,SamplePlayerで役職の希望は無し.
   playerMap.put(new SampleRoleAssignPlayer(), null);
  }
  
  
  DirectConnectServer gameServer = new DirectConnectServer(playerMap);//・・・(2)
  GameSetting gameSetting = GameSetting.getDefaultGame(playerMap.size());//・・・(3)
  AIWolfGame game = new AIWolfGame(gameSetting, gameServer);//・・・(4)
  game.setLogFile(new File("./log/TestStarter.log"));//・・・(5)
  game.start();//・・・(6)
  Team winner = game.getWinner();//・・・(7)
  if(winner == Team.VILLAGER){
   System.out.println("Win!");
  }
  GameData gameData = game.getGameData();//・・・(8)

 }

}

ソースについて説明する.

メインクラスの説明

(1)役職の登録
まずエージェントクラスと当該エージェントを付けたい役職をMap型クラスに登録する.
新しいインスタンスを作成し,当該エージェントを付けたRoleをMapに登録していくだけである.
この時,各役職の人数制限を超えたエージェントが同じ役職に希望していた場合,その中のいずれかのエージェントが希望する役職になり, それ以外のエージェントは余った役職に割り当てられる.
また,役職を特に指定したくないエージェントはnullをsetすればよい.
この例では,自作のBasePlayerだけVillagerを指定し,それ以外はSampleRoleAssignPlayerとして,いずれも特に役職を指定していない.
(2)Connectorの作成
人狼サーバは,TCPIPを使ってサーバとエージェントを接続するが,テスト用環境ではTCPIPを使う必要性がない. そのような場合は,DirectConnectServerを使う.
ここでは,先ほどの希望役職を指定したMapを引数として,DirectConnectServerを作成している.
なお,すべてのエージェントにランダムに役職を与えて実行する場合は,
List<Agent> playerList = new ArrayList<Agent>();
//中略
DirectConnectServer gameServer = new DirectConnectServer(playerList);
のように,AgentのListを引数としてDirectConnectServerを作成すればよい.
(3)GameSettingの作成
GameSettingクラスは,さまざまなゲームの条件を決めるクラスである.
各役職に付けるエージェント数や,各自の投票結果を他のエージェントに知らせるか,などを決定するものである.
現状では,
GameSetting.getDefaultGameエージェント数);
で取得できるデフォルトのGameSettingを利用することが推奨されている.
これ以外のGameSettingを利用した場合の動作の保証はない.
(4)AIWolfGameの作成
ここでは,ゲーム実行サーバ本体の作成を行っている.
GameSettingとGameServerを指定して作成する.
(5)LogFileの設定
ゲームログを出力する場合,ここでログファイルを指定する.
ログを設定しない場合,ゲームログはコンソールにしか表示されない.
(6)ゲームの開始
game.start();
によって,ゲームが開始される.以降,ゲームが終了するまで自動で処理される.
(7)勝利チームの獲得
ゲーム終了後,どちらが勝ったのかを取得する.
勝利したかどうかに応じてエージェントを進化させたい場合などに利用することが可能.
単純に何回も実行して勝率を確認するためにも利用できる.
(8)全ゲームデータの取得
GameData gameData = game.getGameData();
で獲得できるGameDataは,このゲームで行われたすべての行動が記録されているデータクラスである.
このデータクラスを使って,ゲームの解析を行うことが可能.

以上で,起動メインクラスの説明は終了.
いくつかエージェントを作って,Roleを割り当て方を変えながらエージェントの実力をテストしてみればよいと思う.

2014年6月26日木曜日

人狼知能エージェントの作成・その2 Talk編

Talkの仕様変更に伴い,本記事の内容は利用できなくなりました.人狼知能エージェントの作成・その5 Talk編をご覧ください.






さて,人狼知能作成その2である. 今回は会話にチャレンジする.
とりあえず,人狼知能のサーバ,クライアントなどをGitHubにUpしたので, 中身を見たい人は https://github.com/aiwolf/ を参照して欲しい.
この記事は,書いている時点で最新のライブラリを使っているので,古いバージョンだと一部動かないこともあるかもしれないし, 新しいバージョンになっても変更があるかもしれないので,その点はご了承ください.
まだ,開発中のライブラリということで,ご勘弁を・・・
一応動かない機能が発生する場合はマイナーバージョンをアップするつもりではいます.
ちなみに,現在これを書いている段階で,バージョンは0.1.6.

発話の取得

ところで,前回までは村人エージェントについて,適当に自分以外の人に投票するようにした.
今回は,ほかの人が何を言っているのか聞いてみたいと思う.
他の人が話したことは,GameInfo#gameInfo.getTalkList()で獲得可能である.
まずは,自分の話す番が来るたびにそれまでの会話を確認するようにしてみよう.
自分が話す番が来たら,talkメソッドが呼ばれるので,そこに以下のように記述する.
 @Override
 public String talk() {
  GameInfo gameInfo = getLatestDayGameInfo();
  List<Talk> talkList = gameInfo.getTalkList();
  
  System.out.println("Today's talk");
  for(Talk talk:talkList){
   System.out.println(talk);
  }
  return null;
 }
これを実行すると,
Today's talk
Day00[000] Agent[02] 5 werewolf
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
Day01[007] Agent[06] 1 werewolf
Day01[008] Agent[11] 10 werewolf
Day01[009] Agent[11] Over
Day01[010] Agent[10] Over
Day01[011] Agent[03] Over
Day01[012] Agent[04] Over
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
Day01[007] Agent[06] 1 werewolf
Day01[008] Agent[11] 10 werewolf
Day01[009] Agent[11] Over
Day01[010] Agent[10] Over
Day01[011] Agent[03] Over
Day01[012] Agent[04] Over
Day01[013] Agent[06] Over
Day01[014] Agent[02] 9 werewolf
Day01[015] Agent[09] Over
Day01[016] Agent[12] Over
Day01[017] Agent[01] Over
Day01[018] Agent[09] Over
Day01[019] Agent[11] Over
Day01[020] Agent[01] Over
Day01[021] Agent[06] Over
Day01[022] Agent[12] Over
Day01[023] Agent[04] Over
というように表示されるはず.
これを見れば分かる通り,getTalkListは,gameInfoが指す日のすべてのTalkを返してくる. したがって,前回からの差分は取ることができない.
差分がとりたい場合は,前回どこまでのTalkを取得したか記憶しておく必要がある.
そこで,アップデートされたTalkだけを取得するメソッドgetUpdatedTalkを作成してみる.
 @Override
 public String talk() {
  List<Talk> talkList = getUpdatedTalk();
  
  System.out.println("Updated talk");
  for(Talk talk:talkList){
   System.out.println(talk);
  }
  return null;
 }

 /**
  * 前回最後に獲得したTalkのインデックス
  */
 int lastTalkIdx = -1;
 
 /**
  * 前回最後に獲得したTalkの日付
  */
 int lastTalkDay = -1;
 
 /**
  * 前回Talkを取得した後投稿されたTalkのみを獲得する
  * @return 前回獲得したTalkとの差分が入ったList
  */
 protected List<Talk> getUpdatedTalk(){
  GameInfo gameInfo = getLatestDayGameInfo();
  List<Talk> talkList = gameInfo.getTalkList();
  
  if(lastTalkDay != gameInfo.getDay()){
   lastTalkDay = gameInfo.getDay();
   if(!talkList.isEmpty()){
    lastTalkIdx = talkList.get(talkList.size()-1).getIdx();
   }
   return talkList;
  }
  for(int i = 0; i < talkList.size(); i++){
   if(talkList.get(i).getIdx() > lastTalkIdx){
    lastTalkIdx = talkList.get(talkList.size()-1).getIdx();
    return talkList.subList(i, talkList.size());
   }
  }
  return new ArrayList<>();
 }
Updated talk
Day00[000] Agent[04] 7 werewolf
Day00[001] Agent[08] 4 werewolf
Day00[002] Agent[05] 11 werewolf
Day00[003] Agent[10] 4 werewolf
Day00[004] Agent[11] 4 werewolf
Day00[005] Agent[01] 5 werewolf
Day00[006] Agent[12] 8 werewolf
Day00[007] Agent[07] 10 werewolf
Day00[008] Agent[02] 10 werewolf
Day00[009] Agent[06] 10 werewolf
中略
Updated talk
Day00[010] Agent[09] 6 werewolf
Day00[011] Agent[08] Over
中略
Updated talk
Day02[000] Agent[01] 10 werewolf
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
Day02[003] Agent[02] 6 werewolf
Day02[004] Agent[09] 6 werewolf
Day02[005] Agent[11] 6 werewolf
というわけで,無事前回との差分を獲得できた.

発話の内容

さて,次に他のエージェントが何を言っているのかを理解しよう.
ログに表示される
Day00[000] Agent[04] 7 werewolf
は,0日目のTalkIndex000は,Agent04が,「7 werewolf」と発言していることを示している.
Day00[011] Agent[08] Over
は,エージェント08はもうこれ以上話すことはない,という発言をしている.
さらに,
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
では,エージェント10が,「( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 」と発言し, 同様にAgent06も長い発言をしている.
このうち,Overは良いとして,それ以外の発話はどういう意味だろうか.
まず,
Day00[000] Agent[04] 7 werewolf
は,「Agent7が人狼ではないかと疑っている」宣言である.事実上,今日投票するのはAgent7であると同等のもの・・・らしい.
(これは,SamplePlayerの実装依存だが,ソースコードを見ると,そうなっている)
次に,長い発話についてみてみよう.
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
については,
  • Agent2が人狼だと思う
  • Agent10は占い師だとカミングアウトする
  • Agent9は占いの結果人間だった
  • Agent6は占いの結果人狼だった
という4つの内容が含まれた発話である.
一方,
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
は,
  • Agent1が人狼だと思う
  • Agent6は霊媒師だとカミングアウトする
  • Agent3は霊媒の結果人間だった
  • Agent4は霊媒の結果人間だった
という内容を含んだ発話である.

発話の理解

発話は上記のようにテキストとして送られてくるため,それをエージェントが理解する必要がある. このとき,直接発話のテキストを見て,何を言っているのか理解をしてもよいが, そのためのParser(構文解析機)を作るのは手間がかかるため,aiwolf-clientライブラリでは,発話をマシンリーダブルに変換するためのParserライブラリが用意されている.
そこで,実際にTalkの内容をパースしてみよう.
Talkのパースには,Protocolクラスを利用する.
Protocolクラスは,人狼プロトコルを理解するためのクラスで,複数のUtteranceから構成され, UtteranceはSentenceTypeとPassageからなり・・・という多層構造を持っている.
大まかなProtocolクラスの概略は以下の通り.

プロトコルクラスのインスタンスをパースした発話から作成すると,この構造に従って発話をパースしてくれる.
実際に以下のコードで実行してみる.
なお,ここではその日の会話を総合して投票先を決める,ということを考え,talkメソッドではなく,voteメソッドでtalkのパースを行う.
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  
  List<Talk> talkList = gameInfo.getTalkList();
  for(Talk talk:talkList){
   System.out.println(talk);
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   System.out.println("Num of Utterance="+utteranceList.size());
   for(Utterance utterance:utteranceList){
    SentenceType sentenceType = utterance.getSentenceType();
    Passage passage = utterance.getPassage();
    
    if(sentenceType != null){
     System.out.println("sentenceType");
     System.out.println(sentenceType.getUv());
     System.out.println(sentenceType.getRate());
    }
    
    if(passage != null){
     System.out.println("passage");
     System.out.println("Action="+passage.getAction());
     System.out.println("Attribution="+passage.getAttribution());
     System.out.println("Category="+passage.getCategory());
     System.out.println("Object="+passage.getObject());
     System.out.println("State="+passage.getState());
     System.out.println("Subject="+passage.getSubject());
     System.out.println("Verb="+passage.getVerb());
    }
    
   }
  }
  
  List<Agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

すると,「7 werewolf」に関してだと,以下のような出力が得られる.
Day00[003] Agent[07] 7 werewolf
Num of Utterance=1
passage
Action=null
Attribution=null
Category=ESTIMATE
Object=null
State=werewolf
Subject=Agent[07]
Verb=is
特に内容がないところにはnullが入ってくるので注意が必要.
さて,この中身について具体的にみる. まず「7 werewolf」という発話は,「Agent7は人狼だと思う」という意味を持つ.
文としては単文のため,Utteranceは1つ.
そのUtteranceを分解すると,
  • カテゴリは推定(ESTIMATE)
  • 対象(Subject)はAgent07
  • 動詞はis
  • 状態は人狼(werewolf)
となっていることが分かる.

一方,長い発話の場合はどうなるか, 「( 2 werewolf ) and ( 4 comingout seer ) and ( 12 inspected HUMAN ) and ( 10 inspected 」を例に見てみよう.
Day03[005] Agent[04] ( 2 werewolf ) and ( 4 comingout seer ) and ( 12 inspected HUMAN ) and ( 10 inspected werewolf ) 
Num of Utterance=4
passage2 werewolf部分のUtterance
Action=null
Attribution=null
Category=ESTIMATE
Object=null
State=werewolf
Subject=Agent[02]
Verb=is
passage//4 comingout seer部分のUtterance
Action=null
Attribution=null
Category=COMINGOUT
Object=seer
State=null
Subject=Agent[04]
Verb=comingout
passage//12 inspected HUMAN部分のUtterance
Action=inspected
Attribution=Human
Category=RESULT
Object=null
State=null
Subject=Agent[12]
Verb=inspected
passage//10 inspected部分のUtterance
Action=inspected
Attribution=Werewolf
Category=RESULT
Object=null
State=null
Subject=Agent[10]
Verb=inspected
となる.二番目のPassageにおいて,このエージェントがseerであるとCOMINGOUTしていることが分かる.

他エージェントの発話を考慮した投票先決定

では,最後に他のエージェントの発話に基づいて投票先を決めるようにしてみよう.
ここでは,簡単のために
「seerだとカミングアウトしたエージェントの投票に合わせる」.
という戦略を実装する.

 /**
  * 人狼候補
  */
 Set werewolfCandidateSet = new HashSet<>();
 
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  List<Talk> talkList = gameInfo.getTalkList();
  for(Talk talk:talkList){
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   for(Utterance utterance:utteranceList){
    SentenceType sentenceType = utterance.getSentenceType();
    Passage passage = utterance.getPassage();
    
    
    if(passage != null){
     if(passage.getCategory() == Category.COMINGOUT){
      if(passage.getObject() == Role.seer){
       Agent candidate = talk.getAgent();
       seerCandidateSet.add(candidate);
      }
     }
    }
   }
  }
  
  System.out.println("Find Candidate");
  //占い師の意見だけ採用
  for(Talk talk:talkList){
   if(!seerCandidateSet.contains(talk.getAgent())){
    continue;
   }
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   for(Utterance utterance:utteranceList){
    Passage passage = utterance.getPassage();
    
    if(passage.getCategory() == Category.RESULT && passage.getVerb() == Verb.inspected){
     if(passage.getAttribution() == Species.Werewolf){
      System.out.println(talk);
      Agent candidate = passage.getSubject();
      if(candidate != myself){
       werewolfCandidateSet.add(candidate);
      }
      else{
       //自分を人狼だと言ってきたら,その占い師こそが人狼だ!
       seerCandidateSet.remove(talk.getAgent());
       werewolfCandidateSet.add(talk.getAgent());
      }
     }
    }
   }
  }
  
  for(Agent candidate:seerCandidateSet){
   System.out.println("Seer candidate:"+candidate+" "+gameInfo.getStatusMap().get(candidate));
  }
  for(Agent candidate:werewolfCandidateSet){
   System.out.println("Werewolf candidate:"+candidate+" "+gameInfo.getStatusMap().get(candidate));
  }
  
  //見つかった人狼候補に生きているものがいれば,そこに投票
  for(Agent candidate:werewolfCandidateSet){
   if(gameInfo.getStatusMap().get(candidate) == Status.alive){
    System.out.println(myself+" vote to "+candidate+" by seer's result");
    return candidate;
   }
  }
  
  //生きている人狼候補がいなければ,適当に投票
  List<Agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself || seerCandidateSet.contains(agent)){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

この結果以下のようになった.
Find Candidate
Day02[008] Agent[04] ( 12 werewolf ) and ( 3 inspected werewolf ) 
Seer candidate:Agent[04] alive
Seer candidate:Agent[12] alive
Werewolf candidate:Agent[03] alive
Agent[10] vote to Agent[03] by seer's result

中略

===========
Day 03
Agent[03] executed
Agent[12] divine Agent[04]. Result is Human
Agent[07] guarded Agent[09]@2 guarded
null attacked
======
Agent[01] SamplePlayer dead medium
Agent[02] SamplePlayer alive villager
Agent[03] SamplePlayer dead werewolf executed
Agent[04] SamplePlayer alive possessed divined
Agent[05] SamplePlayer alive villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager guarded
Agent[10] BasePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive seer
というわけで,seer(Agent12)の占い結果に基づいてAgent03に投票し, 結果として人狼を処刑することに成功した.
今回は,他のエージェントのTalkを理解して,それを投票につなげるコードを書いてみた.
次は,自分の意見をTalkで表明することを目指してみよう.

2014年6月20日金曜日

人狼知能エージェントの作成・その1

ver0.1.16に対応するよう改訂しました(2015/03/07).
ver0.1.15までとの最大の違いは,RoleAssignPlayerに挿入するPlayerがPlayerクラスを継承しなくなったことです.
AbstractVillagerPlayer→AbstractVillagerのようになりました.
ご了承ください.
今日は人狼知能エージェントを作ろうと思うので,その作り方をメモしていく.

まずは,http://www.aiwolf.org/の資料→人狼知能サーバから人狼知能0.1.15をダウンロード.

中身は簡易マニュアルとjarファイルが4つ.
aiwolf-client-0.1.16.jar
aiwolf-common-0.1.16.jar
aiwolf-server-0.1.16.jar
jsonic-1.3.2.jar

Eclipseで新しいプロジェクトを作って,これらのjarファイルをライブラリに登録しておく.

さて,人狼知能エージェントを作るには,まずRoleBasePlayerを継承したクラスを作るところから始まる.
とりあえず,面倒なのでBasePlayerという名前で作成.


BasePlayerは何をやるかというと,職業が割り当てられたときに,職業ごとに異なるPlayerを起動してくれるのである.
人狼においては,占い師になったときと人狼になったときでは,プレイ方法が全然異なる.
そこで,職業が割り当てられた時点で,職業ごとに対応するPlayerクラスを立ち上げるようにしておけば,一つのPlayerクラスに付き一つの職業についてのみ考えれば良くなるわけである.

ここでは,まず村人から作ってみよう.
というわけで,新しくMyVillagerPlayerを作成する.
自作村人エージェントを作るには,org.aiwolf.client.base.player.AbstractViillagerPlayerを継承したクラスを作れば良い.

package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractVillager;
import org.aiwolf.common.AIWolfRuntimeException;
import org.aiwolf.common.data.Agent;
import org.aiwolf.common.net.GameInfo;

public class MyVillagerPlayer extends AbstractVillager {

 @Override
 public void dayStart() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public void finish() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public String talk() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

 @Override
 public Agent vote() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

}


今のところ何もしないが,とりあえずこのMyVillagerPlayerをBasePlayerに登録する.
package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractRoleAssignPlayer;

public class BasePlayer extends AbstractRoleAssignPlayer {

 public BasePlayer() {
  setVillagerPlayer(new MyVillager());
 }

 @Override
 public String getName() {
  return BasePlayer.class.getSimpleName();
 }
}
まずは,ここで起動してみよう.
起動する場合は,Eclipseのメニューで実行→実行構成
名前はRoleRequestStarterとでもしておいて,メインクラスにRoleRequestStarterを選ぶ.
さらに,引数タグを選んで,プログラムの引数に
-n 12 -c org.aiwolf.player.BasePlayer VILLAGER
と書いておこう.






これで,実行.
Agent[04] request no role
Agent[08] request no role
Agent[01] request no role
Agent[05] request no role
Agent[03] request no role
Agent[10] request no role
Agent[02] request no role
Agent[11] request no role
Agent[09] request villager
Agent[12] request no role
Agent[06] request no role
Agent[07] request no role
===========
Day 00
======
Agent[01] SamplePlayer alive possessed
Agent[02] SamplePlayer alive villager
Agent[03] SamplePlayer alive villager
Agent[04] SamplePlayer alive bodyguard
Agent[05] SamplePlayer alive seer
Agent[06] SamplePlayer alive werewolf
Agent[07] SamplePlayer alive werewolf
Agent[08] SamplePlayer alive medium
Agent[09] BasePlayer alive villager
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive villager
10-2
===========
こんな感じでBasePlayerがvilagerになっていれば起動は完了.
その後は自動でゲームが進行する. ところで,ここではvoteでnullを返している.つまり投票先を決めていないことになる.
この場合,ver0.1.16ではランダムに投票したことになっている.
別に問題は無いが,せっかくなのでnull以外を返すように変更してみよう.

変更するのは,MyVillagerのvoteメソッド.
 @Override
 public Agent vote() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }
まずは,簡単に
・生きているエージェントの中で
・自分を除いて
・一番最初に見つけたエージェント
に投票することにしよう.

進行中のゲームに関する全ての情報は,
getGameInfoで取得可能.
特に,最新の情報は

GameInfo gameInfo = getLatestDayGameInfo();
で取得可能.
GameInfoからは様々な情報が取得可能だが,まずは自分自身のエージェントと状態,職業,そして日付を取得してみよう.
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();
  System.out.println("I am "+gameInfo.getStatusMap().get(myself));
  System.out.println("I am "+gameInfo.getRole());
  System.out.println("Today is "+gameInfo.getDay()+" day");
と書いておくと,
I am alive
I am villager
Today is 0 day
と表示され,とりあえずちゃんと動いていることが分かる.
さて,次はいよいよ投票する対象を決めよう.
生きているエージェント一覧は,gameInfo.getAliveAgentList();で取得可能.
このとき,返ってきたエージェントの中で自分じゃない最初のエージェントに投票することにする.
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();
  List<agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }
自分しかいなかった場合は誰も選ばれずにメソッドが終わってしまうが,ゲームの仕様上そんなことはないので, もしそんなことがあったらAIWolfRuntimeExceptionを投げて終了することにしておこう.
さて,コレで実行するとどうなるか.
6 20, 2014 4:24:48 午後 org.aiwolf.common.util.AiWolfLoggerFactory getSimpleLogger
構成: ログ設定: LogManagerを設定しました。
Agent[07] request no role
Agent[04] request no role
Agent[03] request no role
Agent[01] request no role
Agent[09] request no role
Agent[10] request no role
Agent[08] request no role
Agent[11] request no role
Agent[05] request villager
Agent[12] request no role
Agent[02] request no role
Agent[06] request no role
===========
Day 00
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer alive werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive villager
10-2
===========

中略

Agent[05] vote to Agent[03]
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,5,3
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,6,6
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,11,6
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,12,1
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,9,2
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,10,8

中略

===========
Day 01
Agent[06] executed
Agent[01] divine Agent[09]. Result is Human
Agent[07] guarded Agent[08]@0 guarded
Agent[11] attacked
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer dead werewolf executed
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager guarded
Agent[09] SamplePlayer alive villager divined
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer dead villager attacked
Agent[12] SamplePlayer alive villager
9-1

中略

===========
Day 02
Agent[10] executed
Agent[01] divine Agent[12]. Result is Human
Agent[07] guarded Agent[01]@1 guarded
null attacked
======
Agent[01] SamplePlayer alive seer guarded
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager
Agent[10] SamplePlayer dead villager executed
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager divined

中略
===========
Day 03
Agent[05] executed
Agent[01] divine Agent[04]. Result is Human
Agent[07] guarded Agent[02]@2 guarded
Agent[09] attacked
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf guarded
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium divined
Agent[05] BasePlayer dead villager executed
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager attacked
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager
中略
===========
Day 04
Agent[01] executed
Agent[01] divine Agent[08]. Result is Human
Agent[07] guarded Agent[08]@3 guarded
Agent[01] attacked
======
Agent[01] SamplePlayer dead seer executed attacked
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager divined guarded
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager

中略

===========
Day 05
Agent[07] executed
Agent[07] guarded Agent[04]@4 guarded
null attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium guarded
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard executed
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager

中略

===========
Day 06
Agent[12] executed
Agent[04] attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer dead medium attacked
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer dead villager executed

===========
Day 07
Agent[03] executed
Agent[03] attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer dead possessed executed attacked
Agent[04] SamplePlayer dead medium
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer dead villager
===========
Send finish to Agent[03]
Send finish to Agent[04]
Send finish to Agent[01]
Send finish to Agent[02]
Send finish to Agent[07]
Send finish to Agent[08]
Send finish to Agent[05]
Send finish to Agent[06]
Send finish to Agent[11]
Send finish to Agent[12]
Send finish to Agent[09]
Send finish to Agent[10]
Winner:werewolf
というわけで,ちゃんと別のエージェントを指定して投票行動を行っていたことが分かります.
ただし,三日目に本人が処刑されちゃったみたいですね.
しかも,最後は人狼の勝利.
投票行動を適当に行うだけではやはり勝てそうもありません.
次回は,他の人が何を言っているのか理解して,占い師が人狼だと言っているエージェントに投票するように 改良してみたいと思います.
できるのかなあ?



ちなみに,今日作ったMyVillagerのソースコードは以下の通り.
package org.aiwolf.player;

import java.util.List;

import org.aiwolf.client.base.player.AbstractVillager;
import org.aiwolf.common.AIWolfRuntimeException;
import org.aiwolf.common.data.Agent;
import org.aiwolf.common.net.GameInfo;

public class MyVillager extends AbstractVillager {

 @Override
 public void dayStart() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public void finish() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public String talk() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  List agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

}
BasePlayerのソースコードは以下の通り.
package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractRoleAssignPlayer;

public class BasePlayer extends AbstractRoleAssignPlayer {

 public BasePlayer() {
  setVillagerPlayer(new MyVillager());
 }

 @Override
 public String getName() {
  return BasePlayer.class.getSimpleName();
 }
}

2014年5月31日土曜日

www2014とか輪読会

昨年は誰も参加しなくて断念したwwwの輪読会をやったので,
せっかくなのでスライドをアップしてもらった.
なぜか,一部AAMAS2014とIJCAI2013の論文も混ざってるけどw

Modeling and Predicting the Growth and Death of Membership-based Websites
www2014

Fine-grained Data Partitioning Framework for Distributed Database Systems
www2014

Opinion Dybnamics of Skeptical Agent
AAMAS2014

Information Network or Social Network? The Structure of the Twitter Follow Graph
www2014

Timeline Generation: Tracking individuals on Twitter
www2014

The Bursty Dynamics of the Twitter Information Network
www2014

Decision Generalisation from Game Logs in No Limit Texas Hold’em
IJCAI2013

Who Proposed the Relationship? — Recovering the Hidden Directions of Undirected Social Networks
www2014

土曜日丸一日掛けての輪読会お疲れ様でした.
非常に興味深い&楽しい輪読会でした.
半年に一回くらいはやってもいいかも.