پادشاهِ کُدنویسا شو!
کینگتو - آموزش برنامه نویسی تخصصصی - دات نت - سی شارپ - بانک اطلاعاتی و امنیت

پیاده سازی الگورتیم ژنتیک با سی شارپ

4 بازدید 0 نظر ۱۴۰۵/۰۴/۰۱
در این مقاله تخصصی، معماری، اصول پیاده‌سازی و بهینه‌سازی یک موتور الگوریتم ژنتیک را از صفر تا صد بررسی خواهیم کرد. هدف این است که یک ساختار شیءگرا، تمیز (Clean Code) و با کارایی بالا (High Performance) طراحی کنیم که فراتر از یک مثال آکادمیک ساده باشد.

مقدمه: چرا الگوریتم ژنتیک و چرا #C؟

الگوریتم ژنتیک یک تکنیک بهینه‌سازی و جستجو است که از نظریه تکامل طبیعی داروین الهام گرفته شده است. این الگوریتم برای حل مسائلی که فضای پاسخ آن‌ها بسیار بزرگ است و روش‌های قطعی (Deterministic) مانند Brute Force در زمان معقول پاسخگو نیستند (مانند مسائل NP-Hard مثل مسأله فروشنده دوره‌گرد، زمان‌بندی پروژه‌ها و بهینه‌سازی توپولوژی شبکه) فوق‌العاده عمل می‌کند.

انتخاب #C و پلتفرم NET. برای پیاده‌سازی این الگوریتم مزایای زیر را به همراه دارد:

  1. پشتیبانی عالی از شیءگرایی (OOP): امکان مدل‌سازی دقیق مفاهیمی مثل کروموزوم، ژن و جمعیت.

  2. قابلیت‌های موازات (Parallelism): الگوریتم‌های ژنتیک به شدت مستعد پردازش موازی هستند. با استفاده از TPL (Task Parallel Library) در #C می‌توان محاسبات برازندگی (Fitness) را روی تمام هسته‌های پردازنده توزیع کرد.

  3. مدیریت حافظه بهینه: نسخه‌های مدرن دات‌نت با ارائه‌ی ویژگی‌هایی چون Span<T> و بهبودهای Garbage Collection، کارایی فوق‌العاده‌ای در ساختارهای داده‌ای بزرگ فراهم می‌کنند.

 

بخش اول: مفاهیم پایه‌ای و نگاشت آن به ساختار نرم‌افزاری

قبل از ورود به کدنویسی، باید واژگان زیستی را به مفاهیم شیءگرایی ترجمه کنیم:

  • ژن (Gene): کوچک‌ترین واحد سازنده پاسخ (مثلاً یک بیت، یک عدد یا یک کاراکتر).

  • کروموزوم یا کروموزوم (Chromosome): رشته‌ای از ژن‌ها که یک راه‌حل بالقوه برای مسئله را نشان می‌دهد.

  • برازندگی (Fitness): معیاری که نشان می‌دهد یک کروموزوم چقدر به پاسخ ایده‌آل نزدیک است.

  • جمعیت (Population): مجموعه‌ای از کروموزوم‌ها در یک نسل مشخص.

  • انتخاب (Selection): فرآیندی که در آن کروموزوم‌های برتر برای تولید مثل انتخاب می‌شوند.

  • ترکیب (Crossover / Recombination): ادغام ژن‌های دو والدین برای ایجاد فرزندان جدید.

  • جهش (Mutation): تغییرات تصادفی در ژن‌ها برای حفظ تنوع ژنتیکی و جلوگیری از همگرایی زودرس (Local Optima).

 

بخش دوم: طراحی معماری سیستم (Architecture & Design)

برای اینکه کد ما قابلیت بازآفرینی داشته باشد و بتوانیم از این موتور ژنتیک برای مسائل مختلف استفاده کنیم، آن را به صورت Generic و Interface-Driven طراحی می‌کنیم.

۱. تعریف اینترفیس کروموزوم

هر مسئله‌ای که بخواهد با این موتور حل شود، باید اینترفیس IChromosome را پیاده‌سازی کند:

public interface IChromosome<TGene>
{
    TGene[] Genes { get; }
    double Fitness { get; set; }
    
    void Initialize();
    IChromosome<TGene> Clone();
}

۲. تعریف اینترفیس تابع برازندگی (Fitness Function)

محاسبه میزان شایستگی هر راه‌حل کاملاً وابسته به مسئله است:

public interface IFitnessEvaluator<TGene>
{
    double Evaluate(IChromosome<TGene> chromosome);
}

 

بخش سوم: پیاده‌سازی مکانیزم‌های اصلی الگوریتم

حالا وارد پیاده‌سازی کلاس اصلی موتور الگوریتم ژنتیک (GeneticAlgorithmEngine) می‌شویم. این کلاس مسئول مدیریت چرخه تکامل است.

۱. متد اصلی چرخه تکامل (The Evolution Loop)

چرخه اصلی شامل مراحل زیر است:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class GeneticAlgorithmEngine<TGene>
{
    private List<IChromosome<TGene>> _population;
    private readonly IFitnessEvaluator<TGene> _fitnessEvaluator;
    private readonly Random _random;

    // هایپرپارامترهای الگوریتم
    public int PopulationSize { get; }
    public double MutationRate { get; }
    public double CrossoverRate { get; }
    public bool Elitism { get; }

    public int CurrentGeneration { get; private set; }
    public IChromosome<TGene> BestSolution { get; private set; }

    public GeneticAlgorithmEngine(
        int populationSize, 
        double mutationRate, 
        double crossoverRate, 
        bool elitism,
        IFitnessEvaluator<TGene> fitnessEvaluator)
    {
        PopulationSize = populationSize;
        MutationRate = mutationRate;
        CrossoverRate = crossoverRate;
        Elitism = elitism;
        _fitnessEvaluator = fitnessEvaluator;
        _random = new Random();
        _population = new List<IChromosome<TGene>>();
    }

    public void Initialize(Func<IChromosome<TGene>> chromosomeFactory)
    {
        _population.Clear();
        for (int i = 0; i < PopulationSize; i++)
        {
            var chromosome = chromosomeFactory();
            chromosome.Initialize();
            _population.Add(chromosome);
        }
        CurrentGeneration = 0;
        EvaluatePopulation();
    }

    public void Evolve()
    {
        var nextGeneration = new List<IChromosome<TGene>>();

        // ۱. اعمال قانون نخبه‌گرایی (Elitism)
        if (Elitism)
        {
            var elite = _population.OrderByDescending(c => c.Fitness).First().Clone();
            nextGeneration.Add(elite);
        }

        // ۲. تولید جمعیت جدید
        while (nextGeneration.Count < PopulationSize)
        {
            // انتخاب والدین
            var parent1 = RouletteWheelSelection();
            var parent2 = RouletteWheelSelection();

            // ترکیب (Crossover)
            IChromosome<TGene> child1, child2;
            if (_random.NextDouble() < CrossoverRate)
            {
                (child1, child2) = PerformCrossover(parent1, parent2);
            }
            else
            {
                child1 = parent1.Clone();
                child2 = parent2.Clone();
            }

            // جهش (Mutation)
            PerformMutation(child1);
            PerformMutation(child2);

            nextGeneration.Add(child1);
            if (nextGeneration.Count < PopulationSize)
            {
                nextGeneration.Add(child2);
            }
        }

        _population = nextGeneration;
        CurrentGeneration++;
        
        // ارزیابی جمعیت جدید
        EvaluatePopulation();
    }

    // بهینه‌سازی با پردازش موازی دات‌نت (PLINQ / Task Parallel Library)
    private void EvaluatePopulation()
    {
        Parallel.ForEach(_population, chromosome =>
        {
            chromosome.Fitness = _fitnessEvaluator.Evaluate(chromosome);
        });

        // به‌روزرسانی بهترین پاسخ یافت شده تاکنون
        var currentBest = _population.OrderByDescending(c => c.Fitness).First();
        if (BestSolution == null || currentBest.Fitness > BestSolution.Fitness)
        {
            BestSolution = currentBest.Clone();
        }
    }
}

۲. پیاده‌سازی متد انتخاب چرخ رولت (Roulette Wheel Selection)

در این روش، شانس انتخاب هر کروموزوم متناسب با میزان برازندگی آن است. کروموزوم‌های قوی‌تر، سهم بیشتری از چرخ را به خود اختصاص می‌دهند.

private IChromosome<TGene> RouletteWheelSelection()
{
    double totalFitness = _population.Sum(c => c.Fitness);
    
    // اگر برازندگی همه صفر بود، یک فرد تصادفی انتخاب کن
    if (totalFitness == 0) 
        return _population[_random.Next(_population.Count)];

    double target = _random.NextDouble() * totalFitness;
    double currentSum = 0;

    foreach (var chromosome in _population)
    {
        currentSum += chromosome.Fitness;
        if (currentSum >= target)
        {
            return chromosome;
        }
    }

    return _population.Last();
}

۳. پیاده‌سازی متد تک‌نقطه‌ای ترکیب (Single-Point Crossover)

در این متد، یک نقطه برش تصادفی انتخاب شده و ژن‌های والدین از آن نقطه به بعد با هم تعویض می‌شوند.

private (IChromosome<TGene> child1, IChromosome<TGene> child2) PerformCrossover(
    IChromosome<TGene> parent1, IChromosome<TGene> parent2)
{
    var child1 = parent1.Clone();
    var child2 = parent2.Clone();

    int length = parent1.Genes.Length;
    int crossoverPoint = _random.Next(1, length - 1);

    for (int i = crossoverPoint; i < length; i++)
    {
        child1.Genes[i] = parent2.Genes[i];
        child2.Genes[i] = parent1.Genes[i];
    }

    return (child1, child2);
}

۴. پیاده‌سازی مکانیزم جهش (Mutation)

بر اساس نرخ جهش تعیین‌شده (MutationRate)، برخی از ژن‌ها به صورت کاملاً تصادفی تغییر وضعیت می‌دهند تا فضای جستجوی جدیدی کشف شود. از آنجا که تعریف چگونگی جهش یک ژن به نوع مسئله بستگی دارد، برای تغییر ژن از خود ساختار کروموزوم کمک می‌گیریم (یا می‌توان یک رویداد/دلیگیت تعریف کرد). اما برای سادگی، فرض می‌کنیم متد تغییر را خود کروموزوم هندل می‌کند یا در پیاده‌سازی مسئله مشخص می‌شود. اجازه دهید فرم کلی آن را بنویسیم:

private void PerformMutation(IChromosome<TGene> chromosome)
{
    for (int i = 0; i < chromosome.Genes.Length; i++)
    {
        if (_random.NextDouble() < MutationRate)
        {
            // تغییر ژن بر اساس منطق تصادفی (مثلا معکوس کردن بیت یا تغییر عدد)
            MutateGene(chromosome, i);
        }
    }
}

// این متد باید بر اساس نوع ژن در کلاس‌های مشتق شده یا استراتژی‌ها پیاده شود
private void MutateGene(IChromosome<TGene> chromosome, int index)
{
    // این بخش در مثال عملیاتی پیاده‌سازی دقیق‌تری خواهد داشت
}

 

بخش چهارم: یک مثال واقعی و عملیاتی (مسئله حدس متن سفارشی)

برای تست این موتور قدرتمند، مسئله کلاسیک «حدس زدن یک رشته متن هدف» (مانند "Genetic Algorithm in DotNet 2026") را پیاده‌سازی می‌کنیم. ژن‌های ما در اینجا کاراکترها خواهند بود.

۱. پیاده‌سازی کروموزوم متن (TextChromosome)

public class TextChromosome : IChromosome<char>
{
    public char[] Genes { get; private set; }
    public double Fitness { get; set; }
    
    private readonly int _targetLength;
    private static readonly Random _rand = new Random();
    private const string AllowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789";

    public TextChromosome(int targetLength)
    {
        _targetLength = targetLength;
        Genes = new char[targetLength];
    }

    public void Initialize()
    {
        for (int i = 0; i < _targetLength; i++)
        {
            Genes[i] = AllowedCharacters[_rand.Next(AllowedCharacters.Length)];
        }
    }

    public IChromosome<char> Clone()
    {
        var clone = new TextChromosome(_targetLength);
        Array.Copy(this.Genes, clone.Genes, _targetLength);
        clone.Fitness = this.Fitness;
        return clone;
    }
    
    // متد کمکی برای ایجاد جهش در کاراکتر
    public void MutateGeneAt(int index)
    {
        Genes[index] = AllowedCharacters[_rand.Next(AllowedCharacters.Length)];
    }
}

۲. پیاده‌سازی تابع برازندگی برای متن (TextFitnessEvaluator)

تابع برازندگی تعداد کاراکترهایی که دقیقاً در جایگاه درست خود قرار دارند را محاسبه می‌کند. هرچه تشابه بیشتر باشد، امتیاز بالاتر است.

public class TextFitnessEvaluator : IFitnessEvaluator<char>
{
    private readonly string _targetText;

    public TextFitnessEvaluator(string targetText)
    {
        _targetText = targetText;
    }

    public double Evaluate(IChromosome<char> chromosome)
    {
        int score = 0;
        for (int i = 0; i < _targetText.Length; i++)
        {
            if (chromosome.Genes[i] == _targetText[i])
            {
                score++;
            }
        }
        
        // به توان رساندن امتیاز برای بزرگنمایی تفاوت بین کروموزوم‌های خوب و عالی (تکنیک تقویت انتخاب)
        return Math.Pow(score, 2);
    }
}

 

بخش پنجم: متد Main و اجرای ارکستراسیون سیستم

حالا همه چیز را در کلاس Program به هم متصل می‌کنیم. همچنین متد PerformMutation در موتور اصلی را متناسب با ساختار نمونه‌مان اصلاح کوچکی در ذهن داریم که با کست کردن (Casting) یا افزودن متد به اینترفیس حل می‌شود. برای تمیزی کار، در موتور اصلی متد جهش را به این شکل بازنویسی می‌کنیم که از متد اختصاصی کروموزوم استفاده کند.

// افزودن متد Mutate به اینترفیس اصلی برای انعطاف‌پذیری بیشتر
public interface IChromosome<TGene>
{
    TGene[] Genes { get; }
    double Fitness { get; set; }
    void Initialize();
    IChromosome<TGene> Clone();
    void MutateGene(int index); // متد جدید
}

(با فرض اضافه شدن این متد به اینترفیس، متد جهش در موتور به سادگی تبدیل به chromosome.MutateGene(index) می‌شود).

اجرای برنامه اصلی:

using System;

class Program
{
    static void Main(string[] args)
    {
        string target = "Genetic Algorithm in CSharp 2026";
        int populationSize = 500;
        double mutationRate = 0.01; // 1% احتمال جهش برای هر ژن
        double crossoverRate = 0.85; // 85% احتمال ترکیب
        bool useElitism = true;

        var evaluator = new TextFitnessEvaluator(target);
        var engine = new GeneticAlgorithmEngine<char>(
            populationSize, mutationRate, crossoverRate, useElitism, evaluator);

        // راه‌اندازی با فکتوری متد
        engine.Initialize(() => new TextChromosome(target.Length));

        Console.WriteLine("Starting Evolution Loop...");
        Console.WriteLine("=============================");

        double maxPossibleFitness = Math.Pow(target.Length, 2);

        while (engine.BestSolution.Fitness < maxPossibleFitness && engine.CurrentGeneration < 5000)
        {
            engine.Evolve();

            if (engine.CurrentGeneration % 20 == 0 || engine.BestSolution.Fitness == maxPossibleFitness)
            {
                string bestText = new string(engine.BestSolution.Genes);
                Console.WriteLine($"Gen {engine.CurrentGeneration:D4} | Best: [{bestText}] | Fitness: {engine.BestSolution.Fitness:F0}");
            }
        }

        Console.WriteLine("=============================");
        Console.WriteLine($"Optimization Finished at Generation {engine.CurrentGeneration}!");
        Console.WriteLine($"Final Result: {new string(engine.BestSolution.Genes)}");
        Console.ReadLine();
    }
}

 

بخش ششم: نکات بهینه‌سازی سطح پیشرفته (Advanced Profiling & Optimization)

به عنوان یک توسعه‌دهنده ارشد، هنگام توسعه الگوریتم‌های تکاملی در پروژه‌های بزرگ صنعتی (Enterprise) باید به مباحث کارایی (Performance) توجه ویژه‌ای داشته باشید:

۱. مدیریت حافظه و کاهش تخصیص‌ها (Allocation-Free GA)

در کدهای بالا، متد Clone در هر نسل تعداد زیادی آرایه جدید روی Heap ایجاد می‌کند که باعث تحریک مداوم Garbage Collector (GC) می‌شود. در نسخه‌های دات‌نت ۸ و بالاتر، برای سیستم‌های Real-time پیشنهاد می‌شود:

  • از یک Pool از آرایه‌ها (ArrayPool<T>) استفاده کنید.

  • رشته‌های ژنتیکی را به جای آرایه‌های معمولی، در ساختارهایی مثل Memory<T> یا Span<T> مدیریت کنید تا از کپی‌های غیرضروری حافظه جلوگیری شود.

۲. تکنیک‌های پیشرفته انتخاب (Selection Techniques)

علاوه بر Roulette Wheel، روش‌های دیگری مانند Tournament Selection وجود دارند. در این روش، چند کروموزوم به صورت تصادفی انتخاب شده و بهترین آن‌ها برگزیده می‌شود. این روش پیچیدگی زمانی O(N) روش رولت را ندارد و بسیار سریع‌تر اجرا می‌شود:

private IChromosome<TGene> TournamentSelection(int tournamentSize = 5)
{
    var tournament = new List<IChromosome<TGene>>();
    for (int i = 0; i < tournamentSize; i++)
    {
        tournament.Add(_population[_random.Next(_population.Count)]);
    }
    return tournament.OrderByDescending(c => c.Fitness).First();
}

۳. جهش تطبیقی (Adaptive Mutation)

اگر متوجه شدید الگوریتم در یک نقطه بهینه محلی (Local Minimum/Maximum) گیر کرده و تنوع جمعیت کم شده است، می‌توانید نرخ جهش را به صورت داینامیک افزایش دهید تا الگوریتم تکانی به خود بدهد و از آن چاله خارج شود (تکنیک Hyper-mutation).

 

نتیجه‌گیری

ما موفق شدیم یک موتور الگوریتم ژنتیک کاملاً ژنریک، شیءگرا و همگام با استانداردهای مدرن سی‌شارپ طراحی کنیم. این ساختار به شما اجازه می‌دهد بدون دست زدن به هسته الگوریتم، هر نوع مسئله پیچیده‌ای (از بهینه‌سازی سبد سهام گرفته تا آموزش وزن‌های یک شبکه عصبی کوچک) را با تعریف کروموزوم و تابع برازندگی جدید حل کنید. دات‌نت با بهره‌گیری از توان پردازش موازی و مدیریت حافظه مدرن، یکی از بهترین گزینه‌ها برای پیاده‌سازی این‌گونه سیستم‌های هوشمند است.

 
لینک استاندارد شده: zbgwVt02

0 نظر

    هنوز نظری برای این مقاله ثبت نشده است.
جستجوی مقاله و آموزش
دوره‌ها با تخفیفات ویژه