From 1f97cc2d625e08a2d3ae19ecb01df51f36e80073 Mon Sep 17 00:00:00 2001 From: Matthew Rocklin Date: Sat, 18 Jul 2020 16:29:00 -0700 Subject: [PATCH 01/11] Add hyper-pararmeter-optimization notebook with Hyperband Currently depends on https://github.com/dask/dask-ml/pull/701 This could be improved by using an estimator that benefitted from large amounts of data. --- hyper-parameter-optimization.ipynb | 253 +++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 hyper-parameter-optimization.ipynb diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb new file mode 100644 index 0000000..e6d16ef --- /dev/null +++ b/hyper-parameter-optimization.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get resources" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import coiled\n", + "import dask.distributed" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using existing cluster: play\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

Client

\n", + "\n", + "
\n", + "

Cluster

\n", + "
    \n", + "
  • Workers: 20
  • \n", + "
  • Cores: 40
  • \n", + "
  • Memory: 85.90 GB
  • \n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cluster = coiled.Cluster(\n", + " name=\"play\", \n", + " n_workers=20, \n", + " configuration=\"coiled/dask-examples\", \n", + " shutdown_on_close=False\n", + ")\n", + "client = dask.distributed.Client(cluster)\n", + "\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get and pre-process data" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import dask.dataframe as dd\n", + "df = dd.read_csv(\n", + " \"s3://nyc-tlc/trip data/yellow_tripdata_2019-*.csv\", \n", + " parse_dates=[\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"],\n", + " dtype={\n", + " \"VendorID\": \"UInt8\",\n", + " \"passenger_count\": \"UInt8\",\n", + " \"RatecodeID\": \"UInt8\",\n", + " \"store_and_fwd_flag\": \"category\",\n", + " \"PULocationID\": \"UInt16\",\n", + " \"DOLocationID\": \"UInt16\", \n", + " \"payment_type\": \"UInt8\",\n", + " },\n", + ")\n", + "\n", + "data = df[[\"passenger_count\", \"trip_distance\", \"RatecodeID\", \"payment_type\", \"fare_amount\"]]\n", + "data = data.fillna(0)\n", + "\n", + "labels = (df.tip_amount / df.fare_amount) > 0.25\n", + "labels = labels.fillna(False)\n", + "\n", + "from dask_ml.model_selection import train_test_split\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(data, labels, shuffle=True)\n", + "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train model" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.linear_model import SGDClassifier\n", + "from dask_ml.model_selection import HyperbandSearchCV\n", + "from scipy.stats import uniform, loguniform\n", + "\n", + "\n", + "clf = SGDClassifier(tol=1e-3, penalty='elasticnet', random_state=0)\n", + "\n", + "params = {'alpha': loguniform(1e-2, 1e0), # or np.logspace\n", + " 'l1_ratio': uniform(0, 1)} # or np.linspace\n", + "\n", + "search = HyperbandSearchCV(clf, params, max_iter=81, random_state=0)\n", + "\n", + "search.fit(X_train, y_train, classes=[0, 1]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Score" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.766619056755278" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.score(X_test.sample(frac=0.1, random_state=123), y_test.sample(frac=0.1, random_state=123))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## What if we just sampled instead?" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SGDClassifier(penalty='elasticnet', random_state=0)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "clf.fit(\n", + " X_train.sample(frac=0.01, random_state=123).compute(), \n", + " y_train.sample(frac=0.01, random_state=123).compute()\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.7487741904919819" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "clf.score(\n", + " X_test.sample(frac=0.01, random_state=123).compute(), \n", + " y_test.sample(frac=0.01, random_state=123).compute()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Depending on our business needs, we maybe didn't need to do all of this :)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:coiled-coiled-dask-examples]", + "language": "python", + "name": "conda-env-coiled-coiled-dask-examples-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From 72b479b505ab9b6cca3ff195c76b1a27f6f516e7 Mon Sep 17 00:00:00 2001 From: Matthew Rocklin Date: Fri, 24 Jul 2020 10:29:38 -0700 Subject: [PATCH 02/11] squashme --- hyper-parameter-optimization.ipynb | 138 ++++++++++++++++++----------- 1 file changed, 85 insertions(+), 53 deletions(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index e6d16ef..dabcf13 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -37,23 +37,23 @@ "\n", "

Client

\n", "\n", "\n", "\n", "

Cluster

\n", "
    \n", - "
  • Workers: 20
  • \n", - "
  • Cores: 40
  • \n", - "
  • Memory: 85.90 GB
  • \n", + "
  • Workers: 15
  • \n", + "
  • Cores: 60
  • \n", + "
  • Memory: 128.85 GB
  • \n", "
\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -64,8 +64,8 @@ "source": [ "cluster = coiled.Cluster(\n", " name=\"play\", \n", - " n_workers=20, \n", - " configuration=\"coiled/dask-examples\", \n", + " n_workers=15, \n", + " configuration=\"coiled/default\", \n", " shutdown_on_close=False\n", ")\n", "client = dask.distributed.Client(cluster)\n", @@ -74,12 +74,32 @@ ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 8, "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "distributed.client - ERROR - Failed to reconnect to scheduler after 10.00 seconds, closing client\n", + "_GatheringFuture exception was never retrieved\n", + "future: <_GatheringFuture finished exception=CancelledError()>\n", + "asyncio.exceptions.CancelledError\n" + ] + } + ], "source": [ - "## Get and pre-process data" + "cluster.scale(0)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 3, @@ -99,6 +119,7 @@ " \"DOLocationID\": \"UInt16\", \n", " \"payment_type\": \"UInt8\",\n", " },\n", + " blocksize=\"16 MiB\",\n", ")\n", "\n", "data = df[[\"passenger_count\", \"trip_distance\", \"RatecodeID\", \"payment_type\", \"fare_amount\"]]\n", @@ -113,6 +134,26 @@ "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "131504474" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.passenger_count.sum().compute()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -124,8 +165,32 @@ "cell_type": "code", "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "TypeError", + "evalue": "Cannot operate on Dask array with unknown chunk sizes. Use the following the compute chunk sizes:\n\n >>> X.compute_chunk_sizes() # if Dask.Array\n >>> ddf.to_dask_array(lengths=True) # if Dask.Dataframe", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 671\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 672\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 673\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdefault_client\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msync\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_fit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 674\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 675\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mif_delegate_has_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"best_estimator_\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"estimator\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/client.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 830\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 831\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 832\u001b[0;31m return sync(\n\u001b[0m\u001b[1;32m 833\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcallback_timeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 834\u001b[0m )\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 337\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 338\u001b[0m \u001b[0mtyp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 339\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 340\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 341\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36mf\u001b[0;34m()\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcallback_timeout\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 322\u001b[0m \u001b[0mfuture\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0masyncio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_for\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuture\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 323\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 324\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/tornado/gen.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 733\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 734\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 735\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 736\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 737\u001b[0m \u001b[0mexc_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/tornado/gen.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 207\u001b[0m \u001b[0;31m# performance penalty for the synchronous case.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 208\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 209\u001b[0;31m \u001b[0myielded\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 210\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mStopIteration\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mReturn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 211\u001b[0m future_set_result_unless_cancelled(\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_hyperband.py\u001b[0m in \u001b[0;36m_fit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 387\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 388\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_fit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 389\u001b[0;31m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscorer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_validate_parameters\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 390\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 391\u001b[0m \u001b[0mbrackets\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_get_hyperband_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmax_iter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0meta\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maggressiveness\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36m_validate_parameters\u001b[0;34m(self, X, y)\u001b[0m\n\u001b[1;32m 501\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_dask_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 502\u001b[0m \u001b[0mkwargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maccept_unknown_chunks\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_dask_dataframe\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 503\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 504\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mensure_2d\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 505\u001b[0m \u001b[0mscorer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_scoring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mestimator\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscoring\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscoring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36m_check_array\u001b[0;34m(self, X, **kwargs)\u001b[0m\n\u001b[1;32m 522\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfrom_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 524\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 525\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 526\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/utils.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(array, accept_dask_array, accept_dask_dataframe, accept_unknown_chunks, accept_multiple_blocks, preserve_pandas_dataframe, remove_zero_chunks, *args, **kwargs)\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0maccept_unknown_chunks\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 143\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 144\u001b[0;31m raise TypeError(\n\u001b[0m\u001b[1;32m 145\u001b[0m \u001b[0;34m\"Cannot operate on Dask array with unknown chunk sizes. \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;34m\"Use the following the compute chunk sizes:\\n\\n\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: Cannot operate on Dask array with unknown chunk sizes. Use the following the compute chunk sizes:\n\n >>> X.compute_chunk_sizes() # if Dask.Array\n >>> ddf.to_dask_array(lengths=True) # if Dask.Dataframe" + ] + } + ], "source": [ + "%%time\n", + "\n", "from sklearn.linear_model import SGDClassifier\n", "from dask_ml.model_selection import HyperbandSearchCV\n", "from scipy.stats import uniform, loguniform\n", @@ -150,20 +215,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.766619056755278" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "search.score(X_test.sample(frac=0.1, random_state=123), y_test.sample(frac=0.1, random_state=123))" ] @@ -177,20 +231,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "SGDClassifier(penalty='elasticnet', random_state=0)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "clf.fit(\n", " X_train.sample(frac=0.01, random_state=123).compute(), \n", @@ -200,20 +243,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.7487741904919819" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "clf.score(\n", " X_test.sample(frac=0.01, random_state=123).compute(), \n", @@ -231,9 +263,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:coiled-coiled-dask-examples]", + "display_name": "Python [conda env:coiled-coiled-default]", "language": "python", - "name": "conda-env-coiled-coiled-dask-examples-py" + "name": "conda-env-coiled-coiled-default-py" }, "language_info": { "codemirror_mode": { @@ -245,7 +277,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.4" + "version": "3.8.5" } }, "nbformat": 4, From 3bedf545cd3cf4e4977d30710b53cf7f1f0e71da Mon Sep 17 00:00:00 2001 From: Matthew Rocklin Date: Sat, 25 Jul 2020 09:45:52 -0700 Subject: [PATCH 03/11] cleanup --- hyper-parameter-optimization.ipynb | 121 ++--------------------------- 1 file changed, 8 insertions(+), 113 deletions(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index dabcf13..cc30473 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -19,54 +19,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Using existing cluster: play\n" - ] - }, - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

Client

\n", - "\n", - "
\n", - "

Cluster

\n", - "
    \n", - "
  • Workers: 15
  • \n", - "
  • Cores: 60
  • \n", - "
  • Memory: 128.85 GB
  • \n", - "
\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "cluster = coiled.Cluster(\n", - " name=\"play\", \n", - " n_workers=15, \n", + " n_workers=20, \n", " configuration=\"coiled/default\", \n", - " shutdown_on_close=False\n", ")\n", "client = dask.distributed.Client(cluster)\n", "\n", @@ -74,23 +33,10 @@ ] }, { - "cell_type": "code", - "execution_count": 8, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "distributed.client - ERROR - Failed to reconnect to scheduler after 10.00 seconds, closing client\n", - "_GatheringFuture exception was never retrieved\n", - "future: <_GatheringFuture finished exception=CancelledError()>\n", - "asyncio.exceptions.CancelledError\n" - ] - } - ], "source": [ - "cluster.scale(0)" + "## Get and pre-process data" ] }, { @@ -98,13 +44,6 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], "source": [ "import dask.dataframe as dd\n", "df = dd.read_csv(\n", @@ -134,26 +73,6 @@ "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" ] }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "131504474" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.passenger_count.sum().compute()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -163,34 +82,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "ename": "TypeError", - "evalue": "Cannot operate on Dask array with unknown chunk sizes. Use the following the compute chunk sizes:\n\n >>> X.compute_chunk_sizes() # if Dask.Array\n >>> ddf.to_dask_array(lengths=True) # if Dask.Dataframe", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 671\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 672\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mcontext\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 673\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdefault_client\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msync\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_fit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 674\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 675\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mif_delegate_has_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"best_estimator_\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"estimator\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/client.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 830\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 831\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 832\u001b[0;31m return sync(\n\u001b[0m\u001b[1;32m 833\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcallback_timeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 834\u001b[0m )\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 337\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 338\u001b[0m \u001b[0mtyp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 339\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 340\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 341\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36mf\u001b[0;34m()\u001b[0m\n\u001b[1;32m 321\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcallback_timeout\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 322\u001b[0m \u001b[0mfuture\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0masyncio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_for\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuture\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 323\u001b[0;31m \u001b[0mresult\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32myield\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 324\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 325\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/tornado/gen.py\u001b[0m in \u001b[0;36mrun\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 733\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 734\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 735\u001b[0;31m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfuture\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 736\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 737\u001b[0m \u001b[0mexc_info\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mexc_info\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/tornado/gen.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m 207\u001b[0m \u001b[0;31m# performance penalty for the synchronous case.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 208\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 209\u001b[0;31m \u001b[0myielded\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresult\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 210\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mStopIteration\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mReturn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 211\u001b[0m future_set_result_unless_cancelled(\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_hyperband.py\u001b[0m in \u001b[0;36m_fit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 387\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcoroutine\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 388\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_fit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 389\u001b[0;31m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscorer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_validate_parameters\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 390\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 391\u001b[0m \u001b[0mbrackets\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_get_hyperband_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmax_iter\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0meta\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maggressiveness\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36m_validate_parameters\u001b[0;34m(self, X, y)\u001b[0m\n\u001b[1;32m 501\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mto_dask_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 502\u001b[0m \u001b[0mkwargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maccept_unknown_chunks\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_dask_dataframe\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 503\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 504\u001b[0m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_check_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mensure_2d\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 505\u001b[0m \u001b[0mscorer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_scoring\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mestimator\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscoring\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscoring\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36m_check_array\u001b[0;34m(self, X, **kwargs)\u001b[0m\n\u001b[1;32m 522\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndarray\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mda\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfrom_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 524\u001b[0;31m \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 525\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 526\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/miniconda/envs/coiled-coiled-default/lib/python3.8/site-packages/dask_ml/utils.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(array, accept_dask_array, accept_dask_dataframe, accept_unknown_chunks, accept_multiple_blocks, preserve_pandas_dataframe, remove_zero_chunks, *args, **kwargs)\u001b[0m\n\u001b[1;32m 142\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0maccept_unknown_chunks\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 143\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 144\u001b[0;31m raise TypeError(\n\u001b[0m\u001b[1;32m 145\u001b[0m \u001b[0;34m\"Cannot operate on Dask array with unknown chunk sizes. \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;34m\"Use the following the compute chunk sizes:\\n\\n\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mTypeError\u001b[0m: Cannot operate on Dask array with unknown chunk sizes. Use the following the compute chunk sizes:\n\n >>> X.compute_chunk_sizes() # if Dask.Array\n >>> ddf.to_dask_array(lengths=True) # if Dask.Dataframe" - ] - } - ], + "outputs": [], "source": [ - "%%time\n", - "\n", "from sklearn.linear_model import SGDClassifier\n", "from dask_ml.model_selection import HyperbandSearchCV\n", "from scipy.stats import uniform, loguniform\n", From 7ab89eb0eef2093bc8c1ba8b014d835cae80e7d0 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 27 Jul 2020 22:41:37 -0500 Subject: [PATCH 04/11] Make edits to Hyperband example --- hyper-parameter-optimization.ipynb | 347 ++++++++++++++++++++++++++--- 1 file changed, 311 insertions(+), 36 deletions(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index cc30473..7d88c7b 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -4,12 +4,26 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Get resources" + "This example will walk through the following:\n", + "\n", + "* **Getting and processing the data.**\n", + "* **Defining a model and parameters.**\n", + "* **Finding the best parameters,** and some details on why we're using the chosen search algorithm.\n", + "* **Scoring** and deploying.\n", + "\n", + "All of these tasks will be performed on the New York City Taxi Cab dataset." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup cluster" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -39,37 +53,101 @@ "## Get and pre-process data" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This example will mirror the Kaggle \"[NYC Taxi Trip Duration][1]\" example with different data.\n", + "\n", + "This data has 84 million taxi rides.\n", + "\n", + "[1]:https://www.kaggle.com/c/nyc-taxi-trip-duration/" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import dask.dataframe as dd\n", + "\n", + "features = [\"passenger_count\", \"trip_distance\", \"RatecodeID\", \"payment_type\", \"fare_amount\"]\n", + "output = [\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"]\n", "df = dd.read_csv(\n", " \"s3://nyc-tlc/trip data/yellow_tripdata_2019-*.csv\", \n", - " parse_dates=[\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"],\n", + " parse_dates=output,\n", + " usecols=features + output,\n", " dtype={\n", - " \"VendorID\": \"UInt8\",\n", " \"passenger_count\": \"UInt8\",\n", " \"RatecodeID\": \"UInt8\",\n", - " \"store_and_fwd_flag\": \"category\",\n", - " \"PULocationID\": \"UInt16\",\n", - " \"DOLocationID\": \"UInt16\", \n", " \"payment_type\": \"UInt8\",\n", " },\n", " blocksize=\"16 MiB\",\n", ")\n", + "df = df.persist()\n", "\n", - "data = df[[\"passenger_count\", \"trip_distance\", \"RatecodeID\", \"payment_type\", \"fare_amount\"]]\n", - "data = data.fillna(0)\n", - "\n", - "labels = (df.tip_amount / df.fare_amount) > 0.25\n", - "labels = labels.fillna(False)\n", + "data = df[features]\n", + "data = data.fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "durations = (df[\"tpep_dropoff_datetime\"] - df[\"tpep_pickup_datetime\"]).dt.total_seconds() / 60 # minutes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "84399019" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(durations)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "from dask_ml.preprocessing import OneHotEncoder\n", + "rates = df[\"RatecodeID\"]\n", "\n", + "# Difficulty with this command\n", + "# rates_flags = OneHotEncoder().fit_transform((rates * 1.0).to_dask_array(lengths=True).reshape(-1, 1))\n", + "## After that's done, I'd stick df and rate_flags together and call that the training set\n", + "## It might be simpler to skip this cell" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ "from dask_ml.model_selection import train_test_split\n", "\n", - "X_train, X_test, y_train, y_test = train_test_split(data, labels, shuffle=True)\n", + "features = data.to_dask_array(lengths=True) # because MLPRegressor doesn't support dataframes\n", + "output = durations.to_dask_array(lengths=True)\n", + "X_train, X_test, y_train, y_test = train_test_split(features, output, shuffle=True)\n", + "\n", + "# persist the data so it's not re-computed\n", "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" ] }, @@ -77,28 +155,143 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Train model" + "## Define model and hyperparameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's use Scikit-Learn's neural network as a stand-in for a more complicated model that needs GPUs.\n", + "\n", + "If desired, [PyTorch] can be used seamlessly in Dask-ML through the Scikit-Learn wrapper [skorch]. PyTorch is a popular deep learning that has strong GPU support, and Skorch is a simple wrapper that provides a Scikit-Learn API for PyTorch.\n", + "\n", + "[PyTorch]:https://pytorch.org/\n", + "[skorch]:https://skorch.readthedocs.io/en/stable/" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ - "from sklearn.linear_model import SGDClassifier\n", - "from dask_ml.model_selection import HyperbandSearchCV\n", + "from sklearn.neural_network import MLPRegressor\n", "from scipy.stats import uniform, loguniform\n", "\n", + "# Input: XXX features\n", + "# Output: 1 scalar, estimated trip duration\n", + "model = MLPRegressor()\n", + "\n", + "params = {\n", + " \"hidden_layer_sizes\": [\n", + " (100, ),\n", + " (50, ) * 2,\n", + " (34, 33, 33),\n", + " (25, ) * 4,\n", + " (20, ) * 5,\n", + " (10, ) * 10,\n", + " ], # 100 neurons; how much does width/depth help?\n", + " \"activation\": [\"logistic\", \"tanh\", \"relu\"],\n", + " \"alpha\": loguniform(1e-5, 1e-3),\n", + " \"batch_size\": [128, 256, 512, 1024],\n", + " \"learning_rate_init\": loguniform(1e-4, 1e-2),\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of these parameters control model architecture, execpt for two basic optimizatino parameters, `batch_size` and `learning_rate_init`. They control finding the best model of a particular architecture." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Find the best hyperparameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our search is \"compute constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", "\n", - "clf = SGDClassifier(tol=1e-3, penalty='elasticnet', random_state=0)\n", + "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`. To use this class, we need to know two items:\n", "\n", - "params = {'alpha': loguniform(1e-2, 1e0), # or np.logspace\n", - " 'l1_ratio': uniform(0, 1)} # or np.linspace\n", + "* `n_params`, the (approximate) number of parameters to sample.\n", + "* `n_examples`, the largest number of examples any model will see.\n", "\n", - "search = HyperbandSearchCV(clf, params, max_iter=81, random_state=0)\n", + "[2]:https://ml.dask.org/hyper-parameter-search.html" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [], + "source": [ + "from dask_ml.model_selection import HyperbandSearchCV\n", "\n", - "search.fit(X_train, y_train, classes=[0, 1]);" + "n_params = 25\n", + "n_examples = 1e6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`HyperbandSearchCV` comes with a rule-of-thumb to computer the inputs:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "max_iter = n_params # how many partial_fit calls?\n", + "chunksize = n_examples // n_params # how many examples does each partial_fit call see?\n", + "\n", + "X_train2 = X_train.rechunk(chunks=(chunksize, -1))\n", + "y_train2 = y_train.rechunk(chunks=chunksize)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's run the search. Because this is an initial search, let's set `aggressiveness=4`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0msearch\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mHyperbandSearchCV\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmax_iter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mn_params\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0msearch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX_train2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my_train2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mclasses\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 608\u001b[0m \u001b[0mAdditional\u001b[0m \u001b[0mpartial\u001b[0m \u001b[0mfit\u001b[0m \u001b[0mkeyword\u001b[0m \u001b[0marguments\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mestimator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 609\u001b[0m \"\"\"\n\u001b[0;32m--> 610\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdefault_client\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msync\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_fit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 611\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 612\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mif_delegate_has_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"best_estimator_\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"estimator\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/distributed/client.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 830\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 831\u001b[0m return sync(\n\u001b[0;32m--> 832\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcallback_timeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 833\u001b[0m )\n\u001b[1;32m 834\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 334\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 335\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_set\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 336\u001b[0;31m \u001b[0me\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 337\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 338\u001b[0m \u001b[0mtyp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/anaconda3/lib/python3.7/threading.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0msignaled\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_flag\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 551\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msignaled\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 552\u001b[0;31m \u001b[0msignaled\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cond\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 553\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msignaled\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 554\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/anaconda3/lib/python3.7/threading.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 298\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtimeout\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 300\u001b[0;31m \u001b[0mgotit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwaiter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0macquire\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 301\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[0mgotit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwaiter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0macquire\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + ] + } + ], + "source": [ + "search = HyperbandSearchCV(model, params, max_iter=n_params, aggressiveness=4, random_state=0)\n", + "\n", + "search.fit(X_train2, y_train2, classes=[0, 1]);" ] }, { @@ -121,7 +314,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## What if we just sampled instead?" + "We can also obtain the best estimator through the `best_estimator_` attribute:" ] }, { @@ -130,10 +323,14 @@ "metadata": {}, "outputs": [], "source": [ - "clf.fit(\n", - " X_train.sample(frac=0.01, random_state=123).compute(), \n", - " y_train.sample(frac=0.01, random_state=123).compute()\n", - ")" + "search.best_estimator_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This means we can score on the entire dataset:" ] }, { @@ -142,25 +339,103 @@ "metadata": {}, "outputs": [], "source": [ - "clf.score(\n", - " X_test.sample(frac=0.01, random_state=123).compute(), \n", - " y_test.sample(frac=0.01, random_state=123).compute()\n", - ")" + "from dask_ml.wrappers import ParallelPostFit\n", + "deployed_model = ParallelPostFit(search.best_estimator_)\n", + "deployed_model.score(X_test, y_test)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Depending on our business needs, we maybe didn't need to do all of this :)" + "`HyperbandSearchCV` and the like mirror the Scikit-Learn model selection interface, so all attributes of Scikit-Learn's [RandomizedSearchCV][rscv] are available:\n", + "\n", + "[rscv]:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "search.best_score_" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "search.best_params_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Why not simply sampling instead?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sampling solves the memory issues:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X_train_small = X_train.sample(frac=0.01, random_state=123).compute()\n", + "y_train_small = y_train.sample(frac=0.01, random_state=123).compute()\n", + "\n", + "X_train_small # NumPy ndarray; must fit in memory" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But `HyperbandSearchCV` is meant for computationally-constrained problems, regardless of their memory usage (which [Dask-ML's documentation on hyperparameter searches][2] also indicate). `HyperbandSearchCV` would still be relevant:\n", + "\n", + "[2]:https://ml.dask.org/hyper-parameter-search.html" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "search = HyperbandSearchCV(model, params, max_iter=81, random_state=0)\n", + "search.fit(X_train_small, y_train_small, classes=[0, 1]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:coiled-coiled-default]", + "display_name": "Python [conda env:root] *", "language": "python", - "name": "conda-env-coiled-coiled-default-py" + "name": "conda-root-py" }, "language_info": { "codemirror_mode": { @@ -172,7 +447,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.7.4" } }, "nbformat": 4, From a49ccdaa330f5fb644dc38e9480b9dee72128ebf Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 27 Jul 2020 22:46:19 -0500 Subject: [PATCH 05/11] Note about when to use IncrementalSearchCV --- hyper-parameter-optimization.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index 7d88c7b..1ed73b8 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -420,7 +420,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs." + "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs.\n", + "\n", + "If we had a simpler model and a massive dataset, `IncrementalSearchCV` is recommended. It mirrors Scikit-Learn's `RandomizedSearchCV` but works on Dask Arrays/Dataframes, both of which can be larger than memory." ] }, { From 0dd0a060c5d8ec377de910874129aa88baa7265d Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 27 Jul 2020 22:50:57 -0500 Subject: [PATCH 06/11] wording --- hyper-parameter-optimization.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index 1ed73b8..bc1b61d 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -59,7 +59,7 @@ "source": [ "This example will mirror the Kaggle \"[NYC Taxi Trip Duration][1]\" example with different data.\n", "\n", - "This data has 84 million taxi rides.\n", + "These data have records on 84 million taxi rides.\n", "\n", "[1]:https://www.kaggle.com/c/nyc-taxi-trip-duration/" ] @@ -217,7 +217,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Our search is \"compute constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", + "Our search is \"computationally-constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", "\n", "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`. To use this class, we need to know two items:\n", "\n", From 14c41c04855d8f275ba14caa0d8b429e87af9708 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 27 Jul 2020 22:57:41 -0500 Subject: [PATCH 07/11] Add visualiation --- hyper-parameter-optimization.ipynb | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index bc1b61d..0bf66c6 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -371,6 +371,78 @@ "search.best_params_" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What does the error distribution look like?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = deployed_model.predict(X_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Prediction error')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "image/png": { + "height": 277, + "width": 388 + }, + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "## This cell was run after the fact from a fresh notebook; rerun to \n", + "## y_test := as above. Ground truth trip durations\n", + "## y_pred := model output (as in the cell above?)\n", + "\n", + "y_test = np.random.uniform(0, 1, size=1000)\n", + "y_pred = np.random.uniform(0, 1, size=1000)\n", + "\n", + "err = np.abs(y_pred - y_test)\n", + "ax = pd.Series(err).plot.hist()\n", + "ax.set_xlabel(\"Prediction error (minutes)\")\n", + "ax.set_title(\"Prediction error\")" + ] + }, { "cell_type": "markdown", "metadata": {}, From fc2a73e63db2ab91582cd16368ae8e68ab7eb18e Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 30 Jul 2020 17:06:50 -0500 Subject: [PATCH 08/11] pytorch --- hyper-parameter-optimization.ipynb | 481 +++++++++++++++++++---------- 1 file changed, 321 insertions(+), 160 deletions(-) diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb index 0bf66c6..ecf26bf 100644 --- a/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimization.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -66,34 +66,42 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import dask.dataframe as dd\n", "\n", - "features = [\"passenger_count\", \"trip_distance\", \"RatecodeID\", \"payment_type\", \"fare_amount\"]\n", + "features = [\"passenger_count\", \"trip_distance\", \"fare_amount\"]\n", + "categorical_features = [\"RatecodeID\", \"payment_type\"]\n", "output = [\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"]\n", + "\n", "df = dd.read_csv(\n", " \"s3://nyc-tlc/trip data/yellow_tripdata_2019-*.csv\", \n", " parse_dates=output,\n", - " usecols=features + output,\n", + " usecols=features + categorical_features + output,\n", " dtype={\n", " \"passenger_count\": \"UInt8\",\n", - " \"RatecodeID\": \"UInt8\",\n", - " \"payment_type\": \"UInt8\",\n", + " \"RatecodeID\": \"category\",\n", + " \"payment_type\": \"category\",\n", " },\n", " blocksize=\"16 MiB\",\n", ")\n", + "\n", + "# one hot encode the categorical columns;\n", + "# if df[\"foo\"].unique() == [1, 3, 4], add columns foo_1, foo_3, foo_4\n", + "df = dd.get_dummies(df, columns=categorical_features)\n", + "\n", + "# persist so only download once\n", "df = df.persist()\n", "\n", - "data = df[features]\n", + "data = df[[c for c in df.columns if c not in output]]\n", "data = data.fillna(0)" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -104,47 +112,13 @@ "cell_type": "code", "execution_count": 4, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "84399019" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(durations)" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "from dask_ml.preprocessing import OneHotEncoder\n", - "rates = df[\"RatecodeID\"]\n", - "\n", - "# Difficulty with this command\n", - "# rates_flags = OneHotEncoder().fit_transform((rates * 1.0).to_dask_array(lengths=True).reshape(-1, 1))\n", - "## After that's done, I'd stick df and rate_flags together and call that the training set\n", - "## It might be simpler to skip this cell" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "metadata": {}, "outputs": [], "source": [ "from dask_ml.model_selection import train_test_split\n", + "import dask\n", "\n", - "features = data.to_dask_array(lengths=True) # because MLPRegressor doesn't support dataframes\n", - "output = durations.to_dask_array(lengths=True)\n", + "features = data.to_dask_array(lengths=True).astype(\"float32\")\n", + "output = durations.to_dask_array(lengths=True).astype(\"float32\")\n", "X_train, X_test, y_train, y_test = train_test_split(features, output, shuffle=True)\n", "\n", "# persist the data so it's not re-computed\n", @@ -162,136 +136,207 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's use Scikit-Learn's neural network as a stand-in for a more complicated model that needs GPUs.\n", + "Let's use a simple neural network from [PyTorch] usin Skorch, a simple wrapper that provides a Scikit-Learn API for PyTorch.\n", "\n", - "If desired, [PyTorch] can be used seamlessly in Dask-ML through the Scikit-Learn wrapper [skorch]. PyTorch is a popular deep learning that has strong GPU support, and Skorch is a simple wrapper that provides a Scikit-Learn API for PyTorch.\n", + "This network is only small for demonstration. If desired, we could use much larger networks on GPUs.\n", "\n", "[PyTorch]:https://pytorch.org/\n", "[skorch]:https://skorch.readthedocs.io/en/stable/" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If desired, this model could use GPUs." + ] + }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "from sklearn.neural_network import MLPRegressor\n", - "from scipy.stats import uniform, loguniform\n", + "import torch.optim as optim\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", "\n", - "# Input: XXX features\n", - "# Output: 1 scalar, estimated trip duration\n", - "model = MLPRegressor()\n", + "class HiddenLayerNet(nn.Module):\n", + " def __init__(self, n_features=10, n_outputs=1, n_hidden=100, activation=\"relu\"):\n", + " super().__init__()\n", + " self.fc1 = nn.Linear(n_features, n_hidden)\n", + " self.fc2 = nn.Linear(n_hidden, n_outputs)\n", + " self.activation = getattr(F, activation)\n", "\n", - "params = {\n", - " \"hidden_layer_sizes\": [\n", - " (100, ),\n", - " (50, ) * 2,\n", - " (34, 33, 33),\n", - " (25, ) * 4,\n", - " (20, ) * 5,\n", - " (10, ) * 10,\n", - " ], # 100 neurons; how much does width/depth help?\n", - " \"activation\": [\"logistic\", \"tanh\", \"relu\"],\n", - " \"alpha\": loguniform(1e-5, 1e-3),\n", - " \"batch_size\": [128, 256, 512, 1024],\n", - " \"learning_rate_init\": loguniform(1e-4, 1e-2),\n", - "}" + " def forward(self, x, **kwargs):\n", + " return self.fc2(self.activation(self.fc1(x)))" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 6, "metadata": {}, + "outputs": [], "source": [ - "All of these parameters control model architecture, execpt for two basic optimizatino parameters, `batch_size` and `learning_rate_init`. They control finding the best model of a particular architecture." + "from torch_model import HiddenLayerNet" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 7, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(900, 14)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "## Find the best hyperparameters" + "X_train.shape" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 8, "metadata": {}, + "outputs": [], "source": [ - "Our search is \"computationally-constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", - "\n", - "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`. To use this class, we need to know two items:\n", + "from skorch import NeuralNetRegressor\n", "\n", - "* `n_params`, the (approximate) number of parameters to sample.\n", - "* `n_examples`, the largest number of examples any model will see.\n", + "niceties = {\n", + " \"callbacks\": False,\n", + " \"warm_start\": True,\n", + " \"train_split\": None,\n", + " \"max_epochs\": 1,\n", + "}\n", "\n", - "[2]:https://ml.dask.org/hyper-parameter-search.html" + "model = NeuralNetRegressor(\n", + " module=HiddenLayerNet,\n", + " module__n_features=X_train.shape[1],\n", + " optimizer=optim.SGD,\n", + " criterion=nn.MSELoss,\n", + " lr=0.0001,\n", + " **niceties,\n", + ")" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ - "from dask_ml.model_selection import HyperbandSearchCV\n", + "from scipy.stats import loguniform, uniform\n", "\n", - "n_params = 25\n", - "n_examples = 1e6" + "params = {\n", + " \"module__activation\": [\"relu\", \"elu\", \"softsign\", \"leaky_relu\", \"rrelu\"],\n", + " \"batch_size\": [32, 64, 128, 256],\n", + " \"optimizer__lr\": loguniform(1e-4, 1e-3),\n", + " \"optimizer__weight_decay\": loguniform(1e-6, 1e-3),\n", + " \"optimizer__momentum\": uniform(0, 1),\n", + " \"optimizer__nesterov\": [True],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of these parameters control model architecture, execpt for two basic optimizatino parameters, `batch_size` and `learning_rate_init`. They control finding the best model of a particular architecture." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Find the best hyperparameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "`HyperbandSearchCV` comes with a rule-of-thumb to computer the inputs:" + "Our search is \"computationally-constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", + "\n", + "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`.\n", + "\n", + "[2]:https://ml.dask.org/hyper-parameter-search.html" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "max_iter = n_params # how many partial_fit calls?\n", - "chunksize = n_examples // n_params # how many examples does each partial_fit call see?\n", - "\n", - "X_train2 = X_train.rechunk(chunks=(chunksize, -1))\n", - "y_train2 = y_train.rechunk(chunks=chunksize)" + "from dask_ml.model_selection import HyperbandSearchCV\n", + "search = HyperbandSearchCV(model, params, random_state=42, verbose=True, max_iter=9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's run the search. Because this is an initial search, let's set `aggressiveness=4`:\n" + "By default, `HyperbandSearchCV` will call `partial_fit` on each chunk of the Dask Array. `HyperbandSearchCV`'s rule of thumb specifies how to train for longer or sample more parameters." ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 12, "metadata": {}, "outputs": [ { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0msearch\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mHyperbandSearchCV\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmax_iter\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mn_params\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrandom_state\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0msearch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX_train2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my_train2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mclasses\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/dask_ml/model_selection/_incremental.py\u001b[0m in \u001b[0;36mfit\u001b[0;34m(self, X, y, **fit_params)\u001b[0m\n\u001b[1;32m 608\u001b[0m \u001b[0mAdditional\u001b[0m \u001b[0mpartial\u001b[0m \u001b[0mfit\u001b[0m \u001b[0mkeyword\u001b[0m \u001b[0marguments\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mestimator\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 609\u001b[0m \"\"\"\n\u001b[0;32m--> 610\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mdefault_client\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msync\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_fit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mfit_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 611\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 612\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mif_delegate_has_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdelegate\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"best_estimator_\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"estimator\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/distributed/client.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(self, func, asynchronous, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 830\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 831\u001b[0m return sync(\n\u001b[0;32m--> 832\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mloop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcallback_timeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcallback_timeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 833\u001b[0m )\n\u001b[1;32m 834\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/site-packages/distributed/utils.py\u001b[0m in \u001b[0;36msync\u001b[0;34m(loop, func, callback_timeout, *args, **kwargs)\u001b[0m\n\u001b[1;32m 334\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 335\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_set\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 336\u001b[0;31m \u001b[0me\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 337\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 338\u001b[0m \u001b[0mtyp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0merror\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/threading.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 550\u001b[0m \u001b[0msignaled\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_flag\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 551\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0msignaled\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 552\u001b[0;31m \u001b[0msignaled\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_cond\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 553\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msignaled\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 554\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m~/anaconda3/lib/python3.7/threading.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self, timeout)\u001b[0m\n\u001b[1;32m 298\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 299\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mtimeout\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 300\u001b[0;31m \u001b[0mgotit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwaiter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0macquire\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 301\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 302\u001b[0m \u001b[0mgotit\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mwaiter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0macquire\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " + "name": "stdout", + "output_type": "stream", + "text": [ + "[CV, bracket=2] creating 9 models\n", + "[CV, bracket=1] creating 5 models\n", + "[CV, bracket=0] creating 3 models\n", + "[CV, bracket=0] For training there are between 360 and 360 examples in each chunk\n", + "[CV, bracket=1] For training there are between 360 and 360 examples in each chunk\n", + "[CV, bracket=0] validation score of -0.3191 received after 1 partial_fit calls\n", + "[CV, bracket=2] For training there are between 360 and 360 examples in each chunk\n", + "[CV, bracket=1] validation score of 0.0170 received after 1 partial_fit calls\n", + "[CV, bracket=1] validation score of 0.0322 received after 3 partial_fit calls\n", + "[CV, bracket=2] validation score of 0.2228 received after 1 partial_fit calls\n", + "[CV, bracket=2] validation score of -0.7214 received after 3 partial_fit calls\n", + "[CV, bracket=1] validation score of 0.0183 received after 9 partial_fit calls\n", + "[CV, bracket=0] validation score of -0.2677 received after 9 partial_fit calls\n", + "[CV, bracket=2] validation score of -0.5336 received after 9 partial_fit calls\n" ] + }, + { + "data": { + "text/plain": [ + "HyperbandSearchCV(estimator=[uninitialized](\n", + " module=,\n", + " module__n_features=14,\n", + "),\n", + " max_iter=9,\n", + " parameters={'batch_size': [32, 64, 128, 256],\n", + " 'module__activation': ['relu', 'elu', 'softsign',\n", + " 'leaky_relu', 'rrelu'],\n", + " 'optimizer__lr': ,\n", + " 'optimizer__momentum': ,\n", + " 'optimizer__nesterov': [True],\n", + " 'optimizer__weight_decay': },\n", + " random_state=42, verbose=True)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "search = HyperbandSearchCV(model, params, max_iter=n_params, aggressiveness=4, random_state=0)\n", - "\n", - "search.fit(X_train2, y_train2, classes=[0, 1]);" + "y_train2 = y_train.reshape(-1, 1).persist()\n", + "_ = search.fit(X_train, y_train2)" ] }, { @@ -301,27 +346,81 @@ "## Score" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`HyperbandSearchCV` and the like mirror the Scikit-Learn model selection interface, so all attributes of Scikit-Learn's [RandomizedSearchCV][rscv] are available:\n", + "\n", + "[rscv]:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.018286365127180515" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "search.score(X_test.sample(frac=0.1, random_state=123), y_test.sample(frac=0.1, random_state=123))" + "search.best_score_" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 18, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'batch_size': 32,\n", + " 'module__activation': 'rrelu',\n", + " 'optimizer__lr': 0.0002668107973843001,\n", + " 'optimizer__momentum': 0.5920831762255758,\n", + " 'optimizer__nesterov': True,\n", + " 'optimizer__weight_decay': 3.6363529586270234e-05}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "We can also obtain the best estimator through the `best_estimator_` attribute:" + "search.best_params_" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[initialized](\n", + " module_=HiddenLayerNet(\n", + " (fc1): Linear(in_features=14, out_features=100, bias=True)\n", + " (fc2): Linear(in_features=100, out_features=1, bias=True)\n", + " ),\n", + ")" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "search.best_estimator_" ] @@ -330,14 +429,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This means we can score on the entire dataset:" + "This means we can deploy the best model and score on the entire dataset:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.33630241003610284" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from dask_ml.wrappers import ParallelPostFit\n", "deployed_model = ParallelPostFit(search.best_estimator_)\n", @@ -348,77 +458,136 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`HyperbandSearchCV` and the like mirror the Scikit-Learn model selection interface, so all attributes of Scikit-Learn's [RandomizedSearchCV][rscv] are available:\n", - "\n", - "[rscv]:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html" + "## Visualization" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "search.best_score_" + "What does the error distribution look like on this larger dataset?" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 103, "metadata": {}, "outputs": [], "source": [ - "search.best_params_" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization" + "y_pred = deployed_model.predict(X_test)\n", + "y_pred = y_pred.flatten()" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 108, "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Array Chunk
Bytes 800 B 400 B
Shape (100,) (50,)
Count 4 Tasks 2 Chunks
Type int64 numpy.ndarray
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " 100\n", + " 1\n", + "\n", + "
" + ], + "text/plain": [ + "dask.array<_predict, shape=(100,), dtype=int64, chunksize=(50,), chunktype=numpy.ndarray>" + ] + }, + "execution_count": 108, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "What does the error distribution look like?" + "y_pred" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 109, "metadata": {}, "outputs": [], "source": [ - "y_pred = deployed_model.predict(X_test)" + "import numpy as np\n", + "import pandas as pd\n", + "import dask.array as da\n", + "\n", + "err = np.abs(y_pred - y_test)\n", + "max_min_err = 20\n", + "vals, edges = da.histogram(err, range=(0, max_min_err), bins=max_min_err)\n", + "vals, edges = dask.compute(vals, edges)" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 110, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Text(0.5, 1.0, 'Prediction error')" + "(100,)" ] }, - "execution_count": 7, + "execution_count": 110, "metadata": {}, "output_type": "execute_result" - }, + } + ], + "source": [ + "y_test.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": {}, + "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "image/png": { - "height": 277, + "height": 290, "width": 388 }, "needs_background": "light" @@ -427,20 +596,12 @@ } ], "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "\n", - "## This cell was run after the fact from a fresh notebook; rerun to \n", - "## y_test := as above. Ground truth trip durations\n", - "## y_pred := model output (as in the cell above?)\n", - "\n", - "y_test = np.random.uniform(0, 1, size=1000)\n", - "y_pred = np.random.uniform(0, 1, size=1000)\n", - "\n", - "err = np.abs(y_pred - y_test)\n", - "ax = pd.Series(err).plot.hist()\n", - "ax.set_xlabel(\"Prediction error (minutes)\")\n", - "ax.set_title(\"Prediction error\")" + "df = pd.DataFrame({\"vals\": vals, \"edges\": edges[1:]})\n", + "ax = df.plot.bar(x=\"edges\", y=\"vals\", width=1)\n", + "ax.set_ylabel(\"Frequency\")\n", + "ax.set_xlabel(\"Maximum error (minutes)\")\n", + "ax.set_title(\"Prediction error\")\n", + "ax.legend_.remove()" ] }, { @@ -507,9 +668,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python [conda env:root] *", + "display_name": "Python [conda env:skorch]", "language": "python", - "name": "conda-root-py" + "name": "conda-env-skorch-py" }, "language_info": { "codemirror_mode": { @@ -521,7 +682,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.8.5" } }, "nbformat": 4, From 848710dbaaa1590157d470a9e09a3c627d948725 Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Wed, 5 Aug 2020 15:21:18 -0500 Subject: [PATCH 09/11] Some updates --- hyper-parameter-optimmization/environment.yml | 17 + .../hyper-parameter-optimization.ipynb | 1160 +++++++++++++++++ hyper-parameter-optimmization/torch_model.py | 13 + 3 files changed, 1190 insertions(+) create mode 100644 hyper-parameter-optimmization/environment.yml create mode 100644 hyper-parameter-optimmization/hyper-parameter-optimization.ipynb create mode 100644 hyper-parameter-optimmization/torch_model.py diff --git a/hyper-parameter-optimmization/environment.yml b/hyper-parameter-optimmization/environment.yml new file mode 100644 index 0000000..b908644 --- /dev/null +++ b/hyper-parameter-optimmization/environment.yml @@ -0,0 +1,17 @@ +name: pytorch +channels: + - conda-forge + - pytorch + - defaults +dependencies: + - python=3.7 + - dask + - numpy + - pandas + - coiled + - dask-ml + - skorch + - scipy + - matplotlib + - pytorch>1.1.0 + - s3fs \ No newline at end of file diff --git a/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb b/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb new file mode 100644 index 0000000..46dccc4 --- /dev/null +++ b/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb @@ -0,0 +1,1160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Hyperparameter Optimization with Dask and Coiled\n", + "\n", + "This example will walk through the following:\n", + "\n", + "* **Getting and processing the data.**\n", + "* **Defining a model and parameters.**\n", + "* **Finding the best parameters,** and some details on why we're using the chosen search algorithm.\n", + "* **Scoring** and deploying.\n", + "\n", + "All of these tasks will be performed on the New York City Taxi Cab dataset." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup cluster" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Creating Cluster. This takes about a minute ... \r" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

Client

\n", + "\n", + "
\n", + "

Cluster

\n", + "
    \n", + "
  • Workers: 0
  • \n", + "
  • Cores: 0
  • \n", + "
  • Memory: 0 B
  • \n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import coiled\n", + "import dask.distributed\n", + "\n", + "cluster = coiled.Cluster(\n", + " n_workers=20,\n", + " configuration=\"coiled-examples/pytorch\"\n", + ")\n", + "client = dask.distributed.Client(cluster)\n", + "\n", + "client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### ☝️ Don’t forget to click the \"Dashboard\" link above to view the cluster dashboard!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get and pre-process data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This example will mirror the Kaggle \"[NYC Taxi Trip Duration][1]\" example with different data.\n", + "\n", + "These data have records on 84 million taxi rides.\n", + "\n", + "[1]:https://www.kaggle.com/c/nyc-taxi-trip-duration/" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import dask.dataframe as dd\n", + "\n", + "features = [\"passenger_count\", \"trip_distance\", \"fare_amount\"]\n", + "categorical_features = [\"RatecodeID\", \"payment_type\"]\n", + "output = [\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"]\n", + "\n", + "df = dd.read_csv(\n", + " \"s3://nyc-tlc/trip data/yellow_tripdata_2019-*.csv\", \n", + " parse_dates=output,\n", + " usecols=features + categorical_features + output,\n", + " dtype={\n", + " \"passenger_count\": \"UInt8\",\n", + " \"RatecodeID\": \"category\",\n", + " \"payment_type\": \"category\",\n", + " },\n", + " blocksize=\"16 MiB\",\n", + ")\n", + "\n", + "# one hot encode the categorical columns\n", + "df = df.categorize(categorical_features)\n", + "df = dd.get_dummies(df, columns=categorical_features)\n", + "\n", + "# persist so only download once\n", + "df = df.persist()\n", + "\n", + "data = df[[c for c in df.columns if c not in output]]\n", + "data = data.fillna(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "durations = (df[\"tpep_dropoff_datetime\"] - df[\"tpep_pickup_datetime\"]).dt.total_seconds() / 60 # minutes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "from dask_ml.model_selection import train_test_split\n", + "import dask\n", + "\n", + "features = data.to_dask_array(lengths=True).astype(\"float32\")\n", + "output = durations.to_dask_array(lengths=True).astype(\"float32\")\n", + "X_train, X_test, y_train, y_test = train_test_split(features, output, shuffle=True)\n", + "\n", + "# persist the data so it's not re-computed\n", + "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define model and hyperparameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's use a simple neural network from [PyTorch] using [Skorch], a simple wrapper that provides a Scikit-Learn API for PyTorch.\n", + "\n", + "This network is only small for demonstration. If desired, we could use much larger networks on GPUs.\n", + "\n", + "[PyTorch]:https://pytorch.org/\n", + "[skorch]:https://skorch.readthedocs.io/en/stable/" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Import our HiddenLayerNet pytorch model from a local torch_model.py module\n", + "from torch_model import HiddenLayerNet\n", + "# Send module with HiddenLayerNet to workers on cluster\n", + "client.upload_file(\"torch_model.py\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "import torch.optim as optim\n", + "import torch.nn as nn\n", + "import torch.nn.functional as F\n", + "\n", + "class HiddenLayerNet(nn.Module):\n", + " def __init__(self, n_features=10, n_outputs=1, n_hidden=100, activation=\"relu\"):\n", + " super().__init__()\n", + " self.fc1 = nn.Linear(n_features, n_hidden)\n", + " self.fc2 = nn.Linear(n_hidden, n_outputs)\n", + " self.activation = getattr(F, activation)\n", + "\n", + " def forward(self, x, **kwargs):\n", + " return self.fc2(self.activation(self.fc1(x)))" + ] + } + ], + "source": [ + "# Print contents of torch_model.py module\n", + "!cat torch_model.py" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import torch.optim as optim\n", + "import torch.nn as nn\n", + "from skorch import NeuralNetRegressor\n", + "\n", + "niceties = {\n", + " \"callbacks\": False,\n", + " \"warm_start\": True,\n", + " \"train_split\": None,\n", + " \"max_epochs\": 1,\n", + "}\n", + "\n", + "model = NeuralNetRegressor(\n", + " module=HiddenLayerNet,\n", + " module__n_features=X_train.shape[1],\n", + " optimizer=optim.SGD,\n", + " criterion=nn.MSELoss,\n", + " lr=0.0001,\n", + " **niceties,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.stats import loguniform, uniform\n", + "\n", + "params = {\n", + " \"module__activation\": [\"relu\", \"elu\", \"softsign\", \"leaky_relu\", \"rrelu\"],\n", + " \"batch_size\": [32, 64, 128, 256],\n", + "# \"optimizer__lr\": loguniform(1e-4, 1e-3),\n", + "# \"optimizer__weight_decay\": loguniform(1e-6, 1e-3),\n", + "# \"optimizer__momentum\": uniform(0, 1),\n", + "# \"optimizer__nesterov\": [True],\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "All of these parameters control model architecture, execpt for two basic optimizatino parameters, `batch_size` and `learning_rate_init`. They control finding the best model of a particular architecture." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Find the best hyperparameters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our search is \"computationally-constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", + "\n", + "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`.\n", + "\n", + "[2]:https://ml.dask.org/hyper-parameter-search.html" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "from dask_ml.model_selection import HyperbandSearchCV\n", + "search = HyperbandSearchCV(model, params, random_state=2, verbose=True,\n", + " max_iter=2,\n", + "# max_iter=9,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, `HyperbandSearchCV` will call `partial_fit` on each chunk of the Dask Array. `HyperbandSearchCV`'s rule of thumb specifies how to train for longer or sample more parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[CV, bracket=0] creating 1 models\n", + "[CV, bracket=0] For training there are between 2756 and 169772 examples in each chunk\n", + "[CV, bracket=0] validation score of 0.0210 received after 1 partial_fit calls\n", + "[CV, bracket=0] validation score of 0.0270 received after 2 partial_fit calls\n" + ] + }, + { + "data": { + "text/plain": [ + "HyperbandSearchCV(estimator=[uninitialized](\n", + " module=,\n", + " module__n_features=15,\n", + "),\n", + " max_iter=2,\n", + " parameters={'batch_size': [32, 64, 128, 256],\n", + " 'module__activation': ['relu', 'elu', 'softsign',\n", + " 'leaky_relu', 'rrelu']},\n", + " random_state=2, verbose=True)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_train2 = y_train.reshape(-1, 1).persist()\n", + "search.fit(X_train, y_train2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`HyperbandSearchCV` and the like mirror the Scikit-Learn model selection interface, so all attributes of Scikit-Learn's [RandomizedSearchCV][rscv] are available:\n", + "\n", + "[rscv]:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.02695897736664199" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.best_score_" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'module__activation': 'softsign', 'batch_size': 128}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.best_params_" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[initialized](\n", + " module_=HiddenLayerNet(\n", + " (fc1): Linear(in_features=15, out_features=100, bias=True)\n", + " (fc2): Linear(in_features=100, out_features=1, bias=True)\n", + " ),\n", + ")" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "search.best_estimator_" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This means we can deploy the best model and score on the entire dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.02479510859559464" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dask_ml.wrappers import ParallelPostFit\n", + "deployed_model = ParallelPostFit(search.best_estimator_)\n", + "deployed_model.score(X_test, y_test)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What does the error distribution look like on this larger dataset?" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "y_pred = deployed_model.predict(X_test)\n", + "y_pred = y_pred.flatten()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Array Chunk
Bytes 67.52 MB 151.23 kB
Shape (8440119,) (18904,)
Count 942 Tasks 471 Chunks
Type int64 numpy.ndarray
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " 8440119\n", + " 1\n", + "\n", + "
" + ], + "text/plain": [ + "dask.array<_predict, shape=(8440119,), dtype=int64, chunksize=(18904,), chunktype=numpy.ndarray>" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_pred" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import dask.array as da\n", + "\n", + "err = np.abs(y_pred - y_test)\n", + "max_min_err = 20\n", + "vals, edges = da.histogram(err, range=(0, max_min_err), bins=max_min_err)\n", + "vals, edges = dask.compute(vals, edges)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(8440119,)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_test.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "df = pd.DataFrame({\"vals\": vals, \"edges\": edges[1:]})\n", + "ax = df.plot.bar(x=\"edges\", y=\"vals\", width=1)\n", + "ax.set_ylabel(\"Frequency\")\n", + "ax.set_xlabel(\"Maximum error (minutes)\")\n", + "ax.set_title(\"Prediction error\")\n", + "ax.legend_.remove()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Why not simply sampling instead?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sampling solves the memory issues:" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "# X_train_small = data.sample(frac=0.01, random_state=123).to_dask_array().astype(\"float32\").compute()\n", + "# y_train_small = durations.sample(frac=0.01, random_state=123).to_dask_array().astype(\"float32\").compute()\n", + "\n", + "# X_train_small # NumPy ndarray; must fit in memory" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But `HyperbandSearchCV` is meant for computationally-constrained problems, regardless of their memory usage (which [Dask-ML's documentation on hyperparameter searches][2] also indicate). `HyperbandSearchCV` would still be relevant:\n", + "\n", + "[2]:https://ml.dask.org/hyper-parameter-search.html" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "# search = HyperbandSearchCV(model, params, max_iter=81, random_state=0)\n", + "# search.fit(X_train_small, y_train_small.reshape(-1, 1), classes=[0, 1]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs.\n", + "\n", + "If we had a simpler model and a massive dataset, `IncrementalSearchCV` is recommended. It mirrors Scikit-Learn's `RandomizedSearchCV` but works on Dask Arrays/Dataframes, both of which can be larger than memory." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.8" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/hyper-parameter-optimmization/torch_model.py b/hyper-parameter-optimmization/torch_model.py new file mode 100644 index 0000000..b6683f0 --- /dev/null +++ b/hyper-parameter-optimmization/torch_model.py @@ -0,0 +1,13 @@ +import torch.optim as optim +import torch.nn as nn +import torch.nn.functional as F + +class HiddenLayerNet(nn.Module): + def __init__(self, n_features=10, n_outputs=1, n_hidden=100, activation="relu"): + super().__init__() + self.fc1 = nn.Linear(n_features, n_hidden) + self.fc2 = nn.Linear(n_hidden, n_outputs) + self.activation = getattr(F, activation) + + def forward(self, x, **kwargs): + return self.fc2(self.activation(self.fc1(x))) \ No newline at end of file From b8b03198db5c1e4c19ffac25c4b94bf26ddddabd Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Wed, 5 Aug 2020 15:21:44 -0500 Subject: [PATCH 10/11] Remove old notebook --- hyper-parameter-optimization.ipynb | 690 ----------------------------- 1 file changed, 690 deletions(-) delete mode 100644 hyper-parameter-optimization.ipynb diff --git a/hyper-parameter-optimization.ipynb b/hyper-parameter-optimization.ipynb deleted file mode 100644 index ecf26bf..0000000 --- a/hyper-parameter-optimization.ipynb +++ /dev/null @@ -1,690 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example will walk through the following:\n", - "\n", - "* **Getting and processing the data.**\n", - "* **Defining a model and parameters.**\n", - "* **Finding the best parameters,** and some details on why we're using the chosen search algorithm.\n", - "* **Scoring** and deploying.\n", - "\n", - "All of these tasks will be performed on the New York City Taxi Cab dataset." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup cluster" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import coiled\n", - "import dask.distributed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cluster = coiled.Cluster(\n", - " n_workers=20, \n", - " configuration=\"coiled/default\", \n", - ")\n", - "client = dask.distributed.Client(cluster)\n", - "\n", - "client" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Get and pre-process data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This example will mirror the Kaggle \"[NYC Taxi Trip Duration][1]\" example with different data.\n", - "\n", - "These data have records on 84 million taxi rides.\n", - "\n", - "[1]:https://www.kaggle.com/c/nyc-taxi-trip-duration/" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import dask.dataframe as dd\n", - "\n", - "features = [\"passenger_count\", \"trip_distance\", \"fare_amount\"]\n", - "categorical_features = [\"RatecodeID\", \"payment_type\"]\n", - "output = [\"tpep_pickup_datetime\", \"tpep_dropoff_datetime\"]\n", - "\n", - "df = dd.read_csv(\n", - " \"s3://nyc-tlc/trip data/yellow_tripdata_2019-*.csv\", \n", - " parse_dates=output,\n", - " usecols=features + categorical_features + output,\n", - " dtype={\n", - " \"passenger_count\": \"UInt8\",\n", - " \"RatecodeID\": \"category\",\n", - " \"payment_type\": \"category\",\n", - " },\n", - " blocksize=\"16 MiB\",\n", - ")\n", - "\n", - "# one hot encode the categorical columns;\n", - "# if df[\"foo\"].unique() == [1, 3, 4], add columns foo_1, foo_3, foo_4\n", - "df = dd.get_dummies(df, columns=categorical_features)\n", - "\n", - "# persist so only download once\n", - "df = df.persist()\n", - "\n", - "data = df[[c for c in df.columns if c not in output]]\n", - "data = data.fillna(0)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "durations = (df[\"tpep_dropoff_datetime\"] - df[\"tpep_pickup_datetime\"]).dt.total_seconds() / 60 # minutes" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "from dask_ml.model_selection import train_test_split\n", - "import dask\n", - "\n", - "features = data.to_dask_array(lengths=True).astype(\"float32\")\n", - "output = durations.to_dask_array(lengths=True).astype(\"float32\")\n", - "X_train, X_test, y_train, y_test = train_test_split(features, output, shuffle=True)\n", - "\n", - "# persist the data so it's not re-computed\n", - "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define model and hyperparameters" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's use a simple neural network from [PyTorch] usin Skorch, a simple wrapper that provides a Scikit-Learn API for PyTorch.\n", - "\n", - "This network is only small for demonstration. If desired, we could use much larger networks on GPUs.\n", - "\n", - "[PyTorch]:https://pytorch.org/\n", - "[skorch]:https://skorch.readthedocs.io/en/stable/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If desired, this model could use GPUs." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "import torch.optim as optim\n", - "import torch.nn as nn\n", - "import torch.nn.functional as F\n", - "\n", - "class HiddenLayerNet(nn.Module):\n", - " def __init__(self, n_features=10, n_outputs=1, n_hidden=100, activation=\"relu\"):\n", - " super().__init__()\n", - " self.fc1 = nn.Linear(n_features, n_hidden)\n", - " self.fc2 = nn.Linear(n_hidden, n_outputs)\n", - " self.activation = getattr(F, activation)\n", - "\n", - " def forward(self, x, **kwargs):\n", - " return self.fc2(self.activation(self.fc1(x)))" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "from torch_model import HiddenLayerNet" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(900, 14)" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "X_train.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "from skorch import NeuralNetRegressor\n", - "\n", - "niceties = {\n", - " \"callbacks\": False,\n", - " \"warm_start\": True,\n", - " \"train_split\": None,\n", - " \"max_epochs\": 1,\n", - "}\n", - "\n", - "model = NeuralNetRegressor(\n", - " module=HiddenLayerNet,\n", - " module__n_features=X_train.shape[1],\n", - " optimizer=optim.SGD,\n", - " criterion=nn.MSELoss,\n", - " lr=0.0001,\n", - " **niceties,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.stats import loguniform, uniform\n", - "\n", - "params = {\n", - " \"module__activation\": [\"relu\", \"elu\", \"softsign\", \"leaky_relu\", \"rrelu\"],\n", - " \"batch_size\": [32, 64, 128, 256],\n", - " \"optimizer__lr\": loguniform(1e-4, 1e-3),\n", - " \"optimizer__weight_decay\": loguniform(1e-6, 1e-3),\n", - " \"optimizer__momentum\": uniform(0, 1),\n", - " \"optimizer__nesterov\": [True],\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "All of these parameters control model architecture, execpt for two basic optimizatino parameters, `batch_size` and `learning_rate_init`. They control finding the best model of a particular architecture." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Find the best hyperparameters" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Our search is \"computationally-constrained\" because (hypothetically) it requires GPUs and has a pretty complicated search space (in reality it has neither of those features). And obviously it's \"memory-constrained\" because the dataset doesn't fit in memory.\n", - "\n", - "[Dask-ML's documentation on hyperparameter searches][2] indicates that we should use `HyperbandSearchCV`.\n", - "\n", - "[2]:https://ml.dask.org/hyper-parameter-search.html" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "from dask_ml.model_selection import HyperbandSearchCV\n", - "search = HyperbandSearchCV(model, params, random_state=42, verbose=True, max_iter=9)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, `HyperbandSearchCV` will call `partial_fit` on each chunk of the Dask Array. `HyperbandSearchCV`'s rule of thumb specifies how to train for longer or sample more parameters." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[CV, bracket=2] creating 9 models\n", - "[CV, bracket=1] creating 5 models\n", - "[CV, bracket=0] creating 3 models\n", - "[CV, bracket=0] For training there are between 360 and 360 examples in each chunk\n", - "[CV, bracket=1] For training there are between 360 and 360 examples in each chunk\n", - "[CV, bracket=0] validation score of -0.3191 received after 1 partial_fit calls\n", - "[CV, bracket=2] For training there are between 360 and 360 examples in each chunk\n", - "[CV, bracket=1] validation score of 0.0170 received after 1 partial_fit calls\n", - "[CV, bracket=1] validation score of 0.0322 received after 3 partial_fit calls\n", - "[CV, bracket=2] validation score of 0.2228 received after 1 partial_fit calls\n", - "[CV, bracket=2] validation score of -0.7214 received after 3 partial_fit calls\n", - "[CV, bracket=1] validation score of 0.0183 received after 9 partial_fit calls\n", - "[CV, bracket=0] validation score of -0.2677 received after 9 partial_fit calls\n", - "[CV, bracket=2] validation score of -0.5336 received after 9 partial_fit calls\n" - ] - }, - { - "data": { - "text/plain": [ - "HyperbandSearchCV(estimator=[uninitialized](\n", - " module=,\n", - " module__n_features=14,\n", - "),\n", - " max_iter=9,\n", - " parameters={'batch_size': [32, 64, 128, 256],\n", - " 'module__activation': ['relu', 'elu', 'softsign',\n", - " 'leaky_relu', 'rrelu'],\n", - " 'optimizer__lr': ,\n", - " 'optimizer__momentum': ,\n", - " 'optimizer__nesterov': [True],\n", - " 'optimizer__weight_decay': },\n", - " random_state=42, verbose=True)" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_train2 = y_train.reshape(-1, 1).persist()\n", - "_ = search.fit(X_train, y_train2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Score" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`HyperbandSearchCV` and the like mirror the Scikit-Learn model selection interface, so all attributes of Scikit-Learn's [RandomizedSearchCV][rscv] are available:\n", - "\n", - "[rscv]:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.018286365127180515" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "search.best_score_" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'batch_size': 32,\n", - " 'module__activation': 'rrelu',\n", - " 'optimizer__lr': 0.0002668107973843001,\n", - " 'optimizer__momentum': 0.5920831762255758,\n", - " 'optimizer__nesterov': True,\n", - " 'optimizer__weight_decay': 3.6363529586270234e-05}" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "search.best_params_" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[initialized](\n", - " module_=HiddenLayerNet(\n", - " (fc1): Linear(in_features=14, out_features=100, bias=True)\n", - " (fc2): Linear(in_features=100, out_features=1, bias=True)\n", - " ),\n", - ")" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "search.best_estimator_" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This means we can deploy the best model and score on the entire dataset:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.33630241003610284" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from dask_ml.wrappers import ParallelPostFit\n", - "deployed_model = ParallelPostFit(search.best_estimator_)\n", - "deployed_model.score(X_test, y_test)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What does the error distribution look like on this larger dataset?" - ] - }, - { - "cell_type": "code", - "execution_count": 103, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred = deployed_model.predict(X_test)\n", - "y_pred = y_pred.flatten()" - ] - }, - { - "cell_type": "code", - "execution_count": 108, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Array Chunk
Bytes 800 B 400 B
Shape (100,) (50,)
Count 4 Tasks 2 Chunks
Type int64 numpy.ndarray
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - "\n", - " \n", - " 100\n", - " 1\n", - "\n", - "
" - ], - "text/plain": [ - "dask.array<_predict, shape=(100,), dtype=int64, chunksize=(50,), chunktype=numpy.ndarray>" - ] - }, - "execution_count": 108, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_pred" - ] - }, - { - "cell_type": "code", - "execution_count": 109, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import dask.array as da\n", - "\n", - "err = np.abs(y_pred - y_test)\n", - "max_min_err = 20\n", - "vals, edges = da.histogram(err, range=(0, max_min_err), bins=max_min_err)\n", - "vals, edges = dask.compute(vals, edges)" - ] - }, - { - "cell_type": "code", - "execution_count": 110, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(100,)" - ] - }, - "execution_count": 110, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_test.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 111, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "image/png": { - "height": 290, - "width": 388 - }, - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df = pd.DataFrame({\"vals\": vals, \"edges\": edges[1:]})\n", - "ax = df.plot.bar(x=\"edges\", y=\"vals\", width=1)\n", - "ax.set_ylabel(\"Frequency\")\n", - "ax.set_xlabel(\"Maximum error (minutes)\")\n", - "ax.set_title(\"Prediction error\")\n", - "ax.legend_.remove()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Why not simply sampling instead?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sampling solves the memory issues:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X_train_small = X_train.sample(frac=0.01, random_state=123).compute()\n", - "y_train_small = y_train.sample(frac=0.01, random_state=123).compute()\n", - "\n", - "X_train_small # NumPy ndarray; must fit in memory" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But `HyperbandSearchCV` is meant for computationally-constrained problems, regardless of their memory usage (which [Dask-ML's documentation on hyperparameter searches][2] also indicate). `HyperbandSearchCV` would still be relevant:\n", - "\n", - "[2]:https://ml.dask.org/hyper-parameter-search.html" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "search = HyperbandSearchCV(model, params, max_iter=81, random_state=0)\n", - "search.fit(X_train_small, y_train_small, classes=[0, 1]);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs.\n", - "\n", - "If we had a simpler model and a massive dataset, `IncrementalSearchCV` is recommended. It mirrors Scikit-Learn's `RandomizedSearchCV` but works on Dask Arrays/Dataframes, both of which can be larger than memory." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python [conda env:skorch]", - "language": "python", - "name": "conda-env-skorch-py" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} From fff894b25fba69ce23fd812bffa7b7159654c9f9 Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Wed, 12 Aug 2020 10:16:22 -0500 Subject: [PATCH 11/11] Update notebook --- .../hyper-parameter-optimization.ipynb | 820 ++---------------- hyper-parameter-optimmization/torch_model.py | 1 + 2 files changed, 95 insertions(+), 726 deletions(-) diff --git a/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb b/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb index 46dccc4..ffeff57 100644 --- a/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb +++ b/hyper-parameter-optimmization/hyper-parameter-optimization.ipynb @@ -32,9 +32,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "Creating Cluster. This takes about a minute ... \r" + "Creating Cluster. This takes about a minute ...\r" ] - }, + } + ], + "source": [ + "# Create cluster with Coiled\n", + "import coiled\n", + "\n", + "cluster = coiled.Cluster(\n", + " n_workers=20,\n", + " configuration=\"coiled-examples/pytorch\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ { "data": { "text/html": [ @@ -43,40 +59,35 @@ "\n", "

Client

\n", "\n", "\n", "\n", "

Cluster

\n", "
    \n", - "
  • Workers: 0
  • \n", - "
  • Cores: 0
  • \n", - "
  • Memory: 0 B
  • \n", + "
  • Workers: 20
  • \n", + "
  • Cores: 80
  • \n", + "
  • Memory: 343.60 GB
  • \n", "
\n", "\n", "\n", "" ], "text/plain": [ - "" + "" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import coiled\n", + "# Connect Dask to the cluster\n", "import dask.distributed\n", "\n", - "cluster = coiled.Cluster(\n", - " n_workers=20,\n", - " configuration=\"coiled-examples/pytorch\"\n", - ")\n", "client = dask.distributed.Client(cluster)\n", - "\n", "client" ] }, @@ -107,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -129,6 +140,8 @@ " blocksize=\"16 MiB\",\n", ")\n", "\n", + "df = df.repartition(partition_size=\"10 MiB\").persist()\n", + "\n", "# one hot encode the categorical columns\n", "df = df.categorize(categorical_features)\n", "df = dd.get_dummies(df, columns=categorical_features)\n", @@ -142,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -151,16 +164,16 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from dask_ml.model_selection import train_test_split\n", "import dask\n", "\n", - "features = data.to_dask_array(lengths=True).astype(\"float32\")\n", - "output = durations.to_dask_array(lengths=True).astype(\"float32\")\n", - "X_train, X_test, y_train, y_test = train_test_split(features, output, shuffle=True)\n", + "X = data.to_dask_array(lengths=True).astype(\"float32\")\n", + "y = durations.to_dask_array(lengths=True).astype(\"float32\")\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2, shuffle=True)\n", "\n", "# persist the data so it's not re-computed\n", "X_train, X_test, y_train, y_test = dask.persist(X_train, X_test, y_train, y_test)" @@ -187,7 +200,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -199,13 +212,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "import torch\n", "import torch.optim as optim\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", @@ -229,10 +243,11 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ + "import torch\n", "import torch.optim as optim\n", "import torch.nn as nn\n", "from skorch import NeuralNetRegressor\n", @@ -244,7 +259,13 @@ " \"max_epochs\": 1,\n", "}\n", "\n", - "model = NeuralNetRegressor(\n", + "class NonNanLossRegressor(NeuralNetRegressor):\n", + " def get_loss(self, y_pred, y_true, X=None, training=False):\n", + " if torch.abs(y_true - y_pred).abs().mean() > 1e6:\n", + " return torch.tensor([0.0], requires_grad=True)\n", + " return super().get_loss(y_pred, y_true, X=X, training=training)\n", + "\n", + "model = NonNanLossRegressor(\n", " module=HiddenLayerNet,\n", " module__n_features=X_train.shape[1],\n", " optimizer=optim.SGD,\n", @@ -256,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -265,10 +286,10 @@ "params = {\n", " \"module__activation\": [\"relu\", \"elu\", \"softsign\", \"leaky_relu\", \"rrelu\"],\n", " \"batch_size\": [32, 64, 128, 256],\n", - "# \"optimizer__lr\": loguniform(1e-4, 1e-3),\n", - "# \"optimizer__weight_decay\": loguniform(1e-6, 1e-3),\n", - "# \"optimizer__momentum\": uniform(0, 1),\n", - "# \"optimizer__nesterov\": [True],\n", + " \"optimizer__lr\": loguniform(1e-4, 1e-3),\n", + " \"optimizer__weight_decay\": loguniform(1e-6, 1e-3),\n", + " \"optimizer__momentum\": uniform(0, 1),\n", + " \"optimizer__nesterov\": [True],\n", "}" ] }, @@ -299,15 +320,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from dask_ml.model_selection import HyperbandSearchCV\n", - "search = HyperbandSearchCV(model, params, random_state=2, verbose=True,\n", - " max_iter=2,\n", - "# max_iter=9,\n", - " )" + "search = HyperbandSearchCV(model, params, random_state=2, verbose=True, max_iter=9)" ] }, { @@ -319,34 +337,48 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[CV, bracket=0] creating 1 models\n", - "[CV, bracket=0] For training there are between 2756 and 169772 examples in each chunk\n", - "[CV, bracket=0] validation score of 0.0210 received after 1 partial_fit calls\n", - "[CV, bracket=0] validation score of 0.0270 received after 2 partial_fit calls\n" + "[CV, bracket=2] creating 9 models\n", + "[CV, bracket=1] creating 5 models\n", + "[CV, bracket=0] creating 3 models\n", + "[CV, bracket=0] For training there are between 119153 and 249047 examples in each chunk\n", + "[CV, bracket=1] For training there are between 119153 and 249047 examples in each chunk\n", + "[CV, bracket=2] For training there are between 119153 and 249047 examples in each chunk\n", + "[CV, bracket=1] validation score of 0.0202 received after 1 partial_fit calls\n", + "[CV, bracket=0] validation score of -3.3790 received after 1 partial_fit calls\n", + "[CV, bracket=1] validation score of 0.0210 received after 3 partial_fit calls\n", + "[CV, bracket=2] validation score of 0.0229 received after 1 partial_fit calls\n", + "[CV, bracket=1] validation score of -299404463816680.2500 received after 9 partial_fit calls\n", + "[CV, bracket=0] validation score of -11.9127 received after 9 partial_fit calls\n", + "[CV, bracket=2] validation score of 0.0232 received after 3 partial_fit calls\n", + "[CV, bracket=2] validation score of 0.0280 received after 9 partial_fit calls\n" ] }, { "data": { "text/plain": [ - "HyperbandSearchCV(estimator=[uninitialized](\n", + "HyperbandSearchCV(estimator=[uninitialized](\n", " module=,\n", " module__n_features=15,\n", "),\n", - " max_iter=2,\n", + " max_iter=9,\n", " parameters={'batch_size': [32, 64, 128, 256],\n", " 'module__activation': ['relu', 'elu', 'softsign',\n", - " 'leaky_relu', 'rrelu']},\n", + " 'leaky_relu', 'rrelu'],\n", + " 'optimizer__lr': ,\n", + " 'optimizer__momentum': ,\n", + " 'optimizer__nesterov': [True],\n", + " 'optimizer__weight_decay': },\n", " random_state=2, verbose=True)" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -374,16 +406,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.02695897736664199" + "0.028028356182226544" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -394,16 +426,21 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'module__activation': 'softsign', 'batch_size': 128}" + "{'batch_size': 256,\n", + " 'module__activation': 'softsign',\n", + " 'optimizer__lr': 0.00015404537696021744,\n", + " 'optimizer__momentum': 0.15141540401838427,\n", + " 'optimizer__nesterov': True,\n", + " 'optimizer__weight_decay': 0.000576470051148445}" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -414,13 +451,13 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "[initialized](\n", + "[initialized](\n", " module_=HiddenLayerNet(\n", " (fc1): Linear(in_features=15, out_features=100, bias=True)\n", " (fc2): Linear(in_features=100, out_features=1, bias=True)\n", @@ -428,7 +465,7 @@ ")" ] }, - "execution_count": 13, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -441,21 +478,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This means we can deploy the best model and score on the entire dataset:" + "This means we can deploy the best model and score on the testing dataset:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "0.02479510859559464" + "0.028248285332490686" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -465,675 +502,6 @@ "deployed_model = ParallelPostFit(search.best_estimator_)\n", "deployed_model.score(X_test, y_test)" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What does the error distribution look like on this larger dataset?" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "y_pred = deployed_model.predict(X_test)\n", - "y_pred = y_pred.flatten()" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Array Chunk
Bytes 67.52 MB 151.23 kB
Shape (8440119,) (18904,)
Count 942 Tasks 471 Chunks
Type int64 numpy.ndarray
\n", - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - " \n", - " \n", - "\n", - " \n", - " 8440119\n", - " 1\n", - "\n", - "
" - ], - "text/plain": [ - "dask.array<_predict, shape=(8440119,), dtype=int64, chunksize=(18904,), chunktype=numpy.ndarray>" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_pred" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import dask.array as da\n", - "\n", - "err = np.abs(y_pred - y_test)\n", - "max_min_err = 20\n", - "vals, edges = da.histogram(err, range=(0, max_min_err), bins=max_min_err)\n", - "vals, edges = dask.compute(vals, edges)" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(8440119,)" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "y_test.shape" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXgAAAEjCAYAAAA41BqSAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy86wFpkAAAACXBIWXMAAAsTAAALEwEAmpwYAAAduUlEQVR4nO3deZhdVZnv8e8vCVMIBAgRwpQSRJBGmQLIoAlDdzMPiq22SsPlgjwOgMoj2G03OIDgtUG8iBJboBkUZZBGFEWUgIAMIQkECIhCIGGymAVzxcB7/1iryM6hqs6uOmdXndr1+zzPfmpPa+93VZ3z1jrr7L22IgIzM6ufMcMdgJmZVcMJ3sysppzgzcxqygnezKymnODNzGrKCd7MrKac4K1WJF0g6at5/j2SHhzkcb4r6d/bG53Z0HKCtyEnaaGkJZJelvS0pPMlTWj3eSLitxGxWYl4DpN0c0PZoyPiK+2OyWwoOcHbcNk/IiYA2wLbA19s3EHSuCGPqoP0Vv+B/k5G++9wtHOCt2EVEY8D1wJbAkgKSZ+U9BDwUF63n6R5kl6QdKukd/WUl7SNpDmS/izpR8DKhW0zJC0uLG8o6UpJ3ZKelXS2pHcA3wV2yp8oXsj7vtHVk5ePlPQHSc9JulrSeoVtIeloSQ9Jel7StyWpt/pKGiPpREl/zDH8WNJaeVtXPtYRkh4DfpM/Xdwi6UxJzwEnS5oo6cJcj0clfVHSmHyMN+3f0h/IRjQneBtWkjYE9gHmFlYfBOwIbCFpW+A84OPAJOBc4GpJK0laEbgKuAhYC7gMeH8f5xkLXAM8CnQB6wOXRsQC4GjgdxExISLW6KXs7sDXgH8CpuRjXNqw236kTyJb5f3+sY8qH5PrNx1YD3ge+HbDPtOBdxSOsSPwMPAW4BTg/wITgY3zvocChxfKN+5vo1VEdNREejP/Cbi3xL5TgV8D9wCzgA2GO35Ppf7GC4GXgRdIyfIcYJW8LYDdC/t+B/hKQ/kHSYntvcATgArbbgW+mudnAIvz/E5ANzCul3gOA25uWHdB4TjfB75e2DYB+BvQVYh518L2HwMn9lH3BcAeheUp+VjjSP94Ati4IbbHCstjgb8CWxTWfRyY1dv+nkb31Ikt+AuAvUru+w3gwoh4F/BlUivLRoaDImKNiJgaEZ+IiCWFbYsK81OBz+XumRdyF8qGpNbvesDjEVEcMe/RPs63IfBoRCwdRKzrFY8bES8Dz5I+BfR4qjD/F9I/gd5MBX5SqMsC4DVgncI+ixrKFJfXBlZk+Xo+2hBLY3kbpTouwUfETcBzxXWSNpH0C0l3SfqtpM3zpi1ILXiAG4ADhzBUq04xYS8CTsn/DHqm8RHxQ+BJYP2G/u6N+jjmImCjPr50bDak6hOkxAyApFVJ3UWPN6tIH3Hs3VCflSN9F9FXPMXlZ0gt/qmFdRs1xOIhYg3owATfh5nApyNiO+B40kd6gLtZ1ud6MLCapEnDEJ9V53vA0ZJ2VLKqpH0lrQb8DlgKHCNpnKT3ATv0cZw7SP8QTsvHWFnSLnnb08AGuU+/Nz8ADpe0taSVgFOB2yNi4SDq813gFElTASRNllS6YRIRr5G6gE6RtFo+zmeBiwcRi9Vcxyf4fH30zsBlkuaRvmSbkjcfD0yXNJfUJ/s46Q1vNRERs4EjgbNJX0j+gdTPTES8CrwvLz8PfBC4so/jvAbsD7wNeAxYnPcH+A1wH/CUpGd6Kftr4N+BK0j/JDYBPjTIKp0FXA1cJ+nPwG2kL0UH4tPAK6QvUm8m/QM6b5DxWI1p+e7LziCpC7gmIraUtDrwYERMaVJmAvBARGwwFDGamXW6jm/BR8RLwCOSPgCQP6ZvlefX7rn+F/gCbsWYmb2h4xK8pB+S+lY3k7RY0hHAR4AjJN1N+ijd02c5A3hQ0u9JVyH4ml8zs6wju2jMzKx1HdeCNzOz9nCCNzOrqY4aaW7ttdeOrq6u4Q7DzGzEuOuuu56JiMm9beuoBN/V1cXs2bOHOwwzsxFDUl/Dc7iLxsysrpzgzcxqygnezKymnODNzGrKCd7MrKac4M3MasoJ3sysppzgzcxqqqNudGrUdeLPWiq/8LR92xSJmdnI4xa8mVlNdXQLvlX+BGBmo5lb8GZmNeUEb2ZWU7XuomlVq1084G4eMxs+bsGbmdWUE7yZWU05wZuZ1ZQTvJlZTflL1or5WnwzGy5uwZuZ1ZQTvJlZTTnBm5nVlBO8mVlNOcGbmdWUE7yZWU35MskO58sszWyw3II3M6spJ3gzs5qqNMFL+oyk+yTdK+mHklau8nxmZrZMZQle0vrAMcC0iNgSGAt8qKrzmZnZ8qruohkHrCJpHDAeeKLi85mZWVZZgo+Ix4FvAI8BTwIvRsR1VZ3PzMyWV2UXzZrAgcBbgfWAVSV9tJf9jpI0W9Ls7u7uqsIxMxt1qrwOfk/gkYjoBpB0JbAzcHFxp4iYCcwEmDZtWlQYz6jk58qajV5V9sE/Brxb0nhJAvYAFlR4PjMzK6iyD/524HJgDjA/n2tmVeczM7PlVTpUQUScBJxU5TnMzKx3vpPVzKymnODNzGrKCd7MrKac4M3MasoJ3syspvzAD2vKDx0xG5ncgjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmvKNTlY53yhlNjzcgjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKd/Jah2v1TthwXfD2ujkFryZWU05wZuZ1ZQTvJlZTTnBm5nVlBO8mVlNOcGbmdWUE7yZWU05wZuZ1ZQTvJlZTflOVhsV/FxYG43cgjczqykneDOzmqo0wUtaQ9Llkh6QtEDSTlWez8zMlqm6D/4s4BcRcYikFYHxFZ/PzMyyyhK8pNWB9wKHAUTEq8CrVZ3PzMyWV2UXzcZAN3C+pLmS/kvSqhWez8zMCqpM8OOAbYHvRMQ2wCvAiY07STpK0mxJs7u7uysMx8xsdKkywS8GFkfE7Xn5clLCX05EzIyIaRExbfLkyRWGY2Y2ulTWBx8RT0laJGmziHgQ2AO4v6rzmVXJN0rZSFT1VTSfBi7JV9A8DBxe8fnMzCyrNMFHxDxgWpXnMDOz3pXqg5e0ZdWBmJlZe5X9kvW7ku6Q9AlJa1QZkJmZtUepBB8RuwIfATYEZkv6gaS/rzQyMzNrSenLJCPiIeCLwAnAdOBbeYyZ91UVnJmZDV7ZPvh3SToTWADsDuwfEe/I82dWGJ+ZmQ1S2atozga+B/xrRCzpWRkRT0j6YiWRmZlZS8om+H2AJRHxGoCkMcDKEfGXiLiosujMaqLVG6XAN0vZwJXtg78eWKWwPD6vMzOzDlU2wa8cES/3LOR5j+1uZtbByib4VyS9MVCYpO2AJf3sb2Zmw6xsH/xxwGWSnsjLU4APVhKRmZm1RakEHxF3Stoc2AwQ8EBE/K3SyMzMrCUDGWxse6Arl9lGEhFxYSVRmZlZy0oleEkXAZsA84DX8uoAnODNzDpU2Rb8NGCLiIgqgzEzs/Ypm+DvBdYFnqwwFjPrh58qZQNVNsGvDdwv6Q7grz0rI+KASqIyM7OWlU3wJ1cZhJmZtV/ZyyRvlDQV2DQirpc0HhhbbWhmZtaKssMFHwlcDpybV60PXFVRTGZm1gZlhyr4JLAL8BK88fCPt1QVlJmZta5sgv9rRLzasyBpHOk6eDMz61BlE/yNkv4VWCU/i/Uy4KfVhWVmZq0qexXNicARwHzg48DPgf+qKigzaz9fRz/6lL2K5nXSI/u+V204ZmbWLmXHonmEXvrcI2LjtkdkZmZtMZCxaHqsDHwAWKv94ZiZWbuU+pI1Ip4tTI9HxDeB3asNzczMWlG2i2bbwuIYUot+tUoiMjOztijbRfOfhfmlwELgn9oejZmZtU3Zq2h2qzoQMzNrr7JdNJ/tb3tEnNGecMysU7V6HT34WvqhNpCraLYHrs7L+wM3AYuqCMrMzFo3kAd+bBsRfwaQdDJwWUT876oCMzOz1pQdi2Yj4NXC8qtAV9ujMTOztinbgr8IuEPST0h3tB4MXFhZVGZm1rKyV9GcIula4D151eERMbe6sMzMrFVlu2gAxgMvRcRZwGJJby1TSNJYSXMlXTOoCM3MbFDKXiZ5EulKms2A84EVgItJT3lq5lhgAbD6IGM0s5rwkMVDq2wL/mDgAOAVgIh4ghJDFUjaANgXjx1vZjbkyib4VyMiyEMGS1q1ZLlvAp8HXh94aGZm1oqyCf7Hks4F1pB0JHA9TR7+IWk/4E8RcVeT/Y6SNFvS7O7u7pLhmJlZM0374CUJ+BGwOfASqR/+PyLiV02K7gIcIGkf0hjyq0u6OCI+WtwpImYCMwGmTZvmB3mbmbVJ0wQfESHpqojYDmiW1IvlvgB8AUDSDOD4xuRuZmbVKdtFc5uk7SuNxMzM2qrsnay7AUdLWki6kkakxv27yhSOiFnArEHEZ2b2Bl9mOTD9JnhJG0XEY8DeQxSPmZm1SbMW/FWkUSQflXRFRLx/CGIyM7M2aNYHr8L8xlUGYmZm7dUswUcf82Zm1uGaddFsJeklUkt+lTwPy75k9fgyZmYdqt8EHxFjhyoQMzNrr7KXSZqZjXij7cHhAxkP3szMRhAneDOzmnKCNzOrKSd4M7OacoI3M6spX0VjZjYAI2nAM7fgzcxqygnezKymnODNzGrKCd7MrKac4M3MaspX0ZiZDaGhvArHLXgzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6upyhK8pA0l3SBpgaT7JB1b1bnMzOzNqnwm61LgcxExR9JqwF2SfhUR91d4TjMzyyprwUfEkxExJ8//GVgArF/V+czMbHlD0gcvqQvYBrh9KM5nZmZDkOAlTQCuAI6LiJd62X6UpNmSZnd3d1cdjpnZqFFpgpe0Aim5XxIRV/a2T0TMjIhpETFt8uTJVYZjZjaqVHkVjYDvAwsi4oyqzmNmZr2rsgW/C/AxYHdJ8/K0T4XnMzOzgsouk4yImwFVdXwzM+uf72Q1M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6spJ3gzs5pygjczqykneDOzmnKCNzOrKSd4M7OacoI3M6upShO8pL0kPSjpD5JOrPJcZma2vMoSvKSxwLeBvYEtgA9L2qKq85mZ2fKqbMHvAPwhIh6OiFeBS4EDKzyfmZkVjKvw2OsDiwrLi4EdG3eSdBRwVF58WdKDAzjH2sAzg45w+Mt3QgyuQ2fE4Dp0RgwdXwed/qZVU/vat8oEr17WxZtWRMwEZg7qBNLsiJg2mLKdUL4TYnAdOiMG16EzYqhDHYqq7KJZDGxYWN4AeKLC85mZWUGVCf5OYFNJb5W0IvAh4OoKz2dmZgWVddFExFJJnwJ+CYwFzouI+9p8mkF17XRQ+U6IwXXojBhch86IoQ51eIMi3tQtbmZmNeA7Wc3MasoJ3sysppzgzcxqygnezKymqrzRqSNJWod0l20AT0TE0yOpvGNoT3mzTiFpc9IwLm+8noGrI2JBy8ceaVfRDPaNLWlr4LvARODxvHoD4AXgExExp5PLO4b21SEfp6U3VTvelMMdg+sw/DFIOgH4MGmsrsV59Qak+4YujYjTytajVxExIiZga+A2YAFwfZ4eyOu2LVF+HrBjL+vfDdzd6eUdQ1vrcEI+zonAR/N0Ys+6qst3QgyuQ2fEAPweWKGX9SsCD5WpQ7/Hb/UAQzW1+sbu75dFGvWyo8s7hrbWoaU3VTvelMMdg+vQGTGQGqlTe1k/FXiwTB36m0ZSH/yqEXF748qIuE3SqiXKXyvpZ8CFLBvlckPgUOAXI6C8Y2hPeYDXgfWARxvWT8nbqi7fCTG4Dp0Rw3HAryU9xLLX80bA24BPlSjfrxHTBy/pW8Am9P7GfiQimv4yJO3Nsr4ykfq8ro6In5eMYVjLO4a2ld8LOBvo9U0VEf3+o2i1fCfE4Dp0VAxjSM/PKL6e74yI15qVbXrskZLgoT3JyQxaf1O140053DG4Dp0TQ1VGVIKviqSjIo1LPyLLO4b2lDfrJJKuiYj9WjlGLW50yk+FaukQI7y8Y2hPeSRdM5zlOyEG16FjYjiyxfL1aMFL+nhEnFtiv81JH6Nuj4iXC+v3KtlXtgMQEXFnfoD4XsADg+0iknRhRBw6mLK5/K6kj4b3RsR1JfbfEVgQES9JWoV0Ode2wP3AqRHxYoljHAP8JCIWNdu3j/I9zwZ4IiKul/TPwM6ky19nRsTfShxjE+Bg0ncwS0n9nz8sE3+JY0+JiCeHq3wnxOA6dE4MrapLgj88Is5vss8xwCdJiWRr4NiI+J+8bU5EbNuk/EnA3qS7f39Fer7sLGBP4JcRcUqT8o0POxGwG/AbgIg4oL/y+Rh3RMQOef7IXJ+fAP8A/DSa3BQh6T5gq0hj9c8E/gJcDuyR17+vRAwvAq8AfwR+CFwWEd3NyhXKX0L6HY4n3Zw0Abgyx0BEHNak/DHA/sCNwD6ky2efJyX8T0TErLKx2PIkvSUi/jTMMUyKiGeHM4ahJGki8AXgIGByXv0n4H+A0yLihZZO0Op1lp0wAY+V2Gc+MCHPdwGzSUkeYG7J8mNJieklYPW8fhXgnhLl5wAXAzOA6fnnk3l+esl6zi3M3wlMzvOrAvNLlF9QjKdh27yyMZC69v4B+D7QTbo88V+A1UqUvyf/HAc8DYzNyyr5e5xfKDMemJXnNyrzd8z7TgROI12D/GyeFuR1a7T4Wry25H6rA18DLgL+uWHbOSXKrwt8B/g2MAk4Of9ufgxMKVF+rYZpErAQWBNYq2Qd9mr4nX4fuAf4AbBOifKnAWvn+WnAw8AfSJccTi9Rfg7wRWCTFv5e04Ab8ntzQ1Lj7cX8/tqmRPkJwJeB+3K5btLNl4eVPP8vSTdLrdvwtz0B+FUrr8WIGDl98JLu6WOaD6xT4hBjI3fLRMRCUoLdW9IZlOu7XRoRr0XEX4A/RsRL+VhLKHe96zTgLuDfgBcjtTSXRMSNEXFjifIAYyStKWkS6dNXd47hFVJXRTP3Sjo8z98taRqApLcDTbtGsoiI1yPiuog4gnQN8Dmk7qqHS9ZhRWA1UoKemNevBKxQMoae+zdWyschIh4bQPkfk1r9MyJiUkRMIn2aeh64rFlhSdv2MW1H+nRYxvmk190VwIckXSFppbzt3SXKX0DqWltESlBLgH2B35KGcmjmGdLrsWeaTeq+nJPnyzi1MP+fpAbL/qTk2LTLFNg3Ip7J8/8H+GBEvA34+3y8ZtYE1gBukHSHpM9IWq9k7D3OAb4O/Ay4FTg3IiaSui/PKVH+EtLr/h+BLwHfAj4G7Cbp1P4KZl0RcXpEPNWzIiKeiojTSY2W1rT6H2KoJlJrb2vSHV7FqYvUn9us/G+ArRvWjSNdV/9aifK3A+Pz/JjC+ok0tIabHGcDUhI5mxKfPBrKLiS9mB7JP9eNZa2IeSXKTyQlhj/m+vwtH+dGUhdNmRjm9rNtlRLlP5PP+ShwDPBr4Huk1udJJcofS2olziS1wA/P6ycDN5WsQ593CPa3rbDPa/n1dEMv05KSMcxrWP434BZSS7rp64nlP8091t+x+yh/POmT1zsL6x4Z4OtxTl/nLBnDA8C4PH9bw7Yyn0iL538PKSE/lf8OR5WsQ3+/x7klyt/dsHxn/jmG9P1cs/LXAZ+n8ImH1GA9Abh+IH+PXo/f6gGGaiJ9/Nu1j20/KFF+Awofgxq27VKi/Ep9rF+7+CYZQH32JX2x2Y7fzXjgrQPYfzVgK2A7SnyUbij79jbEux6wXp5fAzgE2GEA5f8ul9l8kOdv6U0F3Ats2se2RSVjWEChoZDX/Qvpo/6jJcrfXZj/asO2pskx79fT2DgjvyYeHuDvcTHwWeBzpH/aKmwr09326fy32J3UxfRN4L2klvBFJcq/6R8hqRt1L+D8knX4Ham78QOkRsdBef10YHaJ8rf25CXSp5dfFraVaSysCZxO+mf3PPBcfm2cTsmusn6P3+oBPHkaaVPDm+q5hjfVmiXKHwJs1se2g0rG8HVgz17W70W5MUy+TP5OqWH924DLB/j72J/Ub/zUAMud1DD1fCe0LnBhyWPMAH5E+m5nPvBz4Chyy75J2Uvb8FrYitQPfi2wOXAW6cv/+4CdS5R/F3BHLnMzuQFE+kR5TMkYNiddrDGhYf1eA6lLr8du9QCePNVpInf5DFf54YqBdLHAliO5DiPx70DqpnwQuIrUBXtgYVvprt++plpcJmnWLpIei4hBf7nVavlOiMF1GLoY8kUiO0XEy5K6SJctXxQRZ0maGxHbDPb8MAqf6GQm6Z6+NlHiiqxWy3dCDK5Dx8Sw3NV9kmYAl0uaShvuzHaCt9FoHdJlbc83rBfpS7Oqy3dCDK5DZ8TwlKStI2IeQG7J7wecB7yzRPl+OcHbaHQN6QuteY0bJM0agvKdEIPr0BkxHErDPSwRsRQ4VFKZewn65T54M7OaGjF3spqZ2cA4wZuZ1ZQTvJlZTTnBW2mSQtJFheVxkroH+2ADSQdIOrF9EXY2ScdJGtD4/5LKXg3SW9kZknYeZNnJkso+xNw6lBO8DcQrwJb5YSGQRv17fLAHi4iro8kY9sNN0rj+lvspN7aXcv+LNJRuaRExqASdzSA9TGXAIo1U+qSkXVo4vw0zJ3gbqGtJA6UBfJj00A8gPfFK0q2S5uafm+X1n5V0Xp5/p6R7JY2XdJiks/P6CyR9R9INkh6WNF3SeZIWSLqgcI7ik7gO6dlWtnyRpO0k3SjpLkm/lDQlr58l6VRJNwLH9rK8R67j/HyOlXK5hZL+Q9LNpMGrinYn3Xq+tHCOMyXdlGPcXtKVkh6S9NXG+ubW+CxJl0t6QNIlklQ479p5flrerws4GviMpHmS3pNb5VdIujNPu+Qy0/M+83K9Vsunvwr4SL+vButsrY514Gn0TMDLpMGVLgdWJj1NaQZwTd6+OsuGf90TuCLPjwFuIj11aTZ59E7gMODsPH8BcCnpBpEDSQ9VeWcuexd5qGfg5UI8hwAXDKR8oewKpBtRegbI+iBwXp6fReGhG8XlXO9FLBtU6kLguDy/EPh8H7+7LwGfbjjm6Xn+WOAJYAppjPvFwKRiffPv+UXSCJBjSKMg7lo4b/HBGbPy/MnA8YVz/qBQZiPyA2CAnxb+JhMKf8P1KTkypafOnHyjkw1IRNyTW4cfJo38VzQR+G9JmwJBfgBHRLwu6TDSOO7nRsQtfRz+pxERSuNzPB0R8+GNRw12kf6h9Gcg5TcDtgR+lRvCY0kPrOjxo4Zj/6hQ7pGI+H1e/m/SoxO/2Ue5HlNII1YW9TzGcT5wX+Tnd0p6mPR0ocZH190REYvzPvNynW7u43y92RPYItcXYPXcWr8FOEPpcYpX9pyD9Oi4gT5AwzqIE7wNxtXAN0itykmF9V8BboiIg/M/gVmFbZuSPgH0lzD+mn++XpjvWe55rRbvzFt5EOV7iJRUd+ojllf6WG42PkhjuR5LaC1eGvZ5rbDPUpZ1tzaeo2gMaWCrJQ3rT5P0M9Izbm+TtGdEPJCP1bivjSDug7fBOA/4ck8LuWAiy750PaxnpdKDhc8iPcxhkqRDWjj305LeIWkMqctnsB4EJkvaKce4gqS/K1HuAaBL0tvy8sdIT8RqZgFprPYqLCQ9vAXg/YX1fyY/0jC7DvhUz4KkrfPPTSJifqTHxM0mjU8O8HbSw01shHKCtwGLiMURcVYvm74OfE3SLaQujx5nkvqwfw8cQWoxvmWQpz+RNP7Hb1i+S2VAIuJVUh/+6ZLuJnXfNL3iJCL+H3A4cFnuCnqdcs9AvZb0D64KXwLOkvRbUsu+x0+Bg3u+ZCWNPT5N6VnG95O+hAU4Ln/xfTepxX5tXr8b6VmlNkJ5LBqzISLpJ6QvYR8a7ljKkHQT6QEUjSMl2gjhBG82RPJlo+tExE3DHUszkiaTrqy5arhjscFzgjczqyn3wZuZ1ZQTvJlZTTnBm5nVlBO8mVlNOcGbmdXU/wf73b9cFaRcHgAAAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "df = pd.DataFrame({\"vals\": vals, \"edges\": edges[1:]})\n", - "ax = df.plot.bar(x=\"edges\", y=\"vals\", width=1)\n", - "ax.set_ylabel(\"Frequency\")\n", - "ax.set_xlabel(\"Maximum error (minutes)\")\n", - "ax.set_title(\"Prediction error\")\n", - "ax.legend_.remove()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Why not simply sampling instead?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sampling solves the memory issues:" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "# X_train_small = data.sample(frac=0.01, random_state=123).to_dask_array().astype(\"float32\").compute()\n", - "# y_train_small = durations.sample(frac=0.01, random_state=123).to_dask_array().astype(\"float32\").compute()\n", - "\n", - "# X_train_small # NumPy ndarray; must fit in memory" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But `HyperbandSearchCV` is meant for computationally-constrained problems, regardless of their memory usage (which [Dask-ML's documentation on hyperparameter searches][2] also indicate). `HyperbandSearchCV` would still be relevant:\n", - "\n", - "[2]:https://ml.dask.org/hyper-parameter-search.html" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "# search = HyperbandSearchCV(model, params, max_iter=81, random_state=0)\n", - "# search.fit(X_train_small, y_train_small.reshape(-1, 1), classes=[0, 1]);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`HyperbandSearchCV` would not be relevant when the search problem is not computationally-constrained, which happens with a smaller search space or a simpler model that doesn't require GPUs.\n", - "\n", - "If we had a simpler model and a massive dataset, `IncrementalSearchCV` is recommended. It mirrors Scikit-Learn's `RandomizedSearchCV` but works on Dask Arrays/Dataframes, both of which can be larger than memory." - ] } ], "metadata": { diff --git a/hyper-parameter-optimmization/torch_model.py b/hyper-parameter-optimmization/torch_model.py index b6683f0..94d9a90 100644 --- a/hyper-parameter-optimmization/torch_model.py +++ b/hyper-parameter-optimmization/torch_model.py @@ -1,3 +1,4 @@ +import torch import torch.optim as optim import torch.nn as nn import torch.nn.functional as F