From 027d7368c01f8c0826150c9b696c1080faae0603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Sun, 20 Feb 2022 17:38:01 +1300 Subject: [PATCH] Fix exercise section --- 13_loading_and_preprocessing_data.ipynb | 788 ++++++++++++------------ 1 file changed, 401 insertions(+), 387 deletions(-) diff --git a/13_loading_and_preprocessing_data.ipynb b/13_loading_and_preprocessing_data.ipynb index a3bf230..1d7e4cd 100644 --- a/13_loading_and_preprocessing_data.ipynb +++ b/13_loading_and_preprocessing_data.ipynb @@ -103,14 +103,6 @@ "execution_count": 4, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2022-01-19 17:53:50.433275: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", - "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n" - ] - }, { "data": { "text/plain": [ @@ -3163,27 +3155,27 @@ "cell_type": "code", "execution_count": 115, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-02-20 15:27:32.431462: I tensorflow/core/platform/cpu_feature_guard.cc:151] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA\n", + "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n" + ] + } + ], "source": [ - "tf.keras.backend.clear_session()\n", - "np.random.seed(42)\n", - "tf.random.set_seed(42)" - ] - }, - { - "cell_type": "code", - "execution_count": 116, - "metadata": {}, - "outputs": [], - "source": [ - "train_set = tf.data.Dataset.from_tensor_slices((X_train, y_train)).shuffle(len(X_train))\n", + "tf.random.set_seed(42)\n", + "train_set = tf.data.Dataset.from_tensor_slices((X_train, y_train))\n", + "train_set = train_set.shuffle(len(X_train), seed=42)\n", "valid_set = tf.data.Dataset.from_tensor_slices((X_valid, y_valid))\n", "test_set = tf.data.Dataset.from_tensor_slices((X_test, y_test))" ] }, { "cell_type": "code", - "execution_count": 117, + "execution_count": 116, "metadata": {}, "outputs": [], "source": [ @@ -3200,7 +3192,7 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 117, "metadata": {}, "outputs": [ { @@ -3243,7 +3235,7 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 118, "metadata": {}, "outputs": [], "source": [ @@ -3264,7 +3256,7 @@ }, { "cell_type": "code", - "execution_count": 120, + "execution_count": 119, "metadata": {}, "outputs": [], "source": [ @@ -3283,7 +3275,7 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 120, "metadata": {}, "outputs": [], "source": [ @@ -3313,7 +3305,7 @@ }, { "cell_type": "code", - "execution_count": 122, + "execution_count": 121, "metadata": {}, "outputs": [], "source": [ @@ -3324,12 +3316,12 @@ }, { "cell_type": "code", - "execution_count": 123, + "execution_count": 122, "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAV0AAABYCAYAAABWMiSwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjQuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/MnkTPAAAACXBIWXMAAAsTAAALEwEAmpwYAAA79UlEQVR4nO19WWxk2Xned4q170VWcSe72Y3u0fQMe3o22DMae9STUSArhpRIDzYiC34JEsTJS6AkbwaCJC8BAhgx7CgQYAhGHCQQDEWOZUESoNFiSW5A0mhmeqY1pJpNNncWydr35d48cL7Dvw5vsVdWlTz1AQTJWs8995x/+f7lKNu2McQQQwwxRG/g6vcAhhhiiCE+TBgK3SGGGGKIHmIodIcYYogheoih0B1iiCGG6CGGQneIIYYYoocYCt0hhhhiiB5iKHSHGGKIIXqIgRG6SqknlVJvKKXySqnbSql/0u8xDQqUUpeUUjWl1F/0eyyDAKXUqFLq/yqlykqpu0qpf9rvMfUbSqnzSqlvKKWySqldpdSfKKXc/R5Xv6CU8iml/uyD9VFUSv1cKfVb/R4XMCBC94PF8VcAvg5gFMA/B/AXSqnLfR3Y4OBPAfyk34MYIPwpgAaACQCfA/BFpdRT/R1S3/HfAaQBTAG4BuBVAH/QzwH1GW4AGziahxiAPwTwFaXU+X4OChgQoQvgIwCmAfyRbdtt27bfAPAjAJ/v77D6D6XU7wLIAfhOn4cyEFBKhQB8FsAf2rZdsm37hwD+H4ZrZQHAV2zbrtm2vQvgmwA+tIrItu2ybdv/wbbtNdu2Ldu2vw5gFcDz/R7boAhd1eWxp3s9kEGCUioK4D8C+EK/xzJAuAygbdv2snjsbXyIBcwH+G8AflcpFVRKzQD4LRwJ3iEAKKUmcLR23uv3WAZF6L6PI9fo3ymlPEqpf4gjtyDY32H1Hf8JwJ/Ztr3R74EMEMIA8sZjeQCRPoxlkPB9HCmeAoBNAD8F8LV+DmhQoJTyAPhfAP7ctu33+z2egRC6tm03AfxjAP8IwC6OLLuv4GjxfCihlLoG4HUAf9TnoQwaSgCixmNRAMU+jGUgoJRyAfgWgK8CCAFIAkgA+C/9HNcg4IO5+Z84igH86z4PB8AR2TwQsG37HRxZtwAApdSPAfx5/0bUd3wMwHkA60op4MjCG1FKXbFt+7k+jqvfWAbgVkpdsm37lx889gwGwG3sI0YBzAH4E9u26wDqSqkvA/jPAP59X0fWR6ijjfNnOAq4fvID467vUIPS2lEpdRVHG8qFo6jrvwLwkQ8W0YcOSqkgOi26f4sjIfwvbdve78ugBgRKqf8DwAbwz3AUqf8GgJdt2/7QCl6l1B0AXwLwX3GkoL8MoGLb9uf6OrA+Qin1P3C0Pl63bbvU5+FoDAS98AE+D2AHR9zuPwDw8Q+rwAUA27Yrtm3v8gdHbnXtwy5wP8AfAAjgaK38bxwpog+twP0AnwHwCQD7AG4DaAH4N30dUR+hlDoH4F/gSOjuKqVKH/z0XQkNjKU7xBBDDPFhwCBZukMMMcQQf+8xFLpDDDHEED3EUOgOMcQQQ/QQQ6E7xBBDDNFDDIXuEEMMMUQPca/iiDNLbbAsCwDgcrlgWRZqtRpqtRq2t7dRr9dRr9fRbDaxu7uLarWKRqOBdruNXC6HarWKcrmMZrOJRqOBZrOJdruNVquFkZERuN1uTE9P4+LFi2i1WqhWq4hGozh//jwSiQSuXLkCt7vj0p16P3TDmaZ7ZDIZ/PSnP0W73UYoFEIgEMDMzAzcbjfq9TparRby+Twsy8LFixcRi8XOaigPMifAQ87L7u4udnZ29L1899138b3vfU/f76eeegq///u/j1gshvHxcbhcznaCbdvI5/MoFov4yle+guXlZVQqFbTbbbz44ou4ePEikskk4vE4xsfHkUwmH2a4QB/Xim3bYLaRnIdms4mDgwO0220opeB2uzE6OgqPx/M4v/40nNmcWJYFy7IwMjICpRRKpRLy+Ty++c1v4ktf+pLe/0opuFwuNBoN5HI5AIDP54NSCh6PBy6XC16vFy6XC0opKKVg2zaUUnjxxRcxNzeH3/7t38a1a9ceZHinoeuc9KUijYvng0orWJaFer2OarWKXC6n/261Wshms1roNptNFItF1Go1VKtVNJtNtFotLXCbzSY8Hg+8Xi/K5TJKpRJarRYqlQoAIJfLwev1aoE/SGg2myiXyzg8PEQ6nUa73UYwGITP54Nt23C73Wg0Gmi1WigWi2i324hEImg2m4hEIvD5fP2+hAdCu91Gu93WCtS2bb25QqEQXC6Xvtf7+/toNBoAjpU0Xw8AIyMjsCwLhUIBpVIJhUIBtVpNK2CXywXbtlGv11Eul/XaGRkZ6SrEBxncN61WC+VyGfV6Hfl8Xhsd/PF6vQiFQhgZGenziB8dlBftdhuNRkP/UNFQ6Lrdbi1spZDlb/l57XZbC2p+Vi/QtzJgy7L0ZNRqNSwtLSGdTuN73/secrkcyuUyGo0G9vb2UK1WUSqVtOCltrdtG16vFyMjI2g2m6jX6wiFQohEIkgkEtjc3ES1WkU2m0UgEEAqlcKVK1fw9NNPw+v19uW6pcKRi2BjYwN/8zd/g9XVVXz7299GtVrVAsPr9WrNbNu2FhjXrl3D9PQ0Pve5z+GZZ57p+A45v4MA3jNe88HBATKZDJaWlrC8vIxYLIbR0VHE43F84hOfwNLSEr7zne/g5s2b2NzchM/nQyQSgW3b2svJ54/63sTjcbjdbq2wafX9xm/8BhYWFhCLxdBoNLC0tIR8Po8rV67gypUriMfjGBsb6xDgHOOgzJuEuWb29vbwjW98A6VSCeVyGQC0wA2Hw4hEIvj4xz+OycnJfg35ocH1YlmWFqwAkM/ncefOHayuruLOnTsIBoOYnp6Gx+OB2+3WXu7IyAj8fj8AoFKpoNFoIJvNotVqaSVcKBTQbrextraGarWq1xNBK9vlcj3W9TAQvRcsy9KCdX9/H4eHh1roptNp1Ot1FItFbe1I+Hw+eDwe1Ot1NBoNKKXg9/u11Uih6/f7tfU0CAUh0tIHgHK5jNu3b2N1dRUrKyuo1WonNhlvfLvdhtvtRiAQ0Jad+dmDjna7jWaziVqthlKpBJ/Ph1arhWAwiFgshr29Pfj9ftRqNayvr8PtdiMcDqPdbqNQKKDRaCCTyQAAxsbG4PF4UCgUtLVHKy+RSGjLp16vo1QqoVaraUrqVxHtdhu1Wg35fB57e3uoVCp6PdF6r9frqNVqKBQKiEQi8Pv9vzIW72nrt9lsolKpoFKpaMOEwpGgAKZXa1q59JToHdPbarVaZ3pdRN+ErhQobrcbY2Nj2lVutVrw+/1otVpQSqHZbGJ+fh5ut1svnGq1qnnfZrMJy7LQarUwNzeH8+fPa83ldrvh9/uRSqVw9epVXLx4sa+LzxSkRDqdxre+9S1ks1mtOLrB5/PB5XJhc3MT+Xwe2Wy2w10eVCtNIplMIhqNasokEAjA5/MhGAwiFArh0qVL+PSnP4319XX86Ec/gmVZaDQa2oKngAGON2kymYTH48HVq1cxPj6OJ598EqlUSnPhCwsLmJmZwaVLlzA7O9vh7cj74nR/Bgnb29v47ne/i3K5DLfbjfn5eXz0ox9FMBiEUgq1Wg03b95EsVjED37wA3i9Xrz22muYnZ3t99DvCdMjouUuqchm86hvjdfrRavVws7ODlwul6biKDzdbremlZRSSCaTcLvdqFQq2jOm4G232yeEveR/HycGwtJ1uVzw+/3w+/1aSwHQLsLIyAgikUgHR1MqlbR1bFkWPB4P/H4/wuEwRkdHkc1mkc/nNc8TCAQwOjqKWCzWV6FEV5Y8NCmDbDaLnZ0dVCoVbZmZ7yEoXCuVCizLQi6XQy6X0/NHwTuoAhg42jBerxeRSASxWEzzr1SsoVAI8/PzaLVaCIVCeqMB6ODuyHdzjYTDYZw7dw7T09NIJpOIRI7b7Lrdbk0/hUIh/bic60EWuFwrpVIJ6+vrsCwLsVgMiUQCCwsLCIfDAI7c6a2tLbTbbWxsbMCyLG3JSQFmeluDiFarhVarpfdMqVTSQpNWbrVa1QqY1yqvjbGeZDKpYwLtdlvvKf5fLpeRz+e1oKUs+ntJL5CDCgQCKBaLyOfz2tKrVqs6KCIFVT6fR7Va1cGT8fFxjI+PAziyGhmIo2Xk9/v1RuynIMpms0in07h79y5u3ryJUqmEw8NDrK+vQykFn8/XsTGkpuUikRulXq/ji1/8Ir72ta/hqaeewuzsLObn5zE5OYmpqamB5/NoucgNUigU4Ha7MTk5qZVooVDA6uoqKpUK0um05vZdLhdisRhCoRCuXr2K0dFRTE1NIRgM6sAZPSG6lb8KwoaQln2lUsHBwQEODg5g2zaSySSuX7+OWCym9wvjHNeuXUOpVNJc9+rqKjKZDC5fvoyxsTE9F6br3S90i3XcuHEDP//5z7G1tYXt7W1NLWQyGc3xt9tthMNhTE1N6UyOVquFer0O27bRaDTg9XqRTCbh9Xo1PUWPsVwuY2trC3/8x3+ML3/5y4jH44hEIrh27RqeeeYZTE1NYX5+/rFd60DQCxQ2Ho9HTxaj8dRC5ACZsUDOqlwua83G95A4lzfS4/EgEoloF6zXoMIoFArY3d3FysoK3nzzTeRyOezs7KBcLmuX2Zwbpv44cdqWZeHdd9/FrVu3UC6XNdXQbre1dU8XbRBBK54KhRYIvROv14twOKyzWIrFol4HtHDj8TjC4TAuXryIVCqFYDCIkZERHBwcoFardQhbmXY16DAVBBVSpVKBUgqhUAjnzp1DMBjUrweOjJhUKqUDylx31WoVc3NzXT+/nzBpBWYl3b17F2+99RZWVlawsrICl8ul1zI5W8oPBkxJPQLQvL3P59MUFnC0b2jgMF1xc3MTlUoF4+PjSCQSGBkZwejoKHw+H6anpx+bxdsXoesUICK9wAvzeDzweDy4dOkS2u028vm8DrpwUi3LQjgcRigUwuTkJGZmZpDNZrG7u6uJdLfbrRco8zT7Yen++Mc/xt/+7d9ic3MTq6uryOfzyGQyHekqtDooJGTGghROcvzy76WlJWxsbOCnP/0pgsEgLl++jEuXLuGFF17Aq6++6jSsnsJpgzOvlNyb1+vVtEEul9NUQjgcxuLiok4BlFHtYDAIj8ejA23ZbFY/T0VOCsrtdvcyf/WRIO89KbXV1VXYto1r165pjpIw59br9WJxcRGlUgk/+clPkE6n8fTTT+vXDpIiluvYsix897vfxbvvvou/+7u/w82bN9FoNPReNr0/xgX4OaSqZLCUMsbn82nKQMZALMtCNBpFIBCAy+VCqVTCjRs3sLS0hGeffRYvv/wyFhYWsLi4qL/3YbNc+mrpyr8pZCl4KDRpmVarVVQqFdRqNR2tpXVLbjAcDiOXy6FQKCAUCulUK/J/oVAIfr+/55rdtm2srq7i+9//PjY2NrC6utoRaeY1ezweLSzkHEmBK58zXUO6VZybdDqN3d1djI6O4pVXXumrK9nNupSJ6q1WCx6PR+fdVioV/b/X68XExMSJzQZA32dayZVKBa1WSytdvp7KfJCEzb0gLXNmbEQiEczPz2suvBtcLhcmJydRr9dx48YNFAoFTcuYMYNBsHYJy7Lwy1/+Ej/84Q/xi1/8Amtra5qHlwKXqV9SuHK/01Jm7vfIyIiWMdKb5PxREHs8Hu2V3r17F7/85dHhJIlEAn6/H4uLi4+ckjkQnC4nij8ejweBQECnAdXrdWxtbelqNE5ou91GKpXC1NQUfD4fMpkMarWa1macXFpL/OklyDmvra1haWkJrVYL0WhUX7f8Td6ali41O2+sy+U6EWXl3PG3rPQ7PDxEqVTC6OgoxsbGcP78eSwuLvZlg3X7zlarhVqtpu87hS+vkQUUEiZNwA3Ea5deghS2/Dyn4phBEjqEpJuAI6FbKBR0bmowGOy66bmG+P54PK5TMpVSmJqaQjwe75ijQZiDQqGAcrmMtbU1LC8vo16vY2xsTO9d895Jy5fj93g8Ok7AfUEDRypnme9PT1N62jTWDg8PcePGDcRiMc2D87sfBgNh6QLoMPeplZjeUSwWkclkdDoVcLx5WPRQqVRQKpU6uD5T4MqJ7xVI+u/v72NnZwfBYFALXRNcJNIlkpwuAM1BUbBwvqTlzMdzuRx2d3fx/vvv46233oLL5cLTTz89EJuLYIWRUkqncMn8WVq03BimsAVw4jEWzPD9UnHJzTboMGk4Uiu2bSORSJxa4GMGygKBAMLhsM5ljsViHUL3LFKjHhS2baNSqSCfzyOdTmN7exvhcBjhcLjjnjkFlAlJL9ASNgWzmQ0k14RME6PsYA79c88991iKJQbC0iUYFGGZL6tGmEYVj8d1wC0YDOqI5MjISEd5rBS6dDdt20Y0GtUuSq+wubmJW7duYXt7+8RzspyVN9tJk7KKhouBm00KG2kZSveLJdErKyuPNQL7qODYGRSl2ycVhxQEtNrkNVOwcr74OGkqQpaKyzzPQYZ0+alwWq2Wrsq8H0hPKhQKIRqNQinVUfI6SMFFy7Jw584drK2tIZ1OdwTR+bypcAmZn2saZk773bxeOdf8n4K33W6jXq8jnU5jeXkZiUQCk5OTv3qWrhNcLhfC4TCCwSBKpVJHrTyFLfN1U6kUwuGw3qC0AqSW4+NcZMFgEIFAoGdC17ZtbWkeHh6e4NEocLmpTLpBvrbdbne4mgBOUA3m37SSK5UKNjY2kMvlBmJzAcc8NZPTpQUhrXUAHVaq/E1aRSpYPs7giHwPM196VXn0sDAtd5nX3S2R/16gpcsc127rpp+wbRtbW1tYWlpCLpfryKc9TTFIy9W8rtO8IsLJapWWb6PRQK1WQyaTwfr6Otrtdkd84UExUEKXlm6lUsHh4SGKxWJHExQGWKR2LhaLOpeXRLmkEiiEmYzfa063VCphd3dX18YDzjfe6TnzpjoF2E4DX1er1ZDNZlEq9f9AVAqNbDaLYrGIcrmMcDgMv99/ggKiAJYWLgMnQKc1QlqGnoJJL5HntywLpVIJd+7c0WlE5JNJa/Xbze6Gh7VKlTrKUw8Gg1romr0mBgG2fVT4wdiMNEy6vR5Ax1oBcEJ5876ar+XfMlvI/Hyp4KrVKtLp9Am640ExcEI3Fot1WLm0AOPxOPx+v87X5EUXCgVks1m9sdhlTH6mWe3USzB4USqVHF0Y3vT7gXmjzYCahLSa6/U6crmc7rbWT5BOSKfT2NnZQavVQjgc1jmTpsCl0ASOG5BIN1JaxNJjkHQNk+A5z6w8ikajel0x24XfNSiCyMTDcNIUuqFQCJlMRtMs5mv6Ddu2da45M1PMINdpWTCSlqOwZdqgFLRmLEReu6S1JCUFQHe8S6VSj3SdAyV0geP0MSYn84Kj0aj+n/m8pA2cXG/TVe9XO0fZrpKQObn3woNsBpO+INg2Uo6hlyBXx7xklmqyfJOCjv0jTKu10Wjo10lPhxFqWsIyVUzOhcxvJuXELmWkpLxery4lj0QiOhm+HzDXrhQQ3YRuN++Iz9XrdV00ZFq68v39RqFQwOHhoQ6ukhZiLrfTfSXnWiqVOrwi5nPTy+G+Y9oprd9utJSkLGz7qLKtXC5rK/xhMVBCl+kdDJCxlpwbCzjKySTHwj67rVarw0IxtZUZbOolSJVUq9UOS5fJ+qcpA6m9gc6oPp/vBsmRspsXyyJ7Dcuy8N5772FpaUk/xupAFrdYlqUDagxeyObUrFAzsxiopOmaNhoNXZEmuWAqZrfbrYtr2HdXpld5vV7MzMxgcnKyr+XiEtwXJk99v7BtW1dv1mo1rfjk84MA27Z1xk21Wu24Xtk3Qu4LCtxarYZisagVJwPo7XZbC1020GLPFlanmVQUZYqUGxS6pDMfBQMldFutlq4tZ808J4Q8HheNbduaSmAFE10S6aZy4ph6ZNIPZw22jnNqI2gGCE4LoJk5qObrzPfTA5CR715b+zL7hIuVPTb8fr9uYCQDilKYSqFJJcLXmhtRKhcZPDOFNK1nr9d7Ijez3W7rApx+CyKpoE0PhuXAwWCwa1m7+R6euECPZxDbWnIPU1iaa4D3TuZcA9DVrBSuAHQDJNnBjnvA7/cjGo3qOILkjbnGzLRF4LhpvFM5/oNgoIRus9nE9vY2dnZ2tCssLR+6DOwrQIHLvqsMVnm9Xr1YuUHpYvc6WCKbrkjLqVuQQCoK/pYpM3xcuqBmmgutRPn6fghdy7KQTqeRyWR0ocbY2BhGR0c1v85Fz3FKJUSXkHNHekGmT8nWf3QbOV+MfMvUI36O3+/XOcIMpBWLRZ2c32+hCzgHVS3L0txiLBbT2Tincfq0IPf29nScZFDT5iqVCgqFAizL0tkpADoEJi1gXkMoFNItQZkSx74Jtm1rWVGv1+FyuRCJRLTiHRkZ0YI0EAhoeoL0BoWwy+XSaakypvQwGBihy4VQKpVQKpVOaDhGvWX6VzAY1DmscnJksjcf42f0Ml2Iwo59Isyk7nsFRe7F052mOEyapR+gkiwWi3C5XB2LmmOUeZh8TAbGpAcjH2Mjb34evSFuFlIOkp+TXo8UyEopHUSjQKfS79cJI07geX/FYlEf6TQxMdERYOqW2UJKzgw2y6DjIIBGkTQS5D3nY6QVgsEg4vE4pqam8Pzzz8O2j9qkAujoVMh7DgDnz5/HyMgIKpUK6vU63nrrLZ3ZY8aGgGPFxerJvxeWLgVirVbDwcEBDg8Pdd9buuayRR8ATE5O6sgzG6VwoUnBSj6MXA6piF5ausVisaNgg9ZdNwXAm/yw2tTkgvsFboDd3V2dDsjadnobDOzQ2qTAlNYpLU9pEbfbbRSLRR2h5v2lN8Mm+MBxhzfplrJTWbPZhN/vRyAQ0IE4nrgQCoUGKoVMHrr4/vvvY3Z2FpcuXerwFgjTe6AxMz8/j0QioTuTMV4yKNfILAvZKYz7gX1xgeMTJCKRCGZnZ3H16lX83u/9HhqNBpaXlxEOh/HSSy8BOGoElc1m8d5778G2bXz605/G1NQUlpeXsbOzg52dHdy+fRuJROJEhoOkKWW3tl95S5cbiHm5wMkuSDLNQ6aA0RJyu936JAJycuQBgWMrgcGYXkJ+n+lCP8pndbOA+ZwsgwSgeVBzbs8CtDoBnHAT2ctU5k96PB7NyfHemRVFvDYz3cecT0bqpVXCAK3P50OtVtOBEpnLLU+ioFfUSwVtQlJHAHRVZigU0lV28rXyPea9B47ufzgcRjKZ1FagmSnRb/C+OykRk/Onp1QsFrG/v4/l5WU0m01sbGzoHGwAuHv3Lkqlkq4KvXXrFvb29rCxsYFMJoNqtdqhXCX1ZwayedrEo2AghG69XsfGxga2trY6juKQgpWFDzwdgSfgZjIZFItFhEIhxONxZLNZHUwht+NyuXQWAXnBXuXrmvwscGyJm5vKhLkI7iWoTa4XOA4qAsepY6zMOyvYtq1Twkgr8Nh4CkMeL08X3+v1ap6VKV08jsnM0aQlTGHOjcrX5fN52LathSvLv2W1I9cIXW2WlQPQATUnC7LXkJs+EongiSee6Kjkc6KgTEpJBhOnp6dx6dIlnRlkvrffoLJ2CvRJYajUUQ/der2O9fV1na/P4hcA+Ou//mu9DigPlFK4desWvF6vXh/pdFq3dOTcMiNK7idauo+a7z4QQrfdPjpssFAo6LQdM8jECL5Mg2IObDqdRiKR0IstFouhXq9rvo/uJE8F7sVGkgvdfPxhcC8B7fT9fB/B6CsF4VnBsiwUi8WOQyDL5bJO6ZHNp8vlsubwpRdj8q5c8AA0JUFagZQCI9t0PXnUOrNWEokEIpHIiZxlpgKRwmo0Glrg9xNUPKVSCblcDmtra1qheDweZLNZLC8v62uWFiDnMxgMaiXCUxUYcHK5XBgfH9c9pvtJSVHZklJyyiM2i2M4Xt6/7e1t2Lat762Z/cL3NRqNjnQ0eXIJvRtmDck9xyDsoxpsAyF0a7UaVldXsbe3p60OeTYS3WG6lIzGVqtVnf85Pz+Pubk5TExM4PLly7rqiBUuxWIRv/jFL9BoNPD888+feYBEHnxngspDKgUJ07KVlTZOgrdbChmf4/Plchmbm5uYmJhAPB4/Mwun1Wrhzp07OvWPnsze3h4mJiYQjUY1zbG+vo733nsP1WoV1WoVyWQSc3NzHV4NrdO9vT0Ax8EtfsbW1pbeEJFIBJVKRd//arWKTCaDSqWC2dlZTExMdHC8brcb9Xod+/v7KBQKKBaLCAQCyOVyJzZdr5HP57Gzs4P33nsPP/jBD1Aul/VZeGNjY9je3sbPfvYzfbw4PTi3241gMAifz4fLly8jFAphY2MDlUoFN27cwJtvvolMJoNyuYxPfepT+M3f/E2dcgWcHsA9C9AapZKmUJSQ1j3vOw+mJJe/s7PTsWcYuCWFRcEti2hcLpdeZ/wOKlwaetxDbJT/qAbLQAjddvvoZAhaMrLHghnpJ/dCDUWrhNaAz+fTJwiwNJiTn8vlenYEu2zkIiG5oge1XoGTgvc0S9rcNLQ4GQg4y00lm9IzTYf9j+PxuM6TrFar2Nra6uB05YkfvIcyzYnPcTNRGbNBPZU1XUa6ipVKBevr69raTiQSGB0d1Rwwj203DwbtF8hDbm9vY2trq8MCJzXDdqaFQqGjN4eT0qrX6/pk3Gw2i0qlgv39fRwcHOhjaYheX7+kFSRvy7+Z4iUPOgBwgnqS7zE9Tae8eEnh0csmbcFUVRlofBzz0nOh66RFq9UqlpaWkMlktGaii0FLkYuNm5luCBcaG+Ssra3h5s2bmJ2dxZNPPolYLIZUKoVSqYTbt29r3vCswUMza7Vax/UyCmrm7DrxtyZHKwUu/zYXDnCyaxKfL5VK2NzchN/vP1PF43a7MTc3h0QigdXVVRSLRbz00kvw+/26eTbv7+HhId544w38+q//Ol5//XV9sCQ5OL/fj3g8DgAnumPRzePjdJvpYZgnuX71q1/FG2+8genpaUxNTeH69et49dVXsbe3h5WVFViWhZmZGZ3N0O90sbW1NXzrW9/C6uoqbt68qRUQMzNarRby+TwA6D3B3gpUrO+++y7cbjcmJiY0tUYXvNVq4Z133oFSCs899xwSiQSA3gtcZlewXJ60EY0rFjiwNaXkXs30Nxpj/M3UQkIphUgkojNcRkZGNLcbi8UwNjamDTdmfAAnqYpHQV8tXU4Izz2r1Wq6wsbkdE0NJRt90/qVfAwnjG6WvCG0as7SomFOsbwGqYUfxtK81/vuRT9wvs+aq1RKaReMPTNGR0f1QZmRSESfDRcIBBAIBHSgi14JrQwpNGVdPa05Sc+QgqKiptBlEjyr4Ci42O6wWq3qht70puS66Req1SoODw/1oaVSEFGg0OqjBRgKhbRFLNcgeW2Zs8yUTDaR6ieYMmpap1TOfr8fiUQCtVoNtVrNMXBoWr/M0KBc4F7kOuEPn+MhqJZl6Qq2fD7fQWs+DmOlbwdTAkCxWMTNmzdx9+5dffAkK06oreSZX8Bx8jT7LgQCAUxNTWnXhLmXW1tbyGazGB8fx5UrV6CUwtjYGDweD5aWljA2NobLly+fiTVj27ZOYpeJ1HSDmFXBuThtY8trl79N69Z0f/ga6Ro1m00Ui8Uz7zbmcrmQSCS05UCuUQbLmC/9wgsv4Hd+53cwOzuLVCqlLQ+CFVicN0LOHwMw/GyuHZaL081eXFxEMpnU5+ldvHgRHo9Hn/4q55Ibt9dd6SRKpRI2NjYAABcuXABw3EOZlvzk5KRWKEybbLVauH37dkc+KQ0TNgPiYZ7NZhN3797FU0891bfrtCxLB8RlCbZSShdMLS4u4qWXXsLy8jLefvttx4IX7pVGo4FYLIZf+7Vf0xSTbR+3gV1aWtIWLD2jWq2GeDyOCxcu6KypH/3oR9jc3Hzsa6DnQlcKGEZT2VzbTHNx0ircDDLnlIKTgpdaiQJYHnDISGcwGDxTF5v5qDLv2LR0HwROr++WLmRaxHJhku88a16bArDbybscWzAYxOjoqO6X7EShdOtR4TSXUjHJ8ul2u62tJR5ySK+KlNWggJYqy1O9Xm9Heh3T5VhQInnwUCikr1XeZ1rE8gBGZpGwGZKs7uw1uFdN6o/XEA6HMTMzo4Nl8r47ecQul0s3VGI2jNlfQX4HvQYecsuK126ZFI+CvtILhUIBP/vZz5DL5XTuqIwYAjhR1suKlVwuh1Kp1OFK8sflcmFsbAzT09OwLEsnRZOW4PedZS+CarWKfD7fUVUje0iYfKzkqiQdwDmQQtt0deRnUbEwAsyKLaBT6PYa3SLi5B/5IwWMdDWlwGDAx8xukaW8DKRSaNN9LZfLSCaTmJ6e1mfV8bv4OU7j7CWWl5dx69Yt3Lp1C81m80QAKRQK6VN+KYQ4XgpPBqboYTCvXQo1mSWQzWaxubmJWCymud1egdkLTNlzEmoLCwu4fv069vb2kM/n9XpwohkAaIqRXpZt27qazPwOCmQGVBcWFrCwsIA333yzYwymdf2w6IvQpXvEdJ5SqaS7A3W7INPl42dwImRaFW8IO+WTA3K5XDqCS9fzrMDUE6eqIadruxecaARz/GZGhJNF/TgCAY8bHKfMWHGKFvNxp0ChGXCUCorfIXstmEexPwzHflbI5XK4e/euPoiV1yyv0awo4/PyOCLZQU16iE5WHlsjygyGXsIs9jARCoWQSqUQDAZ1OpfTKTCcD6aGseTcsiwdN3KykPl/q9WCz+fTR66bVvXj4HXPROg6WXIS5KpWVlawt7enORguGhko4OaRTVBkAI7mP90TLqJCoYC1tTW9yILBIKamphCNRpHJZM7c0pXHjgCdSsOcp26LoJubJzeayffK583NKhsG9RrdBBo7fvl8Pt2MhfMkK874GZLblTw/+T/p+XAtmaW+/AxZGeh0ikC/sLy8jK9//eu6r7TsHZLP5+HxeJDL5To6tVFAFItFtFotHQgkmFLG+z8xMYFIJKLphZ2dHbz//vt44oknMDU11dPrlZau7GMr124wGNSZBey7bCoIvo+512+//faJ56XRIT0cl+uoWX6pVEI0GsXCwgIikYju68L3Pw5PsS+WbqPRwMHBgT6Wg8KS1EI3C1ambVCjU4Dwb048c1JlZJd5oGfd0Js3RxZHcGPzJvN1jzqGBxES5qIbBNAroRvsdL+dLFbzM0zhbAYUJUXDx+X7B6VBkGVZKBQK2N7eRjKZ1BQIjQ25X5hrLK+L+8KMzDOwKJu2ezwe7Y2Vy2UcHBxgZmamL9cuD910AukjUnTSynXy+BickzJB5nfLgglJ/TFAGQ6HdXaM/HkcFMOZCN17WQzpdBrf/va3kclkOpLoGfwiJyddarpTfF2pVMLh4WEH18XILDcyOSy2tJMnTpx1o2ozkCZdPBOmUJE3mO/lc5wPOb9cBFJJ8fVygTASLLMDzhrduFyCQSC6+07BRr6XdIBJ2ZhelZP1Dxw3yqF1OAiCVmJ/f1/TbZFIRKfd2baty9qTyaSuvopEIkilUnC5XLroh/uJwoEZDBTetCTZZYyCaG1tDSsrKwgGg3j55Zd7et22besKQhoptEJP88q6rSnJfXfb49K7lLEPpq2Zr5VKjwaeud/uF49N6Dq5t06vYRXN+vq6PsJGnovVzRKTie60dGVzHEZx+XkUugwocNKklXyWQtcpEPS4Xdd78bfymgF0LJqzvPYHAe+XdCXNACFf5zRmUk8mBeU0H5Lvld83KKC12Ww2O05BoOdEGoFGA/OPlVK60kymSEmhy0IArgV55pjb7UY2m8Xh4SEymYzmTHullGhsycIIUw486L2SckV+j5OlKoPY3QS9fJ/MIumr0JXoJoD39/extLSE27dv68beTNuRictM3QCgX0e+j3XTHo8HyWQSyWQS8XgciUQC8XhcH7XOJHhyq7Q8mSBvHl74uMGcYV6XkyCQQkBCcrZOj5vBJRNOLhExaNkLkv4Bjq12bhonOI2fa0iuJVMh8TNNIT8oYJ+FdDqtjRB5v0iRxWIxnD9/viOtiYYFhWgkEgEATeGNjo7q17bbbZ0twvQoxkTS6TS+853vYH5+Hk8++WTP5ojN7qXQZbkzO9IR92swSCqPQlWC/1N5sT7AhNxHpA3pWYTD4Qeeo8cmdE2NYkaDSfKvrq5id3dXuxHkVujuc6NRy3MxMSeR1Wts75hMJpFKpTAxMYHx8XFdaUS3tVgsYm9vD8ViETs7O9oiPuukd1qZMl2rm7C4n8ceBmaK2CByuiaXyzGedn+clDrXWzdqQXJ3g0YrAEfjI5drnpxCj4njZ74xDRG+Tp4Zx2IIaSUHAgHt5TF1SimlK/SCwSCKxSKWl5fh9Xrx5JNP9uzaG42GPuFBPs6xPWwRkxmkloaP5Hopb+T+cLKSqeDq9fpDZ3o8tNA9jU4wLbJMJoPNzU2srq7i1q1bqFQqCIfD2q0wLTOgs/JG5mKOjo7CsizMzc3p0wiobai9mTXAJh9sCsLUMUbLz1KL07KWN1huHsldmtpYWoX3o9UZMDGr3EzBD9z7iKDHjXvN8Wmt/GRk2eSw+dnyWkwO3Imy4Of10n2+X+zu7uLWrVsol8s655gGByvtNjc3ARz3FGZlFYsiZDtIFqeEw+GOIDJP6mBevCwM2N7exubmJjweDz72sY/1xNK1bVt3CmOBBvd9NBpFKpXSlrv5PsC5IEjCXCemxylljozDhMNhTE5OduwjzjHblD5Mt76HErrd+LJu/xeLRayvr+tG5eSeZGMO0xU2+RMAutzR4/EgFovpggpatCy7rdVq+iA+ThI3WygU6hm9YLrynDN5QgHQ2bqRUVQ5j06LydTCkqqQ0XzZWrIb19lvOCkC3nupwM0gmxlglJ/XLVthUDld27aRz+exubkJn8/XsT5ZQUaDYmTk6KRbpY4PYWTQmEKjVqt1FAwxJkJDhkJW0jFerxe7u7vY3NzE4uLiQ3OWDwNaupKjb7Va2qp3aqdoejfdHjPvM9eW+T6T72WzJUl9MJb0KGelPZTQlRvA6aJYgLCzs4O7d+9ia2sLS0tL+iRSFilwgiWBTWuQfChPBp2amkIwGEQsFtNd39kMpFarIZVKIZlM6rHVajXs7u4iEAgglUp1CPheWboyZUy6iacJPalknDS5/N9pMUnldVowaRBgLnSuBcnrSsFjXo/M45bXJBve83XAcToVgBPZC/2ck62tLRweHuqe0slkUgsZU3HyhAvmtfOcOEbquYd4lhrplEKhgHq9jmq12hFIpYBmwRD/Pjw8xMrKCuLxOMbHx890fjhuWRzBNRCJRDA1NdVx0oX53kcZm9wzslEQAH3+2u7uLgqFgv4ucuIPm3b6SJxut4tlkvHa2hp+8pOfYHt7G6urq/B4PAiFQjpnUPJrFEhcNBRY5XJZWzw+n08fvbKxsYFCoYCdnR3s7+/D5/Ph3LlzOsl+bW0Nh4eHSCaTCIVCupYdQM8sXemqSCveRDfhacLMQ+02ftNrML9rEISuXOyyDyyf429ptZuWrbR4TcvG5HdpOcmOZINAL9i2jXQ6jbW1Nezs7CCbzWq6TCpP3ku/369L4VutFrLZrD4hgoq91WrpaizpCcp0JwC6IAA49sxYQp7L5bC5uQnLspBKpc58zZBikkKXgfZkMqlT3LrhfoWf0+vkOpRCl1VwPLtR0hCP0q3vkYQu+8XydIBSqYRKpYJsNqtPgd3e3tYHQpq10jLazAunBcKzsXiUSCgUgtvtxsbGhj7raGRkBM8//zwSiQTm5uYwNzenuaGxsTGMjY3p1oL8YaPqQCBw5pau2cRcur1m8rqESRMQFC7yOckDm48zdc5slzcIcErPcbrmB7lHUmhTyJqPcb6deOR+wLZtrK+v4+c//zny+bzmX8m1SqXN9EfukZGREW0RVyoVfc1SsFL5K6V0JhBhUjBKKd37olQq4ebNm2g2m7h8+XJP5kGmYnE8yWQSFy9edOwHwYCrEzXl9L/0FGWbUDN+xNdFo1HMzMzg4OCgY2zmYQAPikcKpOXzeeTzeaTTaRQKBezu7uLg4AAHBwdIp9N6gCTznSwwk2ekkOBiIJ1ALvb9999HOp3G/Pw8xsfH8dxzz+GFF17Q7fp2d3exvr6O0dFRnSbDz2QGBIXuWVk6Tu6ShCyUcOJZee1mnqGZPyhTn8z3s4yWjzsdG9RPcH6khfO4ha7pGciN041v77UXQKH79ttvdwhdye3LPGuWwCqlOjISaNma3pUsk+e6kOte5r+z8xiF7jvvvKM907PO9pGK0BS6CwsLJ4Su3Df3umem0OVn83tNr8sUunfu3DlBfZLWfBjct9Btt9s6nWVzc7PDXWGfykKhoNvRkTOVnBwHzuikbdvanGcTZafyPACav718+TKuXr2Kubk5rQXD4XBHXi/5K343byYtXbb2O2ueit/Jajo5l7KnrnwPf9+LXpCvN0GLxUwjkpxpv9FoNFCpVLryYqYAlgEfKVTMgKJ8vzmPFCwyZmB+Zz/QarV0ea78XwZWmZVQr9fhdrt1iTtpAfYhMQWMSd3I8lm+n5Dl+EB/miNx3DRIwuEwxsfHEQqFHF/rRDsRTrSdfNzpvVIhRaNRzM7OIh6Pay+d3C+F7plauq1WCysrK9je3sb3v/99XRsu2/BJvozcqt/v141IuJg4WfyfmgM4TlTm5/BCDw4O4HK58PLLL2NxcRFzc3MYHR09Mc56vY5cLqcbdcsAFoUuz+g6S05P0iXsbSrHQih1fNYX30dQC3Ne+ZxpAfNz5NlgPp+v4/voPdyrtLJXYII50+qcNozZBUxafk7ctLlxOOf8X/b3Nev8+8lzM4BFfpUKiVanbNVIYcxTnSkEeMS9TPSXHpAUnrK5j7km5FFS/aRgeP/j8Timp6c7shdOi2WYnyGfk1lQfMzkkuW6SiQSuHDhAsbHxztOM+GZdGdGL9i2jUqlog/J29raAnBEMjtpGMk98YfamLyJaX1IV9sEgwSXL1/G2NgYLl26hGQyqa1H6WKYN0N+thRK5FN7YelyHLw2pgPJs96YFG5ZlhbS0iUul8v68xhQZM6lWbFElyyVSmFra0uXWtPCoyfSbzSbTVSrVe35yBJvoHP+nBQMXwMcC+FuStRcG1yPg6B8TJhKl6caMKBMoUrqip6izPqQfQHk2uBvej9yXXL+nDjwXsBs7cj96fV69akYfJyNb+R8nTbOBwmySUXDIB5lHb+TdA57ZT8o7il02+02stksMpkMlpeXsb6+rrsbddsYXChOQo0aTPZJoBVq23aHlUKLORKJ4FOf+hQWFxdPbCAuNlOISlrD7XZ3nDbai5MCaGXyGikYeE4Yeep8Pq+joyxTljeZTWpomZHH83q9ulcwBQh579nZWTz99NOwrKMG7lJL5/N5HXTpJ9i/laeuUhHyftJKc1KmJh93P6lf5poxex0PCqQRQUEkj16PRCI666DZbCKfz3fMk4yHSHpAClQ5X/Jxc+56RS/QWpfNZigjAoFAR+YC6TrT0HOCOfZuSplzLtvDAkAsFtNHTsmKuHq9jnw+r42hB8U9ha5lHbVIY5UXLRP2K5W8mrn4u2lLadVQ2LLCpt1u6+TvYDCIq1evYmJiAqOjo46TZm5KKgLJ/5mFCr2wdAkKewrGZDKJq1evIhqNYmJiApVKBZlMpoN3JvUxMjKiOXM519T0jEbTPWThyPnz5zE9PY2VlRVN7dBSCAQCA3E0Dd00ALriyrSyZCaGtGTNzSStHSlA5HvlOpFBvEGBtDhZqi4zEfhDwVqpVDTPC6Cj7Fd+phPHa1mW3m/8TqfqvV6WTMtAGhWpUwGLvI9yzrrFBUyPkzBfz73ndM2mwiaHfmb0Ag95ZIIwOxDJI1M4UG4etlGUncDMmy9TnWSBRKlUglIKqVQKqVQKn/nMZ7CwsODYJZ6fY94IGaWmBckFzAXX7fMeN1gyyA1w4cIFfPazn8W5c+c6DgOU88NUJxlUJNrtNg4ODtBoNJBKpU4E6YDj4pR33nmnI8I6MTGhK/n6yWECx8UjAHTJq1PrRsk/SpdS/sjHpddDhW4qZhlLGARIJcN9xG5iPIC1Vqvp+anX69jb29PXLhUNlbUT5BzI98lAq7kve7VOpHLhd8tGSHLcPFCTHpF5bd1AysUpAEtvm5kjEtJbBY7m/+DgQHsZD4p7Sh7myEajUd0ejq6ZPN2Bi0WmfMlNJFMz6Prwf5nGww5iFy9exOzsLKLR6ANbZvxc3kTeHHYeI6d6VgtKKYX5+Xlcu3YNh4eH2Nvb0xZvNBrVzamdGmbIhcRFLxWEDKp5PB5Hocu5P3fuHK5du6bv1Uc+8hE8++yzWFhY6HthANOfJIdrekXmGE+zZvi8+b+EUzHOIIDNmdgrhA2d6vW6Fi7SyudaAqCLfOghOtExwMlsENOylVYh12AvC0jYqEpWEzo1pjKt8tPwoPc3GAzqY3okKOhlg/hYLPbQGVD3FLoejwfnzp1DJBLBzZs3USwWkc/ndcK1hDw6RHb6Yq2y3Fim+88yxHK5jHA4jE9+8pN44okn7tldyJxYfj4tKXJjLpcLsVgM0WgU4XD4nhUujwKXy4Xr16/jlVdegVJKH6JZLBYxOzuLCxcunFrWKBvlMJAoNx2PUR8fH3f8DCqWV155BeFwWN+zZ555Bq+99lpHhki/wECaPHpHCkLTvZTZC4RJR3TjH3mt3MjAcaOdQQDL29nAnNkIFKIsFOLf3CfyemQQyslS4488PYGfKXlUaRjRAzlrcG+OjY1hZ2dHG148ydd8LU/3dbp/TrTBvcDXTExM4IknnjiRE+z1ehEOh3WaWCgUwsWLFzE9Pf1QSumeM8rgD88NCgQCyGQyqFarOuWH/BIDVSyzZYqObCROLUYzn4ufeatPPfUUpqamEI/H77t1mnQX2GOBZcT8kZq8Fy4TKYyFhQW8+OKLmgO6ePHiqbyqzGbgJpEnCEiP4DRNrpRCIpHAuXPnUKlUUC6XMT093ZF10k+wwQnXDBUzr0tuFtOFdMpeYOYH15cUNNKtpELm+wYB0mOp1+t6v8nYBMdsBq8pRCl0aQkTMmtB0gmMzfAz5J7kfeB39AL0pmVAvVvchXvZTKW8XyEs1wIfs21bp+iZBgkFPQ04vvZhFdI938V2iolEAp/97GfRbDaxvb2tj0/f3NzExsYGDg8Psb+/j1wup4M3Xq+34wBI3lj2wuVE0dKdn5/HF77wBZw7d04fL/KgqFQqSKfT2mrgOIDjE0J75VYqpfCJT3wC169f1zfZ5/OdKEGWAQE2CpKZClyMrI2v1WodyfTdMD8/j+np6Q7OahAELnB0cGc6ndZrRTYUkULTpBxMukVayKy6kpwuNwyFrWy5yYKcfkKpo2NlmHOez+cxPz+PK1euoFgsIpfL6SwPAB1Wqc/nQzweR6vVwu7uLgCciFeYfC9ddipy5tJzvuv1Ovb39xGPxx2D4GeBkZERzMzMoFgs4ubNm7ovizw9g6Aw5l6W6WOS6pQwA26m4UWai42ETHqBVne5XNaZRGYnuAfBfYlqXgQFKFOeZmZmtBDlyQ2FQgGVSgXValWX3ZqugMfj0S4zNYjP58PMzAzGx8d1v4X7gVN0k2NicxCfz6c3Xa/d6kAg4NiWrhuchAZwsuzRvG4zOAB0NnXpd+DMBFMByVmymOa0TQPgxPNmehnQWdoq09EYbyB6FUy9F6LRKCYnJzXPbybvcy0wFhEIBLQnyDkkXcV9JblbwlRoQGfzc84Xhfns7CxGR0fPfO3Qm45EIvoaaZyY92hsbAxXrlzBzs6OvvcyE8MJTnSLpGY4d8lkElNTUyeq3zgf5XJZU6ehUKh3TcxdLheSySRGR0cxOTnZkY7F3MF0Oo2DgwPdfKZSqejMB5nmFAwGMTs7i0QigWvXriEcDj+QwCXkoggEAhgbG0OpVML58+c7Ops1m00kEomeW3um0DgNXq8X4+Pj+j0ymMDHvF6vdg/Nz5ffKYMiZgZAvzE/Pw+/338iak1XTyb7S8jXmO+h4KXgMqPzzKqhleLUREVaRb2Ay+XCs88+i3PnzuHg4EDnwTMOQM+MKZRcv7T42W2MzbYZIDbHz4Ay9x+D4KR3SAVGo1FcunQJH/3oR/H5z39ee1lnCaWOCnoqlQpSqRTi8TgmJiYcBeCrr76Ky5cv48c//jH+8i//EsViUZ/rxiwl1gjQCpY8PudcKYVgMKgbkYfDYbz++ut47bXXThhJExMTeO655xCPx9Fut7GwsIDz589jYmLibDhdJ9ByMHlJCmDe8HK5rAsRGCCitcHzj8LhMGKxGMbHxx/IIuwGLkZa2bR6aA31Q+CaguO0Dc3xmyWL/Kz7+QwzpUq+x8ki7gfoicjqMPJlFKZOTXrMgJuMvMvgo5w3mecr10a/g4kEix5Y9AIc89Sy2o57jlY7rV9ZMGDmNBM0jChwXS5Xh9KSqVSkBWOx2Jl34wM6C6akh+KUMhYMBjE5OamzDEhFOGVumIaGVKjSC2LVWygU0sUnEpxf8riUaQ/rKalBSZsZYoghhvgwYDB8zSGGGGKIDwmGQneIIYYYoocYCt0hhhhiiB5iKHSHGGKIIXqIodAdYoghhughhkJ3iCGGGKKH+P8km+1qmhN7fQAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -3351,24 +3343,13 @@ }, { "cell_type": "code", - "execution_count": 124, + "execution_count": 123, "metadata": {}, "outputs": [], "source": [ - "tf.keras.backend.clear_session()\n", "tf.random.set_seed(42)\n", - "np.random.seed(42)\n", "\n", - "class Standardization(tf.keras.layers.Layer):\n", - " def adapt(self, data_sample):\n", - " self.means_ = np.mean(data_sample, axis=0, keepdims=True)\n", - " self.stds_ = np.std(data_sample, axis=0, keepdims=True)\n", - " def call(self, inputs):\n", - " return (inputs - self.means_) / (self.stds_ + tf.keras.backend.epsilon())\n", - "\n", - "standardization = Standardization(input_shape=[28, 28])\n", - "# or perhaps soon:\n", - "#standardization = tf.keras.layers.Normalization()\n", + "standardization = tf.keras.layers.Normalization(input_shape=[28, 28])\n", "\n", "sample_image_batches = train_set.take(100).map(lambda image, label: image)\n", "sample_images = np.concatenate(list(sample_image_batches.as_numpy_iterator()),\n", @@ -3387,25 +3368,79 @@ }, { "cell_type": "code", - "execution_count": 125, + "execution_count": 124, "metadata": {}, "outputs": [ { - "ename": "TypeError", - "evalue": "unsupported operand type(s) for +: 'PosixPath' and 'str'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m/var/folders/wy/h39t6kb11pnbb0pzhksd_fqh0000gq/T/ipykernel_76919/164425769.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mdatetime\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mlogs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mPath\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;34m\"my_logs\"\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;34m\"run_\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mdatetime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mnow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstrftime\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"%Y%m%d_%H%M%S\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m tensorboard_cb = tf.keras.callbacks.TensorBoard(\n", - "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for +: 'PosixPath' and 'str'" + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-02-20 15:30:49.689831: I tensorflow/core/profiler/lib/profiler_session.cc:110] Profiler session initializing.\n", + "2022-02-20 15:30:49.689858: I tensorflow/core/profiler/lib/profiler_session.cc:125] Profiler session started.\n", + "2022-02-20 15:30:49.691427: I tensorflow/core/profiler/lib/profiler_session.cc:143] Profiler session tear down.\n" ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + " 59/Unknown - 1s 3ms/step - loss: 0.9230 - accuracy: 0.6817" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-02-20 15:30:50.428921: I tensorflow/core/profiler/lib/profiler_session.cc:110] Profiler session initializing.\n", + "2022-02-20 15:30:50.428945: I tensorflow/core/profiler/lib/profiler_session.cc:125] Profiler session started.\n", + "2022-02-20 15:30:50.433359: I tensorflow/core/profiler/lib/profiler_session.cc:67] Profiler session collecting data.\n", + "2022-02-20 15:30:50.446608: I tensorflow/core/profiler/lib/profiler_session.cc:143] Profiler session tear down.\n", + "2022-02-20 15:30:50.461272: I tensorflow/core/profiler/rpc/client/save_profile.cc:136] Creating directory: my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50\n", + "\n", + "2022-02-20 15:30:50.465450: I tensorflow/core/profiler/rpc/client/save_profile.cc:142] Dumped gzipped tool data for trace.json.gz to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.trace.json.gz\n", + "2022-02-20 15:30:50.480245: I tensorflow/core/profiler/rpc/client/save_profile.cc:136] Creating directory: my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50\n", + "\n", + "2022-02-20 15:30:50.480582: I tensorflow/core/profiler/rpc/client/save_profile.cc:142] Dumped gzipped tool data for memory_profile.json.gz to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.memory_profile.json.gz\n", + "2022-02-20 15:30:50.482034: I tensorflow/core/profiler/rpc/client/capture_profile.cc:251] Creating directory: my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50\n", + "Dumped tool data for xplane.pb to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.xplane.pb\n", + "Dumped tool data for overview_page.pb to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.overview_page.pb\n", + "Dumped tool data for input_pipeline.pb to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.input_pipeline.pb\n", + "Dumped tool data for tensorflow_stats.pb to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.tensorflow_stats.pb\n", + "Dumped tool data for kernel_stats.pb to my_logs/run_/20220220_153049/plugins/profile/2022_02_20_15_30_50/kiwimac.kernel_stats.pb\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1719/1719 [==============================] - 5s 2ms/step - loss: 0.4437 - accuracy: 0.8402 - val_loss: 0.3649 - val_accuracy: 0.8682\n", + "Epoch 2/5\n", + "1719/1719 [==============================] - 4s 2ms/step - loss: 0.3333 - accuracy: 0.8775 - val_loss: 0.3346 - val_accuracy: 0.8790\n", + "Epoch 3/5\n", + "1719/1719 [==============================] - 4s 2ms/step - loss: 0.2970 - accuracy: 0.8905 - val_loss: 0.3235 - val_accuracy: 0.8866\n", + "Epoch 4/5\n", + "1719/1719 [==============================] - 4s 2ms/step - loss: 0.2723 - accuracy: 0.8995 - val_loss: 0.3308 - val_accuracy: 0.8888\n", + "Epoch 5/5\n", + "1719/1719 [==============================] - 4s 2ms/step - loss: 0.2534 - accuracy: 0.9047 - val_loss: 0.3174 - val_accuracy: 0.8916\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 124, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "from datetime import datetime\n", "\n", - "logs = Path() / \"my_logs\" / \"run_\" + datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n", + "logs = Path() / \"my_logs\" / \"run_\" / datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n", "\n", "tensorboard_cb = tf.keras.callbacks.TensorBoard(\n", " log_dir=logs, histogram_freq=1, profile_batch=10)\n", @@ -3416,12 +3451,47 @@ }, { "cell_type": "code", - "execution_count": 126, + "execution_count": 125, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The tensorboard extension is already loaded. To reload it, use:\n", + " %reload_ext tensorboard\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%load_ext tensorboard\n", - "%tensorboard --logdir=./my_logs --port=6006" + "%tensorboard --logdir=./my_logs" ] }, { @@ -3437,13 +3507,24 @@ }, { "cell_type": "code", - "execution_count": 127, + "execution_count": 126, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/Users/ageron/.keras/datasets/aclImdb')" + ] + }, + "execution_count": 126, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from pathlib import Path\n", "\n", - "root = \"http://ai.stanford.edu/~amaas/data/sentiment/\"\n", + "root = \"https://ai.stanford.edu/~amaas/data/sentiment/\"\n", "filename = \"aclImdb_v1.tar.gz\"\n", "filepath = tf.keras.utils.get_file(filename, root + filename, extract=True)\n", "path = Path(filepath).with_name(\"aclImdb\")\n", @@ -3459,7 +3540,7 @@ }, { "cell_type": "code", - "execution_count": 128, + "execution_count": 127, "metadata": {}, "outputs": [], "source": [ @@ -3482,18 +3563,74 @@ }, { "cell_type": "code", - "execution_count": 129, + "execution_count": 128, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/ageron/.keras/datasets/aclImdb/\n", + " test/\n", + " neg/\n", + " 0_2.txt\n", + " 10000_4.txt\n", + " 10001_1.txt\n", + " ...\n", + " pos/\n", + " 0_10.txt\n", + " 10000_7.txt\n", + " 10001_9.txt\n", + " ...\n", + " labeledBow.feat\n", + " urls_neg.txt\n", + " urls_pos.txt\n", + " train/\n", + " neg/\n", + " 0_3.txt\n", + " 10000_4.txt\n", + " 10001_4.txt\n", + " ...\n", + " pos/\n", + " 0_9.txt\n", + " 10000_8.txt\n", + " 10001_10.txt\n", + " ...\n", + " unsup/\n", + " 0_0.txt\n", + " 10000_0.txt\n", + " 10001_0.txt\n", + " ...\n", + " labeledBow.feat\n", + " unsupBow.feat\n", + " urls_neg.txt\n", + " ...\n", + " README\n", + " imdb.vocab\n", + " imdbEr.txt\n" + ] + } + ], "source": [ "tree(path)" ] }, { "cell_type": "code", - "execution_count": 130, + "execution_count": 129, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(12500, 12500, 12500, 12500)" + ] + }, + "execution_count": 129, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def review_paths(dirpath):\n", " return [str(path) for path in dirpath.glob(\"*.txt\")]\n", @@ -3516,7 +3653,7 @@ }, { "cell_type": "code", - "execution_count": 131, + "execution_count": 130, "metadata": {}, "outputs": [], "source": [ @@ -3545,7 +3682,7 @@ }, { "cell_type": "code", - "execution_count": 132, + "execution_count": 131, "metadata": {}, "outputs": [], "source": [ @@ -3563,9 +3700,25 @@ }, { "cell_type": "code", - "execution_count": 133, + "execution_count": 132, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tf.Tensor(b\"Working with one of the best Shakespeare sources, this film manages to be creditable to it's source, whilst still appealing to a wider audience.

Branagh steals the film from under Fishburne's nose, and there's a talented cast on good form.\", shape=(), dtype=string)\n", + "tf.Tensor(0, shape=(), dtype=int32)\n", + "\n", + "tf.Tensor(b'Well...tremors I, the original started off in 1990 and i found the movie quite enjoyable to watch. however, they proceeded to make tremors II and III. Trust me, those movies started going downhill right after they finished the first one, i mean, ass blasters??? Now, only God himself is capable of answering the question \"why in Gods name would they create another one of these dumpster dives of a movie?\" Tremors IV cannot be considered a bad movie, in fact it cannot be even considered an epitome of a bad movie, for it lives up to more than that. As i attempted to sit though it, i noticed that my eyes started to bleed, and i hoped profusely that the little girl from the ring would crawl through the TV and kill me. did they really think that dressing the people who had stared in the other movies up as though they we\\'re from the wild west would make the movie (with the exact same occurrences) any better? honestly, i would never suggest buying this movie, i mean, there are cheaper ways to find things that burn well.', shape=(), dtype=string)\n", + "tf.Tensor(0, shape=(), dtype=int32)\n", + "\n", + "tf.Tensor(b\"Ouch! This one was a bit painful to sit through. It has a cute and amusing premise, but it all goes to hell from there. Matthew Modine is almost always pedestrian and annoying, and he does not disappoint in this one. Deborah Kara Unger and John Neville turned in surprisingly decent performances. Alan Bates and Jennifer Tilly, among others, played it way over the top. I know that's the way the parts were written, and it's hard to blame actors, when the script and director have them do such schlock. If you're going to have outrageous characters, that's OK, but you gotta have good material to make it work. It didn't here. Run away screaming from this movie if at all possible.\", shape=(), dtype=string)\n", + "tf.Tensor(0, shape=(), dtype=int32)\n", + "\n" + ] + } + ], "source": [ "for X, y in imdb_dataset(train_pos, train_neg).take(3):\n", " print(X)\n", @@ -3575,9 +3728,17 @@ }, { "cell_type": "code", - "execution_count": 134, + "execution_count": 133, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "29.7 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], "source": [ "%timeit -r1 for X, y in imdb_dataset(train_pos, train_neg).repeat(10): pass" ] @@ -3598,7 +3759,7 @@ }, { "cell_type": "code", - "execution_count": 135, + "execution_count": 134, "metadata": {}, "outputs": [], "source": [ @@ -3614,9 +3775,17 @@ }, { "cell_type": "code", - "execution_count": 136, + "execution_count": 135, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "27.7 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], "source": [ "%timeit -r1 for X, y in imdb_dataset(train_pos, train_neg).repeat(10): pass" ] @@ -3630,22 +3799,31 @@ }, { "cell_type": "code", - "execution_count": 137, + "execution_count": 136, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "20.6 s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)\n" + ] + } + ], "source": [ "%timeit -r1 for X, y in imdb_dataset(train_pos, train_neg).cache().repeat(10): pass" ] }, { "cell_type": "code", - "execution_count": 138, + "execution_count": 137, "metadata": {}, "outputs": [], "source": [ "batch_size = 32\n", "\n", - "train_set = imdb_dataset(train_pos, train_neg).shuffle(25000).batch(batch_size).prefetch(1)\n", + "train_set = imdb_dataset(train_pos, train_neg).shuffle(25000, seed=42)\n", + "train_set = train_set.batch(batch_size).prefetch(1)\n", "valid_set = imdb_dataset(valid_pos, valid_neg).batch(batch_size).prefetch(1)\n", "test_set = imdb_dataset(test_pos, test_neg).batch(batch_size).prefetch(1)" ] @@ -3655,155 +3833,29 @@ "metadata": {}, "source": [ "### d.\n", - "_Exercise: Create a binary classification model, using a `TextVectorization` layer to preprocess each review. If the `TextVectorization` layer is not yet available (or if you like a challenge), try to create your own custom preprocessing layer: you can use the functions in the `tf.strings` package, for example `lower()` to make everything lowercase, `regex_replace()` to replace punctuation with spaces, and `split()` to split words on spaces. You should use a lookup table to output word indices, which must be prepared in the `adapt()` method._" + "_Exercise: Create a binary classification model, using a `TextVectorization` layer to preprocess each review._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's first write a function to preprocess the reviews, cropping them to 300 characters, converting them to lower case, then replacing `
` and all non-letter characters to spaces, splitting the reviews into words, and finally padding or cropping each review so it ends up with exactly `n_words` tokens:" + "Let's create a `TextVectorization` layer and adapt it to the full IMDB training set (if the training set did not fit in RAM, we could just use a smaller sample of the training set by calling `train_set.take(500)`). Let's use TF-IDF for now." ] }, { "cell_type": "code", - "execution_count": 139, + "execution_count": 138, "metadata": {}, "outputs": [], "source": [ - "def preprocess(X_batch, n_words=50):\n", - " shape = tf.shape(X_batch) * tf.constant([1, 0]) + tf.constant([0, n_words])\n", - " Z = tf.strings.substr(X_batch, 0, 300)\n", - " Z = tf.strings.lower(Z)\n", - " Z = tf.strings.regex_replace(Z, b\"\", b\" \")\n", - " Z = tf.strings.regex_replace(Z, b\"[^a-z]\", b\" \")\n", - " Z = tf.strings.split(Z)\n", - " return Z.to_tensor(shape=shape, default_value=b\"\")\n", - "\n", - "X_example = tf.constant([\"It's a great, great movie! I loved it.\", \"It was terrible, run away!!!\"])\n", - "preprocess(X_example)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's write a second utility function that will take a data sample with the same format as the output of the `preprocess()` function, and will output the list of the top `max_size` most frequent words, ensuring that the padding token is first:" - ] - }, - { - "cell_type": "code", - "execution_count": 140, - "metadata": {}, - "outputs": [], - "source": [ - "from collections import Counter\n", - "\n", - "def get_vocabulary(data_sample, max_size=1000):\n", - " preprocessed_reviews = preprocess(data_sample).numpy()\n", - " counter = Counter()\n", - " for words in preprocessed_reviews:\n", - " for word in words:\n", - " if word != b\"\":\n", - " counter[word] += 1\n", - " return [b\"\"] + [word for word, count in counter.most_common(max_size)]\n", - "\n", - "get_vocabulary(X_example)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we are ready to create the `TextVectorization` layer. Its constructor just saves the hyperparameters (`max_vocabulary_size` and `n_oov_buckets`). The `adapt()` method computes the vocabulary using the `get_vocabulary()` function, then it builds a `StaticVocabularyTable` (see Chapter 16 for more details). The `call()` method preprocesses the reviews to get a padded list of words for each review, then it uses the `StaticVocabularyTable` to lookup the index of each word in the vocabulary:" - ] - }, - { - "cell_type": "code", - "execution_count": 141, - "metadata": {}, - "outputs": [], - "source": [ - "class TextVectorization(tf.keras.layers.Layer):\n", - " def __init__(self, max_vocabulary_size=1000, n_oov_buckets=100, dtype=tf.string, **kwargs):\n", - " super().__init__(dtype=dtype, **kwargs)\n", - " self.max_vocabulary_size = max_vocabulary_size\n", - " self.n_oov_buckets = n_oov_buckets\n", - "\n", - " def adapt(self, data_sample):\n", - " self.vocab = get_vocabulary(data_sample, self.max_vocabulary_size)\n", - " words = tf.constant(self.vocab)\n", - " word_ids = tf.range(len(self.vocab), dtype=tf.int64)\n", - " vocab_init = tf.lookup.KeyValueTensorInitializer(words, word_ids)\n", - " self.table = tf.lookup.StaticVocabularyTable(vocab_init, self.n_oov_buckets)\n", - " \n", - " def call(self, inputs):\n", - " preprocessed_inputs = preprocess(inputs)\n", - " return self.table.lookup(preprocessed_inputs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's try it on our small `X_example` we defined earlier:" - ] - }, - { - "cell_type": "code", - "execution_count": 142, - "metadata": {}, - "outputs": [], - "source": [ - "text_vectorization = TextVectorization()\n", - "\n", - "text_vectorization.adapt(X_example)\n", - "text_vectorization(X_example)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Looks good! As you can see, each review was cleaned up and tokenized, then each word was encoded as its index in the vocabulary (all the 0s correspond to the `` tokens).\n", - "\n", - "Now let's create another `TextVectorization` layer and let's adapt it to the full IMDB training set (if the training set did not fit in RAM, we could just use a smaller sample of the training set by calling `train_set.take(500)`):" - ] - }, - { - "cell_type": "code", - "execution_count": 143, - "metadata": {}, - "outputs": [], - "source": [ - "max_vocabulary_size = 1000\n", - "n_oov_buckets = 100\n", - "\n", - "sample_review_batches = train_set.map(lambda review, label: review)\n", - "sample_reviews = np.concatenate(list(sample_review_batches.as_numpy_iterator()),\n", - " axis=0)\n", - "\n", - "text_vectorization = TextVectorization(max_vocabulary_size, n_oov_buckets,\n", - " input_shape=[])\n", + "max_tokens = 1000\n", + "sample_reviews = train_set.map(lambda review, label: review)\n", + "text_vectorization = tf.keras.layers.TextVectorization(\n", + " max_tokens=max_tokens, output_mode=\"tf_idf\")\n", "text_vectorization.adapt(sample_reviews)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's run it on the same `X_example`, just to make sure the word IDs are larger now, since the vocabulary is bigger:" - ] - }, - { - "cell_type": "code", - "execution_count": 144, - "metadata": {}, - "outputs": [], - "source": [ - "text_vectorization(X_example)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -3813,11 +3865,22 @@ }, { "cell_type": "code", - "execution_count": 145, + "execution_count": 139, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "['[UNK]', 'the', 'and', 'a', 'of', 'to', 'is', 'in', 'it', 'i']" + ] + }, + "execution_count": 139, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "text_vectorization.vocab[:10]" + "text_vectorization.get_vocabulary()[:10]" ] }, { @@ -3827,79 +3890,6 @@ "These are the most common words in the reviews." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now to build our model we will need to encode all these word IDs somehow. One approach is to create bags of words: for each review, and for each word in the vocabulary, we count the number of occurences of that word in the review. For example:" - ] - }, - { - "cell_type": "code", - "execution_count": 146, - "metadata": {}, - "outputs": [], - "source": [ - "simple_example = tf.constant([[1, 3, 1, 0, 0], [2, 2, 0, 0, 0]])\n", - "tf.reduce_sum(tf.one_hot(simple_example, 4), axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The first review has 2 times the word 0, 2 times the word 1, 0 times the word 2, and 1 time the word 3, so its bag-of-words representation is `[2, 2, 0, 1]`. Similarly, the second review has 3 times the word 0, 0 times the word 1, and so on. Let's wrap this logic in a small custom layer, and let's test it. We'll drop the counts for the word 0, since this corresponds to the `` token, which we don't care about." - ] - }, - { - "cell_type": "code", - "execution_count": 147, - "metadata": {}, - "outputs": [], - "source": [ - "class BagOfWords(tf.keras.layers.Layer):\n", - " def __init__(self, n_tokens, dtype=tf.int32, **kwargs):\n", - " super().__init__(dtype=dtype, **kwargs)\n", - " self.n_tokens = n_tokens\n", - " def call(self, inputs):\n", - " one_hot = tf.one_hot(inputs, self.n_tokens)\n", - " return tf.reduce_sum(one_hot, axis=1)[:, 1:]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's test it:" - ] - }, - { - "cell_type": "code", - "execution_count": 148, - "metadata": {}, - "outputs": [], - "source": [ - "bag_of_words = BagOfWords(n_tokens=4)\n", - "bag_of_words(simple_example)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It works fine! Now let's create another `BagOfWord` with the right vocabulary size for our training set:" - ] - }, - { - "cell_type": "code", - "execution_count": 149, - "metadata": {}, - "outputs": [], - "source": [ - "n_tokens = max_vocabulary_size + n_oov_buckets + 1 # add 1 for \n", - "bag_of_words = BagOfWords(n_tokens)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -3909,13 +3899,40 @@ }, { "cell_type": "code", - "execution_count": 150, + "execution_count": 140, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + "782/782 [==============================] - 4s 4ms/step - loss: 0.4521 - accuracy: 0.8189 - val_loss: 0.3894 - val_accuracy: 0.8419\n", + "Epoch 2/5\n", + "782/782 [==============================] - 4s 4ms/step - loss: 0.3608 - accuracy: 0.8537 - val_loss: 0.7081 - val_accuracy: 0.7643\n", + "Epoch 3/5\n", + "782/782 [==============================] - 4s 4ms/step - loss: 0.3123 - accuracy: 0.8742 - val_loss: 0.3367 - val_accuracy: 0.8569\n", + "Epoch 4/5\n", + "782/782 [==============================] - 4s 4ms/step - loss: 0.2535 - accuracy: 0.8968 - val_loss: 0.5343 - val_accuracy: 0.8040\n", + "Epoch 5/5\n", + "782/782 [==============================] - 4s 4ms/step - loss: 0.1879 - accuracy: 0.9274 - val_loss: 0.3888 - val_accuracy: 0.8439\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 140, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ + "tf.random.set_seed(42)\n", "model = tf.keras.Sequential([\n", " text_vectorization,\n", - " bag_of_words,\n", " tf.keras.layers.Dense(100, activation=\"relu\"),\n", " tf.keras.layers.Dense(1, activation=\"sigmoid\"),\n", "])\n", @@ -3928,7 +3945,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We get about 73.5% accuracy on the validation set after just the first epoch, but after that the model makes no significant progress. We will do better in Chapter 16. For now the point is just to perform efficient preprocessing using `tf.data` and Keras preprocessing layers." + "We get about 84.2% accuracy on the validation set after just the first epoch, but after that the model makes no significant progress. We will do better in Chapter 16. For now the point is just to perform efficient preprocessing using `tf.data` and Keras preprocessing layers." ] }, { @@ -3948,9 +3965,22 @@ }, { "cell_type": "code", - "execution_count": 151, + "execution_count": 141, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 141, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def compute_mean_embedding(inputs):\n", " not_pad = tf.math.count_nonzero(inputs, axis=-1)\n", @@ -3972,9 +4002,20 @@ }, { "cell_type": "code", - "execution_count": 152, + "execution_count": 142, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 142, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "tf.reduce_mean(another_example[0:1, :2], axis=1) * tf.sqrt(2.)" ] @@ -3988,9 +4029,20 @@ }, { "cell_type": "code", - "execution_count": 153, + "execution_count": 143, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 143, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "tf.reduce_mean(another_example[1:2, :1], axis=1) * tf.sqrt(1.)" ] @@ -3999,22 +4051,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Perfect. Now we're ready to train our final model. It's the same as before, except we replaced the `BagOfWords` layer with an `Embedding` layer followed by a `Lambda` layer that calls the `compute_mean_embedding` layer:" + "Perfect. Now we're ready to train our final model. It's the same as before, except we replaced TF-IDF with ordinal encoding (`output_mode=\"int\"`) followed by an `Embedding` layer, followed by a `Lambda` layer that calls the `compute_mean_embedding` layer:" ] }, { "cell_type": "code", - "execution_count": 154, + "execution_count": 144, "metadata": {}, "outputs": [], "source": [ "embedding_size = 20\n", + "tf.random.set_seed(42)\n", + "\n", + "text_vectorization = tf.keras.layers.TextVectorization(\n", + " max_tokens=max_tokens, output_mode=\"int\")\n", + "text_vectorization.adapt(sample_reviews)\n", "\n", "model = tf.keras.Sequential([\n", " text_vectorization,\n", - " tf.keras.layers.Embedding(input_dim=n_tokens,\n", - " output_dim=embedding_size,\n", - " mask_zero=True), # tokens => zero vectors\n", + " tf.keras.layers.Embedding(input_dim=max_tokens,\n", + " output_dim=embedding_size,\n", + " mask_zero=True), # tokens => zero vectors\n", " tf.keras.layers.Lambda(compute_mean_embedding),\n", " tf.keras.layers.Dense(100, activation=\"relu\"),\n", " tf.keras.layers.Dense(1, activation=\"sigmoid\"),\n", @@ -4031,11 +4088,39 @@ }, { "cell_type": "code", - "execution_count": 155, + "execution_count": 145, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/5\n", + "782/782 [==============================] - 9s 10ms/step - loss: 0.4758 - accuracy: 0.7675 - val_loss: 0.4153 - val_accuracy: 0.8009\n", + "Epoch 2/5\n", + "782/782 [==============================] - 8s 9ms/step - loss: 0.3438 - accuracy: 0.8537 - val_loss: 0.3814 - val_accuracy: 0.8245\n", + "Epoch 3/5\n", + "782/782 [==============================] - 8s 10ms/step - loss: 0.3244 - accuracy: 0.8618 - val_loss: 0.3341 - val_accuracy: 0.8520\n", + "Epoch 4/5\n", + "782/782 [==============================] - 10s 11ms/step - loss: 0.3153 - accuracy: 0.8666 - val_loss: 0.3122 - val_accuracy: 0.8655\n", + "Epoch 5/5\n", + "782/782 [==============================] - 11s 12ms/step - loss: 0.3135 - accuracy: 0.8676 - val_loss: 0.3119 - val_accuracy: 0.8625\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 145, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "model.compile(loss=\"binary_crossentropy\", optimizer=\"nadam\", metrics=[\"accuracy\"])\n", + "model.compile(loss=\"binary_crossentropy\", optimizer=\"nadam\",\n", + " metrics=[\"accuracy\"])\n", "model.fit(train_set, epochs=5, validation_data=valid_set)" ] }, @@ -4043,7 +4128,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The model is not better using embeddings (but we will do better in Chapter 16). The pipeline looks fast enough (we optimized it earlier)." + "The model is just marginally better using embeddings (but we will do better in Chapter 16). The pipeline looks fast enough (we optimized it earlier)." ] }, { @@ -4056,7 +4141,7 @@ }, { "cell_type": "code", - "execution_count": 156, + "execution_count": 146, "metadata": {}, "outputs": [], "source": [ @@ -4068,9 +4153,18 @@ }, { "cell_type": "code", - "execution_count": 157, + "execution_count": 147, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tf.Tensor(b\"This was an absolutely terrible movie. Don't be lured in by Christopher Walken or Michael Ironside. Both are great actors, but this must simply be their worst role in history. Even their great acting could not redeem this movie's ridiculous storyline. This movie is an early nineties US propaganda piece. The most pathetic scenes were those when the Columbian rebels were making their cases for revolutions. Maria Conchita Alonso appeared phony, and her pseudo-love affair with Walken was nothing but a pathetic emotional plug in a movie that was devoid of any real meaning. I am disappointed that there are movies like this, ruining actor's like Christopher Walken's good name. I could barely sit through it.\", shape=(), dtype=string)\n", + "tf.Tensor(0, shape=(), dtype=int64)\n" + ] + } + ], "source": [ "for example in train_set.take(1):\n", " print(example[\"text\"])\n", @@ -4083,86 +4177,6 @@ "metadata": {}, "outputs": [], "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# TODO: remove?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that field 4 is interpreted as a string." - ] - }, - { - "cell_type": "code", - "execution_count": 158, - "metadata": {}, - "outputs": [], - "source": [ - "record_defaults=[0, np.nan, tf.constant(np.nan, dtype=tf.float64), \"Hello\", tf.constant([])]\n", - "parsed_fields = tf.io.decode_csv('1,2,3,4,5', record_defaults)\n", - "parsed_fields" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice that all missing fields are replaced with their default value, when provided:" - ] - }, - { - "cell_type": "code", - "execution_count": 159, - "metadata": {}, - "outputs": [], - "source": [ - "parsed_fields = tf.io.decode_csv(',,,,5', record_defaults)\n", - "parsed_fields" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The 5th field is compulsory (since we provided `tf.constant([])` as the \"default value\"), so we get an exception if we do not provide it:" - ] - }, - { - "cell_type": "code", - "execution_count": 160, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " parsed_fields = tf.io.decode_csv(',,,,', record_defaults)\n", - "except tf.errors.InvalidArgumentError as ex:\n", - " print(ex)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The number of fields should match exactly the number of fields in the `record_defaults`:" - ] - }, - { - "cell_type": "code", - "execution_count": 161, - "metadata": {}, - "outputs": [], - "source": [ - "try:\n", - " parsed_fields = tf.io.decode_csv('1,2,3,4,5,6,7', record_defaults)\n", - "except tf.errors.InvalidArgumentError as ex:\n", - " print(ex)" - ] } ], "metadata": {