اين مقاله در رابطه با اوزان ورودي و نحوه مديريت آنها نوشته شده است. وزن دهي
در مقادير ورودي زماني مورد استفاده قرار مي گيرد كه Agent در مقابله با تعداد زيادي راه قرار دارد و
ميبايستي يكي از اين راهها را انتخاب نمايد. استفاده از وزن دهي در مقادير
ورودي، باعث ميشود تا Agent بهترين راه را در وضعيت فعلي انتخاب نمايد.
اين مقاله ابتدا توضيحاتي مختصر در اين رابطه ميدهد و سپس مسائل مربوط به اوزان
را شرح خواهد داد. به منظور بهتر مشخص شدن مثالهاي ورودي از شبيه سازي فوتبال ٢
بعدي به عنوان محيط استفاده گرديده است.
١- مقادير ورودي
ابتدا بايد مشخص كنيم مقادير ورودي چيستند و چگونه بايد مورد استفاده قرار
گيرند. فرض كنيد شما يك بازيكن(Agent) در زمين بازي هستيد. چه متغيرهايي بر نحوه
بازي شما اثر ميگذارند ؟
· ميزان خستگي بازيكن
· تعداد هم تيميهاي نزديك به اين بازيكن
· تعداد بازيكنان حريف موجود در مقابل
· فاصله شما تا دروازه حريف
· فاصله شما تا دروازه خودي
· فاصله شما با اولين حريف
· موقعيت شما در زمين بازي
· موقعيت ياران هم دادي كه قابل ديد هستند
· و ...
پس مرحله اول تهيه ليستي از متغيرهاي ورودي ميباشد كه بر نحوه بازي كردن يك
بازيكن تاثير مستقيم يا عكس دارند. براي مثال در ليست نمونه كه ذكر شد، ميزان
خستگي بازيكن درحركت كردن بازيكن نقش مستقيم دارد. تعداد هم تيميهاي نزديك به
اين بازيكن شانس پاس دادن را زياد ميكند پس رابطه مستقيم دارد. تعداد بازيكنان
حريف به بازيكن احتمال خطر از دست رفتن توپ را دارد پس نقش عكس دارد و تا انتها
به همين صورت.
بعد از تهيه ليستي از متغيرهاي ورودي ميبايستي اين متغيرها توسط توابعي توليد
شوند. براي مثال ميزان خستگي بازيكن ميبايستي توسط تابعي براي ما فراهم شود كه
در محيط نمونه ما عددي بين ٠ تا ٤٠٠٠ ميباشد. براي تمامي متغيرهاي ورودي ميبايستي
مقادير متناظر در محيط گرد آوري شوند و در ليستي در كنار نام ورودي وارد شوند.
براي مثال:
enumInputTypes
{
INPUT_STAMINA,
INPUT_NEAR_TEAMMATE_PLAYER,
INPUT_NEAR_OPPONENT_PLAYERS,
...
...
INPUT_MAX_ITEM
};
structInputValue
{
unsigned int value;
doubleweight;
};
struct InputValues
{
InputValuevalue[INPUT_MAX_ITEM];
};
بعد از وارد تهيه كردن ليستي از مقادير ورودي، حال ميبايستي اين مقادير مورد
آناليز قرار گيرند.
٢- عملياتها
منظور از عمليات، تمامي اعماليست كه يك بازيكن ميتواند در لحضات تصميم گيري،
آنها را اجرا نمايد. اين اعمال ميبايستي داراي شرايطي از جمله، سطح بالا بودن،
يكتا بودن و كامل بودن داشته باشند.
منظور از سطح بالا بودن، يعني عملي كه مجموعهاي از اعمال منفرد و كوچك باشد.
براي مثال عمل دريبل كردن يك عمل سطح بالا محسوب ميشود زيرا از دو عمل كوچكتر
حركت كردن و شوت كردن استفاده ميكند. منظور از يكتا بودن، اين است كه اعمال
نمونه مشابهي نبايد داشته باشند، اگر نمونه مشابهي دارند ميبايستي اين نمونهها
در يك عمل سطح بالاتر جمع آوري شود و عمل سطح بالاتر مورد استفاده قرار گيرد و
در نهايت منظور از كامل بودن اين است كه اين اعمال نبايستي به اعمال سطح بالاي
ديگر وابسته باشند، يعني شرط لازم براي اجراي آنها تنها دستور مستقيم Agent باشد و نه اينكه نياز به شرايط خاص ديگري
داشته باشند.
بعد از مرحله اول يعني جمع آوري ليست وروديهاي محيط، شما ميبايد مكانيزمي شبيه
به آن را براي اعمال Agent نيز فراهم آوريد. يعني ليستي تهيه كنيد از
كليه اعمالي كه Agent ميتواند انجام دهد.
بسيار خوب. تا اينجا ما دو ليست داريم، ليستي از وروديهايي كه يك بازيكن بر حسب
آنها ميتواند عملي خاص را انتخاب نمايد و ليستي از اعمال كه با توجه به پارامترهاي
ورودي ميبايستي يكي از آنها اجرا شود.
كار اصلي سيستم وزن دهي اينجا شروع ميشود. وظيفه اصلي سيستم وزن دهي انتخاب يكي
از اين اعمال با توجه به مقادير ورودي است. نمونه كدي كه در كلاس ربوكاپ امسال،
از دانش آموزان خواسته شد تا پياده سازي كنند، بدين صورت بود:
SoccerCommandAgentAction()
{
if (canShootToGoal())
returnshootToGoal();
if (canPassTeammates())
returnpassTeamMates();
if (canDribbleForward())
returndribbleForward();
if (myPos.getX() < 0)
returnClearBall();
else
return HoldBall();
}
اين ترتيب بندي، بعني ابتدا شوت، سپس پاس، سپس دريبل و اگر نشد در صورتي كه در نيمه
خودي بود، دفع توپ و در غير اين صورت نگداري توپ، ميتواند يك نمونه ايدهال
براي يك Agent باشد ولي آيا
· اين ترتيب بندي درست است ؟
· اگر درست است، آيا هميشه درست است؟
· اگر هميشه درست عمل ميكند، آيا روش بهتري
هم هست؟
وزنها به ما كمك ميكنند تا اين مشكلات را حل كنيم. يعني بدون توجه به شرايطي
خاص، با توجه به مقادير ورودي، عمل مورد نياز را انتخاب نماييم. گروهي از تيمها،
امسال برنامههايي شبيه به كد زير را نوشته بودند:
SoccerCommand AgentAction()
{
if (canShootToGoal())
return shootToGoal();
if (myPos.getX() < 0)
{
if (canPassTeammates())
returnpassTeamMates();
if (canDribbleForward())
returndribbleForward();
}
else
{
if (canDribbleForward())
return dribbleForward();
if (canPassTeammates())
return passTeamMates();
}
if (myPos.getX() < 0)
return ClearBall();
else
returnHoldBall();
}
در اينجا، ابتدا چك شده است كه آيا در نيمه خودي هستيم يا نه؟ اگر در نيمه خودي
هستيم، پاس بر دريبل ارجهيت دارد اما در نيمه حريف بر عكس اين موضوع.
در اينجا مشكلات زير پيش خواهد آمد:
· اگر تيم حريف دفاعي عمل كند، و تمامي دريبلها
خنثي شوند چطور؟
· اگر تيم حريف در نيمه خودي به صورت پيشرفته
عمل Mark را انجام دهد چطور؟
و بسياري مشكلات ديگر. يك راه براي رفع اين مشكلات، استفاده از تعدادي زيادي if ميباشد. يعني شما براي تمامي شرايط قابل
پيش بيني شرايط لازم را قرار دهيد كه اين باعث افت سرعت تيم، سر درگمي در كد، به
هم ريختگي الگوريتمها و درنهايت عدم پيادهسازي يك شرط خاص.
سوال اصلي اينجاست: چرا به بازيكن امكان پيدا كردن اين شرايط و استفاده بهينه از
اعمال را ندهيم.
حال براي شما، دليل استفاده از اوزان به طور كامل مشخص است. استفاده از اوزان به
ما كمك ميكند تا شرايط پيشبيني نشده را مديريت كنيم و آنها را در ادامه بازي
بهينه نماييم. همچنين وزن دهي به ما كمك ميكند تيمي پويا داشته باشيم و از
تعداد زياد شروط نيز جلوگيري به عمل خواهد آمد.
وزن دهي
حال ببينيم وزن دهي چيست. اگر به ياد داشته باشيد، دو ليست تهيه گرديدند، ليست
اول به منظور ذخيره سازي وروديها و ليست دوم به منظور ذخيره سازي اعمال Agent.
حال بايد چكار كنيم ؟
SoccerCommandAgentAction()
{
return What?HUH?Which Of Them?OK? Merci? NoOne?
}
انتخاب اعمال به طور كامل بستگي به نظر بازيكن دارد. پس مسئله اول، به جاي ديكته
كردن ليست دستورات و شروط آنها به Agent ميبايد براي او نظر !!!! بسازيم.
٣- توليد اوزان
اگر به ليست وروديها نگاه كنيد، هر ورودي داراي يك مقدار به نام value و يا ارزش ميباشد. اين ارزش ميزانياست كه
ما براي آن ورودي در نظر گرفته ايم( در پياده سازي به خاطر داشته باشيد كه هميشه
مقادري عددي نيستند براي مثال ورودي با نوع - مختصات بازيكن – نوعي برداري است و
ميبايد متغيرهاي value با نوعهاي مختلف براي جوابگو بودن به كليه
نوعهاي ورودي فراهم شود.). براي استفاده از اين ارزش در وزن دهي نياز به تابع
خروجي ساز داريم. اين تابع با بررسي كليه وروديها از روي مقدارشان اقدام به
توليد يك خروجي double ميكند و اين مقدار double در اصل همان مقداري است كه از روي متغيرهاي
ورودي ساخته شده است و در يك عمل خاص مورد استفاده قرار ميگيرد.
اما اين مقدار چگونه بايد تهيه شود؟
با يك مثال مطالب بيان شده را بهتر روشن ميكنيم. فرض كنيد مقادير ورودي ما
باشند:
· خستگي بازيكن از نوع مستقيم
· تعداد بازيكنان هم تيمي مقابل از نوع مستقيم
· تعداد بازيكنان حريف مقابل از نوع معكوس
ما تابعي تهيه ميكنيم به نام ResultValue كه وظيفه آن تهيه مقدار مناسب براي دريبل
زدن ميباشد.
به علامت منفي در پشت InputValues.value[INPUT_NEAR_OPPONENT_PLAYERS].value دقت كنيد كه به معناي اين است كه اين مقدار،
رابطه معكوس دارد.
اين تابع عددي را بر ميگرداند كه با توجه به ٣ مقدار ورودي مشخص شده، نشان ميدهد
كه بازيكن چه مقدار شانس براي دريبل زدن دارد.
اما اولين مشكل اينجا مشخص ميشود:
فرض كنيد بازيكني داريم با خستگي ٣٨٠٠ و ٢ هم تيمي و ٥ حريف در مقابل و بازيكن
ديگري داريم با خستگي ٣٧٠٠ و ٢ هم تيمي و ١ حريف در مقابل.
مقادير خروجي به صورت مقابل مشخص ميشوند:
Player1: 3800 + 2 – 5 = 3797
Player2: 3700 + 2 – 1 = 3701
ميزان خروجي براي بازيكن اول بيشتر از ميزان خروجي براي بازيكن دوم است، ولي آيا
بازيكن اول شانس بيشتري براي دريبل دارد ؟ خير اينطور نيست. دليل اين اشكال نا
متناسب بودن مقادير ورودي با هم است. براي نمونه خستگي از ٠ تا ٤٠٠٠ و تعداد
بازيكنان حريف از ١ تا ١١. براي رفع اين مشكل وزن دهي وارد كار ميشود و مقادير
را به تعادل ميرساند. اگر به struct InputValue نگاه كنيد مقداري داريم به نام weight كه ميبايستي توسط شما در ابتداي بازي پر
شود. اين مقدار مشخص كننده ميزان شكست و يا رشد يك متغير در مقابل بقيه متغيرهاي
ورودي ميباشد. همچنين تابع شما ميبايد به صورت زير بازنويسي شود:
به وزني كه براي INPUT_STAMINA مشخص شده است نگاه كنيد. اين وزن معادل ٢٠٠/١ ميباشد بدين معني
كه ميزان Stamina قبل از محاسبه در مقدار خروجي ميبايد ٢٠٠/١ شود. اين باعث ميشود
كه بازيكني كه داراي خستگي ٣٦٠٠ مي باشد معادل بازيكني باشد كه خستگي ٤٠٠٠ دارد
و ٢ بازيكن در مقابل او هستند!!!
3600 / 200 = 18
18 + 0 = 0
4000 / 200 = 20
20 – 2(بازيكنان حريف) = 18
18 = 18
حال مشكل بسيار بزرگ بودن خستگي، از بين رفت. در بخشهاي بعدي ياد خواهيم گرفت
تا اين مقادير در حين بازي تغيير كنند.
٤- وزن دهي دستورات
حال ما توانستيم براي عمل دريبل كردن، يك مقدار عددي تهيه كنيم، كه در مراحل
مختلف بازي اين مقدار براي يك بازيكن ميتواند به صورت مقابل باشد:
كه براي مثال نشان دهنده يك بازيكن خودي در حال حمله است كه ميتوانيد مشاهده
كنيد به دليل رشد بازيكنان حريف در حركت، كاهش Stamina و تعداد بازيكنان خودي رشدي نزولي دارد.
ما بايستي توابعي به صورت تابع دريبل براي عمليات ديگر نيز تهيه كنيم. براي مثال
براي دستور پاس دادن بايد تابعي تهيه كنيم كه مقداري double را بر گرداند.
حال به جاي استفاده از تعداد زيادي if مي توانيم بعد از اتمام عمليات خروجي سازي،
عملي را كه داراي خروجي بيشتر است مشخص نماييم و آن عمل را براي بازيكن انتخاب
نماييم.
اما!!!!!!!!!!!!!!!!!!!!!
وزن دهي كه در نمونه قبل انجام شد، نمونه اي از وزن دهي ثابت ميباشد كه استفاده
از آن در تيم هيچ مزيتي را در مقابل تيم هاي پويا كه هميشه در حال تغييرند
ندارد و تقريبا مانند همان نمونههاي if عمل ميكند.
وظيفه اصلي وزن دهي، وزن دهي دستورات است كه از دو جهت قابل بررسي ميباشد:
وزن دهي دستورات
دليل اول- فرض كنيد تابع خروجي ساز پاس هميشه مقداري بين ١ تا ٦٠ برگرداند، آيا
اين مقدار با مقدار تابع دريبل كه معمولا بين ٤ تا ٢٥ هستند مساوي است. پس بايد
مانند مقادير ورودي براي دستورات نيز وزنهايي را قائل شويم. براي مثال براي عمل
پاس دادن وزن ٣/١ و براي عمل دريبل وزن ١.
دليل دوم – اين دليل بسيار مهمتر از دليل اول ميباشد. فرض كنيد ما وزندهي ها
را به صورت ثابت (٣/١ و ١) براي دستورات مشخص كرديم و در حال اجراي تيم مشخص شد
كه اوزان تعيين شده مناسب تيم مقابل نيستند.
اينجا ميبايستي از تكنيك تعديل وزنها استفاده شود.
تكنيك تعديل وزنها
تكنيك تعديل وزنها بدين معنياست كه شما بايد، با توجه به عملكرد خود در بازي،
ميزان وزنهاي خود را كاهش و يا افزايش دهيد.
براي مثال به ازاي هر پاس صحيح كه به يك هم تيمي ميرسد، 0.3 به وزن پاس خود
اضافه نماييد. اين باعث ميشود كه ميزان پاسهاي شما در طولاني مدت بر دريبلهايي
كه در اول بازي شما انجام ميداديد ارجح تر شوند. يا براي مثال به ازاي هر
دريبلي نا موفق، وزن دريبل خود را 0.3 كاهش دهيد كه باز اين مورد باعث ميشود كه
شما عمل دريبل كردن را در مقابل تيم حريف كمتر انجام دهيد.
اين تكنيك پياده شده باعث هوشمند شدن تيم شما ميشود و تا حدي به نياز خود مبني
بر پويا بودن تيمتان رسيده ايد.
اما در تعديل وزنها مشكلي اساسي وجود دارد. فرض كنيد بازيكن شما، در نيمه خودي
شروع به دريبل زدن كند و بازيكن حريف توپ را از وي بگيرد. بازيكن خودي در اينجا
وزن دريبل را كاهش ميدهد ولي آيا در نيمه حريف نيز، ميبايد با وزن كاهش داده
شده عمليات را انتخاب كرد ؟
رفع مشكل تعديل وزنها
براي رفع مشكل تعديل وزنها، در شرايط مختلف، بهتر است به جاي كاستن از مقدار
وزن يك دستور و يا افزايش آن، متغير جديدي تهيه كنيد به نام WeightOffset به معني انحراف وزن دستور. به مقدار اوليه
وزن دستور، كه بر اساس تفاوتهاي دستورات مختلف (مانند مثالي كه در مورد تابع
خروجي پاس و دريبل زده شد) دست نزنيد و در هنگام استفاده از وزن دستور، براي
توليد وزن اصلي دستور مقدار آنرا با مقدار WeightOffset جمع كنيد:
double RealWeight = Weight + WeightOffset;
حال مسئله اصلي توليد اين مقدار انحراف ميباشد.
مسئله انحراف وزنها
لطفا پاراگراف زير خوانده نشود و براي ادامه بحث به پاراگراف بعدي برويد!!!!!
يكي از بدبختيايه هوش مصنوعي، استفاده زياد اون از حافظس، براي محاسبه انحراف
وزنها به يك عالمه حافظه نياز داريد كه مديريت اين حافظه، الگوريتمهاي جستجو
تو اين حافظه، سورتش و بقيش با خودتون، چون هيچ ربطي به بحث ما نداره، منم بلد
نيستم. اگه پايهاي برو خودت stl رو بخون كلي كمكت ميكنه مثلا
واره مثال، اول پاراگراف بعدي رو بخون بعد بيا دوباره اينجا. برگشتي، تو اون
حالت سادههه، تو ١٠٠٠ سيكل فرض كن ٨٠ تا دريبل صدا بزني، ٣٠ تا پاس و ٥ تا شوت
كه ميشه يه ليسته ١٣٥ تايي از عملها و مقادير ورودي اون عمل. ليستت بايد پويا
باشه، يعني كمو زياد شده، بعدم اينكه سرعت داشته باشه. ديگه با خودت فقط ميگم
خدا بابا stl رو بيامرزه
براي محاسبه انحراف وزنها، شما بايد ليستي بزرگي را از وروديها و دستوري كه بر
حسب آن ورودي توليد شد، توليد كنيد. اين ليست در طولاني مدت داراي حجم زيادي
خواهد شد كه مديريت آن ميتواند در حالتهاي پيشرفته و يا حالتهاي ساده
مثل خارج كردن سيكلهايي كه بيشتر از ١٠٠٠ سيكلقبل هستند باشد.
حال ما يك ليست از دستوراتي كه در ١٠٠٠ سيكل قبل انجام داده ايم، به همراه تابعهاي
ورودي آنها و يك متغير به منظور مشخص كردن اينكه آيا آن عمل انجام شد يا نه در اختيار
داريم.
structEachCycleAction
{
InputValues v;
OutputCommandTypec;
bool actionDoneSuccessfuly;
};
حال، براي اينكه مطالب از هم خيلي دور نشوند، يكبار ديگر مرور ميكنيم.
در اين سيكل ما تعدادي ورودي داريم، كه با وزنهاي مختلف تبديل به يك مقدار عددي
شده است. ميخواهيم اين مقدار عددي را در وزن دستورات ضرب كنيم و دستوري كه
مقدار بيشتري را توليد كرد اجرا نماييم. براي هر دستور وزني داريم به نام Weight و ميخواهيم با محاسبه WeightOffset وزن بهينه دستور را محاسبه نماييم. براي
محاسبه WeightOffset ميبايد ليستي تهيه كنيم و دستورات اجرا شده در هر Cycle را درون آن قرار دهيم كه به آن حافظه
بازيكن(Agent Mind) گفته ميشود. حال ميخواهيم با اين ليست WeightOffset را محاسبه كنيم.
در بين وروديها، شبيه ترين ورودي را به وروديهاي حالت فعلي پيدا كنيد. در
صورتي كه آن دستور به طور موفقيت آميز انجام شده است، ميزان انحراف وزن آن
دستور، در اين سيكل را افزايش دهيد و در صورتي كه موفقيت آميز نبوده است،
ميزان انحراف را كاهش دهيد(توجه داشته باشيد، كه وزنهاي پايه (Weight) بايد به صورتي باشد، كه بعد از محاسبه
وروديها و ضرب كردن آن در اين وزن، اعداد توليد شده براي دستورات مختلف داراي
مقاديري نزديك به هم باشد. براي مثال، در صورتي كه شرايط پاس و دريبل بسيار
عاليست، مقدار توليد شده براي دستور پاس ١ و براي دستور دريبل ٠.٩٨ باشد. در اين
صورت است كه انجراف وزنها ميتواند مفيد باشد)
هدف از نوشتن اين مقاله، آشنايي شما با اصول پايهي وزنها كه يكي از پايههاي
هوش مصنوعي است ميباشد. براي محاسبه انحراف وزنها روشهاي بسيار پيشرفته تري
وجود دارد براي مثال:
توليد يك تابع، از حافظه بازيكن و پيدا كردن نقاط Max اين تابع. اين نقاط Max نقاطي است كه بازيكن در آنها، بهترين حالت
آن دستور را اجرا كرده است و وزنها طوري تعيين ميشود كه بازي به سمت آن ورودي ها
برود.
از همين روش ميشود براي وزنهاي وروديهم استفاده كرد، براي مثال، تعداد
بازيكنان حريف اطراف بسيار مهمتر از بازيكنان خودي اطراف ميباشد. پس وزن
بازيكنان خودي ١ و وزن بازيكنان حريف ١.٢.
اگر بازيكنان خودي دورتر باشند امكان پاس دادن به آنها، در صورتي كه دور بازيكن
پر از بازيكنان حريف است بهتر است. پس به جاي مقدار ١ براي بازيكنان خودي مقدار
١- را در نظر ميگيريم!!!!!!!!!!!!!!!
همانطور كه ميبينيد ميتوان براي تمامي اعداد استدلالهايي را بيان كرد و پيدا
كردن تمامي الگوريتمهاي وزني، به صورتي كه در اين مقاله ذكر شد كار بسيار
دشوار و پيچيدهايست. همچنين زمان زيادي را نياز دارد.
تعداد زيادي الگوريتم در ساليان طولاني گرد آوري شدند و افراد زيادي بر روي آنها
نظر دهي كردند، تا در نهايت:
<< Q Learning >>
توليد گرديد( بخوانيد كيو لرنينگ). Q Learning مجموعهاي از الگوريتمها ميباشد كه با
استفاده از WorldModel كه شما براي آن مشخص ميكنيد، يكي از دستوراتي را كه شما در
ليست دستورات مشخص كردهايد براي شما انتخاب ميكنيد. همانطور كه از اسمش
پيداست، اين مجموعه از الگوريتم ها باعث يادگيري بازيكن ميشوند و در بازي كردن
به او كمك خواهند كرد.
+ نوشته شده توسط معین اوحدی کارشک در دوشنبه بیست و هشتم بهمن 1387 و ساعت
16:56 |