الگوریتم بهینه سازی کلونی مورچگان در جاوا — راهنمای کاربردی

۳۳۱ بازدید
آخرین به‌روزرسانی: ۰۶ شهریور ۱۴۰۲
زمان مطالعه: ۴ دقیقه
الگوریتم بهینه سازی کلونی مورچگان در جاوا — راهنمای کاربردی

در این مقاله به توضیح مفهوم «بهینه‌سازی کلونی مورچگان» (ant colony optimization) یا به اختصار ACO می‌پردازیم و کدهای نمونه آن را نیز ارائه می‌کنیم.

طرز کار الگوریتم کلونی مورچگان چگونه است؟

ACO یک الگوریتم ژنتیک است که از رفتار طبیعی مورچه‌ها الهام گرفته است. برای درک کامل الگوریتم ACO باید با مفاهیم مقدماتی آن آشنا باشیم.

  • مورچه‌ها از «فرمیون» (pheromone) برای یافتن کوتاه‌ترین مسیر بین خانه و منبع غذایی استفاده می‌کنند.
  • فرمیون‌ها به سرعت تبخیر می‌شوند.
  • مورچه‌ها مسیرهای کوتاه‌تر با فرمیون‌های چگال‌تر را ترجیح می‌دهند.

در ادامه مثال ساده‌ای از الگوریتم کلونی مورچگان را که به صورت مسئله فروشنده دوره‌گرد مطرح شده، مورد بررسی قرار می‌دهیم. در مثال زیر باید کوتاه‌ترین مسیر بین همه گره‌های گراف را بیابیم.

الگوریتم کلونی مورچگان

مورچه‌ها با پیروی از رفتار طبیعی‌شان شروع به جستجوی مسیرهای جدید در مرحله کاوش می‌کنند. مسیرهای آبی پررنگ نشان‌دهنده مواردی هستند که نسبت به بقیه بیشتر استفاده شده‌اند؛ در حالی که مسیرهای سبزرنگ نماینده کوتاه‌ترین مسیر کنونی پیدا شده است:

کلونی مورچگان

در نتیجه به کوتاه‌ترین مسیر بین همه گره‌ها دست پیدا کرده‌ایم:

کلونی مورچگان

برای مشاهده یک ابزار زیبای مبتنی بر GUI برای آزمودن ACO می‌توانید به این آدرس (+) مراجعه کنید.

پیاده‌سازی جاوا

در این بخش به بررسی روش موردنیاز برای پیاده‌سازی بهینه‌سازی کلونی مورچگان در زبان برنامه‌نویسی جاوا می‌پردازیم.

پارامترهای ACO

ابتدا پارامترهای الگوریتم ACO را که در کلاس AntColonyOptimization اعلان شده‌اند، بررسی می‌کنیم:

1private double c = 1.0;
2private double alpha = 1;
3private double beta = 5;
4private double evaporation = 0.5;
5private double Q = 500;
6private double antFactor = 0.8;
7private double randomFactor = 0.01;

پارامتر c نشان‌دهنده عدد اولیه «مسیرها» (trails) در ابتدای شبیه‌سازی است. به علاوه، alpha به کنترل اهمیت فرمیون می‌پردازد؛ در حالی که beta اولویت مسافت را کنترل می‌کند. به طور کلی در بهترین نتایج پارامتر بتا باید بزرگ‌تر از آلفا باشد.

سپس متغیر «تبخیر» (evaporation) نشان می‌دهد که چه درصدی از فرمیون در هر تکرار تبخیر می‌شود؛ در حالی که Q اطلاعاتی در مورد مقدار کلی فرمیون باقی‌مانده روی مسیر از سوی هر مورچه و antFactor نیز تعداد مورچه‌های هر شهر را نشان می‌دهد. در نهایت باید مقداری تصادفی بودن به شبیه‌سازی‌های خود اضافه کنیم که این وظیفه نیز به پارامتر ranfomFactor محول شده است.

ایجاد مورچه‌ها

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

1public void visitCity(int currentIndex, int city) {
2    trail[currentIndex + 1] = city;
3    visited[city] = true;
4}
5 
6public boolean visited(int i) {
7    return visited[i];
8}
9 
10public double trailLength(double graph[][]) {
11    double length = graph[trail[trailSize - 1]][trail[0]];
12    for (int i = 0; i < trailSize - 1; i++) {
13        length += graph[trail[i]][trail[i + 1]];
14    }
15    return length;
16}

تنظیم مورچه‌ها

در ابتدای کار، باید پیاده‌سازی کد ACO خود را از طریق ارائه ماتریس‌های مسیرها و مورچه‌ها، مقداردهی اولیه بکنیم:

1graph = generateRandomMatrix(noOfCities);
2numberOfCities = graph.length;
3numberOfAnts = (int) (numberOfCities * antFactor);
4 
5trails = new double[numberOfCities][numberOfCities];
6probabilities = new double[numberOfCities];
7ants = new Ant[numberOfAnts];
8IntStream.range(0, numberOfAnts).forEach(i -> ants.add(new Ant(numberOfCities)));

سپس باید ماتریس مورچگان را با یک شهر تصادفی مقداردهی بکنیم:

1public void setupAnts() {
2    IntStream.range(0, numberOfAnts)
3      .forEach(i -> {
4          ants.forEach(ant -> {
5              ant.clear();
6              ant.visitCity(-1, random.nextInt(numberOfCities));
7          });
8      });
9    currentIndex = 0;
10}

در هر تکرار این حلقه، عملیات زیر اجرا می‌شوند:

1IntStream.range(0, maxIterations).forEach(i -> {
2    moveAnts();
3    updateTrails();
4    updateBest();
5});

جابجایی مورچه‌ها

کار خود را با متد ()moveAnts آغاز می‌کنیم. به این منظور باید شهر بعدی را برای همه مورچه‌ها تنظیم کنیم و مسیر هر مورچه را برای پیمودن از سوی مورچه‌های دیگر به خاطر بسپاریم:

1int t = random.nextInt(numberOfCities - currentIndex);
2if (random.nextDouble() < randomFactor) {
3    OptionalInt cityIndex = IntStream.range(0, numberOfCities)
4      .filter(i -> i == t && !ant.visited(i))
5      .findFirst();
6    if (cityIndex.isPresent()) {
7        return cityIndex.getAsInt();
8    }
9}

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

1public void calculateProbabilities(Ant ant) {
2    int i = ant.trail[currentIndex];
3    double pheromone = 0.0;
4    for (int l = 0; l < numberOfCities; l++) {
5        if (!ant.visited(l)){
6            pheromone
7              += Math.pow(trails[i][l], alpha) * Math.pow(1.0 / graph[i][l], beta);
8        }
9    }
10    for (int j = 0; j < numberOfCities; j++) {
11        if (ant.visited(j)) {
12            probabilities[j] = 0.0;
13        } else {
14            double numerator
15              = Math.pow(trails[i][j], alpha) * Math.pow(1.0 / graph[i][j], beta);
16            probabilities[j] = numerator / pheromone;
17        }
18    }
19}

اگر هیچ شهر تصادفی را انتخاب نکرده باشیم، باید احتمال‌های انتخاب شهر بعدی را نیز محاسبه کنیم و به خاطر داشته باشیم که مورچه ترجیح می‌دهد، مسیر قوی‌تر و کوتاه‌تر را بپیماید. این کار از طریق مرتب‌سازی احتمال رسیدن به هر شهر در آرایه ممکن است:

1public void calculateProbabilities(Ant ant) {
2    int i = ant.trail[currentIndex];
3    double pheromone = 0.0;
4    for (int l = 0; l < numberOfCities; l++) {
5        if (!ant.visited(l)){
6            pheromone
7              += Math.pow(trails[i][l], alpha) * Math.pow(1.0 / graph[i][l], beta);
8        }
9    }
10    for (int j = 0; j < numberOfCities; j++) {
11        if (ant.visited(j)) {
12            probabilities[j] = 0.0;
13        } else {
14            double numerator
15              = Math.pow(trails[i][j], alpha) * Math.pow(1.0 / graph[i][j], beta);
16            probabilities[j] = numerator / pheromone;
17        }
18    }
19}

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

1double r = random.nextDouble();
2double total = 0;
3for (int i = 0; i < numberOfCities; i++) {
4    total += probabilities[i];
5    if (total >= r) {
6        return i;
7    }
8}

به‌روزرسانی مسیرها

در این مرحله، باید مسیرها و میزان فرمیون باقی‌مانده را به‌روزرسانی کنیم:

1public void updateTrails() {
2    for (int i = 0; i < numberOfCities; i++) {
3        for (int j = 0; j < numberOfCities; j++) {
4            trails[i][j] *= evaporation;
5        }
6    }
7    for (Ant a : ants) {
8        double contribution = Q / a.trailLength(graph);
9        for (int i = 0; i < numberOfCities - 1; i++) {
10            trails[a.trail[i]][a.trail[i + 1]] += contribution;
11        }
12        trails[a.trail[numberOfCities - 1]][a.trail[0]] += contribution;
13    }
14}

به‌روزرسانی بهترین انتخاب

این آخرین مرحله برای هر تکرار است. ما باید بهترین انتخاب را به‌روزرسانی کنیم تا ارجاع آن را حفظ کنیم:

1private void updateBest() {
2    if (bestTourOrder == null) {
3        bestTourOrder = ants[0].trail;
4        bestTourLength = ants[0].trailLength(graph);
5    }
6    for (Ant a : ants) {
7        if (a.trailLength(graph) < bestTourLength) {
8            bestTourLength = a.trailLength(graph);
9            bestTourOrder = a.trail.clone();
10        }
11    }
12}

پس از اجرای همه تکرارها، نتیجه نهایی بهترین مسیر مشخص شده از سوی ACO را نشان خواهد داد. توجه داشته باشید که با افزایش تعداد شهرها، احتمال یافتن کوتاه‌ترین مسیر کاهش می‌یابد.

نتیجه‌گیری

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

کد کامل این راهنما را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید.

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

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
baeldung
۱ دیدگاه برای «الگوریتم بهینه سازی کلونی مورچگان در جاوا — راهنمای کاربردی»

با سلام

مشابه همین کاربرد مورچگان در زمانبندی وظایف در رایانش ابری را ندارید؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *