Language Models#

Overview#

In this project, a language model is a deep learning model which can predict next possible token conditioned on given tokens. Each language model has a loss function which can be optimized by the language model training script lmp.script.train_model. The optimization goal of a language model is to have low perplexity, which serve as an indication of performing well on next token prediction. A language model is paired with one and only one tokenizer. A language model always predict tokens contained in the paired tokenizer’s vocabulary. When construct a language model, one must first construct a tokenizer and then pass that tokenizer to model constructor.

See also

lmp.tknzr

All available tokenizers.

lmp.script.eval_dset_ppl

Dataset perplexity evaluation script.

lmp.script.eval_txt_ppl

Text perplexity evaluation script.

lmp.script.gen_txt

Continual text generation script.

lmp.script.train_model

Language model training script.

Import language model module#

All language model classes are collectively gathered under the module lmp.model. One can import language model module as usual Python module:

import lmp.model

Create language model instances#

After importing lmp.model, one can create language model instance through the class attributes of lmp.model. For example, one can create Elman-Net language model ElmanNet as follow:

import lmp.model
import lmp.tknzr

# Create tokenizer instance.
tokenizer = lmp.tknzr.CharTknzr()

# Create language model instance.
model = lmp.model.ElmanNet(tknzr=tokenizer)

Each language model is an instance of torch.nn.Module. Each language model is paired with one and only one tokenizer. In the example above we see that an Elman-Net language model can be paired with a character tokenizer.

Initialize language model parameters#

Pytorch provides built-in utilities to initialize model parameters. All initialization utilities are collectively gathered under the module torch.nn.init.

import lmp.model
import lmp.tknzr
import torch

# Create language model instance.
model = lmp.model.ElmanNet(tknzr=lmp.tknzr.CharTknzr())

# Initialize model parameters.
torch.nn.init.zeros_(model.fc_e2h.bias)

If you cannot decide how to initialize a language model, we have provided an utility params_init for each language model to help you initialize model parameters.

import lmp.model
import lmp.tknzr

# Create language model instance.
model = lmp.model.ElmanNet(tknzr=lmp.tknzr.CharTknzr())

# Initialize model parameters.
model.params_init()

Calculate prediction loss#

One can calculate mini-batch loss of a language model using cal_loss function. For example,

import lmp.model
import lmp.tknzr
import torch

# Create tokenizer instance.
tokenizer = lmp.tknzr.CharTknzr()

# Build tokenizer vocabulary.
batch_txt = ['hello world', 'how are you']
tokenizer.build_vocab(batch_txt=batch_txt)

# Encode mini-batch.
batch_tkids = []
for txt in batch_txt:
  tkids = tokenizer.enc(txt=txt)
  tkids = tokenizer.pad_to_max(max_seq_len=20, tkids=tkids)
  batch_tkids.append(tkids)

# Convert mini-batch to tensor.
batch_tkids = torch.LongTensor(batch_tkids)

# Create language model instance.
model = lmp.model.ElmanNet(tknzr=tokenizer)

# Calculate mini-batch loss.
loss, batch_cur_states = model.cal_loss(
  batch_cur_tkids=batch_tkids[:, :-1],
  batch_next_tkids=batch_tkids[:, 1:],
  batch_prev_states=None,
)

The method cal_loss takes three input and returns a tuple. The batch_cur_tkids is the input token id list and the batch_next_tkids is the prediction target. Both batch_cur_tkids and batch_next_tkids are long tensor and have the same shape \((B, S)\) where \(B\) is the batch size and \(S\) is input sequence length. We set batch_prev_states=None to use initial hidden states. The first item in the returned tuple is a torch.Tensor which represents the mini-batch next token prediction loss. One can call the PyTorch built-in torch.Tensor.backward method to perform back-propagation. The second item in the returned tuple represents the current hidden states of a language model. The exact structure of current hidden states depends on which language model is used. The current hidden states can be used as the initial hidden states of next input. This is done by pass current hidden states as batch_prev_states. This is needed since one can only process certain sequence length at a time.

Predict next token#

Next token prediction can be done by the pred method. The input of pred is almost the same as cal_loss, except that we do not input the prediction target. This is because when performing evaluation one do not and cannot know the prediction target. One set batch_prev_states=None to use initial hidden states just as in cal_loss. The returned tuple have two items. The first item in the returned tuple is a torch.Tensor which represent the next token id probability distribution. The probability distribution tensor has shape \((B, S, V)\) where \(B\) is batch size, \(S\) is input sequence length and \(V\) is the vocabulary size of the language model pairing tokenizer. The second item in the returned tuple represents the current hidden states of a language model. One should compare this with cal_loss.

import lmp.model
import lmp.tknzr
import torch

# Create tokenizer instance.
tokenizer = lmp.tknzr.CharTknzr()

# Build tokenizer vocabulary.
batch_txt = ['hello world', 'how are you']
tokenizer.build_vocab(batch_txt=batch_txt)

# Encode mini-batch.
batch_tkids = []
for txt in batch_txt:
  tkids = tokenizer.enc(txt=txt)
  tkids = tokenizer.pad_to_max(max_seq_len=20, tkids=tkids)
  batch_tkids.append(tkids)

# Convert mini-batch to tensor.
batch_tkids = torch.LongTensor(batch_tkids)

# Create language model instance.
model = lmp.model.ElmanNet(tknzr=tokenizer)

# Calculate next token prediction.
pred, batch_cur_states = model.pred(
  batch_cur_tkids=batch_tkids,
  batch_prev_states=None,
)

All available language models#