I implemented some techniques used in a research paper for n-gram language modeling. It is a simple MLP-based trigram language model that takes three characters’ tokens as input, passes them into an embedding layer, then a hidden linear layer, and finally predicts the next character’s token.
Here are the hyperparameters I used:
n = 3 #trigram model emb_sz = 2 #latent factor n_hidden = 100 vocab_sz = len(chars) # it's 27 Here is the architecture:
Source: https://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf
C = torch.randn(vocab_sz, emb_sz) w1 = torch.randn(n*emb_sz, n_hidden) b1 = torch.randn(n_hidden) w2 = torch.randn(n_hidden, vocab_sz) b2 = torch.randn(vocab_sz) # Setting up gradient requirements parameters = [C, w1, b1, w2, b2] for p in parameters: p.requires_grad = True # --------------------------------------------------- # training epoch = 200_000 bs = 32 # batch sz for i in range(epoch): # mini batch idx = torch.randint(0, xtrain.shape[0], size=(bs,)) xbs, ybs = xtrain[idx], ytrain[idx] # batch # forward pass emb = C[xbs] hpreact = emb.view(emb.shape[0], -1) @ w1 + b1 h = torch.tanh(hpreact) logits = h @ w2 + b2 loss = F.cross_entropy(logits, ybs) perplexity = torch.exp(loss) # backward pass for p in parameters: p.grad = None loss.backward() # update lr = 0.01 if i < 100_000 else 0.001 for p in parameters: p.data -= p.grad.data * lr # track lossi.append(loss.log10().item()) if i%10000 == 0: print(f"epoch: {i:7d}/{epoch:7d} | loss: {loss.item():.4f} | perplexity: {perplexity.item():.4f}") epoch: 0/ 200 | loss: 9.9113 | perplexity: 20157.0859 epoch: 10/ 200 | loss: 4.8961 | perplexity: 133.7642 epoch: 20/ 200 | loss: 3.5018 | perplexity: 33.1759 epoch: 30/ 200 | loss: 3.0638 | perplexity: 21.4093 epoch: 40/ 200 | loss: 2.8681 | perplexity: 17.6032 epoch: 50/ 200 | loss: 2.8150 | perplexity: 16.6932 Firstly, I want to explain why we need to initialize the weights of our model with good values. As observed, the initial loss is very high, and then subsequent losses are very low. The weights we initialized are significantly incorrect, as some are very confidently wrong while others can be overly confident. This necessitates spending time and computational resources to correct them before starting unbiased learning. Thus, improving parameter initialization can help us skip this unnecessary step, which otherwise wastes energy and computation.
...