নিউরাল নেটওয়ার্কের মেকানিস্টিক ইন্টারপ্রেটেবিলিটি এজেন্ডার চেতনায়, এই পোস্টটি (এবং সম্ভবত অন্যদের একটি সিরিজে অনুসরণ করতে হবে) একটি সংকীর্ণ প্রযুক্তিগত কাজ মোকাবেলার জন্য একটি ট্রান্সফরমার মডেলের দ্বারা শেখা "অ্যালগরিদমগুলি" তদন্ত করে - "বৈধ বন্ধনী" এর একটি পরিবর্তিত সংস্করণ লিটকোড সমস্যা।
একটি LLM-তে আপনি যে আরও সাধারণ পরবর্তী টোকেন ভবিষ্যদ্বাণী আশা করেন তার চেয়ে কাজের উপযোগিতা পরিধিতে অনেক বেশি পরিমিত হলেও, অনুশীলনটি আমাদের কিছু প্রাথমিক অন্তর্দৃষ্টি, অনুসন্ধানী সরঞ্জাম এবং সাধারণ জ্ঞানতাত্ত্বিক পদ্ধতিগুলিকে অন্বেষণ করতে সাহায্য করবে যা সাধারণত মডেলগুলি কী করছে তা জানুন (এবং আমরা কীভাবে জানি তারা।)
ARENA Mechinterp মাসিক চ্যালেঞ্জগুলি এই পোস্টে একটি বিশাল প্রভাব ছিল, এবং সমস্যাগুলির প্রথম সেট সেখান থেকেই আসবে। (আপনার অবশ্যই প্রোগ্রামটি পরীক্ষা করা উচিত।)
লিটকোডে দেখা বৈধ বন্ধনী সমস্যা:
সমস্যার কিছু পরিবর্তিত সীমাবদ্ধতা যা আমরা কাজের জন্য ব্যবহার করব:
এটি "([)]" এর মতো কেস পরিচালনা করার প্রয়োজনীয়তাকে সরিয়ে দেয়।
উদাহরণ
“(((()))” → বৈধ
“()()()(” → অবৈধ
“)()()()(” → অবৈধ
def isValid(self, s: str) -> bool: nesting_depth = 0 for bracket in s: if bracket == '(': # An opening bracket increases unresolved nesting depth nesting_depth += 1 elif bracket == ')': # A closing bracket decreases unresolved nesting depth nesting_depth -= 1 # We don't expect to ever have negative unresolved nesting depth, # so we can declare 'invalid' midway through the sequence if we see this if nesting_depth < 0: return False # Final check that all open brackets were closed. return nesting_depth == 0
ব্যর্থতার ক্ষেত্রে নোট:
সিকোয়েন্সের শেষে nesting_depth ≠ 0
“()()()((” → অবৈধ
এর জন্য, এটা স্পষ্ট নয় যে একেবারে শেষ পর্যন্ত কিছু ভুল আছে যখন আমরা দেখতে পাই যে শেষ খোলা বন্ধনীতে একটি সহকারী বন্ধনী নেই। লক্ষ্য করার বিষয় হল যে ক্রমটির কোন বিন্দু নেই, একেবারে শেষ পর্যন্ত, যেখানে আমাদের কাছে কিছু জানার জন্য যথেষ্ট তথ্য ছিল বন্ধ ছিল।
সিকোয়েন্সের যেকোনো বিন্দুতে nesting_depth < 0
উদাহরণ: “())()()(” → অবৈধ
এই ক্ষেত্রে, অন্যদিকে, ক্রমটির বৈধতা জানার জন্য তৃতীয় অবস্থানের দ্বারা যথেষ্ট তথ্য রয়েছে, তাই আমরা এটিকে প্রারম্ভিক প্রস্থান বলতে পারি।
লক্ষণীয় কিছু হল যে এই উদাহরণটি প্রথম ব্যর্থতার পরীক্ষায় উত্তীর্ণ হবে কারণ শেষে nesting_depth
0 এর সমান হবে। তাই এই টেস্ট কেসটি শুধু আমাদের তাড়াতাড়ি থামাতে সাহায্য করে না, এটি অত্যাবশ্যক। এটি প্রথম ব্যর্থতার ক্ষেত্রে উদাহরণের ক্ষেত্রে প্রযোজ্য যেখানে এটি পরীক্ষা 2 পাস করবে।
এখন, আমরা আশা করি না যে একটি অটোরিগ্রেসিভ ট্রান্সফরমার মডেল সমস্যাটি ঠিক একইভাবে সমাধান করবে, কারণ এর স্থাপত্যটি একবার সিকোয়েন্সটি লুপ করার চেয়ে এবং সবকিছু ঠিক আছে কিনা তা পরীক্ষা করার চেয়ে কিছুটা ভিন্ন প্রক্রিয়া দেয়। যাইহোক, আমরা নিশ্চিতভাবে জানি যে ট্রান্সফরমার আর্কিটেকচার (এবং অন্যান্য সিকোয়েন্স প্রসেসিং আর্কিটেকচার) অন্তত একটি ক্রমানুসারে সমস্ত উপাদান সম্পর্কে তথ্য আবিষ্কার এবং প্রক্রিয়া করতে সক্ষম। এটি মনে রাখা গুরুত্বপূর্ণ যে সমাধানটি ভিন্ন দেখাতে পারে, সমস্যার গঠন একই এবং যা জানা যায় তার উপর কঠিন সীমানা এবং যেখানে অনুক্রমটি সত্য হতে থাকে তা লুপ এবং যদি-বিবৃতি বা স্ব-এর একটি সংমিশ্রণ হোক না কেন -অ্যাটেনশন সুইপস এবং এমএলপি ননলাইনারিটিস।
তারপরে আকর্ষণীয় প্রশ্ন হল এই আর্কিটেকচারটি কীভাবে এই তথ্যটি ব্যবহার করে এবং যদি এটি বিদ্যমান টুলিংয়ের সাথে সহজেই বোঝা যায়; কারণ যেকোনো আর্কিটেকচারের পর্যাপ্ত পারফরম্যান্স সমাধানের জন্য অন্তত উপরের দুটি ব্যর্থতার ক্ষেত্রে পরীক্ষা না করা অনিবার্য ।
এটি খেলনা সমস্যার অন্যতম সুবিধা; আমরা এই কঠিন গ্যারান্টিগুলির সাথে একটি যথেষ্ট বোঝার সংকীর্ণ কাজ পেয়েছি যা তদন্তকে জানাতে সাহায্য করতে পারে যা আমরা শীঘ্রই দেখতে পাব।
এখানে কিছু লক্ষ্য বৈশিষ্ট্য রয়েছে যা আমরা ডেটা জেনারেশনের জন্য যাচ্ছি:
সমান সংখ্যক সুষম এবং ভারসাম্যহীন স্ট্রিং।
স্ট্রিংগুলি জোড় দৈর্ঘ্যের হবে, কারণ একটি বিজোড়-দৈর্ঘ্যের স্ট্রিং স্পষ্টতই ভারসাম্যহীন; যা মডেল শেখার জন্য খুব আকর্ষণীয় হিউরিস্টিক হবে না।
সমস্ত স্ট্রিং দৈর্ঘ্য (2-40) সমানভাবে সম্ভাব্য হওয়া উচিত।
একটি প্রদত্ত স্ট্রিং দৈর্ঘ্যের জন্য, সমস্ত সম্ভাব্য বন্ধনীর নেস্টিং গভীরতা সমানভাবে সম্ভাব্য হওয়া উচিত।
একটি সাধারণ থিম স্পষ্ট: আমরা প্রতিটি চিন্তাযোগ্য বন্টন পরিসংখ্যানকে যেকোন দিক থেকে পক্ষপাত কমাতে, দৃঢ়তা নিশ্চিত করতে এবং মডেলের জন্য একটি বিকল্প হিসাবে সুস্পষ্ট দ্রুত-জয় হিউরিস্টিকসকে অস্বীকার করার জন্য সমানভাবে সম্ভাবনা তৈরি করার চেষ্টা করছি। ব্যর্থতার কেস তৈরি করার জন্য, আমরা প্রথমে উপরে তালিকাভুক্ত গ্যারান্টি সহ বৈধ বন্ধনী তৈরি করব এবং তারপর ভারসাম্যহীন হওয়ার জন্য তাদের অর্ধেক পরিবর্তন করব।
from random import randint, randrange, sample from typing import List, Tuple, Union, Optional, Callable, Dict from jaxtyping import Float, Int import torch as t from torch import Tensor import plotly.express as px import einops from dataclasses import dataclass import math
def isValid(s: str) -> bool: nesting_depth = 0 for bracket in s: if bracket == '(': # An opening bracket increases unresolved nesting depth nesting_depth += 1 elif bracket == ')': # A closing bracket decreases unresolved nesting depth nesting_depth -= 1 # We don't expect to ever have negative unresolved nesting depth, # so we can declare 'invalid' midway through the sequence if we see this if nesting_depth < 0: return False # Final check that all open brackets were closed. return nesting_depth == 0
assert isValid('()()((((()())())))') == True assert isValid(')()((((()())()))(') == False
বন্ধনী প্রজন্মের প্রথম প্রচেষ্টা শুধু একটি এলোমেলো হাঁটা করে। কিন্তু আপনি দেখতে পাচ্ছেন ভারসাম্যহীন বন্ধনীর সাবস্পেস নীচের প্লটে ভারসাম্যের তুলনায় অনেক বড়; তাই আমাদের স্টোকাস্টিটি ভিন্নভাবে প্রবর্তন করতে হবে।
PARENS = ['(', ')'] def get_random_walk_parens(parens_num: int, length_range: Tuple[int]) -> List[str]: range_start, range_end = length_range random_parens = [ # Add 1 to make passed range_end inclusive ''.join(PARENS[randint(0, 1)] for _ in range(randrange(range_start, range_end + 1, 2))) for _ in range(parens_num) ] return random_parens
random_parens = get_random_walk_parens(1000, (2, 10))
random_parens[:10] # output [')(', '(((())()', ')(((()()))', '))))))', '))())()(', '))', '(())', ')()(()()()', ')()())))((', '()']
is_valid_evals = [str(isValid(random_paren)) for random_paren in random_parens] len_evals = [len(random_paren) for random_paren in random_parens]
fig = px.histogram(is_valid_evals, title="Count of is-balanced for random walk parentheses strings") fig.show()
আমরা একটি সুষম বন্ধনী স্ট্রিং নির্মাণকে নেস্টেড বন্ধনীর বিচ্ছিন্ন ইউনিটে ভেঙে দিতে পারি। এই লোভনীয় নির্মাণের জন্য, একটি স্ট্রিং তৈরির প্রক্রিয়ার প্রতিটি ধাপে কার্যকর গভীরতার একটি ঝুড়ি থেকে বাসা বাঁধার গভীরতা বেছে নেওয়া হয় (লক্ষ্যযুক্ত স্ট্রিং দৈর্ঘ্যকে সম্মান করার জন্য।)
উদাহরণস্বরূপ লক্ষ্য দৈর্ঘ্য 6
এর জন্য, নিম্নলিখিত অনন্য বাসা বাঁধার পচন সম্ভব:
-> [2, 1], [1, 2], [1,1,1] or [3]
Corresponding to:
-> (())(), ()(()), ()()(), ((()))
def get_balanced_parens(nest_depth: int) -> str: """Generate parentheses at the required nesting depth.""" return (PARENS[0] * nest_depth) + (PARENS[1] * nest_depth) assert get_balanced_parens(3) == '((()))'
def get_balanced_sequence_parens(nest_depth_sequence: List[int]) -> str: """Return a parentheses string following the nesting depth sequence from a given list.""" return ''.join(get_balanced_parens(nest_depth) for nest_depth in nest_depth_sequence) assert get_balanced_sequence_parens([1,1,2,3]) == '()()(())((()))'
def get_random_depth_sequence(target_paren_len: int) -> List[int]: depth_sequence = [] while target_paren_len > 0: depth = randint(1, target_paren_len / 2) depth_sequence.append(depth) target_paren_len -= 2 * depth return depth_sequence rand_depth_seq = get_random_depth_sequence(10) print(rand_depth_seq) # Example output: '[3, 1, 1]' assert sum([2 * depth for depth in rand_depth_seq]) == 10
def get_random_sequence_parens(parens_num: int, length_range: Tuple[int]) -> List[str]: random_depth_sequences = [get_random_depth_sequence( randrange(*length_range, 2) ) for _ in range(parens_num)] random_parens = [ get_balanced_sequence_parens(random_depth_sequence) for random_depth_sequence in random_depth_sequences ] return random_parens, random_depth_sequences
ব্যালেন্সড প্যারেন্স পান
random_seq_parens, depth_sequences = get_random_sequence_parens(100000, (2, 11)) is_valid_evals = [str(isValid(random_paren)) for random_paren in random_seq_parens] len_evals = [len(random_paren) for random_paren in random_seq_parens]
আসুন বাসা বাঁধার গভীরতার ফ্রিকোয়েন্সি দেখি
depth_freq = {} for seq in depth_sequences: for depth in seq: depth_freq.setdefault(depth, 0) depth_freq[depth] += 1 depth_freq # output -> {2: 39814, 1: 100088, 3: 20127, 4: 9908, 5: 4012}
depth_seq_hist = px.histogram(depth_sequences, title="Frequence of nesting depths in 'Random Nesting Depth Sequence' Output") depth_seq_hist.show()
এবং এখন, দৈর্ঘ্য ফ্রিকোয়েন্সি দেখতে.
paren_len_hist = px.histogram(len_evals, title="Frequency of string lengths") paren_len_hist.show()
মনে রাখবেন যে আমাদের ডেটা বিতরণের নিম্নলিখিত সম্ভাব্য বৈশিষ্ট্যগুলির মধ্যে একটি উত্তেজনা রয়েছে।
এর কারণ হল লো নেস্টিং-গভীর সাব-সিকোয়েন্সে একটি প্রদত্ত র্যান্ডম নেস্টিং সিকোয়েন্সে দেখানোর আরও সুযোগ থাকবে, যেমনটি উপরের প্লটে দেখানো হয়েছে।
সম্পূর্ণরূপে এলোমেলো অনুক্রমের এই স্বাভাবিক প্রবণতাকে মোকাবেলা করার জন্য, বন্ধনীর একটি প্রদত্ত সাবস্ট্রিং তৈরি করার সময়, আমরা গভীরতর নেস্ট মানগুলিকে আরও সম্ভাবনাময় করার জন্য তির্যক বন্টন থেকে নমুনা নিতে পারি।
প্রশিক্ষণে প্রথম পাসের পরে এটি পুনর্বিবেচনা করা হবে।
px.histogram(random_seq_parens, title="Frequency of balanced Parentheses").show()
আমাদের ডেটাসেটে শুধুমাত্র সুষম বন্ধনী থাকতে পারে না। তাই আমরা আমাদের ভারসাম্যপূর্ণ ডেটাসেট থেকে ভারসাম্যহীন স্ট্রিং বের করার জন্য একটি ডেটা জেনারেশন কৌশল তৈরি করতে পারি।
def _flip_idx(idx): return (idx + 1) % 2 assert _flip_idx(0) == 1 assert _flip_idx(1) == 0
def make_parens_unbalanced(paren: str) -> str: """Take balanced-parentheses and randomly mutate it till it's unbalanced. Both the number of mutations and indices are chosen at random. """ paren_idx_dict = {'(': 0, ')': 1} paren_list = list(paren) num_flipped_positions = randint(1, len(paren)) while isValid(''.join(paren_list)): flip_points = sample(range(len(paren)), num_flipped_positions) for flip_idx in flip_points: idx_char = paren_idx_dict[paren_list[flip_idx]] flipped_idx = _flip_idx(idx_char) paren_list[flip_idx] = PARENS[flipped_idx] return ''.join(paren_list) assert not isValid(make_parens_unbalanced('((()))'))
ভারসাম্যহীন প্যারেন্স ডেটাসেট পান
unbal_random_seq_parens = [make_parens_unbalanced(paren) for paren in random_seq_parens]
এখন আমাদের ডেটাসেট আছে, এর মজার জন্য, আমরা স্ক্র্যাচ থেকে আমাদের ট্রান্সফরমার আর্কিটেকচার লিখতে যাচ্ছি।
প্রথমে কিছু কনফিগার করুন
@dataclass class Config: context_len = 12 d_vocab: int = 5 d_out_vocab: int = 2 d_model: int = 56 d_head = 28 d_mlp = 56 * 4 causal_attention = False num_heads = 2 num_layers = 3 init_range: float = 1 PAD_TOKEN_IDX = 1
তারপর ইনপুট পার্স করার জন্য আমাদের টোকেনাইজার:
class Tokenizer: def __init__(self, vocab: str, context_width: Int, enforce_context: bool=False): self.START_TOKEN, START_TOKEN_IDX = "<start>", 0 self.PAD_TOKEN, PAD_TOKEN_IDX = "<pad>", 1 self.END_TOKEN, END_TOKEN_IDX = "<end>", 2 util_tokens_t_to_i = {self.START_TOKEN: START_TOKEN_IDX, self.PAD_TOKEN: PAD_TOKEN_IDX, self.END_TOKEN: END_TOKEN_IDX} util_tokens_i_to_t = {START_TOKEN_IDX: self.START_TOKEN, PAD_TOKEN_IDX: self.PAD_TOKEN, END_TOKEN_IDX: self.END_TOKEN} self.enforce_context = enforce_context self.context_width = context_width self.vocab = vocab self.t_to_i = {**util_tokens_t_to_i, **{token: token_id + 3 for token_id, token in enumerate(self.vocab)}} self.i_to_t = {**util_tokens_i_to_t, **{token_id + 3: token for token_id, token in enumerate(self.vocab)}} @staticmethod def pad_sequence(sequence: str, end_token: str, pad_token: str, max_length: Int, enforce_context: bool) -> List[str]: if not enforce_context: # Truncate if sequence length is greater sequence = sequence[:max_length] else: assert len(sequence) <= max_length, f"Sequence length is greater than the max allowed data length: {max_length}" return list(sequence) + [end_token] + [pad_token] * (max_length - len(sequence)) def tokenize(self, data: Union[str, List[str]]) -> Int[Tensor, "batch seq"]: if isinstance(data, str): data = [data] def _list_tokens_to_id(tokens: List[str]) -> List[Int]: return [self.t_to_i[token] for token in tokens] # to leave room for start and end tokens max_seq_len = self.context_width - 2 data_as_tokens = [ _list_tokens_to_id([ self.START_TOKEN, *self.pad_sequence(seq, self.END_TOKEN, self.PAD_TOKEN, max_seq_len, self.enforce_context), ]) for seq in data ] return t.tensor(data_as_tokens)
(আন)এম্বেডিং
class EmbedLayer(t.nn.Module): def __init__(self, cfg: Config): super().__init__() self.W_E = t.nn.Parameter(t.empty(cfg.d_vocab, cfg.d_model)) t.nn.init.normal_(self.W_E, mean=0.0, std=cfg.init_range) def forward(self, x: Int[Tensor, "batch seq"]) -> Int[Tensor, "batch seq d_model"]: return self.W_E[x] class UnEmbedLayer(t.nn.Module): def __init__(self, cfg: Config): super().__init__() self.W_U = t.nn.Parameter(t.empty(cfg.d_model, cfg.d_out_vocab)) t.nn.init.normal_(self.W_U, mean=0.0, std=cfg.init_range) def forward(self, x: Int[Tensor, "batch seq d_model"]) -> Int[Tensor, "batch seq d_out_vocab"]: return x @ self.W_U class PositionalEmbedding(t.nn.Module): def __init__(self, cfg: Config): super().__init__() denom = t.exp( t.arange(0, cfg.d_model, 2) * -(math.log(10000.0) / cfg.d_model) ) pos = t.arange(0, cfg.context_len).unsqueeze(1) param = pos * denom P_E = t.zeros(cfg.context_len, cfg.d_model) P_E[:, 0::2] = t.sin(param) P_E[:, 1::2] = t.cos(param) P_E = P_E.unsqueeze(0) self.register_buffer("P_E", P_E) def forward(self, x): _batch, seq_len, d_model = x.shape x = x + self.P_E[..., :seq_len, :d_model].requires_grad_(False) return x
সহজ স্তরের আদর্শ
class LayerNorm(t.nn.Module): def __init__(self, cfg): super().__init__() self.scale = t.nn.Parameter(t.ones(cfg.d_model)) self.bias = t.nn.Parameter(t.zeros(cfg.d_model)) def forward(self, x): mean = t.mean(x, dim=2, keepdim=True) var = t.var(x, dim=2, keepdim=True, unbiased=False) y = (x - mean) / (var + 0.00001).sqrt() return (y * self.scale) + self.bias
এবং অবশেষে মনোযোগ!
class AttentionLayer(t.nn.Module): def __init__(self, cfg): super().__init__() self.register_buffer("IGNORE", t.tensor(-1e5, dtype=t.float32)) self.cfg = cfg self.W_Q = t.nn.Parameter(t.empty(cfg.num_heads, cfg.d_model, cfg.d_head)) self.W_K = t.nn.Parameter(t.empty(cfg.num_heads, cfg.d_model, cfg.d_head)) self.W_V = t.nn.Parameter(t.empty(cfg.num_heads, cfg.d_model, cfg.d_head)) self.W_O = t.nn.Parameter(t.empty(cfg.num_heads, cfg.d_head, cfg.d_model)) self.b_Q = t.nn.Parameter(t.zeros(cfg.num_heads, cfg.d_head)) self.b_K = t.nn.Parameter(t.zeros(cfg.num_heads, cfg.d_head)) self.b_V = t.nn.Parameter(t.zeros(cfg.num_heads, cfg.d_head)) self.b_O = t.nn.Parameter(t.zeros(cfg.d_model)) t.nn.init.normal_(self.W_Q, mean=0.0, std=cfg.init_range) t.nn.init.normal_(self.W_K, mean=0.0, std=cfg.init_range) t.nn.init.normal_(self.W_V, mean=0.0, std=cfg.init_range) t.nn.init.normal_(self.W_O, mean=0.0, std=cfg.init_range) def forward(self, params): #TODO: revisit implementing pad_mask with hooks x, pad_mask = params Q = einops.einsum(x, self.W_Q, 'bs dm, h dm dh -> bsh dh') + self.b_Q K = einops.einsum(x, self.W_K, 'bs dm, h dm dh -> bsh dh') + self.b_K V = einops.einsum(x, self.W_V, 'bs dm, h dm dh -> bsh dh') + self.b_V attention_scores = einops.einsum(Q, K, 'b s_q h dh, b s_k h dh -> bh s_q s_k') scaled_attention_scores = attention_scores / (self.cfg.d_head ** 0.5) if self.cfg.causal_attention: scaled_attention_scores = self.apply_causal_mask(scaled_attention_scores) scaled_attention_scores = self.apply_padding_mask(scaled_attention_scores, pad_mask) attention_patterns = t.nn.Softmax(dim=-1)(scaled_attention_scores) post_attention_values = einops.einsum( attention_patterns, V, 'bh s_q s_k, b s_k h dh -> b s_q h dh' ) out = einops.einsum( post_attention_values, self.W_O, 'b s_q h dh, h dh dm -> b s_q dm' ) + self.b_O return out def apply_causal_mask(self, attention_scores): b, h, s_q, s_k = attention_scores.shape mask = t.tril(t.ones(s_q,s_k)).bool() return t.where(mask, attention_scores, self.IGNORE) def apply_padding_mask(self, attention_scores, pad_mask): return t.where(pad_mask, attention_scores, self.IGNORE)
MLP স্তরসমূহ
class LinearLayer(t.nn.Module): def __init__(self, in_dim, out_dim, include_bias=True): super().__init__() self.include_bias = include_bias self.W = t.nn.Parameter(t.empty(in_dim, out_dim)) t.nn.init.normal_(self.W, mean=0.0, std=cfg.init_range) self.b = None if include_bias: self.b = t.zeros(out_dim) def forward(self, x: Int[Tensor, "batch seq in_dim"]) -> Int[Tensor, "batch seq out_dim"]: out = x @ self.W if self.include_bias: out = out + self.b return out class MLP(t.nn.Module): def __init__(self, cfg): super().__init__() self.in_layer = LinearLayer(cfg.d_model, cfg.d_mlp) self.out_layer = LinearLayer(cfg.d_mlp, cfg.d_model) self.non_linearity = t.nn.ReLU() def forward(self, x): post_W_in = self.in_layer(x) post_non_lin = self.non_linearity(post_W_in) return self.out_layer(post_non_lin)
এটি একটি ট্রান্সফরমারে একসাথে করা
class TransformerBlock(t.nn.Module): def __init__(self, cfg): super().__init__() self.ln1 = LayerNorm(cfg) self.attention = AttentionLayer(cfg) self.ln2 = LayerNorm(cfg) self.mlp = MLP(cfg) def forward(self, params): x, pad_mask = params resid_mid = self.attention((self.ln1(x), pad_mask)) + x resid_post = self.mlp(self.ln2(resid_mid)) + resid_mid return resid_post, pad_mask
class Transformer(t.nn.Module): def __init__(self, cfg: Config): super().__init__() self.cfg = cfg self.embed = EmbedLayer(cfg) self.pos_embed = PositionalEmbedding(cfg) self.final_ln = LayerNorm(cfg) self.unembed = UnEmbedLayer(cfg) self.blocks = t.nn.Sequential(*([TransformerBlock(cfg)] * cfg.num_layers)) def forward(self, x): #TODO: revisit implementing pad_mask with hooks pad_mask = self.get_pad_mask(x) res_post_pos_embed = self.pos_embed(self.embed(x)) post_blocks, _ = self.blocks((res_post_pos_embed, pad_mask)) logits = self.unembed(self.final_ln(post_blocks)) return logits def get_pad_mask(self, x): batch, seq = x.shape return einops.repeat(x != self.cfg.PAD_TOKEN_IDX, 'batch seq -> batch 1 seq_q seq', seq_q=seq)
প্রশিক্ষণ উপযোগী
def cross_entropy_loss(output, targets): log_probs = output.log_softmax(dim=-1) predictions = log_probs[:, 0] batch, out_dim = predictions.shape true_output = predictions[range(batch), targets] return -true_output.sum() / batch def test(model, data, loss_func): inputs, targets = data with t.no_grad(): output = model(inputs) loss = loss_func(output, targets) return loss def train(model, data, optimizer, loss_func): inputs, targets = data optimizer.zero_grad() output = model(inputs) loss = loss_func(output, targets) loss.backward() optimizer.step() return loss
প্রশিক্ষণ কনফিগারেশন
cfg = Config() tokenizer = Tokenizer('()', 12, True) inputs = tokenizer.tokenize([*unbal_random_seq_parens, *random_seq_parens]) targets = t.tensor([*([0] * len(unbal_random_seq_parens)), *([1] * len(random_seq_parens))]) rand_indices = t.randperm(targets.shape[0]) rand_inputs = inputs[rand_indices, :] rand_targets = targets[rand_indices] model = Transformer(cfg) adamW = t.optim.AdamW(model.parameters(), lr=0.01)
প্রকৃত প্রশিক্ষণ
batch_size = 10000 train_size = int(0.7 * batch_size) epochs = 15 for epoch in range(epochs): for batch_id in range(0, rand_inputs.shape[0], batch_size): rand_inputs_batch, rand_targets_batch = rand_inputs[batch_id : batch_id + batch_size], rand_targets[batch_id : batch_id + batch_size] train_input, train_target = rand_inputs_batch[:train_size, :], rand_targets_batch[:train_size] test_input, test_target = rand_inputs_batch[train_size:, :], rand_targets_batch[train_size:] train(model, (train_input, train_target), adamW, cross_entropy_loss) test_loss = test(model, (test_input, test_target), cross_entropy_loss) print(f'Loss: {test_loss} on epoch: {epoch}/{epochs}')
পার্ট 3-এ, আমরা এই প্রশিক্ষিত নেটওয়ার্কের অভ্যন্তরীণ বিষয়গুলি তদন্ত করব৷ আমরা মনোযোগের ধরণগুলি দেখে এবং নেটওয়ার্ক কীভাবে এই কাজটি সমাধান করেছে তা বোঝার জন্য একটি যান্ত্রিক মডেল তৈরি করতে অ্যাক্টিভেশন প্যাচিংয়ের মতো মেকানিস্টিক ব্যাখ্যাযোগ্যতার কিছু ডায়গনিস্টিক সরঞ্জাম প্রয়োগ করে এটি করব।
এই পর্যন্ত পড়ার জন্য ধন্যবাদ এবং শীঘ্রই আপনাকে পার্ট 3-এ ধরা পড়বে!