{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import imageio as imio\n",
    "import matplotlib.pyplot as plt\n",
    "import collections"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si tienes algún error al ejecutar la celda anterior verifica que tu ambiente de anaconda tenga las librerías necesarias."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Clasificación con K-nearest neighbors\n",
    "En este notebook vamos a implementar K-nearest neighbors, nuestro algoritmo va a **clasificar** a que raza pertenece la imagen de un perro, para entrenar nuestro algoritmo vamos a utilizar un set de datos contiene imagenes de 120 razas de perros, en total son 10,222 imagenes. No trabajaremos directamente con las imágenes, existe un paso de pre-procesamiento que toma como entrada una imágen y devuelve un vector para la imágen, el vector puede ser considerado un vector de features que describen el contenido de la imágen. No nos preocuparemos de momento  por como se obtiene este vector o calcula exactamente, el pre-procesamiento ya fue dado.\n",
    "\n",
    "Data: [Dog Breed Identification](https://www.kaggle.com/c/dog-breed-identification/data)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Pre-procesamiento\n",
    "\n",
    "El pre-procesamiento ya fue realizado y el dataset preparado y esta listo para usarse, para esto se aplico un proceso conocido como \"transfer learning\" usando la red neuronal \"Inception\" de Google(tema a cubrirse en proximas clases)  , para esta tarea ignoremos este detalle  y pensemos que se nos da un dataset con features donde cada columna representa una característica de entrada importante,pero no nos interesa conocer los detalles. \n",
    "\n",
    "Estas features/características fueron almacenadas en un matriz de numpy.\n",
    "\n",
    "En esta parte del notebook solo hay que ejecutar las celdas para cargar los datos, estos datos van a servir de entrada para nuestra implementación de K-nearest neighbors con tensorflow.\n",
    "\n",
    "### Referencias opcionales para transfer learning y \"Google inception\"\n",
    "##### Inception\n",
    "https://www.tensorflow.org/tutorials/image_recognition\n",
    "\n",
    "https://research.googleblog.com/2016/03/train-your-own-image-classifier-with.html\n",
    "\n",
    "##### Transfer Learning\n",
    "https://www.datacamp.com/community/tutorials/transfer-learning"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "images_inception_features = './pre-processing/training_inception_features_array.npy'\n",
    "training_image_labels_array = './pre-processing/training_image_labels_array.npy'\n",
    "training_image_names_array = './pre-processing/training_image_names_array.npy'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "TRAIN_DICTIONARY_KEYS = [\"images\", \"label\",\"features\"]\n",
    "\n",
    "training_set_dict = dict()\n",
    "    \n",
    "training_set_dict['images_name'] = np.load(training_image_names_array, allow_pickle=True)\n",
    "training_set_dict['label'] = np.load(training_image_labels_array, allow_pickle=True)\n",
    "training_set_dict['features'] = np.load(images_inception_features, allow_pickle=True)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  K-Nearest Neighbors"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La data la vamos a manejar en los siguientes 3 arreglos: \n",
    "\n",
    "- features    : features de la imagen dadas por google Inception\n",
    "- labels      : label de la imagen, identifica que raza es\n",
    "- images_name : nombre de la imgen original, si descargan el dataset con link en la descripcion pueden buscar la imagen original por este nombre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Cargamos las features y las labes de cada feature\n",
    "features = training_set_dict['features']\n",
    "labels = training_set_dict['label']\n",
    "images_name = training_set_dict['images_name']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10222"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(features)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las labels pertenecientes a las features tienen el mismo indice, Ejemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Label:  golden_retriever\n",
      "Nombre:  0021f9ceb3235effd7fcde7f7538ed62\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_3076/629895043.py:7: DeprecationWarning: Starting with ImageIO v3 the behavior of this function will switch to that of iio.v3.imread. To keep the current behavior (and make this warning disappear) use `import imageio.v2 as imageio` or call `imageio.v2.imread` directly.\n",
      "  img = imio.imread('./data/train/'+images_name[4]+'.jpg')\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7014a49e3850>"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 235068 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Indice 4\n",
    "\n",
    "print('Label: ',labels[4])\n",
    "\n",
    "print('Nombre: ',images_name[4])\n",
    "\n",
    "img = imio.imread('./data/train/'+images_name[4]+'.jpg')\n",
    "plt.imshow(img)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementa tu codigo de  K-Neighbors aqui:\n",
    "\n",
    "El siguiente es un ejemplo de como puedes  definir tu función para predecir la raza sobre un punto x_predict ,a través de uscar los \"k\" vecinos mas cercanos en X_train y devolver como predicción el \"y\" que mas se repite(la moda) correspondiente a esos \"k\" valores de X_train , este \"y\" estará dado por Y_train. Debemos usar numpy a pesar de que se presenta un ejemplo de tensorflow.\n",
    "\n",
    "Un ejemplo  de KNN en tensorflow: http://marubon-ds.blogspot.com/2017/09/knn-k-nearest-neighbors-by-tensorflow.html (este tiene diferencias, usar como guía no copiar literal, por ejemplo este usa una métrica de distancia diferente a la que debemos usar: distancia euclidiana )\n",
    "\n",
    "\n",
    "#### Instrucciones:\n",
    "1. Implementar KNN en la función predecir_raza_KNN usando numpy usando distancia euclidiana\n",
    "2. Ejecutar el modelo con las 2 imagenes de prueba.\n",
    "\n",
    "#### Tip\n",
    "Algunas funciones útiles:\n",
    "* np.sum(): sumar los elementos de un tensor.\n",
    "* np.sqrt(): calcular la raíz cuadrada de los elementos de un tensor, por ejemplo usada en la fórmula de distancia euclídiana.\n",
    "* np.argsort(): ordenar los elementos de un tensor y devolver las posiciones ordenadas, por ejemplo para ordenar las distancias.\n",
    "* collections.Counter(): crear un contador(por ejemplo para contar cuantas ocurrencias hay para cada clase)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "def predecir_raza_KNN(x_predict,X_train,Y_train,k):\n",
    "    # 1. Distancia euclidiana vectorizada (sin loop):\n",
    "    #    broadcasting: (n_train, n_features) - (n_features,) → resta fila a fila sin loop\n",
    "    diferencia_train_vs_predict = X_train - x_predict\n",
    "    distancias_euclidianas = np.sqrt(np.sum(diferencia_train_vs_predict**2, axis=1))\n",
    "\n",
    "    # 2. Índices ordenados por distancia, tomamos los k más cercanos\n",
    "    indices_k_vecinos_mas_cercanos = np.argsort(distancias_euclidianas)[:k]\n",
    "\n",
    "    # 3. Labels correspondientes a los k vecinos más cercanos\n",
    "    labels_k_vecinos = Y_train[indices_k_vecinos_mas_cercanos]\n",
    "\n",
    "    # 4. La clase más frecuente entre los k vecinos (moda = predicción final)\n",
    "    conteo_por_raza = collections.Counter(labels_k_vecinos)\n",
    "    y = conteo_por_raza.most_common(1)[0][0]\n",
    "\n",
    "    return y #asigna a \"y\" la predicción resulatnte"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Probamos el algoritmo con nuevas imagenes \n",
    "\n",
    "Como buena practica luego de entrenar nuestro algoritmo, lo probamos con imagenes que no ha visto antes "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "test_features = np.load('./data/test/test_inception_features_array.npy', allow_pickle=True)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prueba 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_3076/1681883470.py:1: DeprecationWarning: Starting with ImageIO v3 the behavior of this function will switch to that of iio.v3.imread. To keep the current behavior (and make this warning disappear) use `import imageio.v2 as imageio` or call `imageio.v2.imread` directly.\n",
      "  img_test = imio.imread('./data/test/test1.jpg')\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7014a3181600>"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 290236 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "img_test = imio.imread('./data/test/test1.jpg')\n",
    "plt.imshow(img_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicción Prueba 1: german_shepherd\n"
     ]
    }
   ],
   "source": [
    "predict_image = test_features[0]\n",
    "\n",
    "## Ejecuta tu modelo con la entrada de `predict_image`\n",
    "# Deberiamos obtener: german_shepherd\n",
    "raza_predicha_prueba_1 = predecir_raza_KNN(predict_image, features, labels, k=5)\n",
    "print(\"Predicción Prueba 1:\", raza_predicha_prueba_1)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prueba 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_3076/3034157317.py:1: DeprecationWarning: Starting with ImageIO v3 the behavior of this function will switch to that of iio.v3.imread. To keep the current behavior (and make this warning disappear) use `import imageio.v2 as imageio` or call `imageio.v2.imread` directly.\n",
      "  img_test = imio.imread('./data/test/test2.jpg')\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7014cd784760>"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 180808 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "img_test = imio.imread('./data/test/test2.jpg')\n",
    "plt.imshow(img_test)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicción Prueba 2: american_staffordshire_terrier\n"
     ]
    }
   ],
   "source": [
    "## Ejecuta tu modelo con la entrada de `predict_image`\n",
    "# Deberiamos obtener: american_staffordshire_terrier #\n",
    "predict_image = test_features[1]\n",
    "raza_predicha_prueba_2 = predecir_raza_KNN(predict_image, features, labels, k=5)\n",
    "print(\"Predicción Prueba 2:\", raza_predicha_prueba_2)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementa knn con sklearn para comparar tu implementación:\n",
    "\n",
    "En esta sección utilizarás los mismos datos y realizaras el mismo proceso pero con la implementación de knn existente en sklearn: https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html. Acá se dá un ejemplo de knn usando sklearn(para datos ficticios dados), debemos usar código similar pero con el dataset de razas de perros"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\n",
      "[[0.66666667 0.33333333]]\n",
      "sklearn Prueba 1: german_shepherd\n",
      "sklearn Prueba 2: american_staffordshire_terrier\n"
     ]
    }
   ],
   "source": [
    "X = [[0], [1], [2], [3]]\n",
    "y = [0, 0, 1, 1]\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "neigh = KNeighborsClassifier(n_neighbors=3) #entrenar el clasificador usando vecinos\n",
    "neigh.fit(X, y)\n",
    "print(neigh.predict([[1.1]])) #predicción de clase\n",
    "print(neigh.predict_proba([[0.9]])) #predicción de probabilidad\n",
    "\n",
    "# Entrenamos el clasificador sklearn con el dataset de razas de perros\n",
    "clasificador_sklearn = KNeighborsClassifier(n_neighbors=5)\n",
    "clasificador_sklearn.fit(features, labels)\n",
    "\n",
    "# Prueba 1 - debería predecir: german_shepherd\n",
    "raza_predicha_sklearn_prueba_1 = clasificador_sklearn.predict([test_features[0]])\n",
    "print(\"sklearn Prueba 1:\", raza_predicha_sklearn_prueba_1[0])\n",
    "\n",
    "# Prueba 2 - debería predecir: american_staffordshire_terrier\n",
    "raza_predicha_sklearn_prueba_2 = clasificador_sklearn.predict([test_features[1]])\n",
    "print(\"sklearn Prueba 2:\", raza_predicha_sklearn_prueba_2[0])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
