sysd.org
30abr/122

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!

6jun/112

Perl e módulos na $HOME

Mas, por quê?!

Qual o sentido de alguém instalar os módulos dentro do seu $HOME? Normalmente, uma das duas:

  1. Você não tem permissão suficiente para instalar no próprio sistema (você está mexendo num servidor ou coisa do tipo);
  2. Você está desenvolvendo a bagaça, e quer manter a versão bleeding edge só para você.

Bom, se for o 2-o caso, existe o tal do perlbrew.

Mas eu gosto das instalações locais e as uso desde '97. E o local::lib foi uma mão na roda e tanto! Então, vamos lá:

Passo-a-passo:

Download do local::lib:

curl -LO http://search.cpan.org/CPAN/authors/id/A/AP/APEIRON/local-lib-1.008004.tar.gz
tar xzf local-lib-1.008004.tar.gz
cd local-lib-1.008004/

Instalação do local::lib:

perl Makefile.PL --bootstrap=$HOME/perl5
make
make test
make install

Ativando local::lib na inicialização da shell (Bash):

cat << PROFILE >> $HOME/.profile

export PERL_MM_USE_DEFAULT=1
eval \$(perl -I\$HOME/perl5/lib/perl5/ -Mlocal::lib)
PROFILE

Agora, é hora de logoff/logon para quem está acostumado com o Windows, ou então:

. $HOME/.profile

Prosseguindo com a configuração/teste do CPAN (essa configuração faz as dependências serem instaladas automaticamente):

cpan

cpan[1]> o conf prerequisites_policy follow
cpan[2]> o conf commit
cpan[3]> install App::cpanminus
cpan[4]> quit

Enumerando os módulos instalados:

perl -MExtUtils::Installed -E 'chdir $ENV{PERL_LOCAL_LIB_ROOT}; my $i = new ExtUtils::Installed; say for $i->modules'

cpanminus permite atualizar todos de uma vez, de uma maneira eficiente:

perl -MExtUtils::Installed -E 'chdir $ENV{PERL_LOCAL_LIB_ROOT}; my $i = new ExtUtils::Installed; say for $i->modules' | cpanm

Referências:

14dez/0914

Über-Geek Rugged Pen-Drives From Hell!!!

O crédito vai para o Dmitry Brant, que tornou os seus gadgets USB em indestrutíveis tratando-os com adamantium resina epóxi. Mas a ideia em si eu tive faz um tempão, quando detonei o meu adaptadorzinho de ler cartões SD... O que me faltou era um molde que desse nenhum o mínimo de trabalho. Aí um belo dia olhei para mais um pendrive que quebrei, e depois para um tubo de fio de solda... E, wow, o conector USB tem aproximadamente o mesmo diâmetro que o interior do tubo! Como na época estava fazendo a disciplina "Oficina de Materiais Plásticos", tinha uma quantidade razoável de resina de poliéster para brincar.

Matéria-prima

Matéria-prima: pendrive(s), resina de poliéster, tubos de solda (ATENÇÃO: descobri de uma maneira bastante desconfortável de que não é qualquer tubo que serve... ALGUNS são feitos de um plástico que reage com a resina de poliéster. Portanto, é minimamente sábio deixar um pingo de resina no tubo por um tempo antes de usá-lo como molde :P), copos plásticos de polipropileno (ATENÇÃO... OK, é a mesma coisa de antes: se não for polipropileno, reage com a resina :S), e fita crepe.

Inserindo no molde

Enrolei várias camadas de fita crepe no conector USB dos pendrives para protegê-los e também para que fiquem bem encaixados dentro do tubo-molde. Uma dica: ao enfiar o pendrive no tubo, deixe um espaço até o fundo: depois, essa "sobra" pode ser usada para fazer furo para passar cordinha de chaveiro ou coisa do tipo.

Molde e resina

Agora, o know-how do processo: os tubinhos de solda tem um furo embaixo. Assim, ao invés de encher de resina por cima e melecar tudo e encher de bolha, os moldes com pendrives são submergidos na resina, que entra por baixo 😉

Outro detalhe importante: o nível da resina. Primeiro, enchi o copo de poliéster, misturei com o catalizador (instruções no verso :P), daí coloquei os tubinhos e aí sim empurrei os pendrives para o fundo com uma varetinha, para que o conector USB fique para fora.

Submergindo

É só deixar nesse estado pelo tempo suficiente para a resina esfriar (a reação com o catalisador é exotérmica, mas o calor é pequeno e não chega a prejudicar os circuitos). A resina que usei tem uma coloração verde bizonha, o que até que combina com o verde das plaquinhas PCB. Mas também existe resina transparente (surpreendentemente, se chama "cristal"). Uma nota: se o tubo de solda for de material certo, ele não apresenta a menor dificuldade para sair da resina. E, para tirar os pendrives de dentro dos moldes, é só empurrá-los com um prego/fósforo/palito através daquele furico embaixo 😉

Outra nota: mesmo depois de aparentemente sólida, a resina leva alguns dias para ficar 100%. Portanto, sem pressa, se quiser que fique realmente bonito.

Pendrives prontos!

O resultado final! Me desculpe, Dmitry, mas ficou muito mais bonito do que o seu 😉

Pendrives prontos!

E as principais propriedades continuam as mesmas: alta resistência ao impacto (martelei um dos pendrives e ele sobreviveu) e à umidade (inclusive, é perfeitamente lavável: não tem como água entrar nos circuitos elétricos, no máximo, molha os contatos do conector USB que é só soprar para secar ;)).

LED aceso, versão "polida"

Muitos me perguntaram: "isso funciona"? Mas é claro! Por que não funcionaria?! A resina não é condutora e isola os circuitos de qualquer desgraça que lhes possa ocorrer... E o pendrive não gera calor o suficiente para "torrar" de dentro para fora. Ah, e os efeitos luminosos são ótimos: a "capa" de poliéster, sendo cilíndrica, atua como uma lente.

LED aceso, versão lixada

Esta é a versão "planificada": lixei até ficar um "tijolinho" translúcido. O efeito luminoso também é interessante, devido ao scattering. E as rachaduras internas se devem às porradas com o martelo 😛

Próximo passo: fazer uma tampinha à altura do pendrive, rááááá!!!!!!!