seq2seq in PyTorch
Build an attention-based encoder-decoder LSTM in PyTorch that forecasts a vessel's next positions from its recent AIS track, using AISdb to query, clean, and reshape the trajectories for training.
This tutorial builds a sequence-to-sequence LSTM in PyTorch that forecasts where a vessel is heading next from its last 80 AIS fixes. AISdb handles the querying, cleaning, and interpolation of the trajectories, and the model predicts position residuals that decode back to lat/lon on a map. You walk away with a complete forecasting pipeline, from raw AIS messages to a metric error per predicted step.
What you will learn
Querying and cleaning vessel tracks with AISdb generators (
DBQuery,TrackGen, denoising, interpolation)Turning trajectories into sliding-window tensors for supervised forecasting
Building an attention-based encoder-decoder LSTM with teacher forcing
Training with a coordinate-weighted loss plus a smoothness penalty
Decoding residual predictions back to lat/lon and measuring haversine error
Prerequisites
pip install aisdb torch scikit-learn pandas numpy pyproj cartopy matplotlibThe run on this page trains against a private SQLite extract of AIS traffic in the Gulf of St. Lawrence, first week of August 2018, so treat DB_CONNECTION below as a stand-in for whatever regional extract you have. Without private data, the open NOAA day file AIS_2020_01_01.zip works as a drop-in. Decode it with aisdb.decode_msgs(filepaths=[...], dbconn=dbconn, source='NOAA') the way Using Your AIS Data shows, point DB_CONNECTION at the result, and swap in the Gulf of Mexico window (xmin=-98, xmax=-80, ymin=24, ymax=31, 2020-01-01 to 2020-01-02). Expect a noticeably worse error than the roughly 830 m reported below, since one day of data gives the sliding-window sequencer far fewer long, dense tracks to learn from.
Step 1. Query training and test tracks
DBQuery wraps a time range and a bounding box into a SQL query and hands back a generator of rows, which TrackGen vectorizes into per-vessel tracks. The test set comes from a later date range than the training set so the model cannot memorize it.

Step 2. Preprocess the tracks
Raw AIS is too irregular to feed a model directly. The function below drops near-stationary pings, splits segments that do not belong to the same continuous trip, interpolates to a fixed 5-minute step, keeps only MMSIs with at least 100 points, projects lat/lon into UTM meters with pyproj, encodes course over ground as sine and cosine because it wraps at 360 degrees, and scales every feature with a RobustScaler fit on the training set only.
Step 3. Create sequences and loaders
A sliding window turns each trajectory into supervised examples, 80 input steps predicting the next 2. Splitting train and validation by MMSI keeps every vessel on one side of the split, so validation measures generalization to unseen vessels rather than unseen windows of seen ones.
Step 4. Define the model and losses
The encoder LSTM compresses the 80-step history into hidden states, and the decoder unrolls two future steps, attending over the encoder outputs at each step (Bahdanau et al., 2015) and predicting a residual that is added to the last observed position. Anchoring every forecast to the vessel's current fix keeps predictions physically plausible, and teacher forcing occasionally feeds the decoder ground truth during training so multi-step errors do not compound early (Sutskever et al., 2014).
Step 5. Train
The loss combines the coordinate-weighted data term with a smoothness penalty that decays over epochs, and the best epoch by validation loss is checkpointed to disk. Seeds are fixed so the run is reproducible.
Step 6. Evaluate on held-out tracks
A small decimal loss can hide predictions that land far from the real position, so the evaluation decodes the residuals back to lat/lon, measures haversine error in meters, and plots the track on a map as the sanity check. The residuals are decoded by multiplying with the per-axis standard deviation of the true training residuals in meters, then cumulative-summing from the last observed position.
Results
The call above evaluates a single test batch (num_batches=1), decoding the residual predictions back into lat/lon and printing a predicted-vs-true table.
Predicted vs True (lat/lon)
0
-61.69744
-61.70585
43.22816
43.22385
833.31 m
Summary (meters) t=0 mean error: 833.31 m mean over horizon: 833.31 m, median: 833.31 m
That 833 meter figure is a single two-step window from one batch, not an average across the whole test set, so treat it as illustrative rather than a benchmark. What it does show is that the residual decoding is behaving. If the scaler or the cumulative sum step were wrong, the predicted point would land tens of kilometers off or on the wrong side of the coastline instead of a few hundred meters short of the real fix.

The predicted marker sits close to the true one and on the same heading as the input history, which is what a residual decoder anchored to the last observed position should produce. Error grows with the output horizon and with sharp maneuvers, since the model only sees the pattern in its input window and has no notion of destination or intent.
Takeaway
AISdb's generator chain (
remove_pings_wrt_speed,encode_greatcircledistance,interp_time) turns raw AIS into model-ready tracks in a few lines.Predicting position residuals anchored to the last fix keeps forecasts physically plausible, and decoding mistakes show up on the map immediately.
The 833 m error is one illustrative window under a single split and seed; cross-validate across time windows and vessel types before trusting the number.
UTM zone 20 and the fitted scalers are specific to this region and season, so update the projection and refit the scalers when you move the pipeline elsewhere.
Next, AutoEncoders in Keras tackles the same forecasting problem with a GRU-based encoder-decoder and teacher forcing, this time in Keras.
Last updated