МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ ИМ.ПРОФ.М.А.БОНЧ-БРУЕВИЧА»
(СПбГУТ)
Факультет: «Институт магистратуры»
Кафедра: «Систем автоматизации и робототехники»
Направление подготовки: |
Автоматизация технологических процессов и производств |
Направленность (профиль): |
Интеллектуальные технологии в автоматизации |
ЛАБОРАТОРНАЯ РАБОТА № 3
по дисциплине:
Методы и модели искусственного интеллекта в управлении техническими системами
|
на тему:
Программная реализация и экспериментальное исследование генетического алгоритма
Вариант: 10
-
Выполнили студенты группы:
дата, подпись
Фамилия И. О.
Принял к.т.н., доцент
Верхова Г.В.
дата, подпись
Фамилия И. О.
Цель работы: приобретение навыков разработки, программной реализации на C# генетических алгоритмов, а также решение их помощью задач нелинейного математического программирования.
Задание: Использую созданные в ходе выполнения предыдущей лабораторной работы алгоритмические структуры, создать генетический алгоритм, выполнить его настройку и протестировать его на решении тестовой фитнес функции. Операторы селекции выбираются согласно своему варианту.
По результатам исследований разработанного генетического алгоритма необходимо заполнить таблицы.
Ход работы
1. Оператор селекции
1.1. Блок-схема оператора селекции
Рис.1 — Блок-схема оператора селекции (турнирный отбор)
1.2. Исходный код программной реализации оператора селекции
namespace EvolutionaryAlgorithmTemplate
{
public class Population
{
public List<Individual> Individuals { get; private set; }
public Population()
{
Individuals = new List<Individual>();
}
// Создаёт новую особь и добавляет её в популяцию
public void NewIndividual(List<double> values, double xmin, double xmax)
{
Individual ind = new Individual(values, xmin, xmax);
Individuals.Add(ind);
}
// Создаёт новую случайную особь с заданной размерностью и диапазоном рабочих параметров
public void NewRandomIndividual(int dimension, double xmin, double xmax, Random rnd)
{
List<double> values = new List<double>();
for (int i = 0; i < dimension; i++)
{
double val = xmin + rnd.NextDouble() * (xmax - xmin);
values.Add(val);
}
NewIndividual(values, xmin, xmax);
}
// Выбирает пару родителей методом турнирного отбора
// Каждый турнир состоит из 2 особей, выбранных из 70% лучших (элита),
// и с вероятностью 0.9 выбирается лучшая из них.
public (Individual, Individual) SelectPair(Random rnd)
{
if (Individuals.Count < 2)
throw new InvalidOperationException("Недостаточно особей для селекции.");
Individual parent1 = TournamentSelection(rnd, 0.9);
Individual parent2 = TournamentSelection(rnd, 0.9);
while (parent1 == parent2 && Individuals.Count > 1)
{
parent2 = TournamentSelection(rnd, 0.9);
}
return (parent1, parent2);
}
// Метод турнирного отбора: из элитного подмножества (70% лучших) случайно выбираются 2 особи.
// С вероятностью selectionProbability (0.9) возвращается лучшая, иначе – другая.
private Individual TournamentSelection(Random rnd, double selectionProbability)
{
// Сортируем особей по возрастанию фитнеса (минимизация)
List<Individual> sorted = Individuals.OrderBy(ind => ind.FitnessValue).ToList();
int eligibleCount = (int)Math.Ceiling(0.7 * sorted.Count);
if (eligibleCount < 2)
eligibleCount = sorted.Count;
// Выбираем случайным образом 2 индекса из элитного подмножества
int idx1 = rnd.Next(eligibleCount);
int idx2 = rnd.Next(eligibleCount);
while (idx2 == idx1 && eligibleCount > 1)
{
idx2 = rnd.Next(eligibleCount);
}
Individual candidate1 = sorted[idx1];
Individual candidate2 = sorted[idx2];
// Определяем лучшую особь (с меньшим значением фитнеса)
Individual bestCandidate = candidate1.FitnessValue <= candidate2.FitnessValue ? candidate1 : candidate2;
Individual otherCandidate = bestCandidate == candidate1 ? candidate2 : candidate1;
// С вероятностью selectionProbability выбираем лучшую, иначе – другую
return rnd.NextDouble() < selectionProbability ? bestCandidate : otherCandidate;
}
// Выполняет кроссинговер BLX‑a‑b двух родителей для создания двух потомков
public List<Individual> Crossingover(Individual parent1, Individual parent2, double a, double b, double xmin, double xmax, Random rnd)
{
var (childChrom1, childChrom2) = Chromosome.BLXabCrossover(parent1.Chromosome, parent2.Chromosome, a, b, xmin, xmax, rnd);
Individual child1 = new Individual(childChrom1);
Individual child2 = new Individual(childChrom2);
return new List<Individual> { child1, child2 };
}
// Очищает популяцию
public void Clean()
{
Individuals.Clear();
}
}
}
1.3. Исходный код юнит-теста оператора селекции
namespace EvolutionaryAlgorithmTemplate.Tests
{
public class PopulationTournamentSelectionTests
{
private class FakeRandom : Random
{
private Queue<int> intValues;
private Queue<double> doubleValues;
public FakeRandom(Queue<int> intValues, Queue<double> doubleValues)
{
this.intValues = intValues;
this.doubleValues = doubleValues;
}
// Переопределяем Next(int maxValue) для возврата предопределённых значений
public override int Next(int maxValue)
{
// Игнорируем maxValue, так как значения уже подготовлены для элитного подмножества
return intValues.Dequeue();
}
// Переопределяем NextDouble() для возврата предопределённых значений
public override double NextDouble()
{
return doubleValues.Dequeue();
}
}
[Fact]
public void SelectPair_ShouldReturnTournamentSelectedParents()
{
Population pop = new Population();
double xmin = -5.0, xmax = 5.0;
/* Создаем 5 особей с известными значениями генов.
Фитнес-функция (трехгорбый верблюд) достигает глобального минимума в (0,0).
Таким образом, после сортировки по возрастанию фитнеса особь с (0,0) будет лучшей.*/
pop.NewIndividual(new List<double> { 0.0, 0.0 }, xmin, xmax); // Особь A, фитнес = 0.0 (лучший)
pop.NewIndividual(new List<double> { 1.0, 0.0 }, xmin, xmax); // Особь B, фитнес = 1.11667
pop.NewIndividual(new List<double> { 2.0, 0.0 }, xmin, xmax); // Особь C, фитнес = 1.86667
pop.NewIndividual(new List<double> { 3.0, 0.0 }, xmin, xmax); // Особь D, фитнес значительно выше
pop.NewIndividual(new List<double> { 4.0, 0.0 }, xmin, xmax); // Особь E, фитнес очень высокий
/* После сортировки по фитнесу (OrderBy(ind => ind.FitnessValue)) элитное подмножество (70% от 5, округляем вверх) содержит 4 особи:
элита[0] = A, элита[1] = B, элита[2] = C, элита[3] = D
Для первого турнира:
rnd.Next(eligibleCount) вернет 1 candidate1 = элита[1] (особь B)
rnd.Next(eligibleCount) вернет 3 candidate2 = элита[3] (особь D)
Из них лучшей является особь B (так как её фитнес ниже).
rnd.NextDouble() вернет 0.5 (< 0.9)выбираем лучшую, то есть особь B
Для второго турнира:
rnd.Next(eligibleCount) вернет 0 candidate1 = элита[0] (особь A)
rnd.Next(eligibleCount) вернет 2 candidate2 = элита[2] (особь C)
Лучшей является особь A.
rnd.NextDouble() вернет 0.8 (< 0.9)выбираем лучшую, то есть особь A
Ожидаемая пара: (B, A)*/
Queue<int> intQueue = new Queue<int>(new List<int> { 1, 3, 0, 2 });
Queue<double> doubleQueue = new Queue<double>(new List<double> { 0.5, 0.8 });
FakeRandom fakeRandom = new FakeRandom(intQueue, doubleQueue);
// выбор пары методом турнирного отбора
var (parent1, parent2) = pop.SelectPair(fakeRandom);
// Проверяем, что родитель 1 – особь B с генами [1.0, 0.0]
Assert.Equal(1.0, parent1.Chromosome.Gens[0].Value, 5);
Assert.Equal(0.0, parent1.Chromosome.Gens[1].Value, 5);
// Проверяем, что родитель 2 – особь A с генами [0.0, 0.0]
Assert.Equal(0.0, parent2.Chromosome.Gens[0].Value, 5);
Assert.Equal(0.0, parent2.Chromosome.Gens[1].Value, 5);
}
}
}
1.4. Результаты выполнения юнит-теста оператора селекции
Рис.2 — Результаты выполнения юнит-теста оператора селекции (турнирный отбор)
2. Генетический алгоритм
2.1. Блок-схема генетического алгоритма
Рис.3 — Блок-схема генетического алгоритма
2.2. Исходный код генетического алгоритма
namespace EvolutionaryAlgorithmApp
{
class Program
{
static void Main(string[] args)
{
// Параметры задачи
int dimension = 2; // Число рабочих параметров (генов)
double xmin = -5.0; // Нижняя граница допустимых значений
double xmax = 5.0; // Верхняя граница допустимых значений
int populationSize = 5000; // Число особей в популяции
// Параметры мутации
double Pmin = 0.005; // Минимальная вероятность мутации
double Pmax = 0.1; // Максимальная вероятность мутации
double mutationProbability = 0.1; // Средняя вероятность мутации (Pmax - Pmin) / 2
// Параметры оператора кроссинговера BLX‑a‑b
double crossoverA = 0.3;
double crossoverB = 0.7;
// Параметры остановки ГА
double fitnessThreshold = 0.001; // Если лучшая фитнес-функция меньше порога, останавливаем
int maxGenerations = 100; // Максимальное число поколений для одного запуска
int numberOfRuns = 10; // Число запусков ГА для статистики
List<int> generationCounts = new List<int>();
List<double> runTimes = new List<double>();
List<double> bestFitnessValues = new List<double>();
// Проведение нескольких запусков ГА
for (int run = 0; run < numberOfRuns; run++)
{
EvolutionManager manager = new EvolutionManager(dimension, xmin, xmax, mutationProbability, crossoverA, crossoverB);
manager.InitPopulation(populationSize);
int generationCount = 0;
Stopwatch stopwatch = Stopwatch.StartNew();
double bestFitness = double.MaxValue;
// Эволюционный цикл: создаём новое поколение до достижения порога или maxGenerations
while (generationCount < maxGenerations)
{
bestFitness = GetBestFitness(manager.Populations[manager.Populations.Count - 1]);
if (bestFitness < fitnessThreshold)
break;
manager.CreateNewGeneration();
generationCount++;
}
stopwatch.Stop();
runTimes.Add(stopwatch.Elapsed.TotalSeconds);
generationCounts.Add(generationCount);
bestFitnessValues.Add(bestFitness);
}
// Вычисление статистических показателей
double overallBestFitness = double.MaxValue;
int minGenerations = int.MaxValue;
int maxGenerationsCount = int.MinValue;
double totalGenerations = 0;
double totalTime = 0;
double minTime = double.MaxValue;
double maxTime = double.MinValue;
foreach (var genCount in generationCounts)
{
totalGenerations += genCount;
if (genCount < minGenerations) minGenerations = genCount;
if (genCount > maxGenerationsCount) maxGenerationsCount = genCount;
}
foreach (var time in runTimes)
{
totalTime += time;
if (time < minTime) minTime = time;
if (time > maxTime) maxTime = time;
}
foreach (var fit in bestFitnessValues)
{
if (fit < overallBestFitness) overallBestFitness = fit;
}
double avgGenerations = totalGenerations / numberOfRuns;
double avgTime = totalTime / numberOfRuns;
// Вывод статистики в консоль
Console.WriteLine("Результаты работы генетического алгоритма:");
Console.WriteLine($"Наилучшее значение фитнес-функции: {overallBestFitness:F6}");
Console.WriteLine($"Среднее число поколений: {avgGenerations:F2}");
Console.WriteLine($"Минимальное число поколений: {minGenerations}");
Console.WriteLine($"Максимальное число поколений: {maxGenerationsCount}");
Console.WriteLine($"Среднее время работы ГА (сек): {avgTime:F6}");
Console.WriteLine($"Минимальное время работы ГА (сек): {minTime:F6}");
Console.WriteLine($"Максимальное время работы ГА (сек): {maxTime:F6}");
Console.WriteLine("\nНажмите Enter для завершения...");
Console.ReadLine();
}
// Вспомогательный метод для получения лучшего значения фитнес-функции в популяции
static double GetBestFitness(Population pop)
{
double best = double.MaxValue;
foreach (var ind in pop.Individuals)
{
if (ind.FitnessValue < best)
best = ind.FitnessValue;
}
return best;
}
}
}
