├── Architecture.png ├── Data_Download.py ├── README.md ├── ResCNN_LSTM.py ├── SV.png ├── SWE.png ├── Site Map.png ├── Untitled Diagram.drawio └── project_data.pkl /Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zherbz/EncoderDecoder/3f8544e93dd851a5261ff0d038fe8c9f1b573efd/Architecture.png -------------------------------------------------------------------------------- /Data_Download.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Jul 2 08:50:39 2020 4 | 5 | @author: Zachary 6 | """ 7 | 8 | # Load required libraries 9 | from climata.snotel import StationDailyDataIO 10 | from climata.snotel import StationIO 11 | import pandas as pd 12 | import numpy as np 13 | from functools import reduce 14 | import time 15 | import datetime 16 | import pickle 17 | 18 | # Define function for accessing NRCS API and downloading individual time series data files 19 | def getNRCS(station_id, param_id, nyears, frequency): 20 | ndays, yesterday = 365*nyears, datetime.date.today() 21 | datelist = pd.date_range(end=yesterday, periods=ndays).tolist() 22 | 23 | data = StationDailyDataIO( 24 | start_date=datelist[0], 25 | end_date=datelist[-1], 26 | station=station_id, 27 | parameter=param_id, 28 | ) 29 | if len(data.data) == 0: 30 | return print('The data source is empty for this location') 31 | temp = pd.DataFrame(data.data[0]['data'].data) 32 | df = pd.DataFrame(temp['value'], columns = ['value']) 33 | df.index = pd.to_datetime(temp['date']) 34 | df.index.name = 'Date' 35 | if df.index[-1].year != datetime.date.today().year: 36 | return print('Either today is new years, or the gap in data is too large') 37 | if df.index[0].year > 1990: 38 | return print('The starting year in the time series is too recent') 39 | df.columns = [station_id] 40 | missing_data = len(df)-len(df.dropna()) 41 | if missing_data > 100: 42 | return print('Today is definietly not new years, but the gap in the data is still too large') 43 | print('Missing data points:', missing_data) 44 | if missing_data > 0: 45 | new = pd.DataFrame() 46 | cols = df.columns 47 | interp_df = [new.assign(Interpolant = df[i].interpolate(method='time')) for i in cols][0] 48 | interp_df.columns = [station_id] 49 | resampled_df = interp_df.resample(frequency).mean() 50 | return resampled_df 51 | else: 52 | resampled_df = df.resample(frequency).mean() 53 | return resampled_df 54 | 55 | # Define function for performing a bulk download of all time series files from NRCS API based on a datatype (e.g. snow water equivalent (SWE)) 56 | def bulk_download(parameter, years, frequency): 57 | # Download snow water equivalent data across central and northern Utah 58 | stations = StationIO(state = 'UT', parameter = parameter, min_latitude = 40.3, max_latitude = 40.8, min_longitude = -111.90, max_longitude = -110.45) 59 | station_ids = [stations.data[i]['stationTriplet'] for i in range(len(stations))] 60 | station_names = [stations.data[i]['name'] for i in range(len(stations))] 61 | dflist = [getNRCS(station_ids[i], parameter, years, frequency) for i in range(len(stations)) if type(getNRCS(station_ids[i], parameter, years, frequency)) != type(None)] 62 | df = reduce(lambda x, y: pd.merge(x, y, on = 'Date'), dflist) 63 | # Populate metadata 64 | metadata = pd.DataFrame(station_ids) 65 | metadata.columns = ['id'] 66 | metadata['name'] = station_names 67 | metadata['lat'] = [stations.data[i]['latitude'] for i in range(len(stations))] 68 | metadata['lng'] = [stations.data[i]['longitude'] for i in range(len(stations))] 69 | metadata = metadata[metadata['id'].isin(df.columns)] 70 | df.columns = metadata['name'] 71 | return df, metadata 72 | # %% 73 | # Define object for Upper Stillwater Reservoir storage volume (ac-ft) time series data 74 | sv = getNRCS('09278000:UT:BOR', 'RESC', 31, 'W') 75 | sv.columns = ['Upper Stillwater'] 76 | # Keep only values from 1990 to the present date 77 | sv = sv[sv.index.year >= 1990] 78 | # Define object for all SWE monitoring stations surrounding Upper Stillwater Reservoir 79 | swe, swe_metadata = bulk_download('WTEQ', 31, 'W') 80 | # %% 81 | # Combine SV and SWE together into single dataframe 82 | data = reduce(lambda x, y: pd.merge(x, y, left_index = True, right_index = True), [sv, swe]) 83 | # Save dataframe object as pkl file to be quickly loaded in future training sessions 84 | pickle_out = open("project_data.pkl","wb") 85 | pickle.dump(data, pickle_out) 86 | # %% 87 | # Load pkl file back into work session 88 | # Note: check to make sure the file path location is correct 89 | loaded_data = pickle.load(open(r'C:\Users\Zachary\Documents\Project Data\project_data.pkl', 'rb')) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-step Reservoir Storage Volume Forecasting Using Deep Learning 2 | We propose a new multi-step reservoir forecasting approach based on a hybrid residual CNN-LSTM deep learning Encoder-Decoder algorithm. 3 | The proposed approach offers a three-month, weekly averaged prediction of reservoir storage volume based on historical snow water equivalent (SWE) during the runoff season from April through June each year. 4 | 5 | # Network Architecture 6 | ![Architecture](https://github.com/zherbz/EncoderDecoder/blob/master/Architecture.png) 7 | 8 | # Dependencies 9 | * Python 3 10 | * Climata 11 | * Tensorflow 12 | * Matplotlib 13 | * Sklearn 14 | * Seaborn 15 | * Datetime 16 | * Pandas 17 | * Numpy 18 | 19 | # Site Map: Upper Stillwater Reservoir 20 | This study focuses on the Upper Stillwater reservoir located at the top of the Central Utah Water Conservancy District’s (CUWCD) collection system in the Uinta Mountains. CUWCD is one of Utah's four largest specialty water districts that provides potable and secondary water to various water associations, conservancy districts, irrigation companies, and local residents. It spans eight counties with over \$3.5 billion in infrastructure. There are currently ten lakes/reservoirs maintained and operated by CUWCD that house non-potable water in excess of 1.6 million ac-ft. The storage levels for these reservoirs act as a barometer for the state’s water resources and provide insight for how to appropriately prepare for future water usage. The figure below shows Upper Stillwater (located in the middle of the figure) surrounded by a network of snow telemetry monitoring sites. 21 | ![Site Map](https://github.com/zherbz/EncoderDecoder/blob/master/Site%20Map.png) 22 | 23 | # Training Target: Reservoir Storage Volume (ac-ft) 24 | ![SV](https://github.com/zherbz/EncoderDecoder/blob/master/SV.png) 25 | 26 | # Training Features: Snow Water Equivalent (in) 27 | ![SWE](https://github.com/zherbz/EncoderDecoder/blob/master/SWE.png) 28 | -------------------------------------------------------------------------------- /ResCNN_LSTM.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Oct 22 08:31:38 2019 4 | @author: Zachary 5 | """ 6 | import pickle 7 | import pandas as pd 8 | import numpy as np 9 | from functools import reduce 10 | from sklearn.preprocessing import MinMaxScaler 11 | from tensorflow.keras.layers import Dense 12 | from tensorflow.keras.layers import Flatten 13 | from tensorflow.keras.layers import LSTM 14 | from tensorflow.keras.layers import RepeatVector 15 | from tensorflow.keras.layers import TimeDistributed 16 | from tensorflow.keras.layers import Conv1D 17 | from tensorflow.keras.layers import MaxPooling1D 18 | from tensorflow.keras.layers import ReLU 19 | from tensorflow.keras.layers import Add 20 | from tensorflow.keras import Input 21 | from tensorflow.keras import Model 22 | from tensorflow.keras.callbacks import EarlyStopping 23 | from sklearn.metrics import explained_variance_score 24 | from sklearn.metrics import mean_absolute_error 25 | from sklearn.metrics import mean_squared_error 26 | from sklearn.metrics import median_absolute_error 27 | from sklearn.metrics import r2_score 28 | import time 29 | from datetime import timedelta 30 | import matplotlib.pyplot as plt 31 | import seaborn as sns 32 | from matplotlib.lines import Line2D 33 | sns.set_style("darkgrid") 34 | 35 | # calculate the absolute percent error for each forecast step 36 | def mape(y_test, y_pred): 37 | y_test, y_pred = np.array(y_test), np.array(y_pred) 38 | return np.mean(np.abs((y_test - y_pred) / y_test))*100, np.abs((y_test - y_pred) / y_test)*100 39 | 40 | def sliding_window_input(df, input_length, idx): 41 | window = df.values[idx:input_length+idx,:] 42 | return window 43 | 44 | def sliding_window_output(df, input_length, output_length, idx): 45 | window = df.values[input_length+idx:input_length+output_length+idx] 46 | return window 47 | 48 | def model_configs(): 49 | epochs = [100] 50 | batch_size = [32] 51 | n_nodes1 = [64] 52 | n_nodes2 = [32] 53 | filter1 = [16] 54 | filter2 = [32] 55 | kernel_size = [6] 56 | configs = [] 57 | for a in epochs: 58 | for b in batch_size: 59 | for c in n_nodes1: 60 | for d in n_nodes2: 61 | for e in filter1: 62 | for f in filter2: 63 | for g in kernel_size: 64 | cfg = [a,b,c,d,e,f,g] 65 | configs.append(cfg) 66 | print('Total configs: %d' % len(configs)) 67 | return configs 68 | 69 | def build_model(data, config, output_length, X_test, input_length, index, repeats): 70 | print('Run:', str(index), '/', str(repeats)) 71 | # start time 72 | start = time.time() 73 | print('Configuration:', config) 74 | # define parameters 75 | epochs, batch_size, n_nodes1, n_nodes2, filter1, filter2, kernel_size = config 76 | # convert data to supervised learning environment 77 | X_train = np.stack([sliding_window_input(data, input_length, i) for i in range(len(data)-input_length)])[:-output_length] 78 | y_train = np.stack([sliding_window_output(data['Upper Stillwater'], input_length, output_length, i) for i in range(len(data)-(input_length+output_length))]) 79 | y_train = y_train.reshape(y_train.shape[0], y_train.shape[1], 1) 80 | print('Feature Training Tensor (samples, timesteps, features):', X_train.shape) 81 | print('Target Training Tensor (samples, timesteps, features):', y_train.shape) 82 | # define parameters 83 | n_timesteps, n_features, n_outputs = X_train.shape[1], X_train.shape[2], y_train.shape[1] 84 | # define residual CNN-LSTM 85 | visible1 = Input(shape = (n_timesteps,n_features)) 86 | 87 | model = Conv1D(filter1, kernel_size, padding = 'causal')(visible1) 88 | residual1 = ReLU()(model) 89 | model = Conv1D(filter1, kernel_size, padding = 'causal')(residual1) 90 | model = Add()([residual1, model]) 91 | model = ReLU()(model) 92 | 93 | model = Conv1D(filter1, kernel_size, padding = 'causal')(model) 94 | residual2 = ReLU()(model) 95 | model = Conv1D(filter1, kernel_size, padding = 'causal')(residual2) 96 | model = Add()([residual2, model]) 97 | model = ReLU()(model) 98 | model = MaxPooling1D()(model) 99 | 100 | model = Conv1D(filter2, kernel_size, padding = 'causal')(model) 101 | residual3 = ReLU()(model) 102 | model = Conv1D(filter2, kernel_size, padding = 'causal')(residual3) 103 | model = Add()([residual3, model]) 104 | model = ReLU()(model) 105 | 106 | model = Conv1D(filter2, kernel_size, padding = 'causal')(model) 107 | residual4 = ReLU()(model) 108 | model = Conv1D(filter2, kernel_size, padding = 'causal')(residual4) 109 | model = Add()([residual4, model]) 110 | model = ReLU()(model) 111 | model = MaxPooling1D()(model) 112 | 113 | model = Conv1D(n_nodes1, kernel_size, padding = 'causal')(model) 114 | residual5 = ReLU()(model) 115 | model = Conv1D(n_nodes1, kernel_size, padding = 'causal')(residual5) 116 | model = Add()([residual5, model]) 117 | model = ReLU()(model) 118 | 119 | model = Conv1D(n_nodes1, kernel_size, padding = 'causal')(model) 120 | residual6 = ReLU()(model) 121 | model = Conv1D(n_nodes1, kernel_size, padding = 'causal')(residual6) 122 | model = Add()([residual6, model]) 123 | model = ReLU()(model) 124 | model = MaxPooling1D()(model) 125 | 126 | model = Flatten()(model) 127 | model = RepeatVector(n_outputs)(model) 128 | 129 | model = LSTM(n_nodes1, activation='relu', return_sequences=True)(model) 130 | model = LSTM(n_nodes1, activation='relu', return_sequences=True)(model) 131 | model = LSTM(n_nodes1, activation='relu', return_sequences=True)(model) 132 | model = LSTM(n_nodes1, activation='relu', return_sequences=True)(model) 133 | 134 | dense = TimeDistributed(Dense(n_nodes2, activation='relu'))(model) 135 | output = TimeDistributed(Dense(1))(dense) 136 | model = Model(inputs = visible1, outputs = output) 137 | model.compile(loss='mse', optimizer='adam') 138 | #model.summary() 139 | # fit network 140 | es = EarlyStopping(monitor='loss', mode='min', patience = 10) 141 | history = model.fit(X_train, y_train, epochs=epochs, batch_size=batch_size, verbose=0, validation_split=0.2, callbacks = [es]) 142 | # test model against hold-out set 143 | y_pred = model.predict(X_test) 144 | print('\n Elapsed time:', round((time.time() - start)/60,3), 'minutes') 145 | return y_pred, history 146 | 147 | def get_stats(y_pred, y_test): 148 | print('Mean Absolute Error:', round((mean_absolute_error(y_test, y_pred)/np.mean(y_test))*100,3), '%') 149 | print('Root Mean Squared Error:', round(((mean_squared_error(y_test, y_pred)**0.5)/np.mean(y_test))*100,3), '%') 150 | print('Median Absolute Error:', round((median_absolute_error(y_test, y_pred)/np.mean(y_test))*100,3), '%') 151 | print('NSE:', round(r2_score(y_test, y_pred), 3)) 152 | print('Explained Variance:', round(explained_variance_score(y_test, y_pred),3), '\n') 153 | 154 | def get_stats_df(y_pred, y_test): 155 | Meanae = str(round((mean_absolute_error(y_test, y_pred)/np.mean(y_test))*100,3)) 156 | RMSE = str(round(((mean_squared_error(y_test, y_pred)**0.5)/np.mean(y_test))*100,3)) 157 | Medae = str(round((median_absolute_error(y_test, y_pred)/np.mean(y_test))*100,3)) 158 | NSE = str(round(r2_score(y_test, y_pred), 3)) 159 | Expvar = str(round(explained_variance_score(y_test, y_pred),3)) 160 | stats = [[Meanae], [RMSE], [Medae], [NSE], [Expvar]] 161 | index = ['Mean Absolute Error (%)', 'Root Mean Squared Error (%)', 'Median Absolute Error (%)', 'NSE', 'Explained Variance'] 162 | return stats, index 163 | 164 | def ape(true, test): 165 | value = (abs(true - test)/true)*100 166 | return np.array(value) 167 | 168 | def repeat_runs(repeats, data, cfg, output_length, X_test, input_length, y_test): 169 | print('Start of Training Period:', data.index[0]) 170 | print('End of Training Period:', data.index[-1], '\n') 171 | output = [build_model(data, cfg, output_length, X_test, input_length, i, repeats) for i in range(1, repeats+1)] 172 | y_pred = [sv_scaler.inverse_transform(output[i][0][0].reshape(-1,1)) for i in range(len(output))] 173 | 174 | loss = [pd.DataFrame(output[i][1].history['loss']) for i in range(len(output))] 175 | val_loss = [pd.DataFrame(output[i][1].history['val_loss']) for i in range(len(output))] 176 | max_epochs = max([len(loss[i]) for i in range(len(loss))]) 177 | loss = [loss[i].values for i in range(len(loss))] 178 | val_loss = [val_loss[i].values for i in range(len(val_loss))] 179 | for i in range(len(loss)): 180 | if len(loss[i]) == max_epochs: 181 | loss[i] = loss[i] 182 | else: 183 | while len(loss[i]) < max_epochs: 184 | loss[i] = np.append(loss[i], np.nan) 185 | val_loss[i] = np.append(val_loss[i], np.nan) 186 | loss[i] = pd.DataFrame(loss[i]) 187 | val_loss[i] = pd.DataFrame(val_loss[i]) 188 | loss_df = reduce(lambda x, y: pd.merge(x, y, left_index = True, right_index = True), loss) 189 | val_loss_df = reduce(lambda x, y: pd.merge(x, y, left_index = True, right_index = True), val_loss) 190 | 191 | loss_avg = loss_df.mean(axis = 1) 192 | loss_avg.name = 'loss_avg' 193 | val_loss_std = val_loss_df.std(axis = 1) 194 | val_loss_std.name = 'val_loss_std' 195 | val_loss_avg = val_loss_df.mean(axis = 1) 196 | val_loss_avg.name = 'val_loss_avg' 197 | upper_val = pd.Series(val_loss_avg.values + 1.96*val_loss_std.values/(repeats**(0.5))) 198 | upper_val.name = 'upper_val' 199 | lower_val = pd.Series(val_loss_avg.values - 1.96*val_loss_std.values/(repeats**(0.5))) 200 | lower_val.name = 'lower_val' 201 | history = reduce(lambda x, y: pd.merge(x, y, left_index = True, right_index = True), [loss_avg, val_loss_avg, upper_val, lower_val]) 202 | history.columns = ['loss_avg', 'val_loss_avg', 'upper_val', 'lower_val'] 203 | 204 | plt.figure(figsize=(20,10)) 205 | plt.plot(history.index, history.loss_avg.values, color='Blue', linewidth=2, linestyle='solid', label="Training Loss (80%)") 206 | plt.plot(history.index, history.val_loss_avg.values, color='Orange', linewidth=2, linestyle='solid', label="Validation Loss (20%)") 207 | plt.fill_between(history.index, history.upper_val, history.lower_val, color='Orange', alpha=.15, label = '95% Confidence Interval') 208 | plt.title(str(y_test.index[0].year)+" Model Learning Curve", fontsize = 18) 209 | plt.ylabel("Loss: Mean Squared Error", fontsize=14) 210 | plt.xlabel("Experiences", fontsize=14) 211 | plt.legend(fontsize=14, loc = 'upper left'); 212 | plt.savefig(fname = str(y_test.index[0].year)+'_lc.pdf',bbox_inches = 'tight'); 213 | 214 | df = pd.DataFrame(np.concatenate(y_pred, axis = 1), index = y_test.index.strftime('%B %d')) 215 | spills = np.stack([sv.max() for i in range(len(y_test))]) 216 | future_idx = range(1, len(y_test)+1) 217 | x_labels = y_test.index 218 | x_labels = x_labels.strftime('%B %d') 219 | [get_stats(y_test.values, y_pred[i]) for i in range(len(output))] 220 | 221 | q1, q3 = df.quantile(q = 0.25, axis = 1), df.quantile(q = 0.75, axis = 1) 222 | iqr = q3 - q1 223 | upper_bnd = q3 + 1.5*iqr 224 | lower_bnd = q1 - 1.5*iqr 225 | 226 | filtered_avg = np.stack([df.iloc[i][(df.iloc[i] < upper_bnd[i]) & (df.iloc[i] > lower_bnd[i])].mean() for i in range(len(df))]) 227 | filtered_std = np.stack([df.iloc[i][(df.iloc[i] < upper_bnd[i]) & (df.iloc[i] > lower_bnd[i])].std() for i in range(len(df))]) 228 | upper = filtered_avg + 1.96*filtered_std/(repeats**(0.5)) 229 | lower = filtered_avg - 1.96*filtered_std/(repeats**(0.5)) 230 | cellText, rows = get_stats_df(y_test.values, filtered_avg.reshape(-1,)) 231 | 232 | plt.figure(figsize=(20,10)) 233 | plt.boxplot(df) 234 | plt.xticks([i for i in future_idx],x_labels) 235 | plt.plot(future_idx, filtered_avg, color='Red', linewidth=2, linestyle='solid', label="Forecasted") 236 | plt.plot(future_idx, y_test.values, color='Green', linewidth=2, linestyle='solid', label="Actual") 237 | plt.plot(future_idx, spills, color = 'black', linewidth = 2, linestyle='dashed', label="Spill Limit") 238 | plt.fill_between(future_idx, upper, lower, color='k', alpha=.15, label = '95% Confidence Interval') 239 | plt.table(cellText = cellText, rowLabels = rows, colLabels = ['Stats'], bbox = [0.2,0.4,0.1,0.5]) 240 | plt.title(str(y_test.index[0].year)+" Upper Stillwater Reservoir", fontsize = 18) 241 | plt.ylabel("Storage Volume (ac-ft)", fontsize=14) 242 | plt.xlabel("Future Time", fontsize=14) 243 | plt.legend(fontsize=14, loc = 'upper left'); 244 | plt.savefig(fname = str(y_test.index[0].year)+'_fc.pdf',bbox_inches = 'tight'); 245 | 246 | errors = np.stack(np.array([ape(y_test.values[i], df.mean(axis = 1).values[i]) for i in range(len(df))])) 247 | upper = np.stack(np.array([ape(y_test.values[i], df.mean(axis = 1).values[i] + (df.std(axis = 1).values[i]*1.96)/(repeats**0.5)) for i in range(len(df))])) 248 | lower = np.stack(np.array([ape(y_test.values[i], df.mean(axis = 1).values[i] - (df.std(axis = 1).values[i]*1.96)/(repeats**0.5)) for i in range(len(df))])) 249 | errors_df = pd.DataFrame(lower, columns = ['Lower Limit']) 250 | errors_df['Average'] = errors 251 | errors_df['Upper Limit'] = upper 252 | errors_df.index = df.index 253 | errors_df.plot(kind = 'bar', figsize = [20,10], title = 'Absolute Percent Error (%)'); 254 | return df, history 255 | # %% 256 | data = pickle.load(open(r'C:\Users\Documents\Project Data\stillwater_data.pkl', 'rb')) 257 | sv = data['Upper Stillwater'] 258 | swe = data[data.columns[1:]] 259 | # Declare model input parameters 260 | input_length, output_length, repeats, year = 20, 15, 30, 2019 261 | 262 | # Assign range of values for model to be tested on 263 | test_output = sv[(sv.index.year == year) & (sv.index.month >= 4)][:output_length] 264 | 265 | # Assign range of values for model input during testing 266 | test_input_start = test_output.index[0] - timedelta(weeks = input_length) 267 | test_input = data[data.index >= test_input_start][:input_length] 268 | test_input_end = test_input.index[-1] 269 | training = data[data.index < test_input_start] 270 | 271 | # Temporarily combine training and test_input back together for scaling purposes 272 | data_for_scaling = pd.concat([training, test_input]) 273 | 274 | # Scale target vector seperately from additional features 275 | sv_scaler = MinMaxScaler(feature_range=(0, 1)) 276 | sv_scaled = pd.DataFrame(sv_scaler.fit_transform(data_for_scaling['Upper Stillwater'].values.reshape(-1,1)), index = data_for_scaling.index) 277 | variable_scaler = MinMaxScaler(feature_range=(0, 1)) 278 | variable_scaled = pd.DataFrame(variable_scaler.fit_transform(data_for_scaling[data_for_scaling.columns[1:]].values), index = data_for_scaling.index) 279 | variable_scaled.columns = data.columns[1:] 280 | 281 | # Combine scaled data back into single dataframe 282 | scaled_data = pd.merge(sv_scaled, variable_scaled, left_index = True, right_index = True) 283 | scaled_data.columns = data_for_scaling.columns 284 | 285 | # Assign data to be used for model training 286 | training = scaled_data[:-input_length] 287 | training['Upper Stillwater'].plot(figsize = [20,10], title = 'Target Time Series: Upper Stillwater Reservoir Storage Volume (ac-ft)'); 288 | print('\n Selected Starting Point for Training:', training.index[0]) 289 | print('\n Selected Ending Point for Training:', training.index[-1]) 290 | 291 | # Assign data to be used for model test output (unbiased to model and scaler) 292 | y_test = test_output 293 | plt.figure(figsize=(20,10)) 294 | print('\n Selected Test Output: \n', y_test.plot(figsize = [20,10], title = 'Target Test Output')); 295 | 296 | # Define snow water equivalent input for model 297 | swe_input = variable_scaled[swe.columns][-input_length:] 298 | 299 | fig, ax1 = plt.subplots() 300 | # Plot the scaled swe input for the model 301 | ax = swe_input.plot(kind='line', figsize = [20,15], linewidth = 3, ms = 12, fontsize = 16, ax = ax1) 302 | markers = 2*[m for m, func in Line2D.markers.items() if func != 'nothing' and m not in Line2D.filled_markers] 303 | for i, line in enumerate(ax.get_lines()): 304 | line.set_marker(markers[i]) 305 | ax.legend(ax.get_lines(), swe_input.columns, loc='best', fontsize = 16) 306 | #plt.show() 307 | swe_training = training[swe.columns] 308 | swe_training = pd.merge(sv_scaled, swe_training, left_index = True, right_index = True) 309 | # Plot storage volume input for model 310 | sv_input = sv_scaled[-input_length:] 311 | sv_input.plot(figsize = [20,15], legend = False, fontsize = 16) 312 | 313 | # Structure the model test inputs as 3D tensors (unbiased to model) 314 | scaled_test_input = pd.merge(sv_input, swe_input, left_index = True, right_index = True) 315 | X_test = scaled_test_input.values.reshape(1,scaled_test_input.shape[0],scaled_test_input.shape[1]) 316 | print('Testing Input Tensor (samples, timesteps, features):', X_test.shape) 317 | # %% 318 | # Fit the model to the data repeatedly 319 | model_repeats = repeat_runs(repeats, training, model_configs()[0], output_length, X_test, input_length, y_test) 320 | # %% 321 | model_repeats[0].to_excel(str(year)+'_fc.xlsx') 322 | model_repeats[1].to_excel(str(year)+'_lc.xlsx') -------------------------------------------------------------------------------- /SV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zherbz/EncoderDecoder/3f8544e93dd851a5261ff0d038fe8c9f1b573efd/SV.png -------------------------------------------------------------------------------- /SWE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zherbz/EncoderDecoder/3f8544e93dd851a5261ff0d038fe8c9f1b573efd/SWE.png -------------------------------------------------------------------------------- /Site Map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zherbz/EncoderDecoder/3f8544e93dd851a5261ff0d038fe8c9f1b573efd/Site Map.png -------------------------------------------------------------------------------- /Untitled Diagram.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /project_data.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zherbz/EncoderDecoder/3f8544e93dd851a5261ff0d038fe8c9f1b573efd/project_data.pkl --------------------------------------------------------------------------------