Building Makemore MLP Exercise

Imports

from tqdm import tqdm
import numpy
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plot
import random
import math
g = torch.Generator().manual_seed(42)

Setup

words = open('../data/names.txt', 'r').read().splitlines()
words[:8]
['emma', 'olivia', 'ava', 'isabella', 'sophia', 'charlotte', 'mia', 'amelia']
len(words)
32033
def generate_training_set(words, block_size, print_disabled=False):
    
    chars = sorted(list(set(''.join(words))))
    stoi = {s: i+1 for i, s in enumerate(chars)}
    stoi['.'] = 0
    itos = {i:s for s, i in stoi.items()}
    
    X, Y = [], []
    
    for w in words:
        if print_disabled: print(w)
        
        context = [0] * block_size
        for ch in w + '.':
            ix = stoi[ch]
            X.append(context)
            Y.append(ix)
            if print_disabled: print(''.join(itos[i] for i in context), '--->', itos[ix])
            context = context[1:] + [ix] # crop and append
            
    X = torch.tensor(X)
    Y = torch.tensor(Y)
    return X, Y
X, Y = generate_training_set(words, 3)
X.shape, Y.shape
(torch.Size([228146, 3]), torch.Size([228146]))
def generate_train_valid_test_split(words, block_size=3):
    random.seed(42)
    random.shuffle(words)
    n1 = int(0.8*len(words))
    n2 = int(0.9*len(words))

    Xtr, Ytr = generate_training_set(words[:n1], block_size)
    Xdev, Ydev = generate_training_set(words[n1:n2], block_size)
    Xte, Yte = generate_training_set(words[n2:], block_size)
    
    return Xtr, Ytr, Xdev, Ydev, Xte, Yte
Xtr, Ytr, Xdev, Ydev, Xte, Yte = generate_train_valid_test_split(words, block_size=3)
Xtr.shape, Ytr.shape
(torch.Size([182625, 3]), torch.Size([182625]))
Xdev.shape, Ydev.shape
(torch.Size([22655, 3]), torch.Size([22655]))
Xte.shape, Yte.shape
(torch.Size([22866, 3]), torch.Size([22866]))

E01

Tune the hyperparameters of the training to beat the validation loss of 2.2

  • no of neurons in the hidden layer

  • embedding size

  • no of characters

  • epochs

  • learning rate; change/decay it over the epochs

  • batch size

def evaluate_loss(parameters, X, Y, block_size=3, embedding_size=10):
    C, W1, b1, W2, b2 = parameters
    emb = C[X]
    h = torch.tanh(emb.view(-1, block_size * embedding_size) @ W1 + b1)
    logits = h @ W2 + b2
    loss = F.cross_entropy(logits, Y)
    return loss
def _regularization_loss(parameters, lambdas):
    C = parameters[0]
    W1 = parameters[1]
    W2 = parameters[3]
    
    return lambdas[0]*(C**2).mean() + lambdas[1]*(W1**2).mean() + lambdas[2]*(W2**2).mean()
def train(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.randn((27, embedding_size), generator=g)
        W1 = torch.randn((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.randn(hidden_neuron, generator=g)
        W2 = torch.randn((hidden_neuron, 27), generator=g)
        b2 = torch.randn(27, generator=g)
        parameters = [C, W1, b1, W2, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()

1st try

parameters, loss = train(Xtr, Ytr, 100_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                    | 4/100000 [00:00<1:36:08, 17.34it/s] 10%|████████████████████▊                                                                                                                                                                                           | 10003/100000 [08:59<1:19:38, 18.83it/s] 20%|█████████████████████████████████████████▌                                                                                                                                                                      | 20003/100000 [17:47<1:10:41, 18.86it/s] 30%|██████████████████████████████████████████████████████████████▍                                                                                                                                                 | 30004/100000 [26:35<1:02:27, 18.68it/s] 40%|████████████████████████████████████████████████████████████████████████████████████                                                                                                                              | 40005/100000 [51:47<49:39, 20.13it/s] 50%|█████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                                         | 50005/100000 [59:59<40:59, 20.32it/s] 60%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▊                                                                                   | 60005/100000 [1:08:05<32:22, 20.58it/s] 70%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▌                                                              | 70003/100000 [1:16:09<24:24, 20.49it/s] 80%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍                                         | 80005/100000 [1:24:15<16:17, 20.45it/s] 90%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏                    | 90003/100000 [1:32:20<08:05, 20.60it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100000/100000 [1:40:25<00:00, 16.60it/s]
0 17.771562576293945
10000 2.3100812435150146
20000 2.236790418624878
30000 2.1661746501922607
40000 2.145174980163574
50000 2.1430141925811768
60000 2.1360814571380615
70000 2.1251132488250732
80000 2.1180062294006348
90000 2.1188645362854004
loss, evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
(2.101083755493164, tensor(2.1680, grad_fn=<NllLossBackward0>))

2nd try

parameters, loss = train(Xtr, Ytr, 300_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.01, parameters=parameters, enable_print=False)
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 300000/300000 [4:07:07<00:00, 20.23it/s]
loss, evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
(2.1126763820648193, tensor(2.1603, grad_fn=<NllLossBackward0>))

3rd try

parameters, loss = train(Xtr, Ytr, 10_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=1, parameters=parameters, enable_print=True, print_at_every_nth_epoch=1000)
  0%|                                                                                                                                                                                                                       | 4/10000 [00:00<08:24, 19.80it/s] 10%|█████████████████████▎                                                                                                                                                                                              | 1003/10000 [00:49<07:26, 20.17it/s] 20%|██████████████████████████████████████████▍                                                                                                                                                                         | 2003/10000 [01:39<06:38, 20.07it/s] 30%|███████████████████████████████████████████████████████████████▋                                                                                                                                                    | 3005/10000 [02:28<05:46, 20.21it/s] 40%|████████████████████████████████████████████████████████████████████████████████████▉                                                                                                                               | 4004/10000 [03:18<04:57, 20.19it/s] 50%|██████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                                          | 5003/10000 [04:07<04:09, 19.99it/s] 60%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▎                                                                                    | 6003/10000 [04:56<03:16, 20.39it/s] 70%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍                                                               | 7004/10000 [05:46<02:29, 20.07it/s] 80%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋                                          | 8003/10000 [06:35<01:37, 20.42it/s] 90%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉                     | 9004/10000 [07:24<00:48, 20.40it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 10000/10000 [08:13<00:00, 20.24it/s]
0 2.1231608390808105
1000 2.1130003929138184
2000 2.1557743549346924
3000 2.136502265930176
4000 2.142028331756592
5000 2.1329710483551025
6000 2.1422650814056396
7000 2.148254632949829
8000 2.13120698928833
9000 2.1335060596466064
loss, evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
(2.190765142440796, tensor(2.1794, grad_fn=<NllLossBackward0>))

4th try

parameters, loss = train(Xtr, Ytr, 10_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, parameters=parameters, enable_print=True, print_at_every_nth_epoch=1000)
  0%|                                                                                                                                                                                                                       | 5/10000 [00:00<08:24, 19.82it/s] 10%|█████████████████████▎                                                                                                                                                                                              | 1005/10000 [00:49<07:20, 20.43it/s] 20%|██████████████████████████████████████████▌                                                                                                                                                                         | 2005/10000 [01:38<06:34, 20.29it/s] 30%|███████████████████████████████████████████████████████████████▋                                                                                                                                                    | 3002/10000 [02:27<05:44, 20.32it/s] 40%|████████████████████████████████████████████████████████████████████████████████████▉                                                                                                                               | 4004/10000 [03:17<04:58, 20.06it/s] 50%|██████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                                          | 5003/10000 [04:07<04:03, 20.56it/s] 60%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▎                                                                                    | 6003/10000 [04:56<03:15, 20.48it/s] 70%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍                                                               | 7003/10000 [05:45<02:27, 20.34it/s] 80%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋                                          | 8003/10000 [06:35<01:39, 20.12it/s] 90%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉                     | 9004/10000 [07:24<00:47, 21.10it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 10000/10000 [08:13<00:00, 20.25it/s]
0 2.141832113265991
1000 2.091341495513916
2000 2.089855909347534
3000 2.079847574234009
4000 2.081550121307373
5000 2.096187114715576
6000 2.0649683475494385
7000 2.0917818546295166
8000 2.0842249393463135
9000 2.0907206535339355
loss, evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
(2.090895414352417, tensor(2.1472, grad_fn=<NllLossBackward0>))

5th try

parameters, loss = train(Xtr, Ytr, 100_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.01, parameters=parameters, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                    | 5/100000 [00:00<1:23:39, 19.92it/s] 10%|████████████████████▊                                                                                                                                                                                           | 10004/100000 [08:12<1:14:28, 20.14it/s] 20%|█████████████████████████████████████████▌                                                                                                                                                                      | 20005/100000 [16:24<1:05:10, 20.45it/s] 30%|███████████████████████████████████████████████████████████████                                                                                                                                                   | 30005/100000 [24:34<57:04, 20.44it/s] 40%|████████████████████████████████████████████████████████████████████████████████████                                                                                                                              | 40002/100000 [32:45<49:26, 20.22it/s] 50%|█████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                                         | 50005/100000 [40:56<41:04, 20.28it/s] 60%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                    | 60004/100000 [49:07<32:55, 20.25it/s] 70%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████                                                               | 70005/100000 [57:18<24:55, 20.05it/s] 80%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍                                         | 80004/100000 [1:05:29<16:18, 20.43it/s] 90%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏                    | 90003/100000 [1:13:40<08:24, 19.82it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100000/100000 [1:21:51<00:00, 20.36it/s]
0 2.0824689865112305
10000 2.0864546298980713
20000 2.0779430866241455
30000 2.0869970321655273
40000 2.0827417373657227
50000 2.1026248931884766
60000 2.0927939414978027
70000 2.0810811519622803
80000 2.095008611679077
90000 2.0829107761383057
loss, evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
(2.0964395999908447, tensor(2.1466, grad_fn=<NllLossBackward0>))

Test Loss

loss, evaluate_loss(parameters, Xte, Yte, block_size=3, embedding_size=50)
(2.0964395999908447, tensor(2.1446, grad_fn=<NllLossBackward0>))

E02

  • Weight Initialization
  1. What is the loss you’d get if the predicted probabilities at initialization were perfectly uniform? What loss do we achieve?

  2. Can you tune the initialization to get a starting loss that is much more similar to (1)?

Answer to (1)

If the predicted probabilities were uniform then the probabilities would have been 1/27 of each character prediction

And we would have take the log of the probability which would have been

torch.tensor(1/27).log()
tensor(-3.2958)

to the get the loss it would have been

- torch.tensor(1/27).log()
tensor(3.2958)

No we sum up the losses and divide by the count, (n * (3.2958))/n which is equal to 3.2958

Lets see the initial loss when we train the model with current initialization

parameters, loss = train(Xtr, Ytr, 10, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=1)
 40%|███████████████████████████████████████████████████████████████████████████████████████▏                                                                                                                                  | 4/10 [00:00<00:00, 19.51it/s] 90%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏                     | 9/10 [00:00<00:00, 19.94it/s]100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 10/10 [00:00<00:00, 19.88it/s]
0 18.27653694152832
1 17.431493759155273
2 16.35456085205078
3 16.05698585510254
4 15.747321128845215
5 15.394339561462402
6 15.205368995666504
7 14.835010528564453
8 14.528204917907715
9 14.28638744354248

The initial loss is 18.98 which is high comparative to 3.2958

Lets see the probabilities of the output

parameters, loss = train(Xtr, Ytr, 1, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=1)
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 19.37it/s]
0 18.204519271850586
def compute_probs(parameters, X, block_size=3, embedding_size=50):
    C, W1, b1, W2, b2 = parameters
    emb = C[X]
    h = torch.tanh(emb.view(-1, block_size * embedding_size) @ W1 + b1)
    logits = h @ W2 + b2
    return F.softmax(logits, dim=1)
compute_probs(parameters, Xtr)
tensor([[2.9970e-06, 2.3740e-08, 2.1316e-10,  ..., 1.2648e-13, 8.5370e-04,
         8.7376e-08],
        [1.7422e-05, 1.1364e-09, 1.3196e-09,  ..., 3.6301e-13, 3.8613e-06,
         2.4013e-07],
        [5.8833e-05, 5.7244e-06, 1.0801e-02,  ..., 9.2642e-07, 2.9683e-06,
         2.4511e-06],
        ...,
        [5.7658e-11, 1.4429e-09, 9.7899e-11,  ..., 1.0416e-11, 8.2188e-09,
         2.7279e-10],
        [7.0990e-01, 8.7623e-12, 1.6534e-07,  ..., 3.1374e-09, 3.6852e-06,
         1.1986e-04],
        [9.9999e-01, 1.0279e-07, 6.2436e-11,  ..., 1.4053e-10, 6.7408e-14,
         1.2024e-09]], grad_fn=<SoftmaxBackward0>)

Lets view a single row of probabilities

compute_probs(parameters, Xtr)[0]
tensor([2.9970e-06, 2.3740e-08, 2.1316e-10, 1.9171e-08, 3.7981e-04, 2.2313e-02,
        1.3911e-17, 1.0186e-09, 9.7561e-10, 5.6293e-12, 8.8295e-09, 3.4877e-09,
        1.2439e-08, 7.9825e-14, 7.3846e-04, 1.0648e-11, 5.4885e-08, 3.0407e-13,
        2.0024e-02, 9.5325e-01, 1.7357e-03, 2.2441e-08, 6.8103e-04, 2.4685e-05,
        1.2648e-13, 8.5370e-04, 8.7376e-08], grad_fn=<SelectBackward0>)

to get a uniform probability, I think we need to have all logits as equal so that we can get probability of each as 1/27

Try 1

lets try uniform wieght initialization

def train_v2(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.rand((27, embedding_size), generator=g)
        W1 = torch.rand((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.rand(hidden_neuron, generator=g)
        W2 = torch.rand((hidden_neuron, 27), generator=g)  
        b2 = torch.rand(27, generator=g)
        parameters = [C, W1, b1, W2, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()
parameters, loss = train_v2(Xtr, Ytr, 1, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=1)
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 20.50it/s]
0 7.901321887969971

With uniform weight initialization the intial loss (6.422) obtained is less than of normal weight initialization (17.7)

Try 2

Lets initialize the last layers of weights and biases as zero.

def train_v3(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.rand((27, embedding_size), generator=g)
        W1 = torch.rand((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.rand(hidden_neuron)
        W2 = torch.zeros((hidden_neuron, 27))
        b2 = torch.zeros(27)
        parameters = [C, W1, b1, W2, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()
parameters, loss = train_v3(Xtr, Ytr, 1, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=1)
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 21.42it/s]
0 3.295837163925171

The initial loss is now 3.2958 (which we wanted).

Lets see how well it trains now

parameters, loss = train_v3(Xtr, Ytr, 30_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                               | 0/30000 [00:00<?, ?it/s] 33%|██████████████████████████████████████████████████████████████████████▎                                                                                                                                            | 10005/30000 [07:59<15:49, 21.07it/s] 67%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋                                                                      | 20004/30000 [15:58<08:02, 20.72it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30000/30000 [23:57<00:00, 20.86it/s]
0 3.295837163925171
10000 2.8236281871795654
20000 2.8253743648529053
loss
2.8211419582366943

Try 3

As we can see the losses are not decreasing faster, lets not initialize weight to zero but close to zero and see …

def train_v4(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.rand((27, embedding_size), generator=g)
        W1 = torch.rand((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.rand(hidden_neuron)
        W2 = torch.rand((hidden_neuron, 27)) * 0.01 # close to zero
        b2 = torch.zeros(27)
        parameters = [C, W1, b1, W2, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()
parameters, loss = train_v4(Xtr, Ytr, 30_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                       | 3/30000 [00:00<23:51, 20.95it/s] 33%|██████████████████████████████████████████████████████████████████████▎                                                                                                                                            | 10005/30000 [08:00<15:48, 21.08it/s] 67%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋                                                                      | 20003/30000 [16:00<08:02, 20.73it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30000/30000 [24:00<00:00, 20.82it/s]
0 3.29825496673584
10000 2.8132688999176025
20000 2.826235294342041

Try 4

Lets not try to uniformly initiate all the weights but only the last layers and the rest we can keep as normal initialized

def train_v5(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.randn((27, embedding_size), generator=g)
        W1 = torch.randn((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.randn(hidden_neuron)
        W2 = torch.rand((hidden_neuron, 27)) * 0.01 # close to zero
        b2 = torch.zeros(27)
        parameters = [C, W1, b1, W2, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()
parameters, loss = train_v5(Xtr, Ytr, 30_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                       | 3/30000 [00:00<24:39, 20.28it/s] 33%|██████████████████████████████████████████████████████████████████████▎                                                                                                                                            | 10003/30000 [08:13<16:37, 20.04it/s] 67%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋                                                                      | 20005/30000 [16:28<08:11, 20.33it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 30000/30000 [24:42<00:00, 20.24it/s]
0 3.2991583347320557
10000 2.175701379776001
20000 2.1791296005249023

The losses are reducing now. Lets train for 100_000 and check

parameters, loss = train_v5(Xtr, Ytr, 200_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                    | 4/200000 [00:00<3:01:12, 18.39it/s]  5%|██████████▍                                                                                                                                                                                                     | 10004/200000 [08:33<2:43:37, 19.35it/s] 10%|████████████████████▊                                                                                                                                                                                           | 20002/200000 [16:56<2:28:24, 20.21it/s] 15%|███████████████████████████████▏                                                                                                                                                                                | 30004/200000 [25:11<2:19:51, 20.26it/s] 20%|█████████████████████████████████████████▌                                                                                                                                                                      | 40005/200000 [33:26<2:12:10, 20.17it/s] 25%|████████████████████████████████████████████████████                                                                                                                                                            | 50003/200000 [41:41<2:04:02, 20.15it/s] 30%|██████████████████████████████████████████████████████████████▍                                                                                                                                                 | 60003/200000 [49:56<1:55:31, 20.20it/s] 35%|████████████████████████████████████████████████████████████████████████▊                                                                                                                                       | 70003/200000 [58:23<1:49:37, 19.76it/s] 40%|██████████████████████████████████████████████████████████████████████████████████▍                                                                                                                           | 80004/200000 [1:06:54<1:40:25, 19.92it/s] 45%|████████████████████████████████████████████████████████████████████████████████████████████▋                                                                                                                 | 90003/200000 [1:15:17<1:30:28, 20.26it/s] 50%|██████████████████████████████████████████████████████████████████████████████████████████████████████▌                                                                                                      | 100005/200000 [1:23:47<1:22:01, 20.32it/s] 55%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████▊                                                                                            | 110003/200000 [1:32:17<1:16:43, 19.55it/s] 60%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                  | 120004/200000 [1:40:49<1:06:07, 20.16it/s] 65%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▎                                                                       | 130003/200000 [1:49:13<1:00:42, 19.22it/s] 70%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉                                                              | 140004/200000 [1:57:51<49:56, 20.03it/s] 75%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▎                                                   | 150004/200000 [2:06:14<40:59, 20.32it/s] 80%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▌                                         | 160004/200000 [2:14:38<32:47, 20.33it/s] 85%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉                               | 170003/200000 [2:22:59<25:09, 19.87it/s] 90%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▎                    | 180003/200000 [2:31:25<16:36, 20.06it/s] 95%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▋          | 190005/200000 [2:39:51<08:15, 20.16it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 200000/200000 [2:48:07<00:00, 19.83it/s]
0 3.292088270187378
10000 2.184924364089966
20000 2.168978452682495
30000 2.129955768585205
40000 2.1225433349609375
50000 2.1296749114990234
60000 2.1202642917633057
70000 2.131760358810425
80000 2.1080808639526367
90000 2.1024396419525146
100000 2.0863888263702393
110000 2.0778346061706543
120000 2.084108591079712
130000 2.085371255874634
140000 2.084995985031128
150000 2.0778989791870117
160000 2.0879881381988525
170000 2.0799689292907715
180000 2.0731143951416016
190000 2.0831706523895264
loss
2.0791072845458984

The losses are getting reduced faster!

evaluate_loss(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
tensor(2.1343, grad_fn=<NllLossBackward0>)

E03

Read the Bengio et al 2003 paper, implement and try any idea from the paper. Did it work?

In the paper there is a mention of direct connection from the word features to output.

Lets implement the direct connection from embedding to output and see the results

Direct connection from embedding to output

C = torch.randn((27, 50), generator=g)
C[X].shape; C[X].view(-1, 150).shape
torch.Size([228146, 150])
def evaluate_loss_dir_conn(parameters, X, Y, block_size=3, embedding_size=10):
    C, W1, b1, W2, W3, b2 = parameters
    emb = C[X]
    h = torch.tanh(emb.view(-1, block_size * embedding_size) @ W1 + b1)
    logits = h @ W2 + b2 + C[X].view(-1, block_size * embedding_size) @ W3
    loss = F.cross_entropy(logits, Y)
    return loss
def train_dir_conn(X, 
          Y, 
          epochs, 
          block_size=3, 
          embedding_size=10, 
          hidden_neuron=300, 
          bs=32, 
          lr=0.1, 
          parameters=[], 
          lambdas = [0, 0, 0],
          enable_print=True,
          print_at_every_nth_epoch=10000
         ):
    
    if not parameters:
        C = torch.randn((27, embedding_size), generator=g)
        W1 = torch.randn((block_size * embedding_size, hidden_neuron), generator=g)
        b1 = torch.randn(hidden_neuron)
        W2 = torch.rand((hidden_neuron, 27)) * 0.01 # close to zero
        W3 = torch.rand((block_size * embedding_size, 27)) * 0.01 # close to zero
        b2 = torch.zeros(27)
        parameters = [C, W1, b1, W2, W3, b2]

    
    for p in parameters: p.requires_grad = True 
        
    for epoch in tqdm(range(epochs)):
            
        ix = torch.randint(0, X.shape[0], (bs, ))

        loss = evaluate_loss_dir_conn(parameters, X[ix], Y[ix], block_size, embedding_size)
        regularization_loss = _regularization_loss(parameters, lambdas)
        loss += regularization_loss

        for p in parameters:
            p.grad= None
        loss.backward()


        for p in parameters:
            p.data += - lr * p.grad

        if enable_print and epoch % print_at_every_nth_epoch == 0: print(epoch, loss.item())
    
    return parameters, loss.item()
parameters, loss = train_dir_conn(Xtr, Ytr, 100_000, block_size=3, embedding_size=50, hidden_neuron=100, bs=16384, lr=0.1, enable_print=True, print_at_every_nth_epoch=10_000)
  0%|                                                                                                                                                                                                                    | 2/100000 [00:00<2:09:58, 12.82it/s] 10%|████████████████████▊                                                                                                                                                                                           | 10002/100000 [11:49<1:43:19, 14.52it/s] 20%|█████████████████████████████████████████▌                                                                                                                                                                      | 20002/100000 [23:20<1:35:02, 14.03it/s] 30%|██████████████████████████████████████████████████████████████▍                                                                                                                                                 | 30002/100000 [35:10<1:21:53, 14.24it/s] 40%|███████████████████████████████████████████████████████████████████████████████████▏                                                                                                                            | 40002/100000 [46:47<1:09:35, 14.37it/s] 50%|█████████████████████████████████████████████████████████████████████████████████████████████████████████                                                                                                         | 50002/100000 [58:30<57:41, 14.44it/s] 60%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▊                                                                                   | 60002/100000 [1:10:02<46:07, 14.45it/s] 70%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▌                                                              | 70002/100000 [1:21:52<35:18, 14.16it/s] 80%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▍                                         | 80002/100000 [1:33:29<22:48, 14.61it/s] 90%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▏                    | 90002/100000 [1:44:59<11:26, 14.57it/s]100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 100000/100000 [1:56:30<00:00, 14.31it/s]
0 3.29349684715271
10000 2.151575803756714
20000 2.122009515762329
30000 2.1049506664276123
40000 2.107222318649292
50000 2.098936080932617
60000 2.0728397369384766
70000 2.1058623790740967
80000 2.0761640071868896
90000 2.0695760250091553
loss
2.0880658626556396
evaluate_loss_dir_conn(parameters, Xdev, Ydev, block_size=3, embedding_size=50)
tensor(2.1274, grad_fn=<NllLossBackward0>)
evaluate_loss_dir_conn(parameters, Xte, Yte, block_size=3, embedding_size=50)
tensor(2.1239, grad_fn=<NllLossBackward0>)

The loss decreased by lot with this direct connection and the above method of weight initialization