سرنخ — خودآموز برنامه‌نویسی برای هوش مصنوعی (مقدماتی)

فصل ۱ از ۸

پیشرفت ترم
۰٪

ترم ۴ · مدل‌های آماده: وقتی لازم نیست خودت آموزش بدهی

مدلِ آماده یعنی چه

فصل ۱پیش‌نمایش رایگان
۱۳ دقیقه مطالعه فصل ۱

در این فصل چه یاد می‌گیری#

همهٔ مدل‌هایی که تا اینجا ساختی، خودت آموزششان دادی روی ۲۳۸ ردیفِ خودت. ترمِ ۴ در دنیای دیگری باز می‌شود: مدل‌هایی که دیگران روی میلیاردها نمونه آموزش داده‌اند و تو می‌توانی مستقیم به‌کارشان ببری. در این فصل می‌فهمی pretrained model چیست، تفاوتِ training و inference را می‌بینی، و مهم‌تر از همه یاد می‌گیری کِی باید مدل آموزش بدهی و کِی نباید — که جوابش برای بیشترِ کارهای واقعی «نباید» است.

یک ابزارِ سنگینِ کارخانه‌ای در یک طرف و همان قطعهٔ ساخته‌شده که در دستِ کسی به‌کار می‌رود در طرفِ دیگر

آخر این فصل می‌توانی:

  • بگویی pretrained model چیست و از کجا می‌آید
  • تفاوتِ training و inference را با هزینه‌اش توضیح بدهی
  • تصمیم بگیری که برای یک مسئله مدل بسازی یا مدلِ آماده بگیری
  • بگویی چرا حتی با مدلِ آماده، خطِ پایه هنوز اجباری است

قبل از شروع#

از ترمِ ۳: fit/predict، خطِ پایه، و اینکه هر ادعا به اندازه‌گیری نیاز دارد.

سلولِ راه‌اندازی: یک پیکرهٔ کوچکِ فارسی می‌سازد (docs.csv) — بیست جمله در چهار موضوع. در فصل‌های ۵ و ۶ همین را برای جست‌وجوی معنایی به‌کار می‌بریم.

📓 نوت‌بوک: نوت‌بوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و به‌ترتیب داخلش هست.

۱. دو کارِ کاملاً متفاوت#

training (آموزش) inference (استنتاج)
چه می‌کند از داده، پارامترهای مدل را می‌سازد با پارامترهای موجود، جواب می‌دهد
چند بار یک بار (یا هر چند وقت یک بار) میلیون‌ها بار
چقدر داده لازم دارد خیلی زیاد فقط همان یک ورودی
هزینه از چند ثانیه تا ماه‌ها و میلیون‌ها دلار از میلی‌ثانیه تا چند ثانیه
چه کسی انجامش می‌دهد معمولاً دیگران تو

تا اینجا هر دو کار را خودت می‌کردی، چون داده‌ات کوچک بود و آموزشش یک ثانیه طول می‌کشید. برای مدلی که زبان می‌فهمد یا تصویر تشخیص می‌دهد، آموزش از دسترسِ هر فرد و هر شرکتِ متوسطی خارج است.

pretrained model یعنی همان پارامترهای آموزش‌دیده، که کسی آموزششان داده و در دسترسِ عمومی گذاشته. تو فقط inference می‌کنی.

⚠️ مواظب باش: «آماده» یعنی پارامترها آماده‌اند، نه اینکه مدل برای مسئلهٔ تو آماده است. مدلی که روی متنِ عمومیِ اینترنت آموزش دیده، لزوماً روی متنِ تخصصیِ حوزهٔ تو خوب کار نمی‌کند. این را باید اندازه بگیری، نه فرض کنی — و همان قاعدهٔ ترمِ ۳ است، فقط با یک مدلِ متفاوت.

۲. سه راهِ حلِ یک مسئله#

فرض کن می‌خواهی موضوعِ یک جملهٔ فارسی را تشخیص بدهی. سه راه داری:

import pandas as pd

docs = pd.read_csv("docs.csv")
print(docs.shape)
print(docs["topic"].value_counts().to_dict())
print(docs.head(3).to_string(index=False))
(20, 2)
{'سفر': 5, 'آشپزی': 5, 'برنامه\u200cنویسی': 5, 'سلامت': 5}
topic                                                               text
  سفر   بلیت هواپیمای تهران به شیراز را دو هفته زودتر بخری ارزان‌تر است.
  سفر           بهترین فصل سفر به شمال ایران اواخر بهار است، نه تابستان.
  سفر برای اقامت در شهرهای کوچک، اقامتگاه بومی از هتل ارزان‌تر درمی‌آید.

چک کن: خروجی باید دقیقاً (20, 2) باشد و هر چهار موضوع پنج جمله داشته باشند. اگر تعداد فرق دارد یا خطای FileNotFoundError گرفتی، سلولِ راه‌اندازی را اجرا نکرده‌ای — همان چیزی که ترمِ ۱ فصلِ ۱۰ دربارهٔ ترتیبِ اجرا گفت.

راهِ اول: قاعدهٔ دستی. یک فهرست از کلمه‌های کلیدیِ هر موضوع بنویس.

راهِ دوم: مدلِ خودت. با همان LogisticRegression ترمِ ۳ روی همین بیست جمله آموزش بده.

راهِ سوم: مدلِ آماده. مدلی که کسی روی میلیون‌ها متنِ فارسی آموزش داده را بگیر و به‌کار ببر.

بیایید راهِ دوم را واقعاً بسازیم، چون خطِ پایهٔ ترمِ ۴ است. یک قطعهٔ تازه لازم داریم: مدل‌های ترمِ ۳ فقط عدد می‌پذیرفتند و ورودیِ ما متن است.

TfidfVectorizer این فاصله را پُر می‌کند. کارش در یک جمله: فهرستِ همهٔ کلمه‌های پیکره را می‌سازد و هر متن را به یک ردیفِ عددی به‌طولِ همان فهرست تبدیل می‌کند، که هر خانه‌اش می‌گوید آن کلمه در این متن چقدر وزن دارد. کلمه‌ای که در این متن زیاد آمده ولی در بقیهٔ متن‌ها کم، وزنِ بالا می‌گیرد — چون همان است که این متن را از بقیه جدا می‌کند. این دقیقاً همان شهودی است که در ترمِ ۲ فصلِ ۹ دیدی («کلمه‌ای که در همهٔ ردیف‌ها هست بی‌فایده است»)، حالا به‌صورتِ عدد. فصلِ ۵ همین ترم کامل بازش می‌کند و با embedding مقایسه‌اش می‌کند.

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, StratifiedKFold

own_model = Pipeline([
    ("vector", TfidfVectorizer()),
    ("model", LogisticRegression(max_iter=1000)),
])

folds = StratifiedKFold(4, shuffle=True, random_state=0)
scores = cross_val_score(own_model, docs["text"], docs["topic"], cv=folds)

print("accuracy هر تا:", scores.round(2))
print("میانگین:", round(scores.mean(), 3))
print("خط پایه (حدس تصادفی بین ۴ موضوع):", 0.25)
accuracy هر تا: [0.2 0.4 0.2 0.4]
میانگین: 0.3
خط پایه (حدس تصادفی بین ۴ موضوع): 0.25

دو نکتهٔ فنی قبل از خواندنِ عدد. اول اینکه اینجا scoring= را ننوشتیم، برخلافِ ترمِ ۳ فصلِ ۸ که همیشه می‌نوشتیم: وقتی چیزی ندهی، scikit-learn معیارِ پیش‌فرضِ خودِ مدل را به‌کار می‌برد، و برای یک طبقه‌بند آن معیار accuracy است. همیشه بدان معیارت چیست، حتی وقتی ننوشته‌ای‌اش. و دوم، StratifiedKFold را به‌جای KFold گذاشتیم؛ بخشِ ۶ نشان می‌دهد چرا این انتخاب روی دادهٔ کوچک تعیین‌کننده است.

با بیست جمله، مدلِ خودمان ۳۰ درصد درست است — یعنی تقریباً به‌اندازهٔ حدسِ تصادفی (۲۵ درصد) و برای هر کارِ واقعی کاملاً بی‌فایده.

و دلیلش کاملاً روشن است: بیست جمله برای یادگرفتنِ زبان کافی نیست. مدل فقط کلمه‌های دقیقی را می‌شناسد که در همین بیست جمله دیده. جمله‌ای با کلمه‌های تازه، حتی اگر معنایش کاملاً واضح باشد، برایش ناشناخته است.

📏 اندازه بگیر: توجه کن که باز هم اول خطِ پایه. اینجا خطِ پایه ۰٫۲۵ است (چهار موضوعِ متوازن)، و مدلِ ما ۰٫۳ — یعنی عملاً هیچ. حتی وقتی سراغِ مدل‌های آماده می‌روی، این عادت را رها نکن: بدونِ خطِ پایه نمی‌فهمی مدلِ آماده واقعاً چقدر ارزش اضافه کرده. در فصلِ ۲ همین مسئله را با یک مدلِ آماده حل می‌کنیم و همین عددِ ۰٫۳ مرجعِ مقایسه است.

۳. کِی مدل بساز، کِی نساز#

وضعیت تصمیم
داده‌ات جدولی و مخصوصِ کسب‌وکارِ خودت است خودت بساز — هیچ مدلِ آماده‌ای دادهٔ تو را ندیده
مسئله عمومی است (زبان، تصویر، صدا) مدلِ آماده — آموزشِ دوباره‌اش هدررفتِ محض است
مسئله عمومی است ولی دامنه‌ات خاص است مدلِ آماده + fine-tuning (ژرفا ترم ۴)
داده‌ات کمتر از چند هزار نمونه است تقریباً همیشه مدلِ آماده

ترمِ ۳ کاملاً در ردیفِ اول بود: قیمتِ لپ‌تاپ در بازارِ خودت، دادهٔ جدولی، هیچ مدلِ آماده‌ای وجود ندارد که این را بداند.

این ترم در ردیفِ دوم و چهارم است: فهمیدنِ زبانِ فارسی مسئله‌ای عمومی است و بیست جملهٔ ما هیچ.

💡 نکته: یک قاعدهٔ عملی که خیلی وقت نجات می‌دهد: اگر مسئله‌ات را می‌شود در یک جمله به یک انسانِ عادی توضیح داد و او بتواند انجامش بدهد، احتمالاً مدلِ آماده‌ای برایش هست. «موضوعِ این جمله چیست؟» را هر کسی می‌فهمد. «قیمتِ منصفانهٔ این لپ‌تاپ در بازارِ امروز چقدر است؟» را نه.

۴. مدلِ آماده از کجا می‌آید#

دو مسیرِ اصلی وجود دارد و این ترم هر دو را می‌بینی:

  • دانلودِ مدل و اجرای محلی. مدل روی ماشینِ خودت (یا Colab) اجرا می‌شود. رایگان بعد از دانلود، بدونِ محدودیتِ سهمیه، و داده‌ات جایی نمی‌رود. بزرگ‌ترین منبعش Hugging Face است — فصلِ ۲.
  • فراخوانیِ API. مدل روی سرورِ یک شرکت است و تو درخواست می‌فرستی. قدرتمندتر، ولی داده‌ات از دستگاهت خارج می‌شود، سهمیه دارد، و به اینترنت وابسته است — فصلِ ۳ به بعد.
مدلِ محلی API
هزینهٔ اولیه دانلودِ مدل ثبت‌نام و کلید
هزینهٔ هر فراخوانی برق و زمانِ خودت پول یا سهمیه
دادهٔ تو جایی نمی‌رود به سرورِ دیگری می‌رود
قدرت محدود به مدل‌های کوچک‌تر بهترین مدل‌های موجود
بدونِ اینترنت کار می‌کند نمی‌کند

⚠️ مواظب باش: ردیفِ «دادهٔ تو» یک تصمیمِ فنی نیست، یک تصمیمِ مسئولیتی است. اگر با دادهٔ شخصی، پزشکی یا محرمانه کار می‌کنی، فرستادنش به یک API خارجی می‌تواند مشکلِ حقوقی و اخلاقی بسازد. قبل از اولین فراخوانی، بپرس این داده اجازهٔ خروج دارد یا نه — و در فصلِ ۷ کاملاً بازش می‌کنیم.

۵. حتی مدلِ آماده هم باید سنجیده شود#

آخرین و مهم‌ترین نکتهٔ این فصل. وسوسه‌ای که با مدل‌های آماده می‌آید این است: «این مدل را یک شرکتِ بزرگ روی میلیاردها نمونه آموزش داده، پس حتماً خوب است.»

این استدلال معتبر نیست، به سه دلیل:

  • مدل روی دادهٔ عمومی آموزش دیده، نه دادهٔ تو. متنِ تخصصیِ حوزهٔ تو ممکن است اصلاً شبیهِ چیزی نباشد که دیده.
  • «خوب» بی‌معناست تا وقتی معیارت را نگفته باشی — همان درسِ ترمِ ۳ فصلِ ۱.
  • ممکن است روی مسئلهٔ تو از یک TfidfVectorizer سادهٔ بیست‌خطی بدتر باشد. این واقعاً اتفاق می‌افتد.

پس قاعده تغییری نکرده: مدلِ آماده هم یک نامزد است، نه یک جواب. همان‌طور که در ترمِ ۳ چهار مدل را با خطِ پایه سنجیدی، اینجا هم مدلِ آماده باید در برابرِ همان عددِ ۰٫۳ ثابت کند که ارزشش را دارد.

📏 اندازه بگیر: جدولِ مقایسهٔ این ترم را از همین حالا بساز و در طولِ فصل‌ها پُرش کن: خطِ پایه (۰٫۲۵) ← مدلِ خودت (۰٫۳) ← مدلِ آماده (فصلِ ۲) ← API (فصلِ ۳). آخرِ ترم می‌توانی بگویی هر قدم چقدر ارزش اضافه کرد و به چه قیمتی. بدونِ این جدول، فقط داری ابزارِ جدید امتحان می‌کنی، نه انتخاب می‌کنی.

۶. یک خطای واقعی: تقسیمِ نامتوازن روی دادهٔ کوچک#

در بخشِ ۲ عمداً StratifiedKFold نوشتیم. حالا ببینیم اگر KFold معمولی می‌نوشتیم چه می‌شد.

خطِ آخرِ کدِ زیر فشرده‌تر از چیزی است که تا حالا دیده‌ای، پس تکه‌تکه بخوانش. folds.split(docs) برای هر دور یک جفت برمی‌گرداند: فهرستِ شماره‌ردیف‌های آموزش و فهرستِ شماره‌ردیف‌های آزمون. آن پرانتزِ داخلی — (train_rows, test_rows) — همان جفت را در دو متغیر باز می‌کند، و enumerate دورِ همه‌اش یک شمارنده می‌گذارد. بعد docs["topic"].iloc[test_rows] موضوعِ همان ردیف‌ها را برمی‌دارد (iloc، چون این‌ها شمارهٔ جایگاه‌اند نه برچسب — همان تفاوتِ ترمِ ۲ فصلِ ۴) و nunique() می‌گوید چند موضوعِ متمایز در آن تکه هست.

from sklearn.model_selection import KFold

plain = KFold(4, shuffle=True, random_state=1)
plain_scores = cross_val_score(own_model, docs["text"], docs["topic"], cv=plain)

print("accuracy هر تا:", plain_scores.round(2))
print("میانگین:", round(plain_scores.mean(), 3))

for i, (train_rows, test_rows) in enumerate(plain.split(docs)):
    print(f"تکهٔ {i}: تعداد موضوع در آزمونِ این تکه =", docs["topic"].iloc[test_rows].nunique())
accuracy هر تا: [0.4 0.4 0.4 0.2]
میانگین: 0.35
تکهٔ 0: تعداد موضوع در آزمونِ این تکه = 4
تکهٔ 1: تعداد موضوع در آزمونِ این تکه = 4
تکهٔ 2: تعداد موضوع در آزمونِ این تکه = 4
تکهٔ 3: تعداد موضوع در آزمونِ این تکه = 3

هیچ خطایی نگرفتیم، و عددش حتی از ۰٫۳ بخشِ ۲ بهتر شد. ولی خطِ آخر لوش می‌دهد: در تکهٔ چهارم فقط سه موضوع در دادهٔ آزمون بوده، نه چهار. یعنی یک موضوع در آن دور اصلاً سنجیده نشد و مسئله برای مدل یک درجه آسان‌تر شد.

عددِ ۰٫۳۵ دروغ نیست، ولی جوابِ سؤالِ دیگری است — و اگر خطِ آخر را چاپ نکرده بودی، هیچ راهی نداشتی که بفهمی. StratifiedKFold دقیقاً همین را می‌بندد: نسبتِ دسته‌ها را در هر تکه حفظ می‌کند، همان کاری که stratify در ترمِ ۳ فصلِ ۵ می‌کرد.

و اگر تعدادِ تکه‌ها را از تعدادِ اعضای کوچک‌ترین دسته بیشتر بگیری، این بار واقعاً خطا می‌گیری:

cross_val_score(own_model, docs["text"], docs["topic"], cv=StratifiedKFold(6))
ValueError: n_splits=6 cannot be greater than the number of members in each class.

هر موضوع فقط پنج جمله دارد، پس بیش از پنج تکه ممکن نیست — StratifiedKFold نمی‌تواند نسبت‌ها را حفظ کند و به‌جای حدس‌زدن می‌ایستد.

🔧 اگر کار نکرد: برای هر مسئلهٔ طبقه‌بندی پیش‌فرض را StratifiedKFold بگذار، نه KFold. scikit-learn وقتی cv=5 (یک عددِ ساده) بدهی خودش این کار را می‌کند؛ فقط وقتی صریح KFold می‌نویسی باید حواست باشد. و روی دادهٔ کوچک، همیشه تعدادِ دسته‌های هر تکه را یک بار چاپ کن — پنج خط کد که جلوی یک عددِ بی‌معنا را می‌گیرد.

🤖 از دستیارت بپرس: «برای این سه مسئله بگو مدلِ آماده مناسب است یا مدلِ اختصاصی، و چرا: (۱) تشخیصِ احساسِ نظرِ مشتری، (۲) پیش‌بینیِ فروشِ فردای یک فروشگاهِ خاص، (۳) خلاصه‌کردنِ یک گزارشِ حقوقیِ فارسی.» جوابش را با جدولِ بخشِ ۳ بسنج. موردِ سوم جالب‌ترین است چون هر دو جواب قابلِ دفاع‌اند و انتخاب به داده‌ای که در دست داری بستگی دارد.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
pretrained model پری‌ترِیند مدل مدلی که کسِ دیگری آموزشش داده و تو فقط به‌کارش می‌بری
inference اینفِرِنس استفاده از مدلِ آموزش‌دیده برای گرفتنِ جواب
Hugging Face هاگینگ‌فیس بزرگ‌ترین مخزنِ عمومیِ مدل‌های آماده
StratifiedKFold استرَتیفاید کی‌فولد cross-validation که نسبتِ دسته‌ها را حفظ می‌کند

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

فصلِ بعد اولین مدلِ آماده را به‌کار می‌بری — در سه خط کد، بدونِ هیچ آموزشی. یک کتابخانه که ده‌ها هزار مدلِ آماده را با یک رابطِ واحد در دسترست می‌گذارد، و همان مسئلهٔ تشخیصِ موضوع را دوباره حل می‌کنیم تا ببینیم آن عددِ ۰٫۳ چقدر بالا می‌رود — یا نمی‌رود.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.