スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

MongoDBのシェルでトリガーを実装する

MongoDBにはトリガーがありません。
そこで、OpLogを使って、トリガーの代替を実装します。

var option = DBQuery.Option.awaitData | DBQuery.Option.tailable;
var cursor = connect( 'local' ).oplog.rs.find().addOption( option );
cursor.skip( cursor.count() );

var stop = false;
while ( !stop ) {
var now = new Date();
while ( cursor.hasNext() ) {
var op = cursor.next();
if ( op.ns === 'test.stop' ) {
stop = true;
break;
}
do_something( op );
}
// Safety Trap for busy loop.
if ( (new Date()) - now < 100 ) {
break;
}
}



  • DBQuery.Option.awaitData は、カーソルが空になった後、5秒待つようです。
  • DBQuery.Option.noTimeout で無限に待たせたいのですが、このオプションの効果が分かりませんでした。
  • [停止方法] testデータベースのstopコレクションにinsertかupdateすると、外側の無限ループを抜けます。


[応用]
MongoDBでは「非正規化」のサポートがありませんね。
このトリガー機能を使って、「非正規化」を実装してみました。
GitHub/MongoTrigger
スポンサーサイト

MongoDBのMapReduceでTF-IDFによる日本語の全文検索を実装する(Part 2)

MongoDBのMapReduceでTF-IDFによる日本語の全文検索を実装する(Part 2)



https://github.com/exabugs/similarity_search

前回作ったTF-IDFとコサイン類似度を使って、検索部分のサンプルを実装する。

  • 検索処理そのものも、MapReduceで行う。
  • 高速化のために、先にキーワードをOR条件として通常検索して、その結果のコサイン類似度を求めるように改良した。
  • 検索結果とともに、その結果に類似した文書は何か、も示す。


// 検索機能 サンプル
var tweets_search = function (input) {
var keyword = [];
for (var i = 0; i < input.length; i++)
keyword.push({k:input[i], w:1});
var condition = util.to_hash(keyword);
printjson(condition);
// 検索の実行
db[master_name].mapReduce(
function () {
var s = util.innerproduct(this.tf.v, condition) / this.tf.l
if (0 < s) emit(this._id, s);
},
function(key,values) {
return values[0];
},
{query: {"tf.v.k": {$in: input}}, scope: {util: util, condition: condition}, out: 'tweets_search_result'}
);
// 検索結果の表示
db.tweets_search_result.find().sort({value:-1}).limit(5).forEach(function (r) {
var result = db[master_name].findOne({_id:r._id},{content:1});
if (result) {
// 検索結果に類似したtweetを見つける
similar = [];
db.tweets_similarity.find({a:result._id},{b:1, score:1}).sort({score:-1}).limit(3).forEach(function (sr) {
var similar_result = db.tweets.findOne({_id:sr.b},{content:1});
if (similar_result)
similar.push({content: similar_result.content, score: sr.score});
});
// 検索結果の表示
printjson({content: result.content, score: r.value, similar: similar});
}
});
}


以下のように実行する
tweets_search(["説明"]);
tweets_search(["会社","正月"]);

MongoDBのMapReduceでTF-IDFによる日本語の全文検索を実装する(Part 1)

MongoDBのMapReduceでTF-IDFによる日本語の全文検索を実装する(Part 1)



https://github.com/exabugs/similarity_search

前提


  • MongoDB は v2.4.8 以上のこと。
    (mac の brew でインストールされる 2.4.6 だと MapReduce が不安定です。)
  • 形態素解析は行いません。事前にMeCab等で形態素解析して単語(名詞)を抜き出して下さい。


仕様


  • 以下のようなtweetsコレクションを対象とします。
    (対象となるコレクション名は 'master_name' という変数に入れてください。)
    > db.tweets.find();
    { "_id" : ObjectId("52afd7b550433450593e0100"),
    "content" : "到着後はお早めに召し上がり下さい",
    "tf" : { "v" : [
    { "k" : "到着", "v" : 0.333 },
    { "k" : "後", "v" : 0.333 },
    { "k" : "早め", "v" : 0.333 } ] }
    }

  • フィールド"tf.v"には名詞とその出現確率を入れておく。
    (出現確立 : 対象名詞出現回数 / 名詞総数)
  • バッチ・プログラムで以下を実施します。
    (1〜3全てMapReduceで処理しています。)
    1. IDF (Inverse Document Frequency)の辞書作成
    2. 各文書のTF-IDFを計算する
    3. 文書相互のコサイン類似度を計算する
      (ドキュメント総数がN なら、N(N-1)÷2 回のコサイン類似度を計算する。)

  • db[master_name+'_similarity']コレクションに結果が出力される。
    • フィールド a : 元となる文書のObjectId
    • フィールド b : 類似文書のObjectId
    • フィールド score : コサイン類似度 (0〜1)

  • MongoDBのMapReduceは、実行開始時点で値が確定しているものしか処理できない。
    Map中にコレクションを検索したりはできないので、実行開始時点で処理に必要なデータは全てscopeパラメータとして渡す。


バッチ・プログラム


Mongoシェルで実行します。

var master_name = 'tweets';

// ユーティリティ
var util = {
// 乗算
product : function (a, v) {
for (var i = 0; i < a.length; i++) {
if (v[a[i].k])
a[i].w = a[i].v * v[a[i].k];
else
a[i].w = a[i].v;
}
return a;
},
// ノルム
norm : function (a) {
var sum = 0;
for (var i = 0; i < a.length; i++) {
if (a[i].w) {
sum += a[i].w * a[i].w;
}
}
return Math.sqrt(sum);
},
// 内積
innerproduct : function (a, v) {
var sum = 0;
for (var i = 0; i < a.length; i++) {
var info = a[i];
if (v[info.k]) {
sum += info.w * v[info.k];
}
}
return sum;
},
// 辞書化
to_hash : function (a) {
var ret = {};
for (var i = 0; i < a.length; i++) {
if (a[i].w) {
ret[a[i].k] = a[i].w;
}
}
return ret;
}
}

// IDF Dictionary
db[master_name].mapReduce(
function () {
for (var i = 0; i < this.tf.v.length; i++)
emit(this.tf.v[i].k, 1);
},
function(key,values) {
return Array.sum(values);
},
{finalize: function(key, value) {return Math.log(N/value);}, scope: {N: db[master_name].count()}, out: master_name+'_idf'}
);

// TF-IDF
// (idf辞書をハッシュで渡す部分が不安。大きさやコリジョンによるパフォーマンス劣化等は大丈夫か。)
var dic = {};
db[master_name+'_idf'].find().sort({value:-1}).limit(100000).forEach( function (idf) {dic[idf._id] = idf.value} );
db[master_name].mapReduce(
function () {
var v = util.product(this.tf.v, dic);
emit(this._id, {v: v, l: util.norm(v)});
},
function(key,values) {
return values[0];
},
{scope: {util: util, dic: dic}, out: master_name+'_tfidf'}
);
db[master_name+'_tfidf'].find().forEach(function (tgt) {
db[master_name].update({_id: tgt._id},{$set: {tf: tgt.value}});
});
db[master_name+'_tfidf'].drop();

// コサイン類似度 (Cosine Similarity)
db[master_name+'_similarity'].drop();
var cursor = db[master_name].find();
while (cursor.hasNext()) {
var src = cursor.next();
src.tf.condition = util.to_hash(src.tf.v);
db[master_name].mapReduce(
function () {
var s = util.innerproduct(this.tf.v, src.tf.condition);
if (0 < s) emit(this._id, s / this.tf.l / src.tf.l);
},
function(key,values) {
return values[0];
},
{scope: {util: util, src: src}, query: {_id:{$gt:src._id}}, out: master_name+'_tmp'}
);
db[master_name+'_tmp'].find().forEach(
function (dst) {
db[master_name+'_similarity'].insert({a:dst._id, b:src._id, score:dst.value});
db[master_name+'_similarity'].insert({a:src._id, b:dst._id, score:dst.value});
}
);
}
db[master_name+'_tmp'].drop();


// 確認
db[master_name].find({},{_id:1, content:1, tf:1});
db[master_name+'_idf'].find();
db[master_name+'_similarity'].find();

MongoDBでMapReduceを使って、日本語のTF-IDFとコサイン類似度を求める (メモ)



以下のような tweets コレクションを用意する。
ここで、"tf" は Term Frequency。(名詞, 出現回数/名詞の全体数) になるように。

> db.tweets.find();
{
"_id" : ObjectId("52ad618f50433450590d0000"),
"content" : "到着後はお早めにお召し上がり下さい。",
"tf" : { "到着" : 0.33, "後" : 0.33, "早め" : 0.33 }
}


後は以下のスクリプトをぶっこむ。

// IDF Dictionary
var m = function () {
for (key in this.tf) {
emit(key, 1);
};
};
var r = function(key,values) {
return Array.sum(values);
};
var f = function(key, value) {
return Math.log(N/value);
}
db.tweets.mapReduce(m, r, {finalize: f, scope: {N: db.tweets.count()}, out: 'tweets_idf'});
db.tweet_idf.find();


// TF-IDF
db.tweets.find({},{tf:1}).forEach(function (tweet) {
var tfidf = {};
var V = 0;
for(var key in tweet.tf) {
var word = db.tweets_idf.findOne({_id : key});
var idf = 1;
if (word) {idf = word.value}
var v = tweet.tf[key] * idf;
tfidf[key] = v;
V += v * v;
}
// tfidf.v : term vector
// tfidf.l : term vector length
db.tweets.update({_id: tweet._id}, {$set: {tfidf: {v: tfidf, l: Math.sqrt(V)}}});
});


// Cosine Similarity
var m = function () {
value = 0;
for (var key in this.tfidf.v) {
v = tweet.tfidf.v[key];
if (v) {
value += this.tfidf.v[key] * v;
}
}
emit(this._id, value / this.tfidf.l / tweet.tfidf.l);
};
var r = function(key,values) {
return values[0];
};
db.tweets_similarity.drop();
var cursor = db.tweets.find({},{tfidf:1}).sort({"_id":1});
while (cursor.hasNext()) {
var tweet = cursor.next();
db.tweets.mapReduce(m, r, {scope: {tweet: tweet}, query: {_id:{$gt:tweet._id}}, out: 'tweets_tmp'});
db.tweets_tmp.find().forEach( function (dst) {
db.tweets_similarity.insert({a:dst._id, b:tweet._id, score:dst.value});
});
}


確認

db.tweets_similarity.find();


説明
・IDF(Inverse Document Frequency)の辞書を作る部分は、MapReduceのHelloWorld的な解法。
 名詞単語 -> 自然対数 ( ドキュメント総数 / その名詞が含まれるドキュメント数 )
・TF-IDFを求める部分。MapReduceでないですね。そのうち直す。
・コサイン類似度を求める部分。
 全ドキュメント同士の類似度を計算する。ドキュメント総数がN なら、N(N-1)÷2 回の類似度計算をする。

Map関数はいいが、Reduce関数の使い所が、まだよくわかってない。。。
数をカウントするくらいしか、思いつかないよ。

MongoDB Map/Reduce

MongoDB Map/Reduce



コレクション:access_logs
日付フィールド : date

pathとdateをキーに集計する場合、

var m = function () {

var getYMDH = function (d) {

d.setSeconds(0);
d.setMilliseconds(0);
d.setMinutes(0);

yy = d.getFullYear();
mm = d.getMonth() + 1;
dd = d.getDate();
hh = d.getHours();

mm = ('0' + mm).slice(-2);
dd = ('0' + dd).slice(-2);
hh = ('0' + hh).slice(-2);

return yy + '-' + mm + '-' + dd + ' ' + hh + ':00:00';
};

emit({date:getYMDH(this.date),path:this.path}, 1);
};


var r = function(key,values) {
return Array.sum(values);
};


db.access_logs.mapReduce(m,r,{out: {inline:1}});
						
						 
		
検索フォーム
RSSリンクの表示
リンク
exabugsをフォローしましょう
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。