uczenie-maszynowe/wyk/10_Drzewa_decyzyjne_i_SVM.ipynb

885 lines
95 KiB
Plaintext
Raw Normal View History

2022-12-09 15:06:17 +01:00
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Uczenie maszynowe\n",
"# 10. Przegląd metod uczenia nadzorowanego część 2"
]
},
{
2023-01-13 14:18:12 +01:00
"cell_type": "code",
"execution_count": 1,
2022-12-09 15:06:17 +01:00
"metadata": {
"slideshow": {
2023-01-13 14:18:12 +01:00
"slide_type": "notes"
2022-12-09 15:06:17 +01:00
}
},
2023-01-13 14:18:12 +01:00
"outputs": [],
2022-12-09 15:06:17 +01:00
"source": [
2023-01-13 14:18:12 +01:00
"# Przydatne importy\n",
"\n",
"import ipywidgets as widgets\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import pandas\n",
"\n",
"%matplotlib inline"
2022-12-09 15:06:17 +01:00
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
2023-01-13 14:18:12 +01:00
"## 10.1. Maszyny wektorów nośnych"
2022-12-09 15:06:17 +01:00
]
},
{
"cell_type": "code",
2023-01-13 14:18:12 +01:00
"execution_count": 96,
2022-12-09 15:06:17 +01:00
"metadata": {
"slideshow": {
"slide_type": "notes"
}
},
"outputs": [],
"source": [
2023-01-13 14:18:12 +01:00
"# Wczytanie danych (gatunki kosaćców)\n",
2022-12-09 15:06:17 +01:00
"\n",
2023-01-13 14:18:12 +01:00
"data_iris = pandas.read_csv('iris.csv')\n",
"data_iris_setosa = pandas.DataFrame()\n",
"data_iris_setosa['dł. płatka'] = data_iris['pl'] # \"pl\" oznacza \"petal length\"\n",
"data_iris_setosa['szer. płatka'] = data_iris['pw'] # \"pw\" oznacza \"petal width\"\n",
"data_iris_setosa['Iris setosa?'] = data_iris['Gatunek'].apply(lambda x: 1 if x=='Iris-setosa' else -1)\n",
2022-12-09 15:06:17 +01:00
"\n",
2023-01-13 14:18:12 +01:00
"m, n_plus_1 = data_iris_setosa.values.shape\n",
"n = n_plus_1 - 1\n",
"X = data_iris_setosa.values[:, 0:n].reshape(m, n)\n",
"Y = np.array(data_iris_setosa.values[:, 2])"
]
},
{
"cell_type": "code",
"execution_count": 149,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax = plt.gca()\n",
"ax.scatter(X[:, 0], X[:, 1], c=Y, cmap='coolwarm')\n",
"ax.set_aspect('equal')"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"**Maszyna wektorów nośnych** (*support vector machine*) próbuje oddzielić punkty należące do różnych klas za pomocą hiperpłaszczyzny w taki sposób, żeby margines był jak największy."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"W tym celu wykorzystywana jest funkcja kosztu *hinge loss*."
]
},
{
"cell_type": "code",
"execution_count": 100,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/html": [
"<style>#sk-container-id-3 {color: black;background-color: white;}#sk-container-id-3 pre{padding: 0;}#sk-container-id-3 div.sk-toggleable {background-color: white;}#sk-container-id-3 label.sk-toggleable__label {cursor: pointer;display: block;width: 100%;margin-bottom: 0;padding: 0.3em;box-sizing: border-box;text-align: center;}#sk-container-id-3 label.sk-toggleable__label-arrow:before {content: \"▸\";float: left;margin-right: 0.25em;color: #696969;}#sk-container-id-3 label.sk-toggleable__label-arrow:hover:before {color: black;}#sk-container-id-3 div.sk-estimator:hover label.sk-toggleable__label-arrow:before {color: black;}#sk-container-id-3 div.sk-toggleable__content {max-height: 0;max-width: 0;overflow: hidden;text-align: left;background-color: #f0f8ff;}#sk-container-id-3 div.sk-toggleable__content pre {margin: 0.2em;color: black;border-radius: 0.25em;background-color: #f0f8ff;}#sk-container-id-3 input.sk-toggleable__control:checked~div.sk-toggleable__content {max-height: 200px;max-width: 100%;overflow: auto;}#sk-container-id-3 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {content: \"▾\";}#sk-container-id-3 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-3 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-3 input.sk-hidden--visually {border: 0;clip: rect(1px 1px 1px 1px);clip: rect(1px, 1px, 1px, 1px);height: 1px;margin: -1px;overflow: hidden;padding: 0;position: absolute;width: 1px;}#sk-container-id-3 div.sk-estimator {font-family: monospace;background-color: #f0f8ff;border: 1px dotted black;border-radius: 0.25em;box-sizing: border-box;margin-bottom: 0.5em;}#sk-container-id-3 div.sk-estimator:hover {background-color: #d4ebff;}#sk-container-id-3 div.sk-parallel-item::after {content: \"\";width: 100%;border-bottom: 1px solid gray;flex-grow: 1;}#sk-container-id-3 div.sk-label:hover label.sk-toggleable__label {background-color: #d4ebff;}#sk-container-id-3 div.sk-serial::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: 0;}#sk-container-id-3 div.sk-serial {display: flex;flex-direction: column;align-items: center;background-color: white;padding-right: 0.2em;padding-left: 0.2em;position: relative;}#sk-container-id-3 div.sk-item {position: relative;z-index: 1;}#sk-container-id-3 div.sk-parallel {display: flex;align-items: stretch;justify-content: center;background-color: white;position: relative;}#sk-container-id-3 div.sk-item::before, #sk-container-id-3 div.sk-parallel-item::before {content: \"\";position: absolute;border-left: 1px solid gray;box-sizing: border-box;top: 0;bottom: 0;left: 50%;z-index: -1;}#sk-container-id-3 div.sk-parallel-item {display: flex;flex-direction: column;z-index: 1;position: relative;background-color: white;}#sk-container-id-3 div.sk-parallel-item:first-child::after {align-self: flex-end;width: 50%;}#sk-container-id-3 div.sk-parallel-item:last-child::after {align-self: flex-start;width: 50%;}#sk-container-id-3 div.sk-parallel-item:only-child::after {width: 0;}#sk-container-id-3 div.sk-dashed-wrapped {border: 1px dashed gray;margin: 0 0.4em 0.5em 0.4em;box-sizing: border-box;padding-bottom: 0.4em;background-color: white;}#sk-container-id-3 div.sk-label label {font-family: monospace;font-weight: bold;display: inline-block;line-height: 1.2em;}#sk-container-id-3 div.sk-label-container {text-align: center;}#sk-container-id-3 div.sk-container {/* jupyter's `normalize.less` sets `[hidden] { display: none; }` but bootstrap.min.css set `[hidden] { display: none !important; }` so we also need the `!important` here to be able to override the default hidden behavior on the sphinx rendered scikit-learn.org. See: https://github.com/scikit-learn/scikit-learn/issues/21755 */display: inline-block !important;position: relative;}#sk-container-id-3 div.sk-text-repr-fallback {display: none;}</style><div id=\"sk-container-id-3\" class=\"sk-top-container\
],
"text/plain": [
"SVC(C=10000000000.0, kernel='linear')"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from sklearn.svm import SVC # \"Support vector classifier\"\n",
"model = SVC(kernel='linear', C=1E10)\n",
"model.fit(X, Y)"
]
},
{
"cell_type": "code",
"execution_count": 143,
"metadata": {
"slideshow": {
"slide_type": "notes"
}
},
"outputs": [],
"source": [
"def plot_svc_decision_function(model, plot_support=True):\n",
" \"\"\"Plot the decision function for a 2D SVC\"\"\"\n",
" ax = plt.gca()\n",
" xlim = ax.get_xlim()\n",
" ylim = ax.get_ylim()\n",
" \n",
" # create grid to evaluate model\n",
" x = np.linspace(xlim[0], xlim[1], 30)\n",
" y = np.linspace(ylim[0], ylim[1], 30)\n",
" Y, X = np.meshgrid(y, x)\n",
" xy = np.vstack([X.ravel(), Y.ravel()]).T\n",
" P = model.decision_function(xy).reshape(X.shape)\n",
" \n",
" # plot decision boundary and margins\n",
" ax.contour(X, Y, P, colors='k',\n",
" levels=[-1, 0, 1], alpha=0.5,\n",
" linestyles=['--', '-', '--'])\n",
" \n",
" # plot support vectors\n",
" if plot_support:\n",
" ax.scatter(model.support_vectors_[:, 0],\n",
" model.support_vectors_[:, 1],\n",
" s=300, linewidth=1, edgecolors='k', facecolors='none')\n",
" ax.set_aspect('equal')"
]
},
{
"cell_type": "code",
"execution_count": 144,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAD1CAYAAACC0vWqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB2k0lEQVR4nO3dd3gU1frA8e/MbnpPID2E0AMBQocgvUMCgoKoCIhiAVT0/izca7leC5Zru4qAIiAqoig1dJAqoZfQS6SEVBJSSE925/fHmkjM7qYum92cz/Ps8+jOnpl3JiHz7plz3iMpiqIgCIIgCIJgJrK5AxAEQRAEoWETyYggCIIgCGYlkhFBEARBEMxKJCOCIAiCIJiVSEYEQRAEQTArkYwIgiAIgmBWIhkRBEEQBMGsRDIiCIIgCIJZqc0dQFVotVoSExNxcXFBkiRzhyMIgiAIQhUoisLt27fx9/dHlg33f1hEMpKYmEhQUJC5wxAEQRAEoQbi4+MJDAw0uN0ikhEXFxdAdzKurq5mjsYwjUbDkiVLuHnzJmFhYURFRZk7JJNRFIXvvvuOhIQEWrRowf333y96rQRBEIRysrOzCQoKKruPG2IRyUjpTc7V1bVeJyMAEyZM4JtvvuHSpUvcvHmT5s2bmzskk5kwYQILFy4kPj6ehIQE2rZta+6QBEEQhHqosi+rYgBrHQsMDKR79+4AREdHU1RUZOaITMfb25t77rkHgI0bN1JQUGDmiARBsAYZWUVcjc8l+3Zxne/7VoZu37dzSips02gUbiTmE5+YR4lGrCF7N1lEz4ilGThwIOfPnycjI4Ndu3YxdOhQc4dkMn369OHMmTOkpaWxbds2q340JQiCaV24fJuvvrvCwWMZAMgS9O7uxZNTQmga5FSrfceezeKr765w4nQWACpZon/vRjw5OQRfb3tWrktgxZp4bqbrvkC6u9kwPiqAh+8LQq0W39tNrVpXeO7cuXTr1g0XFxe8vb259957uXDhgtE2S5cuRZKkci97e/taBV3f2dnZMWrUKABiYmJISkoyc0Smo1aryxKQP/74g8LCQjNHJAiCJYo9m8VTLx3n8ImMsve0Cuw/nM70F45z6UpOjfd94OgtnplzktizWWXvabQKu36/yeMvHOO198/w+TdxZYkIQGZWMYt+uMpr751FI3pJTK5aycju3buZOXMmBw4cYNu2bRQXFzN06FByc3ONtnN1dSUpKansde3atVoFbQlatWpFWFgYiqKwbt06tFqtuUMymeDgYCZMmMDTTz+NnZ2ducMRBMHCKIrC3M8uoNEo/P1PpUYLhUUaPpx3sUb7LinR8u6n59Eq+vedk1vC7v3pBuKCvQfT2bX/Zo2OLVRdtR7TbN68udz/L126FG9vb44ePUrfvn0NtpMkCV9f35pFaMGGDx9OXFwcSUlJHDhwgIiICHOHZDJi8KogCDV16lw28Yn5BrdrtXD2wm2uXM8lpEn1HtccOHqLW5mGx55U9j1RlmHNpiQG9fGu1nGF6qnVg7CsLF2Xl6enp9HP5eTkEBwcTFBQEGPGjOHMmTNGP19YWEh2dna5lyVydnYuGy+yc+dOMjIyKmlh+RRF4fDhw6Smppo7FEEQLMT1hLwqfe6GkYTF8L7zMVJrq1JaLVy/UbX4hJqr8Y9Iq9Uye/ZsevfuTVhYmMHPtW7dmsWLF7N27Vq+//57tFotERER3Lhxw2CbuXPn4ubmVvay5IJn4eHhhISEUFxcTHR0NIpi3c8ef/vtNzZs2MC6deus/lwFQagbzo5V66R3clRVf99O6kp7P0xxXKF6apyMzJw5k9OnT7NixQqjn+vVqxeTJ08mPDycfv36sWrVKho3bszChQsNtpkzZw5ZWVllr/j4+JqGaXaSJBEZGYlarSYuLo7Y2Fhzh2RS3bp1w87Ojhs3bnD48GFzhyMIggXo3tkTezvjtyM3Vxs6tHWr9r7v6e6FSq55QUZZhqH9fWrcXqiaGiUjs2bNIjo6mp07dxot76qPjY0NnTp14vLlywY/Y2dnV1bgzBIKnVXGy8uLfv36AbBly5ZKB/xaMldXVwYNGgTAjh07LPYRmyBYuty8EtZtSWLhsiv8uDqe1LT6MdOtoKCEed/EMePl4zz36kl27EvF0UHFpPubGG037aHgGk2x9fSwZVykP8bSEVcXNSo9u5ZlcHG2Ycxwv2ofV6ieav1kFUVh1qxZrF69mt9++42QkJBqH1Cj0XDq1Cn8/BrWDzciIgIfHx/y8vLYsmWLucMxqW7duhEUFERhYSEbNmwQj2sE4S5btyWJ0Y/E8OEXF/lxVTzzl/zB/dMO8MnCS2Yt5vVr9A2GTPidH9fcIPZsNkdPZvLG++cY+dDvDO7XmEn3ByHLIEmgVklIEqhUEk9ODmHcSP8aH3fmtOaMHemPJOkSjNJ929rI/OPpliz6uDPBQY6A7ngqlS518fW25/N3O+Lhblsn5y8YJinVuFPMmDGD5cuXs3btWlq3bl32vpubGw4ODgBMnjyZgIAA5s6dC8B//vMfevbsSYsWLcjMzOTDDz9kzZo1HD16tMozMLKzs3FzcyMrK8uie0kSEhJYtGgRiqIwadIkWrRoYe6QTCY1NZWFCxei0WiYMGGCmG0jCHfJb/tu8vr7Z/VukySYMDqAZx6/+3979h1M45W3DU9ecLBXsenHCDKzi9m+J5VbmcV4e9kxuK837m42dRJDcmoBO/amknW7BH8fewb18cbFWTdeRVEUjsVmcjQ2E0WBDm1d6dHZE7kWj3iEqt+/q5WMGKotv2TJEqZOnQpA//79adq0KUuXLgXg+eefZ9WqVSQnJ+Ph4UGXLl14++236dSpU52fjCXYvHkzBw4cwN3dnRkzZmBra70Z986dO9m9ezeurq4899xzqFRiEJggmJKiKDz41GGjs05UKok1S3ve9W/7E6YfJDHZ+JIRT01uyqTxwXcpIuFuMEkyYi7WlIwUFRUxb948srKyiIiIsOpS8SUlJaxevZrevXvj71/zLlZBEKom7moOU545WunnXprVitHD7t6jcq1WS98xeyv9XJC/Az8u7H4XIhLulqrev0XB/bvM1taWyMhIQFcqPjEx0cwRmY5arWb8+PEiERGEuyQnt+Lib38ny1X7XF0qKKja3NqCQo2JIxHqK5GMmEHLli3LSsWvX7/eqkvF3+nmzZuUlNzdP4KC0JAE+DpQyUrtaLUQ6OdwdwL6k6OjmqoMvfBpLJaTaKhEMmImw4cPx8HBgaSkJGJiYswdjskdOHCABQsWsGfPHnOHIghWq5GXHT27eOqdpgq6AazubjZEdDNeNdsUOnVwr/Qz0ydVf4amYB2qtTaNUHdKS8WvXbuWXbt20bZtWzw8PMwdlsm4urqi0WjYt28fYWFheHuLdR4EwRSend6c6S9kkZun4c4RgaUdE3OebU1mdjFrNiayY99N8vM1NG/qxNiR/vTu7oUkSZw8k8WvGxI4fS4blUqidzcvxkX60yTAkcIiLVt3prBuaxI30wrx9LBl1GBfRgzyxdHB8CD11/8RyoTHD1JYpL8nuEsHd0JbufJrdAIbtidzK6OIxo3siBrqx7ABPtjZGv/urCgKvx9OZ/XGJOKu5ODgoGLgPY0ZO8KfRl6V97jEXc3h1+hEDh67hQJ0CnNjfFQgbVq6VNrWUiWlFLB6YwK796dRVKylTQsXxo3yp2u4h8EJK6YiBrCakaIoLFu2jCtXrtC8eXMmTZp0138B7hZFUVixYgUXLlwgKCiIadOmWe25CoI5FRZqePZfJzlz4XaFbR5uNrzybGve+vgcefmasjLpsqx7fDN8oA+BfvYs+uEaKpWE5s+aJCoZJFni1dmtWbE2gfOXbiNJlCU7EtAk0IEv5oYbnaWTmlbAS/85zeUrfxV+VKskRgzy4YlHQpg15wTXb+RTelMqPUbrFs589nZHnJ30f3/WahXm/u8Cm3aklJ1L6Xk52Kv47O2ORpOKLTtTeOeT80jyHef85/k//1QL7hsVYLCtpTp2KpMX/32K4hJt2fVSybqVjB8YE8Csx5rXyd9oMZvGQqSnpzN//nx
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.scatter(X[:, 0], X[:, 1], c=Y, cmap='coolwarm')\n",
"plot_svc_decision_function(model)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## 10.2. Drzewa decyzyjne"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Drzewa decyzyjne przykład"
2022-12-09 15:06:17 +01:00
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" Day Outlook Humidity Wind Play\n",
"0 1 Sunny High Weak No\n",
"1 2 Sunny High Strong No\n",
"2 3 Overcast High Weak Yes\n",
"3 4 Rain High Weak Yes\n",
"4 5 Rain Normal Weak Yes\n",
"5 6 Rain Normal Strong No\n",
"6 7 Overcast Normal Strong Yes\n",
"7 8 Sunny High Weak No\n",
"8 9 Sunny Normal Weak Yes\n",
"9 10 Rain Normal Weak Yes\n",
"10 11 Sunny Normal Strong Yes\n",
"11 12 Overcast High Strong Yes\n",
"12 13 Overcast Normal Weak Yes\n",
"13 14 Rain High Strong No\n"
]
}
],
"source": [
"alldata = pandas.read_csv('tennis.tsv', sep='\\t')\n",
"print(alldata)"
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"{'Outlook': {'Overcast', 'Rain', 'Sunny'},\n",
" 'Humidity': {'High', 'Normal'},\n",
" 'Wind': {'Strong', 'Weak'}}"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Dane jako lista słowników\n",
"data = alldata.T.to_dict().values()\n",
"features = ['Outlook', 'Humidity', 'Wind']\n",
"\n",
"# Możliwe wartości w poszczególnych kolumnach\n",
"values = {feature: set(row[feature] for row in data)\n",
" for feature in features}\n",
"values"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"* Czy John zagra w tenisa, jeżeli będzie padać, przy wysokiej wilgotności i silnym wietrze?\n",
"* Algorytm drzew decyzyjnych spróbuje _zrozumieć_ „taktykę” Johna.\n",
"* Wykorzystamy metodę „dziel i zwyciężaj”."
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [],
"source": [
"# Podziel dane\n",
"def split(features, data):\n",
" values = {feature: list(set(row[feature]\n",
" for row in data))\n",
" for feature in features}\n",
" if not features:\n",
" return data\n",
" return {val: split(features[1:],\n",
" [row for row in data\n",
" if row[features[0]] == val])\n",
" for val in values[features[0]]}"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 1:\tSunny\tHigh\tWeak\tNo\n",
"Day 2:\tSunny\tHigh\tStrong\tNo\n",
"Day 8:\tSunny\tHigh\tWeak\tNo\n",
"Day 9:\tSunny\tNormal\tWeak\tYes\n",
"Day 11:\tSunny\tNormal\tStrong\tYes\n",
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 4:\tRain\tHigh\tWeak\tYes\n",
"Day 5:\tRain\tNormal\tWeak\tYes\n",
"Day 6:\tRain\tNormal\tStrong\tNo\n",
"Day 10:\tRain\tNormal\tWeak\tYes\n",
"Day 14:\tRain\tHigh\tStrong\tNo\n",
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 3:\tOvercast\tHigh\tWeak\tYes\n",
"Day 7:\tOvercast\tNormal\tStrong\tYes\n",
"Day 12:\tOvercast\tHigh\tStrong\tYes\n",
"Day 13:\tOvercast\tNormal\tWeak\tYes\n"
]
}
],
"source": [
"split_data = split(['Outlook'], data)\n",
"\n",
"for outlook in values['Outlook']:\n",
" print('\\n\\tOutlook\\tHumid\\tWind\\tPlay')\n",
" for row in split_data[outlook]:\n",
" print('Day {Day}:\\t{Outlook}\\t{Humidity}\\t{Wind}\\t{Play}'\n",
" .format(**row))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Obserwacja: John lubi grać, gdy jest pochmurnie.\n",
"\n",
"W pozostałych przypadkach podzielmy dane ponownie:"
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 1:\tSunny\tHigh\tWeak\tNo\n",
"Day 2:\tSunny\tHigh\tStrong\tNo\n",
"Day 8:\tSunny\tHigh\tWeak\tNo\n",
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 9:\tSunny\tNormal\tWeak\tYes\n",
"Day 11:\tSunny\tNormal\tStrong\tYes\n"
]
}
],
"source": [
"split_data_sunny = split(['Outlook', 'Humidity'], data)\n",
"\n",
"for humidity in values['Humidity']:\n",
" print('\\n\\tOutlook\\tHumid\\tWind\\tPlay')\n",
" for row in split_data_sunny['Sunny'][humidity]:\n",
" print('Day {Day}:\\t{Outlook}\\t{Humidity}\\t{Wind}\\t{Play}'\n",
" .format(**row))"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 6:\tRain\tNormal\tStrong\tNo\n",
"Day 14:\tRain\tHigh\tStrong\tNo\n",
"\n",
"\tOutlook\tHumid\tWind\tPlay\n",
"Day 4:\tRain\tHigh\tWeak\tYes\n",
"Day 5:\tRain\tNormal\tWeak\tYes\n",
"Day 10:\tRain\tNormal\tWeak\tYes\n"
]
}
],
"source": [
"split_data_rain = split(['Outlook', 'Wind'], data)\n",
"\n",
"for wind in values['Wind']:\n",
" print('\\n\\tOutlook\\tHumid\\tWind\\tPlay')\n",
" for row in split_data_rain['Rain'][wind]:\n",
" print('Day {Day}:\\t{Outlook}\\t{Humidity}\\t{Wind}\\t{Play}'\n",
" .format(**row))"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"* Outlook=\n",
" * Overcast\n",
" * → Playing\n",
" * Sunny\n",
" * Humidity=\n",
" * High\n",
" * → Not playing\n",
" * Normal\n",
" * → Playing\n",
" * Rain\n",
" * Wind=\n",
" * Weak\n",
" * → Playing\n",
" * Strong\n",
" * → Not playing"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"* (9/5)\n",
" * Outlook=Overcast (4/0)\n",
" * YES\n",
" * Outlook=Sunny (2/3)\n",
" * Humidity=High (0/3)\n",
" * NO\n",
" * Humidity=Normal (2/0)\n",
" * YES\n",
" * Outlook=Rain (3/2)\n",
" * Wind=Weak (3/0)\n",
" * YES\n",
" * Wind=Strong (0/2)\n",
" * NO"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Algorytm ID3"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Pseudokod algorytmu:\n",
"\n",
"* podziel(węzeł, zbiór przykładów):\n",
" 1. A ← najlepszy atrybut do podziału zbioru przykładów\n",
" 1. Dla każdej wartości atrybutu A, utwórz nowy węzeł potomny\n",
" 1. Podziel zbiór przykładów na podzbiory według węzłów potomnych\n",
" 1. Dla każdego węzła potomnego i podzbioru:\n",
" * jeżeli podzbiór jest jednolity: zakończ\n",
" * w przeciwnym przypadku: podziel(węzeł potomny, podzbiór)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Jak wybrać „najlepszy atrybut”?\n",
"* powinien zawierać jednolity podzbiór\n",
"* albo przynajmniej „w miarę jednolity”\n",
"\n",
"Skąd wziąć miarę „jednolitości” podzbioru?\n",
"* miara powinna być symetryczna (4/0 vs. 0/4)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Entropia\n",
"\n",
"$$ H(S) = - p_{(+)} \\log p_{(+)} - p_{(-)} \\log p_{(-)} $$\n",
"\n",
"* $S$ podzbiór przykładów\n",
"* $p_{(+)}$, $p_{(-)}$ procent pozytywnych/negatywnych przykładów w $S$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"Entropię można traktować jako „liczbę bitów” potrzebną do sprawdzenia, czy losowo wybrany $x \\in S$ jest pozytywnym, czy negatywnym przykładem."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Przykład:\n",
"\n",
"* (3 TAK / 3 NIE):\n",
"$$ H(S) = -\\frac{3}{6} \\log\\frac{3}{6} - \\frac{3}{6} \\log\\frac{3}{6} = 1 \\mbox{ bit} $$\n",
"* (4 TAK / 0 NIE):\n",
"$$ H(S) = -\\frac{4}{4} \\log\\frac{4}{4} - \\frac{0}{4} \\log\\frac{0}{4} = 0 \\mbox{ bitów} $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### *Information gain*\n",
"\n",
"*Information gain* różnica między entropią przed podziałem a entropią po podziale (podczas podziału entropia zmienia się):\n",
"\n",
"$$ \\mathop{\\rm Gain}(S,A) = H(S) - \\sum_{V \\in \\mathop{\\rm Values(A)}} \\frac{|S_V|}{|S|} H(S_V) $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Przykład:\n",
"\n",
"$$ \\mathop{\\rm Gain}(S, Wind) = H(S) - \\frac{8}{14} H(S_{Wind={\\rm Weak}}) - \\frac{6}{14} H(S_{Wind={\\rm Strong}}) = \\\\\n",
"= 0.94 - \\frac{8}{14} \\cdot 0.81 - \\frac{6}{14} \\cdot 1.0 = 0.049 $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"* _Information gain_ jest całkiem sensowną heurystyką wskazującą, który atrybut jest najlepszy do dokonania podziału.\n",
"* **Ale**: _information gain_ przeszacowuje użyteczność atrybutów, które mają dużo różnych wartości.\n",
"* **Przykład**: gdybyśmy wybrali jako atrybut *datę*, otrzymalibyśmy bardzo duży *information gain*, ponieważ każdy podzbiór byłby jednolity, a nie byłoby to ani trochę użyteczne!"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### _Information gain ratio_\n",
"\n",
"$$ \\mathop{\\rm GainRatio}(S, A) = \\frac{ \\mathop{\\rm Gain}(S, A) }{ -\\sum_{V \\in \\mathop{\\rm Values}(A)} \\frac{|S_V|}{|S|} \\log\\frac{|S_V|}{|S|} } $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"* _Information gain ratio_ może być lepszym wyborem heurystyki wskazującej najużyteczniejszy atrybut, jeżeli atrybuty mają wiele różnych wartości."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Drzewa decyzyjne a formuły logiczne\n",
"\n",
"Drzewo decyzyjne można pzekształcić na formułę logiczną w postaci normalnej (DNF):\n",
"\n",
"$$ Play={\\rm True} \\Leftrightarrow \\left( Outlook={\\rm Overcast} \\vee \\\\\n",
"( Outlook={\\rm Rain} \\wedge Wind={\\rm Weak} ) \\vee \\\\\n",
"( Outlook={\\rm Sunny} \\wedge Humidity={\\rm Normal} ) \\right) $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Klasyfikacja wieloklasowa przy użyciu drzew decyzyjnych\n",
"\n",
"Algorytm przebiega analogicznie, zmienia się jedynie wzór na entropię:\n",
"\n",
"$$ H(S) = -\\sum_{y \\in Y} p_{(y)} \\log p_{(y)} $$"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Skuteczność algorytmu ID3\n",
"\n",
"* Przyjmujemy, że wśród danych uczących nie ma duplikatów (tj. przykładów, które mają jednakowe cechy $x$, a mimo to należą do różnych klas $y$).\n",
"* Wówczas algorytm drzew decyzyjnych zawsze znajdzie rozwiązanie, ponieważ w ostateczności będziemy mieli węzły 1-elementowe na liściach drzewa."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Nadmierne dopasowanie drzew decyzyjnych\n",
"\n",
"* Zauważmy, że w miarę postępowania algorytmu dokładność przewidywań drzewa (*accuracy*) liczona na zbiorze uczącym dąży do 100% (i w ostateczności osiąga 100%, nawet kosztem jednoelementowych liści).\n",
"* Takie rozwiązanie niekoniecznie jest optymalne. Dokładność na zbiorze testowym może być dużo niższa, a to oznacza nadmierne dopasowanie."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Jak zapobiec nadmiernemu dopasowaniu?\n",
"\n",
"Aby zapobiegać nadmiernemu dopasowaniu drzew decyzyjnych, należy je przycinać (*pruning*)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"Można tego dokonywać na kilka sposobów:\n",
"* Można zatrzymywać procedurę podziału w pewnym momencie (np. kiedy podzbiory staja się zbyt małe).\n",
"* Można najpierw wykonać algorytm ID3 w całości, a następnie przyciąć drzewo, np. kierując się wynikami uzyskanymi na zbiorze walidacyjnym.\n",
"* Algorytm _sub-tree replacement pruning_ (algorytm zachłanny)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Algorytm _Sub-tree replacement pruning_\n",
"\n",
"1. Dla każdego węzła:\n",
" 1. Udaj, że usuwasz węzeł wraz z całym zaczepionym w nim poddrzewem.\n",
" 1. Dokonaj ewaluacji na zbiorze walidacyjnym.\n",
"1. Usuń węzeł, którego usunięcie daje największą poprawę wyniku.\n",
"1. Powtarzaj, dopóki usuwanie węzłów poprawia wynik."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Zalety drzew decyzyjnych\n",
"\n",
"* Zasadę działania drzew decyzyjnych łatwo zrozumieć człowiekowi.\n",
"* Atrybuty, które nie wpływają na wynik, mają _gain_ równy 0, zatem są od razu pomijane przez algorytm.\n",
"* Po zbudowaniu, drzewo decyzyjne jest bardzo szybkim klasyfikatorem (złożoność $O(d)$, gdzie $d$ jest głębokościa drzewa)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"### Wady drzew decyzyjnych\n",
"\n",
"* ID3 jest algorytmem zachłannym może nie wskazać najlepszego drzewa.\n",
"* Nie da się otrzymać granic klas (*decision boundaries*), które nie są równoległe do osi wykresu."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Lasy losowe"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Algorytm lasów losowych idea\n",
"\n",
"* Algorytm lasów losowych jest rozwinięciem algorytmu ID3.\n",
"* Jest to bardzo wydajny algorytm klasyfikacji.\n",
"* Zamiast jednego, będziemy budować $k$ drzew."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Algorytm lasów losowych budowa lasu\n",
"\n",
"1. Weź losowy podzbiór $S_r$ zbioru uczącego.\n",
"1. Zbuduj pełne (tj. bez przycinania) drzewo decyzyjne dla $S_r$, używając algorytmu ID3 z następującymi modyfikacjami:\n",
" * podczas podziału używaj losowego $d$-elementowego podzbioru atrybutów,\n",
" * obliczaj _gain_ względem $S_r$.\n",
"1. Powyższą procedurę powtórz $k$-krotnie, otrzymując $k$ drzew ($T_1, T_2, \\ldots, T_k$)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "subslide"
}
},
"source": [
"#### Algorytm lasów losowych predykcja\n",
"\n",
"1. Sklasyfikuj $x$ według każdego z drzew $T_1, T_2, \\ldots, T_k$ z osobna.\n",
"1. Użyj głosowania większościowego: przypisz klasę przewidzianą przez najwięcej drzew."
]
}
],
"metadata": {
"celltoolbar": "Slideshow",
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
},
"livereveal": {
"start_slideshow_at": "selected",
"theme": "white"
}
},
"nbformat": 4,
"nbformat_minor": 4
}