{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# # Classifiers introduction\n",
"\n",
"In the following program we introduce the basic steps of classification of a dataset in a matrix"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Import the package for learning and modeling trees"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"from sklearn import tree"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define the matrix containing the data (one example per row)\n",
"and the vector containing the corresponding target value"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"X = [[0, 0, 0], [1, 1, 1], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1]]\n",
"Y = [1, 0, 0, 0, 1, 1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Declare the classification model you want to use and then fit the model to the data"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"clf = tree.DecisionTreeClassifier()\n",
"clf = clf.fit(X, Y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Predict the target value (and print it) for the passed data, using the fitted model currently in clf"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0]\n"
]
}
],
"source": [
"print(clf.predict([[0, 1, 1]]))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[1 0]\n"
]
}
],
"source": [
"print(clf.predict([[1, 0, 1],[0, 0, 1]]))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import os\n",
"os.environ[\"PATH\"] += os.pathsep + 'C:/Users/galat/.conda/envs/aaut/Library/bin/graphviz'\n",
"import graphviz\n",
"dot_data = tree.export_graphviz(clf, out_file=None) \n",
"graph = graphviz.Source(dot_data) \n",
"graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the following we start using a dataset (from UCI Machine Learning repository)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"from sklearn.datasets import load_iris\n",
"iris = load_iris()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Declare the type of prediction model and the working criteria for the model induction algorithm"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"clf = tree.DecisionTreeClassifier(criterion=\"entropy\",random_state=300,min_samples_leaf=5,class_weight={0:1,1:1,2:1})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Split the dataset in training and test set"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# Generate a random permutation of the indices of examples that will be later used \n",
"# for the training and the test set\n",
"import numpy as np\n",
"np.random.seed(0)\n",
"indices = np.random.permutation(len(iris.data))\n",
"\n",
"# We now decide to keep the last 10 indices for test set, the remaining for the training set\n",
"indices_training=indices[:-10]\n",
"indices_test=indices[-10:]\n",
"\n",
"iris_X_train = iris.data[indices_training] # keep for training all the matrix elements with the exception of the last 10 \n",
"iris_y_train = iris.target[indices_training]\n",
"iris_X_test = iris.data[indices_test] # keep the last 10 elements for test set\n",
"iris_y_test = iris.target[indices_test]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Fit the learning model on training set"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# fit the model to the training data\n",
"clf = clf.fit(iris_X_train, iris_y_train)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Obtain predictions"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Predictions:\n",
"[1 2 1 0 0 0 2 1 2 0]\n",
"True classes:\n",
"[1 1 1 0 0 0 2 1 2 0]\n",
"['setosa' 'versicolor' 'virginica']\n"
]
}
],
"source": [
"# apply fitted model \"clf\" to the test set \n",
"predicted_y_test = clf.predict(iris_X_test)\n",
"\n",
"# print the predictions (class numbers associated to classes names in target names)\n",
"print(\"Predictions:\")\n",
"print(predicted_y_test)\n",
"print(\"True classes:\")\n",
"print(iris_y_test) \n",
"print(iris.target_names)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Print the index of the test instances and the corresponding predictions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Look at the specific examples"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Instance # 88: \n",
"sepal length (cm)=5.6, sepal width (cm)=3.0, petal length (cm)=4.1, petal width (cm)=1.3\n",
"Predicted: versicolor\t True: versicolor\n",
"\n",
"Instance # 70: \n",
"sepal length (cm)=5.9, sepal width (cm)=3.2, petal length (cm)=4.8, petal width (cm)=1.8\n",
"Predicted: virginica\t True: versicolor\n",
"\n",
"Instance # 87: \n",
"sepal length (cm)=6.3, sepal width (cm)=2.3, petal length (cm)=4.4, petal width (cm)=1.3\n",
"Predicted: versicolor\t True: versicolor\n",
"\n",
"Instance # 36: \n",
"sepal length (cm)=5.5, sepal width (cm)=3.5, petal length (cm)=1.3, petal width (cm)=0.2\n",
"Predicted: setosa\t True: setosa\n",
"\n",
"Instance # 21: \n",
"sepal length (cm)=5.1, sepal width (cm)=3.7, petal length (cm)=1.5, petal width (cm)=0.4\n",
"Predicted: setosa\t True: setosa\n",
"\n",
"Instance # 9: \n",
"sepal length (cm)=4.9, sepal width (cm)=3.1, petal length (cm)=1.5, petal width (cm)=0.1\n",
"Predicted: setosa\t True: setosa\n",
"\n",
"Instance # 103: \n",
"sepal length (cm)=6.3, sepal width (cm)=2.9, petal length (cm)=5.6, petal width (cm)=1.8\n",
"Predicted: virginica\t True: virginica\n",
"\n",
"Instance # 67: \n",
"sepal length (cm)=5.8, sepal width (cm)=2.7, petal length (cm)=4.1, petal width (cm)=1.0\n",
"Predicted: versicolor\t True: versicolor\n",
"\n",
"Instance # 117: \n",
"sepal length (cm)=7.7, sepal width (cm)=3.8, petal length (cm)=6.7, petal width (cm)=2.2\n",
"Predicted: virginica\t True: virginica\n",
"\n",
"Instance # 47: \n",
"sepal length (cm)=4.6, sepal width (cm)=3.2, petal length (cm)=1.4, petal width (cm)=0.2\n",
"Predicted: setosa\t True: setosa\n",
"\n"
]
}
],
"source": [
"for i in range(len(iris_y_test)): \n",
" print(\"Instance # \"+str(indices_test[i])+\": \")\n",
" s=\"\"\n",
" for j in range(len(iris.feature_names)):\n",
" s=s+iris.feature_names[j]+\"=\"+str(iris_X_test[i][j])\n",
" if (j\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dot_data = tree.export_graphviz(clf, out_file=None, \n",
" feature_names=iris.feature_names, \n",
" class_names=iris.target_names, \n",
" filled=True, rounded=True, \n",
" special_characters=True) \n",
"graph = graphviz.Source(dot_data) \n",
"graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 1. Artificial inflation"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"# Generate a random permutation of the indices of examples that will be later used \n",
"# for the training and the test set\n",
"import numpy as np\n",
"np.random.seed(42)\n",
"indices = np.random.permutation(len(iris.data))\n",
"\n",
"# We now decide to keep the last 10 indices for test set, the remaining for the training set\n",
"indices_training=indices[:-10]\n",
"indices_test=indices[-10:]\n",
"\n",
"iris_X_train = iris.data[indices_training] # keep for training all the matrix elements with the exception of the last 10 \n",
"iris_y_train = iris.target[indices_training]\n",
"iris_X_test = iris.data[indices_test] # keep the last 10 elements for test set\n",
"iris_y_test = iris.target[indices_test]"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"samples_x = []\n",
"samples_y = []\n",
"for i in range(0, len(iris_y_train)):\n",
" if iris_y_train[i] == 1:\n",
" for _ in range(9):\n",
" samples_x.append(iris_X_train[i])\n",
" samples_y.append(1)\n",
" elif iris_y_train[i] == 2:\n",
" for _ in range(9):\n",
" samples_x.append(iris_X_train[i])\n",
" samples_y.append(2)\n",
"\n",
"#Samples inflation\n",
"iris_X_train = np.append(iris_X_train, samples_x, axis = 0)\n",
"iris_y_train = np.append(iris_y_train, samples_y, axis = 0)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Accuracy: 0.9\n",
"F1: 0.9153439153439153\n"
]
}
],
"source": [
"clf = tree.DecisionTreeClassifier(criterion=\"entropy\",random_state=300,min_samples_leaf=10,class_weight={0:1,1:1,2:1})\n",
"clf = clf.fit(iris_X_train, iris_y_train)\n",
"predicted_y_test = clf.predict(iris_X_test)\n",
"acc_score = accuracy_score(iris_y_test, predicted_y_test)\n",
"f1 = f1_score(iris_y_test, predicted_y_test, average='macro')\n",
"print(\"Accuracy: \", acc_score)\n",
"print(\"F1: \", f1)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dot_data = tree.export_graphviz(clf, out_file=None, \n",
" feature_names=iris.feature_names, \n",
" class_names=iris.target_names, \n",
" filled=True, rounded=True, \n",
" special_characters=True) \n",
"graph = graphviz.Source(dot_data) \n",
"graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 2. Class weights"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"# Generate a random permutation of the indices of examples that will be later used \n",
"# for the training and the test set\n",
"import numpy as np\n",
"np.random.seed(1231)\n",
"indices = np.random.permutation(len(iris.data))\n",
"\n",
"# We now decide to keep the last 10 indices for test set, the remaining for the training set\n",
"indices_training=indices[:-10]\n",
"indices_test=indices[-10:]\n",
"\n",
"iris_X_train = iris.data[indices_training] # keep for training all the matrix elements with the exception of the last 10 \n",
"iris_y_train = iris.target[indices_training]\n",
"iris_X_test = iris.data[indices_test] # keep the last 10 elements for test set\n",
"iris_y_test = iris.target[indices_test]"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Accuracy: 0.8\n",
"F1: 0.5\n"
]
}
],
"source": [
"clf = tree.DecisionTreeClassifier(criterion=\"entropy\",random_state=300,min_samples_leaf=5,class_weight={0:1,1:10,2:10})\n",
"clf = clf.fit(iris_X_train, iris_y_train)\n",
"predicted_y_test = clf.predict(iris_X_test)\n",
"acc_score = accuracy_score(iris_y_test, predicted_y_test)\n",
"f1 = f1_score(iris_y_test, predicted_y_test, average='macro')\n",
"print(\"Accuracy: \", acc_score)\n",
"print(\"F1: \", f1)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dot_data = tree.export_graphviz(clf, out_file=None, \n",
" feature_names=iris.feature_names, \n",
" class_names=iris.target_names, \n",
" filled=True, rounded=True, \n",
" special_characters=True) \n",
"graph = graphviz.Source(dot_data) \n",
"graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 3. Avoid overfitting"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [],
"source": [
"# Generate a random permutation of the indices of examples that will be later used \n",
"# for the training and the test set\n",
"import numpy as np\n",
"np.random.seed(42)\n",
"indices = np.random.permutation(len(iris.data))\n",
"\n",
"# We now decide to keep the last 10 indices for test set, the remaining for the training set\n",
"indices_training=indices[:-10]\n",
"indices_test=indices[-10:]\n",
"\n",
"iris_X_train = iris.data[indices_training] # keep for training all the matrix elements with the exception of the last 10 \n",
"iris_y_train = iris.target[indices_training]\n",
"iris_X_test = iris.data[indices_test] # keep the last 10 elements for test set\n",
"iris_y_test = iris.target[indices_test]"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Accuracy: 0.9\n",
"F1: 0.9153439153439153\n"
]
}
],
"source": [
"clf = tree.DecisionTreeClassifier(criterion=\"entropy\",random_state=300,min_samples_leaf=3,class_weight={0:1,1:10,2:10}, min_impurity_decrease = 0.005, max_depth = 4, max_leaf_nodes = 6)\n",
"clf = clf.fit(iris_X_train, iris_y_train)\n",
"predicted_y_test = clf.predict(iris_X_test)\n",
"acc_score = accuracy_score(iris_y_test, predicted_y_test)\n",
"f1 = f1_score(iris_y_test, predicted_y_test, average='macro')\n",
"print(\"Accuracy: \", acc_score)\n",
"print(\"F1: \", f1)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dot_data = tree.export_graphviz(clf, out_file=None, \n",
" feature_names=iris.feature_names, \n",
" class_names=iris.target_names, \n",
" filled=True, rounded=True, \n",
" special_characters=True) \n",
"graph = graphviz.Source(dot_data) \n",
"graph"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 4. Confusion Matrix"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
"array([[2, 0, 0],\n",
" [0, 4, 0],\n",
" [0, 1, 3]])"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# initializes the confusion matrix\n",
"confusion = np.zeros([3, 3], dtype = int)\n",
"\n",
"# print the corresponding instances indexes and class names\n",
"for i in range(len(iris_y_test)): \n",
" #increments the indexed cell value\n",
" confusion[iris_y_test[i], predicted_y_test[i]]+=1\n",
"confusion"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 5. ROC Curves"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[(0.0, 48.0), (30.0, 0.0), (30.0, 0.0), (60.0, 0.0), (400.0, 0.0), (400.0, 0.0)], [(0.0, 400.0), (0.0, 30.0), (20.0, 10.0), (40.0, 20.0), (48.0, 0.0), (400.0, 0.0)], [(0.0, 400.0), (10.0, 20.0), (20.0, 40.0), (30.0, 0.0), (48.0, 0.0), (400.0, 0.0)]]\n"
]
},
{
"data": {
"text/plain": [
"[[[0, 0.0, 30.0, 60.0, 120.0, 520.0, 920.0],\n",
" [0, 48.0, 48.0, 48.0, 48.0, 48.0, 48.0]],\n",
" [[0, 0.0, 0.0, 20.0, 60.0, 108.0, 508.0],\n",
" [0, 400.0, 430.0, 440.0, 460.0, 460.0, 460.0]],\n",
" [[0, 0.0, 10.0, 30.0, 60.0, 108.0, 508.0],\n",
" [0, 400.0, 420.0, 460.0, 460.0, 460.0, 460.0]]]"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Calculates the ROC curves (x, y)\n",
"leafs = []\n",
"class_pairs = [[],[],[]]\n",
"roc_curves = [[[0], [0]], [[0], [0]], [[0], [0]]]\n",
"for i in range(clf.tree_.node_count):\n",
" if (clf.tree_.feature[i] == -2):\n",
" leafs.append(i)\n",
"\n",
"# c = class index\n",
"for leaf in leafs:\n",
" for c in range(3):\n",
" #pairs(neg, pos)\n",
" class_pairs[c].append((clf.tree_.value[leaf][0].sum() - clf.tree_.value[leaf][0][c], clf.tree_.value[leaf][0][c]))\n",
"\n",
"#pairs sorting\n",
"for c in range(3):\n",
" class_pairs[c] = sorted(class_pairs[c], key=lambda t: t[0]/max(1,t[1]))\n",
"print(class_pairs)\n",
"\n",
"for i in range(1, len(leafs) + 1):\n",
" for c in range(3):\n",
" roc_curves[c][0].append(class_pairs[c][i - 1][0] + roc_curves[c][0][i - 1])\n",
" roc_curves[c][1].append(class_pairs[c][i - 1][1] + roc_curves[c][1][i - 1])\n",
"\n",
"roc_curves"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"