├── .github └── workflows │ └── semgrep.yml ├── LICENSE ├── README.md ├── notebooks ├── hd_mobilenet_v3_large_100_224 │ ├── saved_model.pb │ └── variables │ │ ├── variables.data-00000-of-00001 │ │ └── variables.index ├── inference.ipynb ├── sample_image.jpg └── training.ipynb └── requirements.txt /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cloudflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Nata or Not Model 2 | 3 | Our model uses a convolutional neural network and [TensorFlow][2] to infer if an image is a Pastel de Nata or not. 4 | 5 | We took the [Mobilenet V3][1] pre-trained network and trained a new layer on top of it with thousands of Portuguese egg custard tart (Pasteis de Nata) images and other non related images to get the "Nata or Not" classifier. The model was trained on a server with an [NVIDIA A100][4] Tensor Core GPU. Check our Jupyter [notebook][5] for details and code, inspired by the "Retraining an Image Classifier" [tutorial][6]. 6 | 7 | #### The dataset 8 | 9 | To get a reasonable training dataset, we simply used as many Creative Commons images as possible from Google Images using the "[pastel de nata][3]" keywords. We also added a few of our own; the Lisbon Office is quite a fan of the famous pastry. 10 | 11 | Finally, we also searched for non-egg tart images, the kind we think would be the most common for people to try out, like other foods, furniture, pets, people, and, of course, hotdogs. 12 | 13 | #### How to build your own 14 | 15 | Get your dataset and use the following folder structure to place the `pastel` and `not-pastel` images. 16 | 17 | ``` 18 | nata-model 19 | │ notebooks 20 | └───data 21 | | pastel 22 | └───not-pastel 23 | ``` 24 | 25 | Setup your python environment using python 3.7.10: 26 | 27 | ``` 28 | python3 -m venv venv 29 | source venv/bin/activate 30 | pip install -r requirements.txt 31 | ``` 32 | 33 | Run the notebook: 34 | 35 | ``` 36 | cd notebooks 37 | jupyter notebook 38 | ``` 39 | 40 | #### How to use ours 41 | 42 | To perform an inference run this notebook [notebooks/inference.ipynb][10]. You can change the [default image][11] in the `fpath` variable. 43 | 44 | #### Demo 45 | 46 | We did a little [live demo][7] using this TensorFlow model using Cloudflare Workers and a Cloudflare server which has an [NVIDIA A100][9] Tensor Core GPU in it. 47 | 48 | Check our [blog post][8] for more information. 49 | 50 | [1]: https://www.tensorflow.org/api_docs/python/tf/keras/applications/mobilenet_v3 51 | [2]: https://www.tensorflow.org/ 52 | [3]: https://www.google.com/search?as_st=y&tbm=isch&hl=en&as_q=pastel+de+nata&tbs=sur%3Acl 53 | [4]: https://www.nvidia.com/en-us/data-center/a100/ 54 | [5]: notebooks/training.ipynb 55 | [6]: https://www.tensorflow.org/hub/tutorials/tf2_image_retraining 56 | [7]: https://nataornot.com/ 57 | [8]: https://blog.cloudflare.com/workers-ai/ 58 | [9]: https://www.nvidia.com/en-us/data-center/a100/ 59 | [10]: notebooks/inference.ipynb 60 | [11]: https://commons.wikimedia.org/wiki/File:Past%C3%A9is_de_Nata_(49066414092).jpg 61 | -------------------------------------------------------------------------------- /notebooks/hd_mobilenet_v3_large_100_224/saved_model.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/tensorflow-nata/c8bf28157a918caf800ccee9ffc8c07b20ff5896/notebooks/hd_mobilenet_v3_large_100_224/saved_model.pb -------------------------------------------------------------------------------- /notebooks/hd_mobilenet_v3_large_100_224/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/tensorflow-nata/c8bf28157a918caf800ccee9ffc8c07b20ff5896/notebooks/hd_mobilenet_v3_large_100_224/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /notebooks/hd_mobilenet_v3_large_100_224/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/tensorflow-nata/c8bf28157a918caf800ccee9ffc8c07b20ff5896/notebooks/hd_mobilenet_v3_large_100_224/variables/variables.index -------------------------------------------------------------------------------- /notebooks/inference.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "055804d1", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import tensorflow as tf\n", 11 | "import numpy as np" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "2bb25cde", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "fpath = './sample_image.jpg'\n", 22 | "image_size = (224, 224)\n", 23 | "model_loaded = tf.keras.models.load_model('./hd_mobilenet_v3_large_100_224')\n", 24 | "\n", 25 | "img = tf.keras.preprocessing.image.load_img(\n", 26 | " fpath,\n", 27 | " grayscale=False,\n", 28 | " color_mode='rgb',\n", 29 | " target_size=image_size,\n", 30 | " interpolation='bilinear')\n", 31 | "\n", 32 | "input_array = tf.keras.preprocessing.image.img_to_array(img)\n", 33 | "prediction = model_loaded.predict(np.expand_dims(input_array, axis = 0)/255)\n", 34 | "\n", 35 | "print('Is pastel:', bool(np.argmax(prediction)))" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "id": "0579b028", 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [] 45 | } 46 | ], 47 | "metadata": { 48 | "kernelspec": { 49 | "display_name": "Python 3", 50 | "language": "python", 51 | "name": "python3" 52 | }, 53 | "language_info": { 54 | "codemirror_mode": { 55 | "name": "ipython", 56 | "version": 3 57 | }, 58 | "file_extension": ".py", 59 | "mimetype": "text/x-python", 60 | "name": "python", 61 | "nbconvert_exporter": "python", 62 | "pygments_lexer": "ipython3", 63 | "version": "3.7.10" 64 | } 65 | }, 66 | "nbformat": 4, 67 | "nbformat_minor": 5 68 | } 69 | -------------------------------------------------------------------------------- /notebooks/sample_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/tensorflow-nata/c8bf28157a918caf800ccee9ffc8c07b20ff5896/notebooks/sample_image.jpg -------------------------------------------------------------------------------- /notebooks/training.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "c9c03e17", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import pandas as pd\n", 12 | "\n", 13 | "import matplotlib.pylab as plt\n", 14 | "\n", 15 | "import tensorflow as tf\n", 16 | "import tensorflow_hub as hub\n", 17 | "\n", 18 | "print(\"TF version:\", tf.__version__)\n", 19 | "print(\"Hub version:\", hub.__version__)\n", 20 | "print(\"GPU is\", \"available\" if tf.test.is_gpu_available() else \"NOT AVAILABLE\")" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "602df745", 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "''' In `data` we created two different folders - one for each class label\n", 31 | "'''\n", 32 | "data_dir_train = '../data'" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "id": "066f10b5", 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "do_data_augmentation = True\n", 43 | "do_fine_tuning = False\n", 44 | "epochs = 5\n", 45 | "BATCH_SIZE = 32\n", 46 | "validation_split = 0.2\n", 47 | "\n", 48 | "# select a model from the list below (efficientnet_b0, efficientnet_b1, ...)\n", 49 | "model_name = \"mobilenet_v3_large_100_224\"" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "id": "6cdcc72d", 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "model_handle_map = {\n", 60 | " \"efficientnet_b0\": \"https://tfhub.dev/tensorflow/efficientnet/b0/feature-vector/1\",\n", 61 | " \"efficientnet_b1\": \"https://tfhub.dev/tensorflow/efficientnet/b1/feature-vector/1\",\n", 62 | " \"efficientnet_b2\": \"https://tfhub.dev/tensorflow/efficientnet/b2/feature-vector/1\",\n", 63 | " \"efficientnet_b3\": \"https://tfhub.dev/tensorflow/efficientnet/b3/feature-vector/1\",\n", 64 | " \"efficientnet_b4\": \"https://tfhub.dev/tensorflow/efficientnet/b4/feature-vector/1\",\n", 65 | " \"efficientnet_b5\": \"https://tfhub.dev/tensorflow/efficientnet/b5/feature-vector/1\",\n", 66 | " \"efficientnet_b6\": \"https://tfhub.dev/tensorflow/efficientnet/b6/feature-vector/1\",\n", 67 | " \"efficientnet_b7\": \"https://tfhub.dev/tensorflow/efficientnet/b7/feature-vector/1\",\n", 68 | " \"bit_s-r50x1\": \"https://tfhub.dev/google/bit/s-r50x1/1\",\n", 69 | " \"inception_v3\": \"https://tfhub.dev/google/imagenet/inception_v3/feature-vector/4\",\n", 70 | " \"inception_resnet_v2\": \"https://tfhub.dev/google/imagenet/inception_resnet_v2/feature-vector/4\",\n", 71 | " \"resnet_v1_50\": \"https://tfhub.dev/google/imagenet/resnet_v1_50/feature-vector/4\",\n", 72 | " \"resnet_v1_101\": \"https://tfhub.dev/google/imagenet/resnet_v1_101/feature-vector/4\",\n", 73 | " \"resnet_v1_152\": \"https://tfhub.dev/google/imagenet/resnet_v1_152/feature-vector/4\",\n", 74 | " \"resnet_v2_50\": \"https://tfhub.dev/google/imagenet/resnet_v2_50/feature-vector/4\",\n", 75 | " \"resnet_v2_101\": \"https://tfhub.dev/google/imagenet/resnet_v2_101/feature-vector/4\",\n", 76 | " \"resnet_v2_152\": \"https://tfhub.dev/google/imagenet/resnet_v2_152/feature-vector/4\",\n", 77 | " \"nasnet_large\": \"https://tfhub.dev/google/imagenet/nasnet_large/feature_vector/4\",\n", 78 | " \"nasnet_mobile\": \"https://tfhub.dev/google/imagenet/nasnet_mobile/feature_vector/4\",\n", 79 | " \"pnasnet_large\": \"https://tfhub.dev/google/imagenet/pnasnet_large/feature_vector/4\",\n", 80 | " \"mobilenet_v2_100_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/feature_vector/4\",\n", 81 | " \"mobilenet_v2_130_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v2_130_224/feature_vector/4\",\n", 82 | " \"mobilenet_v2_140_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v2_140_224/feature_vector/4\",\n", 83 | " \"mobilenet_v3_small_100_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v3_small_100_224/feature_vector/5\",\n", 84 | " \"mobilenet_v3_small_075_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v3_small_075_224/feature_vector/5\",\n", 85 | " \"mobilenet_v3_large_100_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v3_large_100_224/feature_vector/5\",\n", 86 | " \"mobilenet_v3_large_075_224\": \"https://tfhub.dev/google/imagenet/mobilenet_v3_large_075_224/feature_vector/5\",\n", 87 | "}\n", 88 | "\n", 89 | "model_image_size_map = {\n", 90 | " \"efficientnet_b0\": 224,\n", 91 | " \"efficientnet_b1\": 240,\n", 92 | " \"efficientnet_b2\": 260,\n", 93 | " \"efficientnet_b3\": 300,\n", 94 | " \"efficientnet_b4\": 380,\n", 95 | " \"efficientnet_b5\": 456,\n", 96 | " \"efficientnet_b6\": 528,\n", 97 | " \"efficientnet_b7\": 600,\n", 98 | " \"inception_v3\": 299,\n", 99 | " \"inception_resnet_v2\": 299,\n", 100 | " \"nasnet_large\": 331,\n", 101 | " \"pnasnet_large\": 331,\n", 102 | "}\n", 103 | "\n", 104 | "model_handle = model_handle_map.get(model_name)\n", 105 | "pixels = model_image_size_map.get(model_name, 224)\n", 106 | "IMAGE_SIZE = (pixels, pixels)\n", 107 | "\n", 108 | "print(f\"Selected model: {model_name} : {model_handle}\")\n", 109 | "print(f\"Input size {IMAGE_SIZE}\")" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "id": "4fbc7a14", 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "datagen_kwargs = dict(rescale=1./255, validation_split=validation_split)\n", 120 | "dataflow_kwargs = dict(target_size=IMAGE_SIZE, \n", 121 | " batch_size=BATCH_SIZE,\n", 122 | " interpolation=\"bilinear\")" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "id": "8a8cc3f9", 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator(\n", 133 | " **datagen_kwargs)\n", 134 | "\n", 135 | "print(\"number of validation images\")\n", 136 | "valid_generator = valid_datagen.flow_from_directory(\n", 137 | " data_dir_train, \n", 138 | " subset=\"validation\", \n", 139 | " shuffle=False,\n", 140 | " **dataflow_kwargs)\n", 141 | "\n", 142 | "if do_data_augmentation:\n", 143 | " train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(\n", 144 | " rotation_range=40,\n", 145 | " horizontal_flip=True,\n", 146 | " vertical_flip=True,\n", 147 | " width_shift_range=0.2, \n", 148 | " height_shift_range=0.2,\n", 149 | " shear_range=0.2, \n", 150 | " zoom_range=0.2,\n", 151 | " **datagen_kwargs)\n", 152 | "else:\n", 153 | " train_datagen = valid_datagen\n", 154 | " \n", 155 | "print(\"number of training images\")\n", 156 | "train_generator = train_datagen.flow_from_directory(\n", 157 | " data_dir_train, \n", 158 | " subset=\"training\", \n", 159 | " shuffle=True,\n", 160 | " seed=42,\n", 161 | " **dataflow_kwargs)" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "id": "12a17643", 168 | "metadata": {}, 169 | "outputs": [], 170 | "source": [ 171 | "print(\"training class 1 %:\", train_generator.labels.sum()/len(train_generator.labels))\n", 172 | "print(\"validation class 1 %:\", valid_generator.labels.sum()/len(valid_generator.labels))" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "ae8fc53e", 178 | "metadata": {}, 179 | "source": [ 180 | "### Defining the model" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "c286aa82", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "print(\"Building model with\", model_handle)\n", 191 | "model = tf.keras.Sequential([\n", 192 | " tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)),\n", 193 | " hub.KerasLayer(model_handle, trainable=do_fine_tuning),\n", 194 | " tf.keras.layers.Dropout(rate=0.2),\n", 195 | " tf.keras.layers.Dense(train_generator.num_classes,\n", 196 | " kernel_regularizer=tf.keras.regularizers.l2(0.0001)),\n", 197 | " tf.keras.layers.Softmax()\n", 198 | "])\n", 199 | "\n", 200 | "model.build((None,)+IMAGE_SIZE+(3,))\n", 201 | "model.summary()" 202 | ] 203 | }, 204 | { 205 | "cell_type": "markdown", 206 | "id": "ebdae895", 207 | "metadata": {}, 208 | "source": [ 209 | "### Training model" 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "id": "0e0ec54c", 216 | "metadata": {}, 217 | "outputs": [], 218 | "source": [ 219 | "model.compile(\n", 220 | " optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9), \n", 221 | " loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True, label_smoothing=0.1),\n", 222 | " metrics=['accuracy'])" 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": null, 228 | "id": "ca8d354b", 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "steps_per_epoch = train_generator.samples // train_generator.batch_size\n", 233 | "validation_steps = valid_generator.samples // valid_generator.batch_size\n", 234 | "hist = model.fit(\n", 235 | " train_generator,\n", 236 | " epochs=epochs, \n", 237 | " steps_per_epoch=steps_per_epoch,\n", 238 | " validation_data=valid_generator,\n", 239 | " validation_steps=validation_steps).history" 240 | ] 241 | }, 242 | { 243 | "cell_type": "code", 244 | "execution_count": null, 245 | "id": "a3adad0d", 246 | "metadata": {}, 247 | "outputs": [], 248 | "source": [ 249 | "plt.figure()\n", 250 | "plt.ylabel(\"Loss (training and validation)\")\n", 251 | "plt.xlabel(\"Training Steps\")\n", 252 | "plt.ylim([0,2])\n", 253 | "plt.plot(hist[\"loss\"])\n", 254 | "plt.plot(hist[\"val_loss\"])\n", 255 | "\n", 256 | "plt.figure()\n", 257 | "plt.ylabel(\"Accuracy (training and validation)\")\n", 258 | "plt.xlabel(\"Training Steps\")\n", 259 | "plt.ylim([0,1])\n", 260 | "plt.plot(hist[\"accuracy\"])\n", 261 | "plt.plot(hist[\"val_accuracy\"])" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": null, 267 | "id": "e92b91e7", 268 | "metadata": {}, 269 | "outputs": [], 270 | "source": [ 271 | "saved_model_path = f\"./hd_{model_name}\"\n", 272 | "tf.saved_model.save(model, saved_model_path)" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "id": "073182bd", 279 | "metadata": {}, 280 | "outputs": [], 281 | "source": [] 282 | } 283 | ], 284 | "metadata": { 285 | "kernelspec": { 286 | "display_name": "Python 3", 287 | "language": "python", 288 | "name": "python3" 289 | }, 290 | "language_info": { 291 | "codemirror_mode": { 292 | "name": "ipython", 293 | "version": 3 294 | }, 295 | "file_extension": ".py", 296 | "mimetype": "text/x-python", 297 | "name": "python", 298 | "nbconvert_exporter": "python", 299 | "pygments_lexer": "ipython3", 300 | "version": "3.7.10" 301 | } 302 | }, 303 | "nbformat": 4, 304 | "nbformat_minor": 5 305 | } 306 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==0.12.0 2 | appnope==0.1.2 3 | argon2-cffi==20.1.0 4 | astunparse==1.6.3 5 | async-generator==1.10 6 | attrs==20.3.0 7 | backcall==0.2.0 8 | bleach==3.3.0 9 | cachetools==4.2.1 10 | certifi==2020.12.5 11 | cffi==1.14.5 12 | chardet==4.0.0 13 | cycler==0.10.0 14 | decorator==5.0.6 15 | defusedxml==0.7.1 16 | entrypoints==0.3 17 | flatbuffers==1.12 18 | gast==0.3.3 19 | google-auth==1.28.1 20 | google-auth-oauthlib==0.4.4 21 | google-pasta==0.2.0 22 | grpcio==1.32.0 23 | h5py==2.10.0 24 | idna==2.10 25 | importlib-metadata==3.10.1 26 | ipykernel==5.5.3 27 | ipython==7.22.0 28 | ipython-genutils==0.2.0 29 | ipywidgets==7.6.3 30 | jedi==0.18.0 31 | Jinja2==2.11.3 32 | jsonschema==3.2.0 33 | jupyter==1.0.0 34 | jupyter-client==6.1.12 35 | jupyter-console==6.4.0 36 | jupyter-core==4.7.1 37 | jupyterlab-pygments==0.1.2 38 | jupyterlab-widgets==1.0.0 39 | Keras-Preprocessing==1.1.2 40 | kiwisolver==1.3.1 41 | Markdown==3.3.4 42 | MarkupSafe==1.1.1 43 | matplotlib==3.4.1 44 | mistune==0.8.4 45 | nbclient==0.5.3 46 | nbconvert==6.0.7 47 | nbformat==5.1.3 48 | nest-asyncio==1.5.1 49 | notebook==6.3.0 50 | numpy==1.19.5 51 | oauthlib==3.1.0 52 | opt-einsum==3.3.0 53 | packaging==20.9 54 | pandas==1.2.4 55 | pandocfilters==1.4.3 56 | parso==0.8.2 57 | pexpect==4.8.0 58 | pickleshare==0.7.5 59 | Pillow==8.2.0 60 | prometheus-client==0.10.1 61 | prompt-toolkit==3.0.18 62 | protobuf==3.15.8 63 | ptyprocess==0.7.0 64 | pyasn1==0.4.8 65 | pyasn1-modules==0.2.8 66 | pycparser==2.20 67 | Pygments==2.8.1 68 | pyparsing==2.4.7 69 | pyrsistent==0.17.3 70 | python-dateutil==2.8.1 71 | pytz==2021.1 72 | pyzmq==22.0.3 73 | qtconsole==5.0.3 74 | QtPy==1.9.0 75 | requests==2.25.1 76 | requests-oauthlib==1.3.0 77 | rsa==4.7.2 78 | scipy==1.6.2 79 | Send2Trash==1.5.0 80 | six==1.15.0 81 | tensorboard==2.4.1 82 | tensorboard-plugin-wit==1.8.0 83 | tensorflow==2.4.1 84 | tensorflow-estimator==2.4.0 85 | tensorflow-hub==0.11.0 86 | termcolor==1.1.0 87 | terminado==0.9.4 88 | testpath==0.4.4 89 | tornado==6.1 90 | traitlets==5.0.5 91 | typing-extensions==3.7.4.3 92 | urllib3==1.26.4 93 | wcwidth==0.2.5 94 | webencodings==0.5.1 95 | Werkzeug==1.0.1 96 | widgetsnbextension==3.5.1 97 | wrapt==1.12.1 98 | zipp==3.4.1 99 | --------------------------------------------------------------------------------