Facial Keypoints Detection
Download public Facial Keypoints training/test zips plus IdLookupTable, parse 96x96 image strings, train a baseline neural network, and create submission.csv.
Final deliverable: A computer-vision regression notebook that predicts facial landmark coordinates and writes RowId/Location submission.csv.
Dataset: Facial Keypoints Detection public mirror
The notebook downloads public training.zip, test.zip, and IdLookupTable.csv mirrors for the Kaggle Facial Keypoints Detection task. Training rows contain an Image string plus keypoint coordinate columns.
- Image: space-separated 96x96 grayscale pixel values
- keypoint columns such as left_eye_center_x and nose_tip_y: target coordinates
- IdLookupTable.csv: maps each RowId to an ImageId and feature name required for submission
Download Facial Keypoints Data
Download and extract the public facial keypoints zip.
Run the public download cell and inspect available CSV files.
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.
from pathlib import Path
from urllib.request import urlretrieve
import zipfile
DATA_DIR = Path("data/facial-keypoints-detection")
DATA_DIR.mkdir(parents=True, exist_ok=True)
def find_file(name):
possible_roots = [DATA_DIR, Path("/kaggle/input")]
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")
files = {
"training.zip": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/training.zip",
"test.zip": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/test.zip",
"IdLookupTable.csv": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/IdLookupTable.csv"
}
for filename, url in files.items():
target = DATA_DIR / filename
if target.exists() and target.stat().st_size > 0:
print(f"already exists: {target}")
continue
print(f"downloading {filename} ...")
urlretrieve(url, target)
print(f"saved: {target}")
for zip_path in DATA_DIR.glob("*.zip"):
extract_dir = DATA_DIR / zip_path.stem
extract_dir.mkdir(exist_ok=True)
if any(extract_dir.iterdir()):
print(f"already extracted: {extract_dir}")
continue
with zipfile.ZipFile(zip_path, "r") as zf:
zf.extractall(extract_dir)
print(f"extracted: {zip_path} -> {extract_dir}")
print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
print(path)
if len(all_files) > 40:
print(f"... {len(all_files) - 40} more files not shown")Show reference solution
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.
from pathlib import Path
from urllib.request import urlretrieve
import zipfile
DATA_DIR = Path("data/facial-keypoints-detection")
DATA_DIR.mkdir(parents=True, exist_ok=True)
def find_file(name):
possible_roots = [DATA_DIR, Path("/kaggle/input")]
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")
files = {
"training.zip": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/training.zip",
"test.zip": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/test.zip",
"IdLookupTable.csv": "https://raw.githubusercontent.com/ruchawaghulde/Facial-Keypoints-Detection/master/Data/IdLookupTable.csv"
}
for filename, url in files.items():
target = DATA_DIR / filename
if target.exists() and target.stat().st_size > 0:
print(f"already exists: {target}")
continue
print(f"downloading {filename} ...")
urlretrieve(url, target)
print(f"saved: {target}")
for zip_path in DATA_DIR.glob("*.zip"):
extract_dir = DATA_DIR / zip_path.stem
extract_dir.mkdir(exist_ok=True)
if any(extract_dir.iterdir()):
print(f"already extracted: {extract_dir}")
continue
with zipfile.ZipFile(zip_path, "r") as zf:
zf.extractall(extract_dir)
print(f"extracted: {zip_path} -> {extract_dir}")
print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
print(path)
if len(all_files) > 40:
print(f"... {len(all_files) - 40} more files not shown")This project has a less standard submission format, so the first win is making the training CSV, test CSV, and lookup table visible.
Load Images and Targets
Read the landmark CSV and identify image strings plus coordinate columns.
Load training.csv, print shape, inspect coordinate columns, and check missing values.
from pathlib import Path
import pandas as pd
possible_roots = [
Path("data/facial-keypoints-detection"),
Path("/kaggle/input"),
]
def find_file(name):
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")
train_path = find_file("training.csv")
df = pd.read_csv(train_path)
print(train_path)
print(df.shape)
df.head()
# Identify target columns
# Check missing valuesShow reference solution
from pathlib import Path
import pandas as pd
possible_roots = [
Path("data/facial-keypoints-detection"),
Path("/kaggle/input"),
]
def find_file(name):
for root in possible_roots:
if root.exists():
matches = list(root.rglob(name))
if matches:
return matches[0]
raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")
train_path = find_file("training.csv")
df = pd.read_csv(train_path)
print(train_path)
print(df.shape)
df.head()
target_cols = [col for col in df.columns if col != "Image"]
display(df[target_cols[:8] + ["Image"]].head())
display(df[target_cols].isna().sum().sort_values(ascending=False).head())
print("number of coordinate columns:", len(target_cols))Each row stores the whole face as one long pixel string. The targets are named coordinates, so preserving those exact column names matters for submission.
Visualize Face Landmarks
Convert one Image string to a 96x96 array and overlay available keypoints.
Parse one image string, plot the face, and scatter x/y keypoint coordinates.
import numpy as np import matplotlib.pyplot as plt from PIL import Image # Parse one Image string # Overlay keypoints
Show reference solution
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
clean_preview = df.dropna(subset=target_cols).copy()
row = clean_preview.iloc[0]
image = np.fromstring(row["Image"], sep=" ", dtype=np.float32).reshape(96, 96)
x_cols = [col for col in target_cols if col.endswith("_x")]
y_cols = [col.replace("_x", "_y") for col in x_cols]
xs = row[x_cols].to_numpy(dtype=np.float32)
ys = row[y_cols].to_numpy(dtype=np.float32)
plt.imshow(image, cmap="gray")
plt.scatter(xs, ys, c="red", s=10)
plt.title("Face with keypoints")
plt.axis("off")
plt.show()Computer-vision projects need visual sanity checks. A model cannot fix incorrectly parsed images or swapped x/y coordinates.
Prepare a Baseline Training Matrix
Create normalized image arrays and target coordinates for rows with complete labels.
Drop incomplete rows for the baseline, normalize pixels and coordinates, and split train/validation.
from sklearn.model_selection import train_test_split # Drop incomplete target rows # Create X image matrix and y target matrix with normalized coordinates # Split train/validation
Show reference solution
import numpy as np
from sklearn.model_selection import train_test_split
clean = df.dropna(subset=target_cols).copy()
def parse_image_string(value):
return np.fromstring(value, sep=" ", dtype=np.float32) / 255.0
X_images = np.vstack(clean["Image"].map(parse_image_string).to_numpy())
y_points = clean[target_cols].to_numpy(dtype=np.float32) / 96.0
X_train, X_val, y_train, y_val = train_test_split(
X_images, y_points, test_size=0.2, random_state=42
)
print(X_train.shape, y_train.shape)Normalizing pixels and coordinates stabilizes neural-network training. Here every image is 96x96, so target coordinates can be divided by 96.
Train a Small Neural Network
Train a baseline MLP that predicts all keypoint coordinates.
Use PyTorch to train a small fully connected network for coordinate regression.
# Train a small PyTorch model for keypoint regression # Keep this baseline simple before trying CNNs
Show reference solution
import torch
from torch import nn
from torch.utils.data import DataLoader, TensorDataset
device = "cuda" if torch.cuda.is_available() else "cpu"
train_ds = TensorDataset(
torch.tensor(X_train, dtype=torch.float32),
torch.tensor(y_train, dtype=torch.float32),
)
val_x = torch.tensor(X_val, dtype=torch.float32).to(device)
val_y = torch.tensor(y_val, dtype=torch.float32).to(device)
loader = DataLoader(train_ds, batch_size=128, shuffle=True)
model = nn.Sequential(
nn.Linear(96 * 96, 512),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(512, y_train.shape[1]),
).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.MSELoss()
for epoch in range(8):
model.train()
for xb, yb in loader:
xb, yb = xb.to(device), yb.to(device)
optimizer.zero_grad()
loss = loss_fn(model(xb), yb)
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
val_loss = loss_fn(model(val_x), val_y).item()
print(epoch, round(val_loss, 5))This baseline is intentionally simple. It proves the data pipeline works before investing in CNN architecture.
Create Facial Keypoints Submission
Predict test coordinates and save RowId/Location submission.csv using IdLookupTable.
Predict every test image, map requested feature names through IdLookupTable, clip coordinates, and save submission.csv.
# Predict keypoints for test images # Use IdLookupTable.csv to create RowId/Location submission.csv
Show reference solution
model.eval()
with torch.no_grad():
val_pred = model(torch.tensor(X_val, dtype=torch.float32).to(device)).cpu().numpy()
val_rmse_pixels = np.sqrt(np.mean(((val_pred - y_val) * 96.0) ** 2))
print("validation RMSE in pixels:", round(float(val_rmse_pixels), 3))
test_df = pd.read_csv(find_file("test.csv"))
lookup = pd.read_csv(find_file("IdLookupTable.csv"))
X_test_images = np.vstack(test_df["Image"].map(parse_image_string).to_numpy())
with torch.no_grad():
test_pred = model(torch.tensor(X_test_images, dtype=torch.float32).to(device)).cpu().numpy() * 96.0
pred_df = pd.DataFrame(test_pred, columns=target_cols)
pred_df["ImageId"] = np.arange(1, len(pred_df) + 1)
locations = []
for _, row in lookup.iterrows():
image_id = int(row["ImageId"])
feature_name = row["FeatureName"]
value = pred_df.loc[pred_df["ImageId"] == image_id, feature_name].iloc[0]
locations.append(value)
submission = pd.DataFrame({
"RowId": lookup["RowId"],
"Location": np.clip(locations, 0, 96),
})
submission.to_csv("submission.csv", index=False)
display(submission.head())
print("saved submission.csv", submission.shape)The lookup table expands each image into the exact coordinates Kaggle asks for. One image can produce many submission rows.