THE GENIUS PROJECT
🏆 Lesson 5 of 6 · The final assignment

Predict the World Cup Final + the Weather

🕒 60 to 90 minutes 🐍 Two Colab notebooks 📮 One submission, tracked

This is what the whole week was for. You will train two deep learning models in Google Colab: a dense network that predicts the 2026 World Cup final, and an LSTM that predicts tomorrow's maximum temperature in your capital city. Then you submit both predictions below, before the final kicks off on Sunday, July 19, 2026. Reality will grade you. That is the job.

💼 Adrian's brief

Adrian Dunkley is not asking for a lucky guess. He is asking for a process: features, model, training, honest validation, then a prediction you can defend. The write-up box in the submission form is where you show your reasoning. A well-reasoned wrong prediction beats a fluke, because a good process wins in the long run. That is true in football, in weather, and in every AI job you will ever have.

Part A: the World Cup final ⚽

The problem: two teams, one match, three possible outcomes (team A wins, draw after 90 minutes, team B wins) plus a scoreline. Your model learns from past international matches: how ranking gaps, recent goals scored, and recent goals conceded translate into results. This is tabular data, so per Lesson 3, a dense network is the right tool.

Why can a network predict a football match at all?

Because history rhymes. Across hundreds of past internationals, teams with a big ranking advantage and hot scoring form win far more often than they lose. Those regularities are patterns hiding in the numbers, and finding patterns in numbers is the one thing a neural network is built for. Under the hood, each hidden neuron computes a Lesson 1 weighted sum of the five features. For example, a neuron with weights 0.1, 0.3 and −0.2 looking at a ranking gap of 25, 12 goals scored, and 3 conceded computes 0.1 × 25 + 0.3 × 12 − 0.2 × 3 = 2.5 + 3.6 − 0.6 = 5.5, a strong "team A looks dominant" signal. Training uses gradient descent to tune those weights until the signals line up with what actually happened in the past matches you fed it.

The final layer then squashes the network's three raw scores into three probabilities that always add up to 100%. So your model never says "team A will win". It says "in past matches that looked like this one, team A won about 58% of the time". That is an honest, useful statement, and it is the most any forecast of a single football match can honestly claim.

🎚️
New Word!Softmax

The final-layer function that turns the network's raw output scores into probabilities between 0% and 100% that always sum to 100%. Bigger raw score in, bigger share of the probability out. That is the softmax you will see in the starter code below.

Steps
  1. Open Google Colab, create a new notebook, and paste the starter code below.
  2. Run it once as-is to confirm it trains (loss should fall).
  3. Improve the data. The starter dataset is a small teaching sample. Add more matches from this World Cup (group stage and knockouts, you watched them!): ranking difference, goals scored and conceded in the last 5 games, then the result.
  4. Update final_match with the two real finalists' current numbers (rankings at FIFA.com, form from the tournament so far).
  5. Read your model's probabilities, decide your scoreline, and bring both to the form below.
worldcup_final.py · starter notebook
import numpy as np
from tensorflow import keras

# Each row: [ranking_gap, teamA_goals_for_last5, teamA_goals_against_last5,
#            teamB_goals_for_last5, teamB_goals_against_last5]
# ranking_gap = teamB_rank - teamA_rank  (positive = team A ranked better)
# Label: 0 = team A wins, 1 = draw (90 mins), 2 = team B wins
# STARTER SAMPLE: replace/extend with real matches from this tournament!
X = np.array([
    [ 25, 12, 3,  6, 9], [ 18, 10, 4,  7, 8], [  4,  8, 5,  8, 6],
    [ -6,  7, 6,  9, 4], [-15,  5, 8, 11, 3], [ 30, 13, 2,  5, 10],
    [  2,  9, 5,  9, 5], [ 10,  9, 4,  8, 7], [-22,  4, 9, 12, 3],
    [  8, 10, 6,  7, 7], [ -3,  8, 6,  8, 5], [ 14, 11, 4,  6, 8],
    [ -9,  6, 7, 10, 5], [ 20, 12, 3,  7, 9], [  0,  8, 6,  8, 6],
    [ -4,  7, 5,  9, 6], [ 12, 10, 5,  7, 6], [-18,  5, 8, 10, 4],
], dtype=float)
y = np.array([0, 0, 1, 2, 2, 0, 1, 0, 2, 0, 1, 0, 2, 0, 1, 2, 0, 2])

# normalise features so gradient descent behaves
mu, sd = X.mean(axis=0), X.std(axis=0)
X = (X - mu) / sd

model = keras.Sequential([
    keras.layers.Dense(16, activation="relu", input_shape=(5,)),
    keras.layers.Dense(8, activation="relu"),
    keras.layers.Dense(3, activation="softmax"),   # 3 outcome probabilities
])
model.compile(optimizer="adam",
              loss="sparse_categorical_crossentropy",
              metrics=["accuracy"])
model.fit(X, y, epochs=300, validation_split=0.2, verbose=0)

# THE FINAL: fill in the two real finalists' numbers before running!
final_match = np.array([[5, 11, 4, 9, 5]], dtype=float)
final_match = (final_match - mu) / sd   # same normalisation as training

probs = model.predict(final_match, verbose=0)[0]
print(f"Team A win: {probs[0]:.0%}  Draw: {probs[1]:.0%}  Team B win: {probs[2]:.0%}")
>>> Output: Team A win: 58% Draw: 24% Team B win: 18%
🧠 Ideas to beat the starter model

More data beats fancier models: every real match you add helps. Try extra features (tournament goals per game, days of rest, penalty shootout record). For the scoreline, a simple approach is to predict each team's expected goals from its form and round to the nearest sensible score. And sanity-check: if your model says a 90% blowout in a World Cup FINAL between two teams that reached it, be suspicious of your data.

Part B: tomorrow's weather 🌦️

The problem: predict tomorrow's maximum temperature for your capital city. This is a sequence, so per Lesson 3, we use an LSTM. The notebook pulls real historical data for your city from the free Open-Meteo archive (no signup needed), trains on sliding windows of 7 days, and predicts day 8.

weather_lstm.py · starter notebook
import numpy as np, urllib.request, json, datetime
from tensorflow import keras

# 1) Your city here! (Kingston = 17.97, -76.79; Bridgetown = 13.1, -59.62;
#    Port of Spain = 10.65, -61.51; Castries = 14.01, -60.99)
LAT, LON, CITY = 17.97, -76.79, "Kingston"

end = datetime.date.today() - datetime.timedelta(days=1)
start = end - datetime.timedelta(days=120)
url = (f"https://archive-api.open-meteo.com/v1/archive?latitude={LAT}"
       f"&longitude={LON}&start_date={start}&end_date={end}"
       f"&daily=temperature_2m_max&timezone=auto")
data = json.loads(urllib.request.urlopen(url).read())
temps = np.array([t for t in data["daily"]["temperature_2m_max"] if t is not None])
print(f"{CITY}: {len(temps)} days of real max temperatures loaded")

# 2) windows of 7 days -> day 8
mean, std = temps.mean(), temps.std()
t = (temps - mean) / std
X = np.array([t[i:i+7] for i in range(len(t) - 7)])[..., None]
y = np.array([t[i+7] for i in range(len(t) - 7)])

model = keras.Sequential([
    keras.layers.LSTM(16, input_shape=(7, 1)),
    keras.layers.Dense(1),
])
model.compile(optimizer="adam", loss="mse")
model.fit(X, y, epochs=60, validation_split=0.15, verbose=0)

# 3) predict tomorrow from the last 7 real days
last7 = t[-7:].reshape(1, 7, 1)
pred = model.predict(last7, verbose=0)[0][0] * std + mean
print(f"Tomorrow's predicted max in {CITY}: {pred:.1f} °C")
>>> Output: Kingston: 120 days of real max temperatures loaded Tomorrow's predicted max in Kingston: 32.4 °C
🌡️ Push it further

Compare your LSTM against the Week 3 baselines: does it beat "tomorrow = today"? (Check honestly, it might not with only 120 days!) Try 240 days of history, a bigger window, or adding rainfall as a second input feature (daily=temperature_2m_max,precipitation_sum). Write what you tried in the submission form. The comparison IS the science.

⚠️ What your models cannot know

A model only knows the patterns in its training data, so anything outside that data is invisible to it. A red card in minute 3, a star striker with a stomach bug, a penalty shootout: none of that lives in ranking gaps and goal counts. Your weather LSTM has seen roughly 120 days of one city, so a tropical wave that rolls in tomorrow is a pattern it has never met. And on top of surprises and small data sits pure randomness: two evenly matched teams could replay the same final and get a different winner. Surprises, small data, and luck are the three permanent limits of every forecast, including the professional ones. Respect them in your write-up.

Sanity-check your predictions before you trust them

Every good forecaster interrogates the model before repeating its answer. Run these four checks, and mention them in the form:

📮 Submit your predictions

One form, both predictions. Submit before kickoff on Sunday, July 19, 2026. The most accurate combined forecast takes the leaderboard crown, and every submission earns XP toward your certificate week.

Your name, email, and predictions are saved with The Genius Project so we can score the leaderboard and contact the winner.

Last stop: the quiz and your certificate. 🎓