22
See discussions, stats, and author profiles for this publication at: https://www.researchgate.net/publication/261026533 Algoritmos Genéticos em Java ARTICLE · AUGUST 2010 READS 686 1 AUTHOR: Leandro Luque Centro Paula Souza 23 PUBLICATIONS 1 CITATION SEE PROFILE Available from: Leandro Luque Retrieved on: 11 March 2016

Java Magazine - Algoritmos Genéticos com Java

  • Upload
    leluque

  • View
    35

  • Download
    0

Embed Size (px)

DESCRIPTION

Artigo da revista Java Magazine sobre uma biblioteca para a implementação de algoritmos genéticos com Java.

Citation preview

Page 1: Java Magazine - Algoritmos Genéticos com Java

Seediscussions,stats,andauthorprofilesforthispublicationat:https://www.researchgate.net/publication/261026533

AlgoritmosGenéticosemJava

ARTICLE·AUGUST2010

READS

686

1AUTHOR:

LeandroLuque

CentroPaulaSouza

23PUBLICATIONS1CITATION

SEEPROFILE

Availablefrom:LeandroLuque

Retrievedon:11March2016

Page 2: Java Magazine - Algoritmos Genéticos com Java

Algoritmos Genéticos em Java

Conceitos e aplicação

Saiba como a teoria da evolução das espécies pode ser aplicada na solução de problemas

LEANDRO LUQUE E RODRIGO ROCHA SILVA

De que se trata o artigo:

Neste artigo, é apresentada uma introdução aos principais conceitos de Algoritmos

Genéticos, uma importante subárea da Inteligência Artificial, e um exemplo de

aplicação a um problema de distribuição de produtos.

Para que serve:

Apresentar conceitos, técnicas e tecnologias que podem auxiliar desenvolvedores na

criação de produtos com diferenciais competitivos.

Em que situação o tema útil:

Com o aumento da oferta de produtos de software, a procura por diferenciais

competitivos passou a ser uma constante no cenário de desenvolvimento de software.

Técnicas de Inteligência Artificial (IA), como os Algoritmos Genéticos, desempenham

um papel importante neste cenário, uma vez que, podem ampliar e tornar mais úteis e

interessantes as funcionalidades de uma aplicação.

Algoritmos Genéticos em Java:

Algoritmos Genéticos, algoritmos de busca e otimização inspirados na teoria da

evolução das espécies, compreendem uma importante subárea da Inteligência Artificial,

que pode agregar valor a produtos de software de diferentes áreas. A linguagem Java

possui diversas bibliotecas que permitem a implementação dessa família de algoritmos,

sendo a JGAP uma das mais completas e com maior comunidade em atividade. Para

exemplificar os conceitos de Algoritmos Genéticos apresentados no artigo e o uso da

biblioteca JGAP, é utilizado um cenário de distribuição de produtos – pertencente a uma

classe de problemas conhecida como Problema do Caixeiro Viajante.

Page 3: Java Magazine - Algoritmos Genéticos com Java

O aumento da concorrência no mercado de software tem direcionado a atenção de

desenvolvedores para conceitos, técnicas e tecnologias que criem diferenciais

competitivos e contribuam para que seus produtos ocupem uma posição de destaque.

Neste contexto, técnicas de Inteligência Artificial (IA) desempenham um papel

importante, uma vez que, se utilizadas com bom senso, bem planejadas e desenvolvidas,

podem ampliar e tornar mais úteis e interessantes as funcionalidades de uma aplicação.

Para chegar a tal conclusão, basta comparar a utilidade de um software de controle de

empréstimos bancários, por exemplo, que possui apenas funcionalidades para o registro

e consulta de empréstimos realizados, com outro que utiliza técnicas de IA1 para

determinar o risco da concessão de um empréstimo para um cliente em função do seu

perfil e de dados históricos de inadimplência.

Exemplos de aplicações de técnicas de IA incluem a criação de interfaces adaptativas,

alocação de profissionais em tarefas, detecção de intrusão e predição de falhas em redes

de computadores, detecção de SPAM, inteligência em jogos, entre outros. Veja as

referências, no final do artigo, com links para alguns exemplos disponíveis na Web.

Pode-se definir Inteligência Artificial como sendo uma área multidisciplinar, inspirada

em processos naturais e relacionada à reprodução de capacidades normalmente

associadas à inteligência humana, como a aprendizagem, adaptação, o raciocínio, entre

outras.

Para reproduzir estas capacidades, diversas foram as abordagens adotadas no decorrer

da história da IA, como a abordagem simbolista, também conhecida como simbólica ou

cognitiva, baseada em aspectos cognitivos e algorítmicos – procurando reduzir a

inteligência a um conjunto de símbolos, regras e passos –, a conexionista, baseada na

simulação das estruturas consideradas responsáveis pela produção de comportamentos

inteligentes, como os neurônios, e a evolutiva, inspirada na teoria da evolução das

espécies.

Na Tabela 1, estão listados, para as abordagens citadas, exemplos de subáreas e suas

respectivas inspirações.

Abordagem Subárea Inspiração

Conexionista Redes Neurais Artificiais Neurônios biológicos

Simbolista Sistemas Especialistas Inferência humana

Evolutiva Algoritmos Genéticos Evolução das espécies Tabela 1. Principais abordagens da IA, exemplos de subáreas e suas respectivas inspirações.

As Redes Neurais Artificiais (RNA) são modelos computacionais inspirados na

estrutura e comportamento do cérebro humano e são geralmente utilizadas na

reprodução do processo de aprendizagem, associação e generalização. São efetivas

principalmente no aprendizado de padrões – reconhecimento de caracteres e voz, dados

em imagens, entre outros.

Os Sistemas Especialistas (SE) são sistemas computacionais que procuram apresentar

um comportamento semelhante ao de um especialista de um determinado domínio,

possuindo a capacidade de armazenar conhecimentos e os utilizar na solução de

problemas.

1 Na análise de risco de crédito, a subárea da IA conhecida como Redes Neurais Artificiais é geralmente

empregada – algumas vezes em conjunto com Algoritmos Genéticos. Além de técnicas de IA, existem

outras que podem ser empregadas com este mesmo fim, tais como: regressão linear múltipla,

programação linear, árvore de decisão, entre outras.

Page 4: Java Magazine - Algoritmos Genéticos com Java

Por sua vez, os Algoritmos Genéticos (AG) são algoritmos de busca e otimização

inspirados na teoria da evolução das espécies, e consideram a busca pela solução de um

problema como um processo de competição entre soluções candidatas.

Devido ao seu potencial de uso em diferentes áreas, há um crescente número de

aplicações de AGs, incluindo a geração de casos de teste de software, redução do

acoplamento e o aumento da coesão em modelos de classes, a alocação de profissionais

em tarefas, alocação de docentes e salas em instituições de ensino, definição de escalas

de plantão médico, rotas de entrega, otimização de projetos de carros e aviões,

inteligência em jogos, entre outras.

Neste artigo, serão apresentados os principais conceitos de Algoritmos Genéticos e

uma biblioteca voltada para a implementação deste tipo de algoritmo em Java – JGAP.

Para ilustrar a aplicação deste tipo de algoritmo e da biblioteca, será apresentado um

exemplo de otimização de rotas de distribuição de produtos.

Algoritmos Genéticos

Inspiração Biológica

Os Algoritmos Genéticos fazem parte de uma classe de algoritmos, conhecidos como

Algoritmos Evolutivos, inspirados na teoria da evolução das espécies, proposta

simultaneamente por Charles Robert Darwin e Alfred Russel Wallace.

Esta teoria descreve os mecanismos regentes do processo de evolução das espécies e,

para entendê-la, é apresentada, a seguir, uma breve descrição dos seus principais

conceitos.

Em uma comunidade biológica qualquer, a limitação na disponibilidade de recursos

necessários à sobrevivência – como alimento e abrigo – faz com que haja uma

competição, direta ou indireta, entre os indivíduos da comunidade.

Os indivíduos que possuem diferenciais competitivos – que são mais fortes, rápidos,

com maior tolerância ao frio ou ao calor, entre outros – têm maior chance de acesso a

esses recursos – ou fazem melhor uso deles – e maior tolerância às agressões impostas

pelo ambiente, o que, consequentemente, aumenta suas chances de sobrevivência. Estes

diferenciais competitivos, bem como outras características desejáveis e indesejáveis,

surgem da combinação genética decorrente do processo de reprodução e de mutação. A

informação genética envolvida nesse processo de combinação é representada nos

indivíduos através de uma sequência de genes, conhecida como cromossomo.

Aqueles indivíduos que sobrevivem podem transmitir a seus descendentes algumas das

características genéticas que lhes conferem uma condição privilegiada, caso estas sejam

hereditárias. Em longo prazo, a tendência é que as características genéticas dos

indivíduos melhor adaptados ao ambiente estejam presentes em grande parte da

população.

Este processo, que tende a preservar a informação genética que gera diferenciais

competitivos em um determinando ambiente, é conhecido como seleção natural e é a

base da teoria da evolução das espécies e também dos Algoritmos Genéticos. O quadro

“Exemplo de seleção natural” apresenta um exemplo clássico de seleção natural.

Resumindo os conceitos apresentados, pode-se dizer que a teoria fundamenta-se na

ideia de que um processo de seleção, associado a mecanismos de reprodução e mutação

de indivíduos, possibilita a evolução gradual da qualidade genética de populações.

Exemplo de seleção natural Um bom exemplo de seleção natural é o fenômeno conhecido como

“melanismo industrial”, percebido inicialmente na segunda metade do século XIX. Antes da industrialização, os indivíduos de uma espécie de mariposas –

Page 5: Java Magazine - Algoritmos Genéticos com Java

Biston betularia – da região de Manchester, na Inglaterra, eram predominantemente claros, sendo que os indivíduos escuros correspondiam à minoria. Após a instalação de indústrias na região, foi observada uma inversão neste

cenário: as mariposas claras passaram a ser minoria, enquanto as escuras tornaram-se maioria (Figura Q1).

Figura Q1. Mudança na população de mariposas após a industrialização – fenômeno conhecido como melanismo industrial.

A seleção natural pode ser utilizada para explicar o porquê de tal mudança2. As mariposas desta espécie têm o hábito de pousar sobre troncos de árvores que, em locais não poluídos, são cobertos por liquens, que dão aos troncos uma coloração clara. Nesses locais, ao pousar sobre troncos cobertos por liquens, as mariposas claras não são tão visíveis, enquanto as mariposas escuras se tornam presas fáceis de seus predadores – os pássaros. Para este ambiente, a cor da mariposa clara lhe conferia um diferencial competitivo. Na Figura Q2, lado esquerdo, é difícil perceber a presença de uma mariposa

clara, destacada no lado direito.

Figura Q2. Diferencial competitivo das mariposas claras antes da industrialização.

Com a industrialização, a fumaça e a fuligem lançadas pelas fábricas provocaram a morte dos liquens, deixando os troncos das árvores expostos. Dessa forma, o substrato utilizado pelas mariposas para pouso adquiriu coloração escura e, com isso, as mariposas claras tornaram-se mais facilmente identificadas do que as escuras (Figura Q3). Com o tempo, os indivíduos escuros passaram a predominar sobre os claros. Para este novo ambiente, a cor da mariposa escura lhe conferia um diferencial competitivo.

2 Apesar de existirem alguns autores contrários à ideia de que a seleção natural seja o processo regente

no melanismo industrial, há um número muito maior de autores e trabalhos que corroboram o papel da

seleção natural.

Page 6: Java Magazine - Algoritmos Genéticos com Java

Figura Q3. Diferencial competitivo das mariposas escuras após a industrialização.

Com o tempo, nos dois casos, os indivíduos que apresentavam coloração que os camuflava apresentavam maior chance de sobrevivência e, portanto, de transmitir tal característica aos descendentes. O resultado foi a mudança gradual na frequência de indivíduos de cada uma das cores.

Aplicação da Teoria da Evolução na Solução de Problemas

Baseados nesta ideia, diversos procedimentos para a solução de problemas surgiram a

partir de meados do século XX, entre eles, os Algoritmos Genéticos. Estes

procedimentos procuram simular computacionalmente os processos de reprodução,

mutação e seleção natural.

Nesta simulação, soluções de um problema representam os indivíduos de uma

população. Cada solução é representada através de uma estrutura, formada por

sequências de símbolos, que deve ser definida de forma a permitir que os processos citados sejam realizados. Estas estruturas representam os cromossomos dos indivíduos,

enquanto seus símbolos representam os genes.

A seleção natural é simulada através de uma função que associa às estruturas, valores

numéricos que especificam a qualidade das soluções. Os processos de reprodução e

mutação são simulados através da combinação e alteração das estruturas das soluções.

Os princípios básicos do AG podem ser aplicados de diferentes maneiras, produzindo

algoritmos ligeiramente diferentes. Na Figura 1, é apresentado um diagrama de

atividades de uma forma bastante comum de aplicação de AGs.

A primeira etapa para a aplicação de um AG consiste na criação de uma população –

um conjunto – de soluções candidatas para o problema que se deseja resolver. A partir

da população de soluções candidatas, novas soluções são geradas através de processos

que simulam a reprodução e a mutação.

Em seguida, os indivíduos são avaliados e o processo de seleção natural é simulado

para escolher as soluções que permanecerão na próxima execução do algoritmo. Caso os

critérios de parada – que determinam quando o algoritmo deve deixar de ser executado

– não sejam atendidos, as etapas, a partir da reprodução, são novamente executadas.

Caso contrário, o algoritmo deixa de ser executado e a melhor solução encontrada é

retornada.

Page 7: Java Magazine - Algoritmos Genéticos com Java

Figura 1. Etapas básicas de um Algoritmo Genético.

Para ilustrar estas etapas, será apresentado um exemplo de distribuição de produtos3:

Uma empresa distribuidora de produtos recebeu a lista de vários clientes e precisa lhes

entregar produtos. Procurando reduzir custos, a empresa deseja encontrar a rota mais

curta de entrega, o que, neste caso, acarreta em um gasto menor. Este problema, da

forma como foi descrito, pertence a uma classe de problemas conhecida como Problema

do Caixeiro Viajante (Travelling Salesman Problem – TSP)4

3 Mais adiante, no artigo, será abordada a implementação deste exemplo utilizando a biblioteca JGAP. 4 Substituindo os produtos por pacotes de rede e os clientes por roteadores, o mesmo problema poderia

ser estendido para um cenário de otimização na transmissão de pacotes de redes.

Page 8: Java Magazine - Algoritmos Genéticos com Java

Uma primeira ideia para encontrar a melhor rota seria listar todas as rotas possíveis e

compará-las umas com as outras, o que é conhecido como método da força bruta ou

busca exaustiva. No entanto, pode não ser viável resolver este problema dessa forma, já

que para apenas 20 clientes, por exemplo, o número de rotas possíveis é superior a dois

quintilhões – calculado através do fatorial de 20 –, o que exigiria muito tempo e

capacidade de processamento. Neste caso, os AGs podem ser utilizados5.

Para simplificar a estrutura das soluções do problema, será suposto que são apenas 10

os clientes da distribuidora, embora o mesmo raciocínio seja extensível para um número

qualquer de clientes.

Conforme citado anteriormente, no início de um algoritmo genético, deve ser criada

uma população de soluções – rotas que podem ser seguidas – para o problema. Uma

ideia de estrutura para essas soluções seria determinar, em ordem, os clientes que serão

visitados.

Desta forma, estas estruturas – cromossomos – são compostas por uma sequência de

dez genes, cada um podendo assumir um valor que vai de C1 a C10. Estes valores que

um gene pode assumir são conhecidos como alelos.

Na Figura 2, são apresentados dois exemplos de soluções, cada uma representando

uma rota de viagem para a distribuição dos produtos. A primeira solução, por exemplo,

determina que, após sair da distribuidora, o cliente 1 é visitado. Em seguida, o cliente 7,

e assim por diante.

Figura 2. Exemplo de estruturas de soluções candidatas (vetores) para o problema de

distribuição de produtos e as rotas que elas representam (grafo).

A definição do número de soluções geradas é geralmente baseada em experiências

anteriores com a mesma classe de problemas que se deseja resolver. Um número muito

pequeno de soluções pode não garantir a variabilidade adequada à resolução do

problema, enquanto um número muito grande pode tornar o processo de resolução mais

lento. Em muitos trabalhos da área, o número de soluções geradas é próximo de 100.

Após a criação de uma população de soluções, deve-se realizar a reprodução entre os

indivíduos para a produção de novos indivíduos. Existem diversas formas de realização

da reprodução (veja o quadro “Reprodução”). Uma delas, não tão apropriada para este

tipo de problema, mas simples para o momento, envolve a criação de duas novas

soluções a partir da combinação de partes de duas soluções (cruzamento de um ponto –

5 Diversos métodos e heurísticas – o algoritmo de Dijkstra, vizinho mais próximo, cobertura mínima,

entre outros – podem ser empregados na solução desse tipo de problema. Trata-se de um exemplo simples

para o entendimento dos algoritmos genéticos.

Page 9: Java Magazine - Algoritmos Genéticos com Java

1PX). Para que isso seja feito, é definido um ponto de corte que quebra as duas soluções

em duas partes e estas partes são combinadas para a produção de novas soluções.

Supondo um corte no meio das soluções, as novas soluções geradas ficariam conforme

representação da Figura 3.

Figura 3. Soluções geradas a partir do processo de reprodução.

Após a realização da reprodução, a população conta com quatro indivíduos: A, B, C e

D. A mutação, por sua vez, poderia ser aplicada a algumas soluções da população,

representando uma alteração na estrutura das soluções (veja o quadro “Mutação”). Na

Figura 4, é possível observar o resultado da aplicação da mutação na solução A,

envolvendo a troca do quarto com o nono gene de sua estrutura.

Figura 4. Mutação realizada em uma das soluções da população.

Após a realização da reprodução e mutação, entra em cena a seleção natural.

Inicialmente, todas as soluções da população são avaliadas através de uma função de

avaliação, que verifica a qualidade de cada solução.

Page 10: Java Magazine - Algoritmos Genéticos com Java

Para o exemplo apresentado, a avaliação de cada solução pode ser feita a partir da

distância percorrida pelo entregador. A solução que apresentar a menor distância é

aquela que apresenta a maior qualidade. Para tanto, pode-se utilizar uma tabela de

distâncias, como a apresentada na Tabela 2, para a realização dos cálculos.

Destino

Origem

Dis

trib

uid

ora

C1

C2

C3

C4

C5

C6

C7

C8

C9

C10

Distribuidora 0 8 6 6 10 8 4 6 8 9 1

C1 8 0 8 6 10 10 7 9 10 6 2

C2 6 8 0 1 7 6 3 4 10 8 2

C3 6 6 1 0 9 9 3 3 1 7 7

C4 10 10 7 9 0 7 6 3 7 4 3

C5 8 10 6 9 7 0 8 2 6 4 4

C6 4 7 3 3 6 8 0 4 6 2 4

C7 6 9 4 3 3 2 4 0 7 3 10

C8 8 10 10 1 7 6 6 7 0 7 3

C9 9 6 8 7 4 4 2 3 7 0 8

C10 1 2 2 7 3 4 4 10 3 8 0

Tabela 2. Distâncias (em KM) entre a distribuidora e os diferentes clientes.

Como exemplo, a avaliação da solução A – considerando que ela sofreu mutação e

agora sua estrutura é aquela obtida após o processo de mutação – poderia ser feita a

partir da soma das distâncias entre cada um dos clientes e a distribuidora, conforme

Figura 5.

Figura 5. Cálculo da distância de uma solução.

Seguindo o mesmo processo, o valor para cada uma das soluções da população está

representado na Figura 6.

Figura 6. Qualidade de cada solução da população.

Page 11: Java Magazine - Algoritmos Genéticos com Java

A próxima etapa envolve a seleção das soluções que deverão seguir para a próxima

geração. Isto pode ser feito de várias formas (veja o quadro “Seleção”). Uma delas

seria selecionar apenas os dois indivíduos melhores, que, no exemplo apresentado,

corresponderiam às soluções C e D – aquelas que possuem a maior qualidade (menor

distância).

Em seguida, deve-se verificar se as etapas do algoritmo devem ser repetidas,

definindo-se, por exemplo, um número máximo de execuções do algoritmo ou mesmo

um critério de encontro de uma solução aceitável – por exemplo, uma solução com

percurso inferior a 60 é aceitável. Caso o algoritmo deva continuar, as etapas, a partir da

reprodução, são repetidas, até que o critério de parada seja atendido.

O algoritmo apresentado, embora não possa garantir que a melhor solução seja

encontrada, tem trazido excelentes resultados em diferentes áreas de aplicação.

A seguir, será visto como este problema poderia ser implementado em Java com a

biblioteca JGAP.

Reprodução O processo de reprodução pode ser simulado através da criação de cópias

simples ou da combinação de soluções através de operadores genéticos de cruzamento/recombinação. A escolha do operador genético que será empregado em um AG é geralmente baseada em dados obtidos empiricamente em trabalhos anteriores ou em testes com diferentes operadores e na análise dos resultados.

Operadores Genéticos de Cruzamento

Os operadores genéticos de cruzamento criam novas soluções a partir da combinação de duas ou mais soluções existentes. Existem diversos operadores disponíveis, tais como: cruzamento de um ponto (1PX – one-point crossover), dois pontos (2PX – two-point crossover) e uniforme (UX – uniform crossover). Nos exemplos apresentados a seguir, são assumidas estruturas de soluções

que aceitam valores binários. No operador 1PX, é sorteado um número inteiro p entre 0 e ng, onde ng é igual

ao número de genes do cromossomo das soluções pais. O primeiro filho recebe todos os genes de 1 até p do primeiro pai e todos os genes de p+1 até ng do segundo pai. Com o segundo filho, ocorre o inverso (Figura Q1).

Pai 1 1 0 1 0 1

x.

Pai 2 0 1 1 1 1

Pai 1

1o filho 1 0 1 1 1

Pai 2

Pai 1

2o filho 0 1 1 0 1

Pai 2

Figura Q1. Exemplo de reprodução com um ponto de corte utilizada para gerar dois filhos.

O operador 2PX difere-se do 1PX apenas pelo sorteio de dois pontos, ao invés de apenas um (Figura Q2). Pai 1

(P1) 1 0 1 0 1

x.

Pai 2

(P2) 0 1 1 1 1

P1 P1

1o filho 1 0 1 1 1

P2

P1

2ofilho 0 1 1 0 1

P2 P2

Figura Q2. Exemplo de reprodução com dois pontos de corte utilizada para gerar dois filhos.

Page 12: Java Magazine - Algoritmos Genéticos com Java

No operador UX, cada gene do cromossomo das soluções filhas é definido a partir de um sorteio, que determina de qual das soluções pais o gene será herdado. Este sorteio pode ser implementado, por exemplo, através da geração de um número pseudoaleatório inteiro igual a 0 ou 1. Caso o número seja 0, a solução filha herdará o gene de uma das soluções pais. Caso contrário, herdará o gene da outra (Figura Q3).

Parente 1 (P1)

1 0 1 0 1

x.

Parente

2 (P2) 0 1 1 1 1

P1

P1

P1

1ofilho 1 1 1 0 1

P

2

P

2

P1

2ofilho 0 1 1 1 1

P

2

P

2

P

2

P

2

Figura Q3. Exemplo de reprodução uniforme utilizada para gerar dois filhos.

Mutação O processo de mutação pode ser simulado através da realização de pequenas

alterações na estrutura de soluções existentes. Este processo é fundamental para um AG, pois é ele que garante a diversidade genética na população de soluções.

Operadores Genéticos de Mutação

Existem diversos operadores genéticos de mutação, entre eles:

Inversão de bit: Um operador de negação (not), que inverte o valor de um gene binário do cromossomo de uma solução – se o gene for igual a 0, passa a ser 1; se for igual a 1, passa a ser 0. Este operador pode ser aplicado apenas a cromossomos com genes binários;

Uniforme: Um operador que substitui o valor de um dos genes do cromossomo por um valor válido qualquer.

Para alguns tipos de algoritmos, pode ser necessária a adoção de um operador de mutação diferente: como a inversão da posição de dois genes do cromossomo, conforme exemplo apresentado no artigo.

Seleção

Operadores Genéticos de Seleção Natural

Os principais operadores para seleção de indivíduos são: roleta, porcentagem superior, melhor e aleatória: 1. Método da roleta: Neste operador, é criada uma roleta com várias seções,

uma para cada solução que pode ser selecionada. O tamanho da seção é proporcional a qualidade da solução. Em seguida, é simulado o “giro” da roleta e a solução para a qual a roleta apontar é selecionada;

2. Porcentagem superior n%: Seleciona aleatoriamente um indivíduo dos melhores n% da população;

3. Melhor: Seleciona o melhor indivíduo da população; 4. Aleatória: Seleciona aleatoriamente um indivíduo da população.

Bibliotecas Java para Algoritmos Genéticos A implementação de algoritmos genéticos em Java pode ser feita através de diversas

bibliotecas, como JGAP (Java Genetic Algorithms Package), GA Playground, JAGA

Page 13: Java Magazine - Algoritmos Genéticos com Java

(Java API for Genetic Algorithms), GAJIT (Genetic Algorithm Java Implementation

Toolkit), Jenetics, JGAL, jmona, entra outras.

Destas bibliotecas, a JGAP apresenta a vantagem de continuar sendo atualizada e

possuir um fórum e comunidade ativos através do qual é possível tirar dúvidas. A

biblioteca jmona também tem sido constantemente atualizada, mas é nova e, portanto,

não atingiu a maturidade da JGAP. Por esse motivo, os exemplos apresentados neste

trabalho utilizarão a biblioteca JGAP.

JGAP

Para utilizar a biblioteca JGAP, deve-se copiar uma de suas distribuições do site

http://jgap.sourceforge.net/. Até a publicação deste artigo, a versão mais recente da

biblioteca era a 3.4.4. Para copiar essa versão, no site do projeto, selecione a opção

Download para obter o arquivo jgap_3.4.4_full.zip.

Neste arquivo, estão disponíveis o código-fonte, a documentação e o arquivo da

biblioteca já compilada – jgap.jar –, que deve ser importado em projetos que utilizam a

biblioteca.

Figura 7. Classes da biblioteca JGAP para a definição de cromossomos e genes

A classe central da biblioteca JGAP, que representa um algoritmo genético, é a

Configuration (Figura 7). Através dela, é possível parametrizar diversas características de um algoritmo, como a forma de representação dos cromossomos

(setSampleChromosome()), a função de avaliação (setFitnessFunction() e

setFitnessEvaluator()), os operadores genéticos que serão utilizados

(addNaturalSelector() e addGeneticOperator()), o tamanho da população

(setPopulationSize()), entre outras.

Page 14: Java Magazine - Algoritmos Genéticos com Java

As soluções e seus cromossomos são representados na biblioteca através de classes que

implementam a interface IChromosome. A classe BaseChromosome fornece uma implementação padrão para esta interface. Para vários problemas, incluindo o problema

de distribuição, a classe Chromosome, subclasse de BaseChromosome, que representa

um cromossomo de tamanho fixo, pode ser utilizada. Os cromossomos são especificados a partir da definição dos seus genes (Gene). A

biblioteca fornece a implementação de vários tipos de genes, incluindo números inteiros

(IntegerGene) ou reais (DoubleGene), verdadeiro ou falso (BooleanGene), valor binário 1

ou 0 (FixedBinaryGene), textual (StringGene), entre outros. A definição dos valores dos

genes pode ser feita através do método setAllele().

Figura 8. Classes da biblioteca JGAP para a definição de operadores genéticos e função de

avaliação.

Para definir uma função de avaliação, deve-se implementar uma subclasse de

FitnessFunction (Figura 8). Essa classe possui um método abstrato (evaluate()), que

recebe um cromossomo e retorna um número real referente a sua qualidade. A função

de avaliação, por si só, não define se um cromossomo é melhor ou pior. Ela apenas

retorna um valor numérico associado à solução. Dessa forma, é preciso definir se o

Page 15: Java Magazine - Algoritmos Genéticos com Java

cromossomo com o maior ou o menor valor – ou algum outro critério –, calculado pela

função de avaliação, tem maior qualidade.

Para isso, é necessário definir um avaliador, através da interface FitnessEvaluator. O

avaliador padrão, DefaultFitnessEvaluator, considera o indivíduo com o maior valor de

avaliação como tendo maior qualidade. Esta interface define dois métodos isFitter(), um que recebe como parâmetros dois cromossomos e outro que recebe os valores de

avaliação desses cromossomos. Estes métodos verificam se o primeiro parâmetro do

método é mais adequado que o segundo para a solução do problema.

É possível adicionar à configuração de um algoritmo vários operadores de seleção

(addNaturalSelector()), de reprodução e mutação (addGeneticOperator()). Os operadores genéticos são representados na biblioteca por classes que implementam a interface

GeneticOperator. Esta interface possui apenas um método, operate(), que recebe uma população de cromossomos – nos quais será aplicado o operador – e uma lista dos

cromossomos onde deverão ser adicionados os cromossomos resultantes da operação.

Entre os operadores de mutação implementados na biblioteca, estão os operadores de

troca de genes SwappingMutationOperator e RangedSwappingMutationOperator. Entre os operadores de reprodução, está o CrossoverOperator, que implementa o

operador 1PX. A classe GreedyCrossover, por sua vez, implementa um operador que pode ser aplicado para problemas nos quais o que altera no cromossomo é apenas a

ordem dos genes, ou seja, não há repetição nos valores de genes – como o cromossomo

do problema de distribuição, no qual o mesmo cliente não aparece duas vezes.

Este operador foi implementado de forma a realizar n/2 crossovers, onde n

corresponde ao número de soluções na população, e pode ser melhor explicado a partir

do exemplo do problema de distribuição. Para cada crossover, após sortear dois

cromossomos aleatoriamente, o operador seleciona o valor de um gene – um cliente –

de um dos pais e verifica qual dos dois pais possui o trecho de viagem mais curto com

origem nesse cliente, e o adiciona à sequência. Se o cliente já tiver sido adicionado à

sequência, ele adiciona o presente no outro pai, mesmo que não resulte no caminho mais

curto. Se este também já tiver sido adicionado, ele seleciona um dos clientes ainda não

selecionados aleatoriamente.

Para adicionar operadores de seleção natural, subclasses da classe abstrata

NaturalSelector podem ser utilizadas. Estas subclasses compreendem a seleção dos

melhores cromossomos (BestChromosomesSelector), roleta (WeightedRouletteSelector), porcentagem superior (ThresholdSelector), entre outros.

Finalmente, para executar o algoritmo, o método evolve(), da classe Genotype (Figura

9) pode ser executado. Esse método pode receber por parâmetro o número de gerações

ou pode ser executado passo-a-passo. Após a execução do algoritmo, o melhor

cromossomo pode ser recuperado através do método getFittestChromosome().

Figura 9. Classes da biblioteca JGAP para a solução do algoritmo genético e recuperação da melhor solução.

Implementação do Problema de Distribuição

Para o algoritmo genético do problema de distribuição, foram implementadas duas

classes, Problema (Listagem 1) e Local (Listagem 2), utilizadas na representação de

Page 16: Java Magazine - Algoritmos Genéticos com Java

problemas de distribuição com suas áreas de cobertura, localidades de entrega e

distâncias entre estas localidades.

Na definição dessas classes, foi assumido que um problema de distribuição envolve

uma área bidimensional onde estão localizados os clientes e a distribuidora. Para

representar a posição dos clientes e da distribuidora na área de entrega, foi criada a

classe Local, que possui, além de uma identificação, os atributos x e y, que representam as coordenadas do local em relação à área.

Além desses dados, a classe Local possui um método (calcularDistanciaAte()) que

calcula a distância entre dois locais. Em um problema real, essas distâncias podem ser

calculadas de diferentes formas, como através do tempo médio de viagem entre os

trechos, obtido empiricamente, ou da distância e velocidade média do entregador. Para

simplificar a codificação, a distância entre localidades foi assumida como sendo igual à

distância euclidiana entre dois pontos (Figura 10).

Figura 10. Distância euclidiana entre dois pontos.

A área do problema é especificada através dos atributos largura e altura da classe

Problema. Além desses atributos, essa classe possui uma coleção de localidades, que

representam os clientes/pontos de entrega e a localidade da distribuidora. Essas

localidades podem ser geradas dinamicamente através do método gerarLocalidades(), que gera “aleatoriamente” coordenadas (x,y) para um número especificado de

localidades.

Listagem 1. Classe que representa um problema de distribuição.

1 package quadrans.ag.distribuicao; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.Random; 6 7 // A classe Problema representa um problema de distribuição de produtos. 8 public class Problema { 9 10 // Local onde está a distribuidora. 11 Local distribuidora; 12 // Locais onde serão feitas as entregas. 13 private HashMap<Integer, Local> localidades = new HashMap<Integer, Local>(); 14 // Largura e altura da área de distribuição. 15 private int largura, altura; 16 17 // Adiciona um local ao problema. 18 public Local add(Local localidade) { 19 return(this.localidades.put(localidade.getId(), localidade)); 20 } 21 22 // Remove um local do problema. 23 public Local remove(Local localidade) { 24 return(this.localidades.remove(localidade.getId())); 25 } 26

Page 17: Java Magazine - Algoritmos Genéticos com Java

27 // Retorna um local do problema a partir da sua identificação. 28 public Local getLocalidade(Integer id) { 29 return(this.localidades.get(id)); 30 } 31 32 // Retorna todos os locais do problema. 33 public Iterator<Local> getLocalidades() { 34 return(this.localidades.values().iterator()); 35 } 36 37 // Retorna o local da distribuidora. 38 public Local getDistribuidora() { 39 return distribuidora; 40 } 41 42 // Define o local da distribuidora. 43 public void setDistribuidora(Local distribuidora) { 44 this.distribuidora = distribuidora; 45 } 46 47 // Retorna o número de locais. 48 public int countLocalidades() { 49 return(localidades.size()); 50 } 51 52 // Gera, "aleatoriamente", os locais de entrega e da distribuidora, 53 // na quantidade especificada, considerando uma área com a largura e 54 // altura definidas. 55 public void gerarLocalidades(int quantidade, int largura, int altura) { 56 // verifica se a quantidade de localidades de entrega é válido. 57 if(quantidade < 1) { 58 throw new IllegalArgumentException("A quantidade de localidades deve ser

maior que 1.");

59 } 60 if(largura<1 || altura<1) { // verifica se o tamanho da área é válido. 61 throw new IllegalArgumentException("A largura e a altura devem ser maiores

que 1.");

62 } 63 this.largura = largura; 64 this.altura = altura; 65 66 // Gera os locais de entrega e suas posições. 67 Random geradorAleatorio = new Random(); 68 for(int contador=0; contador<quantidade; contador++) { 69 // Sorteia uma posição x na área. 70 int sorteioLargura = geradorAleatorio.nextInt(largura); 71 // Sorteia uma posição y na área. 72 int sorteioAltura = geradorAleatorio.nextInt(altura); 73 // Cria um novo local. 74 Local local = new Local(contador, sorteioLargura, sorteioAltura); 75 this.add(local); 76 } 77 // Gera um local para a distribuidora. 78 int sorteioLargura = geradorAleatorio.nextInt(largura); 79 int sorteioAltura = geradorAleatorio.nextInt(altura); 80 // A identificação da distribuidora é assumida como sendo igual ao 81 // número de locais de entrega. 82 distribuidora = new Local(quantidade, sorteioLargura, sorteioAltura); 83 } 84 85 }

Listagem 2. Classe que representa um local do problema de distribuição.

1 package quadrans.ag.distribuicao; 2 3 // A classe Local representa um local em alguma área bidimensional. 4 public class Local { 5 private int id; // Identificação do local. 6 private int x; // Posição x do local. 7 private int y; // Posição y do local. 8 9 public Local(int id, int x, int y) { 10 setId(id); 11 setX(x); 12 setY(y); 13 }

Page 18: Java Magazine - Algoritmos Genéticos com Java

14 15 public int getId() { return this.id; } 16 public void setId(int id) { this.id = id; } 17 18 public int getX() { return this.x; } 19 public void setX(int x) { this.x = x; } 20 21 public int getY() { return y; } 22 public void setY(int y) { this.y = y; } 23 24 // Calcula a distância euclidiana deste local até o local 25 // recebido por parâmetro. 26 public double calculaDistanciaAte(Local destino) { 27 return(Math.sqrt(Math.pow(getX()-destino.getX(),2) + Math.pow(getY()-

destino.getY(),2)));

28 } 29 }

Após a definição do problema, a implementação do código que utiliza a biblioteca

JGAP foi realizada (Listagem 3). Após criar uma nova configuração, o método

setSampleChromosome() foi utilizado para definir um cromossomo modelo para o problema, que será utilizado na criação dos outros cromossomos da população.

Para o exemplo apresentado, foi criado um cromossomo modelo que possui o número

de genes igual ao número de locais de entrega (linhas 4 a 9 da Listagem 3). Para a

definição desse cromossomo, criou-se um vetor com dez genes, cujos valores podem ser

iguais a qualquer número inteiro entre 1 e 10 – representando cada uma das localidades.

O valor dos genes no cromossomo modelo foi definido como sendo igual a posição do

gene no vetor do cromossomo, ou seja, o primeiro gene teve valor igual a 1, o segundo

igual a 2 etc. (Figura 11).

Figura 11. Cromossomo modelo.

Após a definição do cromossomo modelo, foi definido o tamanho inicial da população

como sendo igual a 500 e também a função que irá avaliar a utilidade dos cromossomos.

Foi criada uma classe chamada FuncaoAvaliacao (Listagem 4), que retorna a distância do percurso de entrega como valor de avaliação do cromossomo. Para definir um

avaliador adequado, foi criada a classe ComparadorUtilidade (Listagem 5) que

implementa a interface FitnessEvaluator. No código implementado, o primeiro parâmetro é mais adequado quando seu valor é menor.

Em seguida, foram definidos os operadores genéticos que devem ser empregados no

algoritmo: GreedyCrossover, BestChromosomesSelector e SwappingMutation. Ao ser adicionado à configuração, o operador de seleção teve sua execução programada para

depois dos outros operadores genéticos. Isso foi especificado através do segundo

parâmetro do método addNaturalSelector() da classe Configuration, que, quando definido como falso, determina a execução da seleção após os outros operadores.

Foi também definido um gerenciador de eventos, que pode ser utilizado como um

observador de eventos do algoritmo genético – sendo informado, por exemplo, quando

uma nova melhor solução for encontrada.

Antes de iniciar a execução do algoritmo, os cromossomos da população foram

criados. Isto pode ser feito através do método randomInitialGenotype() da classe

Genotype, que gera aleatoriamente cromossomos com genes cujos valores são sorteados

entre os valores válidos – entre 1 e 10. Porém, este método não garante que não

existirão genes com valores repetidos no cromossomo – o que acarretaria em

Page 19: Java Magazine - Algoritmos Genéticos com Java

cromossomos inválidos para o problema de distribuição, já que o mesmo cliente não

deve ser visitado duas vezes.

Portanto, para o exemplo citado, foram criados 500 cromossomos iguais ao

cromossomo modelo (linhas 29 a 37). Por fim, o algoritmo foi executado através do

método evolve() da classe Genotype.

Listagem 3. Configuração dos parâmetros de um algoritmo genético.

1 Configuration configuracao = new Configuration(); 2 3 // Define os genes do cromossomo modelo. 4 Gene[] genes = new Gene[problema.countLocalidades()]; 5 for(int contador=0; contador<genes.length; contador++) { 6 genes[contador] = new IntegerGene(configuracao, 0, problema.countLocalidades()); 7 genes[contador].setAllele(new Integer(contador)); 8 } 9 configuracao.setSampleChromosome(new Chromosome(configuracao, genes)); 10 11 // Define o tamanho da população como 500. 12 configuracao.setPopulationSize(500); 13 14 // Cria uma função para avaliar a utilidade dos cromossomos. 15 FuncaoAvaliacao funcaoAvaliacao = new FuncaoAvaliacao(problema); 16 configuracao.setFitnessFunction(funcaoAvaliacao); 17 configuracao.setFitnessEvaluator(new ComparadorUtilidade()); 18 19 // Define os operadores do algoritmo. 20 BestChromosomesSelector opSelecaoMelhor = new BestChromosomesSelector(configuracao); 21 configuracao.addNaturalSelector(opSelecaoMelhor, false); 22 configuracao.addGeneticOperator(new GreedyCrossover(configuracao)); 23 configuracao.addGeneticOperator(new SwappingMutationOperator(configuracao, 5)); 24 configuracao.setRandomGenerator(new StockRandomGenerator()); 25 configuracao.setEventManager(new EventManager()); 26 27 // Cria a população de cromossomos. 28 IChromosome[] cromossomos = new IChromosome[configuracao.getPopulationSize()]; 29 Gene[] genesModelos = configuracao.getSampleChromosome().getGenes(); 30 for (int contador=0; contador<cromossomos.length; contador++) { 31 Gene[] genes = new Gene[genesModelos.length]; 32 for(int contador1=0; contador1<genes.length; contador1++) { 33 genes[contador1] = genesModelos[contador1].newGene(); 34 genes[contador1].setAllele(genesModelos[contador1].getAllele()); 35 } 36 cromossomos[contador] = new Chromosome(configuracao, genes); 37 } 38 39 Genotype populacao = new Genotype(configuracao, new Population(configuracao,

cromossomos));

40 IChromosome melhor = null; 41 populacao.evolve(500); 42 melhor = populacao.getFittestChromosome();

Listagem 4. Função de avaliação.

1 package quadrans.ag.distribuicao; 2 3 import org.jgap.FitnessFunction; 4 import org.jgap.IChromosome; 5 6 /** 7 * A classe FuncaoAvaliacao implementa uma função de avaliação para o 8 * problema de distribuição de produtos. 9 * @author Leandro Luque 10 */ 11 public class FuncaoAvaliacao extends FitnessFunction { 12 13 /** 14 * O problema para o qual a função de avaliar soluções. 15 */ 16 private Problema problema; 17 18 /** 19 * Cria uma nova função de avaliação especificando o problema para o

Page 20: Java Magazine - Algoritmos Genéticos com Java

20 * qual as soluções deverão ser avaliadas. 21 * @param problema o problema para o qual a função de avaliar soluções. 22 */ 23 public FuncaoAvaliacao(Problema problema) { 24 this.problema = problema; 25 } 26 27 /** 28 * Avalia o cromossomo recebido por parâmetro. Esta avaliação é feita 29 * a partir do cálculo da distância total percorrida no processo de 30 * entrega para o cromossomo especificado. 31 * @param cromossomo o cromossomo que deve ser avaliado. 32 * @return o valor da avaliação do cromossomo. 33 */ 34 @Override 35 protected double evaluate(IChromosome cromossomo) { 36 double distanciaTotal = 0; 37 // Adiciona a distância da distribuidora até a primeira localidade. 38 distanciaTotal +=

problema.getDistribuidora().calculaDistanciaAte(problema.getLocalidade((Integer)crom

ossomo.getGene(0).getAllele()));

39 for(int contador=0; contador<cromossomo.getGenes().length-1; contador++) { 40 distanciaTotal +=

problema.getLocalidade((Integer)cromossomo.getGene(contador).getAllele()).calculaDis

tanciaAte(problema.getLocalidade((Integer)cromossomo.getGene(contador+1).getAllele()

));

41 } 42 // Adiciona a distância da última localidade até a distribuidora. 43 distanciaTotal +=

problema.getLocalidade((Integer)cromossomo.getGene(cromossomo.getGenes().length-

1).getAllele()).calculaDistanciaAte(problema.getDistribuidora());

44 return(distanciaTotal); 45 } 46 47 }

Listagem 5. Código do avaliador.

1 package quadrans.ag.distribuicao; 2 3 import org.jgap.FitnessEvaluator; 4 import org.jgap.IChromosome; 5 6 /** 7 * 8 * @author Internet 9 */ 10 public class ComparadorUtilidade implements FitnessEvaluator { 11 12 public boolean isFitter(double d, double d1) { 13 if(d<d1) {return true;} 14 return(false); 15 } 16 17 public boolean isFitter(IChromosome ic, IChromosome ic1) { 18 return(isFitter(ic.getFitnessValue(), ic1.getFitnessValue())); 19 } 20 21 }

Para que a execução do algoritmo possa ser acompanhada passo-a-passo, foi criada

uma interface gráfica com um JFrame e um JPanel que exibem a área e a melhor

solução, a cada etapa (Figura 12). Nesta figura, a marca preta representa a distribuidora

e as marcas azuis representam os clientes.

Page 21: Java Magazine - Algoritmos Genéticos com Java

Figura 12. Interface gráfica para acompanhamento do algoritmo.

Após apenas 20 segundos de execução do algoritmo, cuja solução inicial tinha um

percurso de 3181 metros, foi obtida uma solução de 1962 metros. A execução do

algoritmo genético por um tempo maior poderia resultar em uma solução ainda melhor.

Por tratar-se de um problema relevante para a área de Algoritmos Genéticos, a

biblioteca JGAP define uma implementação similar à apresentada para o problema de

distribuição através das classes do pacote org.jgap.impl.salesman.

Conclusão Embora não garantam que a melhor solução para um problema seja encontrada,

Algoritmos Genéticos constituem uma importante ferramenta para a solução de

problemas complexos e têm sido aplicados com sucesso em diferentes áreas. Apesar de

tratar-se de uma área de conhecimento ampla, os conceitos da AGs e as funcionalidades

da biblioteca JGAP apresentados neste artigo já possibilitam o emprego dessa família de

algoritmos a uma ampla gama de aplicações.

Page 22: Java Magazine - Algoritmos Genéticos com Java

Referências http://www.scielo.br/scielo.php?pid=S0104-530X2009000300007&script=sci_arttext Artigo sobre algumas técnicas que podem ser empregadas na análise de risco de crédito.

http://www.algoritmosgeneticos.com.br Site com materiais sobre o livro “Algoritmos Genéticos”, de Ricardo Linden.

http://www.obitko.com/tutorials/genetic-algorithms/portuguese/ Material sobre Algoritmos Genéticos definição de conceitos e exemplos.

http://www.ridelust.com/evolve-a-car-with-a-genetic-algorithm/ Exemplo de algoritmo genético que evolui um carro, procurando a estrutura melhor adaptada a diferentes

tipos de terreno.

http://math.hws.edu/xJava/GA/ Exemplo de algoritmo genético para a evolução de uma população virtual.

http://www.sambee.co.th/MazeSolver/mazega.htm

Exemplo de algoritmo genético para encontrar a saída de labirintos.

http://brainz.org/15-real-world-applications-genetic-algorithms/ Site com 15 exemplos de aplicações de Algoritmos Genéticos.

Leandro Luque ([email protected]) É professor da Faculdade de Tecnologia de Mogi das Cruzes (FATEC) e da Universidade de Mogi das Cruzes (UMC). Tem formação em Ciência da Computação e mestrado em Computação Aplicada pelo Instituto Nacional de Pesquisas Espaciais (INPE). Trabalha com Java há 10 anos, tendo atuado no desenvolvimento de aplicações de grande porte tanto no segmento empresarial quanto governamental.

Rodrigo Rocha Silva ([email protected]) Formado em Ciência da Computação pela Universidade de Mogi das Cruzes (UMC), mestre em Computação Aplicada pelo INPE e Doutorando pelo ITA. Trabalha com Java há mais de 6 anos, atuando como desenvolvedor, analista e arquiteto em empresas de pequeno e grande porte, nos segmentos de gestão empresarial, saúde e governo. Atua também como professor na UMC e na Veris Faculdades. Possui certificação SCJP 1.4.