A neural code-completion model that predicts the remainder of a line of Python source from the preceding context. Built as an LSTM sequence-to-sequence model with teacher forcing, trained on the CodeSearchNet Python corpus, with hyperparameters selected by Optuna.
Stack: Python · PyTorch · Hugging Face Datasets · Optuna
Rather than predicting a single next token, the model treats completion as a short translation task: an encoder reads a fixed window of preceding tokens, and a decoder generates tokens until the end of the current logical line. This lets a single forward pass produce a whole-line suggestion instead of one token.
The pipeline runs in three stages, one notebook each:
CodeSearchNet Python (455,243 functions)
│
▼
01 Tokenize with Python's `tokenize` module
Strip comments and docstrings
80/10/10 train / test / validation split
Build vocabulary, encode to integer IDs
│
▼
02 Chunk into (context → line-completion) pairs
SEQ_LEN = 32, STRIDE = 16
│
▼
03 Seq2seq LSTM · Optuna tuning · training · evaluation
Source is tokenized with Python's own tokenize module rather than a generic
subword tokenizer, so the units are real Python tokens. Comments and docstrings
are dropped — a docstring is identified as a STRING token immediately
following INDENT, NEWLINE, or DEDENT — leaving the model to learn code
structure instead of prose.
Built from the training split only, to avoid leaking validation and test tokens into the vocabulary. It keeps the 50,000 most frequent tokens that appear at least twice, plus four special tokens:
| Token | ID | Purpose |
|---|---|---|
<PAD> |
0 | Padding, ignored by both loss and accuracy |
<UNK> |
1 | Out-of-vocabulary token |
<BOS> |
2 | Start of sequence |
<EOS> |
3 | End of sequence |
A sliding window of 32 tokens with stride 16 produces each example. The target is not a fixed span: it runs from the split point to the nearest newline (capped at 50 tokens), so each example teaches the model to finish exactly one logical line.
split point
│
... tokens ──────┤────────────── ...
└── 32-token ────┘└─ target: to end of line ─┘
context
Encoder input is wrapped in <BOS> … <EOS>; the decoder is fed
<BOS> + target and supervised against target + <EOS>. Batches are padded with
pad_sequence via a custom collate_fn, since targets vary in length.
An encoder–decoder LSTM. The encoder compresses the context window into its final hidden and cell states; the decoder is initialised from those states and generates the completion one token at a time.
context tokens generated tokens
│ ▲
▼ │
Embedding Linear → vocab
│ ▲
▼ │
LSTM (n_layers, dropout) ──(hidden, cell)──▶ LSTM (n_layers, dropout)
▲
│
Embedding
▲
│
<BOS> / previous token / ground truth
(teacher forcing)
During training, each decoder step is fed the ground-truth previous token with
probability teacher_forcing_ratio, and its own previous prediction otherwise.
Evaluation always runs with teacher_forcing_ratio=0.0, so reported numbers
reflect free-running generation rather than teacher-forced decoding.
Loss: CrossEntropyLoss(ignore_index=vocab['<PAD>'])
Metric: token-level accuracy, masked so padding is excluded from the denominator
Tuned with Optuna — TPE sampler (seed=42) with a Hyperband pruner, minimising
validation loss over 10 trials. Trials run on a 5,000-example training subset
and 1,000-example validation subset to keep the search affordable.
| Hyperparameter | Search space |
|---|---|
hidden_dim |
128, 256, 384, 512 |
n_layers |
1 – 3 |
dropout |
0.1 – 0.5 |
learning_rate |
1e-4 – 3e-3 (log) |
weight_decay |
1e-6 – 1e-3 (log) |
teacher_forcing_ratio |
0.3 – 0.7 |
The best configuration is then retrained on the full training set with gradient
clipping (clip=1.0), a ReduceLROnPlateau schedule, and best-checkpoint
saving on validation loss.
Metrics are not yet recorded — the notebooks were committed with their outputs cleared, so no training curves, tuned hyperparameters, or test scores are preserved in the repository.
To produce them, run notebooks/03_lstm_model_.ipynb end to end. It reports:
- best validation loss and best hyperparameters (Optuna study)
- per-trial results as a dataframe
- training/validation loss and accuracy curves
- final test loss and token accuracy
Re-committing that notebook with outputs intact would make the results reproducible for anyone reading the repository.
notebooks/
01_data_encoding.ipynb tokenization, split, vocabulary, encoding
02_chunking_dataset_preparation.ipynb windowing into context/target pairs
03_lstm_model_.ipynb seq2seq model, Optuna tuning, training, eval
src/
data/preprocess.py tokenize, split, vocab, dataset, dataloaders
models/lstm_model.py encoder, decoder, seq2seq, greedy completion
train.py training loop, Optuna tuning, evaluation
The notebooks are the exploratory record; src/ is the same pipeline as
runnable modules. Import-time side effects were removed, so importing
preprocess no longer downloads the dataset — call build_and_cache() once,
then get_dataloaders().
pip install torch datasets scikit-learn optuna matplotlib tqdmRun the pipeline from src/:
python -m src.data.preprocess # tokenize + encode -> artifacts/
python -m src.train tune --trials 10 # Optuna search -> artifacts/best_params.json
python -m src.train train --epochs 20 # train, checkpoint, report test metricspreprocess accepts --limit N to tokenize only the first N functions, which
is useful for a quick end-to-end check before committing to the full corpus.
Alternatively, run the notebooks in order — 01 → 02 → 03. Stage 01
downloads the dataset from the Hugging Face Hub and writes vocab.pkl plus the
encoded splits; later stages load those artifacts.
A GPU is recommended. The model moves to CUDA when available and falls back to CPU, but the vocabulary is large enough (up to 50,004 entries) that the decoder's output projection dominates runtime on CPU.
Setting HF_TOKEN before stage 01 avoids the Hugging Face rate limit on
unauthenticated downloads.
CodeSearchNet Python
— 455,243 Python functions, loaded via datasets.load_dataset. Raw data and
generated artifacts (.pkl files, checkpoints) are not committed.
- Tokenization and encoding pipeline
- Chunked dataset and dataloaders
- Seq2seq LSTM model
- Optuna hyperparameter tuning
- Runnable
src/modules matching the notebook pipeline - Greedy completion (
Seq2Seq.complete) - Record and commit final metrics
- Top-k / beam search decoding
- Decode generated token ids back to formatted source