words = open('names.txt', 'r').read().splitlines()
import torchN = torch.zeros((27, 27), dtype = torch.int32)chars = sorted(list(set(''.join(words))))stoi = {s:i+1 for i,s in enumerate(chars)}stoi['.'] = 0itos = {i:s for s,i in stoi.items()}
P = N.float()P /= P.sum(1, keepdim=True)
#Creating the training set of bigrams (x,y)xs, ys = [], []for word in words[:1]: chs = ['.'] + list(word) + ['.'] for ch1, ch2 in zip(chs, chs[1:]): ix1 = stoi[ch1] ix2 = stoi[ch2] print(ch1, ch2) xs.append(ix1) ys.append(ix2)xs = torch.tensor(xs)ys = torch.tensor(ys)
Output
. ee mm mm aa .
#Feeding these examples into a neural networkimport torch.nn.functional as F
#<=========OPTIMIZATION============>
xs
Output
tensor([ 0, 5, 13, 13, 1])
ys
Output
tensor([ 5, 13, 13, 1, 0])
# randomly initialize 27 neurons' weights. each neuron receives 27 inputsg = torch.Generator().manual_seed(2147483647)W = torch.randn((27, 27), generator=g, requires_grad=True) #Adding the third parameter here for the Backward pass (as remember in micrograd we had done the same thing)
#FORWARD PASSxenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encodinglogits = xenc @ W # predict log-countscounts = logits.exp() # counts, equivalent to Nprobs = counts / counts.sum(1, keepdims=True) # probabilities for next characterloss = -probs[torch.arange(5), ys].log().mean() #torch.arange(5) is basically 0 to 5(4) position, ys is from that tuple list | We calculate the probability values of that | Then we take their log values | Then we take their mean | Finally take the negative value (since NLL)
loss #This will be similar to the one we also calculated in the SUMMARY part of B-Main
Output
tensor(3.7693)
#BACKWARD PASSW.grad = None #the gradient is first set to zeroloss.backward()
W.grad.shape
Output
torch.Size([27, 27])
W.grad
#UPDATEW.data += -0.1 * W.grad
#JUST PUTTING THEM TOGETHER TO PERFORM GRADIENT DESCENT
#ONLY RUN THIS THE FIRST TIME# randomly initialize 27 neurons' weights. each neuron receives 27 inputsg = torch.Generator().manual_seed(2147483647)W = torch.randn((27, 27), generator=g, requires_grad=True) #Adding the third parameter here for the Backward pass (as remember in micrograd we had done the same thing)
#FORWARD PASSxenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encodinglogits = xenc @ W # predict log-countscounts = logits.exp() # counts, equivalent to Nprobs = counts / counts.sum(1, keepdims=True) # probabilities for next characterloss = -probs[torch.arange(5), ys].log().mean() #torch.arange(5) is basically 0 to 5(4) position, ys is from that tuple list | We calculate the probability values of that | Then we take their log values | Then we take their mean | Finally take the negative value (since NLL)
print(loss.item()) #CHECKING THE LOSS VALUE
Output
3.6891887187957764
#BACKWARD PASSW.grad = None #the gradient is first set to zeroloss.backward()
#UPDATEW.data += -0.1 * W.grad
Yay, that worked. Noice
PUTTING THEM ALL TOGETHER
# create the datasetxs, ys = [], []for w in words: chs = ['.'] + list(w) + ['.'] for ch1, ch2 in zip(chs, chs[1:]): ix1 = stoi[ch1] ix2 = stoi[ch2] xs.append(ix1) ys.append(ix2)xs = torch.tensor(xs)ys = torch.tensor(ys)num = xs.nelement()print('number of examples: ', num)# initialize the 'network'g = torch.Generator().manual_seed(2147483647)W = torch.randn((27, 27), generator=g, requires_grad=True)
Output
number of examples: 228146
# gradient descentfor k in range(20): # forward pass xenc = F.one_hot(xs, num_classes=27).float() # input to the network: one-hot encoding logits = xenc @ W # predict log-counts counts = logits.exp() # counts, equivalent to N probs = counts / counts.sum(1, keepdims=True) # probabilities for next character loss = -probs[torch.arange(num), ys].log().mean() + 0.01*(W**2).mean() print(loss.item()) # backward pass W.grad = None # set to zero the gradient loss.backward() # update W.data += -50 * W.grad
SO WE ALMOST ACHIEVED A VERY LOW LOSS VALUE. SIMILAR TO THE LOSS VALUE WE CALCULATED IN A-MAIN, WHEN WE TYPED OUR OWN NAME AND SAW HOW IT PERFORMS
Finally drumrolls, we are going to see how sampling from this model produces the outputs (Spoiler alert: it will be the same as how we made the model manually, coz... it is the same model just that we made it using Neural nets)
# finally, sample from the 'neural net' modelg = torch.Generator().manual_seed(2147483647)for i in range(5): out = [] ix = 0 while True: # ---------- # BEFORE: #p = P[ix] # ---------- # NOW: xenc = F.one_hot(torch.tensor([ix]), num_classes=27).float() logits = xenc @ W # predict log-counts counts = logits.exp() # counts, equivalent to N p = counts / counts.sum(1, keepdims=True) # probabilities for next character # ---------- ix = torch.multinomial(p, num_samples=1, replacement=True, generator=g).item() out.append(itos[ix]) if ix == 0: break print(''.join(out))