Pesquisar este blog

domingo, 28 de outubro de 2018

Machine Learning no Python

Aqui iremos mostrar como fazer o uso de Machine Learning no python
sudo pip install matplotlib
sudo pip install scipy
sudo pip install pandas
sudo pip install sklearn
Para testar as versões dos pacotes instalados utilize o código a seguir:
import sys
print('Python: {}'.format(sys.version))
# scipy
import scipy
print('scipy: {}'.format(scipy.__version__))
# numpy
import numpy
print('numpy: {}'.format(numpy.__version__))
# matplotlib
import matplotlib
print('matplotlib: {}'.format(matplotlib.__version__))
# pandas
import pandas
print('pandas: {}'.format(pandas.__version__))
# scikit-learn
import sklearn
print('sklearn: {}'.format(sklearn.__version__))
O que na minha máquina atualmente imprime o seguinte:
Python: 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609]
scipy: 0.19.0
numpy: 1.12.1
matplotlib: 2.0.1
pandas: 0.20.1
sklearn: 0.18.1

Importando as bibliotecas

Para importar as bibliotecas que serão utilizadas no nosso exemplo, utilize o código a seguir:



import pandas

from pandas.tools.plotting import scatter_matrix

import matplotlib.pyplot as plt

from sklearn import model_selection

from sklearn.metrics import classification_report

from sklearn.metrics import confusion_matrix

from sklearn.metrics import accuracy_score

from sklearn.linear_model import LogisticRegression

from sklearn.tree import DecisionTreeClassifier

from sklearn.neighbors import KNeighborsClassifier

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

from sklearn.naive_bayes import GaussianNB

from sklearn.svm import SVC

Carregando os dados

Como exemplo aqui será utilizado um dataset de classificação de flores provido pela UCI, e você pode conseguir mais detalhes sobre ele na wikipedia, a leitura deste dataset é feita utilizando a lib panda com o código mostrado a seguir:

url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
nomes = ['comp-sepalo', 'larg-sepalo', 'comp-petala', 'larg-petala', 'classe']
dataset = pandas.read_csv(url, names=nomes)


Caso você tenha algum problema com a sua rede você pode baixar o dataset e usá-lo de algum diretório local.

Analisando os dados

Para saber se está tudo certo com os dados, vamos imprimir as primeiras 10 linhas e isto é feito com o seguinte código
print(dataset.head(1))
    comp-sepalo  larg-sepalo  comp-petala  larg-petala       classe
0          5.1                 3.5              1.4               0.2           Iris-setosa
O próximo passo é dar uma olhada no resumo estatístico destes dados, isto será feito com o comando describe
print(dataset.describe())
Que irá imprimir o seguinte:
comp-sepalo  larg-sepalo  comp-petala  larg-petala
count          150.000000      150.000000       150.000000      150.000000
mean             5.843333        3.054000            3.758667        1.198667
std              0.828066        0.433594            1.764420        0.763161
min              4.300000        2.000000            1.000000        0.100000
25%              5.100000        2.800000            1.600000        0.300000
50%              5.800000        3.000000            4.350000        1.300000
75%              6.400000        3.300000            5.100000        1.800000
max              7.900000        4.400000            6.900000        2.500000
Podemos avaliar quantos dados temos em cada classe agrupando os dados pela coluna classe, isto é feito com o seguinte código:
print(dataset.groupby('classe').size())

Execução de algorítmos

O primeiro passo para iniciar um algoritmo de machine learning é dividir o dataset em dois, um para o treino e um outro para teste.
Por que fazemos isto?
Bom inicialmente  esta divisão ocorre para termos uma parte do nosso dataset para validação dos dados, ou seja, usamos parte do que temos como treinamento para validar o quanto o algoritmo está acertando. Esta divisão é feita da seguinte forma:

array = dataset.values
X = array[:,0:4]
Y = array[:,4]
validation_size = 0.20
seed = 7
X_train, X_validation, Y_train, Y_validation = model_selection.train_test_split(X, Y, test_size=validation_size, random_state=seed)
Note que usamos uma divisão randômica dos dados e isto é feito para que o nosso treino não seja direcionado pelos dados.

Criando um modelo

scoring='accuracy' 
models = []
models.append(('LR', LogisticRegression()))
models.append(('LDA', LinearDiscriminantAnalysis()))
models.append(('KNN', KNeighborsClassifier()))
models.append(('CART', DecisionTreeClassifier()))
models.append(('NB', GaussianNB()))
models.append(('SVM', SVC()))
# evaluate each model in turn
results = []
names = []
for name, model in models:
    kfold = model_selection.KFold(n_splits=10, random_state=seed)
    cv_results = model_selection.cross_val_score(model, X_train, Y_train, cv=kfold, scoring=scoring)
    results.append(cv_results)
    names.append(name)
    msg = "%s: %f (%f)" % (name, cv_results.mean(), cv_results.std())
    print(msg)

A execução dos modelos:

 O que ao ser executado, produz o seguinte resultado:
LR: 0.966667 (0.040825)
LDA: 0.975000 (0.038188)
KNN: 0.983333 (0.033333)
CART: 0.966667 (0.040825)
NB: 0.975000 (0.053359)
SVM: 0.991667 (0.025000)

Note que estes resultados podem variar dentre uma execução e outra, já que os algorítmos usados tem uma certa randomicidade.
Pelo resultado mostrado acima, dos algorítmos utilizados, o que produziu o melhor resultado foi o SVM, pois ele teve o melhor resultado de acurácia, além de ter o menor desvio padrão dos resultados produzidos.
Para comparar o resultado dos algorítmos, vamos plotar um gráfico com a média dos resultados
fig = plt.figure()
fig.suptitle('Algorithm Comparison')
ax = fig.add_subplot(111)
plt.boxplot(results)
ax.set_xticklabels(names)
plt.show()
Isto produz o gráfico abaixo:
No gráfico acima temos, uma caixa com o valores de acurácia, do menor para o maior, mais uma linha marcando a mediana. Veja que tanto o KNN, quando NB e o SVM tiveram um resultado bem próximos, segundo o gráfico, mas voltando a análise do resultado, lembramos que o SVM produziu o melhor resultado.

Fazendo Previsões

Como vimos que o SVM foi o melhor algorítmo, agora chegou a hora de comparar os resultados obtidos, com o nosso dataset de teste, nele iremos executar a previsão com a melhor configuração do algorítmo e comparar o resultado com o obtido anteriormente.
svm = SVC()
svm.fit(X_train, Y_train)
predictions = svm.predict(X_validation)
print(accuracy_score(Y_validation, predictions))
print(confusion_matrix(Y_validation, predictions))
print(classification_report(Y_validation, predictions))
Ao executar esta predição, temos como resultado a seguinte:
curácia0.9333333333333333
Matriz de Confusão[ [ 7  0  0]
  [ 0 10  2]
  [ 0  0 11]]
Relatório de ClassificaçãoIris-setosa             1.00      1.00      1.00         7
Iris-versicolor       1.00      0.83      0.91        12
Iris-virginica         0.85      1.00      0.92        11
    avg / total       0.94      0.93      0.93        30
Acurária, é o quão perto do esperado, o nosso resultado ficou, então estamos com 93% de acerto, o que é algo muto bom.
A Matriz de confusão, é um termo estatístico, utilizado no campo de Machine Learning e ele serve para plotar os resultados e acertos de um algorítmo de classificação, neste caso temos:
                     S   VC  VI
Setosa         [ 7    0      0]
Versicolor   [ 0   10     2]
Virginica     [ 0    0    11] 
Na linha de cima, no cabeçalho, S representa Setosa, VC representa Versicolor e VI representa Virginica. Nesta matrix, temos que das 7 Setosa, o algorítmo acertou todas, das 10 Versicolor, ele acertou todas e finalmente, das 11 Virginicas, ele aertou 11 e classificou 2 delas como Versicolor.
Já no relatório de classificação temos:
                           precision    recall  f1-score   support    Iris-setosa              1.00      1.00        1.00             7Iris-versicolor            1.00      0.83        0.91           12 Iris-virginica             0.85      1.00        0.92           11    avg / total               0.94      0.93        0.93          30Precisão é o cálculo da porcentagem de acertos dos Verdadeiramente Positivos, dividido pelos falso positivos, já Recall é o cálculo dos Verdadeiramente Positivos, dividido pelos falso negativos.
F1-score é um cálculo envolvendo a divisao dos Verdadeiramente Positivos, dividido pela soma dos Verdadeiramente positivos, mais os falso negativos mais os falso positivos.
Support é a ocorrência de cada valor em cada um dos tipos dos atributos.

Conclusão

Neste post foi mostrado como fazer previsão de classificação, de um dataset de flores, em uma predição supervisionada, ou seja, sabíamos qual era o resultado esperado, e por isto, conseguimos quantificar exatamente a quantidade de acertos.
A partir daqui, você pode pegar outros datasets, e executar outros testes dos algorítmos, este post foi uma exposição bem simples de como fazer previsões, usando o python.
Consideramos aqui, que é uma operação de classificação, já que estamos tentando definir, a partir dos dados, qual é o tipo de flor.

Bibliografia

Texto original

Apresentação
Apresentação2

domingo, 21 de outubro de 2018

Microservices Arquitetura de Microserviços em Java na Prática

Microservices Arquitetura de Microserviços em Java na Prática

Anteriormente, aqui no blog, já houve uma explicação sobre a arquitetura de microserviços no post específico de arquitetura, agora vamos ver na prática como implementar um serviço, utilizando esta arquitetura.
Para este post vamos usar a biblioteca chamada DropWizard, que é responsável por trazer algumas bibliotecas usadas pelo java, como o Jetty, servidor light HTTP, o Jersey usado para chamadas REST, parsers de Json e algumas outras bibliotecas que serão bem úteis neste projeto.
Com isto conseguiremos gerar um arquivo jar, executável, que conterá um servidor web para prover o nosso microserviço para teste.

Iniciando o projeto.

O primeiro passo para este projeto é criar um projeto maven e adicionar a dependencia do dropwizard.
<dependencies>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>1.3.5</version>
    </dependency>
</dependencies>
 Assim o maven irá incluir a dependencia para esta versão do dropwizard.

Criando uma classe de configuração

Cada projeto do dropwizard possui uma classe de configuração, permitindo assim, uma flexibilidade bem grande na criação destes projetos
As configurações do projeto serão inseridas em um arquivo yaml. No exemplo postado aqui, será construído um pequeno serviço de exemplo de alta performance.
A seguir segue o exemplo da classe de configuração:
package br.unip.dsd_microservice.config;

import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class ConfiguracaoMicroservicos extends Configuration {
    @NotEmpty
    private String template;

    @NotEmpty
    private String nomePadrao = "Estranho";

    @JsonProperty
    public String getTemplate() {
        return template;
    }

    @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

    @JsonProperty
    public String getNomePadrao() {
        return nomePadrao;
    }

    @JsonProperty
    public void setNomePadrao(String name) {
        this.nomePadrao = name;
    }
}
Quando esta classe ler o nosso arquivo de configuração, ela irá pegar duas propriedades dela, template e nomePadrao, cujas propriedades estão anotadas com @NotEmpty, ou seja, se elas não forem encontradas no arquivo de configuração, uma exceção será disparada.
Cada uma destas propriedades estão anotadas com a @JsonProperty, o que faz com que seja possível desserializá-las do arquivo de configuração, assim como serializá-las.
O arquivo de configuração deverá conter estas duas propriedades, como segue abaixo:
template: Olá, %s!
nomePadrao: Estranho
Este arquivo deverá ser salvo no diretório, onde voce deseja rodar o jar com o microserviço, com qualquer_nome.yml e pronto.

package br.unip.dsd_microservice;


import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import br.unip.dsd_microservice.config.ConfiguracaoMicroservicos;
import br.unip.dsd_microservice.healthcheck.VerificadorMetodoExemplo;
import br.unip.dsd_microservice.resource.ResourceExemplo;

public class AplicacaoExemplo extends Application<ConfiguracaoMicroservicos> {
    public static void main(String[] args) throws Exception {
        new AplicacaoExemplo().run(args);
    }

    @Override
    public String getName() {
        return "exemplo-microservico";
    }

    @Override
    public void initialize(Bootstrap<ConfiguracaoMicroservicos> bootstrap) {
        
    }

    @Override
    public void run(ConfiguracaoMicroservicos configuration,
                    Environment environment) {
        //nada por enquanto
    }

}
Esta classe contem a parametrização da configuração do nosso serviço, que será feita na classe ConfiguracaoMicroservicos. O método initialize é utilizado para realizar configurações, antes que a plicação incialize.

Criando o modelo do serviço

Este serviço irá devolver um json, então ele necessita de um modelo para formatar este retorno. Este modelo nada mais é do que uma forma de parametrizar os dados, e a única diferença entre ele, e o que foi visto anteriormente neste blog, é que este será um modelo de JSON.
package br.unip.dsd_microservice.modelo;


import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length;

public class ExemploJson {
    private long id;

    @Length(max = 3)
    private String content;

    public ExemploJson() {
        // Jackson deserialization
    }

    public ExemploJson(long id, String content) {
        this.id = id;
        this.content = content;
    }

    @JsonProperty
    public long getId() {
        return id;
    }

    @JsonProperty
    public String getContent() {
        return content;
    }
}
Repare que este é um modelo comum, porém ele é imutavel, uma vez que não contem nenhum método de set e suas variáveis são private. Isto o torna muito melhor para rodar em ambientes multithread, comuns em micro serviços, uma vez que não teremos comportamentos alterados, devido a alteração de valores do modelo durante a execução em várias threads diferentes.
Os métodos de get, permitem que a api de Json serialize este objeto e os transforme em um JSON.

Criação da classe de recurso

No dropwizard as classes de recurso são utilizadas para mapear uma url a uma classe, diferente do playframework que possui um arquivo de rota, aqui este mapeamento ocorre dentro da classe. Neste caso estamos mapeando o endereço dsd-webservice para esta classe de recurso, com isto, as chamadas a este caminho serão direcionadas a ela.
package br.unip.dsd_microservice.resource;


import com.codahale.metrics.annotation.Timed;

import br.unip.dsd_microservice.modelo.ExemploJson;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Optional;

@Path("/dsd-microservice")
@Produces(MediaType.APPLICATION_JSON)
public class ResourceExemplo{
    private final String template;
    private final String nomePadrao;
    private final AtomicLong counter;

    public ResourceExemplo(String template, String nomePadrao) {
        this.template = template;
        this.nomePadrao = nomePadrao;
        this.counter = new AtomicLong();
    }

    @GET
    @Timed
    public ExemploJson digaOla(@QueryParam("name") Optional<String> name) {
        final String value = String.format(template, name.orElse(nomePadrao));
        return new ExemploJson(counter.incrementAndGet(), value);
    }
}
Repare que é possível definir qual metodo http será utilizado por aquele método da classe, com isso o @GET mapeia o método http pro método digaOla.
A anotação @Path é quem demonstra o mapeamento da url ao método, e a anotação @Produces é quem indica que este endereço retornaŕa um Json como resposta.
Repare que esta classe recebe dois parametros como argumento, o primeiro que é o Template, usado para produzir a resposta, e o nomePadrao, que será utilizado na resposta, quando um nome não for fornecido na chamada.

Criando um teste para aplicação

É altamente recomendável que se crie testes de saúde na sua aplicação, o intúito destes testes é chamar os métodos dela, que devem ser todos testados, para provar que eles estão em funcionamento.
Abaixo segue um exemplo de teste para o código do serviço que acabamos de gerar.

package br.unip.dsd_microservice.healthcheck;

import com.codahale.metrics.health.HealthCheck;

public class VerificadorMetodoExemplo extends HealthCheck {
    private final String template;

    public VerificadorMetodoExemplo(String template) {
        this.template = template;
    }

    @Override
    protected Result check() throws Exception {
        final String saying = String.format(template, "TEST");
        if (!saying.contains("TEST")) {
            return Result.unhealthy("não inclui um exemplo de json");
        }
        return Result.healthy();
    }
}
Este teste chega duas coisas, uma é se o retornor está sendo realmente a string esperada e outra é se ele inclui a string dada com entrada, no retorno do método.

Registrando o recurso e o teste na aplicação.

Como passo final, deveremos registrar, no método run da aplicação, tanto o Recurso, quanto o teste criado, isto será feito da seguinte maneira:
 @Override
    public void run(ConfiguracaoMicroservicos configuration,
                    Environment environment) {
        final ResourceExemplo resource = new ResourceExemplo(
            configuration.getTemplate(),
            configuration.getNomePadrao()
        );
        environment.jersey().register(resource);
        final VerificadorMetodoExemplo healthCheck =
                new VerificadorMetodoExemplo(configuration.getTemplate());
        environment.healthChecks().register("template", healthCheck);
    }

Criando o seu pacote

Ao adicionar todos estas classes no seu projeto, agora é hora de alterar o arquivo pom do projeto para que ele configure a classe da Aplicação, como mainclass do seu projeto, para que ela seja chamada quando o jar do projeto for executado.
<build>
 <plugins>
           <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>2.3</version>
      <configuration>
          <createDependencyReducedPom>true</createDependencyReducedPom>
         <filters>
             <filter>
                 <artifact>*:*</artifact>
                 <excludes>
                     <exclude>META-INF/*.SF</exclude>
                     <exclude>META-INF/*.DSA</exclude>
                     <exclude>META-INF/*.RSA</exclude>
                 </excludes>
             </filter>
         </filters>
      </configuration>
       <executions>
           <execution>
               <phase>package</phase>
               <goals>
                   <goal>shade</goal>
               </goals>
               <configuration>
                   <transformers>
                       <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                       <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                           <mainClass>br.unip.dsd_microservice.AplicacaoExemplo</mainClass>
                       </transformer>
                   </transformers>
               </configuration>
           </execution>
       </executions>
   </plugin>
  </plugins>
 </build>
Com isso basta digitar mvn package que o maven irá criar um pacote jar dentro do diretório target no seu projeto.

java -jar target/dsd-microservice-0.0.1-SNAPSHOT.jar server hello-world.yml
Pronto, após seguir todos estes passos, você já é capaz de executar um microserviço com o Java, usando a api DropWizzard.
O código de exemplo deste post encontra-se neste repósitório.

quinta-feira, 4 de outubro de 2018

Novos bancos de dados (NewSQL)

Novos banco de dados (NewSQL)

História

Com o desenvolvimento dos aplicativos Web, surgiu uma grande necessidade de ganho de performance, de uma hora pra outra, o volume de dados ficou muito grande, e a quantidade de requisições cresceu de forma a sobrecarregar os sitemas de armazenamento de dados SQL. A partir daí começaram a surgir diversas opções de banco de dados NOSql e isto virou um movimento, conforme foi mostrado neste post.
Alguns sistemas ainda dependem de uma estrutura Relacional com transações, conforme mostrado no post de Consistência e Replicação, como os sistemas de controle de estoque e de transações financeiras, e o volume destes sistemas também cresceu, com isto surgiu uma demanda por sistemas relacionais com alta taxa de performance.
Foi desta demanda que surgiram os banco de dados conhecidos como NewSQL, estes bancos mantêm a consistência em um banco relacional, com transações ACID usando a linguagem SQL como sua linguagem primária.
Estes sistemas conseguiram melhorar muito a performance e a escalabilidade remodelando algumas coisas da arquitetura legada de onde os sistemas de banco SQL se basearam, como a recuperação de transações e os algoritmos de controle de concorrência.

Novas arquiteturas

Os primeiros bancos deste tipo são uma plataforma completamente diferente da qual estamos acostumados, estes bancos foram desenvolvidos para operar em um cluster distribuído em forma de nós. Estes nós são completamente independentes e são criados de forma a serem completamente independentes e auto-suficiente, ou seja ele contem todas as informações necessárias para realizar as operações que eles devem fazer. Eles não compartilham processador, alguma parte da memória do sistema, ou ainda partes do disco.
Estes sistemas foram desenvolvidos baseados em uma arquitetura distribuída, com isto eles conseguem executar queries em paralelo, eles possuem um controle de concorrência distribuído, um controle de fluxo dos dados.
Ou seja, nos sistema NewSQL o servidor executa diversos nós em paralelo, podendo estes nós serem executados em uma mesma máquina, e cada um destes nós cuidará de uma parte dos dados. Com isto é possível fazer com que os dados sejam escritos mais rapidamente, já que serão processos sendo executados em paralelo, assim como principalmente as queries terão uma performance mais rápida, já que cda nó executará um pedaço desta query.

Novas tecnologias velhos problemas

Apesar de existir uma grande evolução na arquitetura e no desenvolvimento, comparando-se com os sistemas de banco de dados existentes, os grandes desafios de se fazer um sistema de armazenamento de dados distribuídos, ainda existem.
Ainda que o foco destes sistemas seja manter a consistência, existe um certo receio no mercado, conforme elucidado neste blog (em inglês) quanto a capacidade destas arquiteturas manterem a consistencia dos dados.

Nova forma de lidar com os velhos problemas.

Uma das coisas que ficou bem claro, com o aparecimento dos bancos NoSQL é que não dá mais pra confiar em uma bala de prata, ou seja em um sistema que vai resolver todos os problemas de armazemento de dados, estes sistemas de NewSQL são desenvolvidos para resolver um problema específico.
Um dos exemplos desta evolução é que nem todos estes bancos possuem o esquema rígido que os bancos SQL tradicionais possuem.
Como a grande parte do mercado de desenvolvedores conhecem o SQL e isto é um padrão de mercado, esta linguagem foi escolhida para fazer a interface com estes sistemas de armazenamento de dados.

Exemplos

CockrauchDB

Surgiu a partir de uma tecnologia do Google criada para escalar aplicações Web, esta base tem uma consistência forte, é escalável, usa SQL distribuído, ou seja, executa as funções em seus vários nós, aumentando a velocidade das operações, ela é escalável e está equipada com um dos melhores sistemas de recuperação de falhas.

Altibase

Altibase é uma base que se aproveita pra deixar parte dos dados na memória, e com isto garantir uma grande velocidade de acesso, e parte dos dados armazenada em sistema de arquivo, com isto ela garente tanto a alta velocidade de acesso da memória, quanto o espaço dos sistemas de arquivos. Ela é muito flexível, ACID, agnóstica de sistema operacional e hardware. Por ser SQL ansi compativel, ela facilita bastante a migração dos sistemas atuais.

Faunadb

Apesar do Fauna, não ser um banco de dados que aceite SQL como forma de iteração, ainda, ele é um dos bancos que se destaca pela alta performance, relacional, que mantem a consistência, mesmo quando a base está alocada em múltiplas geolocalidades, as queries podem operar dentro de transações. Este é um banco sem esquema, que armazena objetos, grafos, dados relacionais e series temporais.
As comunicações com o fauna, são todas via apis RESTful, com isto, praticamnte, qualquer linguagem pode acessar os seus dados, e fazer operações nele.