Skip to content

Commit 8e5cabe

Browse files
committed
CALCLOUD-474 Add script to calculate models for memory and runtime
1 parent 225de69 commit 8e5cabe

1 file changed

Lines changed: 266 additions & 0 deletions

File tree

scripts/manual_predict.py

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import boto3
2+
import joblib
3+
import matplotlib.pyplot as plt
4+
import numpy as np
5+
import pandas as pd
6+
from sklearn.ensemble import RandomForestRegressor, HistGradientBoostingRegressor
7+
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
8+
from sklearn.model_selection import train_test_split
9+
10+
11+
def get_dynamo_items(number_items):
12+
"""Get data from dynamo"""
13+
session = boto3.Session(profile_name="aws-hst-repro-ops-Developer")
14+
dynamodb = session.resource("dynamodb")
15+
table = dynamodb.Table("calcloud-model-ops")
16+
items = []
17+
response = table.scan(Limit=number_items)
18+
items.extend(response["Items"])
19+
20+
while "LastEvaluatedKey" in response and len(items) < number_items:
21+
response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"], Limit=number_items)
22+
items.extend(response["Items"])
23+
24+
items = items[:number_items]
25+
return items
26+
27+
28+
input_keys = ("n_files", "total_mb", "drizcorr", "pctecorr", "crsplit", "subarray", "detector", "dtype", "instr")
29+
prediction_keys = ("mem_pred", "wall_pred", "bin_pred", "wc_mean", "wc_err", "wc_std", "x_mean", "x_files")
30+
actual_keys = ("memory", "wallclock", "mem_bin")
31+
32+
33+
def convert_elements_to_numeric_values(items):
34+
"""Clean up the data, converting strings to numeric values"""
35+
for item in items:
36+
item["inputs"] = {}
37+
item["predictions"] = {}
38+
item["actuals"] = {}
39+
for key in input_keys:
40+
value = item.pop(key)
41+
try:
42+
item["inputs"][key] = int(value)
43+
except ValueError:
44+
item["inputs"][key] = np.float64(value)
45+
for key in prediction_keys:
46+
value = item.pop(key, None)
47+
if value is not None:
48+
item["predictions"][key] = np.float64(value)
49+
for key in actual_keys:
50+
item["actuals"][key] = np.float64(item.pop(key))
51+
52+
53+
def calculate_memory_model(data):
54+
"""Calculate the model for memory, analyze result, present actual vs. predicted"""
55+
df = pd.DataFrame(data)
56+
original_df = pd.DataFrame(data)
57+
58+
df = pd.get_dummies(
59+
original_df,
60+
columns=["instr", "dtype", "detector", "drizcorr", "pctecorr", "crsplit", "subarray"],
61+
drop_first=True,
62+
)
63+
64+
X = df.drop(columns=["wallclock", "memory"])
65+
y = df["memory"]
66+
67+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
68+
69+
# Memory model
70+
memory_model = RandomForestRegressor(
71+
n_estimators=200,
72+
random_state=42,
73+
)
74+
memory_model.fit(X_train, y_train)
75+
actual_memory = y_test
76+
predicted_memory = memory_model.predict(X_test)
77+
78+
memory_r2 = r2_score(y_test, predicted_memory)
79+
memory_mae = mean_absolute_error(y_test, predicted_memory)
80+
memory_rmse = np.sqrt(mean_squared_error(y_test, predicted_memory))
81+
memory_mape = np.mean(np.abs((y_test - predicted_memory) / y_test)) * 100
82+
83+
print("\nMemory")
84+
print(f" R² = {memory_r2:.3f}")
85+
print(f" MAE = {memory_mae:.2f}")
86+
print(f" RMSE = {memory_rmse:.2f}")
87+
print(f" MAPE = {memory_mape:.1f}%")
88+
print("")
89+
90+
# Save the model
91+
joblib.dump(memory_model, "scripts/memory_rf_model.pkl")
92+
93+
# To use:
94+
# rf_model = joblib.load("scripts/memory_rf_model.pkl")
95+
# prediction = np.expm1(rf_model.predict(feature_df))
96+
97+
plot_actual_vs_predicted(
98+
actual_memory,
99+
predicted_memory,
100+
xlabel="Actual Memory",
101+
ylabel="Predicted Memory",
102+
title="Memory Predicted vs Actual",
103+
)
104+
105+
106+
def plot_actual_vs_predicted(actual, predicted, xlabel, ylabel, title, use_log_log_scale=False):
107+
"""Plot actual vs. predicted"""
108+
plt.figure(figsize=(8, 8))
109+
110+
plt.scatter(actual, predicted, alpha=0.6, edgecolors="none")
111+
112+
if use_log_log_scale:
113+
plt.xscale("log")
114+
plt.yscale("log")
115+
116+
# Perfect prediction line
117+
min_val = min(actual.min(), predicted.min())
118+
max_val = max(actual.max(), predicted.max())
119+
120+
plt.plot([min_val, max_val], [min_val, max_val], "r--", linewidth=2, label="Perfect prediction")
121+
122+
plt.xlabel(xlabel)
123+
plt.ylabel(ylabel)
124+
plt.title(title)
125+
plt.legend()
126+
plt.grid(True, alpha=0.3)
127+
128+
plt.tight_layout()
129+
plt.show()
130+
131+
132+
def calculate_wallclock_model(data):
133+
"""Calculate the model for wallclock, analyze result, present actual vs. predicted"""
134+
df = pd.DataFrame(data)
135+
original_df = pd.DataFrame(data)
136+
137+
df = pd.get_dummies(
138+
original_df,
139+
columns=["instr", "dtype", "detector", "drizcorr", "pctecorr", "crsplit", "subarray"],
140+
drop_first=True,
141+
)
142+
df["log_total_mb"] = np.log1p(df["total_mb"])
143+
df["log_n_files"] = np.log1p(df["n_files"])
144+
145+
X = df.drop(columns=["wallclock", "memory", "total_mb", "n_files"])
146+
y = np.log1p(df["wallclock"])
147+
148+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
149+
150+
# Wallclock model
151+
wallclock_model = HistGradientBoostingRegressor(max_depth=5, learning_rate=0.05, max_iter=500, random_state=42)
152+
wallclock_model.fit(X_train, y_train)
153+
154+
log_pred = wallclock_model.predict(X_test)
155+
156+
predicted_wallclock = np.expm1(log_pred)
157+
actual_wallclock = np.expm1(y_test)
158+
159+
log_r2 = r2_score(y_test, log_pred)
160+
161+
mae = mean_absolute_error(y_test, log_pred)
162+
rmse = np.sqrt(mean_squared_error(y_test, log_pred))
163+
164+
ape = (np.abs(predicted_wallclock - actual_wallclock) / actual_wallclock) * 100
165+
mean_ape = np.mean(ape)
166+
median_ape = np.median(ape)
167+
ape_95 = np.percentile(ape, 95)
168+
ape_99 = np.percentile(ape, 99)
169+
170+
print("\nWallclock")
171+
print(f" Log-space R² = {log_r2:.3f}")
172+
print(f" Log-space MAE = {mae:.2f}")
173+
print(f" Log-space RMSE = {rmse:.2f}")
174+
print(f" Mean APE = {mean_ape:.1f}%")
175+
176+
print(f" Median APE = {median_ape:.1f}%")
177+
print(f" 95th pct APE = {ape_95:.1f}%")
178+
print(f" 99th pct APE = {ape_99:.1f}%")
179+
180+
joblib.dump({"model": wallclock_model, "columns": list(X.columns)}, "scripts/wallclock_model.pkl")
181+
182+
# To use:
183+
# saved = joblib.load("scripts/wallclock_model.pkl")
184+
# model = saved["model"]
185+
# prediction = np.expm1(model.predict(feature_df))
186+
187+
plot_actual_vs_predicted(
188+
actual_wallclock,
189+
predicted_wallclock,
190+
xlabel="Actual Wallclock",
191+
ylabel="Predicted Wallclock",
192+
title="Wallclock: Predicted vs Actual (Log Scale)",
193+
use_log_log_scale=True,
194+
)
195+
196+
197+
def calculate_models(items):
198+
"""Create and evaluate the models"""
199+
data = {}
200+
for input_key in input_keys:
201+
data[input_key] = [x["inputs"][input_key] for x in items]
202+
for output_key in ("wallclock", "memory"):
203+
data[output_key] = [x["actuals"][output_key] for x in items]
204+
calculate_memory_model(data)
205+
calculate_wallclock_model(data)
206+
207+
208+
def plot_bins(items):
209+
"""Plot memory and wallclock in bins to see distribution"""
210+
fig, axs = plt.subplots(1, 2, figsize=(18, 6), layout="constrained")
211+
212+
deciles_array = np.arange(10, 100, 10)
213+
214+
# Plot wallclock
215+
percent = 100
216+
cutoff = 0
217+
wallclock = [x["actuals"]["wallclock"] for x in items]
218+
wallclock = [x for x in wallclock if x > cutoff]
219+
deciles = np.percentile(wallclock, deciles_array)
220+
221+
label = f"{len(wallclock)} samples\n"
222+
if cutoff != 0:
223+
label += f"(wallclock > {cutoff} s)\n"
224+
label += f"D10: {deciles[0]:.2f}\n" f"D50 (Med): {deciles[4]:.2f}\n" f"D90: {deciles[8]:.2f}"
225+
axs[0].hist(wallclock, bins="auto", edgecolor="black", color="skyblue")
226+
title = "Wallclock Distribution"
227+
if percent != 100:
228+
title += f" (top {percent}%)"
229+
axs[0].set_title(title)
230+
axs[0].set_xlabel("Wallclock (s)")
231+
axs[0].set_ylabel("Frequency")
232+
axs[0].text(0.60, 0.95, label, transform=axs[0].transAxes, horizontalalignment="left", verticalalignment="top")
233+
234+
# Plot memory
235+
cutoff = 0
236+
memory = [x["actuals"]["memory"] for x in items]
237+
memory = [x for x in memory if x > cutoff]
238+
deciles = np.percentile(memory, deciles_array)
239+
240+
label = f"{len(memory)} samples\n"
241+
if cutoff != 0:
242+
label += f"(memory > {cutoff} GB)\n"
243+
label += f"D10: {deciles[0]:.2f}\n" f"D50 (Med): {deciles[4]:.2f}\n" f"D90: {deciles[8]:.2f}"
244+
axs[1].hist(memory, bins="auto", edgecolor="black", color="salmon")
245+
title = "Memory Distribution"
246+
if percent != 100:
247+
title += f" (top {percent}%)"
248+
axs[1].set_title(title)
249+
axs[1].set_xlabel("Memory (GB)")
250+
axs[1].text(0.60, 0.95, label, transform=axs[1].transAxes, horizontalalignment="left", verticalalignment="top")
251+
252+
# 5. Clean up layout spacing and display
253+
plt.tight_layout()
254+
plt.show()
255+
256+
257+
def main():
258+
items = get_dynamo_items(100000)
259+
convert_elements_to_numeric_values(items)
260+
261+
plot_bins(items)
262+
calculate_models(items)
263+
264+
265+
if __name__ == "__main__":
266+
main()

0 commit comments

Comments
 (0)