پیاده سازی شبکه عصبی SOM در پایتون — راهنمای گام به گام
در آموزشهای پیشین مجله فرادرس، با پیادهسازی شبکه عصبی RBF در پایتون آشنا شدیم. در این آموزش، روش پیاده سازی شبکه عصبی SOM در پایتون را شرح میدهیم.
شبکه عصبی SOM
شبکههای عصبی SOM یا Self-Organizing Map که با نام شبکه کوهونن (Kohonen Network) نیز شناخته میشوند، یک روش غیرنظارتشده (Unsupervised Learning) برای استخراج ویژگی و کاهش ابعاد است که با وجود سادگی، توانایی زیادی از خود نشان داده است.
آموزش شبکه عصبی SOM
در این شبکه، تعدادی نورون با موقعیت اولیه تصادفی انتخاب میشوند که این نورونها در یک شبکه منظم به نام Lattice در کنار هم قرار گرفتهاند. در طول آموزش، نورونهای شبکه به مکانهایی با چگالی بیشتر داده حرکت میکنند و فرم نهایی Lattice حاصل میشود
الگوریتم آموزش SOM بهصورت زیر است:
۱. همه دادهها تک تک وارد شبکه میشوند.
۲. فاصله همه نورونها از بردار ورودی محاسبه میشود.
۳. نزدیکترین نورون به بردار ورودی تعیین و بهعنوان نورون برنده انتخاب میشود.
۴. موقعیت نورون برنده با استفاده از رابطه زیر بهروزرسانی میشود:
5. موقعیت نورونهای موجود در همسایگی نورون برنده با استفاده از رابطه زیر بهروزرسانی میشود:
به این ترتیب، با تکرار این 5 مرحله، شبکه در نهایت به موقعیت مناسبی برای هر نورون دست مییابد.
توجه داشته باشید که در مراحل 3 و 4 فاز رقابت (Competition) و در مرحله 5 فاز همکاری (Cooperation) وجود دارد و وجود این دو عامل، SOM را خاصتر میکند.
برای یادگیری برنامهنویسی با زبان پایتون، پیشنهاد میکنیم به مجموعه آموزشهای مقدماتی تا پیشرفته پایتون فرادرس مراجعه کنید که لینک آن در ادامه آورده شده است.
پیاده سازی شبکه عصبی SOM در پایتون
حال برای پیادهسازی SOM وارد محیط برنامهنویسی شده و کتابخانههای مورد نیاز را فراخوانی میکنیم:
1import numpy as np
2import matplotlib.pyplot as plt
این دو کتابخانه برای کار با آرایهها، تولید داده، آموزش مدل و رسم نمودار استفاده خواهند شد.
حال Random Seed و Plot Style را تنظیم میکنیم:
1np.random.seed(0)
2plt.style.use('ggplot')
برای پیادهسازی و آموزش مدل، به یک مجموعه داده ساده نیاز داریم. میتوانیم یک مجموعه داده دوبعدی به شکل دایره توخالی (دونات) ایجاد کنیم.
تعداد دادهها، شعاع داخلی و شعاع خارجی دایره را تعیین میکنیم:
1nD = 1000 # Data Size
2r1 = 1 # Inner Radius
3r2 = 2 # Outer Radius
حال یک آرایه برای ذخیره دادهها ایجاد میکنیم:
1nX = 2
2X = np.zeros((nD, nX)) # Placeholder for Data
عدد nX نشاندهنده تعداد ویژگیهای هر داده است که بهدلیل محدودیت در نمایش آنها، از دادههای دوبُعدی استفاده میکنیم.
حال یک حلقه While ایجاد میکنیم و تا زمانی که تعداد دادههای مورد نیاز تأمین نشده باشد، عملیات را ادامه میدهیم:
1i = 0
2while i<nD:
حال باید یک داده بهصورت تصادفی درون مربع اول انتخاب کنیم:
1i = 0
2while i<nD:
3 x = np.random.uniform(-r2, +r2, nX)
حال باید شعاع داده از مرکز مختصات را حساب کنیم و در صورتی که داده دارای شعاعی بین و بود، آن را بهعنوان داده جدید به مجموعه داده اضافه کنیم:
1i = 0
2while i<nD:
3 x = np.random.uniform(-r2, +r2, nX)
4 r = np.linalg.norm(x)
5 if r1 <= r <= r2:
6 X[i] = x
7 i += 1
به این ترتیب، دادههای مورد نیاز ایجاد میشود.
حال برای مصورسازی میتوانیم بهشکل زیر عمل کنیم:
1plt.scatter(X[:, 0], X[:, 1], s=12, label='Data')
2plt.title('Created Dataset')
3plt.xlabel('X1')
4plt.ylabel('X2')
5plt.legend()
6plt.show()
نتیجه این مصورسازی در نمودار زیر نشان داده شده است.
به این صورت، مجموعه داده مورد نیاز تولید میشود.
حال برای آموزش مدل، ابتدا موقعیت اولیه نورونها را بهصورت تصادفی انتخاب میکنیم:
1nK = 20 # Kohonen Neurons Count
2W = np.random.uniform(-r2, +r2, (nK, nX)) # Kohonen Neurons Position
توجه داشته باشید که میتوان موقعیتهای اولیه را به صورت منظم نیز انتخاب کرد.
برای رسم نورونها روی مجموعه داده خواهیم داشت:
1plt.scatter(X[:, 0], X[:, 1], s=12, label='Data')
2plt.plot(W[:, 0], W[:, 1], lw=1, c='b', marker='o', ms=4, label='Neurons')
3plt.title('Created Dataset + Neurons Initial Position')
4plt.xlabel('X1')
5plt.ylabel('X2')
6plt.legend()
7plt.show()
در نهایت نمودار زیر حاصل میشود.
به این ترتیب، موقعیت نورونها نیز مقداردهی اولیه میشود.
توجه داشته باشید که خطوط بین هر نورون، نشاندهنده همسایگی بین آنها است.
حال باید پارامترهای مربوط به آموزش مدل را تعیین کنیم:
1nEpochs = 50 # Model Learning Epochs Count
2LR = 0.5 # Learning Rate
3dLR = -0.01 # Learning Rate Linear Decay
4Theta = 1/4 # Cooperation Coefficient
حال میتوانیم حلقه اصلی آموزش مدل را ایجاد کنیم:
1for i in range(nEpochs):
حال بهازای هر داده باید مراحل گفته شده انجام شود، پس:
1for i in range(nEpochs):
2 for x in X:
حال باید فاصله داده از هر نورون را محاسبه کنیم:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
حال با استفاده از Argmin میتوانیم نورون برنده را تعیین کنیم:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
4 J = np.argmin(D)
به این ترتیب، نورون برنده تعیین شد. حال باید موقعیت نورون برنده را بهروزرسانی کنیم:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
4 J = np.argmin(D)
5 W[J] += LR * (x - W[J])
حال باید فاز همکاری را نیز وارد کد کنیم. با توجه به اینکه یک Lattice یکبُعدی استفاده میشود، برای نورون برنده J دو نورون J+1 و J-1 همسایه انتخاب و بهروزرسانی میشوند:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
4 J = np.argmin(D)
5 W[J] += LR * (x - W[J])
6 W[J+1] += Theta * LR * (x - W[J+1])
7 W[J-1] += Theta * LR * (x - W[J-1])
این کد برای نورونها موجود در اواسط Lattice درست کار میکند، اما برای نورونهای موجود در مرزهای Lattice دچار خطا میشود، بنابراین، باید شرطهایی بهصورت زیر اضافه کنیم:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
4 J = np.argmin(D)
5 W[J] += LR * (x - W[J])
6 if J == 0:
7 W[J+1] += Theta * LR * (x - W[J+1])
8 elif J == nK-1:
9 W[J-1] += Theta * LR * (x - W[J-1])
10 else:
11 W[J+1] += Theta * LR * (x - W[J+1])
12 W[J-1] += Theta * LR * (x - W[J-1])
به این ترتیب، همه مراحل پیادهسازی شدند.
حال در انتهای Epoch نرخ یادگیری را بهروزرسانی میکنیم و پیام اتمام مرحله را مینویسیم:
1for i in range(nEpochs):
2 for x in X:
3 D = np.linalg.norm(W - x, axis=1)
4 J = np.argmin(D)
5 W[J] += LR * (x - W[J])
6 if J == 0:
7 W[J+1] += Theta * LR * (x - W[J+1])
8 elif J == nK-1:
9 W[J-1] += Theta * LR * (x - W[J-1])
10 else:
11 W[J+1] += Theta * LR * (x - W[J+1])
12 W[J-1] += Theta * LR * (x - W[J-1])
13 LR += dLR
14 print(f'Epoch {i+1} Ended.')
به این ترتیب، آموزش مدل به اتمام میرسد. حال برای مشاهده نتایج، بهصورت زیر عمل میکنیم:
1plt.scatter(X[:, 0], X[:, 1], s=12, label='Data')
2plt.plot(W[:, 0], W[:, 1], lw=1, c='b', marker='o', ms=4, label='Neurons')
3plt.title('Created Dataset + Neurons Final Position')
4plt.xlabel('X1')
5plt.ylabel('X2')
6plt.legend()
7plt.show()
که نمودار نهایی زیر حاصل میشود.
به این ترتیب، مشاهده میکنیم که نورونها وضعیت مناسبی به خود میگیرند. حال اگر تعداد نورونها را از 20 به 100 عدد برسانیم، نمودار زیر حاصل خواهد شد.
مشاهده میکنیم که شبکه همه دادهها را بهخوبی پوشش داده شده است، اما بهدلیل زیاد بودن تعداد نورونها نسبت به پیچیدگی مسئله، مشکلاتی در آموزش آن رخ داده است. اگر تعداد مناسبی از نورونها انتخاب کنیم، حالتی میان این دو نمودار خواهیم داشت. شکل زیر این موضوع را بهخوبی نشان میدهد.
مشاهده میکنیم که نورونها بهصورت همگن پخش شدهاند و همه نورونها در موقعیت مناسبی درون دادهها قرار دارند.
اگر ضریب را از به کاهش دهیم، نتایج نهایی بهشکل زیر خواهد بود.
مشاهده میکنیم که نظم موجود در حالت قبلی در این نتایج وجود ندارد که نشاندهنده اهمیت مقدار است.
جمعبندی
در این مطلب به پیاده سازی شبکه عصبی SOM در پایتون پرداختیم. برای بررسیها بیشتر، میتوان موارد زیر را بررسی کرد:
- مقداردهی اولیه به موقعیت نورونها بهصورت Lattice منظم
- بررسی مقدار اینرسی در طول آموزش شبکه
- بررسی روند آموزش مدل در شرایطی که نرخ یادگیری ثابت است