├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── keras-wide-deep.ipynb
└── predictions.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea/
3 | .ipynb_checkpoints/
4 | *.csv
5 | .vscode
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows [Google's Open Source Community
28 | Guidelines](https://opensource.google.com/conduct/).
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **This is not an official Google product.**
2 |
3 | Building a wide & deep model with the Keras Functional API
4 | =======================
5 |
6 | This model takes a wine's description and variety (Pinot Noir, Chardonnay, etc.) as input and predicts the price of the wine. It's built with [tf.keras](https://www.tensorflow.org/api_docs/python/tf/keras) using the [Functional Model API](https://www.tensorflow.org/api_docs/python/tf/keras/Model). Here's an example input and prediction:
7 |
8 | #### Inputs
9 |
10 | * Description: This tremendous 100% varietal wine hails from Oakville and was aged over three years in oak. Juicy red-cherry fruit and a compelling hint of caramel greet the palate, framed by elegant, fine tannins and a subtle minty tone in the background. Balanced and rewarding from start to finish, it has years ahead of it to develop further nuance. Enjoy 2022–2030
11 |
12 | * Variety: Cabernet Sauvignon
13 |
14 | #### Prediction
15 |
16 | * Price - $235
17 |
18 | ## Training the model
19 |
20 | You can run the model [live in Colab with zero setup here](https://colab.research.google.com/github/sararob/keras-wine-model/blob/master/keras-wide-deep.ipynb).
21 |
22 | To run it locally, make sure you have TensorFlow, Pandas, and Jupyter installed.
23 |
24 | I've included the model code as a Jupyter notebook (`keras-wide-deep.ipynb`). From the root directory run `jupyter notebook` to start your notebook. Then navigate to `localhost:8888` and click on `keras-wide-deep.ipynb`.
25 |
26 | The data used for training this model is from [Kaggle](https://www.kaggle.com/zynicide/wine-reviews/data). A CSV of the data is available [here](https://www.kaggle.com/zynicide/wine-reviews/downloads/winemag-data_first150k.csv/4).
--------------------------------------------------------------------------------
/keras-wide-deep.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "To run this model directly in the browser with zero setup, open it in [Colab here](https://colab.research.google.com/github/sararob/keras-wine-model/blob/master/keras-wide-deep.ipynb)."
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 1,
13 | "metadata": {
14 | "colab": {
15 | "autoexec": {
16 | "startup": false,
17 | "wait_interval": 0.0
18 | }
19 | },
20 | "colab_type": "code",
21 | "id": "frTMl3sShA3P"
22 | },
23 | "outputs": [],
24 | "source": [
25 | "from __future__ import absolute_import\n",
26 | "from __future__ import division\n",
27 | "from __future__ import print_function"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": 3,
33 | "metadata": {},
34 | "outputs": [],
35 | "source": [
36 | "# Install the latest version of TensorFlow\n",
37 | "!pip install -q -U tensorflow==1.7.0"
38 | ]
39 | },
40 | {
41 | "cell_type": "code",
42 | "execution_count": 5,
43 | "metadata": {
44 | "colab": {
45 | "autoexec": {
46 | "startup": false,
47 | "wait_interval": 0.0
48 | },
49 | "height": 321.0,
50 | "output_extras": [
51 | {
52 | "item_id": 1.0
53 | }
54 | ]
55 | },
56 | "colab_type": "code",
57 | "executionInfo": {
58 | "elapsed": 2880.0,
59 | "status": "error",
60 | "timestamp": 1.505781339378E12,
61 | "user": {
62 | "displayName": "Sara Robinson",
63 | "photoUrl": "//lh4.googleusercontent.com/-RR9n0dvbwgI/AAAAAAAAAAI/AAAAAAAAMYM/SOr5ZExpvXE/s50-c-k-no/photo.jpg",
64 | "userId": "112510032804989247452"
65 | },
66 | "user_tz": 240.0
67 | },
68 | "id": "783h64rGhA3T",
69 | "outputId": "d447b2ab-e321-4ee5-abd4-de2c0116302f"
70 | },
71 | "outputs": [
72 | {
73 | "name": "stdout",
74 | "output_type": "stream",
75 | "text": [
76 | "You have TensorFlow version 1.7.0\n"
77 | ]
78 | }
79 | ],
80 | "source": [
81 | "import itertools\n",
82 | "import os\n",
83 | "import math\n",
84 | "import numpy as np\n",
85 | "import pandas as pd\n",
86 | "import tensorflow as tf\n",
87 | "\n",
88 | "from sklearn.preprocessing import LabelEncoder\n",
89 | "from tensorflow import keras\n",
90 | "layers = keras.layers\n",
91 | "\n",
92 | "# This code was tested with TensorFlow v1.7\n",
93 | "print(\"You have TensorFlow version\", tf.__version__)"
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "metadata": {},
100 | "outputs": [],
101 | "source": [
102 | "# Get the data: original source is here: https://www.kaggle.com/zynicide/wine-reviews/data\n",
103 | "URL = \"https://storage.googleapis.com/sara-cloud-ml/wine_data.csv\"\n",
104 | "path = tf.keras.utils.get_file(URL.split('/')[-1], URL)\n"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 3,
110 | "metadata": {
111 | "colab": {
112 | "autoexec": {
113 | "startup": false,
114 | "wait_interval": 0.0
115 | }
116 | },
117 | "colab_type": "code",
118 | "id": "c7te21f7hA3V"
119 | },
120 | "outputs": [],
121 | "source": [
122 | "# Convert the data to a Pandas data frame\n",
123 | "data = pd.read_csv(path)"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": 4,
129 | "metadata": {
130 | "colab": {
131 | "autoexec": {
132 | "startup": false,
133 | "wait_interval": 0.0
134 | },
135 | "output_extras": [
136 | {}
137 | ]
138 | },
139 | "colab_type": "code",
140 | "id": "chAbp3ryhA3X",
141 | "outputId": "3a16921d-a3be-4c43-a5e2-6012220ee13a"
142 | },
143 | "outputs": [
144 | {
145 | "data": {
146 | "text/html": [
147 | "
\n",
148 | "\n",
161 | "
\n",
162 | " \n",
163 | " \n",
164 | " | \n",
165 | " Unnamed: 0 | \n",
166 | " country | \n",
167 | " description | \n",
168 | " designation | \n",
169 | " points | \n",
170 | " price | \n",
171 | " province | \n",
172 | " region_1 | \n",
173 | " region_2 | \n",
174 | " variety | \n",
175 | " winery | \n",
176 | "
\n",
177 | " \n",
178 | " \n",
179 | " \n",
180 | " 90233 | \n",
181 | " 90233 | \n",
182 | " France | \n",
183 | " This organic Chardonnay offers notes of soft p... | \n",
184 | " O Made From Organic Grapes | \n",
185 | " 85 | \n",
186 | " 18.0 | \n",
187 | " Languedoc-Roussillon | \n",
188 | " Pays d'Oc | \n",
189 | " NaN | \n",
190 | " Chardonnay | \n",
191 | " Gilles Louvet | \n",
192 | "
\n",
193 | " \n",
194 | " 75984 | \n",
195 | " 75984 | \n",
196 | " US | \n",
197 | " Shows This wine boasts Petite Sirah's full-bod... | \n",
198 | " NaN | \n",
199 | " 89 | \n",
200 | " 32.0 | \n",
201 | " California | \n",
202 | " Paso Robles | \n",
203 | " Central Coast | \n",
204 | " Petite Sirah | \n",
205 | " Proulx | \n",
206 | "
\n",
207 | " \n",
208 | " 8459 | \n",
209 | " 8459 | \n",
210 | " France | \n",
211 | " Faiveley owns two acres of this large Grand Cr... | \n",
212 | " NaN | \n",
213 | " 93 | \n",
214 | " 226.0 | \n",
215 | " Burgundy | \n",
216 | " Echézeaux | \n",
217 | " NaN | \n",
218 | " Pinot Noir | \n",
219 | " Domaine Faiveley | \n",
220 | "
\n",
221 | " \n",
222 | " 34267 | \n",
223 | " 34267 | \n",
224 | " Italy | \n",
225 | " This oak-driven Aglianico opens with aromas of... | \n",
226 | " Jumara | \n",
227 | " 86 | \n",
228 | " 25.0 | \n",
229 | " Southern Italy | \n",
230 | " Irpinia | \n",
231 | " NaN | \n",
232 | " Aglianico | \n",
233 | " I Capitani | \n",
234 | "
\n",
235 | " \n",
236 | " 97811 | \n",
237 | " 97811 | \n",
238 | " New Zealand | \n",
239 | " A lot has changed at Cloudy Bay, but the wine ... | \n",
240 | " NaN | \n",
241 | " 89 | \n",
242 | " 25.0 | \n",
243 | " Marlborough | \n",
244 | " NaN | \n",
245 | " NaN | \n",
246 | " Sauvignon Blanc | \n",
247 | " Cloudy Bay | \n",
248 | "
\n",
249 | " \n",
250 | "
\n",
251 | "
"
252 | ],
253 | "text/plain": [
254 | " Unnamed: 0 country \\\n",
255 | "90233 90233 France \n",
256 | "75984 75984 US \n",
257 | "8459 8459 France \n",
258 | "34267 34267 Italy \n",
259 | "97811 97811 New Zealand \n",
260 | "\n",
261 | " description \\\n",
262 | "90233 This organic Chardonnay offers notes of soft p... \n",
263 | "75984 Shows This wine boasts Petite Sirah's full-bod... \n",
264 | "8459 Faiveley owns two acres of this large Grand Cr... \n",
265 | "34267 This oak-driven Aglianico opens with aromas of... \n",
266 | "97811 A lot has changed at Cloudy Bay, but the wine ... \n",
267 | "\n",
268 | " designation points price province \\\n",
269 | "90233 O Made From Organic Grapes 85 18.0 Languedoc-Roussillon \n",
270 | "75984 NaN 89 32.0 California \n",
271 | "8459 NaN 93 226.0 Burgundy \n",
272 | "34267 Jumara 86 25.0 Southern Italy \n",
273 | "97811 NaN 89 25.0 Marlborough \n",
274 | "\n",
275 | " region_1 region_2 variety winery \n",
276 | "90233 Pays d'Oc NaN Chardonnay Gilles Louvet \n",
277 | "75984 Paso Robles Central Coast Petite Sirah Proulx \n",
278 | "8459 Echézeaux NaN Pinot Noir Domaine Faiveley \n",
279 | "34267 Irpinia NaN Aglianico I Capitani \n",
280 | "97811 NaN NaN Sauvignon Blanc Cloudy Bay "
281 | ]
282 | },
283 | "execution_count": 4,
284 | "metadata": {},
285 | "output_type": "execute_result"
286 | }
287 | ],
288 | "source": [
289 | "# Shuffle the data\n",
290 | "data = data.sample(frac=1)\n",
291 | "\n",
292 | "# Print the first 5 rows\n",
293 | "data.head()"
294 | ]
295 | },
296 | {
297 | "cell_type": "code",
298 | "execution_count": 5,
299 | "metadata": {},
300 | "outputs": [],
301 | "source": [
302 | "# Do some preprocessing to limit the # of wine varities in the dataset\n",
303 | "data = data[pd.notnull(data['country'])]\n",
304 | "data = data[pd.notnull(data['price'])]\n",
305 | "data = data.drop(data.columns[0], axis=1) \n",
306 | "\n",
307 | "variety_threshold = 500 # Anything that occurs less than this will be removed.\n",
308 | "value_counts = data['variety'].value_counts()\n",
309 | "to_remove = value_counts[value_counts <= variety_threshold].index\n",
310 | "data.replace(to_remove, np.nan, inplace=True)\n",
311 | "data = data[pd.notnull(data['variety'])]"
312 | ]
313 | },
314 | {
315 | "cell_type": "code",
316 | "execution_count": 6,
317 | "metadata": {
318 | "colab": {
319 | "autoexec": {
320 | "startup": false,
321 | "wait_interval": 0.0
322 | },
323 | "output_extras": [
324 | {}
325 | ]
326 | },
327 | "colab_type": "code",
328 | "id": "h_SDal0khA3n",
329 | "outputId": "e6c311e5-c674-4cf2-f2dc-d6ceabfa6f83",
330 | "scrolled": true
331 | },
332 | "outputs": [
333 | {
334 | "name": "stdout",
335 | "output_type": "stream",
336 | "text": [
337 | "Train size: 95646\n",
338 | "Test size: 23912\n"
339 | ]
340 | }
341 | ],
342 | "source": [
343 | "# Split data into train and test\n",
344 | "train_size = int(len(data) * .8)\n",
345 | "print (\"Train size: %d\" % train_size)\n",
346 | "print (\"Test size: %d\" % (len(data) - train_size))"
347 | ]
348 | },
349 | {
350 | "cell_type": "code",
351 | "execution_count": 8,
352 | "metadata": {
353 | "colab": {
354 | "autoexec": {
355 | "startup": false,
356 | "wait_interval": 0.0
357 | }
358 | },
359 | "colab_type": "code",
360 | "id": "anD38iilhA3r"
361 | },
362 | "outputs": [],
363 | "source": [
364 | "# Train features\n",
365 | "description_train = data['description'][:train_size]\n",
366 | "variety_train = data['variety'][:train_size]\n",
367 | "\n",
368 | "# Train labels\n",
369 | "labels_train = data['price'][:train_size]\n",
370 | "\n",
371 | "# Test features\n",
372 | "description_test = data['description'][train_size:]\n",
373 | "variety_test = data['variety'][train_size:]\n",
374 | "\n",
375 | "# Test labels\n",
376 | "labels_test = data['price'][train_size:]"
377 | ]
378 | },
379 | {
380 | "cell_type": "code",
381 | "execution_count": 9,
382 | "metadata": {
383 | "colab": {
384 | "autoexec": {
385 | "startup": false,
386 | "wait_interval": 0.0
387 | }
388 | },
389 | "colab_type": "code",
390 | "id": "z4GblctFhA3u"
391 | },
392 | "outputs": [],
393 | "source": [
394 | "# Create a tokenizer to preprocess our text descriptions\n",
395 | "vocab_size = 12000 # This is a hyperparameter, experiment with different values for your dataset\n",
396 | "tokenize = keras.preprocessing.text.Tokenizer(num_words=vocab_size, char_level=False)\n",
397 | "tokenize.fit_on_texts(description_train) # only fit on train"
398 | ]
399 | },
400 | {
401 | "cell_type": "code",
402 | "execution_count": 10,
403 | "metadata": {
404 | "colab": {
405 | "autoexec": {
406 | "startup": false,
407 | "wait_interval": 0.0
408 | }
409 | },
410 | "colab_type": "code",
411 | "id": "YatMLCKXhA3x"
412 | },
413 | "outputs": [],
414 | "source": [
415 | "# Wide feature 1: sparse bag of words (bow) vocab_size vector \n",
416 | "description_bow_train = tokenize.texts_to_matrix(description_train)\n",
417 | "description_bow_test = tokenize.texts_to_matrix(description_test)"
418 | ]
419 | },
420 | {
421 | "cell_type": "code",
422 | "execution_count": 11,
423 | "metadata": {
424 | "colab": {
425 | "autoexec": {
426 | "startup": false,
427 | "wait_interval": 0.0
428 | }
429 | },
430 | "colab_type": "code",
431 | "id": "8quTsErLhA3z"
432 | },
433 | "outputs": [],
434 | "source": [
435 | "# Wide feature 2: one-hot vector of variety categories\n",
436 | "\n",
437 | "# Use sklearn utility to convert label strings to numbered index\n",
438 | "encoder = LabelEncoder()\n",
439 | "encoder.fit(variety_train)\n",
440 | "variety_train = encoder.transform(variety_train)\n",
441 | "variety_test = encoder.transform(variety_test)\n",
442 | "num_classes = np.max(variety_train) + 1\n",
443 | "\n",
444 | "# Convert labels to one hot\n",
445 | "variety_train = keras.utils.to_categorical(variety_train, num_classes)\n",
446 | "variety_test = keras.utils.to_categorical(variety_test, num_classes)"
447 | ]
448 | },
449 | {
450 | "cell_type": "code",
451 | "execution_count": 12,
452 | "metadata": {},
453 | "outputs": [],
454 | "source": [
455 | "# Define our wide model with the functional API\n",
456 | "bow_inputs = layers.Input(shape=(vocab_size,))\n",
457 | "variety_inputs = layers.Input(shape=(num_classes,))\n",
458 | "merged_layer = layers.concatenate([bow_inputs, variety_inputs])\n",
459 | "merged_layer = layers.Dense(256, activation='relu')(merged_layer)\n",
460 | "predictions = layers.Dense(1)(merged_layer)\n",
461 | "wide_model = keras.Model(inputs=[bow_inputs, variety_inputs], outputs=predictions)"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": 13,
467 | "metadata": {},
468 | "outputs": [
469 | {
470 | "name": "stdout",
471 | "output_type": "stream",
472 | "text": [
473 | "__________________________________________________________________________________________________\n",
474 | "Layer (type) Output Shape Param # Connected to \n",
475 | "==================================================================================================\n",
476 | "input_1 (InputLayer) (None, 12000) 0 \n",
477 | "__________________________________________________________________________________________________\n",
478 | "input_2 (InputLayer) (None, 40) 0 \n",
479 | "__________________________________________________________________________________________________\n",
480 | "concatenate_1 (Concatenate) (None, 12040) 0 input_1[0][0] \n",
481 | " input_2[0][0] \n",
482 | "__________________________________________________________________________________________________\n",
483 | "dense_1 (Dense) (None, 256) 3082496 concatenate_1[0][0] \n",
484 | "__________________________________________________________________________________________________\n",
485 | "dense_2 (Dense) (None, 1) 257 dense_1[0][0] \n",
486 | "==================================================================================================\n",
487 | "Total params: 3,082,753\n",
488 | "Trainable params: 3,082,753\n",
489 | "Non-trainable params: 0\n",
490 | "__________________________________________________________________________________________________\n",
491 | "None\n"
492 | ]
493 | }
494 | ],
495 | "source": [
496 | "wide_model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])\n",
497 | "print(wide_model.summary())"
498 | ]
499 | },
500 | {
501 | "cell_type": "code",
502 | "execution_count": 14,
503 | "metadata": {},
504 | "outputs": [],
505 | "source": [
506 | "# Deep model feature: word embeddings of wine descriptions\n",
507 | "train_embed = tokenize.texts_to_sequences(description_train)\n",
508 | "test_embed = tokenize.texts_to_sequences(description_test)\n",
509 | "\n",
510 | "max_seq_length = 170\n",
511 | "train_embed = keras.preprocessing.sequence.pad_sequences(\n",
512 | " train_embed, maxlen=max_seq_length, padding=\"post\")\n",
513 | "test_embed = keras.preprocessing.sequence.pad_sequences(\n",
514 | " test_embed, maxlen=max_seq_length, padding=\"post\")"
515 | ]
516 | },
517 | {
518 | "cell_type": "code",
519 | "execution_count": 15,
520 | "metadata": {},
521 | "outputs": [
522 | {
523 | "name": "stdout",
524 | "output_type": "stream",
525 | "text": [
526 | "_________________________________________________________________\n",
527 | "Layer (type) Output Shape Param # \n",
528 | "=================================================================\n",
529 | "input_3 (InputLayer) (None, 170) 0 \n",
530 | "_________________________________________________________________\n",
531 | "embedding_1 (Embedding) (None, 170, 8) 96000 \n",
532 | "_________________________________________________________________\n",
533 | "flatten_1 (Flatten) (None, 1360) 0 \n",
534 | "_________________________________________________________________\n",
535 | "dense_3 (Dense) (None, 1) 1361 \n",
536 | "=================================================================\n",
537 | "Total params: 97,361\n",
538 | "Trainable params: 97,361\n",
539 | "Non-trainable params: 0\n",
540 | "_________________________________________________________________\n",
541 | "None\n"
542 | ]
543 | }
544 | ],
545 | "source": [
546 | "# Define our deep model with the Functional API\n",
547 | "deep_inputs = layers.Input(shape=(max_seq_length,))\n",
548 | "embedding = layers.Embedding(vocab_size, 8, input_length=max_seq_length)(deep_inputs)\n",
549 | "embedding = layers.Flatten()(embedding)\n",
550 | "embed_out = layers.Dense(1)(embedding)\n",
551 | "deep_model = keras.Model(inputs=deep_inputs, outputs=embed_out)\n",
552 | "print(deep_model.summary())"
553 | ]
554 | },
555 | {
556 | "cell_type": "code",
557 | "execution_count": 16,
558 | "metadata": {},
559 | "outputs": [],
560 | "source": [
561 | "deep_model.compile(loss='mse',\n",
562 | " optimizer='adam',\n",
563 | " metrics=['accuracy'])"
564 | ]
565 | },
566 | {
567 | "cell_type": "code",
568 | "execution_count": 17,
569 | "metadata": {},
570 | "outputs": [
571 | {
572 | "name": "stdout",
573 | "output_type": "stream",
574 | "text": [
575 | "__________________________________________________________________________________________________\n",
576 | "Layer (type) Output Shape Param # Connected to \n",
577 | "==================================================================================================\n",
578 | "input_1 (InputLayer) (None, 12000) 0 \n",
579 | "__________________________________________________________________________________________________\n",
580 | "input_2 (InputLayer) (None, 40) 0 \n",
581 | "__________________________________________________________________________________________________\n",
582 | "input_3 (InputLayer) (None, 170) 0 \n",
583 | "__________________________________________________________________________________________________\n",
584 | "concatenate_1 (Concatenate) (None, 12040) 0 input_1[0][0] \n",
585 | " input_2[0][0] \n",
586 | "__________________________________________________________________________________________________\n",
587 | "embedding_1 (Embedding) (None, 170, 8) 96000 input_3[0][0] \n",
588 | "__________________________________________________________________________________________________\n",
589 | "dense_1 (Dense) (None, 256) 3082496 concatenate_1[0][0] \n",
590 | "__________________________________________________________________________________________________\n",
591 | "flatten_1 (Flatten) (None, 1360) 0 embedding_1[0][0] \n",
592 | "__________________________________________________________________________________________________\n",
593 | "dense_2 (Dense) (None, 1) 257 dense_1[0][0] \n",
594 | "__________________________________________________________________________________________________\n",
595 | "dense_3 (Dense) (None, 1) 1361 flatten_1[0][0] \n",
596 | "__________________________________________________________________________________________________\n",
597 | "concatenate_2 (Concatenate) (None, 2) 0 dense_2[0][0] \n",
598 | " dense_3[0][0] \n",
599 | "__________________________________________________________________________________________________\n",
600 | "dense_4 (Dense) (None, 1) 3 concatenate_2[0][0] \n",
601 | "==================================================================================================\n",
602 | "Total params: 3,180,117\n",
603 | "Trainable params: 3,180,117\n",
604 | "Non-trainable params: 0\n",
605 | "__________________________________________________________________________________________________\n",
606 | "None\n"
607 | ]
608 | }
609 | ],
610 | "source": [
611 | "# Combine wide and deep into one model\n",
612 | "merged_out = layers.concatenate([wide_model.output, deep_model.output])\n",
613 | "merged_out = layers.Dense(1)(merged_out)\n",
614 | "combined_model = keras.Model(wide_model.input + [deep_model.input], merged_out)\n",
615 | "print(combined_model.summary())\n",
616 | "\n",
617 | "combined_model.compile(loss='mse',\n",
618 | " optimizer='adam',\n",
619 | " metrics=['accuracy'])"
620 | ]
621 | },
622 | {
623 | "cell_type": "code",
624 | "execution_count": 18,
625 | "metadata": {
626 | "colab": {
627 | "autoexec": {
628 | "startup": false,
629 | "wait_interval": 0.0
630 | }
631 | },
632 | "colab_type": "code",
633 | "id": "gtP-hDRZhA31"
634 | },
635 | "outputs": [
636 | {
637 | "name": "stdout",
638 | "output_type": "stream",
639 | "text": [
640 | "Epoch 1/10\n",
641 | "95646/95646 [==============================] - 52s 546us/step - loss: 1165.0368 - acc: 0.0276\n",
642 | "Epoch 2/10\n",
643 | "95646/95646 [==============================] - 57s 593us/step - loss: 915.0286 - acc: 0.0352\n",
644 | "Epoch 3/10\n",
645 | "95646/95646 [==============================] - 58s 608us/step - loss: 785.9011 - acc: 0.0396\n",
646 | "Epoch 4/10\n",
647 | "95646/95646 [==============================] - 56s 590us/step - loss: 668.5673 - acc: 0.0445\n",
648 | "Epoch 5/10\n",
649 | "95646/95646 [==============================] - 55s 576us/step - loss: 550.9641 - acc: 0.0483\n",
650 | "Epoch 6/10\n",
651 | "95646/95646 [==============================] - 55s 571us/step - loss: 440.3762 - acc: 0.0563\n",
652 | "Epoch 7/10\n",
653 | "95646/95646 [==============================] - 57s 594us/step - loss: 342.1391 - acc: 0.0651\n",
654 | "Epoch 8/10\n",
655 | "95646/95646 [==============================] - 59s 615us/step - loss: 260.5558 - acc: 0.0734\n",
656 | "Epoch 9/10\n",
657 | "95646/95646 [==============================] - 55s 579us/step - loss: 199.9219 - acc: 0.0853\n",
658 | "Epoch 10/10\n",
659 | "95646/95646 [==============================] - 57s 601us/step - loss: 152.7983 - acc: 0.0962\n"
660 | ]
661 | },
662 | {
663 | "data": {
664 | "text/plain": [
665 | ""
666 | ]
667 | },
668 | "execution_count": 18,
669 | "metadata": {},
670 | "output_type": "execute_result"
671 | }
672 | ],
673 | "source": [
674 | "# Run training\n",
675 | "combined_model.fit([description_bow_train, variety_train] + [train_embed], labels_train, epochs=10, batch_size=128)"
676 | ]
677 | },
678 | {
679 | "cell_type": "code",
680 | "execution_count": 19,
681 | "metadata": {},
682 | "outputs": [
683 | {
684 | "name": "stdout",
685 | "output_type": "stream",
686 | "text": [
687 | "23912/23912 [==============================] - 6s 252us/step\n"
688 | ]
689 | },
690 | {
691 | "data": {
692 | "text/plain": [
693 | "[583.1852051067129, 0.06461191035036899]"
694 | ]
695 | },
696 | "execution_count": 19,
697 | "metadata": {},
698 | "output_type": "execute_result"
699 | }
700 | ],
701 | "source": [
702 | "combined_model.evaluate([description_bow_test, variety_test] + [test_embed], labels_test, batch_size=128)"
703 | ]
704 | },
705 | {
706 | "cell_type": "code",
707 | "execution_count": 20,
708 | "metadata": {
709 | "colab": {
710 | "autoexec": {
711 | "startup": false,
712 | "wait_interval": 0.0
713 | },
714 | "output_extras": [
715 | {}
716 | ]
717 | },
718 | "colab_type": "code",
719 | "id": "f000lYoxhA4F",
720 | "outputId": "21cd198f-1979-4b40-a2fd-891a1c0248db"
721 | },
722 | "outputs": [],
723 | "source": [
724 | "# Generate predictions\n",
725 | "predictions = combined_model.predict([description_bow_test, variety_test] + [test_embed])"
726 | ]
727 | },
728 | {
729 | "cell_type": "code",
730 | "execution_count": 27,
731 | "metadata": {},
732 | "outputs": [
733 | {
734 | "name": "stdout",
735 | "output_type": "stream",
736 | "text": [
737 | "Not a whole lot to say about the soft, flat nose. It's o.k., but that's really about it. The palate, meanwhile, turns sweet, nearly to the point of candied. And the finish is downright sugary. Decent weight and feel help it along.\n",
738 | "Predicted: 16.776217 Actual: 14.0 \n",
739 | "\n",
740 | "This 70-30 blend of Chardonnay and Trebbiano is driven by a strong mineral vein that imparts dusty notes of slate, vitamin and granite over fuller aromas of exotic fruit and peach. The wine is simple and tart in the mouth with a creamy, soft feel overall.\n",
741 | "Predicted: 19.51367 Actual: 18.0 \n",
742 | "\n",
743 | "Light, fresh aromas of tomato and plum are gritty. This feels round for the most part, with rubbery tannins. Medicinal, slightly minty plum and berry flavors end with acceptable rawness and a latex note.\n",
744 | "Predicted: 21.455168 Actual: 10.0 \n",
745 | "\n",
746 | "A densely fruity wine, with berry and plum fruits, pushed by some firm tannins. It is the ripe fruit, brimming over the glass, that makes this wine so delicious and approachable now.\n",
747 | "Predicted: 25.588463 Actual: 17.0 \n",
748 | "\n",
749 | "Wild, briary, brambly and semi-sweet from this hot vintage, this is a Zin made for easy drinking with roasts and cheeses. It's a big, hot wine, with some raisiny flavors mixed in with the cherries and spices.\n",
750 | "Predicted: 30.262243 Actual: 38.0 \n",
751 | "\n",
752 | "The nose of this Chard is so lively and welcoming with accents of lilac, jasmine, white peach slices and clementine. Matured in barrel for 10 months, the wine's palate offers more intense, mature flavors of peaches and cream, ginger and supporting tropical acidity. Well structured with a defined and evolving finish, it takes you from citrus to fresh peach to vanilla wooded spice.\n",
753 | "Predicted: 13.833609 Actual: 15.0 \n",
754 | "\n",
755 | "Full bodied with a velvety heft on the palate, this Reserve Merlot from Hudson-Chatham offers a striking balance of intensity and elegance. From the nose to the palate, there's a bounty of rich dark fruit, bramble and smoke layered with silky oak and firm tannins.\n",
756 | "Predicted: 18.991003 Actual: 22.0 \n",
757 | "\n",
758 | "Cranberry, Bing cherry and red plum flavors dominate the fruit profile, enlivened by a bright spice note. The tannins turn quite chalky and drying, which lessens the pleasure of the finish.\n",
759 | "Predicted: 11.892209 Actual: 19.0 \n",
760 | "\n",
761 | "A wine that is all about crispness, structure and bright fruits. The creamy texture from lees aging gives a rounder dimension, but the main thrust here is minerality coupled with delicious acidity.\n",
762 | "Predicted: 28.631481 Actual: 20.0 \n",
763 | "\n",
764 | "At its best Rubicon stuns with power and richness. In lesser vintages (which are usually hotter ones) the wine, which is largely Cabernet Sauvignon, can be raisiny. While 2004 was a warm year, diligent viticulture paid off, resulting in an opulent wine with the purest expression of crushed cherries and blackberries, and oak-inspired hints of nougat and caramel. Fairly aggressive in tannins now, it should begin to open by 2008 and drink well for a decade.\n",
765 | "Predicted: 262.89868 Actual: 125.0 \n",
766 | "\n",
767 | "A bit unripe, with a slight vegetal intrusion into the blackberries and cherries. Teeter-totters just on the edge of ripeness, but falls short. Otherwise, the tannins are pretty, and the oak is deftly applied.\n",
768 | "Predicted: 48.683483 Actual: 50.0 \n",
769 | "\n",
770 | "A good everyday wine. It's dry, full-bodied and has enough berry-cherry flavors to get by, wrapped into a smooth texture.\n",
771 | "Predicted: 9.694958 Actual: 10.0 \n",
772 | "\n",
773 | "Combines dusky peach and passion fruit aromas and flavors in a wine that's a bit softer and sweeter-tasting than many Marlborough Sauvignon Blancs, yet still carries a refreshing touch of acid on the finish. Imported by New Zealand Wine Imports.\n",
774 | "Predicted: 17.764606 Actual: 17.0 \n",
775 | "\n",
776 | "A light-pink color is backed by a simple nose that's nondescript but fresh and clean. A slightly foamy but firm palate isn't all that deep or rich, while mild grapefruit and blood orange flavors finish steady.\n",
777 | "Predicted: 12.843844 Actual: 12.0 \n",
778 | "\n",
779 | "A nice, clean, attractive version of affordable Carmenère? Yep, that's what Odfjell delivers pretty much every year with its Armador bottling. Aromas are floral, minerally and deep, and so is the palate, which is pure, spicy and loaded with blackberry fruit. Smooth on the finish, too. One of Chile's best value-priced Carmenères.\n",
780 | "Predicted: 15.080006 Actual: 12.0 \n",
781 | "\n",
782 | "Plum, red berry, cinnamon and mint aromas give this savory-style red from Lebanon a serious start. Soft and elegant on the palate but with a touch of spice and lively acid, the wine is balanced and unfolding, and will pair well with dishes that offer complex spice. An interesting red with exotic appeal.\n",
783 | "Predicted: 30.442371 Actual: 50.0 \n",
784 | "\n",
785 | "Powerful vanilla scents rise from the glass, but the fruit, even in this difficult vintage, comes out immediately. It's tart and sharp, with a strong herbal component, and the wine snaps into focus quickly with fruit, acid, tannin, herb and vanilla in equal proportion. Firm and tight, still quite young, this wine needs decanting and/or further bottle age to show its best.\n",
786 | "Predicted: 46.233624 Actual: 45.0 \n",
787 | "\n",
788 | "The bouquet delivers touches of graphite and rubber in front of traditional dried cherry and raspberry. Nice in the mouth, with softly fading fruit riding a wave of persistent acids and tannins. Snappy, old-school and succulent. Drink now through 2010.\n",
789 | "Predicted: 24.337423 Actual: 26.0 \n",
790 | "\n",
791 | "Oak powers the nose on this entry-level bottling from the Ballard Canyon-based winery, with peach custard, cinnamon and other brown spices. It's wide and ripe on the palate at first, with cooked Meyer lemons, nectarine and pineapple, but also boasts a line of brisker acidity trending toward lime.\n",
792 | "Predicted: 25.915466 Actual: 24.0 \n",
793 | "\n",
794 | "Shows plenty of weight on the palate, but also an overall lack of intensity to its slightly vegetal—green pea—flavors. Still, there's enough passion fruit and grapefruit to provide a sense of balance and a crisp, clean finish.\n",
795 | "Predicted: 16.476027 Actual: 11.0 \n",
796 | "\n",
797 | "Fresh, vivid fruit flavors pop from this light-colored, well-balanced, mouthwatering wine. Ripe pear, crisp apple and a touch of baking spices mingle on the palate and linger on the finish.\n",
798 | "Predicted: 16.757448 Actual: 16.0 \n",
799 | "\n",
800 | "Dark mineral, toasted French oak and black fruit carry the nose. This is a sturdy, nicely made high-end Rioja, but due to the heat of the year its range of flavors is narrow as it settles on baked plum and molasses. Medium long on the finish, with a lasting taste of chocolate.\n",
801 | "Predicted: 81.83065 Actual: 100.0 \n",
802 | "\n",
803 | "This medium-bodied dry Riesling is nicely balanced between apple and lime, with hints of petrol and spice to bring home its Rieslingness. Drink now for the vibrance and freshness of the fruit, but it should age well for at least a few years.\n",
804 | "Predicted: 15.825063 Actual: 18.0 \n",
805 | "\n",
806 | "Pretty good, but a little on the rustic side, with baked blackberry, cherry and currant flavors wrapped into some rugged, edgy tannins. Could just be going through an awkward phase, but its future is hard to predict.\n",
807 | "Predicted: 26.417683 Actual: 40.0 \n",
808 | "\n",
809 | "Quite woody for this weight class, with balsam shavings and berry aromas that are distant. Chewy, lactic and creamy across the tongue, with a flavor profile that brings chocolate and berry syrup flavors. Finishes with a final wave of oaky spice and bitterness; good but needs more fruit.\n",
810 | "Predicted: 13.144117 Actual: 15.0 \n",
811 | "\n",
812 | "From an organic, dry farmed vineyard, this wine is as feral as the forest—a study in wild boar, wildflowers and dried herbs. Bright acidity buoys that meaty quality in the form of tart rhubarb and strawberry tea flavors. Floral on the nose and palate, it's spicy too, with bold seasonings of clove and pepper.\n",
813 | "Predicted: 67.539925 Actual: 95.0 \n",
814 | "\n",
815 | "This is a very enjoyable Chardonnay from Friuli in northeastern Italy with vibrant luminosity and fresh tones of stone fruit, yellow rose and honey. The mouthfeel is naturally creamy but the wine ends on a bright, crisp note.\n",
816 | "Predicted: 17.144892 Actual: 16.0 \n",
817 | "\n",
818 | "A powerful, fat, still young wine, full of yeast flavors, green apples, plums and with a fine, tight structure—this is a food wine that could certainly age, maybe over 3 to 4 years.\n",
819 | "Predicted: 25.042484 Actual: 23.0 \n",
820 | "\n",
821 | "Past its prime, with herbal, leafy notes dominating the nectarine and melon fruit. Soft and even a bit sweet, with a chalky finish.\n",
822 | "Predicted: 16.498108 Actual: 15.0 \n",
823 | "\n",
824 | "Soft, creamy and buttery, this will surely please those who like a banana cream pie style of Chardonnay. There's peaches in there too, and papaya—a lot of ripe fruit, nicely matched to the oak flavors. Drink now.\n",
825 | "Predicted: 26.30731 Actual: 24.0 \n",
826 | "\n",
827 | "Putting your nose into a glass of Oddero Barolo Villero is like sniffing a handful of Langhe soil. This wine speaks highly of its territory with aromas of smoke, earth, forest berry, white truffle, hazelnut, cola, root beer, humus and dried apple skins. In the mouth, the tannins are impressively polished and fine.\n",
828 | "Predicted: 60.76769 Actual: 65.0 \n",
829 | "\n",
830 | "A reasonably good buy in vintage Port, Silval is something of a second label for Quinta do Noval. It's less concentrated and suppler in the mouth than its sister wine, but still boasts compelling floral aromas allied to prune, berry and chocolate flavors. Worth trying a bottle before 2010.\n",
831 | "Predicted: 44.748398 Actual: 37.0 \n",
832 | "\n",
833 | "Rustic tomato and cherry aromas make for a scratchy opening. This is wiry and grating in feel, with sour plum and red-currant flavors. A fresh but scratchy and herbal tasting finish isn't exactly friendly.\n",
834 | "Predicted: 10.840133 Actual: 13.0 \n",
835 | "\n",
836 | "Fragrant and spicy, this opens with green pepper notes and boatloads of prickly/brambly raspberry/strawberry fruit. From a cool vintage, the blend includes 15% Syrah. It's nicely handled, with great penetration and a dancer's precision.\n",
837 | "Predicted: 27.572311 Actual: 25.0 \n",
838 | "\n",
839 | "This is a cheerful, agreeable wine, with loose-knit fruit flavors of strawberry and sweet cranberry, interwoven with generous streaks of butter and toast. Drink up.\n",
840 | "Predicted: 20.560844 Actual: 15.0 \n",
841 | "\n",
842 | "This terroir-driven Pinot, from the Oregon side of the Columbia River, shows a dramatically different profile than the Willamette Valley Pinots commonly associated with the state. Kudos to winemaker Peter Rosback for finding great Pinot all over Oregon. This unique vineyard shows classic varietal fruit laced with mineral, kissed with toast, and finished with delicious mocha flavors from new oak barrels. The tart, tangy spine keeps it balanced and extends the fruit flavors well into a long, crisp finish.\n",
843 | "Predicted: 38.203552 Actual: 42.0 \n",
844 | "\n",
845 | "Here's a modern, round and velvety Barolo (from Monforte d'Alba) that will appeal to those who love a thick and juicy style of wine. The aromas include lavender, allspice, cinnamon, white chocolate and vanilla. Tart berry flavors backed by crisp acidity and firm tannins give the mouthfeel determination and grit.\n",
846 | "Predicted: 41.028854 Actual: 49.0 \n",
847 | "\n",
848 | "Jammy and simple, with ripe cherry, blackberry and pepper flavors. Nothing terribly complex, but an easy everyday Zin at a decent price.\n",
849 | "Predicted: 9.812495 Actual: 11.0 \n",
850 | "\n",
851 | "Made in a sweeter, more candied style, with lemon, pineapple and vanilla flavors, although crisp acidity provides much needed and drying balance. But it's still a fairly simple sipper.\n",
852 | "Predicted: 19.652079 Actual: 21.0 \n",
853 | "\n",
854 | "It's obvious from the alcohol that the Merlot here was very ripe. But, happily, it doesn't taste that way. The balance of rich raisin and red plum flavors are well balanced by acidity. There is some smokiness from wood, the ripeness finishing freshly with a black cherry taste.\n",
855 | "Predicted: 41.17104 Actual: 26.0 \n",
856 | "\n"
857 | ]
858 | }
859 | ],
860 | "source": [
861 | "# Compare predictions with actual values for the first few items in our test dataset\n",
862 | "num_predictions = 40\n",
863 | "diff = 0\n",
864 | "\n",
865 | "for i in range(num_predictions):\n",
866 | " val = predictions[i]\n",
867 | " print(description_test.iloc[i])\n",
868 | " print('Predicted: ', val[0], 'Actual: ', labels_test.iloc[i], '\\n')\n",
869 | " diff += abs(val[0] - labels_test.iloc[i])"
870 | ]
871 | },
872 | {
873 | "cell_type": "code",
874 | "execution_count": 28,
875 | "metadata": {},
876 | "outputs": [
877 | {
878 | "name": "stdout",
879 | "output_type": "stream",
880 | "text": [
881 | "Average prediction difference: 8.719513726234435\n"
882 | ]
883 | }
884 | ],
885 | "source": [
886 | "# Compare the average difference between actual price and the model's predicted price\n",
887 | "print('Average prediction difference: ', diff / num_predictions)"
888 | ]
889 | }
890 | ],
891 | "metadata": {
892 | "colab": {
893 | "default_view": {},
894 | "name": "keras-wide-deep.ipynb",
895 | "provenance": [],
896 | "version": "0.3.2",
897 | "views": {}
898 | },
899 | "kernelspec": {
900 | "display_name": "Python 3",
901 | "language": "python",
902 | "name": "python3"
903 | },
904 | "language_info": {
905 | "codemirror_mode": {
906 | "name": "ipython",
907 | "version": 3
908 | },
909 | "file_extension": ".py",
910 | "mimetype": "text/x-python",
911 | "name": "python",
912 | "nbconvert_exporter": "python",
913 | "pygments_lexer": "ipython3",
914 | "version": "3.6.1"
915 | }
916 | },
917 | "nbformat": 4,
918 | "nbformat_minor": 1
919 | }
920 |
--------------------------------------------------------------------------------
/predictions.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "To run this model directly in the browser with zero setup, open it in [Colab here](https://colab.research.google.com/github/sararob/keras-wine-model/blob/master/predictions.ipynb)."
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 0,
13 | "metadata": {
14 | "colab": {
15 | "autoexec": {
16 | "startup": false,
17 | "wait_interval": 0
18 | }
19 | },
20 | "colab_type": "code",
21 | "id": "pq9A_C5IH0r8"
22 | },
23 | "outputs": [],
24 | "source": [
25 | "from __future__ import absolute_import\n",
26 | "from __future__ import division\n",
27 | "from __future__ import print_function"
28 | ]
29 | },
30 | {
31 | "cell_type": "code",
32 | "execution_count": 2,
33 | "metadata": {
34 | "colab": {
35 | "autoexec": {
36 | "startup": false,
37 | "wait_interval": 0
38 | },
39 | "base_uri": "https://localhost:8080/",
40 | "height": 34
41 | },
42 | "colab_type": "code",
43 | "executionInfo": {
44 | "elapsed": 1108,
45 | "status": "ok",
46 | "timestamp": 1525890768277,
47 | "user": {
48 | "displayName": "Sara Presentation",
49 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
50 | "userId": "114025536810204251586"
51 | },
52 | "user_tz": 420
53 | },
54 | "id": "zc3JLt1WH0r-",
55 | "outputId": "673ce0d8-db52-44a7-b37e-dd81f9112b3c"
56 | },
57 | "outputs": [
58 | {
59 | "name": "stdout",
60 | "output_type": "stream",
61 | "text": [
62 | "You have TensorFlow version 1.7.0\n"
63 | ]
64 | }
65 | ],
66 | "source": [
67 | "import itertools\n",
68 | "import os\n",
69 | "import math\n",
70 | "import numpy as np\n",
71 | "import pandas as pd\n",
72 | "import tensorflow as tf\n",
73 | "import pickle\n",
74 | "\n",
75 | "from sklearn.preprocessing import LabelEncoder\n",
76 | "from tensorflow import keras\n",
77 | "layers = keras.layers\n",
78 | "\n",
79 | "# This code was tested with TensorFlow v1.7\n",
80 | "print(\"You have TensorFlow version\", tf.__version__)"
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": 3,
86 | "metadata": {
87 | "colab": {
88 | "autoexec": {
89 | "startup": false,
90 | "wait_interval": 0
91 | },
92 | "base_uri": "https://localhost:8080/",
93 | "height": 204
94 | },
95 | "colab_type": "code",
96 | "executionInfo": {
97 | "elapsed": 2749,
98 | "status": "ok",
99 | "timestamp": 1525890771838,
100 | "user": {
101 | "displayName": "Sara Presentation",
102 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
103 | "userId": "114025536810204251586"
104 | },
105 | "user_tz": 420
106 | },
107 | "id": "YVo-k3yMH0sC",
108 | "outputId": "765a9998-638c-4f6d-9329-237c311fedf8"
109 | },
110 | "outputs": [
111 | {
112 | "name": "stdout",
113 | "output_type": "stream",
114 | "text": [
115 | "--2018-05-09 18:32:50-- https://storage.googleapis.com/keras_wine/final_wine_model.h5\r\n",
116 | "Resolving storage.googleapis.com (storage.googleapis.com)... 74.125.197.128, 2607:f8b0:400e:c03::80\r\n",
117 | "Connecting to storage.googleapis.com (storage.googleapis.com)|74.125.197.128|:443... connected.\n",
118 | "HTTP request sent, awaiting response... 200 OK\n",
119 | "Length: 38208016 (36M) [application/octet-stream]\n",
120 | "Saving to: ‘final_wine_model.h5.2’\n",
121 | "\n",
122 | "final_wine_model.h5 100%[===================>] 36.44M 124MB/s in 0.3s \n",
123 | "\n",
124 | "2018-05-09 18:32:51 (124 MB/s) - ‘final_wine_model.h5.2’ saved [38208016/38208016]\n",
125 | "\n"
126 | ]
127 | }
128 | ],
129 | "source": [
130 | "# Load our model\n",
131 | "!wget 'https://storage.googleapis.com/keras_wine/final_wine_model.h5'\n",
132 | "model = keras.models.load_model('final_wine_model.h5')"
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "execution_count": 4,
138 | "metadata": {
139 | "colab": {
140 | "autoexec": {
141 | "startup": false,
142 | "wait_interval": 0
143 | },
144 | "base_uri": "https://localhost:8080/",
145 | "height": 445
146 | },
147 | "colab_type": "code",
148 | "executionInfo": {
149 | "elapsed": 2508,
150 | "status": "ok",
151 | "timestamp": 1525890774949,
152 | "user": {
153 | "displayName": "Sara Presentation",
154 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
155 | "userId": "114025536810204251586"
156 | },
157 | "user_tz": 420
158 | },
159 | "id": "YtEuCyIbH0sE",
160 | "outputId": "40f2a305-aafc-4ea0-e8c9-c7dc745d36de"
161 | },
162 | "outputs": [
163 | {
164 | "name": "stdout",
165 | "output_type": "stream",
166 | "text": [
167 | "--2018-05-09 18:32:53-- https://storage.googleapis.com/keras_wine/word_tokenizer.p\r\n",
168 | "Resolving storage.googleapis.com (storage.googleapis.com)... 74.125.197.128, 2607:f8b0:400e:c03::80\r\n",
169 | "Connecting to storage.googleapis.com (storage.googleapis.com)|74.125.197.128|:443... connected.\r\n",
170 | "HTTP request sent, awaiting response... 200 OK\r\n",
171 | "Length: 1235247 (1.2M) [application/octet-stream]\r\n",
172 | "Saving to: ‘word_tokenizer.p.2’\r\n",
173 | "\r\n",
174 | "\r",
175 | "word_tokenizer.p.2 0%[ ] 0 --.-KB/s \r",
176 | "word_tokenizer.p.2 100%[===================>] 1.18M --.-KB/s in 0.02s \r\n",
177 | "\r\n",
178 | "2018-05-09 18:32:53 (66.0 MB/s) - ‘word_tokenizer.p.2’ saved [1235247/1235247]\r\n",
179 | "\n",
180 | "--2018-05-09 18:32:54-- https://storage.googleapis.com/keras_wine/variety_encoder.p\n",
181 | "Resolving storage.googleapis.com (storage.googleapis.com)... 74.125.197.128, 2607:f8b0:400e:c03::80\n",
182 | "Connecting to storage.googleapis.com (storage.googleapis.com)|74.125.197.128|:443... connected.\n",
183 | "HTTP request sent, awaiting response... 200 OK\n",
184 | "Length: 1029 (1.0K) [application/octet-stream]\n",
185 | "Saving to: ‘variety_encoder.p.1’\n",
186 | "\n",
187 | "variety_encoder.p.1 100%[===================>] 1.00K --.-KB/s in 0s \n",
188 | "\n",
189 | "2018-05-09 18:32:54 (15.2 MB/s) - ‘variety_encoder.p.1’ saved [1029/1029]\n",
190 | "\n"
191 | ]
192 | },
193 | {
194 | "name": "stderr",
195 | "output_type": "stream",
196 | "text": [
197 | "/usr/local/lib/python3.6/dist-packages/sklearn/base.py:311: UserWarning: Trying to unpickle estimator LabelEncoder from version 0.18.2 when using version 0.19.1. This might lead to breaking code or invalid results. Use at your own risk.\n",
198 | " UserWarning)\n"
199 | ]
200 | }
201 | ],
202 | "source": [
203 | "# Load our vocabulary tokenizer and variety encoder\n",
204 | "!wget 'https://storage.googleapis.com/keras_wine/word_tokenizer.p'\n",
205 | "tokenizer = pickle.load(open('word_tokenizer.p', 'rb'))\n",
206 | "\n",
207 | "!wget 'https://storage.googleapis.com/keras_wine/variety_encoder.p'\n",
208 | "encoder = pickle.load(open('variety_encoder.p', 'rb'))"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": 0,
214 | "metadata": {
215 | "colab": {
216 | "autoexec": {
217 | "startup": false,
218 | "wait_interval": 0
219 | }
220 | },
221 | "colab_type": "code",
222 | "id": "6iTvpi18H0sG"
223 | },
224 | "outputs": [],
225 | "source": [
226 | "# Let's make predictions on some raw data\n",
227 | "\n",
228 | "# Enter wine descriptions here\n",
229 | "test_descriptions = [\n",
230 | " 'From 18-year-old vines, this supple well-balanced effort blends flavors of mocha, cherry, vanilla and breakfast tea. Superbly integrated and delicious even at this early stage, this wine seems destined for a long and savory cellar life. Drink now through 2028.',\n",
231 | " 'The Quarts de Chaume, the four fingers of land that rise above the Layon Valley, are one of the pinnacles of sweet wines in the Loire. Showing botrytis and layers of dryness over the honey and peach jelly flavors, but also has great freshness. The aftertaste just lasts.',\n",
232 | " 'Nicely oaked blackberry, licorice, vanilla and charred aromas are smooth and sultry. This is an outstanding wine from an excellent year. Forward barrel-spice and mocha flavors adorn core blackberry and raspberry fruit, while this runs long and tastes vaguely chocolaty on the velvety finish. Enjoy this top-notch Tempranillo through 2030.',\n",
233 | " 'Bright, light oak shadings dress up this medium-bodied wine, complementing the red cherry and strawberry flavors. Its fresh, fruity and not very tannic—easy to drink and enjoy.',\n",
234 | " 'This wine features black cherry, blackberry, blueberry with aromas of black licorice and earth. Ending with a creamy vanilla finish.'\n",
235 | "]\n",
236 | "\n",
237 | "# Enter the corresponding varieties here\n",
238 | "test_varieties = [\n",
239 | " 'Pinot Noir',\n",
240 | " 'Chenin Blanc',\n",
241 | " 'Tempranillo',\n",
242 | " 'Sauvignon Blanc',\n",
243 | " 'Syrah'\n",
244 | "]\n",
245 | "\n",
246 | "# Enter the corresponding prices here\n",
247 | "labels = [\n",
248 | " 48,\n",
249 | " 152,\n",
250 | " 80,\n",
251 | " 10,\n",
252 | " 23\n",
253 | "]"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": 6,
259 | "metadata": {
260 | "colab": {
261 | "autoexec": {
262 | "startup": false,
263 | "wait_interval": 0
264 | },
265 | "base_uri": "https://localhost:8080/",
266 | "height": 292
267 | },
268 | "colab_type": "code",
269 | "executionInfo": {
270 | "elapsed": 340,
271 | "status": "ok",
272 | "timestamp": 1525890779958,
273 | "user": {
274 | "displayName": "Sara Presentation",
275 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
276 | "userId": "114025536810204251586"
277 | },
278 | "user_tz": 420
279 | },
280 | "id": "4igkpWyWH0sI",
281 | "outputId": "d51f84f8-b592-4c15-b17b-5da4e2b16fdb"
282 | },
283 | "outputs": [
284 | {
285 | "name": "stdout",
286 | "output_type": "stream",
287 | "text": [
288 | "Sample vocab\n",
289 | " {'and': 1, 'the': 2, 'a': 3, 'of': 4, 'with': 5, 'this': 6, 'is': 7, 'flavors': 8, 'wine': 9, 'in': 10, 'to': 11, 'it': 12, 'fruit': 13, 'but': 14, 'on': 15, 'that': 16, \"it's\": 17, 'finish': 18, 'cherry': 19, 'aromas': 20} \n",
290 | "\n",
291 | "Variety encoder\n",
292 | " ['Albariño' 'Barbera' 'Bordeaux-style Red Blend'\n",
293 | " 'Bordeaux-style White Blend' 'Cabernet Franc' 'Cabernet Sauvignon'\n",
294 | " 'Carmenère' 'Champagne Blend' 'Chardonnay' 'Chenin Blanc'\n",
295 | " 'Corvina, Rondinella, Molinara' 'Gewürztraminer' 'Grenache'\n",
296 | " 'Grüner Veltliner' 'Malbec' 'Merlot' 'Nebbiolo' 'Petite Sirah'\n",
297 | " 'Pinot Grigio' 'Pinot Gris' 'Pinot Noir' 'Port' 'Portuguese Red'\n",
298 | " 'Portuguese White' 'Prosecco' 'Red Blend' 'Rhône-style Red Blend'\n",
299 | " 'Riesling' 'Rosé' 'Sangiovese' 'Sangiovese Grosso' 'Sauvignon Blanc'\n",
300 | " 'Shiraz' 'Sparkling Blend' 'Syrah' 'Tempranillo' 'Tempranillo Blend'\n",
301 | " 'Viognier' 'White Blend' 'Zinfandel'] \n",
302 | "\n"
303 | ]
304 | }
305 | ],
306 | "source": [
307 | "# Vocab and variety lookup\n",
308 | "vocab_lookup = tokenizer.word_index\n",
309 | "first_20_words = {k: vocab_lookup[k] for k in list(vocab_lookup)[:20]}\n",
310 | "print(\"Sample vocab\\n\", first_20_words, \"\\n\")\n",
311 | "print(\"Variety encoder\\n\", encoder.classes_, \"\\n\")"
312 | ]
313 | },
314 | {
315 | "cell_type": "code",
316 | "execution_count": 7,
317 | "metadata": {
318 | "colab": {
319 | "autoexec": {
320 | "startup": false,
321 | "wait_interval": 0
322 | },
323 | "base_uri": "https://localhost:8080/",
324 | "height": 136
325 | },
326 | "colab_type": "code",
327 | "executionInfo": {
328 | "elapsed": 283,
329 | "status": "ok",
330 | "timestamp": 1525890781312,
331 | "user": {
332 | "displayName": "Sara Presentation",
333 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
334 | "userId": "114025536810204251586"
335 | },
336 | "user_tz": 420
337 | },
338 | "id": "v5DNE1fWH0sM",
339 | "outputId": "62a25fe3-d92b-490b-aa86-638f73006df7"
340 | },
341 | "outputs": [
342 | {
343 | "name": "stdout",
344 | "output_type": "stream",
345 | "text": [
346 | "Bag of words matrix\n",
347 | "[0. 1. 0. ... 0. 0. 0.] \n",
348 | "\n",
349 | "Variety matrix\n",
350 | "[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.\n",
351 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] \n",
352 | "\n"
353 | ]
354 | }
355 | ],
356 | "source": [
357 | "# Wide model features\n",
358 | "bow_description = tokenizer.texts_to_matrix(test_descriptions)\n",
359 | "variety = encoder.transform(test_varieties)\n",
360 | "variety = keras.utils.to_categorical(variety, len(encoder.classes_))\n",
361 | "\n",
362 | "# Print an example for the model inputs\n",
363 | "print(\"Bag of words matrix\")\n",
364 | "print(bow_description[0], \"\\n\")\n",
365 | "print(\"Variety matrix\")\n",
366 | "print(variety[0], \"\\n\")"
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "execution_count": 8,
372 | "metadata": {
373 | "colab": {
374 | "autoexec": {
375 | "startup": false,
376 | "wait_interval": 0
377 | },
378 | "base_uri": "https://localhost:8080/",
379 | "height": 238
380 | },
381 | "colab_type": "code",
382 | "executionInfo": {
383 | "elapsed": 257,
384 | "status": "ok",
385 | "timestamp": 1525890783363,
386 | "user": {
387 | "displayName": "Sara Presentation",
388 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
389 | "userId": "114025536810204251586"
390 | },
391 | "user_tz": 420
392 | },
393 | "id": "jqpc4NACH0sO",
394 | "outputId": "d87d74ea-0435-4fbb-be07-9a9befb774bb"
395 | },
396 | "outputs": [
397 | {
398 | "name": "stdout",
399 | "output_type": "stream",
400 | "text": [
401 | "[ 25 1475 284 347 504 6 344 55 85 552 1007 8 4 226\n",
402 | " 19 52 1 3614 360 2997 476 1 150 232 60 6 887 871\n",
403 | " 6 9 218 4282 21 3 86 1 245 318 1282 32 39 80\n",
404 | " 7083 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
405 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
406 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
407 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
408 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
409 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
410 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
411 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
412 | " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n",
413 | " 0 0]\n"
414 | ]
415 | }
416 | ],
417 | "source": [
418 | "# Deep model feature: word embeddings of wine descriptions\n",
419 | "embed_description = tokenizer.texts_to_sequences(test_descriptions)\n",
420 | "embed_description = keras.preprocessing.sequence.pad_sequences(\n",
421 | " embed_description, maxlen=170, padding=\"post\")\n",
422 | "\n",
423 | "print(embed_description[0])"
424 | ]
425 | },
426 | {
427 | "cell_type": "code",
428 | "execution_count": 0,
429 | "metadata": {
430 | "colab": {
431 | "autoexec": {
432 | "startup": false,
433 | "wait_interval": 0
434 | }
435 | },
436 | "colab_type": "code",
437 | "id": "UGIFAJoNH0sQ"
438 | },
439 | "outputs": [],
440 | "source": [
441 | "predictions = model.predict([bow_description, variety] + [embed_description])"
442 | ]
443 | },
444 | {
445 | "cell_type": "code",
446 | "execution_count": 10,
447 | "metadata": {
448 | "colab": {
449 | "autoexec": {
450 | "startup": false,
451 | "wait_interval": 0
452 | },
453 | "base_uri": "https://localhost:8080/",
454 | "height": 292
455 | },
456 | "colab_type": "code",
457 | "executionInfo": {
458 | "elapsed": 255,
459 | "status": "ok",
460 | "timestamp": 1525890786992,
461 | "user": {
462 | "displayName": "Sara Presentation",
463 | "photoUrl": "https://lh3.googleusercontent.com/a/default-user=s128",
464 | "userId": "114025536810204251586"
465 | },
466 | "user_tz": 420
467 | },
468 | "id": "csv2NgmZH0sS",
469 | "outputId": "6e3e371b-bf03-4fb2-a1db-363a0c21d5e1"
470 | },
471 | "outputs": [
472 | {
473 | "name": "stdout",
474 | "output_type": "stream",
475 | "text": [
476 | "From 18-year-old vines, this supple well-balanced effort blends flavors of mocha, cherry, vanilla and breakfast tea. Superbly integrated and delicious even at this early stage, this wine seems destined for a long and savory cellar life. Drink now through 2028.\n",
477 | "Predicted: 46.476532 Actual: 48 \n",
478 | "\n",
479 | "The Quarts de Chaume, the four fingers of land that rise above the Layon Valley, are one of the pinnacles of sweet wines in the Loire. Showing botrytis and layers of dryness over the honey and peach jelly flavors, but also has great freshness. The aftertaste just lasts.\n",
480 | "Predicted: 117.74728 Actual: 152 \n",
481 | "\n",
482 | "Nicely oaked blackberry, licorice, vanilla and charred aromas are smooth and sultry. This is an outstanding wine from an excellent year. Forward barrel-spice and mocha flavors adorn core blackberry and raspberry fruit, while this runs long and tastes vaguely chocolaty on the velvety finish. Enjoy this top-notch Tempranillo through 2030.\n",
483 | "Predicted: 90.468124 Actual: 80 \n",
484 | "\n",
485 | "Bright, light oak shadings dress up this medium-bodied wine, complementing the red cherry and strawberry flavors. Its fresh, fruity and not very tannic—easy to drink and enjoy.\n",
486 | "Predicted: 9.867294 Actual: 10 \n",
487 | "\n",
488 | "This wine features black cherry, blackberry, blueberry with aromas of black licorice and earth. Ending with a creamy vanilla finish.\n",
489 | "Predicted: 26.843208 Actual: 23 \n",
490 | "\n"
491 | ]
492 | }
493 | ],
494 | "source": [
495 | "for i in range(len(test_descriptions)):\n",
496 | " val = predictions[i]\n",
497 | " print(test_descriptions[i])\n",
498 | " print('Predicted: ', val[0], 'Actual: ', labels[i], '\\n')"
499 | ]
500 | },
501 | {
502 | "cell_type": "code",
503 | "execution_count": 0,
504 | "metadata": {
505 | "colab": {
506 | "autoexec": {
507 | "startup": false,
508 | "wait_interval": 0
509 | }
510 | },
511 | "colab_type": "code",
512 | "id": "cA76-j2IH0sV"
513 | },
514 | "outputs": [],
515 | "source": []
516 | }
517 | ],
518 | "metadata": {
519 | "colab": {
520 | "collapsed_sections": [],
521 | "default_view": {},
522 | "name": "predictions.ipynb",
523 | "provenance": [],
524 | "version": "0.3.2",
525 | "views": {}
526 | },
527 | "kernelspec": {
528 | "display_name": "Python 3",
529 | "language": "python",
530 | "name": "python3"
531 | },
532 | "language_info": {
533 | "codemirror_mode": {
534 | "name": "ipython",
535 | "version": 3
536 | },
537 | "file_extension": ".py",
538 | "mimetype": "text/x-python",
539 | "name": "python",
540 | "nbconvert_exporter": "python",
541 | "pygments_lexer": "ipython3",
542 | "version": "3.6.1"
543 | }
544 | },
545 | "nbformat": 4,
546 | "nbformat_minor": 1
547 | }
548 |
--------------------------------------------------------------------------------