فصل 16: تولید متن با LLMها در Keras
این پست از فصل 16 کتاب DEEP LEARNING with Python ویرایش 3 ترجمه شده است.
محتویات این فصل
- تاریخچه مختصر تولید توالی
- آموزش یک mini-GPT
- استفاده از یک LLM از پیش آموزشدیده
- پیشرفت بیشتر با LLMها
- LLMها به کجا میروند؟
- خلاصه
این فصل شامل موارد زیر است:
- تاریخچه مختصر مدلسازی تولیدی
- آموزش یک مدل GPT کوچک از صفر
- استفاده از یک مدل Transformer از پیش آموزشدیده برای ساخت یک چتبات
- ساخت یک مدل چندوجهی که میتواند تصاویر را به زبان طبیعی توصیف کند
زمانی که برای اولین بار ادعا کردم که در آیندهای نه چندان دور، بیشتر محتوای فرهنگی که مصرف میکنیم با کمک قابل توجه هوش مصنوعی ایجاد خواهد شد، با ناباوری کامل مواجه شدم، حتی از سوی متخصصان باسابقه یادگیری ماشین. این در سال ۲۰۱۴ بود. یک دهه بعد، آن ناباوری با سرعت باورنکردنی کاهش یافته است.
امروزه ابزارهای هوش مصنوعی مولد اکنون افزونههای رایجی در پردازندههای متن، ویرایشگرهای تصویر و محیطهای توسعه هستند. جوایز معتبر به ادبیات و هنری که با مدلهای تولیدی ایجاد شدهاند اعطا میشود که البته با بحث و جدل و مناقشه قابل توجهی همراه بوده است. دیگر دور از ذهن نیست که دنیایی را در نظر بگیریم که در آن هوش مصنوعی و تلاشهای هنری اغلب در هم تنیده شدهاند.

شکل 16.1: تصویری که با نرمافزار تولید تصویر Midjourney ایجاد شده است. پرامپت: “یک منظره علمی-تخیلی دستکشیده از ساکنان زندگی در ساختمانی به شکل حرف K قرمز.”
از نظر عملی، هوش مصنوعی به هیچ وجه نزدیک به رقابت با فیلمنامهنویسان، نقاشان یا آهنگسازان انسانی نیست. اما جایگزینی انسانها نباید و نباید هدف باشد. در بسیاری از زمینهها، به خصوص در زمینههای خلاق، مردم از هوش مصنوعی برای تقویت قابلیتهای خود استفاده خواهند کرد – بیشتر هوش تقویتشده تا هوش مصنوعی.
بخش زیادی از خلق هنری شامل تشخیص الگو و مهارت فنی است. روشهای ادراکی، زبان و آثار هنری ما همگی ساختار آماری دارند، و مدلهای یادگیری عمیق در یادگیری این ساختار عالی هستند. مدلهای یادگیری ماشین میتوانند فضاهای نهفته آماری تصاویر، موسیقی و داستانها را یاد بگیرند و سپس از این فضاها نمونهگیری کنند و آثار هنری جدیدی با ویژگیهای مشابه با آنچه مدل در دادههای آموزشی خود دیده است ایجاد کنند.
تاریخچه مختصر تولید توالی
تا همین اواخر، ایده تولید توالی از یک مدل یک موضوع فرعی در یادگیری ماشین بود و شبکههای بازگشتی مولد تنها در سال ۲۰۱۶ به جریان اصلی حوزه راه یافتند. با این حال، این تکنیکها تاریخچه نسبتاً طولانی دارند که با توسعه الگوریتم LSTM در سال ۱۹۹۷ شروع شد.
در سال ۲۰۰۲، داگلاس اک (Douglas Eck) برای اولین بار LSTM را برای تولید موسیقی به کار برد، با نتایج امیدوارکننده. اک محقق گوگل برین شد و در سال ۲۰۱۶، یک گروه تحقیقاتی جدید به نام Magenta را راهاندازی کرد که بر استفاده از تکنیکهای مدرن یادگیری عمیق برای تولید موسیقی جذاب متمرکز بود. گاهی اوقات، ایدههای خوب ۱۵ سال طول میکشد تا شروع شوند.
در اواخر دهه ۲۰۰۰ و اوایل دهه ۲۰۱۰، الکس گریوز (Alex Graves) پیشگام استفاده از شبکههای بازگشتی برای انواع جدید تولید داده توالی بود. به طور خاص، برخی کار او در سال ۲۰۱۳ درباره استفاده از شبکههای چگالی مخلوط بازگشتی (recurrent mixture density network) برای تولید دستخط شبیه انسان با استفاده از سریهای زمانی موقعیتهای قلم را به عنوان نقطه عطف میدانند.
در سال ۲۰۱۸، یک سال پس از مقاله “Attention Is All You Need”، گروهی از محققان در سازمانی به نام OpenAI مقاله جدیدی با عنوان “بهبود درک زبان با پیشآموزش تولیدی” منتشر کردند. آنها چند مؤلفه را ترکیب کردند:
- پیشآموزش بدون نظارت یک مدل زبانی – اساساً آموزش یک مدل برای “حدس زدن توکن بعدی” در یک توالی
- معماری Transformer
- دادههای متنی در موضوعات مختلف از طریق هزاران کتاب خودمنتشرشده
نویسندگان نشان دادند که چنین مدل از پیش آموزشدیدهای میتواند برای دستیابی به عملکرد پیشرفته در طیف گستردهای از وظایف طبقهبندی متن – از سنجش شباهت دو جمله تا پاسخ به یک سؤال چندگزینهای – تنظیم دقیق شود. آنها این مدل از پیش آموزشدیده را GPT نامیدند، مخفف Generative Pretrained Transformer.
GPT با هیچ پیشرفت مدلسازی یا آموزشی همراه نبود. آنچه در مورد نتایج جالب بود این بود که چنین راهاندازی آموزشی عمومی میتوانست تکنیکهای پیچیدهتر را در تعدادی از وظایف شکست دهد. نرمالسازی پیچیده متن وجود نداشت، نیازی به سفارشیسازی معماری مدل یا دادههای آموزشی برای هر معیار نبود، فقط مقدار زیادی داده پیشآموزش و محاسبات.
در سالهای بعد، OpenAI با تمرکز یکدنده به مقیاسبندی این ایده پرداخت. معماری مدل تنها کمی تغییر کرد. طی چهار سال، OpenAI سه نسخه از GPT را منتشر کرد که به صورت زیر مقیاسبندی شدند:
- GPT-1 (منتشرشده در ۲۰۱۸): ۱۱۷ میلیون پارامتر و آموزش بر روی ۱ میلیارد توکن
- GPT-2 (منتشرشده در ۲۰۱۹): ۱.۵ میلیارد پارامتر و آموزش بر روی بیش از ۱۰ میلیارد توکن
- GPT-3 (منتشرشده در ۲۰۲۰): ۱۷۵ میلیارد پارامتر و آموزش بر روی حدود نیم تریلیون توکن
راهاندازی مدلسازی زبان به هر یک از این مدلها امکان تولید متن را میداد، و توسعهدهندگان در OpenAI متوجه شدند که با هر جهش در مقیاس، کیفیت این خروجی تولیدی به طور قابل توجهی افزایش مییابد.
آموزش یک mini-GPT
برای شروع پیشآموزش mini-GPT خود، به مقدار زیادی داده متنی نیاز خواهیم داشت. GPT-1 از مجموعه دادهای به نام BooksCorpus استفاده کرد که شامل تعدادی کتاب خودمنتشرشده رایگان بود که بدون اجازه صریح نویسندگان به مجموعه داده اضافه شده بودند. مجموعه داده از آن زمان توسط ناشران آن حذف شده است.
ما از یک مجموعه داده پیشآموزش جدیدتر به نام “Colossal Clean Crawled Corpus” (C4) استفاده خواهیم کرد که توسط گوگل در سال ۲۰۲۰ منتشر شد. با ۷۵۰ گیگابایت، بسیار بزرگتر از آن است که بتوانیم به طور معقول برای یک مثال کتاب بر روی آن آموزش دهیم، بنابراین از کمتر از ۱٪ کل مجموعه استفاده خواهیم کرد.
import keras
import pathlib
extract_dir = keras.utils.get_file(
fname="mini-c4",
origin=(
"https://hf.co/datasets/mattdangerw/mini-c4/resolve/main/mini-c4.zip"
),
extract=True,
)
extract_dir = pathlib.Path(extract_dir) / "mini-c4"
اجرای کد در این فصل
مدلهای زبانی تولیدی بزرگ هستند و برای اجرا به محاسبات زیادی نیاز دارند. در حالی که ما برای قابل دسترس کردن کد در این فصل تلاش کردهایم، این همچنان محاسباتیترین فصل در این کتاب است.
اگر میخواهید، میتوانید همه چیز را در runtime رایگان GPU Colab (یک GPU T4 در زمان نوشتن این مطلب) اجرا کنید، اما آماده باشید که منتظر بمانید! این مثال mini-GPT حدود ۶ ساعت برای آموزش طول میکشد و باید runtime Colab خود را در وسط نوتبوک مجدداً راهاندازی کنید تا حافظه GPU را قبل از بارگذاری یک مدل از پیش آموزشدیده بزرگتر آزاد کنید.
ما ۵۰ قطعه از داده متنی داریم که هر کدام حدود ۷۵ مگابایت متن خام دارند. هر خط شامل یک سند در خزش با newlineهای escape شده است. بیایید به یک سند در اولین قطعه خود نگاه کنیم:
>>> with open(extract_dir / "shard0.txt", "r") as f:
>>> print(f.readline().replace("\\n", "\n")[:100])
Beginners BBQ Class Taking Place in Missoula!
Do you want to get better at making delicious BBQ? You
برای پیشپردازش دادههای زیادی که برای اجرای پیشآموزش یک LLM نیاز داریم، حتی یک LLM کوچک مانند آنچه در حال آموزش هستیم، استفاده از یک روال توکنسازی سریع برای پیشپردازش اسناد منبع ما به توکنهای صحیح میتواند زندگی ما را ساده کند.
ما از SentencePiece استفاده خواهیم کرد، یک کتابخانه برای توکنسازی زیرکلمهای دادههای متنی. تکنیک توکنسازی واقعی همان توکنسازی جفتبایت است که خودمان در فصل ۱۴ ساختیم، اما کتابخانه به زبان C++ برای سرعت نوشته شده است و یک تابع detokenize() اضافه میکند که صحیحها را به رشتهها معکوس و آنها را به هم میپیوندد.
import keras_hub
import numpy as np
vocabulary_file = keras.utils.get_file(
origin="https://hf.co/mattdangerw/spiece/resolve/main/vocabulary.proto",
)
tokenizer = keras_hub.tokenizers.SentencePieceTokenizer(vocabulary_file)
میتوانیم از این tokenizer برای نگاشت دوطرفه از متن به توالیهای int استفاده کنیم:
>>> tokenizer.tokenize("The quick brown fox.")
array([ 450, 4996, 17354, 1701, 29916, 29889], dtype=int32)
>>> tokenizer.detokenize([450, 4996, 17354, 1701, 29916, 29889])
"The quick brown fox."
بیایید از این لایه برای توکنسازی متن ورودی خود استفاده کنیم و سپس از tf.data برای پنجرهبندی ورودیمان به توالیهایی با طول ۲۵۶ بهره ببریم.
هنگام آموزش GPT، توسعهدهندگان تصمیم گرفتند کار را ساده نگه دارند و هیچ تلاشی برای جلوگیری از قرار گرفتن مرزهای اسناد در وسط یک نمونه نکردند. در عوض، آنها مرز یک سند را با یک توکن ویژه <|endoftext|> مشخص کردند. ما هم همین کار را خواهیم کرد. باز هم از tf.data برای پایپلاین داده ورودی استفاده میکنیم و با هر backend دلخواهی آموزش خواهیم داد.
ما هر قطعه فایل را به صورت جداگانه بارگذاری میکنیم و دادههای خروجی را به صورت درهمتنیده در یک مجموعه داده واحد قرار میدهیم. این کار بارگذاری دادههایمان را سریع نگه میدارد و نیازی نیست نگران همراستا شدن متن در مرزهای نمونهها باشیم – چون هر کدام مستقل هستند. با این روش درهمتنیدگی، هر پردازنده در CPU ما میتواند به طور همزمان یک فایل جداگانه را بخواند و توکنسازی کند.
import tensorflow as tf
batch_size = 64
sequence_length = 256
suffix = np.array([tokenizer.token_to_id("<|endoftext|>")])
def read_file(filename):
ds = tf.data.TextLineDataset(filename)
# Restores newlines
ds = ds.map(lambda x: tf.strings.regex_replace(x, r"\\n", "\n"))
# Tokenizes data
ds = ds.map(tokenizer, num_parallel_calls=8)
# Adds the <|endoftext|> token
return ds.map(lambda x: tf.concat([x, suffix], -1))
files = [str(file) for file in extract_dir.glob("*.txt")]
ds = tf.data.Dataset.from_tensor_slices(files)
# Combines our file shards into a single dataset
ds = ds.interleave(read_file, cycle_length=32, num_parallel_calls=32)
# Windows tokens into even samples of 256 tokens
ds = ds.rebatch(sequence_length + 1, drop_remainder=True)
# Splits labels, offset by one
ds = ds.map(lambda x: (x[:-1], x[1:]))
ds = ds.batch(batch_size).prefetch(8)
همانطور که برای اولین بار در فصل ۸ انجام دادیم، پایپلاین tf.data خود را با فراخوانی prefetch() پایان میدهیم. این کار تضمین میکند که همیشه تعدادی batch روی GPU بارگذاری شده و برای مدل آماده باشند.
ما ۵۸,۷۴۶ batch داریم. اگر بخواهید میتوانید خودتان این تعداد را بشمارید — خط کد ds.reduce(0, lambda c, _: c + 1) روی کل مجموعه داده تکرار میشود و یک شمارنده را افزایش میدهد. اما صرفاً توکنسازی یک مجموعه داده به این بزرگی چند دقیقه روی یک CPU نسبتاً سریع طول خواهد کشید.
با ۶۴ نمونه در هر batch و ۲۵۶ توکن در هر نمونه، این مجموعه کمی کمتر از یک میلیارد توکن داده دارد. بیایید ۵۰۰ batch را به عنوان یک مجموعه اعتبارسنجی سریع جدا کنیم، و آماده شروع پیشآموزش هستیم:
num_batches = 58746 num_val_batches = 500 num_train_batches = num_batches - num_val_batches val_ds = ds.take(num_val_batches).repeat() train_ds = ds.skip(num_val_batches).repeat()
ساخت مدل
مدل GPT اصلی، Transformer توالی-به-توالی که در فصل قبل دیدیم را سادهتر میکند. به جای اینکه یک توالی منبع و هدف را با یک encoder و decoder دریافت کنیم، همانطور که برای مدل ترجمهمان انجام دادیم، رویکرد GPT encoder را به کلی حذف میکند و فقط از decoder استفاده میکند. این یعنی اطلاعات فقط میتوانند از چپ به راست در یک توالی جریان پیدا کنند.
این یک شرطبندی جالبی از سوی توسعهدهندگان GPT بود. یک مدل فقط-decoder همچنان میتواند مسائل توالی-به-توالی مثل پرسش و پاسخ را مدیریت کند. با این حال، به جای اینکه سؤال و جواب را به عنوان ورودیهای جداگانه بدهیم، باید هر دو را در یک توالی واحد ترکیب کنیم و به مدلمان بدهیم. بنابراین، برخلاف Transformer اصلی، توکنهای سؤال با توکنهای جواب هیچ تفاوتی در برخورد نخواهند داشت. همه توکنها با یک مجموعه پارامتر یکسان در فضای نهفته یکسانی جاسازی میشوند.
پیامد دیگر این رویکرد این است که جریان اطلاعات دیگر دوطرفه نیست، حتی برای توالیهای ورودی. با یک ورودی مثل “Where is the capital of France?”، نمایش یادگرفته شده کلمه “Where” نمیتواند در لایه attention به کلمات “capital” و “France” توجه کند. این موضوع قدرت بیان مدل را محدود میکند اما مزیت عظیمی از نظر سادگی پیشآموزش دارد. ما نیازی نداریم مجموعه دادههایی با جفتهای ورودی و خروجی آماده کنیم؛ همه چیز میتواند یک توالی واحد باشد. میتوانیم روی هر متنی که در اینترنت پیدا میکنیم در مقیاس عظیم آموزش دهیم.
بیایید TransformerDecoder را از فصل ۱۵ کپی کنیم اما لایه cross-attention را حذف کنیم، همان لایهای که به decoder اجازه میداد به توالی encoder توجه کند. همچنین یک تغییر جزئی انجام خواهیم داد و dropout را بعد از بلوکهای attention و feedforward اضافه میکنیم. در فصل ۱۵، فقط از یک لایه Transformer در encoder و decoder خود استفاده کردیم، بنابراین میتوانستیم تنها با یک لایه dropout در انتهای کل مدلمان کنار بیاییم. برای مدل GPT خودمان، تعداد زیادی لایه روی هم میچینیم، پس اضافه کردن dropout در داخل هر لایه decoder برای جلوگیری از overfitting مهم است.
from keras import layers
class TransformerDecoder(keras.Layer):
def __init__(self, hidden_dim, intermediate_dim, num_heads):
super().__init__()
key_dim = hidden_dim // num_heads
# Self-attention layers
self.self_attention = layers.MultiHeadAttention(
num_heads, key_dim, dropout=0.1
)
self.self_attention_layernorm = layers.LayerNormalization()
# Feedforward layers
self.feed_forward_1 = layers.Dense(intermediate_dim, activation="relu")
self.feed_forward_2 = layers.Dense(hidden_dim)
self.feed_forward_layernorm = layers.LayerNormalization()
self.dropout = layers.Dropout(0.1)
def call(self, inputs):
# Self-attention computation
residual = x = inputs
x = self.self_attention(query=x, key=x, value=x, use_causal_mask=True)
x = self.dropout(x)
x = x + residual
x = self.self_attention_layernorm(x)
# Feedforward computation
residual = x
x = self.feed_forward_1(x)
x = self.feed_forward_2(x)
x = self.dropout(x)
x = x + residual
x = self.feed_forward_layernorm(x)
return x
در مرحله بعد، میتوانیم لایه PositionalEmbedding را از فصل ۱۵ کپی کنیم. به یاد بیاورید که این لایه یک راه ساده به ما میدهد تا برای هر موقعیت در یک توالی یک embedding یاد بگیریم و آن را با embeddingهای توکنهایمان ترکیب کنیم.
یک ترفند جالب وجود دارد که میتوانیم در اینجا به کار ببریم تا مقداری از حافظه GPU را ذخیره کنیم. بزرگترین وزنها در یک مدل Transformer، embeddingهای توکن ورودی و لایه dense پیشبینی خروجی هستند، چون آنها با فضای واژگان ما سروکار دارند. وزن embedding توکن شکل (vocab_size, hidden_dim) دارد تا هر توکن ممکن را جاسازی کند. پروجکشن خروجی ما شکل (hidden_dim, vocab_size) دارد تا برای هر توکن ممکن یک پیشبینی اعشاری بسازد.
در واقع میتوانیم این دو ماتریس وزن را به هم گره بزنیم. برای محاسبه پیشبینیهای نهایی مدلمان، حالتهای مخفی را در ترانهاده ماتریس embedding توکن ضرب میکنیم. میتوانید به پروجکشن نهاییمان به عنوان یک “embedding معکوس” فکر کنید. این نگاشت از فضای مخفی به فضای توکن انجام میدهد، در حالی که یک embedding از فضای توکن به فضای مخفی نگاشت میکند. معلوم میشود که استفاده از وزنهای یکسان برای این پروجکشن ورودی و خروجی ایده خوبی است.
اضافه کردن این قابلیت به PositionalEmbedding ساده است؛ فقط یک آرگومان reverse به متد call اضافه میکنیم که پروجکشن را با ترانهاده embedding توکن محاسبه میکند.
from keras import ops
class PositionalEmbedding(keras.Layer):
def __init__(self, sequence_length, input_dim, output_dim):
super().__init__()
self.token_embeddings = layers.Embedding(input_dim, output_dim)
self.position_embeddings = layers.Embedding(sequence_length, output_dim)
def call(self, inputs, reverse=False):
if reverse:
token_embeddings = self.token_embeddings.embeddings
return ops.matmul(inputs, ops.transpose(token_embeddings))
positions = ops.cumsum(ops.ones_like(inputs), axis=-1) - 1
embedded_tokens = self.token_embeddings(inputs)
embedded_positions = self.position_embeddings(positions)
return embedded_tokens + embedded_positions
بیایید مدل خود را بسازیم. ما هشت لایه decoder را روی هم میچینیم تا یک مدل “mini” GPT واحد بسازیم.
همچنین یک تنظیم Keras به نام دقت مختلط (mixed precision) را روشن خواهیم کرد تا آموزش را سریعتر کنیم. این کار به Keras اجازه میدهد برخی از محاسبات مدل را با فدا کردن مقداری از دقت عددی خیلی سریعتر اجرا کند. فعلاً این موضوع کمی مبهم میماند، اما توضیح کامل آن در فصل ۱۸ در انتظار شماست.
# Enables mixed precision (see chapter 18)
keras.config.set_dtype_policy("mixed_float16")
vocab_size = tokenizer.vocabulary_size()
hidden_dim = 512
intermediate_dim = 2056
num_heads = 8
num_layers = 8
inputs = keras.Input(shape=(None,), dtype="int32", name="inputs")
embedding = PositionalEmbedding(sequence_length, vocab_size, hidden_dim)
x = embedding(inputs)
x = layers.LayerNormalization()(x)
for i in range(num_layers):
x = TransformerDecoder(hidden_dim, intermediate_dim, num_heads)(x)
outputs = embedding(x, reverse=True)
mini_gpt = keras.Model(inputs, outputs)
این مدل ۴۱ میلیون پارامتر دارد که برای مدلهای این کتاب بزرگ است، اما در مقایسه با بیشتر LLMهای امروزی که از چند میلیارد تا تریلیونها پارامتر دارند، بسیار کوچک محسوب میشود.
پیشآموزش مدل
آموزش یک Transformer بزرگ به طور مشهوری سختگیر است — مدل به مقداردهی اولیه پارامترها و انتخاب بهینهساز حساس است. وقتی لایههای زیادی از Transformer روی هم چیده میشوند، به راحتی ممکن است دچار گرادیانهای انفجاری شویم، جایی که پارامترها خیلی سریع بهروزرسانی میشوند و تابع loss ما همگرا نمیشود. یک ترفند که خوب جواب میدهد این است که به صورت خطی و تدریجی به نرخ یادگیری کامل برسیم، طی تعدادی گام گرمکردن (warmup)، تا بهروزرسانیهای اولیه پارامترهای مدلمان کوچک باشند. پیادهسازی این کار در Keras با LearningRateSchedule آسان است.
class WarmupSchedule(keras.optimizers.schedules.LearningRateSchedule):
def __init__(self):
# Peak learning rate
self.rate = 2e-4
self.warmup_steps = 1_000.0
def __call__(self, step):
step = ops.cast(step, dtype="float32")
scale = ops.minimum(step / self.warmup_steps, 1.0)
return self.rate * scale
میتوانیم نرخ یادگیری خود را در طول زمان رسم کنیم تا مطمئن شویم همان چیزی است که انتظار داریم (شکل ۱۶.۲):
import matplotlib.pyplot as plt
schedule = WarmupSchedule()
x = range(0, 5_000, 100)
y = [ops.convert_to_numpy(schedule(step)) for step in x]
plt.plot(x, y)
plt.xlabel("Train Step")
plt.ylabel("Learning Rate")
plt.show()

شکل 16.2: Warmup باعث میشود بهروزرسانیهای پارامترهای مدل در ابتدای آموزش کوچکتر باشد و میتواند به پایداری کمک کند.
مدل خود را با استفاده از یک بار عبور از ۱ میلیارد توکنمان آموزش خواهیم داد، که در هشت epoch تقسیم شده است تا بتوانیم گاهی اوقات loss و accuracy مجموعه اعتبارسنجی خود را بررسی کنیم.
ما در حال آموزش یک نسخه مینیاتوری از GPT هستیم، با ۳ برابر پارامتر کمتر از GPT-1 و ۱۰۰ برابر گام آموزشی کمتر در کل. اما با وجود اینکه آموزش این مدل دو مرتبه ارزانتر از کوچکترین مدل GPT است، این فراخوانی fit() محاسباتیترین اجرای آموزشی در کل کتاب خواهد بود. اگر همزمان با خواندن در حال اجرای کد هستید، همه چیز را راه بیندازید و یک نفس عمیق بکشید!
num_epochs = 8
# Set these to a lower value if you don't want to wait for training.
steps_per_epoch = num_train_batches // num_epochs
validation_steps = num_val_batches
mini_gpt.compile(
optimizer=keras.optimizers.Adam(schedule),
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=["accuracy"],
)
mini_gpt.fit(
train_ds,
validation_data=val_ds,
epochs=num_epochs,
steps_per_epoch=steps_per_epoch,
validation_steps=validation_steps,
)
logit چیست؟
هنگامی که مدل خود را compile میکنیم، متوجه یک مقدار جدید برای loss میشوید: SparseCategoricalCrossentropy(from_logits=True). logit چیست؟
پروجکشن خروجی در انتهای مدل transformer ما شامل activation معمول softmax نیست. میتوانید به این خروجی به عنوان یک دسته “احتمالات لگاریتمی نرمالنشده” برای هر توکن فکر کنید. اگر هر مقدار خروجی را تواندار کنید و همه مقدارها را نرمال کنید تا مجموع آنها ۱ شود (این همان کاری است که تابع softmax انجام میدهد)، یک مقدار احتمال به دست خواهید آورد. یک اصطلاح رایج برای یک “احتمال لگاریتمی نرمالنشده”، logit است، و همانطور که در بخش بعدی خواهیم دید، کار با logitها هنگام تولید متن میتواند آسانتر باشد.
Keras به شما این انتخاب را میدهد که تابع softmax را کجا اعمال کنید. برای مسائل طبقهبندی، میتوانید یا از softmax به عنوان آخرین activation مدل استفاده کنید و احتمالات را خروجی بگیرید، یا softmax را به داخل تابع loss منتقل کنید و logitها را خروجی بگیرید. برای انجام دومی، باید SparseCategoricalCrossentropy(from_logits=True) را به عنوان loss طبقهبندی پاس دهید.
پس از آموزش، مدل ما میتواند توکن بعدی در یک توالی را حدود ۳۶٪ از مواقع در مجموعه اعتبارسنجیمان پیشبینی کند، هرچند که چنین معیاری فقط یک heuristic خام است.
توجه کنید که مدل ما کمآموزش است. loss اعتبارسنجیمان بعد از هر epoch همچنان کاهش خواهد یافت، که با توجه به اینکه صد برابر کمتر از GPT-1 گام آموزشی استفاده کردیم، تعجبآور نیست. آموزش برای مدت طولانیتر ایده خوبی خواهد بود، اما برای پرداخت هزینه محاسبات به زمان و پول نیاز داریم.
بیایید با مدل mini-GPT خودمان بازی کنیم.
رمزگشایی مولد (Generative decoding)
برای نمونهگیری برخی خروجی از مدل خود، میتوانیم رویکردی را که برای تولید شکسپیر یا ترجمههای اسپانیایی در فصل ۱۵ استفاده کردیم دنبال کنیم. یک prompt از توکنهای ثابت را به مدل میدهیم. برای هر موقعیت در توالی ورودی، مدل یک توزیع احتمال روی کل واژگان برای توکن بعدی خروجی میدهد. با انتخاب محتملترین توکن بعدی در آخرین موقعیت، اضافه کردن آن به توالیمان، و سپس تکرار این فرآیند، قادر خواهیم بود یک توالی جدید تولید کنیم، هر بار یک توکن.
def generate(prompt, max_length=64):
tokens = list(ops.convert_to_numpy(tokenizer(prompt)))
prompt_length = len(tokens)
for _ in range(max_length - prompt_length):
prediction = mini_gpt(ops.convert_to_numpy([tokens]))
prediction = ops.convert_to_numpy(prediction[0, -1])
tokens.append(np.argmax(prediction).item())
return tokenizer.detokenize(tokens)
بیایید این را با یک prompt متنی امتحان کنیم:
>>> prompt = "A piece of advice" >>> generate(prompt) A piece of advice, and the best way to get a feel for yourself is to get a sense of what you are doing. If you are a business owner, you can get a sense of what you are doing. You can get a sense of what you are doing, and you can get a sense of what
اولین چیزی که با اجرای این کد متوجه میشوید این است که تکمیل شدنش چند دقیقه طول میکشد. این کمی گیجکننده است. ما در طول آموزش حدود ۲۰۰,۰۰۰ توکن در ثانیه روی سختافزار مرجع خود پیشبینی میکردیم. حلقه تولیدی ممکن است زمان اضافه کند، اما تاخیر یک دقیقهای خیلی زیاد است. چه اتفاقی افتاده؟
بزرگترین دلیل کندی ما، حداقل در backendهای Jax و TensorFlow، این است که یک محاسبه کامپایلنشده اجرا میکنیم.
هر بار که fit() یا predict() را اجرا میکنید، Keras محاسباتی که روی هر batch از داده اجرا میشود را کامپایل میکند. تمام keras.ops استفادهشده از Python خارج میشوند و توسط فریمورک backend به شدت بهینه میشوند. برای یک batch کند است اما برای هر فراخوانی بعدی به شدت سریعتر است. با این حال، وقتی مستقیماً مدل را فراخوانی میکنیم همانطور که قبلاً انجام دادیم، فریمورک backend باید forward pass را در هر گام به صورت زنده و بدون بهینهسازی اجرا کند.
راهحل آسان در اینجا تکیه کردن به predict() است. با predict()، Keras کامپایل را برای ما مدیریت میکند، اما یک نکته مهم وجود دارد که باید مراقب آن باشیم. وقتی TensorFlow یا Jax یک تابع را کامپایل میکنند، این کار را برای یک شکل ورودی خاص انجام میدهند. با یک شکل مشخص، backend میتواند برای سختافزار خاص بهینهسازی کند، چون دقیقاً میداند چند دستور پردازنده منفرد یک عملیات تانسور را تشکیل میدهند. اما در تابع تولید ما، مدل را با یک توالی فراخوانی میکنیم که بعد از هر پیشبینی شکلش تغییر میکند. این هر بار که predict() را فراخوانی میکنیم، کامپایل مجدد را ایجاد میکند.
در عوض، میتوانیم از کامپایل مجدد تابع predict() جلوگیری کنیم اگر ورودیمان را padding کنیم تا توالیمان همیشه همطول باشد. بیایید این را امتحان کنیم.
def compiled_generate(prompt, max_length=64):
tokens = list(ops.convert_to_numpy(tokenizer(prompt)))
prompt_length = len(tokens)
# Pads tokens to the full sequence length
tokens = tokens + [0] * (max_length - prompt_length)
for i in range(prompt_length, max_length):
prediction = mini_gpt.predict(np.array([tokens]), verbose=0)
prediction = prediction[0, i - 1]
tokens[i] = np.argmax(prediction).item()
return tokenizer.detokenize(tokens)
بیایید ببینیم این تابع جدید چقدر سریع است:
>>> import timeit >>> tries = 10 >>> timeit.timeit(lambda: compiled_generate(prompt), number=tries) / tries 0.4866470648999893
فراخوانی تولید ما با کامپایل از دقایق به کمتر از یک ثانیه رسید. این بهبود کاملاً چشمگیری است.
تولید کششده (Cached generation)
هنوز یک ناکارآمدی بزرگ دیگر در تابع تولیدی که ساختیم وجود دارد. میتوانید آن را تشخیص دهید؟
هر بار که مدل خود را فراخوانی میکنیم، آن را برای یک توالی کامل فراخوانی میکنیم و سپس همه چیز را به جز پیشبینیها برای یک موقعیت واحد دور میاندازیم. این اتلاف است — توالیمان فقط با یک توکن بین گامهای تولید تغییر میکند. وقتی در فصل ۱۵ تولید با RNN انجام دادیم، میتوانستیم state RNN خود را نگه داریم و فقط خروجیها را برای یک توکن واحد در هر گام محاسبه کنیم. این بردار state تمام اطلاعاتی که مدل درباره توالی گذشته نیاز داشت را در خود داشت. Transformerهایی که از attention علّی استفاده میکنند، مثل GPT، در واقع مفهوم مشابهی از state دارند.
اگر کل مدلی که ساختیم را مرور کنیم، متوجه میشوید که attention تنها جایی است که مدل اطلاعات را از موقعیتی به موقعیت دیگر منتقل میکند. بلوکهای feedforward یک transformer فقط نمایش مخفی هر موقعیت توکن را به صورت مجزا تغییر میدهند.
در داخل attention، اطلاعات مربوط به توکنهای گذشته را از طریق بردارهای key و value وارد میکنیم. برای یک query در یک موقعیت، امتیازهای attention را با ضرب داخلی query با تمام بردارهای key قبلی و ترکیب تمام بردارهای value قبلی محاسبه میکنیم. این بردارهای key و value برای توکنهای گذشته در توالی هرگز تغییر نمیکنند — ورودی گذشته ثابت است، و ماسک علّی مانع از “نگاه کردن به جلو” Transformer به توکنهای آینده میشود. بنابراین اگر تمام بردارهای key و value را در هر لایه Transformer کش کنیم، معادل state یک RNN را خواهیم داشت. میتوانیم از آن برای محاسبه خروجیهای Transformer برای یک موقعیت واحد در هر بار استفاده کنیم.
پیادهسازی این کار کمی دست و پاگیر است، چون شامل ذخیره و استفاده مجدد از آرایههای میانی از هر لایه attention در Transformer است، اما مهم است. ورودیهای مدل شما میتوانند از به اندازه حداکثر طول خروجیتان به یک توکن واحد کاهش یابند. اگر در حال تولید یک توالی هزاران توکنی هستید، کشکردن میتواند به سرعتی هزار برابری منجر شود! هر پیادهسازی کارآمد نمونهگیری تولیدی شامل کشکردن key و value خواهد بود.
استراتژیهای نمونهگیری
یکی دیگر از مشکلات آشکار خروجی تولیدی ما این است که مدلمان اغلب خودش را تکرار میکند. در اجرای آموزشی خاص ما، مدل گروه کلمات “get a sense of what you are doing” را بارها و بارها تکرار میکند.
این آنقدرها هم یک باگ نیست، بلکه نتیجه مستقیم هدف آموزشی ما است. مدل ما سعی میکند محتملترین توکن بعدی را در یک توالی، در حدود یک میلیارد کلمه روی موضوعات بسیار بسیار زیاد، پیشبینی کند. اگر انتخاب واضحی برای اینکه یک توالی متنی باید به کجا برود وجود نداشته باشد، یک استراتژی مؤثر این است که کلمات رایج یا الگوهای تکراری از کلمات را حدس بزنیم. جای تعجب نیست که مدل ما تقریباً بلافاصله طی آموزش یاد میگیرد این کار را انجام دهد. اگر آموزش مدلمان را خیلی زود متوقف کنید، احتمالاً بیوقفه کلمه "the" را تولید میکند، چون "the" رایجترین کلمه در زبان انگلیسی است.
در طول حلقه تولیدی خود، همیشه محتملترین توکن پیشبینیشده در خروجی مدلمان را انتخاب کردهایم. اما خروجی ما فقط یک توکن پیشبینیشده نیست؛ بلکه یک توزیع احتمال روی تمام ۳۲,۰۰۰ توکن در واژگان ما است.
استفاده از محتملترین خروجی در هر گام تولید را جستجوی حریصانه (greedy search) مینامند. این سادهترین رویکرد برای استفاده از پیشبینیهای مدل است، اما به هیچ وجه تنها رویکرد نیست. اگر در عوض مقداری تصادفیبودن به فرآیند اضافه کنیم، میتوانیم توزیع احتمالی که مدل یاد گرفته را به طور گستردهتری کاوش کنیم. این میتواند مانع شود که در حلقههای توالیهای توکن با احتمال بالا گیر کنیم.
بیایید این را امتحان کنیم. میتوانیم با بازنویسی تابع تولید خود شروع کنیم تا بتوانیم یک تابع پاس دهیم که از پیشبینیهای مدل به انتخاب توکن بعدی نگاشت میکند. این را استراتژی نمونهگیریمان مینامیم:
def compiled_generate(prompt, sample_fn, max_length=64):
tokens = list(ops.convert_to_numpy(tokenizer(prompt)))
prompt_length = len(tokens)
tokens = tokens + [0] * (max_length - prompt_length)
for i in range(prompt_length, max_length):
prediction = mini_gpt.predict(np.array([tokens]), verbose=0)
prediction = prediction[0, i - 1]
next_token = ops.convert_to_numpy(sample_fn(prediction))
tokens[i] = np.array(next_token).item()
return tokenizer.detokenize(tokens)
حالا میتوانیم جستجوی حریصانه خود را به صورت یک تابع ساده بنویسیم که به compiled_generate() پاس میدهیم:
def greedy_search(preds):
return ops.argmax(preds)
compiled_generate(prompt, greedy_search)
خروجیهای Transformer یک توزیع دستهای را تعریف میکنند که در آن هر توکن احتمال مشخصی برای خروجی شدن در هر گام زمانی دارد. به جای اینکه فقط محتملترین توکن را انتخاب کنیم، میتوانیم مستقیماً از این توزیع نمونهگیری کنیم. keras.random.categorical() پیشبینیهای ما را از تابع softmax عبور میدهد تا یک توزیع احتمال به دست بیاورد و سپس به صورت تصادفی از آن نمونهگیری میکند. بیایید آن را امتحان کنیم:
def random_sample(preds, temperature=1.0):
preds = preds / temperature
return keras.random.categorical(preds[None, :], num_samples=1)[0]
>>> compiled_generate(prompt, random_sample) A piece of advice, just read my knees and stick with getables and a hello to me. However, the bar napkin doesn't last as long. I happen to be waking up close and pull it up as I wanted too and I still get it, really, shouldn't be a reaction
خروجیهای ما متنوعتر هستند و مدل دیگر در حلقهها گیر نمیکند. اما نمونهگیری ما حالا بیش از حد در حال کاوش است؛ خروجی بدون هیچ پیوستگی به شدت جابهجا میشود.
متوجه میشوید که یک پارامتر به نام temperature اضافه کردهایم. میتوانیم از این پارامتر برای تیز کردن یا پهن کردن توزیع احتمال خود استفاده کنیم تا نمونهگیریمان کمتر یا بیشتر توزیع ما را کاوش کند.
اگر یک temperature پایین پاس دهیم، همه logitها را قبل از تابع softmax بزرگتر میکنیم، که باعث میشود محتملترین خروجیمان حتی محتملتر شود. اگر یک temperature بالا پاس دهیم، logitهای ما قبل از softmax کوچکتر خواهند بود و توزیع احتمال ما پراکندهتر خواهد شد. بیایید این را چند بار امتحان کنیم تا ببینیم چگونه بر نمونهگیریمان تأثیر میگذارد:
>>> from functools import partial >>> compiled_generate(prompt, partial(random_sample, temperature=2.0)) A piece of advice tran writes using ignore unnecessary pivot - come without introdu accounts indicugelâ per\u3000divuren sendSolisżsilen om transparent Gill Guide pover integer song arrays coding\u3000LIST**…Allow index criteria Draw Reference Ex artifactincluding lib tak Br basunker increases entirelytembre AnyкаTextView cardinal spiritual heavenToen >>> compiled_generate(prompt, partial(random_sample, temperature=0.8)) A piece of advice I wrote about the same thing today. I have been a writer for two years now. I am writing this blog and I just wrote about it. I am writing this blog and it was really interesting. I have been writing about the book and I have read many things about my life. The >>> compiled_generate(prompt, partial(random_sample, temperature=0.2)) A piece of advice, and a lot of people are saying that they have to be careful about the way they think about it. I think it's a good idea to have a good understanding of the way you think about it. I think it's a good idea to have a good understanding of the
در temperature بالا، خروجیهای ما دیگر شبیه انگلیسی نیستند و به توکنهای به ظاهر تصادفی متوسل میشوند. در temperature پایین، رفتار مدلمان شروع میکند به جستجوی حریصانه شبیه شود و الگوهای خاصی از متن را بارها و بارها تکرار میکند.
یکی دیگر از تکنیکهای محبوب برای شکلدهی توزیع ما، محدود کردن نمونهگیریمان به مجموعهای از محتملترین توکنها است. به این نمونهگیری top-k میگویند، که در آن K تعداد کاندیداهایی است که باید کاوش کنید. شکل ۱۶.۳ نشان میدهد که چگونه نمونهگیری top-k نقطه میانی بین رویکردهای حریصانه و تصادفی را پیدا میکند.

شکل 16.3: استراتژیهای نمونهگیری Greedy، top-k و random روی یک توزیع احتمال یکسان
بیایید این را در کد امتحان کنیم. میتوانیم از keras.ops.top_k برای پیدا کردن K عنصر برتر یک آرایه استفاده کنیم:
def top_k(preds, k=5, temperature=1.0):
preds = preds / temperature
top_preds, top_indices = ops.top_k(preds, k=k, sorted=False)
choice = keras.random.categorical(top_preds[None, :], num_samples=1)[0]
return ops.take_along_axis(top_indices, choice, axis=-1)
میتوانیم چند حالت مختلف از top-k را امتحان کنیم تا ببینیم چگونه بر نمونهگیری تأثیر میگذارد:
>>> compiled_generate(prompt, partial(top_k, k=5)) A piece of advice that I can't help it. I'm not going to be able to do anything for a few months, but I'm trying to get a little better. It's a little too much. I have a few other questions on this site, but I'm sure I >>> compiled_generate(prompt, partial(top_k, k=20)) A piece of advice and guidance from the Audi Bank in 2015. With all the above, it's not just a bad idea, but it's very good to see that is going to be a great year for you in 2017. That's really going to
پاس دادن یک cutoff top-k با نمونهگیری temperature متفاوت است. پاس دادن یک temperature پایین توکنهای محتمل را محتملتر میکند، اما هیچ توکنی را کنار نمیگذارد. نمونهگیری top-k احتمال هر چیزی خارج از K کاندیدا را صفر میکند. میتوانید این دو را ترکیب کنید، برای مثال، نمونهگیری از پنج کاندیدای برتر با temperature برابر 0.5:
>>> compiled_generate(prompt, partial(top_k, k=5, temperature=0.5)) A piece of advice that you can use to get rid of the problem. The first thing you need to do is to get the job done. It is important that you have a plan that will help you get rid of it. The first thing you need to do is to get rid of the problem yourself.
یک استراتژی نمونهگیری کنترل مهمی هنگام تولید متن است، و رویکردهای بسیار بیشتری وجود دارند. به عنوان مثال، beam search تکنیکی است که به صورت ابتکاری زنجیرههای متعددی از توکنهای پیشبینیشده را با نگه داشتن تعداد ثابتی “beam” (زنجیرههای مختلف توکنهای پیشبینیشده) برای کاوش در هر گام زمانی، بررسی میکند.
با نمونهگیری top-k، مدل ما چیزی نزدیکتر به متن انگلیسی قابل قبول تولید میکند، اما کاربرد ظاهری کمی برای چنین خروجی وجود دارد. این با نتایج GPT-1 همخوانی دارد. برای مقاله اولیه GPT، خروجی تولید شده بیشتر یک کنجکاوی بود، و نتایج پیشرفته فقط با تنظیم دقیق مدلهای طبقهبندی به دست آمد. mini-GPT ما خیلی کمتر از GPT-1 آموزش دیده است.
برای رسیدن به مقیاس LLMهای تولیدی امروزی، باید تعداد پارامترهایمان را حداقل ۱۰۰ برابر و تعداد گامهای آموزشیمان را حداقل ۱,۰۰۰ برابر افزایش دهیم. اگر این کار را انجام میدادیم، همان جهشهای کیفی را که OpenAI با GPT مشاهده کرد، میدیدیم. و میتوانیم این کار را انجام دهیم! دستور العمل آموزشی که قبلاً استفاده کردیم دقیقاً همان نقشه راهی است که امروزه همه برای آموزش LLMها استفاده میکنند. تنها قطعات گمشده یک بودجه محاسباتی بسیار بزرگ و چند ترفند برای آموزش روی چند ماشین هستند که در فصل ۱۸ پوشش خواهیم داد.
برای یک رویکرد عملیتر، به استفاده از یک مدل از پیش آموزشدیده انتقال پیدا میکنیم. این به ما اجازه میدهد رفتار یک LLM را در مقیاس امروزی کاوش کنیم.
استفاده از یک LLM از پیش آموزشدیده
حالا که یک مدل زبانی کوچک را از صفر آموزش دادهایم، بیایید از یک مدل از پیش آموزشدیده یک میلیارد پارامتری استفاده کنیم و ببینیم چه کاری میتواند انجام دهد.
تولید متن با مدل Gemma
برای بارگذاری این مدل از پیش آموزشدیده، میتوانیم از KerasHub استفاده کنیم، همانطور که در فصلهای قبلی انجام دادهایم.
import kagglehub kagglehub.login()
gemma_lm = keras_hub.models.CausalLM.from_preset(
"gemma3_1b",
dtype="float32",
)
>>> gemma_lm.compile(sampler="greedy")
>>> gemma_lm.generate("A piece of advice", max_length=40)
A piece of advice from a former student of mine:
"I'm not sure if you've heard of it, but I've been told that the best way to learn
>>> gemma_lm.generate("How can I make brownies?", max_length=40)
How can I make brownies?
[User 0001]
I'm trying to make brownies for my son's birthday party. I've never made brownies before.
تنظیم دقیق دستورالعمل
import json
PROMPT_TEMPLATE = """[instruction]\n{}[end]\n[response]\n"""
RESPONSE_TEMPLATE = """{}[end]"""
dataset_path = keras.utils.get_file(
origin=(
"https://hf.co/datasets/databricks/databricks-dolly-15k/"
"resolve/main/databricks-dolly-15k.jsonl"
),
)
data = {"prompts": [], "responses": []}
with open(dataset_path) as file:
for line in file:
features = json.loads(line)
if features["context"]:
continue
data["prompts"].append(PROMPT_TEMPLATE.format(features["instruction"]))
data["responses"].append(RESPONSE_TEMPLATE.format(features["response"]))
>>> data["prompts"][0] [instruction] Which is a species of fish? Tope or Rope[end] [response] >>> data["responses"][0] Tope[end]
انطباق کمرتبه (LoRA)
class LoraLinear(keras.Layer):
def __init__(self, input_dim, output_dim, rank):
super().__init__()
self.kernel = self.add_weight(
shape=(input_dim, output_dim), trainable=False
)
self.alpha = self.add_weight(shape=(input_dim, rank))
self.beta = self.add_weight(shape=(rank, output_dim))
def call(self, inputs):
frozen = ops.matmul(inputs, self.kernel)
update = ops.matmul(ops.matmul(inputs, self.alpha), self.beta)
return frozen + update

شکل 16.4: تجزیه کرنل کمرتبه شامل پارامترهای بسیار کمتری نسبت به خود کرنل است.
gemma_lm.backbone.enable_lora(rank=8)
gemma_lm.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=keras.optimizers.Adam(5e-5),
weighted_metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
gemma_lm.fit(train_ds, validation_data=val_ds, epochs=1)

شکل 16.5: LoRA به طور چشمگیری حافظه مورد نیاز برای گرادیانها و حالتهای بهینهساز را کاهش میدهد.
>>> gemma_lm.generate( ... "[instruction]\nHow can I make brownies?[end]\n" ... "[response]\n", ... max_length=512, ... ) [instruction] How can I make brownies?[end] [response] You can make brownies by mixing together 1 cup of flour, 1 cup of sugar, 1/2 cup of butter, 1/2 cup of milk, 1/2 cup of chocolate chips, and 1/2 cup of chocolate chips. Then, you can bake it in a 9x13 pan for 30 minutes at 350 degrees Fahrenheit. You can also add a little bit of vanilla extract to the batter to make it taste better.[end]
پیشرفت بیشتر با LLMها
یادگیری تقویتی با بازخورد انسانی (RLHF)
for prompts in dataset:
# Takes an action
responses = model.generate(prompts)
# Receives a reward
rewards = reward_model.predict(responses)
good_responses = []
for response, score in zip(responses, rewards):
if score > cutoff:
good_responses.append(response)
# Updates the model parameters. We do not update the reward model.
model.fit(good_responses)
gemma_lm = keras_hub.models.CausalLM.from_preset(
"gemma3_instruct_4b",
dtype="bfloat16",
)
PROMPT_TEMPLATE = """user
{}
model
"""
>>> prompt = "Why can't you assign values in Jax tensors? Be brief!" >>> gemma_lm.generate(PROMPT_TEMPLATE.format(prompt), max_length=512) user Why can't you assign values in Jax tensors? Be brief! model Jax tensors are designed for efficient automatic differentiation. Directly assigning values disrupts this process, making it difficult to track gradients correctly. Instead, Jax uses operations to modify tensor values, preserving the differentiation pipeline.
LLMهای چندوجهی

شکل 16.6: مدیریت ورودی تصویر با ترکیب توکنهای متن و توکنهای نرم تصویر
import matplotlib.pyplot as plt
image_url = (
"https://github.com/mattdangerw/keras-nlp-scripts/"
"blob/main/learned-python.png?raw=true"
)
image_path = keras.utils.get_file(origin=image_url)
image = np.array(keras.utils.load_img(image_path))
plt.axis("off")
plt.imshow(image)
plt.show()

شکل 16.7: یک تصویر آزمایشی برای مدل Gemma
>>> # Limits the maximum input size of the model
>>> gemma_lm.preprocessor.max_images_per_prompt = 1
>>> gemma_lm.preprocessor.sequence_length = 512
>>> prompt = "What is going on in this image? Be concise!"
>>> gemma_lm.generate({
... "prompts": PROMPT_TEMPLATE.format(prompt),
... "images": [image],
... })
user
What is going on in this image? Be concise!
model
A snake wearing glasses is sitting in a leather armchair, surrounded by a large
bookshelf, and reading a book. It's a whimsical, slightly surreal image.
مدلهای “استدلالی”
prompt = """Judy wrote a 2-page letter to 3 friends twice a week for 3 months. How many letters did she write? Be brief, and add "ANSWER:" before your final answer.""" # Turns on random sampling to get a diverse range of outputs gemma_lm.compile(sampler="random")
>>> gemma_lm.generate(PROMPT_TEMPLATE.format(prompt)) user Judy wrote a 2-page letter to 3 friends twice a week for 3 months. How many letters did she write? Be brief, and add "ANSWER:" before your final answer. model Here's how to solve the problem: * **Letters per week:** 3 friends * 2 letters/week = 6 letters/week * **Letters per month:** 6 letters/week * 4 weeks/month = 24 letters/month * **Letters in 3 months:** 24 letters/month * 3 months = 72 letters * **Total letters:** 72 letters * 2 = 144 letters ANSWER: 144
LLMها به کجا میروند؟
با توجه به مسیر LLMها که در ابتدای این فصل بحث شد، ممکن است واضح به نظر برسد که LLMها به کجا خواهند رفت. پارامترهای بیشتر! عملکرد حتی بهتر! به طور کلی، احتمالاً صحیح است، اما مسیر ما ممکن است چندان خطی نباشد.
اگر یک بودجه ثابت برای پیشآموزش داشته باشید، بگویید یک میلیون دلار، میتوانید تقریباً به آن به عنوان خرید مقدار ثابتی از محاسبات یا عملیات اعشاری فکر کنید. میتوانید آن flops را یا صرف آموزش با دادههای بیشتر یا آموزش یک مدل بزرگتر کنید.

شکل 16.8: تعداد پارامترهای LLM (چپ) و اندازه مجموعه دادههای پیشآموزش (راست) در طول زمان
خلاصه
- مدلهای زبانی بزرگ یا LLMها ترکیبی از چند مؤلفه کلیدی هستند:
- معماری Transformer
- یک وظیفه مدلسازی زبان (پیشبینی توکن بعدی بر اساس توکنهای گذشته)
- مقدار زیادی داده متنی بدون برچسب
- یک LLM یک توزیع احتمال برای پیشبینی توکنهای فردی میآموزد. این میتواند با یک استراتژی نمونهگیری برای تولید یک رشته طولانی از متن ترکیب شود.
- LLMها از میلیاردها پارامتر استفاده میکنند و بر روی تریلیونها کلمه متن آموزش داده میشوند.
- خروجی LLM غیرقابل اعتماد است و همه LLMها گاهی اطلاعات واقعی نادرست را توهم میبینند.
- LLMها میتوانند برای دنبال کردن دستورالعملها در یک دیالوگ چت تنظیم دقیق شوند.
- رایجترین گلوگاه منبع هنگام کار با LLMها حافظه شتابدهنده است.
- LoRA تکنیکی برای کاهش استفاده از حافظه است با فریز کردن بیشتر پارامترهای Transformer و فقط بهروزرسانی یک تجزیه کمرتبه از وزنهای پروژکشن attention.
- LLMها میتوانند دادهها را از روشهای مختلف ورودی یا خروجی کنند اگر بتوانید بفهمید چگونه این ورودیها یا خروجیها را به عنوان توالی در یک مسئله پیشبینی توالی قالببندی کنید.
- یک مدل پایه یک اصطلاح کلی برای مدلهای هر روشی است که با استفاده از خودنظارت برای طیف گستردهای از وظایف downstream آموزش داده شدهاند.
منبع: یادگیری عمیق با پایتون، ویرایش سوم نوشته فرانسوا شوله
دیدگاهتان را بنویسید