第2回 さくさくテキストマイニング勉強会に行ってきた

前回に引き続き、「第2回 さくさくテキストマイニング勉強会」に参加してきました。
会場は前回と同じOracle青山センターです。200人ぐらい入る大きな会議室。さすがです。

目的&概要

目的

テキストマイニングについての学習のスタートアップ
テキストマイニング技術に関して気軽に参加・議論することができる場の提供
概要

テキストマイニングとは、例えば製品の評判をweb上のテキストから抽出したり、
大量のアンケートテキストを分析するために用いられる技術であり、特にマーケティングの場で多くの利用例があります。

この勉強会ではそうしたテキストマイニングを題材とし、用いられている要素技術とそれに関わる課題の議論、
またテキストマイニングを実務に活かす方法について考えていきます。

第2回「にこにこ」改め さくさくテキストマイニング勉強会
言語処理学会へ遊びに行ったよ〜不自然言語処理へのお誘い〜 [twitter:@AntiBayesian]

資料は ここ(PDF) から。

  • 自然言語処理
    • 実際の言語は、誤字、脱字、略字、顔文字、絵文字などが溢れている
    • 従来のテキストマイニングでは、顔文字などはゴミとして除去していた
    • 顔文字こそ書き手の思いが宿っているのでは?
  • 顔文字情報と文の評価表現の関連性についての一考察
    • 顔文字は周辺言語的要素を持つ
    • 顔文字単体での極性だけではなく、文脈把握が大切
    • 顔文字は回答者によって、快・不快バラバラ
    • 極性が異なるのに同じ顔文字が使われることも
    • クラス分類ではなく、複数の感情軸を併せ持つ
    • 自身は意味を持たず、強調、緩衝材としての顔文字利用
  • どんな時に不自然言語を使うか?
    • 仲の良い人同士だと砕けた表現や隠語使いやすい
    • 他の人より頻繁に不自然言語を用いて会話する相手=仲が良いのでは?ソーシャルネットワーク抽出出来る
    • 自然言語の利用度合いが親密さを表すかも
    • 顔文字は非言語的な情報まで伝達出来るかも
    • 誤字・脱字から精神状態などを読み取れるかも
    • 誤った語の使い方から年齢等が推定できるかも

必ずチェックすべき10のブログ (下に行く程、難しい内容らしい。上から5つぐらいは読むべし)

  1. コーパスいぢり( [twitter:@langstat] )
  2. あらびき日記( [twitter:@a_bicky] )
  3. 睡眠不足?( [twitter:@sleepy_yoshi] )
  4. EchizenBlog-Zwei( [twitter:@echizen_tm] )
  5. Overlasting::Life( [twitter:@overlast] )
  6. おとうさんの解析日記( [twitter:@isseing333] )
  7. はやしのブログRev.2( [twitter:@phosphor_m] )
  8. nokunoの日記( [twitter:@nokuno] )
  9. ぬいぐるみライフ(仮)( [twitter:@mickey24] )
  10. Mi manca qualche giovedi`( [twitter:@shuyo] )
今日から使える! みんなのクラスタリング超入門 [twitter:@toilet_lunch]

非階層型、ハードクラスタリングの手法 k-meansについてのお話。

  • 問題点
    • 最適になるとは限らない
      • 最初の重心に決め方によっては、誤った
    • 偏ったクラスタリングに不向き
      • 分けるべきクラスタのサイズが異なると精度が下がる
  • bayon
  • x-means
条件付き頻度分布 with NLTK [twitter:@gepuro]

インターンシップ先募集中の大学3年生!!

  • twitterのパブリックタイムラインは複数の言語を含む
    • SJIS変換を行い変換できれば日本語
      • ひらがなで正規表現チェックすれば、そこそこの精度で判定可能らしい
    • 形態素解析を行い日本語の名詞のみ抽出
『可視化するだけ』でも面白い テキストマイニング最初の一歩 [twitter:@ts_3156]

えごったー の中の人の発表。

  • えごったー
    • ツイッターのフォロー情報を可視化
    • アカウント名を入力するだけで使える
    • 認証情報は不要です→誰の情報でも見れます
    • 5000回 / 日くらい使われている
    • フォローしている / されているだけの人一覧
    • 口癖分析
    • よくツイートする時間帯
    • リプライを見られている人一覧
    • 入っているクラスタ分析
    • リムーブした / された人一覧
  • クラスタ分析の実装解析
    • 同じリストに入っているユーザを見つける
    • そのユーザのプロフィールを連結し、形態素解析する
    • 出現頻度が高い単語が、そのユーザの関連語
  • 高度な知識は必ずしも必要ない
    • 今回利用したのは、形態素解析だけ
    • 簡単な解法で解ける問題もたくさんある
  • 可視化するだけでもよい、面白さは人が勝手に見つける
    • えごったー側で行うのは簡単な情報の提示のみ
    • 意味を汲み取るのは人間の役目
  • 面白い可視化の例

見切り発車でも大丈夫。とりあえず作ってみよう

概観テキストマイニング [twitter:@todesking]

  • 大量のデータ
    • 人間ではハンドリングできない程に多いデータを相手にする
    • 統計的手法はデータが多いほうが有効
    • 計算機の出番

データマイニング

  • フリーテキスト
    • 自然言語による記述
    • 構造化されていないデータ
    • さまざまなノイズ
      • 表記ゆれ
      • 未知の表現
    • 計算機で処理するには工夫が必要
    • 前処理必須
    • 解析ミスがつきもの
  • なぜフリーテキストなのか?
    • データ量はパワーである
    • ノイズ混じりだが大量のデータを入手可能
    • 事前に構造を規定しなくていい
  • フリーテキストの構造を分析する
    • ノイズとの戦い
      • 未知の用語
      • 誤記
  • テキストマイニングでいかに価値を生むか
    • その分析結果が何を意味するのか?
    • 次にすべき行動は?
    • その行動は誰に、どのような価値をもたらすか?
WordNetで作ろう! 言語横断検索サービス [twitter:@stakemura]

社内google code を作りたいが、増え続けるプログラムをすぐに見つからない状態をなんとかしたい。

  • 検索機能を強化
    • 日英表記揺れを解決
    • 語義曖昧性を解決
  • 言語横断検索のアプローチ
    • 「対訳辞書」ベース
      • 日本語WordNetのような辞書を活用するアプローチ
    • 「対訳コーパス」ベース
      • 辞書がない、あるいは併用するアプローチ
    • 機械翻訳」ベース
  • WordNetから類義語の抽出手順(日→英)
    • MeCabで標準形と品詞を取得
    • 名詞・副詞・形容詞・動詞のみ抽出
    • SQLにてword→sense→関連sense→関連word

検索結果がよろしくない。

  • BM25Fでやっと納得のいく結果が得られた
R言語によるはじめてのテキストマイニング [twitter:@yokkuns]

Rパッケージガイドブックの著者の一人のyokkunsさんの発表。

  • YjdnJlpパッケージを使用すると、MeCabやCaBoChaをなど外部ライブラリを使用せず、テキスト解析が可能
  • コレスポンデス分析まで一気にやれる
  • 形態素解析
    • MAService関数
  • 係り受け解析
    • DAService関数
  • 特徴語抽出
    • Keyphrase関数
「コレカモネット」の作り方

発表の中で出てきた「コレカモネット」について、飛び入りLTが。
精度が足りない分を、アイデアでカバーしたお話。

togetter [twitter:@kimukou_26]


所感&まとめ
  • 初心者向け勉強会のはずが、業界を代表する面々が参加されている。
  • テキストマイニングは前処理が必須で、最重要工程。十分な精度が出せるとは限らないが、適用箇所やそれを補うアイデアも必要。

MongoDBのShardingを試してみた。その2 Migration中の挙動について

Sharding環境にて、shard keyを使用しない以下のクエリに問題があることが知られている。
(第2回MongoDB勉強会の@doryokujinさんの発表参照。私の参加日記は ここ から)

  • Chunk Migration中のcount
  • 同時書き込み発生時のsingle-update()
  • 同時書き込み発生時のunique index
  • tmp_collectionが削除されないmapreduce

今回は、Sharding環境にて、chunkのMigration中の各クエリの挙動について検証します。
同時書き込みの発生再現は、難しいので今回は割愛します。

この検証では、mongodb 1.8.0を使用しています。
構成については、 MongoDBのShardingを試してみた。その1 を参照

Chunk Migration中のcount

現象
  • 正しい値より大きい値が出力される可能性がある。

イメージでみるとこんな感じ。

原因
  • Chunk Migration中は処理が完了するまで、移動元、移動先に同じchunkが存在する。
  • 完全にコピーが完了し、configサーバが更新されて完了となる。
検証

まずは、意図的にchunkの偏りを発生させます。
sharding設定前に100万件程のアクセスログを投入し、確認していこうかと思います。

[matsuou1@testsvr mongodb]$ ./bin/mongoimport -h localhost:10001 -d logdb -c logs10 --ignoreBlanks --type tsv --fields month,uid,timestamp,path,device /tmp/ mongodb/work/access_201003.log
connected to: localhost:10001
(略)
imported 946611 objects

shardingの設定を行い、chunkの分散を確認します。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10000/logdb
MongoDB shell version: 1.8.0
connecting to: localhost:10000/logdb
> db.logs10.count();
946611
> db.logs10.ensureIndex( { uid : 1 } );
> use admin
switched to db admin
> db.runCommand( { shardcollection : "logdb.logs10" , key : { uid : 1 } } );
{ "collectionsharded" : "logdb.logs10", "ok" : 1 }
> db.printShardingStatus();
(略)
                logdb.logs10 chunks:
                                shard0001       1
                                shard0002       1
                                shard0000       266
                        too many chunksn to print, use verbose if you want to force print

> db.printShardingStatus();
(略)
                logdb.logs10 chunks:
                                shard0001       25
                                shard0002       25
                                shard0000       218

最初、shard0000にあった全てのchunkが、shard0001、shard0002にmigrationが行われ、平準化されています。
全て平準化される前に、countを実施するとchunk migration途中のchunkが重複カウントされ、正しい値より大きな値が表示されるはず。

とりあえず、shard keyではないtimestampを条件にしたcountを10秒に1回程度実行してみましょう。

> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285133 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285108 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285120 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285047
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285128 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285140 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284984 ★
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
284078
> db.logs10.find(  { timestamp : {$gt : "2010/03/10 00:00:00" ,  $lt : "2010/03/20 00:00:00" } } ).count();
285153 ★
まとめ

★がついている個所が、正しい値(284078)より多い数値が表示された。
表示される値は都度異なり、重複カウントされるchunkに依存する。
25回中6回、不正な値が出力された。約25%なので、migration中はかなりの確率で発生しうると言える。
運用中のchunk migration時にも気をつける必要があるが、single構成からsharding構成に変更する際も当然注意が必要。

MongoDBのShardingを試してみた。その1

MongoDB勉強会で発表を聞いてきたので、早速Shardingの機能を試してみます。

環境構成

まずは、以下の構成をします。
本当は複数台のサーバを使用したいのですが、手元に自由に扱えるサーバがさくらVPSしかないため
1台に複数のmongodを立て、mongosとconfigも同居させています。
mongodbのバージョンは、1.8.0を使用しています。


shardサーバ起動

mongodをポート別に3つ起動します。

[matsuou1@testsvr mongodb]$ ./bin/mongod --shardsvr --port 10001 --dbpath /tmp/mongodb/shard1 --logpath /tmp/mongodb/log/shard1.log &
[1] 14269
[matsuou1@testsvr mongodb]$ ./bin/mongod --shardsvr --port 10002 --dbpath /tmp/mongodb/shard2 --logpath /tmp/mongodb/log/shard2.log &
[2] 14276
[matsuou1@testsvr mongodb]$ ./bin/mongod --shardsvr --port 10003 --dbpath /tmp/mongodb/shard3 --logpath /tmp/mongodb/log/shard3.log &
[3] 14287
configサーバ起動
[matsuou1@testsvr mongodb]$ ./bin/mongod --configsvr --port 10004 --dbpath /tmp/mongodb/config --logpath /tmp/mongodb/log/config.log &
[4] 14296
mongosルータ起動

chunkの動作も見たいので、chunk sizeを1MBに設定し起動する。

[matsuou1@testsvr mongodb]$ ./bin/mongos --configdb localhost:10004 --port 10000 --logpath /tmp/mongodb/log/mongos.log --chunkSize 1 &
[5] 14319
Shard構成

まずは、mongosのadminに接続し、Shardを追加する。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10000/admin
MongoDB shell version: 1.8.0
connecting to: localhost:10000/admin
> db
admin
> db.runCommand( { addshard : "localhost:10001" } );
{ "shardAdded" : "shard0000", "ok" : 1 }
> db.runCommand( { addshard : "localhost:10002" } );
{ "shardAdded" : "shard0001", "ok" : 1 }
> db.runCommand( { addshard : "localhost:10003" } );
{ "shardAdded" : "shard0002", "ok" : 1 }

追加したshardが正しく追加されているかどうか、確認する。

> db.runCommand( { listshards : 1 } );
{
        "shards" : [
                {
                        "_id" : "shard0000",
                        "host" : "localhost:10001"
                },
                {
                        "_id" : "shard0001",
                        "host" : "localhost:10002"
                },
                {
                        "_id" : "shard0002",
                        "host" : "localhost:10003"
                }
        ],
        "ok" : 1
}

> db.printShardingStatus();
--- Sharding Status ---
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
      { "_id" : "shard0000", "host" : "localhost:10001" }
      { "_id" : "shard0001", "host" : "localhost:10002" }
      { "_id" : "shard0002", "host" : "localhost:10003" }
  databases:
        { "_id" : "admin", "partitioned" : false, "primary" : "config" }
テストデータ登録

使用するテストデータは、某サーバのアクセスログを使用しようかと思います。
年月、ユーザID、タイムスタンプ、URI、デバイスコードぐらいを投入してみます。

[matsuou1@testsvr mongodb]$ ./bin/mongoimport -h localhost:10000 -d logdb -c logs --ignoreBlanks --type tsv --fields month,uid,timestamp,path,device /tmp/mongodb/work/access_20100301.log
connected to: localhost:10000
imported 28130 objects

sharding開始

ようやく環境の構成が完了したので、次はshardingの設定を行います。

コレクション単位でのshardingの開始

mongosに接続して、shardcollectionコマンドを発行する。今回のshard keyは勉強会で良い例として紹介されてたアクセスログ
年月とユーザIDの複合キーを採用。

いざ、shardcollectionコマンドを実行すると以下のエラーが。。。

> db.runCommand( { shardcollection : "logdb.logs" , key : { month : 1 , uid : 1 } } );
{ "ok" : 0, "errmsg" : "sharding not enabled for db" }

エラーメッセージに従い、先にデータベース単位でのshardingしないとだめそうなので、先にenableshardingコマンドを実行する。

> db.runCommand( { enablesharding : "logdb" });
{ "ok" : 1 }

> db.runCommand( { shardcollection : "logdb.logs" , key : { month : 1 , uid : 1 } } );
{
	"ok" : 0,
	"errmsg" : "please create an index over the sharding key before sharding."
}

今度はsharding keyに対して、sharding実施前にインデックスを作る必要があるとのこと。
chunkingプロセスの高速化のために必要であるとのことで、無ければ自動で作成しますと
マニュアルには記載されているが、どうも手動で作成する必要がありそう。

> use logdb
switched to db logdb
> db.logs.ensureIndex( { month : 1 , uid : 1 } );  

> use admin                                      
switched to db admin
> db.runCommand( { shardcollection : "logdb.logs" , key : { month : 1 , uid : 1 } } );
{ "collectionsharded" : "logdb.logs", "ok" : 1 }

> db.printShardingStatus();
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
      { "_id" : "shard0000", "host" : "localhost:10001" }
      { "_id" : "shard0001", "host" : "localhost:10002" }
      { "_id" : "shard0002", "host" : "localhost:10003" }
  databases:
	{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
	{ "_id" : "logdb", "partitioned" : true, "primary" : "shard0000" }
		logdb.logs chunks:
				shard0001	3
				shard0002	2
				shard0000	3
			{ "month" : { $minKey : 1 }, "uid" : { $minKey : 1 } } -->> { "month" : "2010/02", "uid" : "c2UGMAoXRXXXDgJ7" } on : shard0001 { "t" : 2000, "i" : 0 }
			{ "month" : "2010/02", "uid" : "c2UGMAoXRXXXDgJ7" } -->> { "month" : "2010/03", "uid" : "a10StyXhoXXX2nb2" } on : shard0002 { "t" : 3000, "i" : 0 }
			{ "month" : "2010/03", "uid" : "a10StyXhoXXX2nb2" } -->> { "month" : "2010/03", "uid" : "a34m3gIYWXXXH5xP" } on : shard0001 { "t" : 4000, "i" : 0 }
			{ "month" : "2010/03", "uid" : "a34m3gIYWXXXH5xP" } -->> { "month" : "2010/03", "uid" : "b2EwnvdeFXXXoKO8" } on : shard0002 { "t" : 5000, "i" : 0 }
			{ "month" : "2010/03", "uid" : "b2EwnvdeFXXXoKO8" } -->> { "month" : "2010/03", "uid" : "c23k2FVIiXXX86m3" } on : shard0001 { "t" : 6000, "i" : 0 }
			{ "month" : "2010/03", "uid" : "c23k2FVIiXXX86m3" } -->> { "month" : "2010/03", "uid" : "d2GIFKklCXXXpoKq" } on : shard0000 { "t" : 6000, "i" : 1 }
			{ "month" : "2010/03", "uid" : "d2GIFKklCXXXpoKq" } -->> { "month" : "2010/03", "uid" : "f3tMomXcKXXXwQMn" } on : shard0000 { "t" : 1000, "i" : 7 }
			{ "month" : "2010/03", "uid" : "f3tMomXcKXXXwQMn" } -->> { "month" : { $maxKey : 1 }, "uid" : { $maxKey : 1 } } on : shard0000 { "t" : 1000, "i" : 8 }

printShardingStatusを見てみると、chunkが8個に分割され、shard000に3つ、shard001に3つ、shard002に2つ配置されている。
※ 使ったデータは小さめのログなので、デフォルトのchunk size(200MB)だと分割されないので、chunksizeを1Mに設定している。
※ 上記のuidは携帯のuidなので一部マスクを掛けています。

本当にshardingされているか確認

まずはmongosに接続し、全体logsコレクションの件数を確認する。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10000/logdb
MongoDB shell version: 1.8.0
connecting to: localhost:10000/logdb
> db.logs.count();
28130

次に各shardでlogsコレクションの件数を確認する。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10001/logdb
> db.logs.count();
10296

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10002/logdb
> db.logs.count();
10696

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10003/logdb
> db.logs.count();
7138

各shardのlogsコレクションの件数を確認してみると、確かに分割され、件数もmongos経由した場合と同じ。
ドキュメントのサイズで分けているので、件数が同じにはならない。

データ追加

この状態でさらに7万件程データの追加を行って、chunkの偏りが発生しないかを確認してみる。

> db.printShardingStatus();
--- Sharding Status --- 
  sharding version: { "_id" : 1, "version" : 3 }
  shards:
      { "_id" : "shard0000", "host" : "localhost:10001" }
      { "_id" : "shard0001", "host" : "localhost:10002" }
      { "_id" : "shard0002", "host" : "localhost:10003" }
  databases:
	{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
	{ "_id" : "logdb", "partitioned" : true, "primary" : "shard0000" }
		logdb.logs chunks:
				shard0001	6
				shard0002	6
				shard0000	5
			{ "month" : { $minKey : 1 }, "uid" : { $minKey : 1 } } -->> { "month" : "2010/02", "uid" : "c2UGMAoXRXXXDgJ7" } on : shard0001 { "t" : 2000, "i" : 0 }
			{ "month" : "2010/02", "uid" : "c2UGMAoXRXXXDgJ7" } -->> { "month" : "2010/03", "uid" : "a10StyXhoXXX2nb2" } on : shard0002 { "t" : 3000, "i" : 0 }
			{ "month" : "2010/03", "uid" : "a10StyXhoXXX2nb2" } -->> { "month" : "2010/03", "uid" : "a2QsXVOrKXXXOsQf" } on : shard0001 { "t" : 6000, "i" : 2 }
			{ "month" : "2010/03", "uid" : "a2QsXVOrKXXXOsQf" } -->> { "month" : "2010/03", "uid" : "a34m3gIYWXXXH5xP" } on : shard0001 { "t" : 6000, "i" : 3 }
			{ "month" : "2010/03", "uid" : "a34m3gIYWXXXH5xP" } -->> { "month" : "2010/03", "uid" : "a3aDeqciNXXXwVSs" } on : shard0002 { "t" : 7000, "i" : 8 }
			{ "month" : "2010/03", "uid" : "a3aDeqciNXXXwVSs" } -->> { "month" : "2010/03", "uid" : "b2EwnvdeFXXXoKO8" } on : shard0002 { "t" : 7000, "i" : 9 }
			{ "month" : "2010/03", "uid" : "b2EwnvdeFXXXoKO8" } -->> { "month" : "2010/03", "uid" : "b2zjWlq3xXXXufa4" } on : shard0001 { "t" : 6000, "i" : 6 }
			{ "month" : "2010/03", "uid" : "b2zjWlq3xXXXufa4" } -->> { "month" : "2010/03", "uid" : "b3vJ4e1bCXXXVHQS" } on : shard0001 { "t" : 7000, "i" : 10 }
			{ "month" : "2010/03", "uid" : "b3vJ4e1bCXXXVHQS" } -->> { "month" : "2010/03", "uid" : "c23k2FVIiXXX86m3" } on : shard0001 { "t" : 7000, "i" : 11 }
			{ "month" : "2010/03", "uid" : "c23k2FVIiXXX86m3" } -->> { "month" : "2010/03", "uid" : "c2egPLOFzXXXvHZd" } on : shard0002 { "t" : 7000, "i" : 2 }
			{ "month" : "2010/03", "uid" : "c2egPLOFzXXXvHZd" } -->> { "month" : "2010/03", "uid" : "c3N2vMU8ZXXXsCmT" } on : shard0002 { "t" : 7000, "i" : 4 }
			{ "month" : "2010/03", "uid" : "c3N2vMU8ZXXXsCmT" } -->> { "month" : "2010/03", "uid" : "d2GIFKklCXXXpoKq" } on : shard0002 { "t" : 7000, "i" : 5 }
			{ "month" : "2010/03", "uid" : "d2GIFKklCXXXpoKq" } -->> { "month" : "2010/03", "uid" : "f10StyXdrXXX4mb2" } on : shard0000 { "t" : 7000, "i" : 1 }
			{ "month" : "2010/03", "uid" : "f10StyXdrXXX4mb2" } -->> { "month" : "2010/03", "uid" : "f3tMomXcKXXXwQMn" } on : shard0000 { "t" : 6000, "i" : 5 }
			{ "month" : "2010/03", "uid" : "f3tMomXcKXXXwQMn" } -->> { "month" : "2010/03", "uid" : "g2XA4wGX5XXXFjqn" } on : shard0000 { "t" : 7000, "i" : 6 }
			{ "month" : "2010/03", "uid" : "g2XA4wGX5XXXFjqn" } -->> { "month" : "2010/03", "uid" : "i3zqETmH1XXXQeIX" } on : shard0000 { "t" : 7000, "i" : 7 }
			{ "month" : "2010/03", "uid" : "i3zqETmH1XXXQeIX" } -->> { "month" : { $maxKey : 1 }, "uid" : { $maxKey : 1 } } on : shard0000 { "t" : 6000, "i" : 9 }

chunk数を見ると17個に分割され、shard0000に5つ、shard0001に6つ、shard0002に6つ配置されているのが分かり、shard間の差はほぼない。

一応、各shardの件数も確認してみる。

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10000/logdb
> db.logs.count();
96974

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10001/logdb
> db.logs.count();
28353

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10002/logdb
> db.logs.count();
33500

[matsuou1@testsvr mongodb]$ ./bin/mongo localhost:10003/logdb
> db.logs.count();
35121

件数についてもだいたい、均等に振られていることがわかる。

printShardingStatusで表示されるchunkの情報の最後の"t"と"i"は何の情報を示しているんだろうか?
ほんの少しだけ調べたけど分からなかったので、今度調べておくことにする。(分かる人がいたら教えてください><)

{ "month" : "2010/03", "uid" : "a34m3gIYWXXXH5xP" } -->> { "month" : "2010/03", "uid" : "a3aDeqciNXXXwVSs" } on : shard0002 { "t" : 7000, "i" : 8 }

まとめ

今回はmongodbのshardingの機能について、少しだけ使ってみました。
上記の通り、割と簡単に試せるので、やったことが無い人はやってみるとより理解が深まるかと思います。

次回以降は、shard keyを効率が悪いものに代えるとchunkの偏りがどのように発生するか、その場合に発生するMigrationの動作、failover時の動作、configサーバが落ちた場合の動作などを見ていけたらと考えています。
その前に、投入したアクセスログを集計してみるかもしれません。

第2回 MongoDB JP 勉強会に行ってきた

今週はMongoDBの勉強会に参加してきました。

Sharding詳解 [twitter:@doryokujin]

MongoDBでShardingを使用する際のポイントや注意点についての発表です。
こういう話はこれからShardingを使おうとする人にとっては大変有益。
前日にさくらVPSで試して見たので、割と理解できたような気がする。

  • Shard Keyの設定は非常に重要、慎重に
    • Shardの偏りを極力減らすことは重要
    • Shard Keyによって偏り具合が大きく異なる
  • 注意点
    • Shard Keyは変更できない
    • Shard Keyを持たないドキュメントは保存できない(nullは可能)
  • Shard Key選択時の悪い例
    • カーディナリティが低い値(性別等)
      • Chunkの分割ができない
      • カーディナリティがNならば、N個のChunkしかできない → N台以上のShardサーバは無意味
    • 常に増加するデータ(Time、ObjectID、シーケンス等)
      • Chunkの分割が常に同じ方向となるため、Shard間で不均一となる
    • ランダムな値(ハッシュ値等)
      • 非常に大きな範囲を持ったChunkが存在する可能性があり、Migration時にRAMを圧迫する
      • indexが非効率
  • Shard Key選択時の良い例
    • 緩く増加するキー + 検索でよく使われるキー
      • アクセス解析{month:1 , user:1}
      • monthは一定期間固定で、userは均等に分布される
      • 時間がたつとmonthのキーの値が増え、古い値のChunkは分割されずMigrationが起こりにくい
  • Sharding環境でShard Keyを使用しないクエリは正しく実行されるとは限らない
    • Chunk Migration中のcount
      • Migration中は移動元、移動先に同じChunkが存在するため、重複してカウントする可能性がある
    • 同時書き込み発生中のunique index
      • 書き込みロックを行わないため、unique indexとして設定したキーが異なるShard間で存在する可能性がある(_idを含む)
    • 同時書き込み発生中のsingle-update()
      • Non Shard Keyに対し、同時にsingle-updateを実行するとエラーとなるため、multi-updateを実行
    • tmp_collectionが削除されないmapreduce(v1.8で改善。手動で削除)
      • tmp_collectionが自動で削除されないため、不要なcollectionが増えていく
Type safe mongodb with Scala [twitter:@bibrost]

勝間Web 中の人の発表。

↓bibrostさんのブログで+αの話が。
第2回 MongoDB JP 勉強会+α アーキテクチャ選択の話

アクセスログをできるだけいろいろ見る時のmapreduce+ニフティクラウドでの構築とパフォーマンスを初心者からわかりやすく [twitter:@muddydixon]

資料は こちら から。

↓muddydixonさんのブログで解説があります。
第2回 MongoDB JP 勉強会 in Tokyoに参加してきました

  • Hadoop streamingとmongoimport相性が良い。
  • mongodbのmapreduceは、hadoopと異なりshuffleフェーズが無かったり、finalizeがあったりと多少異なる点がある。
  • 世界でも数少ないmongodbのmapreduceのサンプルが記載されているので、必見。
地理空間インデックスを利用したWebアプリケーション [twitter:@yamaneko1212]

一部で話題沸騰の農家ニートプログラマことyamaneko1212さんによる発表。

資料は こちら から。(PDFです)

mongodbを使った地理空間インデックスのお話。

Tachy with MongoDB [twitter:@joe_hrmn]

サイバーエージェントの実名携帯SNSサイト「Tachy」でmongoDBを使用した際の話。

↓joe_hrmnさんのブログです。
第2回 MongoDB JP 勉強会に参加してきました

  • ユーザ情報はMySQLで管理しているが、それ以外の情報は全てMongoDBで管理している。
  • 採用理由
    • picoで社内実績あり
    • アクセスが高速
    • 自動failover
    • データ分散が自動
  • 大量のinsertが遅いのでbulk insertにしたら早くなった
Toggeter [twitter:@ixixi]


所管&まとめ
  • 全体的にログ解析等のバックエンドに使用されている感が強いが、「Tachy」でフロントエンドでも実用事例も出てきている。
  • RDBMSみたいに誰でもなんとなく使えるものではないので、適用箇所、目的をはっきりする必要あり。
  • 難しいのはcollectionやshard keyの設計。Embedした方が良いのか、しない方がよいのか、きちんと考えてから決めるべし。

さくらVPSにRStudioをインストールしてみた

昨日のR勉強会@東京でちょっとだけ話に出てきたRStudioのサーバ版をさくらVPSにインストールしてみました。

RStudioとは?

R言語IDEです。デスクトップ版とサーバ版があります。
デスクトップ版は、Windowsだけでなく、MacUbuntuFedoraもサポートしています。
サーバ版はUbuntuCentOSはバイナリがあるので楽にインストールでき、サーバ版でもコードの補完が
出来るなど、ディスクトップ版と遜色ない感じで使えます。

Rのインストール

RStudioにはR自体は含まれていないので、まずはRのインストールから。
yumリポジトリにはRがないので、CRANからインストール。
CRANのRはバージョンが2.10なので、RPMforgeやEPELを使ってください。
WindowsLinuxは2.11.1以上、Macは2.12が必須環境となります。
私の場合は、EPELを使ってたっぽいです。。。
リポジトリの追加は、RPMforge EPEL Remiリポジトリインストール辺りをご参照頂ければと。
riywoさん、ご指摘ありがとうございます!!

$ sudo yum install R

RStudioインストール

RPMからは以下の感じでさっくりインストールできます。

$ wget https://s3.amazonaws.com/rstudio-server/rstudio-server-0.92.44-x86_64.rpm
$ sudo rpm -Uvh rstudio-server-0.92.44-x86_64.rpm

インストール後にサーバが起動します。

最新版はGitHubに公開されていますので、より人柱版を使いたい人はソースからインストールしてみると良いかと。
(若干、面倒くさそうなので私は見送りました・・・)

ポート設定

デフォルトのポートは、8787なので

http://<server-ip>:8787

でアクセスできますが、ポート閉じている場合はiptables等の設定をする必要があります。

使用ポートを代えたい場合は、設定ファイルでポートを指定する必要があります。

/etc/rstudio/rserver.conf
www-port=8787

設定後は、サーバを再起動します。

$ sudo rstudio-server stop
$ sudo rstudio-server start

または

$ sudo rstudio-server restart

画面イメージ

ログイン画面はこんな感じです。
認証はローカルのユーザ、パスワードを使用していようです。
設定ファイルでauth-required-user-groupを設定すると、使用するユーザを制限できる模様。


ログイン直後


線形単回帰分析してみたところ

まとめ&所感

  • インストールはディスクトップ版もサーバ版も簡単にできる
  • ちょっと使ってみた感じでは、割と使いやすそう。(Eclipseと比べると)
  • 開発が始まって4ヶ月程(2010/12/7にfirst commit)のプロジェクトなのに、完成度も高いので今後も期待。
  • サーバ版は会社の空き時間に、ちょっとRの勉強するのには最適?

第12回R勉強会@東京に行ってきた

今回初めてR勉強会@東京に参加してきました。


結構前(1年ぐらい?)から開催されている勉強会に途中から参加、しかもRは素人とドキドキでしたが、楽しく勉強できました。
なにげに大森駅に降りたのも初めてだったような気もします。

Rによる回帰分析入門 [twitter:@yokkuns]

Rによるデータサイエンス第Ⅱ部 第7,8章 回帰分析
データ解析の基本である回帰分析について、Rを使って学んでいきます。

  • 残差の正規Q-Qプロットで正規分布に準じているかどうかを確認できる
  • 重回帰分析で使うファクターを自動選択する関数step。モデルはAICで評価される。これは使えるかも。
  • AIC(Akaike's Information Criterion:赤池情報量規準)
    • 統計モデルを評価するための指標
    • 小さい方がよい 
    • step関数で自動選択
  • 欠損値の扱いをどうするべき?
    • ケースバイケースだが、使わなくて済むなら外してみる
    • 欠損していない値の平均値とかを当てる
    • それ以外のモデルで回帰分析して当てはめてみる

Rで学ぶデータサイエンス 5パターン認識 1章 判別能力の評価 [twitter:@sleipnir002]

  • K交差検証法の学習用と推定用の比率は7:3が一般的
  • 陽性率と偽陽性率をプロットしたのがROC曲線
  • ROC曲線を描くRのパッケージ"ROCR"はあんまりおすすめできない
    • 2値判別しか使えないし、Rの可視化は十分に強力

Rで学ぶ回帰分析と単位根検定 [twitter:@teramonagi]

単回帰分析を復習した後、単純に回帰分析を適用してはいけない『やってはいけないケース』を紹介。
そしてそれがなぜ起こるのかを実例を通して紹介した後、この問題を避けるための方法の1つという観点から単位根検定の紹介をします

  • ランダムウォーク(=単位根過程)同士を回帰すると、あたかも関係がありそうに見える「見せかけの回帰」が発生する。
  • 変数が単位根過程であるかどうかを検定する単位根検定をしよう
  • Phillips-Perron検定や拡張Dickey-Fuller検定がよく使われるやり方
    • 1に近いほど単位根過程である(0に近ければ単位根過程ではない)と判断できる

RをSQLで操る [twitter:@aad34210]

データベース(RDB)を利用している人でもRがとっつきやすくなる!
dataframeをSQLで操作ができる「sqldf」パッケージを紹介。

  • RDBMS使ったことがある人はRの集計はめんどくさいので、sqldf使えば幸せになるかも。
  • 直接SQLを記述するだけで、あとはやってくれる
  • パフォーマンスは裏でDBを使用し、インポートの時間などが必要なため、オーバヘッドがある。
    • 生産性と性能を求められる場合とで、使い分けるた方がよいかも

Rあんな時こんな時 〜いつか役立つ(かもしれない)Tips〜 [twitter:@sfchaos]

Rを使っていてこんなことをやりたいとき,こんなことに遭遇したとき,あなたはどうしますか?
クイズ形式で進行しながら,Rを使用する際のちょっとしたコツについて議論させていただければと思います

  • NA/NaN/Infの判定(欠損値の発見)は、is.finite関数を使用する
  • 「Rの基礎とプログラミ ング技法」は良い本なので、読みましょう

世界のR(仮) [twitter:@dichika]

  • 出来る男は早口

Togetter [twitter:@bob3bob3]

まとめ&所感

  • 大学生からその道の専門家、私みたいなWEBエンジニアなど多岐に渡る人が参加してて楽しい。
  • 回帰分析はきっちり復習して、理解を深めておきたい。
  • Rの勉強とともに統計学の勉強も出来て一石二鳥です。
    • が、まだまだ不勉強のため初めて聞く話もあり、復習しないとなかなか自分のものにならないので復習は必須。

帰りに非常用エレベーターに乗ってしまい若干迷子になりかけたのは秘密です。。。

第1回 にこにこテキストマイニング勉強会に参加してきました。

テキストマイニングの勉強会に参加してきました。
非常に勉強になったので、復習の意味も込めて超久しぶりにブログを書きます。
この分野には前から興味はありましたが、なかなか勉強するきっかけがなかったので放置気味でしたが、折角の機会なので参加することに。


会場は勉強会の会場には定評があるOracleさんにて。
何気に初Oracleだったりしましたが、非常にすばらしい会場でしかも広い。
入館に若干時間がかかることが難点ですが、そこは仕方ないことでしょう。

目的&概要

目的

テキストマイニングについての学習のスタートアップ
テキストマイニング技術に関して気軽に参加・議論することができる場の提供
概要

テキストマイニングとは、例えば製品の評判をweb上のテキストから抽出したり、
大量のアンケートテキストを分析するために用いられる技術であり、特にマーケティングの場で多くの利用例があります。

この勉強会ではそうしたテキストマイニングを題材とし、用いられている要素技術とそれに関わる課題の議論、
またテキストマイニングを実務に活かす方法について考えていきます。

第1回 にこにこテキストマイニング勉強会

テキストマイニングの歩き方 [twitter:@AntiBayesian]

発表資料はここ(PDF)で公開されています。

テキストマイニングの利点
  • ブログや掲示板など定型化されていないものを対象とするため、分析できるデータの範囲が大幅に拡大
  • アンケートとは異なり想定外の回答が寄せられる可能性があり、収集者のフィルタがかかっていない生の声が得られる
  • 性別・年代・地域などパーソナリティを8割ぐらい取得できる
    • アンケートで個人情報を質問項目に入れると、回収率が極端に下がってしまう
利用分野
言語処理の手法
  1. 形態素解析:精度95% 絶対使う
  2. 構文解析:精度80% よく使う
    • 文法規則によって文の構造を句や文節を単位として係り受けなどを見る
    • 形態素解析より難易度は高い
  3. 意味解析:精度60% たまに使う
    • 評判分析等で使われる
    • 単語情報単体では自然言語を評価できない
  4. 文脈解析

テキストマイニングで使用するのは、1、2がほとんど。

テキストの統計処理
  • 統計処理を行うため、テキストを数学に落とし込む
  • 単語:トークンとタイプ
    • トークン:単語の数(同一単語も数える)
    • タイプ:単語の種類(同一単語は数えない)
  • bag-of-words:文章の単語ベクトル表現
  • Nグラム
  • コーパス
    • 言語分析用例データ
  • 素性(そせい)
    • 文法的な情報を表す情報量(頻出単語)
テキストマイニングの注意点
  • テキストを数値化する際、必ず情報は欠損する
  • テキストデータはスパース(疎)なデータである。疎なデータは通常の統計解析は通用しないケースが多い
    1. スパースデータ用の解析手法を用いる
    2. データ量を増やす
    3. 分析範囲を蜜な箇所だけに制限する

どの手法もそれぞれに問題があるが、まずは2を検討するべき(さらにスパースなデータが増加する可能性があるが。。)

実践するために
  • 95%までは美しく実装できる@shuyo
    • 400字詰め原稿用紙に20文字の誤字→使い物にならない
  • 辞書を作り、クリーニングする
    • 同義語辞書:私=私、俺、自分、僕、わたし
    • ストップワード:(それ、あれ、私、君)
    • 20人月の勝利
      • 精度が単調に増加する。モデルを使用してやる場合は危険がある
今後の学習

ゆるふわテキストマイニングをしてみよう [twitter:@toilet_lunch]

発表資料はここ(PDF)で公開されています。

テキストマイニングとは

何をマイニングしたいかによって、扱うデータや手法が異なるため、一般的な定義が決められない。

評判分析のツール

 評判分析ツール(一晩で作成したらしい)

  • 評価表現
    • 物事に対する評価を表しているような言葉=評価表現
    • 大きくポジティブとネガティブを分けられ、数値化が可能
    • 単語感情極性対応表
    • 形容詞が活用され、マッチングできない。→形態素解析(文章を単語に分解し、品詞を推定する)を使う
精度をあげるには
  • ジャンルを限定する
    • テキストマイニングは通常、知りたい情報のジャンルをあらかじめ想定して行われる。
    • 同じ評価表現がジャンルによって、反転を起こす場合がある
  • 対象テキストのクリーニング
    • 整理された形式で記述されていない
    • つべこべいわず地道にクリーニングする
      • 統計的な信頼性が損なわれるため、対象データが小さくなりすぎないように注意。
  • 言語の困難さ
    1. 否定表現
    2. 未知語
    3. 助詞の省略
    4. 複合的表現
    5. 表記ゆれ
    6. 複数の評価の混在
    7. 複雑すぎる構造の文

考えだすときりがなく、人間でも解読できない文などがあるため、完全な対応は不可能。
要求される精度とリソースの兼ね合い

コピー&ペーストのみで始めるテキストマイニング超入門 [twitter:@langstat]

テキストマイニングの一般的な流れ
  • データ構築
    • テキスト収集、電子化
  • テキスト処理
    • 語彙表の作成、用例の抽出
  • 統計処理
    • 検定、多変量解析
  • 質的分析
    • 結果の解釈、実質科学的な考察
テキストを入手できるサイト
形態素解析ができるサイト
統計解析できるサイト

初めてのnltk [twitter:@gepuro]

なんと大学2年生。
この分野に置ける先達がいる中で、初回発表する勇気がすばらしい。

NLTK(Natural Language Toolkit)
  • 純化
    • 退屈で面倒な作業を少なくする
    • ユーザへNLPの実用知識を与える
    • 直感的なフレームワーク
  • 一貫性
    • 一貫したインターフェースとデータ構造
    • 推測しやすいメソッド名
  • 拡張性
    • 新しいモジュールが容易に対応可能である後続
  • モジュール性
    • ツールキットの他の部分の理解を必要なしに、独立して利用できるコンポーネント

アンケート自由回答のテキストマイニング事例 [twitter:@bob3bob3]

発表資料はここ(PDF)で公開されています。

「楽しい食事」ってどんな食事?
  • 単語出現頻度分析
    • これを出すまでが大変
    • 表記ゆれの一部はツールで対応可能だが、単語の読み替えは辞書で対応する必要がある
    • 名詞と形容詞と動詞を抽出対象
  • 話題分析
  • 特徴語分析
    • 全体での単語の出現数と属性毎の単語の出現数を元にした「補完類似度」を指標して、属性毎に特徴的に出現する単語を抽出
  • コレスポンデンス分析
    • 縦横軸は大きな意味を持たず、関連性をみるためのもの
    • 今回のケースのようにうまく関連性を読み取れるものばかりではない
  • 仮説の抽出
    • 特徴語分析、コレスポンデンス分析をもとに仮説を抽出

Togetter [twitter:@hamadakoichi]

所感

前提知識はほぼなし(全文検索の絡みで、形態素解析、Nグラム辺りは多少知っている程度)の状態で参加させて頂いたが、
分かりやすい内容だった。
テキストマイニングとはどういうものか、どのような流れで行われるものか、具体的な例みたいな流れで丁寧な内容でした。
(発表された方々、お疲れさまでした。)
まずは、やってみる。
MeCabやRと戯れながら、統計学の勉強をすることにしよう。