همواره یکی از دغدغههای وبمسترها و صاحبین کسبوکارهای آنلاین این است که با ارائهٔ محتواهای مرتبط با یک مقاله، محصول و غیره، از یک سو تجربهٔ کاربری بهتری برای کاربران هدف رقم بزنند و از سوی دیگر درآمدزایی خود را با ماندگاری بیشتر ایشان در سایت بیشتر نمایند. آنچه در این مقاله قصد داریم مورد بررسی قرار دهیم، طراحی الگوریتمی است که چنانچه درست پیادهسازی گردد، میتواند بهسادگی محتواهای مرتبط را یافته و درمعرض دید کاربران قرار دهد.
در گذشته -بهخصوص در سیستمهای مدیریت محتوایی همچون وردپرس و غیره- محتوای یک سایت در چندین Category (دستهبندی) مختلف قرار میگرفت به بدین شکل محتوای سایت طبقهبندی میشد اما استفاده از کتگوری چندین نقطه ضعف عمده داشت (البته امروزه هم خیلی از وبمسترها از کتگوریهای برای دستهبندی محتوای سایت خود استفاده میکنند).
اول اینکه در طول زمان ممکن بود به این نتیجه برسیم که یک دستهبندی بایستی حذف شده و یا با دستهٔ دیگی ادغام گردد؛ علاوهبر این، ممکن بود در آینده دستههای جدید بیفزاییم و محتواهای قبلی را به دستهبندی جدید انتقال دهیم و مشکلاتی از این دست.
اما آنچه امروزه ترند شده است، استفاده از Tag (تگ یا برچسب) برای دستهبندی محتوای مختلف است. بهعبارت دیگر، بااستفاده از تگها میتوانیم با یک تیر دو نشان را بزنیم: هم دستهبندی محتواها را مشخص ساخته و هم ارتباط محتوا با دیگر محتواها را یافته و بهعنوان محتوای مرتبط درمعرض دید کاربران قرار دهیم.
یکی از راهکارهایی که درعمل سادهترین کار اما درعینحال نامؤثرترین راه میباشد این است که اولین تگ درنظر گرفته شده برای محتوای مدنظر را گرفته و دیگر محتواهایی که دارای آن تگ هستند را از دیتابیس فراخوانی کرده و بهعنوان محتوای بهاصطلاح مرتبط به کاربر نشان دهیم اما این درحالی است که این روش اصلاً اثربخش نبوده و ضریب خطای بالایی دارد.
فرض کنیم مقالهای داریم تحتعنوان MicroPython: نسخهٔ کاستومایز شدهای از زبان برنامهنویسی پایتون برای دیوایسهای امبدد که دارای تگهای برنامهنویسی، پایتون، میکروپایتون و سی است. همانطور که مشخص است، اولین تگ این مقاله برنامهنویسی است و درصورتیکه بخواهیم دیگر محتواهای مرتبط با این مقاله را از دیتابیس فراخوانی کنیم، اسکیوالی مینویسم با این مضمون که هرآنچه محتوا با تگ برنامهنویسی است را یافته و در دسترسمان قرار دهد.
در چنین شرایطی، مقالاتی همچون آشنایی با EditorConfig: مدیریت استایل سورسکد در ادیتورهای مختلف، چگونه به شکلی حرفهای دست به Code Review بزنیم؟ و راهکارهای گوگل جهت تست نرمافزار در سرویس بهداشتی! بهطورحتم جزو پیشنهادات چنین بهاصطلاح الگوریتمی خواهد بود!
گرچه هر ۴ مقاله بهنوعی مرتبط با برچسب برنامهنویسی است، اما اگر بخواهیم ریزبینانه نگاه کنیم، در ۳ مقالهٔ پیشنهادی هیچ رنگ و بویی از تگ دوم مقاله -یعنی پایتون- دیده نمیشود و کاربری که این مقاله را میخواند به احتمال زیاد انتظار دارد تا دیگر مقالات پیشنهادی بهنوعی مرتبط با زبان برنامهنویسی پایتون باشند.
طراحی الگورتیمی جهت یافتن محتواهای مرتبط
در الگوریتمی که قرار است طراحی کنیم، پیش از هرچیز میبایست با مفهومی تحتعنوان K-nearest Neighbors Algorithm (الگوریتم نزدیکترین همسایه) آشنا شد. بهعبارت دیگر، برای هر پست میبایست نزدیکترین تگهایی که با محتوا سازگاری دارند را برگزید و این درحالی است که تگها نیز دارای رنک یا رتبهبندی هستند، از شبیهترین تگ به کمشباهتترین تگ.
برای استفاده از این اَپروچ (رویکرد)، میبایست راهی بیابیم تا پستها را براساس تگهایشان با یکدیگر مقایسه کنیم که یکی از اینراهها، نمایش برداری پستها است و بااستفاده از مفهومی تحتعنوان Cosine Similarity (مشابهت کسینوسی) امکانپذیر است. بهعبارت دیگر، مشابهت کسینوسی عبارت است از کسینوس زاویهٔ بین ۲ بردار.
بهخاطر داشته باشیم ۲ پستی که دقیقاً بردارهایی به یک جهت داشته باشند (یعنی زاویهٔ ۲ بردار برابر با ۰ درجه باشد) دارای مشابهت کسینوسی ۱ هستند و این درحالی است که اگر بردارهای ۲ پست دارای جهات متضاد یکدیگر باشند (یعنی زاویهٔ ۲ بردار ۹۰ درجه باشد)، دارای مشابهت کسینوسی ۰ هستند. با این تفاسیر، میتوانیم فرمولی برای یافتن میزان مشابهت ۲ پست درنظر بگیریم:
آموزش پیادهسازی طراحی الگورتیمی جهت یافتن مقالات مرتبط در وبسایت
همانطور که در تصویر فوق مشاهده میشود، ۲ بردار تحت عناوین A و B داریم؛ حال میبایست بااستفاده از مفهومی تحتعنوان Dot Product (ضرب داخلی)، به میزان مشابهت این ۲ بردار پی ببریم. بهطور خلاصه، ضرب داخلی یک عمل ریاضیاتی دوتایی مابین ۲ بردار در فضای nبعدی است که نتیجهٔ آن یک عدد حقیقی است.
بهعبارت دیگر، با نمایش کلیهٔ پستها بهعنوان ماتریسی با n ردیف که هر ردیف هم نشانگر بردار یک پست است، میتوان بهسادگی یک ضرب داخلی با پست مدنظر انجام داده و برداری nبعدی از میزان مشابهت پست مدنظر با سایر پستها بهدست آورد.
بازهم اگر بخواهیم سادهتر توضیح دهیم، میتوان گفت که ضرب داخلی ۲ بردار A و B برابر است با اندازهٔ تصویر بردار A بر روی بردار B که در تصویر فوق با نقطهچین مشخص شده است. در یک کلام، یعنی تعداد تگهای مشترک مابین ۲ پست وبسایت.
برای روشنتر شدن این مسئله، مجدد به مثال مقالات فوق بازمیگردیم. مقالهٔ MicroPython: نسخهٔ کاستومایز شدهای از زبان برنامهنویسی پایتون برای دیوایسهای امبدد که از این پس آنرا تحتعنوان بردار A درنظر میگیریم دارای تگهای زیر است:
- برنامهنویسی
- پایتون
- میکروپایتون
- سی
و مقالهای همچون آشنایی با EditorConfig: مدیریت استایل سورسکد در ادیتورهای مختلف که از این پس آنرا تحتعنوان بردار B درنظر میگیریم دارای تگهای زیر است:
- برنامهنویسی
- ویرایشگر کد
با این تفاسیر، ضرب داخلی ۲ بردار A و B میشود عدد ۱ چراکه فقط و فقط ۱ تگ (برنامهنویسی) مابین این ۲ بردار مشترک است. حال نیاز به فرمولی داریم تا درصد مشابهت این ۲ بردار را در بیاوریم که فرمول زیر اینکار را برایمان انجام خواهد داد:
آموزش پیادهسازی طراحی الگورتیمی جهت یافتن مقالات مرتبط در وبسایت
در تفسیر فرمول فوق بهطور خلاصه، بایستی بگوییم که میزان Similarity (مشابهت) برابر است با میزان ضرب داخلی بردارهای A و B تقسیم بر اندازهٔ بردار A ضرب در اندازهٔ بردار B (درواقع، در مثال فوق، اندازهٔ بردار A برابر است با تعداد تگهای این مقاله که برابر با ۴ است و اندازهٔ بردار B برابر است با تعداد تگهای مقالهٔ B که مساوی است با ۲).
درحقیقت، اگر بخواهیم میزان مشابهت این ۲ مقاله را بهدست آوریم، عمل (2√ × 4√) ÷ 1 که تفسیر میشود به ضرب داخلی هر دو بردار (یعنی عدد ۱) تقسیم بر رادیکال ۴ ضرب در رادیکال ۲ که برابر است با 0.35355339059
درواقع، در عدد بهدست آمده بیشترین اعداد -که مابین ۰ و ۱ هستند- را مدنظر داده و هرچه برداری دارای مقدار بزرگتری باشد (مثلاً ۰.۹۹)، این بدان معنا است که میزان مشابهتش با پست مدنظر ما بیش از سایرین بوده و اگر برداری دارای مقدار ۰ باشد هم حاکی از آن است که هر ۲ بردار عمود بر یکدیگر بوده (زاویهٔ ۹۰ درجه که مقدار کسینوس در این زاویه برابر با ۰ است) و هیچ سنخیتی با یکدیگر ندارند. در این مثال، میزان مشابهت هر ۲ مقاله چیزی در حدود ۳۵٪ است.
تا اینجای بحث، همهچیز تئوریک بود و ممکن است درک آن برای دولوپرهای تازهکار کمی مشکل باشد. در همین راستا، در ادامه قصد داریم تا الگورتیم فوق را کدنویسی کرده تا ببینیم که در عمل خروجی چنین الگوریتمی چه خواهد بود.
پیادهسازی الگوریتم فوق بااستفاده از زبان PHP
پس از اینکه توانستیم الگوریتم مدنظر را روی کاغذ پیادهسازی کنیم، حال نوبت به کدنویسی آن میرسد که قصد داریم با زبان PHP اینکار را انجام دهیم. در ابتدا، تابعی تحتعنوان ()calSimilarity بهصورت زیر مینویسیم:
function calSimilarity($firstContent, $secondContent) {
$dotProduct = count(array_intersect($firstContent['tags'], $secondContent['tags']));
$cosineSimilarity = $dotProduct / (sqrt(count($firstContent['tags'])) * sqrt(count($secondContent['tags'])));
return $cosineSimilarity;
}
تابع فوق ۲ پارامتر ورودی تحتعناوین firstContent$ و secondContent$ میگیرد که میتوان بهترتیب به همان مقالات A و B آنها را تشبیه کرد. در خط دوم، متغیری ساختهایم تحتعنوان dotProduct$ که قرار است حاصل ضرب داخلی این ۲ بردار را در خود ذخیره سازد.
در زبان برنامهنویسی PHP تابعی از پیش تعریف شده داریم تحتعنوان ()array_intersect که ۲ پارامتر ورودی اجباری میگیرد و کلیدهای مشابه را مشخص میسازد. در این مثال، کلید tags در پارامترهای ورودی شامل آرایهای از تگهای مرتبط با هر مقاله است که بهعنوان ۲ پارامتر ورودی این تابع درنظر گرفته شده است؛ سپس خروجی این تابع را داخل تابع دیگری از PHP تحتعنوان ()count قرار دادهایم که اندازهٔ یک آرایه را مشخص میسازد و درنهایت خروجی در متغیر dotProduct$ ذخیره میشود.
سپس متغیر دیگری در خط سوم تحتعنوان cosineSimilarity$ ایجاد کرده و مقدار آن را برابر با حاصل تقسیم متغیر dotProduct$ بر جذر تعداد تگهای مقالهٔ A ضرب در جذر تعداد تگهای مقالهٔ B قرار داده و درنهایت در خط چهارم مقدار متغیر cosineSimilarity$ را ریترن کردهایم. حال جهت تست علمکرد این تابع، آرایههای زیر را درنظر میگیریم:
$firstContent = [
'tags' => ['programming', 'python', 'micropython', 'c']
];
$secondContent = [
'tags' => ['programming', 'editor']
];
echo calSimilarity($firstContent, $secondContent);
و بهعنوان خروجی کد فوق داریم:
0.35355339059327
در گذشته -بهخصوص در سیستمهای مدیریت محتوایی همچون وردپرس و غیره- محتوای یک سایت در چندین Category (دستهبندی) مختلف قرار میگرفت به بدین شکل محتوای سایت طبقهبندی میشد اما استفاده از کتگوری چندین نقطه ضعف عمده داشت (البته امروزه هم خیلی از وبمسترها از کتگوریهای برای دستهبندی محتوای سایت خود استفاده میکنند).
اول اینکه در طول زمان ممکن بود به این نتیجه برسیم که یک دستهبندی بایستی حذف شده و یا با دستهٔ دیگی ادغام گردد؛ علاوهبر این، ممکن بود در آینده دستههای جدید بیفزاییم و محتواهای قبلی را به دستهبندی جدید انتقال دهیم و مشکلاتی از این دست.
اما آنچه امروزه ترند شده است، استفاده از Tag (تگ یا برچسب) برای دستهبندی محتوای مختلف است. بهعبارت دیگر، بااستفاده از تگها میتوانیم با یک تیر دو نشان را بزنیم: هم دستهبندی محتواها را مشخص ساخته و هم ارتباط محتوا با دیگر محتواها را یافته و بهعنوان محتوای مرتبط درمعرض دید کاربران قرار دهیم.
یکی از راهکارهایی که درعمل سادهترین کار اما درعینحال نامؤثرترین راه میباشد این است که اولین تگ درنظر گرفته شده برای محتوای مدنظر را گرفته و دیگر محتواهایی که دارای آن تگ هستند را از دیتابیس فراخوانی کرده و بهعنوان محتوای بهاصطلاح مرتبط به کاربر نشان دهیم اما این درحالی است که این روش اصلاً اثربخش نبوده و ضریب خطای بالایی دارد.
فرض کنیم مقالهای داریم تحتعنوان MicroPython: نسخهٔ کاستومایز شدهای از زبان برنامهنویسی پایتون برای دیوایسهای امبدد که دارای تگهای برنامهنویسی، پایتون، میکروپایتون و سی است. همانطور که مشخص است، اولین تگ این مقاله برنامهنویسی است و درصورتیکه بخواهیم دیگر محتواهای مرتبط با این مقاله را از دیتابیس فراخوانی کنیم، اسکیوالی مینویسم با این مضمون که هرآنچه محتوا با تگ برنامهنویسی است را یافته و در دسترسمان قرار دهد.
در چنین شرایطی، مقالاتی همچون آشنایی با EditorConfig: مدیریت استایل سورسکد در ادیتورهای مختلف، چگونه به شکلی حرفهای دست به Code Review بزنیم؟ و راهکارهای گوگل جهت تست نرمافزار در سرویس بهداشتی! بهطورحتم جزو پیشنهادات چنین بهاصطلاح الگوریتمی خواهد بود!
گرچه هر ۴ مقاله بهنوعی مرتبط با برچسب برنامهنویسی است، اما اگر بخواهیم ریزبینانه نگاه کنیم، در ۳ مقالهٔ پیشنهادی هیچ رنگ و بویی از تگ دوم مقاله -یعنی پایتون- دیده نمیشود و کاربری که این مقاله را میخواند به احتمال زیاد انتظار دارد تا دیگر مقالات پیشنهادی بهنوعی مرتبط با زبان برنامهنویسی پایتون باشند.
طراحی الگورتیمی جهت یافتن محتواهای مرتبط
در الگوریتمی که قرار است طراحی کنیم، پیش از هرچیز میبایست با مفهومی تحتعنوان K-nearest Neighbors Algorithm (الگوریتم نزدیکترین همسایه) آشنا شد. بهعبارت دیگر، برای هر پست میبایست نزدیکترین تگهایی که با محتوا سازگاری دارند را برگزید و این درحالی است که تگها نیز دارای رنک یا رتبهبندی هستند، از شبیهترین تگ به کمشباهتترین تگ.
برای استفاده از این اَپروچ (رویکرد)، میبایست راهی بیابیم تا پستها را براساس تگهایشان با یکدیگر مقایسه کنیم که یکی از اینراهها، نمایش برداری پستها است و بااستفاده از مفهومی تحتعنوان Cosine Similarity (مشابهت کسینوسی) امکانپذیر است. بهعبارت دیگر، مشابهت کسینوسی عبارت است از کسینوس زاویهٔ بین ۲ بردار.
بهخاطر داشته باشیم ۲ پستی که دقیقاً بردارهایی به یک جهت داشته باشند (یعنی زاویهٔ ۲ بردار برابر با ۰ درجه باشد) دارای مشابهت کسینوسی ۱ هستند و این درحالی است که اگر بردارهای ۲ پست دارای جهات متضاد یکدیگر باشند (یعنی زاویهٔ ۲ بردار ۹۰ درجه باشد)، دارای مشابهت کسینوسی ۰ هستند. با این تفاسیر، میتوانیم فرمولی برای یافتن میزان مشابهت ۲ پست درنظر بگیریم:
آموزش پیادهسازی طراحی الگورتیمی جهت یافتن مقالات مرتبط در وبسایت
همانطور که در تصویر فوق مشاهده میشود، ۲ بردار تحت عناوین A و B داریم؛ حال میبایست بااستفاده از مفهومی تحتعنوان Dot Product (ضرب داخلی)، به میزان مشابهت این ۲ بردار پی ببریم. بهطور خلاصه، ضرب داخلی یک عمل ریاضیاتی دوتایی مابین ۲ بردار در فضای nبعدی است که نتیجهٔ آن یک عدد حقیقی است.
بهعبارت دیگر، با نمایش کلیهٔ پستها بهعنوان ماتریسی با n ردیف که هر ردیف هم نشانگر بردار یک پست است، میتوان بهسادگی یک ضرب داخلی با پست مدنظر انجام داده و برداری nبعدی از میزان مشابهت پست مدنظر با سایر پستها بهدست آورد.
بازهم اگر بخواهیم سادهتر توضیح دهیم، میتوان گفت که ضرب داخلی ۲ بردار A و B برابر است با اندازهٔ تصویر بردار A بر روی بردار B که در تصویر فوق با نقطهچین مشخص شده است. در یک کلام، یعنی تعداد تگهای مشترک مابین ۲ پست وبسایت.
برای روشنتر شدن این مسئله، مجدد به مثال مقالات فوق بازمیگردیم. مقالهٔ MicroPython: نسخهٔ کاستومایز شدهای از زبان برنامهنویسی پایتون برای دیوایسهای امبدد که از این پس آنرا تحتعنوان بردار A درنظر میگیریم دارای تگهای زیر است:
- برنامهنویسی
- پایتون
- میکروپایتون
- سی
و مقالهای همچون آشنایی با EditorConfig: مدیریت استایل سورسکد در ادیتورهای مختلف که از این پس آنرا تحتعنوان بردار B درنظر میگیریم دارای تگهای زیر است:
- برنامهنویسی
- ویرایشگر کد
با این تفاسیر، ضرب داخلی ۲ بردار A و B میشود عدد ۱ چراکه فقط و فقط ۱ تگ (برنامهنویسی) مابین این ۲ بردار مشترک است. حال نیاز به فرمولی داریم تا درصد مشابهت این ۲ بردار را در بیاوریم که فرمول زیر اینکار را برایمان انجام خواهد داد:
آموزش پیادهسازی طراحی الگورتیمی جهت یافتن مقالات مرتبط در وبسایت
در تفسیر فرمول فوق بهطور خلاصه، بایستی بگوییم که میزان Similarity (مشابهت) برابر است با میزان ضرب داخلی بردارهای A و B تقسیم بر اندازهٔ بردار A ضرب در اندازهٔ بردار B (درواقع، در مثال فوق، اندازهٔ بردار A برابر است با تعداد تگهای این مقاله که برابر با ۴ است و اندازهٔ بردار B برابر است با تعداد تگهای مقالهٔ B که مساوی است با ۲).
درحقیقت، اگر بخواهیم میزان مشابهت این ۲ مقاله را بهدست آوریم، عمل (2√ × 4√) ÷ 1 که تفسیر میشود به ضرب داخلی هر دو بردار (یعنی عدد ۱) تقسیم بر رادیکال ۴ ضرب در رادیکال ۲ که برابر است با 0.35355339059
درواقع، در عدد بهدست آمده بیشترین اعداد -که مابین ۰ و ۱ هستند- را مدنظر داده و هرچه برداری دارای مقدار بزرگتری باشد (مثلاً ۰.۹۹)، این بدان معنا است که میزان مشابهتش با پست مدنظر ما بیش از سایرین بوده و اگر برداری دارای مقدار ۰ باشد هم حاکی از آن است که هر ۲ بردار عمود بر یکدیگر بوده (زاویهٔ ۹۰ درجه که مقدار کسینوس در این زاویه برابر با ۰ است) و هیچ سنخیتی با یکدیگر ندارند. در این مثال، میزان مشابهت هر ۲ مقاله چیزی در حدود ۳۵٪ است.
تا اینجای بحث، همهچیز تئوریک بود و ممکن است درک آن برای دولوپرهای تازهکار کمی مشکل باشد. در همین راستا، در ادامه قصد داریم تا الگورتیم فوق را کدنویسی کرده تا ببینیم که در عمل خروجی چنین الگوریتمی چه خواهد بود.
پیادهسازی الگوریتم فوق بااستفاده از زبان PHP
پس از اینکه توانستیم الگوریتم مدنظر را روی کاغذ پیادهسازی کنیم، حال نوبت به کدنویسی آن میرسد که قصد داریم با زبان PHP اینکار را انجام دهیم. در ابتدا، تابعی تحتعنوان ()calSimilarity بهصورت زیر مینویسیم:
function calSimilarity($firstContent, $secondContent) {
$dotProduct = count(array_intersect($firstContent['tags'], $secondContent['tags']));
$cosineSimilarity = $dotProduct / (sqrt(count($firstContent['tags'])) * sqrt(count($secondContent['tags'])));
return $cosineSimilarity;
}
تابع فوق ۲ پارامتر ورودی تحتعناوین firstContent$ و secondContent$ میگیرد که میتوان بهترتیب به همان مقالات A و B آنها را تشبیه کرد. در خط دوم، متغیری ساختهایم تحتعنوان dotProduct$ که قرار است حاصل ضرب داخلی این ۲ بردار را در خود ذخیره سازد.
در زبان برنامهنویسی PHP تابعی از پیش تعریف شده داریم تحتعنوان ()array_intersect که ۲ پارامتر ورودی اجباری میگیرد و کلیدهای مشابه را مشخص میسازد. در این مثال، کلید tags در پارامترهای ورودی شامل آرایهای از تگهای مرتبط با هر مقاله است که بهعنوان ۲ پارامتر ورودی این تابع درنظر گرفته شده است؛ سپس خروجی این تابع را داخل تابع دیگری از PHP تحتعنوان ()count قرار دادهایم که اندازهٔ یک آرایه را مشخص میسازد و درنهایت خروجی در متغیر dotProduct$ ذخیره میشود.
سپس متغیر دیگری در خط سوم تحتعنوان cosineSimilarity$ ایجاد کرده و مقدار آن را برابر با حاصل تقسیم متغیر dotProduct$ بر جذر تعداد تگهای مقالهٔ A ضرب در جذر تعداد تگهای مقالهٔ B قرار داده و درنهایت در خط چهارم مقدار متغیر cosineSimilarity$ را ریترن کردهایم. حال جهت تست علمکرد این تابع، آرایههای زیر را درنظر میگیریم:
$firstContent = [
'tags' => ['programming', 'python', 'micropython', 'c']
];
$secondContent = [
'tags' => ['programming', 'editor']
];
echo calSimilarity($firstContent, $secondContent);
و بهعنوان خروجی کد فوق داریم:
0.35355339059327