Newer
Older
{
"cells": [
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"# IGNORE THIS CELL WHICH CUSTOMIZES LAYOUT AND STYLING OF THE NOTEBOOK !\n",
"%matplotlib inline\n",
"%config InlineBackend.figure_format = 'retina'\n",
"import warnings\n",
"\n",
"import matplotlib.pyplot as plt\n",
"\n",
"warnings.filterwarnings(\"ignore\", category=FutureWarning)\n",
"warnings.filterwarnings(\"ignore\", message=\"X does not have valid feature names, but [a-zA-Z]+ was fitted with feature names\", category=UserWarning)\n",
" \n",
"from IPython.core.display import HTML\n",
"\n",
"HTML(open(\"custom.html\", \"r\").read())"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"# Chapter 7: Regression\n",
"\n",
"Regression belongs like classification to the field of supervised learning. \n",
"\n",
"<div class=\"alert alert-block alert-warning\">\n",
"<i class=\"fa fa-info-circle\"></i> \n",
"<strong>Regression predicts numerical values</strong> \n",
"in contrast to classification which predicts categories.\n",
"</div>\n",
"\n",
"<img src=\"./images/30416v.jpg\" title=\"made at imgflip.com\" width=35%/>\n",
"<div class=\"alert alert-block alert-warning\">\n",
"<i class=\"fa fa-info-circle\"></i> \n",
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"## Example: Salmon weight\n",
"\n",
"The dataset `data/salmon.csv` holds measurements of `circumference`, `length` and `weight` for `atlantic` and `sockeye` salmons.\n",
"\n",
"Our goal is to predict `weight` based on the other three features."
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"\n",
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"Let us inspect the features and their distributions:"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"import seaborn as sns\n",
"sns.set(style=\"ticks\")\n",
"\n",
"sns.pairplot(df, hue=\"kind\", diag_kind=\"hist\");"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"In contrast to our previous examples, our data set contains a non-numerical text column `kind`.\n",
"\n",
"As discussed before a common method to encode categorical features is **one-hot-encoding**, <code>sklearn.preprocessing.OneHotEncoder</code> is a preprocessor which transforms a categorical feature to this encoding:\n"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"from sklearn.preprocessing import OneHotEncoder\n",
"features = df.iloc[:, :-1]\n",
"values = df.iloc[:, -1]\n",
"\n",
"# needs 2d data structure, features.iloc[2] has dimension 1\n",
"encoder = OneHotEncoder(sparse=False)\n",
"one_hot = encoder.fit_transform(features.iloc[:, 2:3])\n",
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"So the one-hot encoder computes two columns with exclusive flags 0 and 1."
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"features[\"is_atlantic\"] = one_hot[:, 0]\n",
"features[\"is_sockeye\"] = one_hot[:, 1]\n",
"\n",
"features.head()"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"# we remove the categorical column now:\n",
"del features[\"kind\"]"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"Now we prepare the data for training and testing:"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"from sklearn.model_selection import train_test_split\n",
"\n",
"(features_train, features_test, values_train, values_test) = train_test_split(\n",
" features, values, random_state=42\n",
")"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"Without further explanation we pick a regression algorithm, more about regrssion algorithms will be discussed later:"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"from sklearn.kernel_ridge import KernelRidge\n",
"\n",
"kr = KernelRidge(alpha=0.001, kernel=\"rbf\", gamma=0.05)"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
" <i class=\"fa fa-info-circle\"></i> Regression methods in <code>scikit-learn</code> also have <code>fit</code> and <code>predict</code> methods. Thus cross validation, pipelines and hyperparameter-optimization will be available.\n",
" \n",
"</div>"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"kr.fit(features_train, values_train)\n",
"predicted = kr.predict(features_test)"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"Let us plot how good given and predicted values match on the training data set (sic !)."
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"def plot_fit_quality(values_test, predicted):\n",
" plt.figure(figsize=(12, 4))\n",
" plt.subplot(1, 2, 1)\n",
" plt.scatter(x, predicted - values_test, color=\"steelblue\", marker=\"o\")\n",
" plt.plot([0, len(predicted)], [0, 0], \"k:\")\n",
" max_diff = np.max(np.abs(predicted - values_test))\n",
" plt.ylim([-max_diff, max_diff])\n",
" plt.ylabel(\"error\")\n",
" plt.xlabel(\"sample id\")\n",
"\n",
" plt.subplot(1, 2, 2)\n",
"\n",
" plt.scatter(\n",
" x, (predicted - values_test) / values_test, color=\"steelblue\", marker=\"o\"\n",
" )\n",
" plt.plot([0, len(predicted)], [0, 0], \"k:\")\n",
" plt.ylabel(\"relative error\")\n",
" plt.xlabel(\"sample id\")\n",
"\n",
"plot_fit_quality(values_test, predicted)"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"For assessing the quality of the predictions of a regression method, we can use multiple methods which we will discuss later in this script.\n",
"\n",
"For our current example we compute the **mean absolute error** which is the average absolute difference between given values $y_i$ and predicted values $\\hat{y}_i$:\n",
"\\frac{1}{n} \\left(\\, |y_1 - \\hat{y}_1| \\, + \\, |y_2 - \\hat{y}_2| \\, + \\, \\ldots \\,+ \\,|y_n - \\hat{y}_n| \\,\\right)\n",
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"\n",
"error = np.sum(np.abs(predicted - values_test)) / len(values_test)\n",
"print(error)"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"## Metrics / error measures"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"For the classification metrics we introduced (like accuracy, precision, recall, F1) higher numbers indicated better classification performance. \n",
"\n",
"Most regression metrics turn this upside down. E.g. smaller values indicate a better regression model.\n",
"\n",
"| | | | | |\n",
"|----------------|-----------------------------------|------------------------|---|---|\n",
"| classification | accuracy, F1, ... | the larger the better | | |\n",
"| regression |mean absolute error, ... | the smaller the better | | |\n",
"To harmonize this we can flip the sign of the regression scores, e.g."
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"error = -np.sum(np.abs(predicted - values_test)) / len(values_test)\n",
"print(error)"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"metadata": {},
"Now the regression score 0.3 which is better then 0.4 also becomes larger when we flip the sign:"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"The benefit is that we can use hyperparameter optimization functions from `scikit-learn` (which select configurations which yield a large score) without further modification. \n",
"This is why the names of the scores we mention below are prefixed by `neg_`.\n",
"Below some of the [metrics `scikit-learn` offers for measuring regression quality](https://scikit-learn.org/stable/modules/model_evaluation.html#regression-metrics):\n",
"\n",
"### 1. Mean absolute error\n",
"\n",
"This is the metric we used before. Taking absolute values before adding up the deviatons assures that deviations with different signs can not cancel out.\n",
"\n",
"<div class=\"alert alert-block alert-warning\">\n",
" <i class=\"fa fa-info-circle\"></i> <strong>mean absolute error</strong> is defined as \n",
"\\frac{1}{n} \\left(\\, |y_1 - \\hat{y}_1| \\, + \\, |y_2 - \\hat{y}_2| \\, + \\, \\ldots \\,+ \\,|y_n - \\hat{y}_n| \\,\\right)\n",
"The name of the corresponding score in `scikit-learn` is `neg_mean_absolute_error`.\n",
"\n",
"\n",
"### 2. Mean squared error\n",
"\n",
"Here we replace the absolute difference by its squared difference. Squaring also insures positive differences.\n",
"\n",
"<div class=\"alert alert-block alert-warning\">\n",
" <i class=\"fa fa-info-circle\"></i> <strong>mean squared error</strong> is defined as \n",
"\n",
"\n",
"\\frac{1}{n} \\left(\\, (y_1 - \\hat{y}_1)^2 \\, + \\, (y_2 - \\hat{y}_2)^2 \\, \\, \\ldots \\,+ \\,(y_n - \\hat{y}_n)^2 \\,\\right)\n",
"This measure is more sensitive to outliers: A few larger differences contribute more significantly to a larger mean squared error. The name of the corresponding score in `scikit-learn` is `neg_mean_squared_error`.\n",
"\n",
"\n",
"### 3. Median absolute error\n",
"\n",
"<div class=\"alert alert-block alert-warning\">\n",
" <i class=\"fa fa-info-circle\"></i> <strong>median absolute error</strong> is defined as \n",
"\n",
"\n",
"\n",
"\\text{median}\\left(\\,|y_1 - \\hat{y}_1|, \\,|y_2 - \\hat{y}_2|, \\,\\ldots, \\,|y_n - \\hat{y}_n| \\, \\right)\n",
"This measure is less sensitive to outliers than the metrics we discussed before: A few larger differences will not contribute significantly to a larger error value. The name of the corresponding score in `scikit-learn` is `neg_median_absolute_error`.\n",
"\n",
"### 4. Mean squared log error\n",
"\n",
"The formula for this metric can be found [here](https://scikit-learn.org/stable/modules/model_evaluation.html#mean-squared-log-error). \n",
"\n",
"This metric is recommended when your target values are distributed over a huge range of values, like popoluation numbers. \n",
"The previous error metrics would put a larger weight on large target values. One could consider relative deviations to compensate such effects but relative deviations come with other problems like division by zero.\n",
"\n",
"\n",
"The name is `neg_mean_squared_log_error`\n",
"\n",
"\n",
"### 5. Explained variance and $r^2$-score\n",
"\n",
"Two other scores to mention are *explained variance* and $r^2$-score. For both larger values indicate better regression results.\n",
"\n",
"The formula for [r2 can be found here](https://scikit-learn.org/stable/modules/model_evaluation.html#r2-score), the score takes values in the range $0 .. 1$. The name within `scikit-learn` is `r2`.\n",
"\n",
"The formula for [explained variance](https://scikit-learn.org/stable/modules/model_evaluation.html#explained-variance-score), the score takes values up to $1$. The name within `scikit-learn` is `explained_variance`."
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"- `sklearn.linear_model.LinearRegression` is a linear regression method, which only works well for target values which can be described as a linear combination of feature values. This is also known as linear least squares method.\n",
"- `sklearn.linear_model.Ridge`, `sklearn.linear_model.Lasso`, and `sklearn.linear_model.ElasticNet` are linear regression methods with `L2`, `L1` resp `L2 + L1` regularization terms to avoid overfitting resp. improve generalization.\n",
"- `sklearn.kernel_ridge.KernelRidge` is [documented here](https://scikit-learn.org/stable/modules/kernel_ridge.html#kernel-ridge). It combines the kernel trick from SVMs with ridge regression.\n",
"- `sklearn.svm.SVR` is an extension of support vector classification concepts to regression, including the kernel trick for non-linear regression, [you find examples here](https://scikit-learn.org/stable/modules/svm.html#svm-regression).\n",
"\n",
"\n",
"- `sklearn.neighbors.KNeighborsRegressor` extends the idea of nearest neighbour classification to regression: Search for similar data points in the learning data set and compute the predicted value from the values from the neighbourhood, e.g. by averaging or by linear interpolation. [Documentation is available here](https://scikit-learn.org/stable/modules/neighbors.html#regression)\n",
"\n",
"\n",
"- `sklearn.tree.DecisionTreeRegressor` expands the concept of decision trees to regression [is documented here](https://scikit-learn.org/stable/modules/tree.html#regression).\n",
"\n",
"- `sklearn.linear_model.TweedieRegressor`, `sklearn.linear_model.PoissonRegressor`, `sklearn.linear_model.GammaRegressor` offer so-called *Generalized Linear Models* (**GLM**) \n",
" - These models are usually of interest when your target values are event-based discrete counts/frequencies, or continuous amounts, durations, costs/prices, or rates/probabilities. The [scikit-learn GLM tutorial](https://scikit-learn.org/stable/modules/linear_model.html#generalized-linear-regression) provides a formal insight as well as tips for choosing GLM with some use case examples. \n",
" - Beyond that the [wikipedia article about generalized linear models](https://en.wikipedia.org/wiki/Generalized_linear_model#Intuition) gives a nice intuition and [this discussion](https://stats.stackexchange.com/questions/190763/how-to-decide-which-glm-family-to-use) provides a guide which GLM should be used when.\n",
" - For assessing and hyperparameter-optimization of such General Linear Models you should also use [suitable metrics from scikit-learn](https://scikit-learn.org/stable/modules/model_evaluation.html#mean-poisson-gamma-and-tweedie-deviances)."
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"## A full pipeline\n",
"\n",
"Let us now try to find a good regressor using `scikit-learn`s hyper-parameter tuning:"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"from sklearn.kernel_ridge import KernelRidge\n",
"from sklearn.linear_model import LinearRegression\n",
"from sklearn.model_selection import cross_val_score\n",
"from sklearn.pipeline import make_pipeline\n",
"from sklearn.preprocessing import PolynomialFeatures, StandardScaler\n",
"def eval_regression(p, features, values):\n",
" score = cross_val_score(\n",
" p, features, values, scoring=\"neg_median_absolute_error\", cv=4\n",
" ).mean()\n",
" print(\"cross val score:\", score)\n",
" predicted = p.fit(features_train, values_train).predict(features_test)\n",
" plot_fit_quality(values_test, predicted)\n",
"\n",
"p = make_pipeline(StandardScaler(), PolynomialFeatures(2), PCA(2), LinearRegression())\n",
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"p = make_pipeline(StandardScaler(), PolynomialFeatures(), PCA(), LinearRegression())\n",
"param_grid = {\n",
" \"polynomialfeatures__degree\": range(3, 6),\n",
" \"pca__n_components\": range(3, 11),\n",
"}"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"from sklearn.model_selection import GridSearchCV\n",
"\n",
"search = GridSearchCV(\n",
" p, param_grid, scoring=\"neg_median_absolute_error\", cv=4, n_jobs=4\n",
")\n",
"\n",
"search.fit(features, values)\n",
"\n",
"print(search.best_params_)\n",
"eval_regression(search, features, values)"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"- Play with the examples above and try different algorithms, metrics and pipelines.\n",
"### Optional exercise: Timeseries prediction\n",
"The file `data/sales.csv` holds sales data of a swiss sports shop selling skiing equipment. The time axis is in units of months, starting with January.\n",
"* Load the data and plot sales value over months"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"sales_data = pd.read_csv(\"data/sales.csv\")\n",
"sales_data.head()"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"solution"
]
Mikolaj Rybinski
committed
},
"outputs": [],
"source": [
"months = sales_data.iloc[:160, 0].values\n",
"sales = sales_data.iloc[:160, 1].values\n",
"\n",
"months_validate = sales_data.iloc[160:, 0].values\n",
"sales_validate = sales_data.iloc[160:, 1].values\n",
"\n",
"\n",
"plt.figure(figsize=(12, 5))\n",
"plt.plot(months, sales, color=\"chocolate\", label=\"train\")\n",
"plt.plot(\n",
" months_validate, sales_validate, color=\"chocolate\", linestyle=\"--\", label=\"validate\"\n",
")\n",
"plt.legend()\n",
"plt.xlabel(\"month\")\n",
"plt.ylabel(\"sales\");"
]
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"<div style=\"font-size: 130%;\">Approach for time series forecasting</div>\n",
"\n",
"**Advice**: you might want to read the full instructions several times to understand the concept.\n",
"\n",
"We want to learn from our single time series of sales by looking at its smaller parts and take sales value that comes after each such part (window) as an expected prediction.\n",
"More formally, we learn to predict n-th value in the series $s_{n}$ based on few previous values `W`: $s_{n - W}, s_{n - W + 1} \\ldots s_{n - 1}$. `W` is a parameter called *window size*.\n",
"\n",
"E.g. for window size $W$ = 3 we create a feature matrix `X` based on sales values:\n",
"\n",
" s_0, s_1, s_2\n",
" s_1, s_2, s_3\n",
" s_2, s_3, s_4\n",
" ...\n",
" \n",
"and a vector of target values `y` as follows:\n",
"\n",
" s_3\n",
" s_4\n",
" s_5\n",
" ...\n",
" \n",
"Here is a function which takes sales vector `s_0, s_1, ...` and window size `W` and returns features matrix `X` and the right hand side `y`:"
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"source": [
"def create_feature_matrix_and_target_values(sales, window_size):\n",
" features = np.zeros((len(sales) - window_size, window_size))\n",
"\n",
" for i in range(len(sales) - window_size):\n",
" features[i] = sales[i : i + window_size]\n",
"\n",
" return features, sales[window_size:]"
Mikolaj Rybinski
committed
]
},
{
"cell_type": "code",
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"def test():\n",
" X, y = create_feature_matrix_and_target_values(sales, 4)\n",
" assert np.all(X[0] == sales[:4])\n",
" assert np.all(X[1] == sales[1:5])\n",
" assert np.all(X[2] == sales[2:6])\n",
" assert np.all(X[-1] == sales[-5:-1])\n",
"\n",
" assert np.all(y[0] == sales[4])\n",
" assert np.all(y[-1] == sales[-1])\n",
Mikolaj Rybinski
committed
]
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"source": [
"- Find optimal configurations (use the `r2` metric) for the regressors `Lasso`, `SVR` and `KernelRidge(kernel=\"rbf\")` and several window sizes. \n",
"- For every month $i$ we want to compare the known value of $s_i$ and the predicted value for this month. To that end, plot both sales and predicted time series on one plot, and plot sales against predicted points as a scatter plot."
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {},
"outputs": [],
"from sklearn.linear_model import Lasso\n",
"from sklearn.model_selection import GridSearchCV\n",
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"lasso_grid = {\"alpha\": 10 ** np.linspace(-2, 3, 30)}\n",
"svr_grid = {\"C\": 10 ** np.linspace(-4, 2, 30)}\n",
"kernel_ridge_grid = {\"alpha\": 10 ** np.linspace(-4, 3, 30)}\n",
"WINDOW_SIZE = 36\n",
"X, y = create_feature_matrix_and_target_values(sales, WINDOW_SIZE)\n",
Mikolaj Rybinski
committed
" # Note: do not scale - the time series data (windows) are already scaled\n",
" for regressor, param_grid in [\n",
" (Lasso(), lasso_grid),\n",
" (SVR(), svr_grid),\n",
" (KernelRidge(kernel=\"rbf\"), kernel_ridge_grid),\n",
" ]:\n",
" search = GridSearchCV(regressor, param_grid, scoring=\"r2\", cv=5)\n",
" search.fit(X, y)\n",
" # we predict on the learning data set to get a general\n",
" # \"feeling\" how well the regressors work\n",
" predicted = search.predict(X)\n",
" plot_regression(regressor.__class__.__qualname__, predicted)\n",
" regressors.append(search)\n",
"\n",
" return regressors\n",
"\n",
"def plot_regression(title, predicted):\n",
Mikolaj Rybinski
committed
" f, (a0, a1) = plt.subplots(\n",
" 1, 2, figsize=(18, 5), gridspec_kw={\"width_ratios\": [2, 1]}\n",
" )\n",
" f.suptitle(title)\n",
Mikolaj Rybinski
committed
" a0.plot(months, sales, color=\"chocolate\", label=\"train\")\n",
" a0.plot(\n",
" months_validate,\n",
" sales_validate,\n",
Mikolaj Rybinski
committed
" linestyle=\"--\",\n",
" label=\"validate\",\n",
" )\n",
" a0.plot(\n",
" months[WINDOW_SIZE:], predicted, color=\"steelblue\", label=\"predicted (train)\"\n",
Mikolaj Rybinski
committed
" a0.legend()\n",
" a0.set_xlabel(\"month\")\n",
" a0.set_ylabel(\"sales\")\n",
Mikolaj Rybinski
committed
" a1.scatter(sales[WINDOW_SIZE:], predicted, color=\"steelblue\")\n",
" mi, ma = np.min(sales), np.max(sales)\n",
" a1.plot([mi, ma], [mi, ma], \"k:\")\n",
" r2 = r2_score(sales[WINDOW_SIZE:], predicted)\n",
Mikolaj Rybinski
committed
" a1.set_title(\"r2 = {:.3f}\".format(r2))\n",
" a1.set_xlabel(\"sales\")\n",
" a1.set_ylabel(\"predicted (train)\")\n",
Mikolaj Rybinski
committed
]
{
"cell_type": "markdown",
Mikolaj Rybinski
committed
"metadata": {},
"Now we want to use our model to get long term predictions starting at month `N`, with a given window size `W`.\n",
"We will be iteratively predicting next value after the window and shifting the window by one. We execute the following steps:\n",
"\n",
"1. predict first value after given time window.\n",
"2. update data window: shift by one time step, thus: discard the first entry and append the newly predicted value\n",
"3. continue with step 1.\n",
"\n",
"\n",
"We demonstrate this for `W=4` and `N=100` to predict values `z_101`, `z_102`, ...:\n",
"\n",
"<div style=\"margin-left: 1em;\">\n",
"<code>\n",
"predict <span style=\"color: green; \">z_101</span> from s_097, s_098, s_099, s_100\n",
"predict <span style=\"color: green; ;\">z_102</span> from s_098, s_099, s_100, <span style=\"color: green; \">z_101</span>\n",
"predict <span style=\"color: green; \">z_103</span> from s_099, s_100, <span style=\"color: green; \">z_101, z_102</span>\n",
"predict <span style=\"color: green; \">z_104</span> from s_100, <span style=\"color: green; \">z_101, z_102, z_103</span>\n",
"predict <span style=\"color: green; ;\">z_105</span> from <span style=\"color: green; \">z_101, z_102, z_103, z_104</span>\n",
"... \n",
"</code> \n",
"</div>\n",
"\n",
"\n",
"- Implement this procedure and plot forecasts vs real data for different regression algorithms and window sizes.\n",
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"execution_count": null,
Mikolaj Rybinski
committed
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
Mikolaj Rybinski
committed
"# start predicting from a first validation time point\n",
"# => add the last training window for the first WINDOW_SIZE predictions\n",
"X_validate, y_validate = create_feature_matrix_and_target_values(\n",
" np.r_[sales[-WINDOW_SIZE:], sales_validate], WINDOW_SIZE\n",
")\n",
Mikolaj Rybinski
committed
"def forecast(X, y, regressor):\n",
" # we crate a copy because we change below content\n",
" # current_window in place. Without copy we would\n",
" # also change X!\n",
Mikolaj Rybinski
committed
" n_last = len(y) # make sure to skip the last training window\n",
" current_window = X[-n_last, :].copy()\n",
"\n",
" for k in range(n_last):\n",
" new = regressor.predict(current_window[None, :])\n",
" predicted.append(new)\n",
" # this relates to the comment above! here we\n",
" # modify the window data in place:\n",
" current_window[:-1] = current_window[1:]\n",
" current_window[-1] = new\n",
" return np.array(predicted).flatten(), y[-n_last:]\n",
Mikolaj Rybinski
committed
"def forecast_and_plot(regressor):\n",
" predicted, correct = forecast(X_validate, y_validate, regressor)\n",
Mikolaj Rybinski
committed
" f, (a0, a1) = plt.subplots(\n",
" 1, 2, figsize=(18, 5), gridspec_kw={\"width_ratios\": [2, 1]}\n",
" )\n",
" f.suptitle(regressor.estimator.__class__.__qualname__)\n",
Mikolaj Rybinski
committed
" a0.plot(months, sales, color=\"chocolate\", linestyle=\"--\", label=\"train\")\n",
" a0.plot(months_validate, correct, color=\"chocolate\", label=\"validate\")\n",
" a0.plot(months_validate, predicted, color=\"steelblue\", label=\"predicted (validate)\")\n",
" a0.legend()\n",
" a0.set_xlabel(\"month\")\n",
" a0.set_ylabel(\"sales\")\n",
Mikolaj Rybinski
committed
" a1.scatter(correct, predicted, color=\"steelblue\")\n",
" mi, ma = np.min(correct), np.max(correct)\n",
Mikolaj Rybinski
committed
" a1.plot([mi, ma], [mi, ma], \"k:\")\n",
" r2 = r2_score(correct, predicted)\n",
" a1.set_title(\"r2 = {:.3f}\".format(r2))\n",
" a1.set_xlabel(\"sales\")\n",
" a1.set_ylabel(\"predicted (validate)\")\n",
Mikolaj Rybinski
committed
" forecast_and_plot(regressor)"
]
Mikolaj Rybinski
committed
"metadata": {},
Mikolaj Rybinski
committed
]
Mikolaj Rybinski
committed
"kernelspec": {
Mikolaj Rybinski
committed
"language": "python",
Mikolaj Rybinski
committed
},
Mikolaj Rybinski
committed
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",