Note
Click here to download the full example code
02. Saving & Loading keras models
This script provides a hands-on demonstration of training an LSTM neural network for time-series forecasting on the classic airline passenger dataset.
Its primary focus is on illustrating three distinct methods for model serialization (saving), each suited for a different use case:
Standard Method (`.keras`): Simplest approach which saves a complete model in a single binary file.
Architecture + Weights (`.json` / `.h5`): Separates the model’s human-readable structure from its binary weights, offering more flexibility.
Readable Text Export (`.txt`): A manual method for maximum portability, ideal for secure environments or re-implementing the model in another language.
Warning
The script provides a mechanism for persisting the corresponding scikit-learn scaler; however, this feature has not been formally validated. Its performance on sophisticated scikit-learn preprocessing pipelines has not been determined. The ability to reuse the identical pipeline is a mandatory requirement for achieving a fully reproducible prediction workflow
26 # Libraries
27 import os
28 import glob
29 import numpy as np
30 import pandas as pd # pandas is needed for reading the URL
31 import tensorflow as tf
32 import jsonpickle
33 import jsonpickle.ext.numpy as jsonpickle_numpy
34 from pathlib import Path
35 from tensorflow.keras.models import Sequential
36 from tensorflow.keras.layers import Dense, LSTM
37 from tensorflow.keras.layers import Input
38 from sklearn.preprocessing import MinMaxScaler
39
40 # fix random seed for reproducibility
41 tf.random.set_seed(7)
42
43 # Configure jsonpickle to handle NumPy arrays
44 jsonpickle.set_preferred_backend('json')
45 jsonpickle_numpy.register_handlers()
46
47 # -----------------------------------------------------------------
48 # Helper methods
49 # -----------------------------------------------------------------
50 def to_jsonpickle(obj, filename):
51 with open(filename, 'w') as f:
52 f.write(jsonpickle.encode(obj))
53
54 def from_jsonpickle(filename):
55 with open(filename, 'r') as f:
56 return jsonpickle.decode(f.read())
Let’s load and prepare the data
63 # -------------------------------------
64 # Load and prepare data
65 # --------------------------------------
66 print("Loading and preparing data from online URL...")
67
68 # URL for the raw airline passengers CSV file
69 url = 'https://raw.githubusercontent.com/jbrownlee/Datasets/master/airline-passengers.csv'
70 df = pd.read_csv(url, usecols=[1], engine='python')
71 dataset = df.values.astype('float32')
72
73 # Normalize the dataset to the range [0, 1]
74 scaler = MinMaxScaler(feature_range=(0, 1))
75 scaled_data = scaler.fit_transform(dataset)
76
77 # Split into train and test sets
78 train_size = int(len(scaled_data) * 0.67)
79 train_data = scaled_data[0:train_size]
80 test_data = scaled_data[train_size:]
81
82 # Prepare datasets for training and prediction
83 look_back = 1
84 batch_size = 1
85
86 # Use the efficient Keras utility for the training dataset
87 train_ds = tf.keras.utils.timeseries_dataset_from_array(
88 data=train_data[:-look_back],
89 targets=train_data[look_back:],
90 sequence_length=look_back,
91 batch_size=batch_size,
92 )
93
94 # For verification, we need a consistent NumPy array for test predictions
95 def create_numpy_sequences(data, look_back=1):
96 """Creates X and Y NumPy arrays from time series data."""
97 X, Y = [], []
98 for i in range(len(data) - look_back -1):
99 X.append(data[i:(i + look_back), 0])
100 Y.append(data[i + look_back, 0])
101 return np.array(X), np.array(Y)
102
103 testX, testY = create_numpy_sequences(test_data, look_back)
104 # Reshape input to be [samples, time steps, features] for the LSTM model
105 testX = np.reshape(testX, (testX.shape[0], testX.shape[1], 1))
106 print(f"Data prepared. Test input shape: {testX.shape}")
Out:
Loading and preparing data from online URL...
Data prepared. Test input shape: (46, 1, 1)
Lets create and train our LSTM model
112 # ---------------------------------------
113 # Model training
114 # ---------------------------------------
115
116 def create_model(look_back=1):
117 """Creates a compiled LSTM model."""
118 model = Sequential([
119 Input(shape=(look_back, 1)),
120 LSTM(4),
121 Dense(1)
122 ])
123 model.compile(loss='mean_squared_error', optimizer='adam')
124 return model
125
126 # Create and train the model
127 print("\nTraining LSTM model...")
128 model = create_model(look_back)
129 model.fit(train_ds, epochs=100, verbose=0)
130 print("Model training complete.")
131
132 # Predictions from the original, in-memory model
133 original_predictions = model.predict(testX)
134 print(f"Made baseline predictions. Shape: {original_predictions.shape}")
Out:
Training LSTM model...
Model training complete.
1/2 ━━━━━━━━━━━━━━━━━━━━ 0s 121ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 105ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 119ms/step
Made baseline predictions. Shape: (46, 1)
Let’s save in memory using standard tensorflow/keras serialization
143 # --------------------------------------------------
144 # Example I: Standard Tensorflow/Keras serialization
145 # --------------------------------------------------
146 print("\n--- Example I: Standard Keras .save() / .load_model() ---")
147 path_output_keras = Path('./outputs/main02/keras_model')
148 keras_model_path = path_output_keras / 'model.keras'
149 path_output_keras.mkdir(parents=True, exist_ok=True)
150
151 # Save the model in the recommended .keras format
152 model.save(keras_model_path)
153 print(f"Model saved to {keras_model_path}")
154
155 # Load the model back
156 loaded_keras_model = tf.keras.models.load_model(keras_model_path)
157 print("Model loaded successfully.")
158
159 # Verify predictions
160 keras_predictions = loaded_keras_model.predict(testX)
161 are_equal_keras = np.array_equal(original_predictions, keras_predictions)
162 print(f"==> [Keras] Are test set predictions equal? {are_equal_keras}")
Out:
--- Example I: Standard Keras .save() / .load_model() ---
Model saved to outputs\main02\keras_model\model.keras
Model loaded successfully.
1/2 ━━━━━━━━━━━━━━━━━━━━ 0s 276ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 109ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 122ms/step
==> [Keras] Are test set predictions equal? True
Let’s save in memory using keras .json + weights
170 # -----------------------------------------------------
171 # Example II: Serialisation using keras JSON + weights
172 # -----------------------------------------------------
173 print("\n--- Example II: Serialization using Keras JSON (architecture) + H5 (weights) ---")
174
175 from tensorflow.keras.models import model_from_json
176
177 path_output_jsonp = Path('./outputs/main02/jsonpickle_model')
178 path_output_arch = path_output_jsonp / 'model_architecture.json'
179 path_output_weights = path_output_jsonp / 'model_weights.weights.h5' # Use .h5 for weights
180 path_output_json_scaler = path_output_jsonp / 'scaler.json'
181 path_output_jsonp.mkdir(parents=True, exist_ok=True)
182
183 # --- SAVING ---
184 # 1. Save the model's architecture as a JSON string
185 model_json = model.to_json()
186 with open(path_output_arch, "w") as json_file:
187 json_file.write(model_json)
188
189 # 2. Save the model's weights
190 model.save_weights(path_output_weights)
191
192 # 3. Save the scaler (jsonpickle is fine for this simple object)
193 to_jsonpickle(scaler, path_output_json_scaler)
194 print("Model architecture, weights, and scaler saved successfully.")
195
196 # --- LOADING ---
197 # 1. Load the model architecture from the JSON file
198 with open(path_output_arch, 'r') as json_file:
199 loaded_model_json = json_file.read()
200 loaded_model = model_from_json(loaded_model_json)
201
202 # 2. Load the weights into the new model structure
203 loaded_model.load_weights(path_output_weights)
204 print("Model loaded from architecture and weights.")
205
206 # 3. CRUCIAL: Compile the model after loading
207 # The model must be compiled before you can use it.
208 loaded_model.compile(loss='mean_squared_error', optimizer='adam')
209
210 # --- VERIFYING ---
211 # Now the loaded_model is a proper Keras model and has the .predict method
212 json_predictions = loaded_model.predict(testX)
213 are_equal_json = np.array_equal(original_predictions, json_predictions)
214 print(f"==> [Keras JSON] Are test set predictions equal? {are_equal_json}")
Out:
--- Example II: Serialization using Keras JSON (architecture) + H5 (weights) ---
Model architecture, weights, and scaler saved successfully.
Model loaded from architecture and weights.
WARNING:tensorflow:5 out of the last 5 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x000001916DFB8E00> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
1/2 ━━━━━━━━━━━━━━━━━━━━ 0s 120ms/stepWARNING:tensorflow:6 out of the last 6 calls to <function TensorFlowTrainer.make_predict_function.<locals>.one_step_on_data_distributed at 0x000001916DFB8E00> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 109ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 122ms/step
==> [Keras JSON] Are test set predictions equal? True
Let’s save in memory using readable .txt files.
222 # -----------------------------------------------------
223 # Example III: Serialization using readable .txt files
224 # -----------------------------------------------------
225 print("\n--- Example III: Serialization using readable .txt files ---")
226
227 def to_txt(weights, path):
228 """Saves model weights and their shapes to a directory of .txt files."""
229 path = Path(path)
230 # Save shapes
231 shapes = [e.shape for e in weights]
232 with open(path / 'shapes.txt', 'w') as f:
233 f.write(str(shapes))
234 # Save weights
235 for i, e in enumerate(weights):
236 # Flatten array to save, will be reshaped on load
237 np.savetxt(path / f'weights_layer_{i}.txt', e.flatten())
238
239 def from_txt(path):
240 """Loads weights from a directory of .txt files."""
241 path = Path(path)
242 # Load shapes from shapes.txt
243 with open(path / 'shapes.txt', 'r') as f:
244 shapes = eval(f.read())
245 # Load weights and reshape
246 weights = []
247 for i, shape in enumerate(shapes):
248 w = np.loadtxt(path / f'weights_layer_{i}.txt')
249 weights.append(w.reshape(shape))
250 return weights
251
252 # Create folder
253 path_output_txt = Path('./outputs/main02/txt_model')
254 path_output_txt.mkdir(parents=True, exist_ok=True)
255
256 # Get weights from the original model and save them
257 original_weights = model.get_weights()
258 to_txt(original_weights, path=path_output_txt)
259 print(f"Model weights saved to text files in {path_output_txt}")
260
261 # Load the weights from the text files
262 loaded_txt_weights = from_txt(path=path_output_txt)
263 print("Weights loaded successfully from text files.")
264
265 # To use these weights, we must create an identical model structure
266 txt_model = create_model(look_back)
267 txt_model.set_weights(loaded_txt_weights)
268 print("New model created and weights have been set.")
269
270 # Verify predictions
271 txt_predictions = txt_model.predict(testX)
272 are_equal_txt = np.array_equal(original_predictions, txt_predictions)
273 print(f"==> [manual .txt] Are test set predictions equal? {are_equal_txt}")
Out:
--- Example III: Serialization using readable .txt files ---
Model weights saved to text files in outputs\main02\txt_model
Weights loaded successfully from text files.
New model created and weights have been set.
1/2 ━━━━━━━━━━━━━━━━━━━━ 0s 120ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 113ms/step
2/2 ━━━━━━━━━━━━━━━━━━━━ 0s 126ms/step
==> [manual .txt] Are test set predictions equal? True
Total running time of the script: ( 0 minutes 24.096 seconds)