Baixe o app para aproveitar ainda mais
Prévia do material em texto
INSTITUTO FEDERAL CATARINENSE Curso de Graduação em Engenharia de Controle e Automação Relatório do Projeto Final de Tópicos em Inteligência Artificial ALGORITMOS GENÉTICOS APLICADOS AO PROBLEMA DO CAIXEIRO VIAJANTE Autoras: XX Professor: XX Luzerna, 04 de junho de 2018 SUMÁRIO 1. INTRODUÇÃO....................................................................................................... 4 2. OBJETIVOS .......................................................................................................... 7 3. METODOLOGIA .................................................................................................... 8 4. RESULTADOS E DISCUSSÕES ....................................................................... 101 5. CONCLUSÃO .................................................................................................... 198 6. REFERÊNCIAS BIBLIOGRÁFICAS ................................................................... 209 RESUMO O presente trabalho trata da atividade avaliativa final da disciplina de Tópicos em Inteligência Artificial. Neste projeto final foi escolhido abordar sobre o problema do caixeiro viajante, aplicando os conceitos referentes à Algoritmos Genéticos. Neste relatório será enunciado o que são Algoritmos Genéticos e seus princípios e qual o problema escolhido para ser solucionado através desta técnica, além de ser exposta a metodologia utilizada para a solução do exercício, bem como os resultados obtidos na elaboração do mesmo. Palavras-chave: Algoritmos Genéticos, Caixeiro Viajante, Inteligência Artificial. 4 1. INTRODUÇÃO A Teoria da Evolução das Espécies descreve o desenvolvimento das espécies que habitavam ou habitam o planeta Terra. Desse modo, as espécies atuais descendem de outras espécies, as quais passaram por modificações ao longo do tempo e transmitiram novas características aos seus descendentes. Nessa teoria, baseiam-se os algoritmos genéticos, que são uma técnica de otimização ou uma tática de busca, estruturada e paralela, porém aleatória, que é direcionada à busca pelos pontos de maior aptidão, ou melhor, pontos onde a função a ser minimizada ou maximizada tem valores altos ou baixos. A busca aleatória para os algoritmos genéticos é direcionada, já que utilizam informações históricas afim de obter novos pontos de busca, nos quais são aguardados melhores desempenhos. Para realizar tal fato, são necessários efetuar iterações, onde cada iteração é denominada geração. Ao longo de cada processo iterativo, as noções de seleção e reprodução são empregues a uma população de candidatos capaz de variar, de acordo com a complexidade do problema e do processamento computacional. A partir da seleção é possível precisar quais indivíduos estarão aptos a se reproduzir, com uma probabilidade caracterizada pelo seu índice de aptidão. Assim, segue-se a reprodução, de forma que a população se mantenha evoluindo, até que se possa atingir um critério de parada. Os algoritmos genéticos possuem determinados conceitos, como a população, caracterizada por ser um subconjunto de todas as possibilidades de solução para certo problema, onde a diversidade necessita ser mantida, pois caso não ocorra, poderá ocasionar uma convergência prematura. Além disso, é fatídico que o tamanho ideal de uma população precisa ser definido por tentativa e erro. Uma população pode ser inicializada de forma aleatória ou de forma heurística, utilizando uma investigação conhecida para o problema. A população pode possuir o estado estacionário, onde se gera uma ou duas proles em cada iteração as quais substituem um ou dois indivíduos da população, ou geracional, em que toda a população é substituída pela nova ao fim da iteração. Outra propriedade dos algoritmos genéticos são os cromossomos. Cada cromossomo é uma das possíveis soluções para o problema. Já os genes, são uma posição ou elemento de um cromossomo, e o valor que um gene leva para um cromossomo em particular é denominado alelo. Quando há uma população no espaço computacional, afirma-se que há um genótipo. Já quando a população está contida no espaço real da solução, afirma-se que 5 existem um fenótipo. Quando ocorre uma transformação do fenótipo para o espaço de genótipos ocorre uma codificação. A Função Objetivo ou Fitness é uma função que a partir de entradas que recebe, replique a aptidão para certo indivíduo. Em outras palavras essa função toma uma solução possível como entrada e produz como saída um valor que retrata se a solução é boa ou não em relação ao problema apresentado. Os operadores genéticos, por sua vez, são operações capazes de modificar a composição genética da prole. O crossover ou cruzamento, por sua vez, é um operador de funcionamento semelhando ao da reprodução e ao crossover biológico que ocorre na natureza, aproveitando o material genético dos progenitores para a composição da prole. O resultado desta operação é um indivíduo que potencialmente possua as melhores características dos pais. Os dois principais tipos de operadores de crossover são o crossover de um ponto e o de múltiplos pontos. No crossover de um ponto, seleciona- se aleatoriamente um ponto de corte do cromossomo. Já no caso do crossover de múltiplos pontos, são realizados mais de um ponto de corte do cromossomo. Quando ocorrem pequenos ajustes aleatórios no cromossomo com a finalidade de obter uma nova solução, afirma-se que ocorreu uma mutação, a qual é demasiadamente utilizada afim de manter e introduzir diversidade na população, e normalmente é aplicada com baixa probabilidade. A mutação no algoritmo genético está intimamente ligada à verificação do espaço de busca, e é necessária para que ocorra a convergência no algoritmo genético. Outra propriedade dos algoritmos genéticos é a seleção de sobreviventes, que determina quais indivíduos devem ser eliminados e quais devem ser mantidos para uma nova geração, o que é de extrema importância pois garante que os “melhores” indivíduos não sejam expulsos da população. Para isso, o elitismo pode ser utilizado, de maneira que o indivíduo mais apto da população sempre seja prolongado para uma população futura, então o mesmo não deve ser substituído. Essa seleção pode ocorrer baseada na idade ou na função de fitness dos sobreviventes. O critério de parada de um algoritmo genético é de fiel importância, já que define quando a execução do mesmo terminará, definindo o ponto final da implementação. A utilização de algoritmos genéticos possui várias vantagens, como o fato de não requerer nenhuma informação derivativa, ser mais rápido e eficiente em comparação com os métodos tradicionais, ter bons recursos para paralelização e fornecer uma lista de boas soluções invés de não apenas uma solução. Porém, os algoritmos genéticos também possuem certas limitações, já que não são adequados para todos os problemas e é estocástico, não havendo garantias sobre a otimização ou qualidade da solução. 6 A profissão de caixeiro viajante é antiga e se trata de uma pessoa que vende produtos fora de onde os mesmos são produzidos. Antigamente, quando não havia facilidade do transporte entre cidades, os caixeiros viajantes eram a única forma de transportar produtos entre diferentes regiões fora das grandes cidades. Porém, essa profissão possuía um dilema, que era determinar a menor rota possível para percorrer uma série de cidades, visitando uma única vez cada uma delas, retornando à cidade de origem. Esse é um complexo problema deotimização inspirado na necessidade dos vendedores em realizar entregas em várias cidades, percorrendo o menor caminho possível, de forma a reduzir o tempo necessário para a viagem e os possíveis custos com transporte e combustível. 7 2. OBJETIVOS Este trabalho tem por objetivo solucionar um problema que consiste em resolver o problema do caixeiro viajante, ou seja, determinar a menor distância possível para percorrer uma série de cidades, visitando apenas uma vez cada uma delas, retornando à cidade de origem. 8 3. METODOLOGIA O problema apresentado é baseado em combinar as 15 cidades de maneira a minimizar a distância total percorrida, de forma a solucionar o problema do caixeiro viajante. Para a resolução do mesmo, criamos uma população inicial, onde cada indivíduo que também pode ser interpretado como um caminho, recebia aleatoriamente uma ordem de números de cidades, sempre verificando se não haviam cidades repetidas num mesmo caminho. A única especificação é que não hajam cidades repetidas, pois passar pela mesma cidade duas vezes seria um problema, já que estamos procurando um caminho menor possível. Cada indivíduo ou caminho então, nada mais é que uma linha e quinze colunas de uma matriz que varia de 0 a 14 e não tem números repetidos dentro. As distâncias de uma cidade à outra podem ser observadas na matriz abaixo, o elemento [2]x[5] dessa matriz é a distância da cidade 2 até a cidade 5. Com esses dados é possível calcular a distância total percorrida por aquele caminho. É por isso que a coluna diagonal principal desta matriz é 0, já que a distância de uma determinada cidade por ela mesma é zero. // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int dist[15][15]={{ 0, 29, 82, 46, 68, 52, 72, 42, 51, 55, 29, 74, 23, 72, 46},// 0 {29, 0, 55, 46, 42, 43, 43, 23, 23, 31, 41, 51, 11, 52, 21}, // 1 {82, 55, 0, 68, 46, 55, 23, 43, 41, 29, 79, 21, 64, 31, 51}, // 2 {46, 46, 68, 0, 82, 15, 72, 31, 62, 42, 21, 51, 51, 43, 64}, // 3 {68, 42, 46, 82, 0, 74, 23, 52, 21, 46, 82, 58, 46, 65, 23}, // 4 {52, 43, 55, 15, 74, 0, 61, 23, 55, 31, 33, 37, 51, 29, 59}, // 5 {72, 43, 23, 72, 23, 61, 0, 42, 23, 31, 77, 37, 51, 46, 33}, // 6 {42, 23, 43, 31, 52, 23, 42, 0, 33, 15, 37, 33, 33, 31, 37}, // 7 {51, 23, 41, 62, 21, 55, 23, 33, 0, 29, 62, 46, 29, 51, 11}, // 8 {55, 31, 29, 42, 46, 31, 31, 15, 29, 0, 51, 21, 41, 23, 37}, // 9 {29, 41, 79, 21, 82, 33, 77, 37, 62, 51, 0, 65, 42, 59, 61}, // 10 {74, 51, 21, 51, 58, 37, 37, 33, 46, 21, 65, 0, 61, 11, 55}, // 11 {23, 11, 64, 51, 46, 51, 51, 33, 29, 41, 42, 61, 0, 62, 23}, // 12 {72, 52, 31, 43, 65, 29, 46, 31, 51, 23, 59, 11, 62, 0, 59}, // 13 {46, 21, 51, 64, 23, 59, 33, 37, 11, 37, 61, 55, 23, 59, 0}};//14 Foi criada uma função especialmente para o cálculo do fitness de cada caminho, o melhor fitness é aquele que tem o menor valor, pois quanto menos forem as distâncias entre as cidades, mais curto será o caminho total. A reprodução foi feita utilizando o operador ORDER CROSSOVER, na qual os dados do pai sofrem cortes aleatórios e o filho herda os dados integralmente do pai ou da mãe, cada um dos pais gera um dos filhos apenas com cortes e inversões em seus cromossomos de posição das cidades. 9 Após a criação de novos indivíduos, sempre que acontece o cálculo do fitness é feito a comparação e é guardado aquele que possui a menor distância entre as cidades, para posteriormente ser utilizado na função do elitismo, ou seja o melhor indivíduo da geração dos pais é passado para geração dos filhos, sendo que um dos filhos é morto para que isso aconteça. A taxa de mutação utilizada neste código foi de 10%, uma boa taxa, garantindo que todos os indivíduos não sejam iguais quando a população tender para o melhor. A menor distância possível entre as cidades é de 291 km, no código implementado obteve-se uma distância mínima de 306 km, ou seja, um resultado muito próximo do melhor resultado possível, mesmo se observarmos que foi utilizado um método diferente de crossover. 10 4. RESULTADOS E DISCUSSÕES Para encontrar uma boa solução do problema do caixeiro viajante, foi utilizado software Codeblocks e onde foi desenvolvido o código de programação com a implementação de algoritmos genéticos onde se buscam indivíduos bons dentro de repetidas populações. Foram incluídas no código as bibliotecas utilizadas e definida a matriz da distância entre as cidades, e as demais matrizes e vetores utilizados em todo o código também foram definidas com seus respectivos valores fixos ou iniciais conforme parte do código: #include <stdio.h> #include <stdlib.h> #include <time.h> #define num_ind 1000 #define taxa_mutacao 10 int dist[15][15]={ { 0, 29, 82, 46, 68, 52, 72, 42, 51, 55, 29, 74, 23, 72, 46}, {29, 0, 55, 46, 42, 43, 43, 23, 23, 31, 41, 51, 11, 52, 21}, {82, 55, 0, 68, 46, 55, 23, 43, 41, 29, 79, 21, 64, 31, 51}, {46, 46, 68, 0, 82, 15, 72, 31, 62, 42, 21, 51, 51, 43, 64}, {68, 42, 46, 82, 0, 74, 23, 52, 21, 46, 82, 58, 46, 65, 23}, {52, 43, 55, 15, 74, 0, 61, 23, 55, 31, 33, 37, 51, 29, 59}, {72, 43, 23, 72, 23, 61, 0, 42, 23, 31, 77, 37, 51, 46, 33}, {42, 23, 43, 31, 52, 23, 42, 0, 33, 15, 37, 33, 33, 31, 37}, {51, 23, 41, 62, 21, 55, 23, 33, 0, 29, 62, 46, 29, 51, 11}, {55, 31, 29, 42, 46, 31, 31, 15, 29, 0, 51, 21, 41, 23, 37}, {29, 41, 79, 21, 82, 33, 77, 37, 62, 51, 0, 65, 42, 59, 61}, {74, 51, 21, 51, 58, 37, 37, 33, 46, 21, 65, 0, 61, 11, 55}, {23, 11, 64, 51, 46, 51, 51, 33, 29, 41, 42, 61, 0, 62, 23}, {72, 52, 31, 43, 65, 29, 46, 31, 51, 23, 59, 11, 62, 0, 59}, {46, 21, 51, 64, 23, 59, 33, 37, 11, 37, 61, 55, 23, 59, 0} }; int pop[num_ind][15]; int novaPopulacao[num_ind][15]; int fitness[num_ind]; int fitnessTotal; int melhor[15]; int menor = 999999; int indmenor; A função calculaFitness foi declarada com suas variáveis internas. Esta função, além de calcular o fitness armazena também o melhor indivíduo que para melhor entendimento é o melhor caminho que neste caso é o que possui o menor fitness. Dentro de uma população de x indivíduos este código percorre cada um somando as distancias e armazenando sempre que encontra um indivíduo com um menor fitness, além do seu valor de fitness (indmenor), um vetor de 15 posições (melhor[15]) recebe a ordem das cidades que possui aquele fitness: 11 int calculaFitness() { int cont=0; int n=0; int f=0; int a,b; for(cont=0;cont<num_ind;cont++) { fitness[cont]=0; for(n=0;n<15;n++) { a = pop[cont][n]; b = pop[cont][n+1]; f = dist[a][b]; fitness[cont] = f + fitness[cont]; } if (fitness[cont]<menor) { menor = fitness[cont]; indmenor = cont; melhor[0]=pop[cont][0]; melhor[1]=pop[cont][1]; melhor[2]=pop[cont][2]; melhor[3]=pop[cont][3]; melhor[4]=pop[cont][4]; melhor[5]=pop[cont][5]; melhor[6]=pop[cont][6]; melhor[7]=pop[cont][7]; melhor[8]=pop[cont][8]; melhor[9]=pop[cont][9]; melhor[10]=pop[cont][10]; melhor[11]=pop[cont][11];melhor[12]=pop[cont][12]; melhor[13]=pop[cont][13]; melhor[14]=pop[cont][14]; } } } A função inteiroAleatorio foi declarada para posterior utilização delimitando um valor máximo e gerando números inteiros e aleatórios. Esta função é utilizada para gerar uma população inicial e também para criação do pai e da mãe na reprodução: int inteiroAleatorio(int maximo) { return(rand() % (maximo+1)); } A função geraIndiv é a função que dá início a uma população do número de indivíduos declarado como num_ind anteriormente. Ela cria uma matriz onde o número de indivíduos é o número de linhas e quinze é o número de colunas e dentro destas colunas, são preenchidos aleatoriamente os valores das cidades de 0 a 14. Portanto 12 cada uma das linhas desta matriz corresponde a uma possibilidade de caminho. Esta função garante que os valores preenchidos aleatoriamente não se repitam dentro de uma mesma linha, e, portanto, não existam indivíduos deficientes: int geraIndiv() { int ind,coluna,aleat, col_1,a; for(ind=0;ind<num_ind;ind++) { for(coluna=0;coluna<15;coluna++) { col_1 = coluna-1; aleat = inteiroAleatorio(14); while(col_1>=0) { a = pop[ind][col_1]; if(aleat == a) { aleat = inteiroAleatorio(14); col_1 = (coluna - 1); } else { col_1--; } } pop[ind][coluna] = aleat; } } } Para a reprodução dos indivíduos, foi declarada a função reproducao que consiste em sortear através da função inteiroAleatorio dois indivíduos chamados pai e mãe, dentro da população já criada para gerar dois filhos da nova população. Estes filhos recebem as características do pai ou da mãe com uma ordenação diferente de genes. Para variação desta ordenação de genes, também aleatoriamente, pode utilizar um ou outro arranjo dos genes dos pais. No termino desta reprodução de novos filhos, os valores são sobrepostos a população e é chamada a função calculaFitness para obter os fitness desta nova população e verificar se existe um indivíduo melhor, ou seja, que possui um menor fitness que o armazenado: void reproducao() { int xp, xm, yp, ym, colp, colm, c; int pai[15]; int mae[15]; int conta=0; int conta2=0; while(conta<num_ind) { 13 xp = inteiroAleatorio(num_ind-1); for(colp=0;colp<15;colp++) { yp = pop[xp][colp]; pai[colp] = yp; } xm = inteiroAleatorio(num_ind-1); for(colm=0;colm<15;colm++) { ym = pop[xm][colm]; mae[colm] = ym; } c = inteiroAleatorio(10); if(c<5) { //Filho 1 novaPopulacao[conta][0]=pai[4]; novaPopulacao[conta][1]=pai[5]; // filho 1 gene 1 novaPopulacao[conta][2]=pai[6]; // filho 1 gene 2 novaPopulacao[conta][3]=pai[7]; // filho 1 gene 3 novaPopulacao[conta][4]=pai[8]; // filho 1 gene 4 novaPopulacao[conta][5]=pai[0]; // filho 1 gene 5 novaPopulacao[conta][6]=pai[1]; novaPopulacao[conta][7]=pai[2]; // filho 1 gene 1 novaPopulacao[conta][8]=pai[3]; // filho 1 gene 2 novaPopulacao[conta][9]=pai[9]; // filho 1 gene 3 novaPopulacao[conta][10]=pai[10]; // filho 1 gene 4 novaPopulacao[conta][11]=pai[11]; // filho 1 gene 5 novaPopulacao[conta][12]=pai[12]; // filho 1 gene 3 novaPopulacao[conta][13]=pai[13]; // filho 1 gene 4 novaPopulacao[conta][14]=pai[14]; // filho 1 gene 5 //Filho 2 novaPopulacao[conta+1][0]=mae[6]; novaPopulacao[conta+1][1]=mae[7]; // filho 1 gene 1 novaPopulacao[conta+1][2]=mae[8]; // filho 1 gene 2 novaPopulacao[conta+1][3]=mae[9]; // filho 1 gene 3 novaPopulacao[conta+1][4]=mae[0]; // filho 1 gene 4 novaPopulacao[conta+1][5]=mae[1]; // filho 1 gene 5 novaPopulacao[conta+1][6]=mae[2]; novaPopulacao[conta+1][7]=mae[3]; // filho 1 gene 1 novaPopulacao[conta+1][8]=mae[4]; // filho 1 gene 2 novaPopulacao[conta+1][9]=mae[5]; // filho 1 gene 3 novaPopulacao[conta+1][10]=mae[10]; // filho 1 gene 4 novaPopulacao[conta+1][11]=mae[11]; // filho 1 gene 5 novaPopulacao[conta+1][12]=mae[12]; // filho 1 gene 3 novaPopulacao[conta+1][13]=mae[13]; // filho 1 gene 4 novaPopulacao[conta+1][14]=mae[14]; // filho 1 gene 5 conta= conta+2; } else { //Filho 1 novaPopulacao[conta][0]=pai[1]; novaPopulacao[conta][1]=pai[2]; // filho 1 gene 1 novaPopulacao[conta][2]=pai[3]; // filho 1 gene 2 novaPopulacao[conta][3]=pai[4]; // filho 1 gene 3 novaPopulacao[conta][4]=pai[5]; // filho 1 gene 4 novaPopulacao[conta][5]=pai[9]; // filho 1 gene 5 novaPopulacao[conta][6]=pai[10]; 14 novaPopulacao[conta][7]=pai[11]; // filho 1 gene 1 novaPopulacao[conta][8]=pai[12]; // filho 1 gene 2 novaPopulacao[conta][9]=pai[13]; // filho 1 gene 3 novaPopulacao[conta][10]=pai[14]; // filho 1 gene 4 novaPopulacao[conta][11]=pai[6]; // filho 1 gene 5 novaPopulacao[conta][12]=pai[7]; // filho 1 gene 3 novaPopulacao[conta][13]=pai[8]; // filho 1 gene 4 novaPopulacao[conta][14]=pai[0]; // filho 1 gene 5 //Filho 2 novaPopulacao[conta+1][0]=mae[0]; novaPopulacao[conta+1][1]=mae[1]; // filho 1 gene 1 novaPopulacao[conta+1][2]=mae[2]; // filho 1 gene 2 novaPopulacao[conta+1][3]=mae[3]; // filho 1 gene 3 novaPopulacao[conta+1][4]=mae[11]; // filho 1 gene 4 novaPopulacao[conta+1][5]=mae[12]; // filho 1 gene 5 novaPopulacao[conta+1][6]=mae[13]; novaPopulacao[conta+1][7]=mae[14]; // filho 1 gene 1 novaPopulacao[conta+1][8]=mae[4]; // filho 1 gene 2 novaPopulacao[conta+1][9]=mae[5]; // filho 1 gene 3 novaPopulacao[conta+1][10]=mae[6]; // filho 1 gene 4 novaPopulacao[conta+1][11]=mae[7]; // filho 1 gene 5 novaPopulacao[conta+1][12]=mae[8]; // filho 1 gene 3 novaPopulacao[conta+1][13]=mae[9]; // filho 1 gene 4 novaPopulacao[conta+1][14]=mae[10]; // filho 1 gene 5 conta = conta+2; } } for(conta=0; conta <num_ind ;conta++){ pop[conta][0]=novaPopulacao[conta][0]; pop[conta][1]=novaPopulacao[conta][1]; pop[conta][2]=novaPopulacao[conta][2]; pop[conta][3]=novaPopulacao[conta][3]; pop[conta][4]=novaPopulacao[conta][4]; pop[conta][5]=novaPopulacao[conta][5]; pop[conta][6]=novaPopulacao[conta][6]; pop[conta][7]=novaPopulacao[conta][7]; pop[conta][8]=novaPopulacao[conta][8]; pop[conta][9]=novaPopulacao[conta][9]; pop[conta][10]=novaPopulacao[conta][10]; pop[conta][11]=novaPopulacao[conta][11]; pop[conta][12]=novaPopulacao[conta][12]; pop[conta][13]=novaPopulacao[conta][13]; pop[conta][14]=novaPopulacao[conta][14]; } calculaFitness(); } A função de mutacao por sua vez, consiste em alterar os genes de um indivíduo escolhido aleatoriamente. No caso das cidades, como não se pode repetir cidades, foi adotado o método de trocar o conteúdo de dois genes de posição entre si, o gene fixo escolhido para trocar com algum gene aleatório é o de posição 5. Da mesma forma que as demais funções, é feito de maneira aleatória tanto a mutação em si como o gene a sofrer a mutação. Assim é sobrescrito alguns indivíduos da população e a função calculaFitness novamente é chamada com o objetivo de verificar se há um indivíduo 15 com menor fitness que o melhor obtido até o momento, ou seja, se através da mutação de alguns indivíduos, se obteve um indivíduo melhor. void mutacao() { int cont=0; int temp=0; int individuoMutante[15]; for(cont=0;cont<num_ind;cont++) { if((inteiroAleatorio(100))<taxa_mutacao) { // faz uma cópia do individuo a sofrer mutação individuoMutante[0]=pop[cont][0]; individuoMutante[1]=pop[cont][1]; individuoMutante[2]=pop[cont][2]; individuoMutante[3]=pop[cont][3]; individuoMutante[4]=pop[cont][4]; individuoMutante[5]=pop[cont][5]; individuoMutante[6]=pop[cont][6]; individuoMutante[7]=pop[cont][7]; individuoMutante[8]=pop[cont][8]; individuoMutante[9]=pop[cont][9]; individuoMutante[10]=pop[cont][10]; individuoMutante[11]=pop[cont][11]; individuoMutante[12]=pop[cont][12]; individuoMutante[13]=pop[cont][13]; individuoMutante[14]=pop[cont][14]; // verifica qual o gene a sofrer mutação temp=inteiroAleatorio(14); // 0 a 14 pois são 15 cidades // encontra um novo valor para o gene if(temp==0) { individuoMutante[0]=pop[cont][5]; individuoMutante[5]=pop[cont][0]; } if(temp==1) { individuoMutante[1]=pop[cont][5]; individuoMutante[5]=pop[cont][1]; } if(temp==2) { individuoMutante[2]=pop[cont][5]; individuoMutante[5]=pop[cont][2]; } if(temp==3) { individuoMutante[3]=pop[cont][5]; individuoMutante[5]=pop[cont][3]; } if(temp==4) { individuoMutante[4]=pop[cont][5]; individuoMutante[5]=pop[cont][4]; } if(temp==5) { 16 individuoMutante[5]=pop[cont][0]; individuoMutante[0]=pop[cont][5]; } if(temp==6) { individuoMutante[6]=pop[cont][5]; individuoMutante[5]=pop[cont][6]; } if(temp==7) { individuoMutante[7]=pop[cont][5]; individuoMutante[5]=pop[cont][7]; } if(temp==8) { individuoMutante[8]=pop[cont][5]; individuoMutante[5]=pop[cont][8]; } if(temp==9) { individuoMutante[9]=pop[cont][5]; individuoMutante[5]=pop[cont][9]; } if(temp==10) { individuoMutante[10]=pop[cont][5]; individuoMutante[5]=pop[cont][10]; } if(temp==11) { individuoMutante[11]=pop[cont][5]; individuoMutante[5]=pop[cont][11]; } if(temp==12) { individuoMutante[12]=pop[cont][5]; individuoMutante[5]=pop[cont][12]; } if(temp==13) { individuoMutante[13]=pop[cont][5]; individuoMutante[5]=pop[cont][13]; } if(temp==14) { individuoMutante[14]=pop[cont][5]; individuoMutante[5]=pop[cont][14]; } // se for válido grava o indivíduo mutante na população pop[cont][0]=individuoMutante[0]; pop[cont][1]=individuoMutante[1]; pop[cont][2]=individuoMutante[2]; pop[cont][3]=individuoMutante[3]; pop[cont][4]=individuoMutante[4]; pop[cont][5]=individuoMutante[5]; pop[cont][6]=individuoMutante[6]; pop[cont][7]=individuoMutante[7]; pop[cont][8]=individuoMutante[8]; pop[cont][9]=individuoMutante[9]; pop[cont][10]=individuoMutante[10]; 17 pop[cont][11]=individuoMutante[11]; pop[cont][12]=individuoMutante[12]; pop[cont][13]=individuoMutante[13]; pop[cont][14]=individuoMutante[14]; } } calculaFitness(); } A função de elitismo garante que o melhor indivíduo encontrado na população atual faça parte da nova população. Desta forma o melhor individuo sempre permanece com o passar das gerações de indivíduos: void elitismo() { int elemento; elemento=inteiroAleatorio(num_ind-1); pop[elemento][0]=melhor[0]; pop[elemento][1]=melhor[1]; pop[elemento][2]=melhor[2]; pop[elemento][3]=melhor[3]; pop[elemento][4]=melhor[4]; pop[elemento][5]=melhor[5]; pop[elemento][6]=melhor[6]; pop[elemento][7]=melhor[7]; pop[elemento][8]=melhor[8]; pop[elemento][9]=melhor[9]; pop[elemento][10]=melhor[10]; pop[elemento][11]=melhor[11]; pop[elemento][12]=melhor[12]; pop[elemento][13]=melhor[13]; pop[elemento][14]=melhor[14]; } Finamente a função principal inicia uma população de maneira aleatória através da função geraIndiv e calcula o fitness e encontra o melhor através da função calculaFitness. Depois gera uma nova população através da reprodução, altera alguns de seus indivíduos através da mutação e passa o melhor para a próxima geração. E assim refeito para 10000 gerações. void main() { int contGeracao=0; geraIndiv(); calculaFitness(); while(contGeracao<10000) {contGeracao++; reproducao(); mutacao(); elitismo(); } } 18 O melhor valor obtido para população com 1000 indivíduos, taxa de mutação de 10% e 10000 gerações foi de 306. 19 5. CONCLUSÃO Os Algoritmos Genéticos, conforme afirmado neste trabalho, se tratam de uma técnica de busca demasiadamente utilizada na área da ciência da computação, com a finalidade de encontrar soluções aproximadas em problemas de otimização e busca, ou seja concentram uma tática de busca estruturada e paralela, porém aleatória, a qual é direcionada à busca dos pontos de maior aptidão, onde a função a ser maximizada ou minimizada – fitness, possui valores altos ou baixos. Tal técnica de otimização, oriunda dos princípios da Teoria da Evolução das Espécies, possui uma busca aleatória direcionada, já que são utilizadas informações históricas para se obter novos pontos de busca, onde são aguardados melhores desempenhos. Para que isso seja possível, se faz necessário o uso de iterações, onde as noções de seleção e reprodução são empregues a uma população de candidatos, a qual é capaz de variar, de acordo com a complexidade do problema e do processamento computacional. A partir da seleção é possível precisar quais indivíduos estarão aptos a se reproduzir, com uma probabilidade caracterizada pelo seu índice de aptidão. Desse modo, segue-se a reprodução, de forma que a população se mantenha evoluindo, até que se possa atingir um critério de parada. A partir dos conceitos e propriedades referentes aos Algoritmos Genéticos, foi possível solucionar um exercício, acima explícito, o qual consistia em solucionar o problema do caixeiro viajante, uma antiga profissão que se trata de uma pessoa que A profissão de caixeiro viajante é antiga e se trata de uma pessoa que vende produtos fora de onde os mesmos são produzidos. Antigamente, quando não havia facilidade do transporte entre cidades, os caixeiros viajantes eram a única forma de transportar produtos entre diferentes regiões fora das grandes cidades. Porém, essa profissão possuía um dilema, que era determinar a menor rota possível para percorrer uma série de cidades, visitando uma única vez cada uma delas, retornando à cidade de origem. Esse é um complexo problema de otimização inspirado na necessidade dos vendedores em realizar entregas em várias cidades, percorrendo o menor caminho possível, de forma a reduzir o tempo necessário para a viagem e os possíveis custos com transporte e combustível. Ao findar este trabalho é importante ressaltar que o desenvolvimento do mesmo proporcionou um melhor entendimento do conteúdo abordado na disciplina de Tópicos em Inteligência Artificial, concretizando os objetivos da realização do mesmo. 20 6. REFERÊNCIAS BIBLIOGRÁFICAS [1] SILVA, Marco Aurélio. Inteligência Artificial. Disponível em: < https://brasilescola.uol.com.br/informatica/inteligencia-artificial.htm>. Acesso em: 14 de março de 2018. [2] POZO, Aurora; CAVALHEIRO, Andrea de Fátima; ISHIDA, Celso; SPINOSA, Eduardo; RODRIGUES, Ernesto Malta. João Roberto; OLIVEIRA, André Schneider. Computação Evolutiva. Disponível em: <http://www.inf.ufpr.br/aurora/tutoriais/Ceapostila.pdf>. Acesso em: 05 de abril de 2018. [3] MAGALHÃES, Lana. Teoria da Evolução. Disponível em: <https://www.todamateria.com.br/teoria-da-evolucao/>. Acesso em: 05 de abril de 2018. [4] Conteúdo ICMC USP. Algoritmos Genéticos. Disponível em:<http://conteudo.icmc.usp.br/pessoas/andre/research/genetic/>. Acesso em: 05 de abril de 2018. [5] KERSCHBAUMER, Ricardo. Algoritmos Genéticos. Disponível em:<http://professor.luzerna.ifc.edu.br/ricardo-kerschbaumer/wp- content/uploads/sites/43/2018/03/5-Algoritmos-Gen%C3%A9ticos.pdf>. Acesso em: 05 de abril de 2018. [5] KERSCHBAUMER, Ricardo. Exemplo de Algoritmos Genéticos. Disponível em:<http://professor.luzerna.ifc.edu.br/ricardo-kerschbaumer/wp content/uploads/sites/43/2018/03/Exemplo-de-Algoritmo-Gen%C3%A9tico-1.pdf>. Acesso em: 05 de abril de 2018. [6] KERSCHBAUMER, Ricardo. Exercício Algoritmos Genéticos. Disponível em:<http://professor.luzerna.ifc.edu.br/ricardo-kerschbaumer/wp- content/uploads/sites/43/2018/04/Exerc%C3%ADcio-de-algoritmos- gen%C3%A9ticos.pdf>. Acesso em: 05 de abril de 2018. [7] AUTOR DESCONHECIDO. Caixeiro Viajante. Disponível em: < https://pt.wikipedia.org/wiki/Caixeiro-viajante>. Acesso em: 20 de junho de 2018. [8] AUTOR DESCONHECIDO. Problema do Caixeiro Viajante. Disponível em: < https://pt.wikipedia.org/wiki/Problema_do_caixeiro-viajante>. Acesso em: 20 de junho de 2018. [9] SARAIVA, Filipe de Oliveira; OLIVEIRA, Antonio Costa. Uma Comparação Empírica de Operadores de Crossover para o Problema de Job Shop com Datas de Entregas. Disponível em: <http://www.abepro.org.br/biblioteca/enegep2010_TN_STO_118_772_15277.pdf>. Acesso em: 20 de junho de 2018.
Compartilhar