Google Refine + Perl
Google Refine é show. Para quem não conhece, acesse já o site oficial e assista pelo menos o primeiro vídeo. Serve para inúmeras tarefas envolvendo ETL.
Eu, no momento, uso muito para coisas simples, como carregar um CSV, eliminar outliers e salvar em JSON para carregar no MongoDB. Nada que um one-liner em Perl não pudesse fazer.
Infelizmente, a recíproca não é válida: one-liners em Perl são bem mais versáteis do que o Google Refine. Que tal integrar os dois?
- Google Refine pode ser facilmente integrado com um web-service qualquer.
- Perl transforma one-liners em webservices.
- PROFIT!!!
Como exemplo concreto, utilizarei dados georeferenciados. Digamos que preciso eliminar registros duplicatas, e uma das formas de detectar é pela proximidade física dos pontos no mapa. Google Refine não é um GIS, e não faz a menor ideia de como processar latitude/longitude. Entra o GeoDNA: um algoritmo que transforma coordenadas numéricas bidimensionais em uma string com propriedade interessante: quanto mais longo o prefixo compartilhado de dois GeoDNAs, mais perto estão as respectivas coordenadas no mapa (infelizmente, GeoDNAs com prefixos totalmente distintos também podem estar fisicamente próximos; localidades próximas ao meridiano de Greenwich são um exemplo clássico). Portanto, ordenando os registros por GeoDNA, as localidades próximas tenderão a ficar em linhas adjascentes. Para a integração, usaremos o recurso Add column by fetching URLs, clickando em header de qualquer coluna (a coluna não importa pois, de qualquer maneira, usaremos dados de duas):
No diálogo que aparecer, colamos o seguinte código (atenção para o nome das colunas com as respectivas coordenadas):
'http://127.0.0.1:3000/?lat='+ row.cells['latitude'].value +'&lon='+ row.cells['longitude'].value
Não precisamos do throttle delay, pois o nosso webservice é local. O diálogo deve ficar assim (nada de clickar em OK, ainda):
Agora, certifique-se de que você tem os módulos Mojolicious e Geo::DNA instalados, e rode no terminal:
perl -MGeo::DNA -Mojo -E 'a("/"=>sub{my$s=shift;$s->render(json=>{geocode=>Geo::DNA::encode_geo_dna($s->param("lat"),$s->param("lon"))})})->start' daemon
Se preferir, aqui está a versão "por extenso":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #!/usr/bin/env perl use Geo::DNA qw(encode_geo_dna); use Mojolicious::Lite; any '/' => sub { my $self = shift; $self->render(json => { geocode => encode_geo_dna( $self->param('lat'), $self->param('lon'), ), }); }; app->start; |
Uma vez iniciado o webservice, clique em OK lá no diálogo do Google Refine e aguarde. Mesmo sem o delay, é relativamente lento; porém o custo/benefício dessa gambiarrinha é, evidentemente, favorável
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!
Y is the new X
Segue aqui uma listinha de utilitários ligeiramente diferentes dos tradicionais e consolidados, mas cujas pequenas e grandes melhorias me fizeram renunciar quase que completamente os mais clássicos e abraçar os bleeding edge
mosh is the new ssh
Não me entenda mal: SSH é muito mais do que uma simples shell remota! Também serve para tunelamento de conexões, transferência de arquivos, autenticação remota... Aliás, o próprio Mosh se autentica via SSH. Só que daí em diante, é outra história.
Mosh utiliza o protocolo UDP ao invés do TCP, então não precisa manter uma conexão. Simplificando muito, Mosh faz sincronização do estado do terminal remoto com o local, ao invés de redesenhar as telas caractere por caractere.
Ou seja: o seu link pode ser muito ruim, que o Mosh não está nem aí. Pode perder conexão, pode colocar o notebook em standby; quando o link voltar, está aí a sua shell, do jeito que você deixou! 'Network error: Connection reset by peer', nunca mais.
Outro "efeito colateral" bacana do Mosh é que ele dispensa buffers. Já me ferrei dando um lsof numa conexão lerda; até que todo o output saia, SSH não deixa nem o input de Ctrl-C passar.
Para finalizar, o comparativo de tempo de resposta de SSH com Mosh, do site oficial do MIT:

tmux is the new screen
Mesmo que você seja um ávido fã do screen, o tmux vale uma espiada. O grande distintivo talvez seja o modelo client/server, que garante uma grande flexibilidade às sessões gerenciadas pelo tmux. Por exemplo, dá para mover janelas entre diversas sessões, ou então replicar o input em várias janelas (útil quando se quer iniciar o mesmo processo em diversos nós). Os atalhos de teclas tem perfis pré-definidos "emprestados" de Vim ou Emacs, e, é claro, dá para redefinir quase tudo do jeito que quiser pelo arquivo de configuração bastante legível. Ah, um bônus: split vertical da tela funciona sem patch adicional
Um breve tutorial:
htop is the new top
Esse dispensa apresentações. É praticamente um 'Activity Monitor.app' versão terminal. Infelizmente, a versão para MacOS tem algumas esquisitices (especialmente na parte que se refere à exibição da quantidade de memória). Ainda assim, é excelente para overview geral do sistema. Sempre mantenho um htop aberto nos meus servidores
ack is the new grep
Descobri o ack recentemente. Não é bem um grep, mas sim uma combinação de find/grep específica para trabalhar com código-fonte. Em suma, ele faz busca recursiva utilizando regexp "sabor Perl", e desconsidera coisas como subdiretórios do Git ou SVN. Por exemplo uma versão (very) quick & dirty do Perl::PrereqScanner seria:
ack -ho --perl '^\s*use\s+[\w+\.:]+' | sort -u
xz is the new bzip2
Um pequeno devaneio: aparentemente, Inteligência Artificial e compressão de dados são assuntos afins. Os organizadores do Hutter Prize, por exemplo, acreditam que uma compressão eficiente de um texto natural seria de dificuldade equivalente a passar no teste de Turing.
Voltando ao tema original: anos atrás, quando clock de um PC high-end não passava de 1 GHz, e conexão broadband ainda estava engatinhando nos países do 3-o mundo, ver um código-fonte compactado com bzip2 numa página de download era de restaurar a fé na humanidade. Pois era evidentemente um ato altruístico por parte do disponibilizador levar muito mais tempo para compactar para poupar um pouquinho de tempo e banda de quem fizer o download.
Hoje, o bzip2 roda na velocidade do gzip de outrora. Eis que revive o algoritmo LZMA: muito mais voraz quanto à CPU/RAM, na grande maioria dos casos chuta bonito o traseiro do bzip2! O conheci através do 7-Zip, de longe o melhor compactador para Windows (xupem, WinZip e WinRAR!). Depois, para a minha alegria, descobri o xz, que:
- É mais fácil de digitar do que 'bzip2';
- Compacta mais do que o bzip2;
- Usa os mesmos parâmetros que o bzip2 (
cat ... | xz -cv9 > dump.xzcontinua válido).
E você, tem algum exemplo assim? Comente!



