Cromossomo Simples - O que é cromossomo? - Brasil Escola
O que é cromossomo? - Brasil Escola

Entendendo cromossomo simples na prática

A maioria dos tutoriais sobre algoritmos genéticos começa com teoria demais e código de menos. Vou direto ao ponto. Um cromossomo simples é basicamente um vetor — uma lista de genes que representa uma solução candidata para o problema que você está tentando resolver. Em termos mais concretos, se você está otimizando algo como a temperatura de um processo industrial, cada gene do cromossomo pode ser um parâmetro, e o conjunto forma uma combinação que será testada, avaliada e possivelmente selecionada para reprodução.

O que é cromossomo simples e como implementá-lo

O formato mais comum é um array de números, seja float ou binário. Vamos falar de float primeiro porque é mais intuitivo e mais usado em problemas do mundo real. Cada posição no array corresponde a uma variável de decisão. Se você tem três variáveis — digamos, pressão, temperatura e vazão — seu cromossomo vai ter três genes. O algoritmo genético vai gerar uma população inicial de cromossomos aleatórios dentro dos limites definidos para cada variável, avaliar cada um usando sua função fitness, selecionar os melhores, aplicar cruzamento e mutação, e repetir até convergir ou atingir o número máximo de gerações.

O código em Python para criar um indivíduo básico leva menos de dez linhas: ```python
import random

def gerar_cromossomo(n_genes, limite_inferior, limite_superior):
return [random.uniform(limite_inferior, limite_superior) for _ in range(n_genes)]
```

Isso é tudo que existe no fundo. O resto é lógica de seleção e operadores genéticos.

Cruzamento e mutação no cromossomo simples

O cruzamento combina dois pais para gerar filhos. No formato real-valued, o método mais comum é o crossover aritmético, que faz uma média ponderada dos genes dos pais. Outra opção é o BLX-, que cria um intervalo levemente expandido além dos valores parentais. Para problemas contínuos, o aritmético funciona bem na maioria das vezes. A mutação é onde as pessoas erram com mais frequência. O erro clássico é usar uma taxa de mutação fixa muito baixa, como 0.01 por gene, e depois se perguntar por que o algoritmo converge prematuramente. A resposta é que a diversidade genética se esgota rápido demais. Uma taxa entre 0.1 e 0.2 por gene, combinada com uma magnitude de mutação adaptativa, costuma funcionar melhor.

Um detalhe que poucos mencionam: se suas variáveis têm escalas muito diferentes — uma varia entre 0 e 1, outra entre 0 e 1000 — a mutação Gaussiana padrão vai tratar todas igualmente em termos de desvio padrão, o que significa que a variável de escala maior receberá mutações drasticamente maiores. A normalização dos genes antes de aplicar o operador de mutação resolve isso, mas é uma etapa que quase todo tutoriais pulam.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Problemas comuns que eu enfrentei na prática

Uma vez tive um projeto onde o cromossomo simples parecia não convergir para nenhuma solução aceitável, mesmo com milhares de gerações. O problema era que a função fitness tinha um platô enorme — uma região onde pequenas alterações nos genes não geravam diferença significativa no score. O algoritmo girava em círculos porque não havia pressão seletiva suficiente para empurrar a população para fora daquela região. A solução foi adicionar um termo de exploração forçada: a cada cinquenta gerações, eu substituía 20% da população por indivíduos completamente aleatórios. Isso quebrou o platô e o algoritmo encontrou a região ótima em cerca de duzentas gerações a mais do que o esperado, mas encontrou. Sem esse resgate periódico, eu provavelmente teria parado a execução e trocado a abordagem.

Outro ponto que vale destacar: cromossomo simples não é adequado para todos os tipos de problema. Se sua solução envolve permutações — como o problema do caixeiro-viajante — usar um array de floats e operadores de crossover padrão vai gerar soluções inválidas na maioria das vezes. Nesses casos, você precisa de representações e operadores específicos, como order crossover ou cycle crossover. Tentar forçar um cromossomo simples em problemas combatórios é uma das causas mais comuns de frustração nessa área.

Vantagens e limitações

A principal vantagem do cromossomo simples é a facilidade de implementação e a compatibilidade com uma ampla gama de problemas contínuos. Você não precisa se preocupar com codificações complexas ou com a integridade estrutural das soluções geradas. Os operadores são diretos e o espaço de busca é bem compreendido. As limitações são reais. Para problemas com variáveis discretas ou híbridas, o formato simples exige ajustes manuais que podem introduzir viés. A representação não carrega nenhuma informação estrutural sobre as relações entre genes — se duas variáveis são altamente correlacionadas, o algoritmo não sabe disso automaticamente. Você precisa explicitamente capturar essas dependências, seja via função fitness, seja com operadores de crossover especializados.

Em problemas de alta dimensionalidade, acima de cinquenta variáveis, a convergência tende a ficar lenta porque o espaço de busca cresce exponencialmente. Nesse cenário, é mais eficiente combinar o algoritmo genético com métodos locais de busca, como gradiente, para refinar as melhores soluções em vez de depender exclusivamente da evolução. Essa abordagem híbrida geralmente reduz o tempo de execução pela metade em comparação com o uso isolado do algoritmo genético.

Implementação mínima funcional

Aqui está um esqueleto completo que você pode rodar e adaptar: ```python
import random

n_genes = 5
pop_size = 100
limites = [(-10, 10)] * n_genes
geracoes_maximas = 500
taxa_cruzamento = 0.8
taxa_mutacao = 0.15

def fitness(cromossomo):
Função de teste: soma dos quadrados dos genes
return sum(g 2 for g in cromossomo)

def gerar_populacao():
return [ [
random.uniform(li, ls)
for (li, ls) in limites
] for _ in range(pop_size) ]

def cruzamento(pai, mae):
if random.random() > taxa_cruzamento:
return pai[:], mae[:]
alpha = random.random()
filho1 = [alpha * p + (1 - alpha) * m for p, m in zip(pai, mae)]
filho2 = [alpha * m + (1 - alpha) * p for p, m in zip(pai, mae)]
return filho1, filho2

def mutacao(individuo):
for i in range(len(individuo)):
if random.random() < taxa_mutacao:
li, ls = limites[i]
individuo[i] += random.gauss(0, (ls - li) * 0.05)
individuo[i] = max(li, min(ls, individuo[i]))
return individuo

pop = gerar_populacao()
melhor = None

for geraçao in range(geracoes_maximas):
avaliados = [(fitness(i), i) for i in pop]
avaliados.sort(key=lambda x: x[0])
melhor = avaliados[0]
nova_pop = []
for _ in range(pop_size // 2):
p1 = random.choices(avaliados, weights=[1 / (r + 1) for r, _ in avaliados], k=2)< br> f1, f2 = cruzamento(p1[0][1], p1[1][1])
nova_pop.append(mutacao(f1))
nova_pop.append(mutacao(f2))
pop = nova_pop[:pop_size]

print("Melhor solução:", melhor)
```

Esse exemplo usa a função soma dos quadrados como fitness, que é um benchmark padrão. Substitua pela sua função de avaliação e ajuste os parâmetros conforme a escala do seu problema. Os valores apresentados são pontos de partida razoáveis, não prescrições. Cada problema exige calibragem própria.

Quando evitar cromossomo simples

Se o seu problema tem restrições de integridade estrutural — sequência obrigatória de tarefas, grafos conectados, árvores válidas — o cromossomo simples vai gerar muita inviabilidade. Nesse caso, considere representar a solução como grafo, árvore ou sequência permutacional, e usar operadores que respeitem a estrutura desde o início. O esforço extra de implementação paga dividendos rápidos em qualidade de solução. Para otimização multiobjetivo com três ou mais objetivos, cromossomo simples funciona, mas você vai precisar de um mecanismo de seleção não-dominada, como NSGA-II. A lógica básica permanece a mesma, mas a pressão seletiva muda radicalmente quando há trade-offs entre objetivos conflitantes.