Skip to content

Commit 38cf328

Browse files
authored
Merge pull request #230 from winedarksea/dev
0.6.10
2 parents 3e6baff + 3b3d0b0 commit 38cf328

37 files changed

+336
-372
lines changed

TODO.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
* Series will largely be consistent in period, or at least up-sampled to regular intervals
1212
* The most recent data will generally be the most important
1313
* Forecasts are desired for the future immediately following the most recent data.
14+
* trimmed_mean to AverageValueNaive
1415

15-
# 0.6.9 🇺🇦 🇺🇦 🇺🇦
16-
* expanded regressor options for MultivariateRegression, NeuralForecast (currently only available directly, not from AutoTS class)
17-
* matse bug fix on all 0 history
16+
# 0.6.10 🇺🇦 🇺🇦 🇺🇦
17+
* assorted minor bug fixes
18+
* bug in mosaic model selection fixed
19+
* added crosshair_lite mosaic
1820

1921
### Unstable Upstream Pacakges (those that are frequently broken by maintainers)
2022
* Pytorch-Forecasting

autots/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from autots.models.cassandra import Cassandra
2727

2828

29-
__version__ = '0.6.9'
29+
__version__ = '0.6.10'
3030

3131
TransformTS = GeneralTransformer
3232

autots/evaluator/auto_model.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,6 +1470,7 @@ def TemplateWizard(
14701470
current_model_file: str = None,
14711471
mosaic_used=None,
14721472
force_gc: bool = False,
1473+
additional_msg: str = "",
14731474
):
14741475
"""
14751476
Take Template, returns Results.
@@ -1548,13 +1549,12 @@ def virtual_memory():
15481549
template_result.model_count += 1
15491550
if verbose > 0:
15501551
if validation_round >= 1:
1551-
base_print = (
1552-
"Model Number: {} of {} with model {} for Validation {}".format(
1553-
str(template_result.model_count),
1554-
template.shape[0],
1555-
model_str,
1556-
str(validation_round),
1557-
)
1552+
base_print = "Model Number: {} of {} with model {} for Validation {}{}".format(
1553+
str(template_result.model_count),
1554+
template.shape[0],
1555+
model_str,
1556+
str(validation_round),
1557+
str(additional_msg),
15581558
)
15591559
else:
15601560
base_print = (
@@ -1568,9 +1568,10 @@ def virtual_memory():
15681568
if verbose > 1:
15691569
print(
15701570
base_print
1571-
+ " with params {} and transformations {}".format(
1571+
+ " with params {} and transformations {}{}".format(
15721572
json.dumps(parameter_dict),
15731573
json.dumps(transformation_dict),
1574+
str(additional_msg),
15741575
)
15751576
)
15761577
else:

autots/evaluator/auto_ts.py

Lines changed: 110 additions & 298 deletions
Large diffs are not rendered by default.

autots/models/basics.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
sliding_window_view,
2020
chunk_reshape,
2121
)
22-
from autots.tools.percentile import nan_quantile
22+
from autots.tools.percentile import nan_quantile, trimmed_mean
2323
from autots.tools.fast_kalman import KalmanFilter, new_kalman_params
2424
from autots.tools.transform import (
2525
GeneralTransformer,
@@ -309,6 +309,12 @@ def fit(self, df, future_regressor=None):
309309
self.average_values = np.average(
310310
df_used.to_numpy(), axis=0, weights=weights
311311
)
312+
elif method == "trimmed_mean_20":
313+
self.average_values = trimmed_mean(df_used, percent=0.2, axis=0)
314+
elif method == "trimmed_mean_40":
315+
self.average_values = trimmed_mean(df_used, percent=0.4, axis=0)
316+
else:
317+
raise ValueError(f"method {method} not recognized")
312318
self.fit_runtime = datetime.datetime.now() - self.startTime
313319
self.lower, self.upper = historic_quantile(
314320
df_used, prediction_interval=self.prediction_interval
@@ -366,8 +372,10 @@ def get_new_params(self, method: str = 'random'):
366372
"Midhinge",
367373
"Weighted_Mean",
368374
"Exp_Weighted_Mean",
375+
"trimmed_mean_20",
376+
"trimmed_mean_40",
369377
],
370-
[0.3, 0.3, 0.01, 0.1, 0.4, 0.1],
378+
[0.3, 0.3, 0.01, 0.1, 0.4, 0.1, 0.05, 0.05],
371379
)[0]
372380

373381
return {

autots/models/cassandra.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,8 +2011,9 @@ def get_new_params(self, method='fast'):
20112011
'lstsq',
20122012
'linalg_solve',
20132013
'bayesian_linear',
2014+
'l1_positive',
20142015
],
2015-
[0.8, 0.15, 0.05],
2016+
[0.8, 0.15, 0.05, 0.01],
20162017
)[0]
20172018
recency_weighting = random.choices(
20182019
[None, 0.05, 0.1, 0.25, 0.5], [0.7, 0.1, 0.1, 0.1, 0.05]
@@ -2537,8 +2538,11 @@ def lstsq_minimize(X, y, maxiter=15000, cost_function="l1", method=None):
25372538
elif cost_function == "quantile":
25382539
cost_func = cost_function_quantile
25392540
elif cost_function == "l1_positive":
2540-
bounds = [(0, 14) for x in x0]
2541+
max_bound = 14
2542+
bounds = [(0, max_bound) for x in x0]
25412543
cost_func = cost_function_l1
2544+
x0[x0 <= 0] = 0.000001
2545+
x0[x0 > max_bound] = max_bound - 0.0001
25422546
else:
25432547
cost_func = cost_function_l1
25442548
return minimize(

autots/models/ensemble.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,16 @@ def parse_mosaic(ensemble):
117117
# zero is considered None here
118118
if swindow == 0:
119119
swindow = None
120+
if "crosshair_lite" in ensemble:
121+
crosshair = 'crosshair_lite'
122+
elif 'crosshair' in ensemble:
123+
crosshair = True
124+
else:
125+
crosshair = False
120126
return {
121127
'metric': metric,
122128
'smoothing_window': swindow,
123-
'crosshair': "crosshair" in ensemble,
129+
'crosshair': crosshair,
124130
'n_models': n_models,
125131
}
126132

@@ -481,7 +487,7 @@ def mosaic_classifier(df_train, known, classifier_params=None):
481487
"model_params": {
482488
'n_estimators': 62,
483489
'max_features': 0.181116,
484-
'max_leaves': 261,
490+
'max_leaf_nodes': 261,
485491
'criterion': 'entropy',
486492
},
487493
}
@@ -1564,17 +1570,21 @@ def HorizontalTemplateGenerator(
15641570
return ensemble_templates
15651571

15661572

1567-
def generate_crosshair_score(error_matrix):
1568-
arr_size = error_matrix.size
1569-
base_weight = 0.001 / arr_size
1570-
sum_error = np.sum(error_matrix) * base_weight
1573+
def generate_crosshair_score(error_matrix, method=None):
1574+
# 'lite' only takes the weighted axis down a series not from other series
1575+
if method == 'crosshair_lite':
1576+
return error_matrix + (np.median(error_matrix, axis=0) / 3)
1577+
else:
1578+
arr_size = error_matrix.size
1579+
base_weight = 0.001 / arr_size
1580+
sum_error = np.sum(error_matrix) * base_weight
15711581

1572-
cross_base = error_matrix * (base_weight * 50)
1573-
row_sums = cross_base.sum(axis=1)
1574-
col_sums = cross_base.sum(axis=0)
1575-
outer_sum = np.add.outer(row_sums, col_sums)
1582+
cross_base = error_matrix * (base_weight * 50)
1583+
row_sums = cross_base.sum(axis=1)
1584+
col_sums = cross_base.sum(axis=0)
1585+
outer_sum = np.add.outer(row_sums, col_sums)
15761586

1577-
return error_matrix + sum_error + outer_sum
1587+
return error_matrix + sum_error + outer_sum
15781588

15791589

15801590
def generate_crosshair_score_list(error_list):

autots/models/neural_forecast.py

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(
4444
activation='ReLU',
4545
scaler_type='robust',
4646
model_args={},
47+
point_quantile=None,
4748
**kwargs,
4849
):
4950
ModelObject.__init__(
@@ -66,11 +67,18 @@ def __init__(
6667
self.activation = activation
6768
self.scaler_type = scaler_type
6869
self.model_args = model_args
70+
self.point_quantile = point_quantile
6971
self.forecast_length = forecast_length
7072
self.df_train = None
7173
self.static_regressor = None
7274

73-
def fit(self, df, future_regressor=None, static_regressor=None):
75+
def fit(
76+
self,
77+
df,
78+
future_regressor=None,
79+
static_regressor=None,
80+
regressor_per_series=None,
81+
):
7482
"""Train algorithm given data supplied.
7583
7684
Args:
@@ -86,6 +94,9 @@ def fit(self, df, future_regressor=None, static_regressor=None):
8694
self.static_regressor = static_regressor
8795
if isinstance(self.static_regressor, pd.DataFrame):
8896
static_cols = static_regressor.columns.tolist()
97+
if regressor_per_series is not None:
98+
if not isinstance(regressor_per_series, dict):
99+
raise ValueError("regressor_per_series in incorrect format")
89100

90101
from neuralforecast import NeuralForecast
91102
from neuralforecast.losses.pytorch import (
@@ -121,7 +132,12 @@ def fit(self, df, future_regressor=None, static_regressor=None):
121132
logging.getLogger("pytorch_lightning").setLevel(logging.CRITICAL)
122133
loss = self.loss
123134
if loss == "MQLoss":
124-
loss = MQLoss(level=levels)
135+
if self.point_quantile is None:
136+
loss = MQLoss(level=levels)
137+
else:
138+
div = (1 - self.prediction_interval) / 2
139+
quantiles = [div, 1 - div, self.point_quantile]
140+
loss = MQLoss(quantiles=quantiles)
125141
elif loss == "Poisson":
126142
loss = DistributionLoss(
127143
distribution='Poisson', level=levels, return_params=False
@@ -187,7 +203,10 @@ def fit(self, df, future_regressor=None, static_regressor=None):
187203
models = self.model
188204
model_args = self.model_args
189205
if self.regression_type in ['User', 'user', True]:
190-
self.base_args["futr_exog_list"] = future_regressor.columns.tolist()
206+
regr_cols = future_regressor.columns.tolist()
207+
if regressor_per_series is not None:
208+
regr_cols + next(iter(regressor_per_series.values())).columns.tolist()
209+
self.base_args["futr_exog_list"] = regr_cols
191210
self.base_args['stat_exog_list'] = static_cols
192211

193212
if isinstance(models, list):
@@ -221,6 +240,17 @@ def fit(self, df, future_regressor=None, static_regressor=None):
221240
silly_format = silly_format.merge(
222241
future_regressor, left_on='ds', right_index=True
223242
)
243+
if regressor_per_series is not None:
244+
full_df = []
245+
for key, value in regressor_per_series.items():
246+
local_copy = value.copy().reindex(df.index)
247+
local_copy.index.name = 'ds'
248+
local_copy = local_copy.reset_index()
249+
local_copy['unique_id'] = str(key)
250+
full_df.append(local_copy)
251+
silly_format = silly_format.merge(
252+
pd.concat(full_df), on=['unique_id', 'ds'], how='left'
253+
).fillna(0)
224254
self.nf = NeuralForecast(models=models, freq=freq)
225255
if self.static_regressor is None:
226256
self.nf.fit(df=silly_format)
@@ -234,7 +264,11 @@ def fit(self, df, future_regressor=None, static_regressor=None):
234264
return self
235265

236266
def predict(
237-
self, forecast_length=None, future_regressor=None, just_point_forecast=False
267+
self,
268+
forecast_length=None,
269+
future_regressor=None,
270+
just_point_forecast=False,
271+
regressor_per_series=None,
238272
):
239273
predictStartTime = datetime.datetime.now()
240274
if self.regression_type in ['User', 'user', True]:
@@ -249,6 +283,17 @@ def predict(
249283
future_regressor, left_index=True, right_index=True
250284
)
251285
futr_df = futr_df.reset_index(names='ds')
286+
if regressor_per_series is not None:
287+
full_df = []
288+
for key, value in regressor_per_series.items():
289+
local_copy = value.copy().reindex(index)
290+
local_copy.index.name = 'ds'
291+
local_copy = local_copy.reset_index()
292+
local_copy['unique_id'] = str(key)
293+
full_df.append(local_copy)
294+
futr_df = futr_df.merge(
295+
pd.concat(full_df), on=['unique_id', 'ds'], how='left'
296+
).fillna(0)
252297
self.futr_df = futr_df
253298
long_forecast = self.nf.predict(futr_df=futr_df)
254299
else:
@@ -259,6 +304,9 @@ def predict(
259304
target_col = long_forecast.columns[-1]
260305
else:
261306
target_col = target_col[0]
307+
if self.point_quantile is not None:
308+
# print(long_forecast.columns)
309+
target_col = long_forecast.columns[-1]
262310
forecast = long_forecast.reset_index().pivot_table(
263311
index='ds', columns='unique_id', values=target_col
264312
)[self.column_names]
@@ -274,10 +322,12 @@ def predict(
274322
)
275323
else:
276324
target_col = [x for x in long_forecast.columns if "hi-" in x][0]
325+
# print(f"upper target col: {target_col}")
277326
upper_forecast = long_forecast.reset_index().pivot_table(
278327
index='ds', columns='unique_id', values=target_col
279328
)[self.column_names]
280329
target_col = [x for x in long_forecast.columns if "lo-" in x][0]
330+
# print(f"lower target col {target_col}")
281331
lower_forecast = long_forecast.reset_index().pivot_table(
282332
index='ds', columns='unique_id', values=target_col
283333
)[self.column_names]
@@ -311,6 +361,16 @@ def get_new_params(self, method: str = 'random'):
311361
regression_type_choice = random.choices([None, "User"], weights=[0.8, 0.2])[
312362
0
313363
]
364+
if "deep" in method:
365+
max_steps = random.choices(
366+
[40, 80, 100, 1000, 5000, 10000, 50000],
367+
[0.2, 0.2, 0.2, 0.1, 0.05, 0.05, 0.01],
368+
)[0]
369+
else:
370+
max_steps = random.choices(
371+
[40, 80, 100, 1000, 5000],
372+
[0.2, 0.2, 0.2, 0.05, 0.03],
373+
)[0]
314374
activation = random.choices(
315375
['ReLU', 'Softplus', 'Tanh', 'SELU', 'LeakyReLU', 'PReLU', 'Sigmoid'],
316376
[0.5, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
@@ -328,8 +388,13 @@ def get_new_params(self, method: str = 'random'):
328388
"SMAPE",
329389
"StudentT",
330390
],
331-
[0.3, 0.1, 0.01, 0.1, 0.1, 0.01, 0.1, 0.1, 0.1, 0.01],
391+
[0.5, 0.1, 0.01, 0.1, 0.1, 0.01, 0.1, 0.1, 0.1, 0.01],
332392
)[0]
393+
point_quantile = None
394+
if loss == "MQLoss":
395+
point_quantile = random.choices(
396+
[None, 0.35, 0.45, 0.55, 0.65, 0.7], [0.5, 0.1, 0.1, 0.1, 0.1, 0.1]
397+
)[0]
333398
if models == "TFT":
334399
model_args = {
335400
"n_head": random.choice([2, 4]),
@@ -368,14 +433,12 @@ def get_new_params(self, method: str = 'random'):
368433
'learning_rate': random.choices(
369434
[0.001, 0.1, 0.01, 0.0003, 0.00001], [0.4, 0.1, 0.1, 0.1, 0.1]
370435
)[0],
371-
"max_steps": random.choices(
372-
[40, 80, 100, 1000],
373-
[0.2, 0.2, 0.2, 0.05],
374-
)[0],
436+
"max_steps": max_steps,
375437
'input_size': random.choices(
376438
[10, 28, "2ForecastLength", "3ForecastLength"], [0.2, 0.2, 0.2, 0.2]
377439
)[0],
378440
# "early_stop_patience_steps": random.choice([1, 3, 5]),
441+
"point_quantile": point_quantile,
379442
"model_args": model_args,
380443
'regression_type': regression_type_choice,
381444
}
@@ -390,13 +453,14 @@ def get_params(self):
390453
'learning_rate': self.learning_rate,
391454
"max_steps": self.max_steps,
392455
'input_size': self.input_size,
456+
'point_quantile': self.point_quantile,
393457
"model_args": self.model_args,
394458
'regression_type': self.regression_type,
395459
}
396460

397461

398462
if False:
399-
from autots.models.neural_forecast import NeuralForecast
463+
# from autots.models.neural_forecast import NeuralForecast
400464
from autots import load_daily, create_regressor, infer_frequency
401465

402466
df = load_daily(long=False)

0 commit comments

Comments
 (0)