Implementação de classificador Naïve Bayes em MongoDB
O presente artigo é derivado do que escrevi para o evento Equinócio de Outono de 2012, promovido por São Paulo Perl Mongers.
Porém, ao invés de servir como uma espécie de complemento, é uma "volta às origens". O algoritmo original, desenvolvi em JavaScript puro, depois traduzi para Perl, refinei, detalhei e escrevi sobre. A intenção agora é ser mais show me the code possível, com um exemplo simples, enxuto e prático. Portanto, a teoria e os detalhes continuam lá no artigo original. Aqui, é mão na massa!
Ensinando o classificador
O classificador funciona por palavras-chave, e o MongoDB, nesse estágio, atua como um mero key/value storage. Salve o código de inicialização a seguir como init.js
e carregue com mongo bayes init.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | db.bayes.update({_id: 'pelos'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'bigodes'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'rabo'}, {$inc: {total: 1, 'categ.gato': 1}}, 1); db.bayes.update({_id: 'pelos'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'rabo'}, {$inc: {total: 1, 'categ.cachorro': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'bico'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'asa'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'asa'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'penas'}, {$inc: {total: 1, 'categ.galo': 1}}, 1); db.bayes.update({_id: 'bico'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'pata'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'rabo'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); db.bayes.update({_id: 'pelos'}, {$inc: {total: 1, 'categ.ornitorrinco': 1}}, 1); |
Agora, entrando em MongoDB Interactive Shell com mongo bayes
, podemos ver a estrutura resultante com db.mongo.find()
:
{ "_id" : "bigodes", "categ" : { "gato" : 1 }, "total" : 1 } { "_id" : "bico", "categ" : { "galo" : 1, "ornitorrinco" : 1 }, "total" : 2 } { "_id" : "asa", "categ" : { "galo" : 2 }, "total" : 2 } { "_id" : "penas", "categ" : { "galo" : 1 }, "total" : 1 } { "_id" : "pata", "categ" : { "cachorro" : 4, "galo" : 2, "gato" : 4, "ornitorrinco" : 4 }, "total" : 14 } { "_id" : "rabo", "categ" : { "cachorro" : 1, "gato" : 1, "ornitorrinco" : 1 }, "total" : 3 } { "_id" : "pelos", "categ" : { "cachorro" : 1, "gato" : 1, "ornitorrinco" : 1 }, "total" : 3 } |
Cada palavra-chave é associada aos contadores das categorias, além do contador geral de ocorrências. Agora, vamos processar essa estrutura de dados!
Classificador em map/reduce
O código a seguir é quase que um programa independente em JavaScript, salve-o como bayes-mongodb.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | var words = []; for (var word in doc) words.push(word); var categ = [ 'gato', 'cachorro', 'galo', 'ornitorrinco' ]; var guess = db.runCommand({ mapreduce: 'bayes', out: { inline: 1 }, query: { _id: { $in: words } }, scope: { categ: categ, doc: doc, }, map: function () { for (var i = 0; i < categ.length; i++) { var ctg = categ[i]; var prob = Math.log( typeof(this.categ[ctg]) != 'undefined' ? this.categ[ctg] : 1.18e-38 ); prob -= Math.log(this.total); emit(ctg, prob * doc[this._id]); } }, reduce: function (key, values) { var result = 0; values.forEach(function (value) { result += value; }); return result; } }); var res = { value: -Infinity }; for (var i in guess.results) if (guess.results[i].value > res.value) res = guess.results[i]; print(res._id); |
Para passar parâmetros via linha de comando, usamos a opção --eval
do MongoDB Shell, e o script imprime na tela a categoria "adivinhada":
$ mongo bayes --quiet --eval 'var doc = { bigodes: 1, rabo: 1 }' bayes-mongodb.js gato $ mongo bayes --quiet --eval 'var doc = { bico: 1, rabo: 1, patas: 4 }' bayes-mongodb.js ornitorrinco $ mongo bayes --quiet --eval 'var doc = { bico: 1, asa: 2 }' bayes-mongodb.js galo
Conclusão
O básico do básico esta aí 🙂
É evidente que os resultados e a eficiência do classificador bayesiano variam de caso para caso, e até de implementação para implementação. Se a primeira tentativa não for um sucesso, se quiser entender para que serve o "número mágico" 1.18e-38
, ou se quiser automatizar o "treinamento" através de um pequeno script em Perl, consulte o artigo original!