Hej!
Mam taki projekt, o którym słyszałem na sucho na uczelni na tzw. wstępie do informatyki i trochę mnie to zaciekawiło, ale niestety po napisaniu go mam dosyć marne rezultaty, także jak ktoś ma ochotę, to proszę o wsparcie ;)
Zadanie:
Znajdź trzy sinusy, które po zsumowaniu będą "jak najbliżej" listy punktów(x,y) podanych jako argument do programu.
Mój program:
Każdy sinus zapisałem w takiej postaci a*sin(b*x+c)+d, gdzie a,b,c,d to zmienne typu double, które będę próbował dobrać.
Jest jeszcze klasa Creature, która po prostu zawiera trzy sinusy i zmienną double rating która jest sumą odległości y punktów od funkcji w punkcie x. - Zmienna potrzebna do sortowania.
Pętla główna programu:
//sw - instancja stopwatch, do mierzenia czasu, aby nie spamować konsoli
//population - tablica Creature
//vec - tablica wektorów 2d określająca punkty do których chcę się zbliżyć
do
{
rateCreatures(population, vec);//wyznacza ratio dla każdego
bestRatio = sortCreatures(population);//sortuje i zwraca wartość pierwszego po posortowaniu
timeSinceLastLog = sw.Elapsed.Seconds;
if (timeSinceLastLog > 1f)
{
Console.WriteLine("Iteration: " + iter);
Console.WriteLine("current best creature ratio: " + bestRatio);
Console.WriteLine("current average creature ratio: " + getAvgRate(population)+"\n");
timeSinceLastLog = 0;
sw.Restart();
}
replaceHalfCreatures(population, random, range);//zostawia 0-50%, dla 50%-95% wybiera dwa losowe Creature z najlepszej połowy i kopiuje po koleji argumenty(rzuca monetą żeby sprawdzić z którego rodzica wziąć dany argument), dla 95%-100% nadpisuje nową, zupełnie losową instancją Creature
iter++;
} while (true);
Co do losowania argumentów przy tworzeniu nowych, losowych instancji Creature i zawierających Sinusów, to mam zmienną double range, którą ręcznie ustawiam, np na 10 - wtedy wszystkie typy wartościowe są losowane od -10 do 10. Zmienianie nie pomaga za bardzo - próbowałem
Na koniec załączam całość kodu:(projekt na 2h, więc nie jest piknie, ale komentarze co do jakości kodu, albo dobrych praktyk programowania też mile widziane! ;))
Klasa Sinus:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GeneticAlgorithm
{
class Sinus
{
public double a, b, c;
public Sinus(Random rand, double range)
{
//default: make a random Sinus object
a = (rand.NextDouble() - 0.5f) * range*2f;
b = (rand.NextDouble() - 0.5f) * range*2f;
c = (rand.NextDouble() - 0.5f) * range*2f;
}
public Sinus(double _a, double _b, double _c)
{
//used for parenting in creature func.
a = _a;
b = _b;
c = _c;
}
}
}
Klasa Creature:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GeneticAlgorithm
{
class Creature
{
public Sinus s1, s2, s3;
public double d;
public double rating;
public Creature(Random rand, double range)
{
//default: for first generation only
s1 = new Sinus(rand, range);
s2 = new Sinus(rand, range);
s3 = new Sinus(rand, range);
d = (rand.NextDouble() - 0.5f) * 200f;
}
public void replace(Creature parent1, Creature parent2, Random rand, double range)
{
//data to fill and use to make new dude
Sinus sin1 = new Sinus(rand, range);
Sinus sin2 = new Sinus(rand, range);
Sinus sin3 = new Sinus(rand, range);
double d1=0;
for (int i=0;i<10;i++)
{
int coinFlip = rand.Next(2);
bool useFirst = true;
if (coinFlip == 1)
useFirst = false;
switch(i)
{
case 0:
sin1.a = (useFirst) ? parent1.s1.a : parent2.s1.a;
break;
case 1:
sin1.b = (useFirst) ? parent1.s1.b : parent2.s1.b;
break;
case 2:
sin1.c = (useFirst) ? parent1.s1.c : parent2.s1.c;
break;
case 3:
sin2.a = (useFirst) ? parent1.s2.a : parent2.s2.a;
break;
case 4:
sin2.b = (useFirst) ? parent1.s2.b : parent2.s2.b;
break;
case 5:
sin2.c = (useFirst) ? parent1.s2.c : parent2.s2.c;
break;
case 6:
sin3.a = (useFirst) ? parent1.s3.a : parent2.s3.a;
break;
case 7:
sin3.b = (useFirst) ? parent1.s3.b : parent2.s3.b;
break;
case 8:
sin3.c = (useFirst) ? parent1.s3.c : parent2.s3.c;
break;
case 9:
d1 = (useFirst) ? parent1.d : parent2.d;
break;
}
s1 = sin1;
s2 = sin2;
s3 = sin3;
d = d1;
}
}
public void rate(Vector2[] points)
{
double toReturn = 0;
for(int i=0; i < points.Length; i++)
{
double x = points[i].x;
double y = points[i].y;
double fY = this.valueIn(x);
toReturn += Math.Sqrt((y - fY) * (y - fY));//TODO: consider getting rid of sqrt - may be more efficient
}
rating = toReturn;
}
public double valueIn(double x)
{
return Math.Sin(x * s1.a + s1.b) * s1.c + Math.Sin(x * s2.a + s2.b) * s2.c + Math.Sin(x * s3.a + s3.b) * s3.c + d;
}
}
}
Klasa Program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace GeneticAlgorithm
{
public struct Vector2
{
public double x, y;
}
class Program
{
static void Main(string[] args)
{
double range = 1f;
Vector2[] vec = new Vector2[5];
vec[0].x = 0f;
vec[0].y = 5f;
vec[1].x = 1f;
vec[1].y = 3f;
vec[2].x = 2f;
vec[2].y = -5f;
vec[3].x = 3f;
vec[3].y = 3f;
vec[4].x = 4f;
vec[4].y = 0f;
Random random = new Random();
const int populationSize = 200;
Creature[] population;
List<Creature> creatures = new List<Creature>();
for (int i = 0; i < populationSize; i++)
creatures.Add(new Creature(random, range));
population = creatures.ToArray();
int iter = 0;
double bestRatio = 0;
float timeSinceLastLog = 1f;
Stopwatch sw = Stopwatch.StartNew();
rateCreatures(population, vec);
Console.WriteLine("Iteration: " + 0);
Console.WriteLine("current best creature ratio: " + sortCreatures(population));
Console.WriteLine("current average creature ratio: " + getAvgRate(population) + "\n");
do
{
rateCreatures(population, vec);
bestRatio = sortCreatures(population);
timeSinceLastLog = sw.Elapsed.Seconds;
if (timeSinceLastLog > 1f)
{
Console.WriteLine("Iteration: " + iter);
Console.WriteLine("current best creature ratio: " + bestRatio);
Console.WriteLine("current average creature ratio: " + getAvgRate(population)+"\n");
timeSinceLastLog = 0;
sw.Restart();
}
replaceHalfCreatures(population, random, range);
iter++;
} while (population[0].rating > 10f);
}
static void rateCreatures(Creature[] population, Vector2[] points)
{
foreach (Creature c in population)
c.rate(points);
}
static double sortCreatures(Creature[] population)
{
population = population.OrderBy(n => n.rating).ToArray();
/*Console.WriteLine("first: " + population[0].rating);
Console.WriteLine("second: " + population[1].rating);
Console.WriteLine("last: " + population[199].rating);*/
return population[0].rating;
}
static void replaceHalfCreatures(Creature[] population, Random rand, double range)
{
int creaturesToKill = population.Length / 2;
for(int i = creaturesToKill - 1; i<population.Length; i++)
{
if (i < population.Length - 10)
{
//find two random creatures of the better part(TODO: the higher they are, the more likely should they be to be picked for gene selection)
int parent1Index = rand.Next(creaturesToKill);
int parent2Index = rand.Next(creaturesToKill);
population[i].replace(population[parent1Index], population[parent2Index], rand, range);
}
else
{
population[i] = new Creature(rand, range);
}
}
}
static double getAvgRate(Creature[] population)
{
double toReturn = 0;
foreach (Creature c in population)
toReturn += c.rating;
return toReturn / population.Length;
}
}
}
Na koniec to co widać na konsoli:
Iteration: 0
current best creature ratio: 19,8839451514098
current average creature ratio: 378,409788452106
Iteration: 4330
current best creature ratio: 19,8839451514098
current average creature ratio: 342,072062725768
Iteration: 8751
current best creature ratio: 19,7690783591971
current average creature ratio: 346,062072233154
Iteration: 13241
current best creature ratio: 19,6651993027145
current average creature ratio: 353,915512082182
Iteration: 17643
current best creature ratio: 19,8839451514098
current average creature ratio: 326,881308248833
Iteration: 22078
current best creature ratio: 18,7688923014568
current average creature ratio: 346,773040628046
Iteration: 26373
current best creature ratio: 19,3618102124538
current average creature ratio: 342,846981933711