آموزش آموزش c# برنامه نویسی فناوری

استفاده از الگوریتم‌های شبکه عصبی و ژنتیک در C#.NET

امیرحسین عراقی
نوشته شده توسط امیرحسین عراقی

استفاده از الگوریتم‌های شبکه عصبی و ژنتیک در C#.NET

مقدمه

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

در این مقاله ما بر آنیم که یک شبکه عصبی را در C#.NET پیاده سازی کنیم و شبکه را با استفاده از الگوریتم ژنتیک آموزش دهیم. شبکه‌های ما برای بقا با یکدیگر رقابت می‌کنند تا بهترین راه حل را برای توابع ریاضی AND، OR و XOR به دست آورند. اگرچه پاسخ به این توابع بدیهی است ولی پیاده سازی ما مقدمه‌ای بر پیاده سازی شبکه عصبی با الگوریتم ژنتیک خواهد بود. اگر بتوان شبکه‌ای برای حل ساده ترین نوابع ریاضی طراحی کرد، به طور قطع می‌توان شبکه‌های قدرمندی نیز تولید کرد.

شبکه عصبی درواقع یک مغز است

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

نظارت بر آموزش شبکه عصبی کاری کسل کننده است

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

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

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

اساس الگوریتم ژنتیک تکامل است. ما ابتدا با گروهی از شبکه‌های عصبی با وزن‌های تصادفی آغاز می‌کنیم. معیاری برای اجرای هریک از شبکه‌ها تعیین می‌کنیم. این معیار ما را در رسیدن به مناسبترین پاسخ به مسئله کمک می‌کند. مناسبترین شبکه فرزندانی را با وزن‌های مختلف ایجاد می‌کند. و این کار می تواند بارها و بارها برای رسیدن به پاسخ مطلوب تکرار شود.

تنظیمات شبکه عصبی

ما از کتابخانه‌ای به نام NeuronDoNet برای پیاده سازی شبکه عصبی استفاده می‌کنیم. برای الگوریتم ژنتیک نیز ما از کتابخانه پایه بهره می‌بریم.

کد تمام پروژه را می‌توانید از اینجا دانلود کنید.

برای آغاز پروژه یک کنسول اپلیکیشن C#.NET در Visual Studio می‌سازیم و بدنه پروژه را به صورت زیر تعریف می‌کنید:



using System;
using System.Text;
using NeuronDotNet.Core.Backpropagation;
using NeuronDotNet.Core;
using btl.generic;
namespace NeuralNetworkTest
{
class Program
{
public static BackpropagationNetwork network;
static void Main(string[] args)
{
LinearLayer inputLayer = new LinearLayer(2);
SigmoidLayer hiddenLayer = new SigmoidLayer(2);
SigmoidLayer outputLayer = new SigmoidLayer(1);
BackpropagationConnector connector = new BackpropagationConnector(inputLayer, hiddenLayer);
BackpropagationConnector connector2 = new BackpropagationConnector(hiddenLayer, outputLayer);
network = new BackpropagationNetwork(inputLayer, outputLayer);
network.Initialize();
// AND
TrainingSet trainingSet = new TrainingSet(2, 1);
trainingSet.Add(new TrainingSample(new double[2] { 0, 0 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 0, 1 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 1, 0 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 1, 1 }, new double[1] { 1 }));
network.Learn(trainingSet, 5000);
double input1;
string strInput1 = "";
while (strInput1 != "q")
{
Console.Write("Input 1: ");
strInput1 = Console.ReadLine().ToString();
if (strInput1 != "q")
{
input1 = Convert.ToDouble(strInput1);
if (input1 != 'q')
{
Console.Write("Input 2: ");
double input2 = Convert.ToDouble(Console.ReadLine().ToString());
double[] output = network.Run(new double[2] { input1, input2 });
Console.WriteLine("Output: " + output[0]);
}
}
}
}
}
}

 

 

در کد بالا ما از کتابخانه Neural Network برای ساخت شبکه‌مان استفاده می‌کنیم. ما درواقع برای آغاز از روش بازگشت به عقب برای آموزش شبکه‌مان استفاده می‌کنیم، ولی الگوریتم ژنتیک را در گام بعدی عوض خواهیم کرد. به یاد داشته باشید ما متغیری به نام شبکه برای نشان دادن مغز پروژه داریم. سپس سه لایه برای شبکه‌مان خواهیم ساخت. البته این نکته لازم به ذکر است که برای تعریف توابع AND و OR تنها نیاز به تعریف دو لایه داریم. اگرچه برای حل تابع XOR نیاز به یک لایه پنهانی دیگر نیز داریم. شما می‌توانید دلایل ریاضی آن را به دست بیاورید ولی ما در اینجا از آن‌ها می‌گذریم.

 کتابخانه C# neural network نیاز به ساخت اتصالات بین لایه‌ها دارد. ما برای این منظور اشیای BackpropagationConnector برای هر لایه نصب می‌کنیم. به محض اتصال لایه‌ها ما تابع Initialize() برای مقدار دهی اولیه به بارهای نرون‌ها به صورت تصادفی فراخوانی می‌کنیم. سپس مجموعه‌ای آموزشی برای تابع AND تولید می‌کنیم و شبکه‌مان را به کمک آن آموزش می‌دهیم.

تابع AND

تابع AND به مثابه ضرب عمل می‌کند و هنگامی که دو عدد دودویی را به این تابع می‌دهیم نتایج زیر را به دست می‌دهد:

AND

۰ ۰ = ۰
۰ ۱ = ۰
۱ ۰ = ۰
۱ ۱ = ۱

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

خروجی شبکه عصبی به تابع AND


using System;
using System.Text;
using NeuronDotNet.Core.Backpropagation;
using NeuronDotNet.Core;
using btl.generic;
namespace NeuralNetworkTest
{
class Program
{
public static BackpropagationNetwork network;
static void Main(string[] args)
{
LinearLayer inputLayer = new LinearLayer(2);
SigmoidLayer hiddenLayer = new SigmoidLayer(2);
SigmoidLayer outputLayer = new SigmoidLayer(1);
BackpropagationConnector connector = new BackpropagationConnector(inputLayer, hiddenLayer);
BackpropagationConnector connector2 = new BackpropagationConnector(hiddenLayer, outputLayer);
network = new BackpropagationNetwork(inputLayer, outputLayer);
network.Initialize();
// AND
TrainingSet trainingSet = new TrainingSet(2, 1);
trainingSet.Add(new TrainingSample(new double[2] { 0, 0 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 0, 1 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 1, 0 }, new double[1] { 0 }));
trainingSet.Add(new TrainingSample(new double[2] { 1, 1 }, new double[1] { 1 }));
network.Learn(trainingSet, 5000);
double input1;
string strInput1 = "";
while (strInput1 != "q")
{
Console.Write("Input 1: ");
strInput1 = Console.ReadLine().ToString();
if (strInput1 != "q")
{
input1 = Convert.ToDouble(strInput1);
if (input1 != 'q')
{
Console.Write("Input 2: ");
double input2 = Convert.ToDouble(Console.ReadLine().ToString());
double[] output = network.Run(new double[2] { input1, input2 });
Console.WriteLine("Output: " + output[0]);
}
}
}
}
}
}

وقتی که برنامه نشان داده شده در بالا را اجرا می‌کنیم، دو ورودی دودویی را به عنون ورودی به هوش مصنوعی می‌دهیم و یک خروجی از آن دریافت می‌کنیم. هوش مصنوعی عددی بین ۰ تا ۱ را به ما بازخواهد گرداند. هر چه که عدد خروجی به یک نزدیکتر باشد، ارزش آن را می‌توان بیشتر از یک “YES” در نظر گرفت. و هرچه که عدد خروجی به صفر نزدیکتر باشد، ارزش آن را می‌توان بیشتر به یک “NO” نزدیک دانست. در خروجی بالا، شما می‌توانید ببنید که دادن ۰ و ۰ به تابع چگونه عدد ۰٫۰۰۵ را به ما می‌دهد که در صورت گرد کردن آن به ۰ می‌رسیم. که البته این پاسخی صحیح به این دو ورودی است. سایر حالات نیز از این قاعده پیروی می‌کنند. به ویژه زمانی که ۱ و ۱ را به تابع ارائه می‌دهیم شبکه عدد ۰٫۹۵۷ را به ما بازمی‌گرداند که در صورت گرد کردن به عدد ۱ خواهیم رسید. که این نیز پاسخی صحیح به این دو ورودی است.

روش بازگشت به عقب روشی جالب است، ولی بیایید نبرد مغزها را شروع کنیم.

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

اضافه کردن کد برای الگوریتم ژنتیک

کد بالا را با حذف کردن بخش آموزش و جایگیزینی آن به صورت زیر اصلاح کنید:

static void Main(string[] args)
{
LinearLayer inputLayer = new LinearLayer(2);
SigmoidLayer hiddenLayer = new SigmoidLayer(2);
SigmoidLayer outputLayer = new SigmoidLayer(1);
BackpropagationConnector connector = new BackpropagationConnector(inputLayer, hiddenLayer);
BackpropagationConnector connector2 = new BackpropagationConnector(hiddenLayer, outputLayer);
network = new BackpropagationNetwork(inputLayer, outputLayer);
network.Initialize();
GA ga = new GA(0.50, 0.01, 100, 2000, 12);
ga.FitnessFunction = new GAFunction(fitnessFunction);
ga.Elitism = true;
ga.Go();
double[] weights;
double fitness;
ga.GetBest(out weights, out fitness);
Console.WriteLine("Best brain had a fitness of " + fitness);
setNetworkWeights(network, weights);
double input1;
string strInput1 = "";
while (strInput1 != "q")
{
Console.Write("Input 1: ");
strInput1 = Console.ReadLine().ToString();
if (strInput1 != "q")
{
input1 = Convert.ToDouble(strInput1);
if (input1 != 'q')
{
Console.Write("Input 2: ");
double input2 = Convert.ToDouble(Console.ReadLine().ToString());
double[] output = network.Run(new double[2] { input1, input2 });
Console.WriteLine("Output: " + output[0]);
}
}
}
}

 

ما توابع کمکی نظیر fitnessFunction را شرح خواهیم داد ولی ابتدا بیایید چند نکته مربوط به کد بالا را ذکر کنیم. درنظر داشته باشید که ما بخش آموزش شبکه عصبی را با روش آموزش الگوریتم ژنتیک جایگزین کردیم. ما الگوریتم ژنتیکی با تقاطع ۵۰٪، نرخ جهش ۱٪، اندازه جمعیت ۱۰۰، طول دوره ۲۰۰۰ تکرار، و تعداد وزن ۱۲ تعریف کرده‌ایم. اگرچه تمامی این اعداد متغیر هستند ولی آخرین عدد اینگونه نیست. این عدد باید دقیقا مطابق با وزن استفاده شده در شبکه عصبی باشد. از آنجا که شبکه ما شامل ۳ لایه (ورودی، پنهان و خروجی) با ۲ نورون در لایه ورودی، ۲ نورون در لایه پنهان و ۱ نورون در لایه خروجی است، برای به دست آوردن یک شبکه عصبی کاملا متصل نیاز  به ۶ اتصال (که همچنین به نام سیناپس نیز شناخته می‌شود) داریم. که باید این مقدار را برای شامل شدن مقادیر سرکش دوبرابر کنیم. در واقع ما نیاز به ۱۲ متغیر برای تعریف وزن شبکه داریم. الگوریتم ژنتیک ما مراقب تعیین وزن‌ها خواهد بود. و تکامل نیز مراقب انتخاب بهترین شبکه خواهد بود. ما باید تنها نگران راه اندازی شبکه باشیم.

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

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

public static void setNetworkWeights(BackpropagationNetwork aNetwork, double[] weights)
{
// Setup the network's weights.
int index = 0;
foreach (BackpropagationConnector connector in aNetwork.Connectors)
{
foreach (BackpropagationSynapse synapse in connector.Synapses)
{
synapse.Weight = weights[index++];
synapse.SourceNeuron.SetBias(weights[index++]);
}
}
}
public static double fitnessFunction(double[] weights)
{
double fitness = 0;
setNetworkWeights(network, weights);
// AND
double output = network.Run(new double[2] { 0, 0 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
output = network.Run(new double[2] { 0, 1 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
output = network.Run(new double[2] { 1, 0 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
output = network.Run(new double[2] { 1, 1 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
return fitness;
}

 

اولین تابع، یک تابع کمکی ساده است که برای جا به جا کردن وزن در شبکه عصبی ما مجموعه‌ای از آرایه‌های دوبعدی را به آن تخصیص می‌دهد. ( الگوریتم ژنتیک ما از آرایه‌ای دو بعدی نگهداری می‌کند). مهمترین تابع در الگوریتم ژنتیک ما Fitness Test (آزمون سازگاری) است.

سختترین قسمت، آزمون سازگاری

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

در تابع fitnessFunction()، ما شبکه‌ای عصبی را با وزن‌هایی از الگوریتم کنونی ساکن می‌کنیم. سپس ۴ بار شبکه را با ورودی‌های ممکن،اجرا می‌کنیم. درواقع ما می‌خواهیم که خروجی تابع زمانی که وروردی ۱ و ۱ است بسیار به ۱ نزدیک شود. برای حالات دیگر خروجی باید صفر باشد. ما می‌توانیم این کار را با دادن امتیاز بر حسب نزدیک بودن به خروجی مطلوب به تابع آزمون سازگاری بفهمانیم. برای مثال، در هنگامی که ورودی‌ها صفر هستند، خروجی باید تا حد ممکن به صفر نزدیک باشد از این رو هر چه که خروجی به صفر نزدیکتر باشد، امتیاز بالاتری به شبکه تعلق می‌گیرد. ما این کار را با کم کردن خروجی از ۱ انجام می‌دهیم. بنابراین اگر خروجی ۰٫۸ باشد (بسیار نزدیک به ۱، و بسیار غلط زمانی که ورودی تابع AND صفر و صفر است) ما امتیازی برابر با ۰٫۲ به شبکه خواهیم داد، از طرفی چنانچه خروجی ۰٫۱ باشد. امتیاز کسب شده توسط شبکه ۰٫۹ خواهد بود. ما این روال را برای تمامی حالات ادامه می‌دهیم.

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

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

خروجی تابع AND شبکه عصبی با الگوریتم ژنتیک


Generation 0, Best Fitness: 3
Generation 100, Best Fitness: 3.47803165619214
Generation 200, Best Fitness: 3.99974219528311
Generation 300, Best Fitness: 3.99999960179664
Generation 400, Best Fitness: 3.99999981699116
Generation 500, Best Fitness: 3.99999986294525
Generation 600, Best Fitness: 3.99999990917201
Generation 700, Best Fitness: 3.99999994729483
Generation 800, Best Fitness: 3.9999999621852
Generation 900, Best Fitness: 3.99999996309631
Generation 1000, Best Fitness: 3.99999997541078
Generation 1100, Best Fitness: 3.99999997739028
Generation 1200, Best Fitness: 3.99999997740393
Generation 1300, Best Fitness: 3.99999997740393
Generation 1400, Best Fitness: 3.99999998185631
Generation 1500, Best Fitness: 3.99999998214724
Generation 1600, Best Fitness: 3.99999998217092
Generation 1700, Best Fitness: 3.99999998217092
Generation 1800, Best Fitness: 3.99999998410326
Generation 1900, Best Fitness: 3.99999998410326
Best brain had a fitness of 3.9999999842174
Input 1: 0
Input 2: 0
Output: 0.05820069534838
Input 1: 0
Input 2: 1
Output: 0.06753356769009
Input 1: 1
Input 2: 0
Output: 0.02594788736069
Input 1: 1
Input 2: 1
Output: 0.999999996869079

 

با توجه به خروجی، مزایای الگوریتم ژنتیک با تکامل جمعیت، تکثیر می‌یابد. پس از ۲۰۰۰ دوره اجرا، هوش مصنوعی ما میزان سازگاری ۳٫۹۹ را از خود نشان می‌دهد. وقتی که شبکه اجرا می‌شود، پاسخی بسیار صحیح دریافت خواهیم کرد. تمامی خروجی‌ها صفر یا کمتر خواهند بود، به جز حالت ۱ AND 1، که خروجی ۰٫۹۹ را دارد که اگر رند شود خروجی ۱ را به ما خواهد داد.

پیاده سازی تابع OR

با راه‌اندازی هسته کد، می توانیم به راحتی به وسیله عوض کردن تابع fitnessFunction به صورت زیر تابع OR را پیاده سازی کنیم.

public static double fitnessFunction(double[] weights)
{
double fitness = 0;
// OR
double output = network.Run(new double[2] { 0, 0 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
output = network.Run(new double[2] { 0, 1 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
output = network.Run(new double[2] { 1, 0 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
output = network.Run(new double[2] { 1, 1 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
return fitness;
}

خروجی تابع OR شبکه عصبی با الگوریتم ژنتیک


Generation 0, Best Fitness: 2.99999999999995
Generation 100, Best Fitness: 3.99600659181652
Generation 200, Best Fitness: 3.9991103135676
Generation 300, Best Fitness: 3.99996421958631
Generation 400, Best Fitness: 3.99999675609333
Generation 500, Best Fitness: 3.99999943413239
Generation 600, Best Fitness: 3.99999989442878
Generation 700, Best Fitness: 3.9999999064053
Generation 800, Best Fitness: 3.99999994092478
Generation 900, Best Fitness: 3.99999994092478
Generation 1000, Best Fitness: 3.99999994092478
Generation 1100, Best Fitness: 3.9999999494151
Generation 1200, Best Fitness: 3.99999995357012
Generation 1300, Best Fitness: 3.99999995357012
Generation 1400, Best Fitness: 3.99999995485334
Generation 1500, Best Fitness: 3.99999995485334
Generation 1600, Best Fitness: 3.99999996197061
Generation 1700, Best Fitness: 3.9999999632428
Generation 1800, Best Fitness: 3.9999999636009
Generation 1900, Best Fitness: 3.9999999636009
Best brain had a fitness of 3.99999996499874
Input 1: 0
Input 2: 0
Output: 0.0001883188626
Input 1: 0
Input 2: 1
Output: 0.999999983783592
Input 1: 1
Input 2: 0
Output: 0.999999997192107
Input 1: 1
Input 2: 1
Output: 0.999999997194924

دوباره پس از ۲۰۰۰ بار اجرا، بهترین شبکه عصبی می‌تواند به درستی تابع OR را حل کند. تابع OR به صورت زیر عمل می‌کند:

OR

۰ ۰ = ۰
۰ ۱ = ۱
۱ ۰ = ۱
۱ ۱ = ۱

از خروجی ما می‌توانید مشاهده کنید که هنگامی که ورودی ۰ و ۰ است خروجی ۰ و یا کمتر است. وقتی ۰ و ۱ را به عنوان ورودی به تابع می‌دهیم ۰٫۹۹ را دریافت می‌کنیم که گرد شده آن برابر ۱ خواهد بود. سایر حالات نیز به همین منوال خواهند بود.

پیاده سازی تابع XOR

تابع XOR نیاز به کمی حقه و کلک دارد. پیاده سازی این تابع به سادگی توابع AND و OR نیست و درواقع نیازمند یک لایه پنهان در شبکه عصبی است. بدون این نورون اضافی، هوش مصنوعی قادر نخواهد بود که تابع XOR را اجرا کند. از آنجایی که شبکه ما یک لایه مخفی با نورون مورد نیاز دارد، ما برای پیاده سازی تابع XOR می‌توانیم تابع fitnessFunction را به راحتی به صورت زیر تغییر دهیم:


public static double fitnessFunction(double[] weights)
{
double fitness = 0;
setNetworkWeights(network, weights);
// XOR
double output = network.Run(new double[2] { 0, 0 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
output = network.Run(new double[2] { 0, 1 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
output = network.Run(new double[2] { 1, 0 })[0];
// The closest the output is to one, the more fit it is.
fitness += output;
output = network.Run(new double[2] { 1, 1 })[0];
// The closest the output is to zero, the more fit it is.
fitness += 1 - output;
return fitness;
}

خروجی تابع XOR شبکه عصبی با الگوریتم ژنتیک

Generation 0, Best Fitness: 2.39064761320888
Generation 100, Best Fitness: 3.49697448976411
Generation 200, Best Fitness: 3.49799189851772
Generation 300, Best Fitness: 3.59338089950075
Generation 400, Best Fitness: 3.60622027042199
Generation 500, Best Fitness: 3.60624715441267
Generation 600, Best Fitness: 3.60780488281301
Generation 700, Best Fitness: 3.61234442064262
Generation 800, Best Fitness: 3.61234442064262
Generation 900, Best Fitness: 3.61237915839054
Generation 1000, Best Fitness: 3.61237915839054
Generation 1100, Best Fitness: 3.61243174970198
Generation 1200, Best Fitness: 3.61257107003452
Generation 1300, Best Fitness: 3.61268778306298
Generation 1400, Best Fitness: 3.61268778306298
Generation 1500, Best Fitness: 3.61268778306298
Generation 1600, Best Fitness: 3.61268778306298
Generation 1700, Best Fitness: 3.61268778306298
Generation 1800, Best Fitness: 3.61268825395901
Generation 1900, Best Fitness: 3.61268825395901
Best brain had a fitness of 3.61268825395901
Input 1: 0
Input 2: 0
Output: 0.00897356605564295
Input 1: 0
Input 2: 1
Output: 0.881275105575929
Input 1: 1
Input 2: 0
Output: 0.942749100068267
Input 1: 1
Input 2: 1
Output: 0.202362385629545

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

XOR

۰ ۰ = ۰
۰ ۱ = ۱
۱ ۰ = ۱
۱ ۱ = ۰

هوش مصنوعی آموزش دیده ما به درستی به این مسئله پاسخ می‌دهد. وقتی که ورودی‌ها ۰ و ۱ هستند، خروجی ۰٫۸۸ خواهد بود. اگر چه این خروجی به قدر ۰٫۹۹ به ۱ نزدیک نیست ولی همچنان وقتی رند می‌شود صحیح است. این هوش مصنوعی می‌تواند از تکامل بیشتری بهره‌مند شود. ما تنها ۲۰۰۰ بار این تابع را اجرا کردیم، درحالی که تابع XOR از مثال‌های قبل پیچیده‌تر است. پس از ۲۰۰۰ بار اجرا به دست آوردن میزان سازگاری ۳٫۸۴۰۸۸ پیشرفت قابل توجهی به شمار می‌رود، و خروجی‌ها به صورت زیر خواهند بود:


Generation 19900, Best Fitness: 3.84087575095576
Best brain had a fitness of 3.84088136209706
Input 1: 0
Input 2: 0
Output: 0.044799648625185
Input 1: 0
Input 2: 1
Output: 0.961866510073782
Input 1: 1
Input 2: 0
Output: 0.992772678034412
Input 1: 1
Input 2: 1
Output: 0.0689581773859488

 

با دادن ۰ و ۱ خروجی ۰٫۹۶ خواهد بود و با دادن ۱ و ۰ به عنوان ورودی، خروجی ۰٫۹۹ خواهد بود که افزایش میزان دقت بسیار مشهود است.

نتیجه گیری

توابع AND، OR و XOR خوب هستند، ولی نظرتان راجع به چیزی باحال‌تر چیست؟

ما شبکه عصبی خودمان را با یک الگوریتم ژنتیک در C#.NET برای اجرای چند تابع ریاضی ساده آموزش دادیم. ما متوجه شدیم که انتخاب یک آزمون سازگاری کلید اساسی تکامل یک شبکه عصبی است. آموزش توابع AND، OR و XOR بسیار ساده بود. در واقع برای آموزش شبکه عصبی‌مان، کاری نکردیم، هربار به راحتی تابع سازگاری را تغییر دادیم و الگوریتم ژنتیک باقی کارها را انجام داد. الگوریتم ژنتیک هر طور که شما بخواهید وابسته به تابع سازگاری تکامل می‌یابد. صد البته، شبکه شما باید از تعداد کافی نورون عصبی برخوردار باشد تا از منطق پیروی کند ولی شما هم می‌توانید در صورت لزوم آن را تنظیم کنید. فقط به خاطر داشته باشید هرچه که شبکه عصبی شما پیچیده تر باشد، زمان بیشتری برای تکامل شبکه‌هایتان نیاز دارید و برای پردازش آن به CPUای با قدرتی بالاتر نیاز دارید.

در مورد ساخت HAL، داده، یا نابودگر بعدی فکر کرده‌اید؟ شما تنها نیاز به یک تابع سازگاری درست به همراه تعداد بیشتری شبکه عصبی دارید؛ از آنجایی که هنوز تمام ظرفیت‌های شبکه‌های عصبی درک نشده‌اند، این امر که بتوان مرز‌های علم را جا به جا بسیار ممکن است.

نویسنده :

Kory Becker

درباره نویسنده
این مقاله توسط Kory Becker توسعه دهنده و طراح(معمار) نرم‌افزار نوشته شده است که به بسیاری از تکنولوژی‌ها تسلط دارد، نظیر توسعه وب، یادگیری ماشین، هوش مصنوعی و داده کاوی.

[تعداد رای:0]

درباره نویسنده

امیرحسین عراقی

امیرحسین عراقی

درج دیدگاه