├── .gitignore ├── AWS-S3-Dask-XGBoost.ipynb ├── LICENSE ├── README.md ├── brenowitz-data-loading.ipynb ├── djgagne_partial_dependence_plot.ipynb └── rasp-data-loading.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /AWS-S3-Dask-XGBoost.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "toc": true 7 | }, 8 | "source": [ 9 | "

Table of Contents

\n", 10 | "
" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Overview" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "**Author**:\n", 25 | "**Zhonghua Zheng** (zzheng25@illinois.edu) \n", 26 | "GitHub: **zzheng93** \n", 27 | "\n", 28 | "**Motivation**:\n", 29 | "This notebook shows the workflow of \n", 30 | "* Loading Climate Data ([CESM-LE from AWS S3](https://medium.com/pangeo/cesm-lens-on-aws-4e2a996397a1))\n", 31 | "* Scalable [Dask-XGBoost](https://examples.dask.org/machine-learning/xgboost.html) *regression* implementation (the official documentation of [Dask-XGBoost](https://examples.dask.org/machine-learning/xgboost.html) only provides the *classfication* implementation.\n", 32 | "\n", 33 | "**Task**:\n", 34 | "How to predict **maximum reference height temperature (TREFHTMX)** based on related features?\n", 35 | "\n", 36 | "* Response (Y): \n", 37 | "\"TREFHTMX\": Maximum reference height temperature over output period \n", 38 | "\n", 39 | "* Features (X): \n", 40 | "\"PRECT\": Total (convective and large-scale) precipitation rate (liq + ice) \n", 41 | "\"WSPDSRFAV\": Horizontal total wind speed average at the surface \n", 42 | "\"TS\": Surface temperature (radiative) \n", 43 | "\"TREFHT\": Reference height temperature \n", 44 | "\n", 45 | "**Prerequisite** \n", 46 | "How to create environment for this workflow? \n", 47 | "Recommendation: \n", 48 | "```bash\n", 49 | "git clone http://github.com/dask/dask-tutorial\n", 50 | "cd dask-tutorial\n", 51 | "conda env create -f binder/environment.yml \n", 52 | "conda activate dask-tutorial\n", 53 | "conda install xarray cftime=1.0.3.4\n", 54 | "conda install -c conda-forge dask-ml dask-xgboost\n", 55 | "```\n" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "# Load libraries" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Here we load the necessary libraries, connect to cluster, and set up anonymous access to s3" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 1, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "import numpy as np\n", 79 | "import s3fs\n", 80 | "import xarray as xr\n", 81 | "import matplotlib.pyplot as plt\n", 82 | "import dask\n", 83 | "import dask.array as da\n", 84 | "import dask.dataframe as dd\n", 85 | "import dask_xgboost\n", 86 | "import matplotlib.pyplot as plt\n", 87 | "from dask.distributed import Client\n", 88 | "from dask_ml.model_selection import train_test_split\n", 89 | "from sklearn import metrics\n", 90 | "import xgboost\n", 91 | "\n", 92 | "client = Client() # connect to cluster\n", 93 | "s3 = s3fs.S3FileSystem(anon=True) # anonymous access to s3" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 2, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/html": [ 104 | "\n", 105 | "\n", 106 | "\n", 113 | "\n", 121 | "\n", 122 | "
\n", 107 | "

Client

\n", 108 | "\n", 112 | "
\n", 114 | "

Cluster

\n", 115 | "
    \n", 116 | "
  • Workers: 4
  • \n", 117 | "
  • Cores: 16
  • \n", 118 | "
  • Memory: 32.66 GB
  • \n", 119 | "
\n", 120 | "
" 123 | ], 124 | "text/plain": [ 125 | "" 126 | ] 127 | }, 128 | "execution_count": 2, 129 | "metadata": {}, 130 | "output_type": "execute_result" 131 | } 132 | ], 133 | "source": [ 134 | "client" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "# Load Data" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "Here we load the CESM-LE RCP8.5 data from [AWS S3](https://medium.com/pangeo/cesm-lens-on-aws-4e2a996397a1). \n", 149 | "The original format of the data is [Zarr](https://zarr.readthedocs.io/en/stable/), but Dask-XGBoost only accepts Dask.array or Dask.dataframe collections.\n", 150 | "\n", 151 | "So we use [xarray](http://xarray.pydata.org/en/stable/) to load the data, and convert to [Dask Dataframe](https://docs.dask.org/en/latest/dataframe.html). \n", 152 | "\n", 153 | "As an illustration, here I only use \"member_id=1\" and \"time=\"2010-01-01\"\" for this tutorial.\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 3, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "name": "stdout", 163 | "output_type": "stream", 164 | "text": [ 165 | "CPU times: user 6.55 s, sys: 308 ms, total: 6.85 s\n", 166 | "Wall time: 1min 34s\n" 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "%%time\n", 172 | "zarr_ls = [xr.open_zarr(s3fs.S3Map(root=\"ncar-cesm-lens/atm/daily/cesmLE-RCP85-\"+var+\".zarr\", s3=s3))[var]\n", 173 | " .sel(member_id=1,time=\"2010-01-01\") \n", 174 | " for var in [\"TREFHT\",\"TREFHTMX\",\"PRECT\",\"TS\",\"WSPDSRFAV\"]] # read data from s3" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": 4, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "dddf=xr.merge(zarr_ls).to_dask_dataframe() # merge arrays and convert to dask dataframe" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": 5, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "X=dddf[[\"TREFHT\",\"PRECT\",\"TS\",\"WSPDSRFAV\"]] # features for Dask-XGBoost\n", 193 | "Y=dddf[\"TREFHTMX\"] # response for Dask-XGBoost" 194 | ] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": 6, 199 | "metadata": {}, 200 | "outputs": [], 201 | "source": [ 202 | "X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.15) # split into training and testing" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": {}, 208 | "source": [ 209 | "# Train the XGB model" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "This parameters are available at [Learning Task Parameters](https://xgboost.readthedocs.io/en/latest/parameter.html).\n", 217 | "* **reg:squarederror**: regression with squared loss.\n", 218 | "* **eta (learning_rate)**: Step size shrinkage used in update to prevents overfitting. After each boosting step, we can directly get the weights of new features, and eta shrinks the feature weights to make the boosting process more conservative. \n", 219 | "* **max_depth**: Maximum depth of a tree. Increasing this value will make the model more complex and more likely to overfit. 0 is only accepted in lossguided growing policy when tree_method is set as hist and it indicates no limit on depth. Beware that XGBoost aggressively consumes memory when training a deep tree. \n", 220 | "* **num_boost_round**: [Number of boosting](iterationshttps://xgboost.readthedocs.io/en/latest/python/python_api.html)." 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 7, 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "CPU times: user 306 ms, sys: 57.8 ms, total: 364 ms\n", 233 | "Wall time: 13 s\n" 234 | ] 235 | } 236 | ], 237 | "source": [ 238 | "%%time\n", 239 | "params = {'objective': 'reg:squarederror',\n", 240 | " 'max_depth': 6, 'eta': 0.01}\n", 241 | "\n", 242 | "bst = dask_xgboost.train(client, params, X_train, y_train, num_boost_round=500)" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "## Determine feature importance" 250 | ] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "metadata": {}, 255 | "source": [ 256 | "Generally, importance provides a score that indicates how useful or valuable each feature was in the construction of the boosted decision trees within the model. The more an attribute is used to make key decisions with decision trees, the higher its relative importance (reference: [Feature Importance and Feature Selection With XGBoost in Python](https://machinelearningmastery.com/feature-importance-and-feature-selection-with-xgboost-in-python/))." 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 8, 262 | "metadata": {}, 263 | "outputs": [ 264 | { 265 | "data": { 266 | "image/png": "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\n", 267 | "text/plain": [ 268 | "
" 269 | ] 270 | }, 271 | "metadata": { 272 | "needs_background": "light" 273 | }, 274 | "output_type": "display_data" 275 | } 276 | ], 277 | "source": [ 278 | "%matplotlib inline\n", 279 | "ax = xgboost.plot_importance(bst)\n", 280 | "ax.grid(False, axis=\"y\")\n", 281 | "ax.set_title('Estimated feature importance')\n", 282 | "plt.show()" 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "metadata": {}, 288 | "source": [ 289 | "# Test model performance" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": {}, 295 | "source": [ 296 | "Here we test our XGBoost emulator performance using the testing data" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 9, 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [ 305 | "y_hat = dask_xgboost.predict(client, bst, X_test).persist()\n", 306 | "y_test, y_hat = dask.compute(y_test, y_hat) # get the predicted data and true data" 307 | ] 308 | }, 309 | { 310 | "cell_type": "code", 311 | "execution_count": 10, 312 | "metadata": {}, 313 | "outputs": [ 314 | { 315 | "data": { 316 | "image/png": "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\n", 317 | "text/plain": [ 318 | "
" 319 | ] 320 | }, 321 | "metadata": { 322 | "needs_background": "light" 323 | }, 324 | "output_type": "display_data" 325 | }, 326 | { 327 | "name": "stdout", 328 | "output_type": "stream", 329 | "text": [ 330 | "rmse: 2.5904884\n", 331 | "r2_score: 0.98544579149015\n" 332 | ] 333 | } 334 | ], 335 | "source": [ 336 | "fig, ax = plt.subplots(figsize=(5, 5))\n", 337 | "ax.scatter(y_hat,y_test,s=0.1)\n", 338 | "ax.plot([y_test.min(), y_test.min()], [y_test.max(), y_test.max()],c=\"black\")\n", 339 | "ax.set(\n", 340 | " xlabel=\"Predicted\",\n", 341 | " ylabel=\"True\",\n", 342 | ")\n", 343 | "plt.show()\n", 344 | "print(\"rmse:\",np.sqrt(metrics.mean_squared_error(y_test, y_hat)))\n", 345 | "print(\"r2_score:\",metrics.r2_score(y_test,y_hat))" 346 | ] 347 | } 348 | ], 349 | "metadata": { 350 | "kernelspec": { 351 | "display_name": "Python 3", 352 | "language": "python", 353 | "name": "python3" 354 | }, 355 | "language_info": { 356 | "codemirror_mode": { 357 | "name": "ipython", 358 | "version": 3 359 | }, 360 | "file_extension": ".py", 361 | "mimetype": "text/x-python", 362 | "name": "python", 363 | "nbconvert_exporter": "python", 364 | "pygments_lexer": "ipython3", 365 | "version": "3.7.4" 366 | }, 367 | "toc": { 368 | "base_numbering": 1, 369 | "nav_menu": {}, 370 | "number_sections": false, 371 | "sideBar": true, 372 | "skip_h1_title": false, 373 | "title_cell": "Table of Contents", 374 | "title_sidebar": "Contents", 375 | "toc_cell": true, 376 | "toc_position": {}, 377 | "toc_section_display": true, 378 | "toc_window_display": false 379 | } 380 | }, 381 | "nbformat": 4, 382 | "nbformat_minor": 2 383 | } 384 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ml-workflow-examples 2 | Simple examples of data pipelines from xarray to ML training 3 | -------------------------------------------------------------------------------- /brenowitz-data-loading.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Here is a toy xarray dataset. It has a few 3D and 2D variables." 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [ 15 | { 16 | "data": { 17 | "text/plain": [ 18 | "\n", 19 | "Dimensions: (time: 4, x: 2, y: 5, z: 4)\n", 20 | "Dimensions without coordinates: time, x, y, z\n", 21 | "Data variables:\n", 22 | " a (time, z, y, x) float64 1.0 1.0 1.0 1.0 1.0 ... 1.0 1.0 1.0 1.0 1.0\n", 23 | " b (time, y, x) float64 1.0 1.0 1.0 1.0 1.0 ... 1.0 1.0 1.0 1.0 1.0" 24 | ] 25 | }, 26 | "execution_count": 1, 27 | "metadata": {}, 28 | "output_type": "execute_result" 29 | } 30 | ], 31 | "source": [ 32 | "import numpy as np\n", 33 | "import xarray as xr\n", 34 | "from itertools import product\n", 35 | "\n", 36 | "from torch.utils.data import Dataset, DataLoader\n", 37 | "\n", 38 | "class XRTimeSeries(Dataset):\n", 39 | " \"\"\"A pytorch Dataset class for time series data in xarray format\n", 40 | "\n", 41 | " This function assumes the data has dimensions ['time', 'z', 'y', 'x'], and\n", 42 | " that the axes of the data arrays are all stored in that order.\n", 43 | "\n", 44 | " An individual \"sample\" is the full time time series from a single\n", 45 | " horizontal location. The time-varying variables in this sample will have\n", 46 | " shape (time, z, 1, 1).\n", 47 | "\n", 48 | " Examples\n", 49 | " --------\n", 50 | " >>> ds = xr.open_dataset(\"in.nc\")\n", 51 | " >>> dataset = XRTimeSeries(ds)\n", 52 | " >>> dataset[0]\n", 53 | "\n", 54 | " \"\"\"\n", 55 | " dims = ['time', 'z', 'x', 'y']\n", 56 | "\n", 57 | " def __init__(self, data, time_length=None):\n", 58 | " \"\"\"\n", 59 | " Parameters\n", 60 | " ----------\n", 61 | " data : xr.DataArray\n", 62 | " An input dataset. This dataset must contain at least some variables\n", 63 | " with all of the dimensions ['time' , 'z', 'x', 'y'].\n", 64 | " time_length : int, optional\n", 65 | " The length of the time sequences to use, must evenly divide the\n", 66 | " total number of time points.\n", 67 | " \"\"\"\n", 68 | " self.time_length = time_length or len(data.time)\n", 69 | " self.data = data\n", 70 | " self.numpy_data = {key: data[key].values for key in data.data_vars}\n", 71 | " self.data_vars = set(data.data_vars)\n", 72 | " self.dims = {key: data[key].dims for key in data.data_vars}\n", 73 | " self.constants = {\n", 74 | " key\n", 75 | " for key in data.data_vars\n", 76 | " if len({'x', 'y', 'time'} & set(data[key].dims)) == 0\n", 77 | " }\n", 78 | " self.setup_indices()\n", 79 | "\n", 80 | " def setup_indices(self):\n", 81 | " len_x = len(self.data['x'].values)\n", 82 | " len_y = len(self.data['y'].values)\n", 83 | " len_t = len(self.data['time'].values)\n", 84 | "\n", 85 | " x_iter = range(0, len_x, 1)\n", 86 | " y_iter = range(0, len_y, 1)\n", 87 | " t_iter = range(0, len_t, self.time_length)\n", 88 | " assert len_t % self.time_length == 0\n", 89 | " self.indices = list(product(t_iter, y_iter, x_iter))\n", 90 | "\n", 91 | " def __len__(self):\n", 92 | " return len(self.indices)\n", 93 | "\n", 94 | " def __getitem__(self, i):\n", 95 | " t, y, x = self.indices[i]\n", 96 | " output_tensors = {}\n", 97 | " for key in self.data_vars:\n", 98 | " if key in self.constants:\n", 99 | " continue\n", 100 | "\n", 101 | " data_array = self.numpy_data[key]\n", 102 | " if 'z' in self.dims[key]:\n", 103 | " this_array_index = (slice(t, t + self.time_length),\n", 104 | " slice(None), y, x)\n", 105 | " else:\n", 106 | " this_array_index = (slice(t, t + self.time_length), None, y, x)\n", 107 | "\n", 108 | " sample = data_array[this_array_index][:, :, np.newaxis, np.newaxis]\n", 109 | " output_tensors[key] = sample.astype(np.float32)\n", 110 | " return output_tensors\n", 111 | "\n", 112 | " @property\n", 113 | " def time_dim(self):\n", 114 | " return self.dims[0][0]\n", 115 | "\n", 116 | " def torch_constants(self):\n", 117 | " return {\n", 118 | " key: torch.tensor(self.data[key].values, requires_grad=False)\n", 119 | " .float()\n", 120 | " for key in self.constants\n", 121 | " }\n", 122 | "\n", 123 | " @property\n", 124 | " def scale(self):\n", 125 | " std = self.std\n", 126 | " return valmap(lambda x: x.max(), std)\n", 127 | " \n", 128 | "\n", 129 | "def get_xarray_dataset():\n", 130 | "\n", 131 | " dims_3d = ['time', 'z', 'y', 'x']\n", 132 | " dims_2d = ['time', 'y', 'x']\n", 133 | "\n", 134 | " data_3d = np.ones((4, 4, 5, 2))\n", 135 | " data_2d = np.ones((4, 5, 2))\n", 136 | "\n", 137 | " return xr.Dataset({\n", 138 | " 'a': (dims_3d, data_3d),\n", 139 | " 'b': (dims_2d, data_2d)\n", 140 | " })\n", 141 | "\n", 142 | "ds = get_xarray_dataset()\n", 143 | "ds" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 2, 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "torch_dataset = XRTimeSeries(ds, time_length=4)" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 3, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "data": { 162 | "text/plain": [ 163 | "10" 164 | ] 165 | }, 166 | "execution_count": 3, 167 | "metadata": {}, 168 | "output_type": "execute_result" 169 | } 170 | ], 171 | "source": [ 172 | "len(torch_dataset)" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": {}, 178 | "source": [ 179 | "The length of the dataset is $x\\dot y$" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 4, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "data": { 189 | "text/plain": [ 190 | "{'a': array([[[[1.]],\n", 191 | " \n", 192 | " [[1.]],\n", 193 | " \n", 194 | " [[1.]],\n", 195 | " \n", 196 | " [[1.]]],\n", 197 | " \n", 198 | " \n", 199 | " [[[1.]],\n", 200 | " \n", 201 | " [[1.]],\n", 202 | " \n", 203 | " [[1.]],\n", 204 | " \n", 205 | " [[1.]]],\n", 206 | " \n", 207 | " \n", 208 | " [[[1.]],\n", 209 | " \n", 210 | " [[1.]],\n", 211 | " \n", 212 | " [[1.]],\n", 213 | " \n", 214 | " [[1.]]],\n", 215 | " \n", 216 | " \n", 217 | " [[[1.]],\n", 218 | " \n", 219 | " [[1.]],\n", 220 | " \n", 221 | " [[1.]],\n", 222 | " \n", 223 | " [[1.]]]], dtype=float32), 'b': array([[[[1.]]],\n", 224 | " \n", 225 | " \n", 226 | " [[[1.]]],\n", 227 | " \n", 228 | " \n", 229 | " [[[1.]]],\n", 230 | " \n", 231 | " \n", 232 | " [[[1.]]]], dtype=float32)}" 233 | ] 234 | }, 235 | "execution_count": 4, 236 | "metadata": {}, 237 | "output_type": "execute_result" 238 | } 239 | ], 240 | "source": [ 241 | "sample = torch_dataset[0]\n", 242 | "sample" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "metadata": {}, 248 | "source": [ 249 | "Two dimensional and three dimensional variables have broadcastable shapes" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": 5, 255 | "metadata": {}, 256 | "outputs": [ 257 | { 258 | "data": { 259 | "text/plain": [ 260 | "(4, 1, 1, 1)" 261 | ] 262 | }, 263 | "execution_count": 5, 264 | "metadata": {}, 265 | "output_type": "execute_result" 266 | } 267 | ], 268 | "source": [ 269 | "sample['b'].shape" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 6, 275 | "metadata": {}, 276 | "outputs": [ 277 | { 278 | "data": { 279 | "text/plain": [ 280 | "(4, 4, 1, 1)" 281 | ] 282 | }, 283 | "execution_count": 6, 284 | "metadata": {}, 285 | "output_type": "execute_result" 286 | } 287 | ], 288 | "source": [ 289 | "sample['a'].shape" 290 | ] 291 | }, 292 | { 293 | "cell_type": "markdown", 294 | "metadata": {}, 295 | "source": [ 296 | "Now that we have made the torch dataset object, we can pass it to pytorch's DataLoader class." 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 7, 302 | "metadata": {}, 303 | "outputs": [], 304 | "source": [ 305 | "train_loader = DataLoader(torch_dataset, batch_size=4)" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 8, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | "shape of b torch.Size([4, 4, 1, 1, 1])\n", 318 | "shape of b torch.Size([4, 4, 1, 1, 1])\n", 319 | "shape of b torch.Size([2, 4, 1, 1, 1])\n" 320 | ] 321 | } 322 | ], 323 | "source": [ 324 | "for batch in train_loader:\n", 325 | " print(\"shape of b\", batch['b'].shape)" 326 | ] 327 | }, 328 | { 329 | "cell_type": "markdown", 330 | "metadata": {}, 331 | "source": [ 332 | "The first dimension becomes the \"batch\" dimension. The other dimensions are the physical dimensions (time, z, y, x). My [model classes](https://github.com/nbren12/uwnet/blob/047a63b70985b12e17013355ecd25c908681ab76/uwnet/modules.py) accept data in this format." 333 | ] 334 | } 335 | ], 336 | "metadata": { 337 | "kernelspec": { 338 | "display_name": "Python 3", 339 | "language": "python", 340 | "name": "python3" 341 | }, 342 | "language_info": { 343 | "codemirror_mode": { 344 | "name": "ipython", 345 | "version": 3 346 | }, 347 | "file_extension": ".py", 348 | "mimetype": "text/x-python", 349 | "name": "python", 350 | "nbconvert_exporter": "python", 351 | "pygments_lexer": "ipython3", 352 | "version": "3.6.6" 353 | } 354 | }, 355 | "nbformat": 4, 356 | "nbformat_minor": 2 357 | } 358 | -------------------------------------------------------------------------------- /djgagne_partial_dependence_plot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Partial Dependence Plot Example\n", 8 | "David John Gagne\n", 9 | "\n", 10 | "The goal of this notebook is to show an example of a serial and potentially parallel partial dependence plot in order to figure out ways to scale this better with Pangeo tools." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 4, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "%matplotlib inline\n", 20 | "import numpy as np\n", 21 | "import pandas as pd\n", 22 | "from sklearn.ensemble import RandomForestRegressor\n", 23 | "from sklearn.linear_model import Ridge\n", 24 | "import matplotlib.pyplot as plt\n", 25 | "from dask.distributed import LocalCluster, Client\n", 26 | "import os\n", 27 | "from os.path import exists, join\n", 28 | "from urllib.request import urlretrieve\n", 29 | "import tarfile\n", 30 | "import glob" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "Download CSV Data." 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 3, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "name": "stdout", 47 | "output_type": "stream", 48 | "text": [ 49 | "Get csv files\n", 50 | "Extract csv tar file\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "if not exists(\"tornado_data\"):\n", 56 | " os.mkdir(\"tornado_data\")\n", 57 | "csv_tar_file = \"https://storage.googleapis.com/track_data_ncar_ams_3km_csv_small/track_data_ncar_ams_3km_csv_small.tar.gz\"\n", 58 | "print(\"Get csv files\")\n", 59 | "urlretrieve(csv_tar_file, join(\"tornado_data\", csv_tar_file.split(\"/\")[-1]))\n", 60 | "print(\"Extract csv tar file\")\n", 61 | "csv_tar = tarfile.open(join(\"tornado_data\", csv_tar_file.split(\"/\")[-1]))\n", 62 | "csv_tar.extractall(\"tornado_data/\")\n", 63 | "csv_tar.close()" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "Load CSV data" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 11, 76 | "metadata": {}, 77 | "outputs": [ 78 | { 79 | "name": "stdout", 80 | "output_type": "stream", 81 | "text": [ 82 | "['tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20101024-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20101122-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110201-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110308-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110326-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110404-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110414-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110420-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110425-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110509-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110522-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110527-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110605-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110610-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110615-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110620-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110625-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110704-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20110712-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20111116-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120218-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120315-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120323-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120401-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120409-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120426-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120503-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120510-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120529-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120606-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120622-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120701-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120706-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20120715-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20121225-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130318-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130331-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130411-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130429-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130513-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130519-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130527-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130602-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130613-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130619-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130625-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130701-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130708-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20130715-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140220-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140328-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140407-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140425-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140508-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140514-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140526-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140604-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140609-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140617-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140622-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140628-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140705-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20140710-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20141123-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150331-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150416-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150422-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150505-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150510-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150523-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150528-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150605-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150612-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150620-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150625-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150630-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150706-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20150712-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20151031-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20151227-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160224-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160323-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160401-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160415-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160429-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160505-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160511-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160522-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160528-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160608-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160616-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160622-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160628-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160707-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20160712-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20161129-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20170121-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20170228-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20170323-0000.csv', 'tornado_data/track_data_ncar_ams_3km_csv_small/track_step_NCARSTORM_d01_20170329-0000.csv']\n" 83 | ] 84 | } 85 | ], 86 | "source": [ 87 | "path = \"tornado_data/track_data_ncar_ams_3km_csv_small/\"\n", 88 | "files = sorted(glob.glob(path+\"/*.csv\"))\n", 89 | "print(files)\n", 90 | "df = pd.concat([pd.read_csv(f, parse_dates=[\"Run_Date\", \"Valid_Date\"]) for f in files], ignore_index=True)\n" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 9, 96 | "metadata": {}, 97 | "outputs": [ 98 | { 99 | "name": "stdout", 100 | "output_type": "stream", 101 | "text": [ 102 | "Step_ID\n", 103 | "Track_ID\n", 104 | "Ensemble_Name\n", 105 | "Ensemble_Member\n", 106 | "Run_Date\n", 107 | "Valid_Date\n", 108 | "Forecast_Hour\n", 109 | "Valid_Hour_UTC\n", 110 | "Duration\n", 111 | "Centroid_Lon\n", 112 | "Centroid_Lat\n", 113 | "Centroid_X\n", 114 | "Centroid_Y\n", 115 | "Storm_Motion_U\n", 116 | "Storm_Motion_V\n", 117 | "REFL_COM_mean\n", 118 | "REFL_COM_max\n", 119 | "REFL_COM_min\n", 120 | "REFL_COM_std\n", 121 | "REFL_COM_percentile_10\n", 122 | "REFL_COM_percentile_25\n", 123 | "REFL_COM_percentile_50\n", 124 | "REFL_COM_percentile_75\n", 125 | "REFL_COM_percentile_90\n", 126 | "U10_mean\n", 127 | "U10_max\n", 128 | "U10_min\n", 129 | "U10_std\n", 130 | "U10_percentile_10\n", 131 | "U10_percentile_25\n", 132 | "U10_percentile_50\n", 133 | "U10_percentile_75\n", 134 | "U10_percentile_90\n", 135 | "V10_mean\n", 136 | "V10_max\n", 137 | "V10_min\n", 138 | "V10_std\n", 139 | "V10_percentile_10\n", 140 | "V10_percentile_25\n", 141 | "V10_percentile_50\n", 142 | "V10_percentile_75\n", 143 | "V10_percentile_90\n", 144 | "T2_mean\n", 145 | "T2_max\n", 146 | "T2_min\n", 147 | "T2_std\n", 148 | "T2_percentile_10\n", 149 | "T2_percentile_25\n", 150 | "T2_percentile_50\n", 151 | "T2_percentile_75\n", 152 | "T2_percentile_90\n", 153 | "RVORT1_MAX-future_mean\n", 154 | "RVORT1_MAX-future_max\n", 155 | "RVORT1_MAX-future_min\n", 156 | "RVORT1_MAX-future_std\n", 157 | "RVORT1_MAX-future_percentile_10\n", 158 | "RVORT1_MAX-future_percentile_25\n", 159 | "RVORT1_MAX-future_percentile_50\n", 160 | "RVORT1_MAX-future_percentile_75\n", 161 | "RVORT1_MAX-future_percentile_90\n", 162 | "HAIL_MAXK1-future_mean\n", 163 | "HAIL_MAXK1-future_max\n", 164 | "HAIL_MAXK1-future_min\n", 165 | "HAIL_MAXK1-future_std\n", 166 | "HAIL_MAXK1-future_percentile_10\n", 167 | "HAIL_MAXK1-future_percentile_25\n", 168 | "HAIL_MAXK1-future_percentile_50\n", 169 | "HAIL_MAXK1-future_percentile_75\n", 170 | "HAIL_MAXK1-future_percentile_90\n", 171 | "area\n", 172 | "eccentricity\n", 173 | "major_axis_length\n", 174 | "minor_axis_length\n", 175 | "orientation\n", 176 | "Matched\n", 177 | "Max_Hail_Size\n", 178 | "Num_Matches\n", 179 | "Shape\n", 180 | "Location\n", 181 | "Scale\n" 182 | ] 183 | } 184 | ], 185 | "source": [ 186 | "for col in df.columns:\n", 187 | " print(col)" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "We are using reflectivity, u-wind, v-wind, and 2 m temperature to predict vorticity for a given storm in a dataset of storms." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 12, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "input_cols = [\"REFL_COM_mean\", \"U10_mean\", \"V10_mean\", \"T2_mean\"]\n", 204 | "output_col = [\"RVORT1_MAX-future_max\"]\n", 205 | "split_date = pd.Timestamp(\"2015-01-01\")\n", 206 | "train_in = df.loc[df[\"Run_Date\"] < split_date, input_cols]\n", 207 | "train_out = df.loc[df[\"Run_Date\"] < split_date, output_col]\n", 208 | "test_in = df.loc[df[\"Run_Date\"] >= split_date, input_cols]\n", 209 | "test_out = df.loc[df[\"Run_Date\"]>= split_date, output_col]\n" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": 16, 215 | "metadata": {}, 216 | "outputs": [ 217 | { 218 | "data": { 219 | "text/plain": [ 220 | "(76377, 1)" 221 | ] 222 | }, 223 | "execution_count": 16, 224 | "metadata": {}, 225 | "output_type": "execute_result" 226 | } 227 | ], 228 | "source": [ 229 | "train_out.shape" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "Train the random forest" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 17, 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "data": { 246 | "text/plain": [ 247 | "RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=8,\n", 248 | " max_features='auto', max_leaf_nodes=None,\n", 249 | " min_impurity_decrease=0.0, min_impurity_split=None,\n", 250 | " min_samples_leaf=1, min_samples_split=2,\n", 251 | " min_weight_fraction_leaf=0.0, n_estimators=50, n_jobs=None,\n", 252 | " oob_score=False, random_state=None, verbose=0, warm_start=False)" 253 | ] 254 | }, 255 | "execution_count": 17, 256 | "metadata": {}, 257 | "output_type": "execute_result" 258 | } 259 | ], 260 | "source": [ 261 | "rf = RandomForestRegressor(n_estimators=50, max_depth=8)\n", 262 | "rf.fit(train_in, train_out.values.ravel())" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "We want to interpret the input sensitivities for the random forest. We will use a technique called partial dependence plots, which changes the inputs in a way that can affect the mean output and reveal sensitivities of the model to certain ranges of inputs. " 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 18, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "def partial_dependence_1d(x, model, var_index, var_vals):\n", 279 | " \"\"\"\n", 280 | " Calculate how the mean prediction of an ML model varies if one variable's value is fixed across all input\n", 281 | " examples.\n", 282 | "\n", 283 | " Args:\n", 284 | " x: array of input variables\n", 285 | " model: scikit-learn style model object\n", 286 | " var_index: column index of the variable being investigated\n", 287 | " var_vals: values of the input variable that are fixed.\n", 288 | "\n", 289 | " Returns:\n", 290 | " Array of partial dependence values.\n", 291 | " \"\"\"\n", 292 | " partial_dependence = np.zeros(var_vals.shape)\n", 293 | " x_copy = np.copy(x)\n", 294 | " for v, var_val in enumerate(var_vals):\n", 295 | " x_copy[:, var_index] = var_val\n", 296 | " partial_dependence[v] = model.predict(x_copy).mean()\n", 297 | " return partial_dependence" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": 19, 303 | "metadata": {}, 304 | "outputs": [ 305 | { 306 | "data": { 307 | "text/html": [ 308 | "
\n", 309 | "\n", 322 | "\n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | "
REFL_COM_meanU10_meanV10_meanT2_mean
count76377.00000076377.00000076377.00000076377.000000
mean46.8465150.4497490.542538289.408528
std3.9840914.3476254.4619366.931555
min40.082820-18.653700-20.561280262.921600
25%43.755930-2.366580-2.301950285.114260
50%46.1706500.3968100.659590290.625850
75%49.1281903.2719803.496990294.398190
max68.74316019.68664018.616930312.149230
\n", 391 | "
" 392 | ], 393 | "text/plain": [ 394 | " REFL_COM_mean U10_mean V10_mean T2_mean\n", 395 | "count 76377.000000 76377.000000 76377.000000 76377.000000\n", 396 | "mean 46.846515 0.449749 0.542538 289.408528\n", 397 | "std 3.984091 4.347625 4.461936 6.931555\n", 398 | "min 40.082820 -18.653700 -20.561280 262.921600\n", 399 | "25% 43.755930 -2.366580 -2.301950 285.114260\n", 400 | "50% 46.170650 0.396810 0.659590 290.625850\n", 401 | "75% 49.128190 3.271980 3.496990 294.398190\n", 402 | "max 68.743160 19.686640 18.616930 312.149230" 403 | ] 404 | }, 405 | "execution_count": 19, 406 | "metadata": {}, 407 | "output_type": "execute_result" 408 | } 409 | ], 410 | "source": [ 411 | "train_in.describe()" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "metadata": {}, 417 | "source": [ 418 | "Here is an example for a single variable." 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": 41, 424 | "metadata": {}, 425 | "outputs": [], 426 | "source": [ 427 | "pd_count = 50\n", 428 | "index = 0\n", 429 | "var_vals = np.linspace(train_in.iloc[:, 0].min(), train_in.iloc[:, 0].max(), pd_count)\n", 430 | "pd_vals = partial_dependence_1d(train_in, rf, index, var_vals)" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": 43, 436 | "metadata": {}, 437 | "outputs": [ 438 | { 439 | "name": "stdout", 440 | "output_type": "stream", 441 | "text": [ 442 | "4.99 s ± 236 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" 443 | ] 444 | } 445 | ], 446 | "source": [ 447 | "%timeit pd_vals = partial_dependence_1d(train_in, rf, index, var_vals)" 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 45, 453 | "metadata": {}, 454 | "outputs": [ 455 | { 456 | "data": { 457 | "text/plain": [ 458 | "Text(0.5, 0, 'REFL_COM_mean')" 459 | ] 460 | }, 461 | "execution_count": 45, 462 | "metadata": {}, 463 | "output_type": "execute_result" 464 | }, 465 | { 466 | "data": { 467 | "image/png": "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\n", 468 | "text/plain": [ 469 | "
" 470 | ] 471 | }, 472 | "metadata": { 473 | "needs_background": "light" 474 | }, 475 | "output_type": "display_data" 476 | } 477 | ], 478 | "source": [ 479 | "plt.plot(var_vals, pd_vals, 'ko-')\n", 480 | "plt.xlabel(input_cols[index])" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "How well can Dask parallelize the task across all the input variables?" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 25, 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "cluster = LocalCluster(n_workers=4)\n", 497 | "client = Client(cluster)" 498 | ] 499 | }, 500 | { 501 | "cell_type": "code", 502 | "execution_count": 26, 503 | "metadata": {}, 504 | "outputs": [ 505 | { 506 | "data": { 507 | "text/html": [ 508 | "\n", 509 | "\n", 510 | "\n", 517 | "\n", 525 | "\n", 526 | "
\n", 511 | "

Client

\n", 512 | "\n", 516 | "
\n", 518 | "

Cluster

\n", 519 | "
    \n", 520 | "
  • Workers: 4
  • \n", 521 | "
  • Cores: 8
  • \n", 522 | "
  • Memory: 17.18 GB
  • \n", 523 | "
\n", 524 | "
" 527 | ], 528 | "text/plain": [ 529 | "" 530 | ] 531 | }, 532 | "execution_count": 26, 533 | "metadata": {}, 534 | "output_type": "execute_result" 535 | } 536 | ], 537 | "source": [ 538 | "client" 539 | ] 540 | }, 541 | { 542 | "cell_type": "code", 543 | "execution_count": 38, 544 | "metadata": {}, 545 | "outputs": [], 546 | "source": [ 547 | "pd_count = 50\n", 548 | "var_vals = np.zeros((len(input_cols), pd_count))\n", 549 | "futures = []\n", 550 | "train_future = client.scatter(train_in.values)\n", 551 | "for i, input_col in enumerate(input_cols):\n", 552 | " var_vals[i] = np.linspace(train_in[input_col].min(), train_in[input_col].max(), pd_count)\n", 553 | " futures.append(client.submit(partial_dependence_1d, train_future, rf, i, var_vals[i]))" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": 39, 559 | "metadata": {}, 560 | "outputs": [], 561 | "source": [ 562 | "results = client.gather(futures)" 563 | ] 564 | }, 565 | { 566 | "cell_type": "code", 567 | "execution_count": 40, 568 | "metadata": {}, 569 | "outputs": [ 570 | { 571 | "data": { 572 | "image/png": "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\n", 573 | "text/plain": [ 574 | "
" 575 | ] 576 | }, 577 | "metadata": { 578 | "needs_background": "light" 579 | }, 580 | "output_type": "display_data" 581 | } 582 | ], 583 | "source": [ 584 | "fig, axes = plt.subplots(2, 2, figsize=(10, 8))\n", 585 | "axef = axes.ravel()\n", 586 | "for r, res in enumerate(results):\n", 587 | " axef[r].plot(var_vals[r], res, 'ko-')\n", 588 | " axef[r].set_xlabel(input_cols[r])" 589 | ] 590 | }, 591 | { 592 | "cell_type": "markdown", 593 | "metadata": {}, 594 | "source": [ 595 | "It turns out that in this case, dask already does quite well with paralellizing the task? \n", 596 | "\n", 597 | "Is there a way we can make dask struggle or fail? At what dataset size do we see problems?\n", 598 | "\n", 599 | "Is there a more concise way to paralellize the task?" 600 | ] 601 | }, 602 | { 603 | "cell_type": "code", 604 | "execution_count": null, 605 | "metadata": {}, 606 | "outputs": [], 607 | "source": [] 608 | } 609 | ], 610 | "metadata": { 611 | "kernelspec": { 612 | "display_name": "Python 3", 613 | "language": "python", 614 | "name": "python3" 615 | }, 616 | "language_info": { 617 | "codemirror_mode": { 618 | "name": "ipython", 619 | "version": 3 620 | }, 621 | "file_extension": ".py", 622 | "mimetype": "text/x-python", 623 | "name": "python", 624 | "nbconvert_exporter": "python", 625 | "pygments_lexer": "ipython3", 626 | "version": "3.6.7" 627 | } 628 | }, 629 | "nbformat": 4, 630 | "nbformat_minor": 2 631 | } 632 | -------------------------------------------------------------------------------- /rasp-data-loading.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# xarray use case: Neural network training\n", 8 | "\n", 9 | "\n", 10 | "**tl;dr**\n", 11 | "\n", 12 | "1. This notebook is an example of reading from a climate model netCDF file to train a neural network. Neural networks (for use in parameterization research) require random columns of several stacked variables at a time. \n", 13 | "\n", 14 | "2. Experiments in this notebook show:\n", 15 | " 1. Reading from raw climate model output files is super slow (1s per batch... need speeds on the order of ms)\n", 16 | " 2. open_mfdataset is half as fast as opening the same dataset with open_dataset\n", 17 | " 3. Pure h5py is much faster than reading the same dataset using xarray (even using the h5 backend)\n", 18 | "\n", 19 | "3. Currently, I revert to preformatting the dataset (flatten time, lat, lon). This gets the reading speed down to milliseconds per batch.\n", 20 | "\n", 21 | "**Conclusions**\n", 22 | "\n", 23 | "Reading straight from the raw netCDF files (with all dimensions intact) is handy and might be necessary for later applications (using continuous time slices or lat-lon regions for RNNs or CNNs).\n", 24 | "\n", 25 | "However, at the moment this is many orders of magnitude too slow. Preprocessing seems required.\n", 26 | "\n", 27 | "What would be a good way of speeding this up without too extensive post processing?\n" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 1, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import xarray as xr\n", 37 | "import numpy as np" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "text/plain": [ 48 | "'0.11.2'" 49 | ] 50 | }, 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "output_type": "execute_result" 54 | } 55 | ], 56 | "source": [ 57 | "xr.__version__" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "## Load an example dataset\n", 65 | "\n", 66 | "I uploaded a sample dataset here: http://doi.org/10.5281/zenodo.2559313\n", 67 | "\n", 68 | "The files are around 1GB large. Let's download it.\n", 69 | "\n", 70 | "NOTE: I have all my data on an SSD" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 47, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "# Modify this path!\n", 80 | "DATADIR = '/local/S.Rasp/tmp/'" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 48, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "--2019-02-07 13:08:52-- https://zenodo.org/record/2559183/files/sample_SPCAM_1.nc\n", 93 | "Resolving zenodo.org (zenodo.org)... 137.138.76.77\n", 94 | "Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected.\n", 95 | "HTTP request sent, awaiting response... 200 OK\n", 96 | "Length: 923498891 (881M) [application/octet-stream]\n", 97 | "Saving to: ‘/local/S.Rasp/tmp/sample_SPCAM_1.nc’\n", 98 | "\n", 99 | "sample_SPCAM_1.nc 100%[===================>] 880.72M 6.59MB/s in 1m 49s \n", 100 | "\n", 101 | "2019-02-07 13:10:42 (8.09 MB/s) - ‘/local/S.Rasp/tmp/sample_SPCAM_1.nc’ saved [923498891/923498891]\n", 102 | "\n", 103 | "--2019-02-07 13:10:42-- https://zenodo.org/record/2559183/files/sample_SPCAM_2.nc\n", 104 | "Resolving zenodo.org (zenodo.org)... 137.138.76.77\n", 105 | "Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected.\n", 106 | "HTTP request sent, awaiting response... 200 OK\n", 107 | "Length: 923498891 (881M) [application/octet-stream]\n", 108 | "Saving to: ‘/local/S.Rasp/tmp/sample_SPCAM_2.nc’\n", 109 | "\n", 110 | "sample_SPCAM_2.nc 100%[===================>] 880.72M 24.5MB/s in 87s \n", 111 | "\n", 112 | "2019-02-07 13:12:09 (10.1 MB/s) - ‘/local/S.Rasp/tmp/sample_SPCAM_2.nc’ saved [923498891/923498891]\n", 113 | "\n", 114 | "--2019-02-07 13:12:09-- https://zenodo.org/record/2559183/files/sample_SPCAM_concat.nc\n", 115 | "Resolving zenodo.org (zenodo.org)... 137.138.76.77\n", 116 | "Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected.\n", 117 | "HTTP request sent, awaiting response... 200 OK\n", 118 | "Length: 1846816429 (1.7G) [application/octet-stream]\n", 119 | "Saving to: ‘/local/S.Rasp/tmp/sample_SPCAM_concat.nc’\n", 120 | "\n", 121 | "sample_SPCAM_concat 100%[===================>] 1.72G 3.80MB/s in 3m 28s \n", 122 | "\n", 123 | "2019-02-07 13:15:38 (8.46 MB/s) - ‘/local/S.Rasp/tmp/sample_SPCAM_concat.nc’ saved [1846816429/1846816429]\n", 124 | "\n" 125 | ] 126 | } 127 | ], 128 | "source": [ 129 | "!wget -P $DATADIR https://zenodo.org/record/2559313/files/sample_SPCAM_1.nc\n", 130 | "!wget -P $DATADIR https://zenodo.org/record/2559313/files/sample_SPCAM_2.nc\n", 131 | "!wget -P $DATADIR https://zenodo.org/record/2559313/files/sample_SPCAM_concat.nc" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 49, 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "name": "stdout", 141 | "output_type": "stream", 142 | "text": [ 143 | "-rw-r--r-- 1 S.Rasp ls-craig 881M Feb 7 13:00 /local/S.Rasp/tmp//sample_SPCAM_1.nc\r\n", 144 | "-rw-r--r-- 1 S.Rasp ls-craig 881M Feb 7 13:00 /local/S.Rasp/tmp//sample_SPCAM_2.nc\r\n", 145 | "-rw-r--r-- 1 S.Rasp ls-craig 1.8G Feb 7 13:00 /local/S.Rasp/tmp//sample_SPCAM_concat.nc\r\n" 146 | ] 147 | } 148 | ], 149 | "source": [ 150 | "!ls -lh $DATADIR/sample_SPCAM*" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "The files are typical climate model output files. `sample_SPCAM_1.nc` and `sample_SPCAM_2.nc` are two contiguous output files. `sample_SPCAM_concat.nc` is the concatenated version of the two files." 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 53, 163 | "metadata": {}, 164 | "outputs": [ 165 | { 166 | "name": "stdout", 167 | "output_type": "stream", 168 | "text": [ 169 | "CPU times: user 56 ms, sys: 0 ns, total: 56 ms\n", 170 | "Wall time: 54.7 ms\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "%%time\n", 176 | "ds = xr.open_mfdataset(DATADIR + 'sample_SPCAM_1.nc')" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 54, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "text/plain": [ 187 | "\n", 188 | "Dimensions: (crm_x: 32, crm_y: 1, crm_z: 28, ilev: 31, isccp_prs: 7, isccp_prstau: 49, isccp_tau: 7, lat: 64, lev: 30, lon: 128, tbnd: 2, time: 48)\n", 189 | "Coordinates:\n", 190 | " * lat (lat) float64 -87.86 -85.1 -82.31 -79.53 ... 82.31 85.1 87.86\n", 191 | " * lon (lon) float64 0.0 2.812 5.625 8.438 ... 351.6 354.4 357.2\n", 192 | " * crm_x (crm_x) float64 0.0 4.0 8.0 12.0 ... 112.0 116.0 120.0 124.0\n", 193 | " * crm_y (crm_y) float64 0.0\n", 194 | " * crm_z (crm_z) float64 992.6 976.3 957.5 936.2 ... 38.27 24.61 14.36\n", 195 | " * lev (lev) float64 3.643 7.595 14.36 24.61 ... 957.5 976.3 992.6\n", 196 | " * ilev (ilev) float64 2.255 5.032 10.16 18.56 ... 967.5 985.1 1e+03\n", 197 | " * isccp_prs (isccp_prs) float64 90.0 245.0 375.0 500.0 620.0 740.0 900.0\n", 198 | " * isccp_tau (isccp_tau) float64 0.15 0.8 2.45 6.5 16.2 41.5 219.5\n", 199 | " * isccp_prstau (isccp_prstau) float64 90.0 90.0 90.0 ... 900.0 900.0 900.2\n", 200 | " * time (time) object 0000-01-01 00:00:00 ... 0000-01-01 23:29:59\n", 201 | "Dimensions without coordinates: tbnd\n", 202 | "Data variables:\n", 203 | " P0 float64 ...\n", 204 | " time_bnds (time, tbnd) object dask.array\n", 205 | " date_written (time) |S8 dask.array\n", 206 | " time_written (time) |S8 dask.array\n", 207 | " ntrm int32 ...\n", 208 | " ntrn int32 ...\n", 209 | " ntrk int32 ...\n", 210 | " ndbase int32 ...\n", 211 | " nsbase int32 ...\n", 212 | " nbdate int32 ...\n", 213 | " nbsec int32 ...\n", 214 | " mdt int32 ...\n", 215 | " nlon (lat) int32 dask.array\n", 216 | " wnummax (lat) int32 dask.array\n", 217 | " hyai (ilev) float64 dask.array\n", 218 | " hybi (ilev) float64 dask.array\n", 219 | " hyam (lev) float64 dask.array\n", 220 | " hybm (lev) float64 dask.array\n", 221 | " gw (lat) float64 dask.array\n", 222 | " ndcur (time) int32 dask.array\n", 223 | " nscur (time) int32 dask.array\n", 224 | " date (time) int32 dask.array\n", 225 | " datesec (time) int32 dask.array\n", 226 | " nsteph (time) int32 dask.array\n", 227 | " DTV (time, lev, lat, lon) float32 dask.array\n", 228 | " DTVKE (time, lev, lat, lon) float32 dask.array\n", 229 | " FLNS (time, lat, lon) float32 dask.array\n", 230 | " FLNT (time, lat, lon) float32 dask.array\n", 231 | " FLUT (time, lat, lon) float32 dask.array\n", 232 | " FSNS (time, lat, lon) float32 dask.array\n", 233 | " FSNT (time, lat, lon) float32 dask.array\n", 234 | " LHFLX (time, lat, lon) float32 dask.array\n", 235 | " PHCLDICE (time, lev, lat, lon) float32 dask.array\n", 236 | " PHCLDLIQ (time, lev, lat, lon) float32 dask.array\n", 237 | " PHQ (time, lev, lat, lon) float32 dask.array\n", 238 | " PRECC (time, lat, lon) float32 dask.array\n", 239 | " PRECL (time, lat, lon) float32 dask.array\n", 240 | " PRECSC (time, lat, lon) float32 dask.array\n", 241 | " PRECSL (time, lat, lon) float32 dask.array\n", 242 | " PRECSTEN (time, lat, lon) float32 dask.array\n", 243 | " PRECT (time, lat, lon) float32 dask.array\n", 244 | " PRECTEND (time, lat, lon) float32 dask.array\n", 245 | " PS (time, lat, lon) float32 dask.array\n", 246 | " QAP (time, lev, lat, lon) float32 dask.array\n", 247 | " QCAP (time, lev, lat, lon) float32 dask.array\n", 248 | " QIAP (time, lev, lat, lon) float32 dask.array\n", 249 | " QRL (time, lev, lat, lon) float32 dask.array\n", 250 | " QRS (time, lev, lat, lon) float32 dask.array\n", 251 | " SHFLX (time, lat, lon) float32 dask.array\n", 252 | " SOLIN (time, lat, lon) float32 dask.array\n", 253 | " SPDQ (time, lev, lat, lon) float32 dask.array\n", 254 | " SPDT (time, lev, lat, lon) float32 dask.array\n", 255 | " T (time, lev, lat, lon) float32 dask.array\n", 256 | " TAP (time, lev, lat, lon) float32 dask.array\n", 257 | " TPHYSTND (time, lev, lat, lon) float32 dask.array\n", 258 | " TS (time, lat, lon) float32 dask.array\n", 259 | " UAP (time, lev, lat, lon) float32 dask.array\n", 260 | " VAP (time, lev, lat, lon) float32 dask.array\n", 261 | " VD01 (time, lev, lat, lon) float32 dask.array\n", 262 | " VPHYSTND (time, lev, lat, lon) float32 dask.array\n", 263 | "Attributes:\n", 264 | " Conventions: CF-1.0\n", 265 | " source: CAM\n", 266 | " case: AndKua_aqua_SPCAM3.0_sp_fbp32\n", 267 | " title: \n", 268 | " logname: tg847872\n", 269 | " host: \n", 270 | " Version: $Name: $\n", 271 | " revision_Id: $Id: history.F90,v 1.26.2.38 2003/12/15 18:52:35 hender Exp $" 272 | ] 273 | }, 274 | "execution_count": 54, 275 | "metadata": {}, 276 | "output_type": "execute_result" 277 | } 278 | ], 279 | "source": [ 280 | "ds" 281 | ] 282 | }, 283 | { 284 | "cell_type": "markdown", 285 | "metadata": {}, 286 | "source": [ 287 | "## Random columns for machine learning parameterizations\n", 288 | "\n", 289 | "For the work on ML parameterizations that a few of us are doing now, we would like to work one column at a time. One simple example would be predicting the temperature and humidity tendencies (TPHYSTND and PHQ) from the temperature and humidity profiles (TAP and QAP). \n", 290 | "\n", 291 | "This means we would like to give the neural network a stacked vector containing the inputs (2 x 30 levels) and ask it to predict the outputs (also 2 x 30 levels).\n", 292 | "\n", 293 | "In NN training, we usually train on a batch of data at a time. Batches typically have a few hundred samples (columns in our case). It is really important that the samples in a batch are not correlated but rather represent a random sample of the entire dataset.\n", 294 | "\n", 295 | "To achieve this we will write a data generator that loads the batches by randomly selecting along the time, lat and lon dimensions." 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 57, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "class DataGenerator(object):\n", 305 | " \"\"\"\n", 306 | " Data generator that randomly (if shuffle = True) picks columns from the dataset and returns them in \n", 307 | " batches. For each column the input variables and output variables will be stacked.\n", 308 | " \"\"\"\n", 309 | " def __init__(self, fn_or_ds, batch_size=128, input_vars=['TAP', 'QAP'], output_vars=['TPHYSTND', 'PHQ'], \n", 310 | " shuffle=True, engine='netcdf4'):\n", 311 | " self.ds = xr.open_mfdataset(fn_or_ds, engine=engine) if type(fn_or_ds) is str else fn_or_ds\n", 312 | " self.batch_size = batch_size\n", 313 | " self.input_vars = input_vars\n", 314 | " self.output_vars = output_vars\n", 315 | " self.ntime, self.nlat, self.nlon = self.ds.time.size, self.ds.lat.size, self.ds.lon.size\n", 316 | " self.ntot = self.ntime * self.nlat * self.ntime\n", 317 | " self.n_batches = self.ntot // batch_size\n", 318 | " self.indices = np.arange(self.ntot)\n", 319 | " if shuffle:\n", 320 | " self.indices = np.random.permutation(self.indices)\n", 321 | " def __getitem__(self, index):\n", 322 | " time_indices, lat_indices, lon_indices = np.unravel_index(\n", 323 | " self.indices[index*self.batch_size:(index+1)*self.batch_size], (self.ntime, self.nlat, self.nlon)\n", 324 | " )\n", 325 | " \n", 326 | " X, Y = [], []\n", 327 | " for itime, ilat, ilon in zip(time_indices, lat_indices, lon_indices):\n", 328 | " X.append(\n", 329 | " np.concatenate(\n", 330 | " [self.ds[v].isel(time=itime, lat=ilat, lon=ilon).values for v in self.input_vars]\n", 331 | " )\n", 332 | " )\n", 333 | " Y.append(\n", 334 | " np.concatenate(\n", 335 | " [self.ds[v].isel(time=itime, lat=ilat, lon=ilon).values for v in self.output_vars]\n", 336 | " )\n", 337 | " )\n", 338 | "\n", 339 | " return np.array(X), np.array(Y)" 340 | ] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "metadata": {}, 345 | "source": [ 346 | "### Multi-file dataset\n", 347 | "\n", 348 | "Let's start by using the split dataset `sample_SPCAM_1.nc` and `sample_SPCAM_2.nc`." 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": 58, 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [ 357 | "gen = DataGenerator(DATADIR + 'sample_SPCAM_[1-2].nc')" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 59, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "# This is how we get one batch of inputs and corresponding outputs\n", 367 | "x, y = gen[0]" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": 60, 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "data": { 377 | "text/plain": [ 378 | "((128, 60), (128, 60))" 379 | ] 380 | }, 381 | "execution_count": 60, 382 | "metadata": {}, 383 | "output_type": "execute_result" 384 | } 385 | ], 386 | "source": [ 387 | "x.shape, y.shape" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": 61, 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [ 396 | "# A little test function to check the timing.\n", 397 | "def test(g, n):\n", 398 | " for i in range(n):\n", 399 | " x, y = g[i]" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": 64, 405 | "metadata": {}, 406 | "outputs": [ 407 | { 408 | "name": "stdout", 409 | "output_type": "stream", 410 | "text": [ 411 | "CPU times: user 13.3 s, sys: 1.34 s, total: 14.6 s\n", 412 | "Wall time: 14.3 s\n" 413 | ] 414 | } 415 | ], 416 | "source": [ 417 | "%%time\n", 418 | "test(gen, 10)" 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": 65, 424 | "metadata": {}, 425 | "outputs": [ 426 | { 427 | "name": "stdout", 428 | "output_type": "stream", 429 | "text": [ 430 | "CPU times: user 12.5 s, sys: 1.28 s, total: 13.8 s\n", 431 | "Wall time: 13.5 s\n" 432 | ] 433 | } 434 | ], 435 | "source": [ 436 | "# does shuffling make a big difference\n", 437 | "gen = DataGenerator(DATADIR + 'sample_SPCAM_[1-2].nc', shuffle=True)\n", 438 | "%time test(gen, 10)" 439 | ] 440 | }, 441 | { 442 | "cell_type": "markdown", 443 | "metadata": {}, 444 | "source": [ 445 | "So it takes more than one second to read one batch. This is way too slow to train a neural network in a reasonable amount of time. Shuffling doesn't seem to be a huge problem, but even without shuffling I am probably accessing the data in a different order than saved on disc. \n", 446 | "\n", 447 | "Let's check what actually takes that long." 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 66, 453 | "metadata": {}, 454 | "outputs": [ 455 | { 456 | "name": "stdout", 457 | "output_type": "stream", 458 | "text": [ 459 | "The line_profiler extension is already loaded. To reload it, use:\n", 460 | " %reload_ext line_profiler\n" 461 | ] 462 | } 463 | ], 464 | "source": [ 465 | "%load_ext line_profiler" 466 | ] 467 | }, 468 | { 469 | "cell_type": "code", 470 | "execution_count": 67, 471 | "metadata": {}, 472 | "outputs": [], 473 | "source": [ 474 | "%lprun -f gen.__getitem__ test(gen, 10)" 475 | ] 476 | }, 477 | { 478 | "cell_type": "markdown", 479 | "metadata": {}, 480 | "source": [ 481 | "Output:\n", 482 | "\n", 483 | "```\n", 484 | "Timer unit: 1e-06 s\n", 485 | "\n", 486 | "Total time: 24.5229 s\n", 487 | "File: \n", 488 | "Function: __getitem__ at line 18\n", 489 | "\n", 490 | "Line # Hits Time Per Hit % Time Line Contents\n", 491 | "==============================================================\n", 492 | " 18 def __getitem__(self, index):\n", 493 | " 19 10 17.0 1.7 0.0 time_indices, lat_indices, lon_indices = np.unravel_index(\n", 494 | " 20 10 267.0 26.7 0.0 self.indices[index*self.batch_size:(index+1)*self.batch_size], (self.ntime, self.nlat, self.nlon)\n", 495 | " 21 )\n", 496 | " 22 \n", 497 | " 23 10 10.0 1.0 0.0 X, Y = [], []\n", 498 | " 24 1290 4642.0 3.6 0.0 for itime, ilat, ilon in zip(time_indices, lat_indices, lon_indices):\n", 499 | " 25 1280 1399.0 1.1 0.0 X.append(\n", 500 | " 26 1280 1721.0 1.3 0.0 np.concatenate(\n", 501 | " 27 1280 12256070.0 9575.1 50.0 [self.ds[v].isel(time=itime, lat=ilat, lon=ilon).values for v in self.input_vars]\n", 502 | " 28 )\n", 503 | " 29 )\n", 504 | " 30 1280 2393.0 1.9 0.0 Y.append(\n", 505 | " 31 1280 1750.0 1.4 0.0 np.concatenate(\n", 506 | " 32 1280 12253415.0 9573.0 50.0 [self.ds[v].isel(time=itime, lat=ilat, lon=ilon).values for v in self.output_vars]\n", 507 | " 33 )\n", 508 | " 34 )\n", 509 | " 35 \n", 510 | " 36 10 1218.0 121.8 0.0 return np.array(X), np.array(Y)\n", 511 | "```" 512 | ] 513 | }, 514 | { 515 | "cell_type": "markdown", 516 | "metadata": {}, 517 | "source": [ 518 | "### Using the concatenated dataset\n", 519 | "\n", 520 | "Let's see whether it makes a difference to use the pre-concatenated dataset." 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 74, 526 | "metadata": {}, 527 | "outputs": [ 528 | { 529 | "name": "stdout", 530 | "output_type": "stream", 531 | "text": [ 532 | "CPU times: user 5.93 s, sys: 984 ms, total: 6.91 s\n", 533 | "Wall time: 6.91 s\n" 534 | ] 535 | } 536 | ], 537 | "source": [ 538 | "ds = xr.open_dataset(f'{DATADIR}sample_SPCAM_concat.nc')\n", 539 | "gen = DataGenerator(ds, shuffle=True)\n", 540 | "%time test(gen, 10)" 541 | ] 542 | }, 543 | { 544 | "cell_type": "code", 545 | "execution_count": 76, 546 | "metadata": {}, 547 | "outputs": [ 548 | { 549 | "name": "stdout", 550 | "output_type": "stream", 551 | "text": [ 552 | "CPU times: user 11.5 s, sys: 1.25 s, total: 12.8 s\n", 553 | "Wall time: 12.5 s\n" 554 | ] 555 | } 556 | ], 557 | "source": [ 558 | "ds = xr.open_mfdataset(f'{DATADIR}sample_SPCAM_concat.nc')\n", 559 | "gen = DataGenerator(ds, shuffle=True)\n", 560 | "%time test(gen, 10)" 561 | ] 562 | }, 563 | { 564 | "cell_type": "markdown", 565 | "metadata": {}, 566 | "source": [ 567 | "So yes, it approximately halves the time but only if the single dataset is NOT opened with `open_mfdataset`." 568 | ] 569 | }, 570 | { 571 | "cell_type": "markdown", 572 | "metadata": {}, 573 | "source": [ 574 | "### With h5py engine\n", 575 | "\n", 576 | "Let's see whether using the h5py backend makes a difference" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": 77, 582 | "metadata": {}, 583 | "outputs": [], 584 | "source": [ 585 | "import h5netcdf" 586 | ] 587 | }, 588 | { 589 | "cell_type": "code", 590 | "execution_count": 79, 591 | "metadata": {}, 592 | "outputs": [], 593 | "source": [ 594 | "ds.close()" 595 | ] 596 | }, 597 | { 598 | "cell_type": "code", 599 | "execution_count": 80, 600 | "metadata": {}, 601 | "outputs": [], 602 | "source": [ 603 | "ds = xr.open_dataset(f'{DATADIR}sample_SPCAM_concat.nc', engine='h5netcdf')\n", 604 | "gen = DataGenerator(ds)" 605 | ] 606 | }, 607 | { 608 | "cell_type": "code", 609 | "execution_count": 81, 610 | "metadata": {}, 611 | "outputs": [ 612 | { 613 | "name": "stdout", 614 | "output_type": "stream", 615 | "text": [ 616 | "CPU times: user 6.97 s, sys: 972 ms, total: 7.94 s\n", 617 | "Wall time: 7.8 s\n" 618 | ] 619 | } 620 | ], 621 | "source": [ 622 | "%%time\n", 623 | "test(gen, 10)" 624 | ] 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "metadata": {}, 629 | "source": [ 630 | "Doesn't seem to speed it up" 631 | ] 632 | }, 633 | { 634 | "cell_type": "code", 635 | "execution_count": 83, 636 | "metadata": {}, 637 | "outputs": [], 638 | "source": [ 639 | "ds.close()" 640 | ] 641 | }, 642 | { 643 | "cell_type": "markdown", 644 | "metadata": {}, 645 | "source": [ 646 | "### Using plain h5py\n", 647 | "\n", 648 | "Let's write a version of the data generator that uses plain h5py for data loading." 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": 82, 654 | "metadata": {}, 655 | "outputs": [], 656 | "source": [ 657 | "class DataGeneratorH5(object):\n", 658 | " def __init__(self, fn, batch_size=128, input_vars=['TAP', 'QAP'], output_vars=['TPHYSTND', 'PHQ'], shuffle=True):\n", 659 | " self.ds = xr.open_dataset(fn)\n", 660 | " self.batch_size = batch_size\n", 661 | " self.input_vars = input_vars\n", 662 | " self.output_vars = output_vars\n", 663 | " self.ntime, self.nlat, self.nlon = self.ds.time.size, self.ds.lat.size, self.ds.lon.size\n", 664 | " self.ntot = self.ntime * self.nlat * self.ntime\n", 665 | " self.n_batches = self.ntot // batch_size\n", 666 | " self.indices = np.arange(self.ntot)\n", 667 | " if shuffle:\n", 668 | " self.indices = np.random.permutation(self.indices)\n", 669 | " \n", 670 | " # Close xarray dataset and open h5py object\n", 671 | " self.ds.close()\n", 672 | " self.ds = h5py.File(fn, 'r')\n", 673 | " \n", 674 | " def __getitem__(self, index):\n", 675 | " time_indices, lat_indices, lon_indices = np.unravel_index(\n", 676 | " self.indices[index*self.batch_size:(index+1)*self.batch_size], (self.ntime, self.nlat, self.nlon)\n", 677 | " )\n", 678 | " \n", 679 | " X, Y = [], []\n", 680 | " for itime, ilat, ilon in zip(time_indices, lat_indices, lon_indices):\n", 681 | " X.append(\n", 682 | " np.concatenate(\n", 683 | " [self.ds[v][itime, :, ilat, ilon] for v in self.input_vars]\n", 684 | " )\n", 685 | " )\n", 686 | " Y.append(\n", 687 | " np.concatenate(\n", 688 | " [self.ds[v][itime, :, ilat, ilon] for v in self.output_vars]\n", 689 | " )\n", 690 | " )\n", 691 | "\n", 692 | " return np.array(X), np.array(Y)" 693 | ] 694 | }, 695 | { 696 | "cell_type": "code", 697 | "execution_count": 84, 698 | "metadata": {}, 699 | "outputs": [], 700 | "source": [ 701 | "gen = DataGeneratorH5(f'{DATADIR}sample_SPCAM_concat.nc')" 702 | ] 703 | }, 704 | { 705 | "cell_type": "code", 706 | "execution_count": 85, 707 | "metadata": {}, 708 | "outputs": [ 709 | { 710 | "name": "stdout", 711 | "output_type": "stream", 712 | "text": [ 713 | "CPU times: user 1.78 s, sys: 860 ms, total: 2.64 s\n", 714 | "Wall time: 2.61 s\n" 715 | ] 716 | } 717 | ], 718 | "source": [ 719 | "%%time\n", 720 | "test(gen, 10)" 721 | ] 722 | }, 723 | { 724 | "cell_type": "code", 725 | "execution_count": 96, 726 | "metadata": {}, 727 | "outputs": [], 728 | "source": [ 729 | "gen.ds.close()" 730 | ] 731 | }, 732 | { 733 | "cell_type": "markdown", 734 | "metadata": {}, 735 | "source": [ 736 | "So this is significantly faster than xarray." 737 | ] 738 | }, 739 | { 740 | "cell_type": "markdown", 741 | "metadata": {}, 742 | "source": [ 743 | "## Use in a simple neural network\n", 744 | "\n", 745 | "How would we actually use this data generator for network training...\n", 746 | "\n", 747 | "Note that this neural network will not actually learn much because we didn't normalize the input data. But we only care about computational performance here, right?" 748 | ] 749 | }, 750 | { 751 | "cell_type": "code", 752 | "execution_count": 87, 753 | "metadata": {}, 754 | "outputs": [], 755 | "source": [ 756 | "import tensorflow as tf\n", 757 | "from tensorflow.keras.layers import *\n", 758 | "from tensorflow.keras.models import Sequential" 759 | ] 760 | }, 761 | { 762 | "cell_type": "code", 763 | "execution_count": 88, 764 | "metadata": {}, 765 | "outputs": [ 766 | { 767 | "data": { 768 | "text/plain": [ 769 | "'2.1.6-tf'" 770 | ] 771 | }, 772 | "execution_count": 88, 773 | "metadata": {}, 774 | "output_type": "execute_result" 775 | } 776 | ], 777 | "source": [ 778 | "tf.keras.__version__" 779 | ] 780 | }, 781 | { 782 | "cell_type": "code", 783 | "execution_count": 89, 784 | "metadata": {}, 785 | "outputs": [], 786 | "source": [ 787 | "model = Sequential([\n", 788 | " Dense(128, input_shape=(60,), activation='relu'),\n", 789 | " Dense(60),\n", 790 | "])" 791 | ] 792 | }, 793 | { 794 | "cell_type": "code", 795 | "execution_count": 90, 796 | "metadata": {}, 797 | "outputs": [ 798 | { 799 | "name": "stdout", 800 | "output_type": "stream", 801 | "text": [ 802 | "_________________________________________________________________\n", 803 | "Layer (type) Output Shape Param # \n", 804 | "=================================================================\n", 805 | "dense (Dense) (None, 128) 7808 \n", 806 | "_________________________________________________________________\n", 807 | "dense_1 (Dense) (None, 60) 7740 \n", 808 | "=================================================================\n", 809 | "Total params: 15,548\n", 810 | "Trainable params: 15,548\n", 811 | "Non-trainable params: 0\n", 812 | "_________________________________________________________________\n" 813 | ] 814 | } 815 | ], 816 | "source": [ 817 | "model.summary()" 818 | ] 819 | }, 820 | { 821 | "cell_type": "code", 822 | "execution_count": 91, 823 | "metadata": {}, 824 | "outputs": [], 825 | "source": [ 826 | "model.compile('adam', 'mse')" 827 | ] 828 | }, 829 | { 830 | "cell_type": "code", 831 | "execution_count": 99, 832 | "metadata": {}, 833 | "outputs": [], 834 | "source": [ 835 | "# Load the xarray version using the concatenated dataset\n", 836 | "ds = xr.open_dataset(f'{DATADIR}sample_SPCAM_concat.nc')\n", 837 | "gen = DataGenerator(ds, shuffle=True)" 838 | ] 839 | }, 840 | { 841 | "cell_type": "code", 842 | "execution_count": 101, 843 | "metadata": {}, 844 | "outputs": [ 845 | { 846 | "name": "stdout", 847 | "output_type": "stream", 848 | "text": [ 849 | "Epoch 1/1\n", 850 | " 37/4608 [..............................] - ETA: 1:04:11 - loss: 1733.6299" 851 | ] 852 | }, 853 | { 854 | "ename": "KeyboardInterrupt", 855 | "evalue": "", 856 | "output_type": "error", 857 | "traceback": [ 858 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 859 | "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", 860 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfit_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0miter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msteps_per_epoch\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mgen\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_batches\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 861 | "\u001b[0;32m~/miniconda3/envs/tf/lib/python3.6/site-packages/tensorflow/python/keras/engine/training.py\u001b[0m in \u001b[0;36mfit_generator\u001b[0;34m(self, generator, steps_per_epoch, epochs, verbose, callbacks, validation_data, validation_steps, class_weight, max_queue_size, workers, use_multiprocessing, shuffle, initial_epoch)\u001b[0m\n\u001b[1;32m 2175\u001b[0m \u001b[0muse_multiprocessing\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0muse_multiprocessing\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2176\u001b[0m \u001b[0mshuffle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mshuffle\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2177\u001b[0;31m initial_epoch=initial_epoch)\n\u001b[0m\u001b[1;32m 2178\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2179\u001b[0m def evaluate_generator(self,\n", 862 | "\u001b[0;32m~/miniconda3/envs/tf/lib/python3.6/site-packages/tensorflow/python/keras/engine/training_generator.py\u001b[0m in \u001b[0;36mfit_generator\u001b[0;34m(model, generator, steps_per_epoch, epochs, verbose, callbacks, validation_data, validation_steps, class_weight, max_queue_size, workers, use_multiprocessing, shuffle, initial_epoch)\u001b[0m\n\u001b[1;32m 145\u001b[0m \u001b[0mbatch_index\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 146\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0msteps_done\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0msteps_per_epoch\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 147\u001b[0;31m \u001b[0mgenerator_output\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0moutput_generator\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 148\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 149\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgenerator_output\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'__len__'\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", 863 | "\u001b[0;32m~/miniconda3/envs/tf/lib/python3.6/site-packages/tensorflow/python/keras/utils/data_utils.py\u001b[0m in \u001b[0;36mget\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 823\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mStopIteration\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 824\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--> 825\u001b[0;31m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait_time\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 826\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 827\u001b[0m \u001b[0;31m# Make sure to rethrow the first exception in the queue, if any\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 864 | "\u001b[0;31mKeyboardInterrupt\u001b[0m: " 865 | ] 866 | } 867 | ], 868 | "source": [ 869 | "model.fit_generator(iter(gen), steps_per_epoch=gen.n_batches)" 870 | ] 871 | }, 872 | { 873 | "cell_type": "markdown", 874 | "metadata": {}, 875 | "source": [ 876 | "So as you can see, it would take around 1 hour to go through one epoch (i.e. the entire dataset once). This is crazy slow since we only used 2 days of data. The full dataset contains a year of data..." 877 | ] 878 | }, 879 | { 880 | "cell_type": "markdown", 881 | "metadata": {}, 882 | "source": [ 883 | "## Pre-processing the dataset\n", 884 | "\n", 885 | "What I have resorted to to solve this issue is to prestack the data, preshuffle the data and save it to disc conveniently.\n", 886 | "\n", 887 | "These files contain the exactly same information for the input (features) and output (targets) variables required.\n", 888 | "\n", 889 | "The files only have two dimensions: sample, which is the shuffled, flattened time, lat and lon dimensions and lev which is the stacked vertical coordinate.\n", 890 | "\n", 891 | "The preprocessing for these two files only takes a few seconds but for an entire year of data, the preprocessing alone can take around an hour.\n" 892 | ] 893 | }, 894 | { 895 | "cell_type": "code", 896 | "execution_count": 163, 897 | "metadata": {}, 898 | "outputs": [ 899 | { 900 | "name": "stdout", 901 | "output_type": "stream", 902 | "text": [ 903 | "--2019-02-07 15:42:32-- https://zenodo.org/record/2559313/files/preproc_features.nc\n", 904 | "Resolving zenodo.org (zenodo.org)... 137.138.76.77\n", 905 | "Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected.\n", 906 | "HTTP request sent, awaiting response... 200 OK\n", 907 | "Length: 205465847 (196M) [application/octet-stream]\n", 908 | "Saving to: ‘/local/S.Rasp/tmp/preproc_features.nc.2’\n", 909 | "\n", 910 | "preproc_features.nc 100%[===================>] 195.95M 10.8MB/s in 15s \n", 911 | "\n", 912 | "2019-02-07 15:42:48 (13.0 MB/s) - ‘/local/S.Rasp/tmp/preproc_features.nc.2’ saved [205465847/205465847]\n", 913 | "\n", 914 | "--2019-02-07 15:42:48-- https://zenodo.org/record/2559313/files/preproc_targets.nc\n", 915 | "Resolving zenodo.org (zenodo.org)... 137.138.76.77\n", 916 | "Connecting to zenodo.org (zenodo.org)|137.138.76.77|:443... connected.\n", 917 | "HTTP request sent, awaiting response... 200 OK\n", 918 | "Length: 205465846 (196M) [application/octet-stream]\n", 919 | "Saving to: ‘/local/S.Rasp/tmp/preproc_targets.nc.1’\n", 920 | "\n", 921 | "preproc_targets.nc. 100%[===================>] 195.95M 9.98MB/s in 9.5s \n", 922 | "\n", 923 | "2019-02-07 15:42:58 (20.6 MB/s) - ‘/local/S.Rasp/tmp/preproc_targets.nc.1’ saved [205465846/205465846]\n", 924 | "\n" 925 | ] 926 | } 927 | ], 928 | "source": [ 929 | "!wget -P $DATADIR https://zenodo.org/record/2559313/files/preproc_features.nc\n", 930 | "!wget -P $DATADIR https://zenodo.org/record/2559313/files/preproc_targets.nc" 931 | ] 932 | }, 933 | { 934 | "cell_type": "code", 935 | "execution_count": 104, 936 | "metadata": {}, 937 | "outputs": [ 938 | { 939 | "name": "stdout", 940 | "output_type": "stream", 941 | "text": [ 942 | "-rw-r--r-- 1 S.Rasp ls-craig 196M Feb 7 13:57 /local/S.Rasp/tmp//preproc_features.nc\r\n", 943 | "-rw-r--r-- 1 S.Rasp ls-craig 196M Feb 7 13:57 /local/S.Rasp/tmp//preproc_targets.nc\r\n" 944 | ] 945 | } 946 | ], 947 | "source": [ 948 | "!ls -lh $DATADIR/preproc*" 949 | ] 950 | }, 951 | { 952 | "cell_type": "code", 953 | "execution_count": 105, 954 | "metadata": {}, 955 | "outputs": [], 956 | "source": [ 957 | "ds = xr.open_dataset(f'{DATADIR}preproc_features.nc')" 958 | ] 959 | }, 960 | { 961 | "cell_type": "code", 962 | "execution_count": 106, 963 | "metadata": {}, 964 | "outputs": [ 965 | { 966 | "data": { 967 | "text/plain": [ 968 | "\n", 969 | "Dimensions: (feature_lev: 60, sample: 778240)\n", 970 | "Coordinates:\n", 971 | " * feature_lev (feature_lev) int64 0 1 2 3 4 5 6 7 ... 53 54 55 56 57 58 59\n", 972 | " time (sample) int64 ...\n", 973 | " lat (sample) float64 ...\n", 974 | " lon (sample) float64 ...\n", 975 | " feature_names (feature_lev) object ...\n", 976 | "Dimensions without coordinates: sample\n", 977 | "Data variables:\n", 978 | " features (sample, feature_lev) float32 ...\n", 979 | "Attributes:\n", 980 | " log: \\n Time: 2019-02-07T13:57:24\\n\\n Executed command:\\n\\n ..." 981 | ] 982 | }, 983 | "execution_count": 106, 984 | "metadata": {}, 985 | "output_type": "execute_result" 986 | } 987 | ], 988 | "source": [ 989 | "ds" 990 | ] 991 | }, 992 | { 993 | "cell_type": "code", 994 | "execution_count": 129, 995 | "metadata": {}, 996 | "outputs": [], 997 | "source": [ 998 | "# Write a new data generator\n", 999 | "class DataGeneratorPreproc(object):\n", 1000 | " \"\"\"\n", 1001 | " Data generator that randomly (if shuffle = True) picks columns from the dataset and returns them in \n", 1002 | " batches. For each column the input variables and output variables will be stacked.\n", 1003 | " \"\"\"\n", 1004 | " def __init__(self, feature_fn, target_fn, batch_size=128, shuffle=True, engine='netcdf4'):\n", 1005 | " self.feature_ds = xr.open_dataset(feature_fn, engine=engine)\n", 1006 | " self.target_ds = xr.open_dataset(target_fn, engine=engine)\n", 1007 | " self.batch_size = batch_size\n", 1008 | " self.ntot = self.feature_ds.sample.size\n", 1009 | " self.n_batches = self.ntot // batch_size\n", 1010 | " self.indices = np.arange(self.ntot)\n", 1011 | " if shuffle:\n", 1012 | " self.indices = np.random.permutation(self.indices)\n", 1013 | " def __getitem__(self, index):\n", 1014 | " batch_indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n", 1015 | " \n", 1016 | " X = self.feature_ds.features.isel(sample=batch_indices)\n", 1017 | " Y = self.target_ds.targets.isel(sample=batch_indices)\n", 1018 | "\n", 1019 | " return X, Y" 1020 | ] 1021 | }, 1022 | { 1023 | "cell_type": "code", 1024 | "execution_count": 130, 1025 | "metadata": {}, 1026 | "outputs": [], 1027 | "source": [ 1028 | "gen = DataGeneratorPreproc(f'{DATADIR}preproc_features.nc', f'{DATADIR}preproc_targets.nc')" 1029 | ] 1030 | }, 1031 | { 1032 | "cell_type": "code", 1033 | "execution_count": 131, 1034 | "metadata": {}, 1035 | "outputs": [], 1036 | "source": [ 1037 | "x, y = gen[0]" 1038 | ] 1039 | }, 1040 | { 1041 | "cell_type": "code", 1042 | "execution_count": 132, 1043 | "metadata": {}, 1044 | "outputs": [ 1045 | { 1046 | "data": { 1047 | "text/plain": [ 1048 | "((128, 60), (128, 60))" 1049 | ] 1050 | }, 1051 | "execution_count": 132, 1052 | "metadata": {}, 1053 | "output_type": "execute_result" 1054 | } 1055 | ], 1056 | "source": [ 1057 | "x.shape, y.shape" 1058 | ] 1059 | }, 1060 | { 1061 | "cell_type": "code", 1062 | "execution_count": 133, 1063 | "metadata": {}, 1064 | "outputs": [ 1065 | { 1066 | "name": "stdout", 1067 | "output_type": "stream", 1068 | "text": [ 1069 | "CPU times: user 84 ms, sys: 0 ns, total: 84 ms\n", 1070 | "Wall time: 81.6 ms\n" 1071 | ] 1072 | } 1073 | ], 1074 | "source": [ 1075 | "%%time\n", 1076 | "test(gen, 10)" 1077 | ] 1078 | }, 1079 | { 1080 | "cell_type": "code", 1081 | "execution_count": 134, 1082 | "metadata": {}, 1083 | "outputs": [], 1084 | "source": [ 1085 | "gen = DataGeneratorPreproc(f'{DATADIR}preproc_features.nc', f'{DATADIR}preproc_targets.nc', shuffle=False)" 1086 | ] 1087 | }, 1088 | { 1089 | "cell_type": "code", 1090 | "execution_count": 135, 1091 | "metadata": {}, 1092 | "outputs": [ 1093 | { 1094 | "name": "stdout", 1095 | "output_type": "stream", 1096 | "text": [ 1097 | "CPU times: user 84 ms, sys: 0 ns, total: 84 ms\n", 1098 | "Wall time: 83.9 ms\n" 1099 | ] 1100 | } 1101 | ], 1102 | "source": [ 1103 | "%%time\n", 1104 | "test(gen, 10)" 1105 | ] 1106 | }, 1107 | { 1108 | "cell_type": "code", 1109 | "execution_count": 152, 1110 | "metadata": {}, 1111 | "outputs": [], 1112 | "source": [ 1113 | "gen.feature_ds.close(); gen.target_ds.close()" 1114 | ] 1115 | }, 1116 | { 1117 | "cell_type": "code", 1118 | "execution_count": 139, 1119 | "metadata": {}, 1120 | "outputs": [], 1121 | "source": [ 1122 | "gen = DataGeneratorPreproc(f'{DATADIR}preproc_features.nc', f'{DATADIR}preproc_targets.nc', engine='h5netcdf')" 1123 | ] 1124 | }, 1125 | { 1126 | "cell_type": "code", 1127 | "execution_count": 140, 1128 | "metadata": {}, 1129 | "outputs": [ 1130 | { 1131 | "name": "stdout", 1132 | "output_type": "stream", 1133 | "text": [ 1134 | "CPU times: user 84 ms, sys: 0 ns, total: 84 ms\n", 1135 | "Wall time: 80.6 ms\n" 1136 | ] 1137 | } 1138 | ], 1139 | "source": [ 1140 | "%%time\n", 1141 | "test(gen, 10)" 1142 | ] 1143 | }, 1144 | { 1145 | "cell_type": "markdown", 1146 | "metadata": {}, 1147 | "source": [ 1148 | "So these are the sort of times that are required for training a neural network." 1149 | ] 1150 | }, 1151 | { 1152 | "cell_type": "markdown", 1153 | "metadata": {}, 1154 | "source": [ 1155 | "### Pure h5py version" 1156 | ] 1157 | }, 1158 | { 1159 | "cell_type": "code", 1160 | "execution_count": 158, 1161 | "metadata": {}, 1162 | "outputs": [], 1163 | "source": [ 1164 | "class DataGeneratorPreprocH5(object):\n", 1165 | " \"\"\"\n", 1166 | " Data generator that randomly (if shuffle = True) picks columns from the dataset and returns them in \n", 1167 | " batches. For each column the input variables and output variables will be stacked.\n", 1168 | " \"\"\"\n", 1169 | " def __init__(self, feature_fn, target_fn, batch_size=128):\n", 1170 | " self.feature_ds = xr.open_dataset(feature_fn)\n", 1171 | " self.target_ds = xr.open_dataset(target_fn)\n", 1172 | " self.batch_size = batch_size\n", 1173 | " self.ntot = self.feature_ds.sample.size\n", 1174 | " self.n_batches = self.ntot // batch_size\n", 1175 | " \n", 1176 | " # Close xarray dataset and open h5py object\n", 1177 | " self.feature_ds.close()\n", 1178 | " self.feature_ds = h5py.File(feature_fn, 'r')\n", 1179 | " self.target_ds.close()\n", 1180 | " self.target_ds = h5py.File(target_fn, 'r')\n", 1181 | " \n", 1182 | " def __getitem__(self, index):\n", 1183 | " \n", 1184 | " X = self.feature_ds['features'][index*self.batch_size:(index+1)*self.batch_size, :]\n", 1185 | " Y = self.target_ds['targets'][index*self.batch_size:(index+1)*self.batch_size, :]\n", 1186 | "\n", 1187 | " return X, Y" 1188 | ] 1189 | }, 1190 | { 1191 | "cell_type": "code", 1192 | "execution_count": 159, 1193 | "metadata": {}, 1194 | "outputs": [], 1195 | "source": [ 1196 | "gen.feature_ds.close(); gen.target_ds.close()" 1197 | ] 1198 | }, 1199 | { 1200 | "cell_type": "code", 1201 | "execution_count": 160, 1202 | "metadata": {}, 1203 | "outputs": [], 1204 | "source": [ 1205 | "gen = DataGeneratorPreprocH5(f'{DATADIR}preproc_features.nc', f'{DATADIR}preproc_targets.nc')" 1206 | ] 1207 | }, 1208 | { 1209 | "cell_type": "code", 1210 | "execution_count": 161, 1211 | "metadata": {}, 1212 | "outputs": [ 1213 | { 1214 | "name": "stdout", 1215 | "output_type": "stream", 1216 | "text": [ 1217 | "CPU times: user 8 ms, sys: 0 ns, total: 8 ms\n", 1218 | "Wall time: 6.61 ms\n" 1219 | ] 1220 | } 1221 | ], 1222 | "source": [ 1223 | "%%time\n", 1224 | "test(gen, 10)" 1225 | ] 1226 | }, 1227 | { 1228 | "cell_type": "markdown", 1229 | "metadata": {}, 1230 | "source": [ 1231 | "So again, the pure h5py version is an order of magnitude faster than the xarray version." 1232 | ] 1233 | }, 1234 | { 1235 | "cell_type": "markdown", 1236 | "metadata": {}, 1237 | "source": [ 1238 | "## End" 1239 | ] 1240 | }, 1241 | { 1242 | "cell_type": "code", 1243 | "execution_count": null, 1244 | "metadata": {}, 1245 | "outputs": [], 1246 | "source": [] 1247 | } 1248 | ], 1249 | "metadata": { 1250 | "kernelspec": { 1251 | "display_name": "Python 3", 1252 | "language": "python", 1253 | "name": "python3" 1254 | }, 1255 | "language_info": { 1256 | "codemirror_mode": { 1257 | "name": "ipython", 1258 | "version": 3 1259 | }, 1260 | "file_extension": ".py", 1261 | "mimetype": "text/x-python", 1262 | "name": "python", 1263 | "nbconvert_exporter": "python", 1264 | "pygments_lexer": "ipython3", 1265 | "version": "3.6.5" 1266 | }, 1267 | "toc": { 1268 | "base_numbering": 1, 1269 | "nav_menu": {}, 1270 | "number_sections": true, 1271 | "sideBar": false, 1272 | "skip_h1_title": true, 1273 | "title_cell": "Table of Contents", 1274 | "title_sidebar": "Contents", 1275 | "toc_cell": false, 1276 | "toc_position": {}, 1277 | "toc_section_display": true, 1278 | "toc_window_display": false 1279 | } 1280 | }, 1281 | "nbformat": 4, 1282 | "nbformat_minor": 2 1283 | } 1284 | --------------------------------------------------------------------------------