Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build and debug deep learning models with Keras and TensorFlow backend
.claude/skills/brycewang-stanford-keras-deep-learning/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 85% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 54% | 0% |
| case-02 | ✓→✓ | = Same ✓ | -16% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 109% | 0% |
Keras is the high-level deep learning API that ships as part of TensorFlow 2.x and is the recommended interface for building, training, and deploying neural networks. Its Sequential and Functional APIs provide a progressive disclosure of complexity: beginners can stack layers in minutes, while researchers can build arbitrary DAG architectures, custom training loops, and multi-output models with the same framework.
This guide covers practical patterns for academic research with Keras, from image classification and sequence modeling to custom loss functions and experiment reproducibility. The focus is on patterns that appear repeatedly in published work -- data loading pipelines, callback orchestration, hyperparameter search, and model introspection -- rather than toy examples.
Keras is particularly strong in rapid prototyping for research papers. Its integration with TensorBoard, Weights & Biases, and tf.data pipelines makes it straightforward to go from idea to reproducible experiment to publication-quality results.
pythonimport tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # Image classification baseline model = keras.Sequential([ layers.Input(shape=(224, 224, 3)), layers.Rescaling(1.0 / 255), layers.Conv2D(32, 3, activation="relu", padding="same"), layers.BatchNormalization(), layers.MaxPooling2D(2), layers.Conv2D(64, 3, activation="relu", padding="same"), layers.BatchNormalization(), layers.MaxPooling2D(2), layers.Conv2D(128, 3, activation="relu", padding="same"), layers.GlobalAveragePooling2D(), layers.Dropout(0.3), layers.Dense(256, activation="relu"), layers.Dense(10, activation="softmax"), ]) model.compile( optimizer=keras.optimizers.AdamW(learning_rate=1e-3, weight_decay=1e-4), loss="sparse_categorical_crossentropy", metrics=["accuracy"], )
python# Multi-input model for multimodal research image_input = keras.Input(shape=(224, 224, 3), name="image") text_input = keras.Input(shape=(128,), dtype="int32", name="text") # Image branch x_img = keras.applications.EfficientNetV2B0( include_top=False, weights="imagenet", input_tensor=image_input ).output x_img = layers.GlobalAveragePooling2D()(x_img) # Text branch x_txt = layers.Embedding(10000, 128)(text_input) x_txt = layers.Bidirectional(layers.LSTM(64))(x_txt) # Merge merged = layers.Concatenate()([x_img, x_txt]) merged = layers.Dense(256, activation="relu")(merged) merged = layers.Dropout(0.4)(merged) output = layers.Dense(5, activation="softmax", name="classification")(merged) model = keras.Model(inputs=[image_input, text_input], outputs=output)
Efficient data loading is critical for GPU utilization in research experiments:
pythondef build_dataset(file_pattern, batch_size=32, training=True): """Build a tf.data pipeline with augmentation for research experiments.""" dataset = tf.data.Dataset.list_files(file_pattern, shuffle=training) def parse_image(path): img = tf.io.read_file(path) img = tf.image.decode_jpeg(img, channels=3) img = tf.image.resize(img, [256, 256]) label = tf.strings.split(path, os.sep)[-2] return img, label dataset = dataset.map(parse_image, num_parallel_calls=tf.data.AUTOTUNE) if training: dataset = dataset.shuffle(1000) dataset = dataset.map( lambda x, y: (tf.image.random_flip_left_right(x), y), num_parallel_calls=tf.data.AUTOTUNE, ) dataset = dataset.batch(batch_size) dataset = dataset.prefetch(tf.data.AUTOTUNE) return dataset
pythonimport os import random import numpy as np def set_seed(seed=42): """Ensure reproducibility across runs for paper results.""" os.environ["PYTHONHASHSEED"] = str(seed) random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed) set_seed(42) callbacks = [ keras.callbacks.ModelCheckpoint( "best_model.keras", monitor="val_loss", save_best_only=True ), keras.callbacks.EarlyStopping( monitor="val_loss", patience=10, restore_best_weights=True ), keras.callbacks.ReduceLROnPlateau( monitor="val_loss", factor=0.5, patience=5, min_lr=1e-6 ), keras.callbacks.TensorBoard(log_dir="./logs", histogram_freq=1), keras.callbacks.CSVLogger("training_log.csv"), ] history = model.fit( train_dataset, validation_data=val_dataset, epochs=100, callbacks=callbacks, )
python@tf.function def train_step(model, optimizer, x, y, loss_fn): with tf.GradientTape() as tape: predictions = model(x, training=True) loss = loss_fn(y, predictions) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) return loss # Custom metric tracking train_loss = keras.metrics.Mean(name="train_loss") for epoch in range(num_epochs): train_loss.reset_state() for x_batch, y_batch in train_dataset: loss = train_step(model, optimizer, x_batch, y_batch, loss_fn) train_loss.update_state(loss) print(f"Epoch {epoch+1}, Loss: {train_loss.result():.4f}")
| Issue | Symptom | Solution | |-------|---------|----------| | Exploding gradients | Loss becomes NaN | Add gradient clipping, reduce learning rate | | Overfitting | Val loss diverges from train loss | Add Dropout, data augmentation, weight decay | | Underfitting | Both losses plateau high | Increase model capacity, reduce regularization | | Slow training | Low GPU utilization | Use tf.data with prefetch, increase batch size | | Memory errors | OOM on GPU | Reduce batch size, use mixed precision | | Non-deterministic results | Different results per run | Call set_seed(), set TF_DETERMINISTIC_OPS=1 |
python# Enable mixed precision for 2x speedup on modern GPUs keras.mixed_precision.set_global_policy("mixed_float16") # Ensure the output layer uses float32 for numerical stability output = layers.Dense(10, activation="softmax", dtype="float32")(x)
tensorflow, keras, numpy, and cuda versions in your paper appendix.keras.utils.set_random_seed(42) for full determinism (TF 2.12+)..keras format (not HDF5) for forward compatibility.tf.debugging.enable_check_numerics() during development to catch NaN/Inf early.tf.saved_model for deployment; export ONNX for cross-framework comparison.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | fail→fail | 19,050 | 17,443 | -8% | 1 | 1 | 0% | 3,194 | 5,381 | +68% | 0 | 0 | — |
case-01 | fail→fail | 20,481 | 23,507 | +15% | 1 | 1 | 0% | 4,410 | 5,368 | +22% | 0 | 0 | — |
case-02 | pass→pass | 25,359 | 11,858 | -53% | 1 | 1 | 0% | 5,297 | 4,471 | -16% | 0 | 0 | — |
case-03 | fail→pass | 20,929 | 19,596 | -6% | 1 | 1 | 0% | 4,019 | 5,909 | +47% | 0 | 0 | — |
case-04 | pass→pass | 16,671 | 24,682 | +48% | 1 | 1 | 0% | 3,108 | 6,511 | +109% | 0 | 0 | — |
case-05 | fail→fail | 14,486 | 11,210 | -23% | 1 | 1 | 0% | 2,456 | 4,107 | +67% | 0 | 0 | — |
case-06 | pass→pass | 13,132 | 14,361 | +9% | 1 | 1 | 0% | 2,238 | 4,848 | +117% | 0 | 0 | — |
case-08 | pass→pass | 12,526 | 7,693 | -39% | 1 | 1 | 0% | 2,225 | 3,195 | +44% | 0 | 0 | — |
case-09 | pass→pass | 15,557 | 14,486 | -7% | 1 | 1 | 0% | 2,589 | 4,570 | +77% | 0 | 0 | — |
case-10 | fail→pass | 16,202 | 15,495 | -4% | 1 | 1 | 0% | 2,557 | 4,741 | +85% | 0 | 0 | — |
case-11 | fail→pass | 18,291 | 17,163 | -6% | 1 | 1 | 0% | 3,288 | 5,052 | +54% | 0 | 0 | — |
case-12 | pass→pass | 6,905 | 4,620 | -33% | 1 | 1 | 0% | 1,291 | 2,944 | +128% | 0 | 0 | — |
case-13 | pass→pass | 12,101 | 12,088 | -0% | 1 | 1 | 0% | 2,373 | 4,447 | +87% | 0 | 0 | — |
case-14 | pass→pass | 12,976 | 8,085 | -38% | 1 | 1 | 0% | 2,286 | 3,585 | +57% | 0 | 0 | — |
case-15 | pass→pass | 8,330 | 7,262 | -13% | 1 | 1 | 0% | 1,383 | 3,539 | +156% | 0 | 0 | — |
case-16 | pass→pass | 9,744 | 8,629 | -11% | 1 | 1 | 0% | 1,744 | 3,592 | +106% | 0 | 0 | — |
case-17 | pass→pass | 12,562 | 12,468 | -1% | 1 | 1 | 0% | 1,965 | 4,213 | +114% | 0 | 0 | — |
case-18 | pass→pass | 9,271 | 10,409 | +12% | 1 | 1 | 0% | 1,719 | 3,988 | +132% | 0 | 0 | — |
case-19 | pass→pass | 9,425 | 9,322 | -1% | 1 | 1 | 0% | 1,515 | 3,418 | +126% | 0 | 0 | — |
case-20 | pass→pass | 14,801 | 16,689 | +13% | 1 | 1 | 0% | 2,768 | 5,068 | +83% | 0 | 0 | — |
case-21 | pass→pass | 9,886 | 10,924 | +10% | 1 | 1 | 0% | 2,105 | 4,279 | +103% | 0 | 0 | — |
case-22 | pass→pass | 14,393 | 10,207 | -29% | 1 | 1 | 0% | 3,016 | 4,222 | +40% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.