From 61f9b54cdb6b7fcf4fd8076a5aa50be8b2cce01e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 3 Nov 2016 23:47:11 +0100 Subject: [PATCH] Upgrade chapter 2 to sklearn 0.18 and ensure python 2 and python 3 both work --- 02_end_to_end_machine_learning_project.ipynb | 94 +++++++++++-------- images/end_to_end_project/california.png | Bin 0 -> 10034 bytes 2 files changed, 54 insertions(+), 40 deletions(-) create mode 100644 images/end_to_end_project/california.png diff --git a/02_end_to_end_machine_learning_project.ipynb b/02_end_to_end_machine_learning_project.ipynb index e3a88ed..1ee28e6 100644 --- a/02_end_to_end_machine_learning_project.ipynb +++ b/02_end_to_end_machine_learning_project.ipynb @@ -92,13 +92,14 @@ "source": [ "import os\n", "import tarfile\n", - "import urllib.request\n", + "from six.moves import urllib\n", "\n", "HOUSING_PATH = \"datasets/housing\"\n", "HOUSING_URL = DATASETS_URL + \"/housing/housing.tgz\"\n", "\n", "def fetch_housing_data(housing_url=HOUSING_URL, housing_path=HOUSING_PATH):\n", - " os.makedirs(housing_path, exist_ok=True)\n", + " if not os.path.exists(housing_path):\n", + " os.makedirs(housing_path)\n", " tgz_path = os.path.join(housing_path, \"housing.tgz\")\n", " urllib.request.urlretrieve(housing_url, tgz_path)\n", " housing_tgz = tarfile.open(tgz_path)\n", @@ -235,7 +236,7 @@ "import hashlib\n", "\n", "def test_set_check(identifier, test_ratio, hash):\n", - " return hash(str(identifier).encode(\"ascii\")).digest()[-1] < 256 * test_ratio\n", + " return bytearray(hash(np.int64(identifier)).digest())[-1] < 256 * test_ratio\n", "\n", "def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5):\n", " ids = data[id_column]\n", @@ -264,7 +265,7 @@ }, "outputs": [], "source": [ - "from sklearn.cross_validation import train_test_split\n", + "from sklearn.model_selection import train_test_split\n", "\n", "train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)\n", "test_set.head()" @@ -302,12 +303,12 @@ }, "outputs": [], "source": [ - "from sklearn.cross_validation import StratifiedShuffleSplit\n", + "from sklearn.model_selection import StratifiedShuffleSplit\n", "\n", - "split = StratifiedShuffleSplit(housing[\"income_cat\"], test_size=0.2, random_state=42)\n", - "train_index, test_index = next(iter(split))\n", - "strat_train_set = housing.loc[train_index]\n", - "strat_test_set = housing.loc[test_index]" + "split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n", + "for train_index, test_index in split.split(housing, housing[\"income_cat\"]):\n", + " strat_train_set = housing.loc[train_index]\n", + " strat_test_set = housing.loc[test_index]" ] }, { @@ -953,10 +954,10 @@ }, "outputs": [], "source": [ - "from sklearn.cross_validation import cross_val_score\n", + "from sklearn.model_selection import cross_val_score\n", "\n", "tree_scores = cross_val_score(tree_reg, housing_prepared, housing_labels,\n", - " scoring=\"mean_squared_error\", cv=10)\n", + " scoring=\"neg_mean_squared_error\", cv=10)\n", "tree_rmse_scores = np.sqrt(-tree_scores)" ] }, @@ -985,7 +986,7 @@ "outputs": [], "source": [ "lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels,\n", - " scoring=\"mean_squared_error\", cv=10)\n", + " scoring=\"neg_mean_squared_error\", cv=10)\n", "lin_rmse_scores = np.sqrt(-lin_scores)\n", "display_scores(lin_rmse_scores)" ] @@ -1016,10 +1017,10 @@ }, "outputs": [], "source": [ - "from sklearn.cross_validation import cross_val_score\n", + "from sklearn.model_selection import cross_val_score\n", "\n", "forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels,\n", - " scoring=\"mean_squared_error\", cv=10)\n", + " scoring=\"neg_mean_squared_error\", cv=10)\n", "forest_rmse_scores = np.sqrt(-forest_scores)\n", "display_scores(forest_rmse_scores)" ] @@ -1032,7 +1033,7 @@ }, "outputs": [], "source": [ - "scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring=\"mean_squared_error\", cv=10)\n", + "scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring=\"neg_mean_squared_error\", cv=10)\n", "pd.Series(np.sqrt(-scores)).describe()" ] }, @@ -1062,7 +1063,7 @@ }, "outputs": [], "source": [ - "from sklearn.grid_search import GridSearchCV\n", + "from sklearn.model_selection import GridSearchCV\n", "\n", "param_grid = [\n", " {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},\n", @@ -1070,7 +1071,7 @@ " ]\n", "\n", "forest_reg = RandomForestRegressor()\n", - "grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='mean_squared_error')\n", + "grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='neg_mean_squared_error')\n", "grid_search.fit(housing_prepared, housing_labels)" ] }, @@ -1104,8 +1105,9 @@ }, "outputs": [], "source": [ - "for params, mean_score, scores in grid_search.grid_scores_:\n", - " print(np.sqrt(-mean_score), np.sqrt(-scores).std(), params)" + "cvres = grid_search.cv_results_\n", + "for mean_score, params in zip(cvres[\"mean_test_score\"], cvres[\"params\"]):\n", + " print(np.sqrt(-mean_score), params)" ] }, { @@ -1116,18 +1118,7 @@ }, "outputs": [], "source": [ - "from sklearn.grid_search import RandomizedSearchCV\n", - "from scipy.stats import randint\n", - "\n", - "param_distribs = {\n", - " 'n_estimators': randint(low=1, high=200),\n", - " 'max_features': randint(low=1, high=8),\n", - " }\n", - "\n", - "forest_reg = RandomForestRegressor()\n", - "rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,\n", - " n_iter=10, cv=5, scoring='mean_squared_error')\n", - "rnd_search.fit(housing_prepared, housing_labels)" + "pd.DataFrame(grid_search.cv_results_)" ] }, { @@ -1138,8 +1129,18 @@ }, "outputs": [], "source": [ - "for params, mean_score, scores in rnd_search.grid_scores_:\n", - " print(np.sqrt(-mean_score), np.sqrt(-scores).std(), params)" + "from sklearn.model_selection import RandomizedSearchCV\n", + "from scipy.stats import randint\n", + "\n", + "param_distribs = {\n", + " 'n_estimators': randint(low=1, high=200),\n", + " 'max_features': randint(low=1, high=8),\n", + " }\n", + "\n", + "forest_reg = RandomForestRegressor()\n", + "rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs,\n", + " n_iter=10, cv=5, scoring='neg_mean_squared_error')\n", + "rnd_search.fit(housing_prepared, housing_labels)" ] }, { @@ -1149,6 +1150,19 @@ "collapsed": false }, "outputs": [], + "source": [ + "cvres = rnd_search.cv_results_\n", + "for mean_score, params in zip(cvres[\"mean_test_score\"], cvres[\"params\"]):\n", + " print(np.sqrt(-mean_score), params)" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "collapsed": false + }, + "outputs": [], "source": [ "feature_importances = grid_search.best_estimator_.feature_importances_\n", "feature_importances" @@ -1156,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 74, "metadata": { "collapsed": false }, @@ -1170,7 +1184,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 75, "metadata": { "collapsed": false }, @@ -1208,7 +1222,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 76, "metadata": { "collapsed": false }, @@ -1240,7 +1254,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 77, "metadata": { "collapsed": true }, @@ -1251,7 +1265,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 78, "metadata": { "collapsed": false }, @@ -1262,7 +1276,7 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 79, "metadata": { "collapsed": false }, @@ -1281,7 +1295,7 @@ }, { "cell_type": "code", - "execution_count": 79, + "execution_count": 80, "metadata": { "collapsed": false }, diff --git a/images/end_to_end_project/california.png b/images/end_to_end_project/california.png new file mode 100644 index 0000000000000000000000000000000000000000..0103e3babc1a2dd196d0b4a3aaacfd771df7c243 GIT binary patch literal 10034 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%0D>usU|)Ve#g(YmI@BEWib+e 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 literal 0 HcmV?d00001