Генетический алгоритм - пошаговое объяснение на примере
В этой статье я собираюсь объяснить, как работает генетический алгоритм (ГА), решая очень простую задачу оптимизации. Идея этой заметки состоит в том, чтобы понять концепцию алгоритма путем пошагового решения задачи оптимизации.
Оценим оптимальные значения a и b с помощью GA, которые удовлетворяют приведенному ниже выражению.
Любая задача оптимизации начинается с целевой функции. Приведенное выше уравнение можно записать как:
Понятно, что значение функции равно 0. Эта функция является нашей целевой функцией, и цель состоит в том, чтобы оценить значения a и b таким образом, чтобы значение целевой функции было минимизировано до нуля. Весь процесс оптимизации описан ниже в виде четырех основных шагов и закодирован в R для одной итерации (или генерации).
Шаг 1
Этот шаг начинается с угадывания начальных наборов значений a и b, которые могут включать или не включать оптимальные значения. Эти наборы значений называются «хромосомами», а этап называется «инициализация популяции». Здесь популяция означает наборы a и b [a, b]. Случайная равномерная функция используется для генерации начальных значений a и b. В этой задаче оптимизации шесть наборов значений a и b генерируются между 1 и 10. Код R для генерации исходных хромосом показан ниже.
intial_popu <- NULL x <- 1 repeat { crm <- runif(2,1,10) crm <- as.integer(crm) intial_popu <- rbind(intial_popu,crm) x = x+1 if (x == 7){ break } } rownames(intial_popu) <- c('Cromosome1','Cromosome2','Cromosome3','Cromosome4','Cromosome5','Cromosome6')
print(intial_popu) ## [,1] [,2] ## Cromosome1 5 9 ## Cromosome2 9 7 ## Cromosome3 9 9 ## Cromosome4 3 3 ## Cromosome5 7 8 ## Cromosome6 8 4
Шаг 2
На этом этапе вычисляется значение целевой функции для каждой хромосомы. Значение целевой функции также называется значением пригодности. Этот шаг очень важен и называется «выбором», потому что наиболее подходящие хромосомы отбираются из популяции для последующих операций.
На основе значений пригодности выбираются более подходящие хромосомы, у которых есть возможность производить низкие значения функции приспособленности (поскольку значение нашей целевой функции должно быть равно 0), и им позволяют выжить в последующих поколениях. Некоторые хромосомы отбрасываются, поскольку они непригодны для получения низких значений приспособленности.
Одним из наиболее широко используемых методов отбора в GA является «метод колеса рулетки». Метод колеса рулетки подробно рассматривается ниже.
Метод колеса рулетки
Колесо рулетки - это круговая диаграмма, где стоимость каждого пирога выражается с точки зрения вероятности пригодности. Обратите внимание, что значение пригодности и вероятность пригодности - это два разных термина. В этой задаче оптимизации хромосома, которая дает низкое значение пригодности, имеет высокую вероятность пригодности. Вероятность приспособленности каждой хромосомы вычисляется на основе значений приспособленности. Хромосома с высокой вероятностью пригодности будет иметь больше шансов быть выбранной. Следующее выражение используется для вычисления вероятности пригодности отдельной хромосомы.
Где, FP = вероятность пригодности i -й хромосомы, Fi = значение пригодности i -й хромосомы
На этом рисунке показаны вероятности пригодности для шести разных хромосом, вычисленные с использованием приведенного выше выражения. Сумма всех вероятностей пригодности всегда должна быть равна 1.
Чтобы выбрать наиболее подходящие хромосомы, генерируются шесть случайных вероятностей (т. Е. Для генерации шести значений от 0 до 1). В качестве примера предположим, что сгенерированные шесть вероятностей таковы:
Pr 01 = 0,02 для хромосомы 1
Pr 02 = 0,13 для хромосомы 2
Pr 03 = 0,40 для хромосомы 3
Pr 04 = 0,60 для хромосомы 4
Pr 05 = 0,85 для хромосомы 5
Pr 06 = 0,96 для хромосомы 6
Выбор осуществляется на основе положения указанных выше значений вероятности на колесе рулетки, выраженных в шкале кумулятивных вероятностей пригодности. Сегменты, на которых лежат указанные выше значения, выбираются для следующего шага. Помните, что каждый сегмент представляет соответствующую ему хромосому. Для лучшего понимания колесо рулетки выпрямлено и показаны положения сгенерированных значений вероятности.
На этой иллюстрации хромосомы 1, 2,5,6 выбраны из 6 хромосом. Последовательность хромосом следует слева направо: синий - хромосома -1, оранжевый - хромосома -6. Эти хромосомы будут использоваться для выполнения операции кроссовера на следующем этапе. Наконец, новый набор хромосом:
Новая хромосома-1 = Хромосома -1
Новая хромосома-2 = Хромосома -1
Новая хромосома-3 = Хромосома -2
Новая хромосома-4 = Хромосома -2
Новая хромосома -5 = Хромосома -5
Новая хромосома-6 = Хромосома -6
Вышеуказанные новые хромосомы являются потенциальными родителями для операции кроссовера. Замечено, что хромосомы -3 и -4 были отброшены как непригодные.
Для этой задачи оптимизации ниже представлен фрагмент R-кода для выбора наиболее подходящих хромосом (новой популяции).
## Function to compute fitness fit <- function(A){ a <- A[1] b <- A[2] return(((2*a^2 + b) - 57)) } fitness <- apply(intial_popu, 1, FUN = 'fit') fitting <- 1/(fitness) probfitting <- fitting/sum(fitting)
#generate random values of fitness prob. from 0 to 1 prob_gen <- runif(6,0,1) newpopulation <- NULL for(rr in prob_gen){ sum <- 0 for(prob in probfitting){ sum <- sum + prob if(rr < sum){ bin <- prob cromosomeS <- which(probfitting == bin, arr.ind = T) if(length(cromosomeS)>1){ cromosomeS <- cromosomeS[1] } else{ cromosomeS <- cromosomeS } cromname <- paste0('Cromosome',cromosomeS[1]) newcromosome <- intial_popu[which(rownames(intial_popu) == cromname),] break } } newpopulation <- rbind(newpopulation,newcromosome,deparse.level = 2) } rownames(newpopulation) <- rownames(intial_popu)
print(newpopulation) ## [,1] [,2] ## Cromosome1 5 9 ## Cromosome2 5 9 ## Cromosome3 9 7 ## Cromosome4 5 9 ## Cromosome5 5 9 ## Cromosome6 5 9
Шаг 3
Этот шаг называется «кроссовером». На этом этапе хромосомы выражаются в виде генов. Это может быть сделано путем преобразования значений a и b в двоичные строки, что означает, что значения должны быть выражены в терминах 0 или 1. Например, двоичная форма числа 9 - [1001].
Что такое кроссовер?
Кроссовер - это «изменение одного (0 или 1) или группы генов (например, [1,0,1])», произошедшее из-за спаривания между двумя родительскими хромосомами. Новая хромосома, полученная после операции кроссовера, называется «потомством». Следующая иллюстрация объясняет процесс кроссовера. Всегда помните, что кроссовер происходит между родительскими хромосомами.
В нашей текущей задаче оптимизации хромосомы, полученные на шаге 2, записываются в двоичном виде. Например, если двоичное представление a = [1,0,0,1] и b = [1,1,1,0], то хромосома [a, b] выражается как [1,0,0, 1,1,1,1,0]. Все хромосомы преобразованы в двоичную форму и записаны в виде матрицы с 6 строками и 8 столбцами.
Здесь вступает в игру один важный параметр, который называется параметр кроссовера. Значение этого параметра находится в диапазоне от 0 до 1. Значение 0,5 указывает на то, что из шести хромосом трем хромосомам разрешено кроссинговер с образованием трех потомков.
Затем идет подбор позиций. Гены будут подвергаться кроссоверу в выбранных положениях хромосомы. Например, если хромосома [1,1,0,1,1,0,0,1] и позиция 2 (слева). Тогда значение 1 будет заменено значением хромосомы спаривания в той же позиции. Помните, что для кроссовера можно выбрать несколько позиций или сегментов.
Блок R-кода, который включает функцию преобразования целого числа в двоичные строки и операцию пересечения, представлен ниже.
##Function to convert integer to binary binary <- function(x) { i <- 0 string <- numeric(32) while(x > 0) { string[32 - i] <- x %% 2 x <- x %/% 2 i <- i + 1 } first <- match(1, string) string[first:32] }
##create binary matrix of 8 cols and 6 rows binary_popu <- matrix(NA,nrow = 6,ncol = 8) for(i in 1:nrow(newpopulation)){ x <- binary(newpopulation[i,1]) binary_popu[i,1:length(x)] <- x y <- binary(newpopulation[i,2]) binary_popu[i,5:(4+length(y))] <- y } rownames(binary_popu) <- rownames(newpopulation) cross_paramter <- 0.5 i = 1 crom_cross <- NULL while(length(crom_cross) < 3){ cross.crossover.prob <- runif(6,0,1) crom_cross <- which(cross.crossover.prob < cross_paramter,arr.ind = T) i = i + 1 } parents <- binary_popu[crom_cross,] position = 2 ##crossover position cross_parent <- parents cross_parent[1,1:position] <- parents[2,1:position] cross_parent[2,1:position] <- parents[3,1:position] cross_parent[3,1:position] <- parents[1,1:position] listofindex <- which(row.names(binary_popu) %in% row.names(cross_parent)) offSpring <- binary_popu offSpring[listofindex,] <- cross_parent
print(offSpring) ##offspring after crossover ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] ## Cromosome1 1 0 1 NA 1 0 0 1 ## Cromosome2 1 0 1 NA 1 0 0 1 ## Cromosome3 1 0 0 1 1 1 1 NA ## Cromosome4 1 0 1 NA 1 0 0 1 ## Cromosome5 1 0 1 NA 1 0 0 1 ## Cromosome6 1 0 1 NA 1 0 0 1
Шаг - 4
Этот шаг называется «мутация». Мутация - это процесс изменения значения гена, т.е. замена значения 1 на 0 и наоборот. Например, если хромосома потомства [1,0,0,1], после мутации она становится [1,1,0,1]. Здесь решено мутировать 2-е значение хромосомы потомства. Он был изменен на 1 с 0.
Теперь возникает вопрос, сколько генов нужно изменить и на каких позициях. Параметр мутации определяет, сколько генов нужно мутировать. Если параметр мутации равен 0,1 (обычно сохраняются низкие значения). Тогда 0,1-кратное общее количество генов может мутировать. В данной задаче оптимизации общее количество генов составляет 48 (6 x 8). Следовательно, допускается мутация 4,8 ~ 5 генов.
Что касается позиций мутаций, выбираются 5 случайных значений строк и столбцов. Иллюстрация процесса мутации приведена ниже.
После мутации бинарные хромосомы преобразуются в целочисленную форму и рассчитываются значения приспособленности. Если какая-либо из хромосом дает целевое значение пригодности, равное 0, мы останавливаемся на этом. В противном случае процесс будет повторяться от шага - 2 до шага - 4, приравнивая мутировавшие хромосомы к новой популяции.
Блок R-кода для этого шага представлен ниже.
mutation_paramter <- 0.09 no.of.mutations <- 4 ## Calculated as nrow(offSpring)*ncol(offSpring)*mutation_paramter randrow <- round(runif(no.of.mutations,1,nrow(offSpring))) rancol <- round(runif(no.of.mutations,1,ncol(offSpring))) ## Now get the offsprings by mutating at above position for(r in 1:length(randrow)){ if(is.na(offSpring[randrow[r],rancol[r]])){ offSpring[randrow[r],rancol[r]] <- NA }else{ if(offSpring[randrow[r],rancol[r]] == 0){ offSpring[randrow[r],rancol[r]] <- 1 }else{ offSpring[randrow[r],rancol[r]] <- 0 } } } print(offSpring) ## Chromosomes after mutation ## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] ## Cromosome1 1 0 1 NA 1 0 0 1 ## Cromosome2 1 0 1 NA 1 0 0 1 ## Cromosome3 1 0 0 1 1 1 0 NA ## Cromosome4 1 0 1 NA 0 0 0 1 ## Cromosome5 1 0 1 NA 1 0 0 1 ## Cromosome6 1 1 1 NA 1 0 0 1
## Now convert binary back to integer binary_decimal = function(base_number, base = 2) { split_base = strsplit(as.character(base_number), split = "") return(sapply(split_base, function(x) sum(as.numeric(x) * base^(rev(seq_along(x) - 1))))) } offSpring_inter <- matrix(NA,6,2) for(i in 1:nrow(offSpring)){ a <- offSpring[i,1:4] a <- na.omit(a) a <- as.numeric(paste(a, collapse = "")) a <- binary_decimal(a) b <- offSpring[i,5:8] b <- na.omit(b) b <- as.numeric(paste(b, collapse = "")) b <- binary_decimal(b) offSpring_inter[i,1] <- a offSpring_inter[i,2] <- b } rownames(offSpring_inter) <- rownames(offSpring) ## Chromosomes converted back to integer after end of 1st of generation print(offSpring_inter) ## [,1] [,2] ## Cromosome1 5 9 ## Cromosome2 5 9 ## Cromosome3 9 6 ## Cromosome4 5 1 ## Cromosome5 5 9 ## Cromosome6 7 9
Окончательный результат этой задачи оптимизации показан ниже, где значения пригодности вычислены для 10 поколений. Хромосома 6, то есть [a, b] = [5,7], является оптимальным решением, при котором значение пригодности равно 0.
## Iteration: 1 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -30 76 -17 -18 -3 -45 ## Iteration: 2 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 45 -40 -42 -30 -3 ## Iteration: 3 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -3 -3 -3 -7 -3 ## Iteration: 4 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -2 -2 -3 -3 -3 ## Iteration: 6 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -3 -3 -3 -2 -3 ## Iteration: 7 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -20 -3 -3 -3 -2 ## Iteration: 8 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -2 -3 -2 -2 -2 ## Iteration: 9 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## 46 -2 -2 -20 -2 -2 ## Iteration: 10 ## Cromosome1 Cromosome2 Cromosome3 Cromosome4 Cromosome5 Cromosome6 ## -3 -19 -1 47 -1 0
## The final set of Chromosomes ## [,1] [,2] ## Cromosome1 5 4 ## Cromosome2 4 6 ## Cromosome3 5 6 ## Cromosome4 7 6 ## Cromosome5 5 6 ## Cromosome6 5 7
Ссылка
Деб К. Многоцелевая оптимизация с использованием эволюционных алгоритмов (2005)