quarta-feira, 25 de setembro de 2019

Rede neural para Mega-sena em Colab + Keras

Como tudo começou

Resolvi aprender um pouco sobre implementação de redes neurais, e um bom projeto para começar é montar uma rede para prever os resultados da mega-sena. Os mais racionais vão dizer que isso é perda de tempo pois, o sorteio sendo aleatório, não é possível a rede aprender nenhum comportamento ou padrão. Bom, isso não importa, só de conseguir montar a rede e não gerar erros na execução já está de bom tamanho para começar.

Começei como acho que todo mundo começa: com linux e python. Baixei os pacotes com pip, e tudo ficou uma merda, com zilhões de versões diferentes, um monte dependencia incompreenssível e sem solução do tipo pacote A precisa do pacote B, que precisa do pacote C que não é compativel com o pacote A. O começo é difícil.

Ai conheci  Anaconda. Isso facilita muito a vida de quem esta começando. Vem tudo bunitinho e a maioria das coisas funciona de primeira. O Jupyter notebook vem instalado e funcionando, assim como os framework de inteligencia artificial como o tensorflow funcionam com facilidade. Começo a rodar diferentes exemplos e percebo que a minha máquina é muito ruim, e serve só para fazer fofoca do facebook e ver youtuber mané. Para rede neural falta memória, falta velocidade e qualquer aplicação boba leva muito tempo para processar.

Encontrando o Colab

Então, por acaso, no meu caminho, aparece o Colab, uma plataforma do Google. As máquinas virtuais alí tem mais memoria RAM que todos meus computadores juntos, e placas GPUs que preciso vender o carro para comprar uma. E tudo de graça. Fora que a conexão de internet dessas máquinas é muito rápida, assim posso baixar qualquer coisa da internet rapidamente na máquina virtual. Beleza, posso continuar com meu computador vagabundo e fazer minhas redes no Colab. Meu computador virou um mero terminal ;-)

O link para o Colab é esse aqui:

https://colab.research.google.com/


E Keras?

Junto com o Colab, descobri o Keras. De inicio achei que Keras era coisa de vagabundo preguiçoso que não queria se importar com os detalhes mais profundos de programação da rede neurais. Coisa de chefe: só fala oque quer, e os outros que se virem para conseguir fazer o negócio funcionar. E era isso mesmo!!! Keras é fácil, funciona e remove boa parte da complicação que vc não quer ter enquanto aprende algo novo. Seria o similar do seguinte: Python é muito facil, e C tem um monte de detalhes que vc precisa saber para utilizar. Keras é o Python, e tensorflow é o C. E Keras já vem instalado dentro da máquino do Colab, dentro do pacote do tensorflow. Nada mais fácil que isso.

E a Mega? Eu quero grana...

Como aprendizado montei um tutorial rápido de como utilizar o Colab, baixar arquivo de resultados da mega-sena no site da Caixa, processar e testar diferentes redes neurais em Keras. O arquivo esta no link abaixo, e pode ser aberto no Colab, procure por um link na página

https://github.com/JairoRotava/Colab_studies/blob/master/megasena_keras.ipynb

Mas não vá acreditar que isso vai em algum momento acertar os resultados da mega. Esse estudo também pode conter erros teóricos, não fazer sentido. Mas é uma boa forma de começar e aprender a sintaxe dos comandos em python e keras.

Boa sorte


sábado, 21 de setembro de 2019

Controle remoto para Linux utilizando um Arduino

Bateu um tédio, pouco serviço pra fazer, e oque precisa ser feito é muito chato. Resolvi fazer um projeto que estava muito tempo na fila: utilizar um controle remoto para controlar um computador que uso no rádio e tv. Da muita preguiça chato ter ir até o computador para mudar de música, pausar o filme no netflix, ou outra coisa qualquer.

O projeto é simples, utiliza um arduino UNO, um controle remoto que estava sobrando - da propria TV, uma panasonic, e um receptor de IR manjado ( TSOP4838 ). O arduino interpreta o sinal vindo do TSOP4838, comunica pelo USB com o computador, e um programa em python converte isso para comandos em Linux.


Receptor IR - Arduino

O hardware não tem nada demais, é o receptor de IR espetado no arduino. Coloquei tudo dentro de um copo do starbuck para não ficar dando curto com o resto dos equipamentos que estão no rack da tv.

O programa utilizado no Arduino é baseado num dos exemplos disponíves com a biblioteca IRremote. Foi adicionado código para lidar coma alimentação do sensor (pino 12 = GND e pino 13 = VCC), e tudo oque era descenessário foi retirado. No meu caso tive sorte do protocolo do controle remoto que eu tinha ser suportado pela biblioteca. No meu caso o protocolo utilizado é o PANASONIC.

#include <IRremote.h>

int RECV_PIN = 11;
int RECV_VCC_PIN = 13;
int RECV_GND_PIN = 12;

IRrecv irrecv(RECV_PIN);

decode_results results;

void setup()
{
  digitalWrite(RECV_GND_PIN, LOW);
  digitalWrite(RECV_VCC_PIN, HIGH);

  pinMode(RECV_GND_PIN, OUTPUT);
  pinMode(RECV_VCC_PIN, OUTPUT);
  
  Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    irrecv.resume(); // Receive the next value
}

Esse programa retorna um número hexadecimal para cada tecla do controle. Mapeando a função com cada código retornado é feito um mapa do controle remoto. Neste controle da Panasonic algumas teclas mandam o mesmo código 3 vezes, geralmente as teclas que vc não que que fique repetindo, como play/pause e outras. As demais teclas com volume ficam mandando o código periodicamente enquanto o botão é apertado.



Interpretador em python

Programa python para recepção dos dados. Utiliza biblioteca pySerial e a ferramenta xdotool. Utilizo o python3. O programa acabou ficando mais complicado que o previsto inicialmente porque o receptor pega um pouco de ruído e envia alguns códigos incorretos. O software também gera uma movimentação não linear para o ponteiro do mouse, ou seja, começa devagar e vai movendo cada vez mais rápido, isso torna o controle pelo controle remoto mais fluido. O código esta bastante comentado.

# Recepção de control remoto
import time
import serial 
import os

VERSION = "0.1"
SERIAL_PORT = "/dev/ttyACM0"
BAUD_RATE = 9600

REPEATABLE = True
NOT_REPEATABLE = False
UNKNOW_COMMAND = '??'
NO_COMMAND = ''
NO_DESCRIPTION = ''
NO_IR_CODE = -1
#Lista de relacao entre codigo IR e comando linux. Formato [Codigo IR, Comando Linux, Descricao, Pode Repetir?]
command_list = [
 ['E17AF807','amixer -q -D pulse sset Master toggle','mute',NOT_REPEATABLE],
 ['BE3BB37','xdotool key XF86AudioRaiseVolume','aumenta volume',REPEATABLE],
 ['D4D9F2A7','xdotool key XF86AudioLowerVolume','baixa volume',REPEATABLE],
 ['21035431','xdotool mousemove_relative -- %MOUSE_MOVE 0','seta para direita',REPEATABLE],
 ['983AB4C1','xdotool mousemove_relative -- -%MOUSE_MOVE 0','seta para esquerda',REPEATABLE],
 ['C20370A1','xdotool mousemove_relative -- 0 -%MOUSE_MOVE','seta para cima',REPEATABLE],
 ['81930A09','xdotool mousemove_relative -- 0 %MOUSE_MOVE','seta para baixo',REPEATABLE],
 ['BB0ED9E1','xdotool click 1; sleep 0.2','ok',NOT_REPEATABLE],
 ['AA7B2167','xdotool key XF86AudioPlay','play',NOT_REPEATABLE],
 ['2E8C2A89','xdotool key XF86AudioNext','fwd',NOT_REPEATABLE],
 ['1F6E01C9','xdotool key XF86AudioPrev','rew',NOT_REPEATABLE],
 ['1D15F4D7','xdotool key XF86AudioStop','stop',NOT_REPEATABLE],
 ['406A954D','systemctl poweroff -i','desliga',NOT_REPEATABLE],
 ['E17A24DB','xset dpms force off; sleep 1','apaga tv',NOT_REPEATABLE],
 ['E17A18E7','xdotool key C; sleep 1','apaga tv',NOT_REPEATABLE],
 ['E17A7887','xdotool getwindowfocus getwindowname;sleep 1','pega nome janela em foco',NOT_REPEATABLE],
 ['A26409C9','pkill --oldest chrome; google-chrome-stable deezer.com/br/ & sleep 1','Abre pagina deezer',NOT_REPEATABLE],
 ['240C9161','pkill --oldest chrome; google-chrome-stable www.netflix.com & sleep 1','Abre pagina netflix',NOT_REPEATABLE],
 ['68E839F1','pkill --oldest chrome; google-chrome-stable www.youtube.com & sleep 1','Abre pagina youtube',NOT_REPEATABLE],
 ['ABBE1086','xdotool key Escape','esc',NOT_REPEATABLE], 
 ['68E69B7F','xdotool click 4','scrool up',REPEATABLE],
 ['223C02A7','xdotool click 5','scrool down',REPEATABLE]
]

#Codigo q corresponde a tecla repetida do IR. Tecla ainda esta apertada
button_down = 'FFFFFFFF'
#inicializa para evitar possivel chamada dessa variavel sem inicilizacao
linux_command = NO_COMMAND
last_command = NO_COMMAND
description = NO_DESCRIPTION
allow_repeat = NOT_REPEATABLE
repetition_count=0
last_ir_code = NO_IR_CODE
ir_code = NO_IR_CODE
#tempo max para interpretar que a tecla foi solta
IR_TIMEOUT = 0.3
#sinaliza q tecla esta pressionada desde ultima vez
pressed = False
#Maximo deslocamento do mouse
max_mouse = 35

# Hello message
print("Controle Remoto Arduino/Linux TabaJairo para controle Panasonic")
print("Versao: " + VERSION);


# Tenta conectar a serial
serial_connected = False
print("Procurando porta serial...")
while (not serial_connected):
 try:
  ser = serial.Serial(port=SERIAL_PORT,baudrate=BAUD_RATE, timeout=1)
  serial_connected = True
  print("yeah...achei")
 except:
  print("buuu...ainda não achei")
  time.sleep(1)
  

#Loop infinito - ctrl+c para terminar
while 1 :
 #Recebe ir code. Aguarda enquanto nao tem nada na serial
 last_ir_code = ir_code
 t = 0
 while ser.inWaiting() == 0:
  time.sleep(0.1)
  t = t + 0.1
  if t > IR_TIMEOUT:
   t=0
   #limpa ultimo codigo pois estourou timeout
   last_ir_code = NO_IR_CODE
 out = ser.readline().rstrip()
 ir_code = out.decode('utf-8')


 #Procura comando na lista
 #Coloca NO_COMMAND em linux_command para indicar q nao foi encontrado
 linux_command = NO_COMMAND
 for command in command_list:
  if ir_code == command[0]:
   linux_command = command[1]
   description = command[2]
   allow_repeat = command[3]

 # Se teve timeout na recepcao do codigo IR entao o ultimo comando eh vazio
 if last_ir_code == NO_IR_CODE:
  last_command = NO_COMMAND
  
 pressed = False
 #Verifica se eh comando repetido (tecla pressionada)
 if (linux_command == last_command) and (last_ir_code != NO_IR_CODE):
  pressed = True
  
 # Aqui tem um truque para melhor responsividade: se o comando atual nao eh reconhecido e 
 #  nao teve timeout na recepcap de IR supoe que houve problema na recepcao do codigo:
 #  o codigo eh diferente mas corresponde a ultima tecla pressionada
 if (linux_command == NO_COMMAND) and (last_ir_code != NO_IR_CODE) and (last_command != NO_COMMAND):
  pressed = True
  linux_command = last_command
  print("Codigo IR invalido! Provavelmente eh o ultimo comando, vamos repetir..."); 
 
 # Incrementa contador de botao pressionado
 if pressed:
  repetition_count+=1
 else:
  repetition_count=0

 # Atualiza last command. A posicao da linha eh importante para o codigo funcionar  
 last_command = linux_command
  
 #subsitui wildcard %MOUSE_MOVE do movimento do mouse para fazer aceleracao
 mouse_move = repetition_count*repetition_count+1;
 if mouse_move > max_mouse:
  mouse_move = max_mouse
 linux_command = linux_command.replace("%MOUSE_MOVE",str(mouse_move)) 

 #executa comando se for conhecido. Verifica se ele pode ser repetido ou nao.
 # Nao queremos o play/pause repetindo, nem o mute por exemplo
 if linux_command != NO_COMMAND:
  if repetition_count == 0 or  (repetition_count > 0 and allow_repeat):
   print(ir_code + ' ' +  str(repetition_count) + ' --> ' + description + ' --> ' + linux_command); 
   os.system(linux_command)
  else:
   print(ir_code + ' ' +  str(repetition_count) + ' --> ' + description + ' --> nao pode repetir');
 else:
  print(ir_code + ' ' +  str(repetition_count) + ' --> buuu, nao conheco esse comando ');


O programa monitor a porta serial /dev/ttyACM0 na qual o arduino esta conectada. Sempre que recebe um comando serial ele dispara o correspondente no linux. A ferramente xdotool permite simular o mouse e teclado, e com isso é possivel enviar comandos como aumentar/baixa volume pelas teclas especiais de volume, play/pause etc. Também é possível utilizar diretamente outros comandos do linux, como por exemplo desligar a tela ou fazer shutdown da máquina.
No exemplo acima estão comandos diretos no linux, comandos que simulam teclado ( xdotool key ... ) e comandos que simulam mouse (xdotool mousemove_relative e click ) .


Na figura acima esta uma amostra do programa recebendo os códugos. O primeiro campo é o código recebido pela IR, depois o numero de repetições, uma descrição do código, e o comando executado no linux.
Para algumas teclas do controle é simulado o uso de teclas de multimedia (volume, play, stop por exemplo), barra de rolagem do mouse, ou é executado algum comando específico, como desligar o sistema.

Ajuste do navegador

Foi encontrado um problema inesperado durante esse projeto: os navegadores de internet de forma geral não aceitam as teclas de acesso rápido multimidia (play, pause, stop...). Para fazer isso funcionar é necessário instalar um plugin no navegador. Não tive sucesso ao utilizar isso no firefox - o plugin não funcionava. No chrome o plugin funcionou corretamente e agora consigo controlar sites de musica, youtube e netflix com o controle remoto.

Falta deixar claro que estamos simulando tecla de atalho multimidia, que é possivel encontra em alguns teclado reais. Existe o padrão XF86 que são as telcas multimidia. Existe diferentes funções que podem ser acessadas com essas teclas.

Recepção Arduino

A comunicação com o Arduino é feita pela USB. Se o sistema não estiver encontrado o arduino verifique o nome da porta que o arduino esta instalado e corrija no programa de recepção em python. Também verifique se o acesso a USB esta liberado. Falta de acesso é um problema comum com o Arduino, existe bastane informação sobre isso na internet.


Como compilar SWAT 2018 rev670 no Gfortran Linux

Instalação do GFortran

Para instalar o GFortran no Ubuntu utilize os comandos:

sudo apt update
sudo apt install gfortran

Modificações do código original

Pode ser necessário alterar os arquivos de código fonte para atendenrem o padrão do fortran. O compilador da intel é mais premissivo quanto ao padrão do arrquivo, enquanto o gfortran é mais chato, requer o código no padrão fixed ou free from, e não lida bem com a mistura dos dois dentro do mesmo arquivo. Os códigos mais recentes do SWAT já tem corrigido essa padronização, mas os antigos podem precisar de bastante modificação.
O SWAT é compilado originalmente com ifortran (intel), e o compilador da intel é mais permissivo que o compilador GFortran para questões de compatibilidade de formato de código antigo (.f) e novo (.f90) . O fortran passou por várias revisões com o tempo, e o código de swat tem arquivos com padrões antigos e novos, as vezes misturados dentro de um mesmo arquivo. O codigo original do SWAT não compila com GFortran, pois ele é mais chato com relação a formatação dos arquivos. Além disso, no SWAT também são utilizadas estruturas que são diferentes das recomendadas pelo padrão fortran, mas que o compilador da intel aceita, pois facilitam o uso da linguangem.
Modificações necessárias para compilar:
  1. Comentar a linha !! include 'modparm.f' no main.f
  2. É necessário alterar o comando "use" em alguns arquivos, para "use parm, except_this_one => tair", onde tair deve ser substituido pelo nome da função do arquivo com erro.
O erro ao compilar caso o comando "use" não seja corrigido é este:
       use parm
         1
Error: ‘regres’ of module ‘parm’, imported at (1), is also the name of the current program unit 
 Os arquivos que precisam ser corrigidos são:

  1. tair.f
  2. layersplit.f
  3. vbl.f
  4. ndenit.f
  5. atri.f
  6. rsedaa.f
  7. regres.h
  8. HQDAV.f90
Para corrigir altere a linha com “use parm” para “use parm, except_this_one => tair”. O nome na exceção deve ser ser o nome da função atual.
Essa modificações devem tornar o código compativel com o GFortran, sem atrapalhar o IFortran. Os os compiladores devem funcionar corretamente.


Arquivo makefile

Foi criado um makefile para facilitar a compilação do sistema. O makefile verifica se o módulo esta disponível e compila, e depois constroi o programa. A versão release e debug estão em diretórios separados. Para compilar executar: make all, make release ou make debug. Make clean limpa os builds antigos e make prep cria os diretórios.
Baixe o arquivo do link disponível no final do post e copie no mesmo diretório do código fonte.


Compilando

Para compilar execute o comando "make all" no diretório do código fonte. Isso vai criar as pastas dos executáveis. O binario está é salvo na pasta /release com o nome swat


Flags do compilador

Aqui é apresentado uma pequena descrição das flags utilizadas no compilador. Voce não precisa entender oque acontece aqui. Elas são apresentadas somente como curiosidade e para ajudar e resolver algum tipo de problema.

Para compilador com sucesso é necessário utilizar algumas flags do compilador. Elas são:
  1. release: -c -O3 -static -ffixed-line-length-none -ffree-line-length-none -std=gnu -ffpe-trap=invalid,zero,overflow -fno-automatic
  2. debug: -c -O0 -static -ffixed-line-length-none -ffree-line-length-none -std=gnu -ffpe-trap=invalid,zero,overflow -fcheck=all -fbacktrace -fno-automatic
Uma breve descrição das flags são:
  1. -O0 ou -O3: nível de otimização do código.
  2. -static: compila bibliotecas estaticamente no código.
  3. -ffixed-line-length-none: informa ao compilador para ler a linha inteira dos arquivos de código que estão no formato fixo. Pode ser necessário retirar essa flag caso o compilador apresente erro ao ler os arquivos.
  4. -ffree-line-length-none: informa ao compilador para ler a linha inteira dos arquivos de código que estão no formato livre. Pode ser necessário retirar essa flag caso o compilador apresente erro ao ler os arquivos.
  5. std=gnu: padrão do fortran utilizado.
  6. -ffpe-trap=invalid,zero,overflow: ativa exceções para capturar erro de overflow, número inválido e divisão por zero. O erro de underflow não é capturado, o número é aproximado para zero.
  7. -fno-automatic: trata variáveis locais como estáticas, ou seja, elas são salvas e mantém o mesmo valor entre as chamadas da função. O fortran inicialmente tratava as variáveis locais como estáticas, mas nas versões mais novas elas não mantém mais o valor entre chamadas. O código swat assume que as variáveis mantém o valor entre as chamadas, por iso precisa dessa flag.
  8. -fcheck=all: verifica por todos erros possíveis durante execução. Pode deixar o programa mais lento. Utilizado somente no debug.
  9. -fbacktrace: faz um rastreamento para a função que gera erro. Utilizado somente no debug.


Sobre os formatos dos arquivos fortran

Os código fontes do fortran seguiam o padrão fixed form (.f), e os mais atuais seguem o free form (.f90). O código do SWAT parece misturar os dois formatos, oque pode gerar erros na compilação com o gfortran.
Os arquivos do SWAT parecem ser todos escritos em fixed form, mas alguns arquivos utilizam a quebra de linha do formato free form. O compilador informa um erro quando isso acontece.
O erro mais comum nos códigos do SWAT é utilizar a extensão .f, e utilizar para quebra de linha o caracter & (que é do padrão .f90). Isso gera um erro. A solução é alterar a extensão do arquivo para .f90, ou corrigir a quebra de linha.
Como regra geral: se o fim da linha tiver um &, a extensão do arquivo deve ser .f90. Se a linha de baixo tiver o & ou outro caracter na coluna 6 então deve ser .f.
Extensão .f → fixed form: primeira cinco colunas em branco. A continuação de linha é incluir algum caracter na coluna 6 da próxima linha. Esse é o primeiro padrão do fortran – F77. F77 le somente até coluna 72 por padrão.
Extensão .f90 → free form: é a versão mais moderna. A continuação de linha é com & no fim a linha atual e no inicio da segunda linha.
É recomendável utilizar .f90.
Alguns arquivos de algumas versões do SWAT tem as duas formas de quebra de linha no mesmo arquivo, oque torna impossível compilar sem corrigir o código para um dos dois padrões definidos.


Link para arquivos

Os arquivos originais do SWAT podem ser obtidos nesse link: https://swat.tamu.edu/software/swat-executables/

Os arquivos já modificados para GFortran e com o arquivo makefile podem ser obtidos no github: https://github.com/JairoRotava/SWAT-2018-GFortran

Vale a pena verificar se os resultados gerados com o binário compilado pelo GFortran é o mesmo que o gerado pelo binário gerado pelo fornecido no site.