Implementación básica de redes neuronales con Keras¶
En este documento veremos cómo podemos implementar redes neuronales sencillas usando la librería Keras. Ésta es una librería que ofrece una API muy cómoda y sencilla para definir los diferentes elementos de la red neuronal, y ajustar los parámetros relevantes de la misma. Veremos a continuación las características más importantes de la librería y cómo definir algunas redes neuronales sencillas. Pero antes, recuerda que en este documento se explican en profundidad algunos conceptos teóricos que veremos aquí más de pasada, como el concepto de función de activación, función de coste, etc. Conviene que le eches un vistazo si no recuerdas algunos de esos conceptos y sus posibilidades.
1. Introducción a Keras¶
Keras es una librería para desarrollo de redes neuronales desarrollada por François Chollet en 2015. Es de código abierto, y facilita mucho la tarea de crear capas de neuronas, conectarlas entre sí, definir funciones de activación, iniciar la etapa de entrenamiento, etc. Tanto es así que su API puede integrarse en otras librerías, como TensorFlow (otra librería de redes neuronales desarrollada por Google) o Theano. De hecho, TensorFlow 2.0 la ha incorporado como parte de su core.
En otras palabras, Keras va a proporcionar una interfaz de clases y métodos para crear y entrenar redes neuronales con muy pocos pasos. Pero, internamente, utilizará los mecanismos del sistema en que está embebida (TensorFlow, Theano, etc) para ejecutar la red neuronal de forma transparente para el usuario.
1.1. Instalación y primeros pasos¶
Dado que Keras forma parte de otro entorno más complejo como TensorFlow o Theano, debemos instalar ambas cosas para poderlas utilizar. Con este comando se instalan TensorFlow y Keras:
pip install tensorflow
Podemos comprobar qué versión de Keras tenemos instalada con el comando pip list
(viendo la versión concreta de Keras) o bien desde el propio código Python con algo como esto:
import keras
print(keras.__version__)
También podemos hacerlo accediendo al módulo keras
de la librería tensorflow
, obteniendo el mismo resultado:
import tensorflow as tf
print(tf.keras.__version__)
1.2. Elementos principales de Keras¶
Dentro de la librería keras
o tensorflow.keras
disponemos de multitud de elementos para poder crear nuestras redes neuronales. Vamos a empezar por los más habituales para crear redes sencillas:
1.2.1. Clases para construir la estructura de capas¶
La clase Sequential
, del paquete keras.models
, nos va a permitir crear una secuencia de capas de neuronas. Junto a ella, la clase Dense
, del paquete keras.layers
, permite crear una capa densa, la capa de neuronas más habitual. Consiste en una capa densamente conectada (es decir, todas las neuronas de esta capa se conectan con todas las de la capa anterior). Al crear la capa densa podemos especificar una serie de parámetros, como son:
- Número de neuronas que conformarán la capa
- Número de conexiones de entrada, normalmente expresado en el parámetro
input_dim
como una cantidad numérica, o eninput_shape
como una tupla. Este parámetro sólo será necesario especificarlo para la primera capa, para indicar cuántos datos de entrada se tienen. El resto de capas ya se conectarán automáticamente con las salidas de la capa anterior. - Función de activación que se aplicará a todas las neuronas de la capa, en el parámetro
activation
. Algunos posibles valores de este parámetro son: linear
: activación lineal, se emite lo mismo que se calcularelu
: aplica la función de activación ReLUsigmoid
: aplica la activación sigmoideasoftmax
: aplica la activación softmaxtanh
: aplica la activación de tangente hiperbólica- ... más información aquí
- Inicializador de pesos, en el parámetro
kernel_initializer
. Por defecto se le asignan valores aleatorios cercanos a 0, pero podemos elegir alguna función de distribución concreta para estos valores, como por ejemplo una distribución uniforme (kernel_initializer='uniform'
) - Existen otros parámetros no tan habituales, que podéis consultar aquí para más información.
Este ejemplo crea un modelo con una capa densa de 10 neuronas, que recibirán 8 datos de entrada, con una función de activación relu:
modelo = Sequential()
modelo.add(Dense(10, input_dim=8, activation='relu'))
De forma alternativa, también podemos emplear la capa Input
(del mismo paquete keras.layers
) para especificar la capa de entrada con sus neuronas, y luego conectarle la capa densa. El código anterior sería equivalente a este otro:
modelo = Sequential()
# Capa de entrada de 8 neuronas
modelo.add(Input(shape=(8,)))
# Capa densa conectada a la anterior, sin parámetro "input_dim"
modelo.add(Dense(10, activation='relu'))
1.2.2. Preparación y entrenamiento del modelo¶
Una vez definamos el modelo (añadiendo capas secuencialmente), debemos compilarlo para dejarlo preparado, a través de un método compile
. Aquí definimos cuál va a ser la función de coste o pérdida, a través de un parámetro loss
. Algunas de las funciones de coste más habituales en el parámetro loss
son:
mse
: error cuadrático medio entre los valores reales y los predichosmsle
: error cuadrático medio logarítmicomae
: error absoluto mediobinary_crossentropy
: para calcular entropía en clasificadores binarios (dos categorías)categorical_crossentropy
: para calcular entropía en clasificadores de más de dos categorías- ... aquí podéis consultar otras opciones
- También podemos definir nuestra propia función de coste, que reciba los valores predichos con los reales y devuelva una cantidad numérica. En la documentación del método "compile" explican algunos detalles.
Aquí vemos un ejemplo de uso de la función compile
:
modelo.compile(loss='mse')
El método summary
muestra por pantalla una información-resumen del modelo que hemos construido, si la queremos consultar. Una vez construido, el método fit
del modelo creado comenzará la etapa de entrenamiento. Deberemos indicar algunos parámetros:
- El conjunto de datos de entrada X que le proporcionamos a la red. El tamaño de cada registro o fila de X deberá coincidir con el tamaño de entrada de la primera capa (deberían ser 8 columnas de datos para el ejemplo anterior)
- El conjunto de resultados asociados a cada fila de valores de entrada (y).
- El número de iteraciones que queremos dar, en un parámetro llamado
epochs
. En cada iteración, se pasan todos los datos de entrada por la red, y luego se reajustan los pesos en base a la función de coste seleccionada, para la siguiente iteración. - Finalmente, hay un parámetro
verbose
que, si se pone a 0, no mostrará por pantalla información sobre el proceso y las iteraciones. Si se omite, nos va informando tras cada iteración de cómo van las métricas. - La función devuelve unos resultados estadísticos del entrenamiento, que luego se pueden aprovechar para mostrar curvas de aprendizaje y otras gráficas.
Aquí vemos un ejemplo:
datos_entrenamiento = modelo.fit(X_train, y_train, epochs=30)
1.2.3. Evaluación de resultados¶
Finalmente, llega la hora de evaluar el modelo ya entrenado para ver cómo se comporta. Podemos usar el método evaluate
para obtener el coste o pérdida resultante, indicando el conjunto de datos de entrada y las salidas esperadas. Podemos evaluarlo contra el conjunto de test, o contra el propio conjunto de entrenamiento también, para comprobar si los resultados difieren mucho. Por ejemplo:
coste = modelo.evaluate(X_test, y_test)
Alternativamente, podemos usar el método predict
para que prediga un conjunto de valores de salida a partir de unos de entrada, y podemos emplear estos valores de salida para cotejarlos con los que debería haber mostrado:
y_pred = modelo.predict(X_test)
1.3. Un primer ejemplo sencillo¶
Vamos a desarrollar una pequeña red neuronal con los componentes anteriores, para ver cómo funciona todo. Usaremos para ello este archivo CSV que almacena diferentes datos de salud de distintos pacientes. Es una adaptación de un dataset que podéis encontrar en este ejemplo de Kaggle. Vamos a relacionar la edad (columna age) con la presión sanguínea (columna ap_hi)
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense, Input
from sklearn.model_selection import train_test_split
datos = pd.read_csv('datos_salud.csv')
# Creamos estructura de la red
modelo = Sequential()
modelo.add(Input(shape=(1,)))
modelo.add(Dense(1, activation='linear', kernel_initializer='uniform'))
# Compilamos la red con los parámetros elegidos (coste)
modelo.compile(loss='mae')
modelo.summary()
# Entrenamos la red con los datos de entrenamiento
X_train, X_test, y_train, y_test = \
train_test_split(datos['age'], datos['ap_hi'], test_size=0.15, random_state=0)
datos_entrenamiento = modelo.fit(X_train, y_train, epochs=100)
# Evaluamos la red con los datos de entrenamiento y test
coste_train = modelo.evaluate(X_train, y_train)
coste_test = modelo.evaluate(X_test, y_test)
print(f"Coste train: {coste_train}")
print(f"Coste test: {coste_test}")
# Obtenemos la predicción del test y la cotejamos con la real
y_pred = modelo.predict(X_test).flatten()
print(pd.DataFrame({'edades': X_test, 'reales': y_test, 'predichos': y_pred}))
Analicemos algunas líneas de código del ejemplo anterior. Cuando añadimos la capa Dense
, estamos creando una capa de 1 neurona en este caso, que va a recibir los datos de la capa de entrada (uno en este ejemplo, tal y como hemos especificado en input_dim
). Esta será directamente la capa de salida, al no haber más capas. Recibirá la entrada y emitirá un resultado.
Hemos aplicado una función de coste MAE (error absoluto medio). Podemos lanzar el programa varias veces, y veremos que cada vez obtendremos resultados distintos, no demasiado satisfactorios en este caso ya que, aunque los datos tienen cierta dependencia lineal, ésta no es muy evidente, y las diferencias entre los valores predichos y los reales pueden ser muy distintos, sobre todo con una red de una sola capa y una sola neurona.
Respecto a la función summary
, nos da una información como esta:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1) 2
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
Vemos que se detecta una única capa densa, con un valor de salida. Respecto al número de parámetros entrenables (2), se corresponderían con el peso y el bias de la única neurona que tenemos, para el único valor de entrada que se le proporciona. De este modo, la neurona mostrará una salida y = wx + b, pudiendo configurar w y b para ajustar la recta.
Al iniciarse el entrenamiento con fit
veremos por pantalla los datos al final de cada etapa o epoch. Podemos observar cómo la función de pérdida va reduciendo su valor en cada iteración, hasta estabilizarse (los resultados variarán en cada ejecución):
Epoch 1/100
1860/1860 [==============================] - 2s 859us/step - loss: 78.2451
Epoch 2/100
1860/1860 [==============================] - 2s 830us/step - loss: 19.3909
Epoch 3/100
1860/1860 [==============================] - 2s 832us/step - loss: 18.4683
Epoch 4/100
1860/1860 [==============================] - 2s 862us/step - loss: 18.4247
...
Epoch 97/100
1860/1860 [==============================] - 2s 832us/step - loss: 15.7043
Epoch 98/100
1860/1860 [==============================] - 2s 842us/step - loss: 15.6873
Epoch 99/100
1860/1860 [==============================] - 2s 860us/step - loss: 15.6712
Epoch 100/100
1860/1860 [==============================] - 2s 847us/step - loss: 15.6562
2. Otros componentes de Keras¶
Ahora que ya hemos tenido una primera toma de contacto con Keras y hemos creado, entrenado y probado nuestra primera red neuronal, vamos a ver qué otras variantes ofrece la librería para ciertos casos concretos.
2.1. Procesamiento por lotes¶
En el método fit
que inicia el entrenamiento de la red neuronal podemos especificar un tamaño de lote, lo que define un proceso conocido como batch learning o aprendizaje por lotes. La idea es que, en lugar de pasar todo el conjunto de datos de entrada de golpe a la red y luego recalcular los pesos, se divide ese conjunto de datos de entrada en lotes de tamaño fijo N. Esto aporta algunas ventajas:
- Se requiere menos memoria, ya que en lugar de almacenar todas las filas del conjunto de entrada, se van almacenando y pasando a la red por lotes
- La red aprende más rápido, ya que tras cada lote se recalculan los pesos para el siguiente.
Como inconveniente de este método, si el tamaño del lote es muy pequeño, el reajuste de pesos puede que no sea el óptimo, y se tardará más en encontrar el error mínimo.
Para definir el tamaño del lote usaremos el parámetro batch_size
en el método fit
. Por defecto, si no indicamos nada, se toma un tamaño de 32.
datos_entrenamiento = modelo.fit(X_train, y_train, epochs=30, batch_size=24)
De hecho, el número que aparece durante el entrenamiento en cada epoch (1860 en el ejemplo anterior) indica cuántos lotes se han procesado en esa epoch, para el tamaño de batch indicado.
2.2. Los optimizadores y la tasa de aprendizaje¶
En la etapa de descenso por gradiente, para minimizar los resultados de la función de coste se puede aplicar un optimizador. Básicamente es una mejora sobre el descenso por gradiente habitual, que hace que el sistema se aproxime mejor o más rápido al mínimo. Esto se consigue añadiendo un parámetro optimizer
al método compile
de nuestro modelo. Podemos elegir algunos optimizadores predefinidos, como sgd o adam, bien escribiendo el nombre del optimizador como un string, o instanciando la clase que lo representa, dentro del paquete keras.optimizers
(más información aquí). En este último caso, podemos especificar el learning rate o tasa de aprendizaje, que determinará la longitud de los "saltos" que dará el sistema buscando el mínimo error. Si es demasiado grande puede que no lo encuentre, y si es demasiado pequeño puede que tarde mucho en encontrarlo.
Aquí vemos un ejemplo de uso simple:
modelo.compile(loss='mse', optimizer='sgd')
Y aquí otro donde usamos la clase asociada, fijando nosotros el learning rate
from keras.optimizers import SGD
...
sgd = SGD(learning_rate=0.05)
modelo.compile(loss='mse', optimizer=sgd)
2.3. Las métricas¶
Al compilar el modelo, podemos indicar que se calculen algunas métricas en el proceso, como la precisión o tasa de aciertos obtenidos (accuracy), indicándolo en el parámetro metrics
. Aquí vemos un ejemplo de uso:
modelo.compile(loss='mse', optimizer='sgd', metrics=['accuracy'])
Esto permitirá ver esta precisión en la etapa de entrenamiento, y recogerla al final junto al resto de resultados del proceso.
coste, precision = modelo.evaluate(X_test, y_test)
NOTA: la precisión (accuracy) sólo estará disponible en redes de clasificación, no de regresión.
2.4. Afinando el ejemplo¶
Vamos a afinar un poco más el ejemplo anterior sobre estimación de la presión sanguínea según la edad. Definiremos en este caso un optimizador Adam y un batch_size de 24:
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense, Input
from sklearn.model_selection import train_test_split
datos = pd.read_csv('datos_salud.csv')
# Creamos estructura de la red
modelo = Sequential()
modelo.add(Input(shape=(1,)))
modelo.add(Dense(1, activation='linear', kernel_initializer='uniform'))
# Compilamos la red con los parámetros elegidos (coste)
modelo.compile(loss='mae', optimizer='adam')
modelo.summary()
# Entrenamos el modelo
X_train, X_test, y_train, y_test = \
train_test_split(datos['age'], datos['ap_hi'], test_size=0.15, random_state=0)
datos_entrenamiento = modelo.fit(X_train, y_train, epochs=100, batch_size=24)
# Evaluamos la red con los datos de test
coste_train = modelo.evaluate(X_train, y_train)
coste_test = modelo.evaluate(X_test, y_test)
print(f"Coste train: {coste_train}")
print(f"Coste test: {coste_test}")
y_pred = modelo.predict(X_test).flatten()
print(pd.DataFrame({'reales': y_test, 'predichos': y_pred}))
En este caso, vemos que el coste también va decreciendo y convergiendo, y los resultados pueden ser ligeramente mejores que en el caso anterior al aplicar el optimizador y ajustar el tamaño del batch (aunque tampoco mejoran mucho por las limitaciones de la propia red en sí). La convergencia también es más rápida. Notar también cómo empleamos el método predict
para predecir los valores (sobre el conjunto de test, en este caso), y así poderlos comparar con los reales
3. Trabajando con más capas y opciones¶
Plantearemos ahora otro ejemplo diferente sobre el mismo dataset anterior. Tomaremos todas las columnas salvo el id y determinaremos si esa persona padece o no problemas cardíacos (última columna cardio).
Lo primero que debemos hacer es inicializar el programa con las librerías necesarias y cargar el dataset, como es habitual:
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense, Input
from sklearn.model_selection import train_test_split
datos = pd.read_csv('datos_salud.csv')
3.1. Procesamiento inicial de los datos¶
En este caso, hay un total de 13 columnas en el dataset, pero la primera (id) es irrelevante para nuestro estudio, y la última (cardio) es nuestra variable objetivo.
Antes de separar en X e y, vamos a codificar las columnas categóricas. Usaremos la instrucción map
de Pandas para controlar mejor el valor que se asigna en cada caso, ya que es importante en todas las columnas, salvo gender, donde asignaremos los valores arbitrariamente (0 = hombre, 1 = mujer).
datos['gender'] = datos['gender'].map({'M':0, 'F':1})
datos['smoke'] = datos['smoke'].map({'No':0, 'Yes':1})
datos['alco'] = datos['alco'].map({'No':0, 'Yes':1})
datos['active'] = datos['active'].map({'No':0, 'Yes':1})
datos['cholesterol'] = datos['cholesterol'].map({'Normal':0, 'Above Normal':1, \
'Well Above Normal':2})
datos['gluc'] = datos['gluc'].map({'Normal':0, 'Above Normal':1, \
'Well Above Normal':2})
datos['cardio'] = datos['cardio'].map({'No':0, 'Yes':1})
Ahora guardaremos en la tabla X
todas las columnas relevantes (menos la primera y la última), y en la y
esa columna cardio. Dividimos también el conjunto en entrenamiento y test, guardando un 20% de datos para este último.
X = datos.iloc[:, 1:12]
y = datos['cardio'].values
X_train, X_test, y_train, y_test = \
train_test_split(X, y, test_size=0.2, random_state=0)
A continuación, vamos a escalar los datos X del conjunto de entrenamiento que sean numéricos, y aplicar dicho escalado a los de test. Usaremos la clase StandardScaler
de SKLearn:
from sklearn.preprocessing import StandardScaler
...
columnas_numericas = ['age', 'height', 'weight', 'ap_hi', 'ap_lo']
scaler_X = StandardScaler()
X_train[columnas_numericas] = scaler_X.fit_transform(X_train[columnas_numericas])
X_test[columnas_numericas] = scaler_X.transform(X_test[columnas_numericas])
Notar que escalamos el conjunto de entrenamiento con fit_transform
y aplicamos ese escalado sobre el de test con transform
, para no tener dos escalados distintos en entrenamiento y test que distorsionarían el resultado final. Así es como si todo tuviera un mismo escalado (misma media y desviación típica para los dos conjuntos).
3.2. Construcción de la red¶
Llega la hora de construir la red: tenemos un total de 11 datos de entrada. Crearemos una capa oculta que procese los datos y una capa de salida que emita el resultado:
- La capa de salida tendrá una única neurona que emitirá si la persona tiene problemas cardíacos o no (clasificador binario)
- ¿Cuántas neuronas debe tener la capa oculta? Existen diferentes teorías sobre esto, y se puede experimentar con distintos tamaños para ver cuál da mejor resultado. Un buen punto de partida puede consistir en asignar a la capa oculta la media de neuronas entre la capa de entrada y la de salida. En nuestro caso, tenemos 11 neuronas en la capa de entrada y 1 en la capa de salida, así que nuestra capa oculta puede tener 6 neuronas, por ejemplo.
Con estos datos construimos nuestro modelo. En la capa oculta definiremos una función de activación relu
para que se desactiven las neuronas irrelevantes para cada caso, y en la capa de salida una función sigmoid
para que aproxime a 0 o 1 el resultado, ajustándonos así a los valores de y
codificados antes.
modelo = Sequential()
modelo.add(Input(shape=(11,)))
modelo.add(Dense(6, activation='relu', kernel_initializer='uniform'))
modelo.add(Dense(1, activation='sigmoid', kernel_initializer='uniform'))
A la hora de definir la dimensión de entrada podemos especificar un valor fijo, o bien valernos de la dimensión de los datos de entrada. En este caso, el número de columnas que usaremos lo tenemos disponible en X.shape[1]
, por lo que podríamos haberlo hecho así:
modelo = Sequential()
modelo.add(Input(shape=(X.shape[1],)))
modelo.add(Dense(6, activation='relu', kernel_initializer='uniform'))
modelo.add(Dense(1, activation='sigmoid', kernel_initializer='uniform'))
3.3. Preparación, entrenamiento y resultados¶
Compilamos el modelo, usando como función de pérdida la entropía binaria (ya que estamos implementando un clasificador binario), y como optimizador usaremos el optimizador adam en esta ocasión. Añadiremos también como métrica la precisión (accuracy), al tratarse de un modelo de clasificación:
modelo.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
Ahora iniciamos el proceso de entrenamiento. Lo ejecutaremos durante 10 epochs, con un tamaño de batch de 24 muestras:
datos_entrenamiento = modelo.fit(X_train, y_train, epochs=10, batch_size=24)
Evaluamos los resultados sobre el conjunto de test:
coste_train, precision_train = modelo.evaluate(X_train, y_train)
coste_test, precision_test = modelo.evaluate(X_test, y_test)
print(f"Coste train: {coste_train} / Coste test: {coste_test}")
print(f"Prec. train: {precision_train} / Prec. test: {precision_test}")
Los resultados que obtengamos pueden ser parecidos a éstos:
Coste train: 0.5611541867256165 / Coste test: 0.5699867010116577
Prec. train: 0.7287678718566895 / Prec. test: 0.7264285683631897
Como vemos, la red consigue casi un 73% de acierto en el diagnóstico, tanto para el conjunto de entrenamiento como para el de test.
Ejercicio 1
Sobre el ejemplo anterior, una vez la red ya esté entrenada, queremos conocer la estimación que hará para una persona con estos datos:
- Edad: 50 años
- Género: hombre
- Altura: 175 cm
- Peso: 100 kg
- Tensión alta: 150
- Tensión baja: 90
- Colesterol: Above Normal
- Glucosa: Normal
- Fumador: Sí
- Alcohol: No
- Activo: No
Ejercicio 2
Sobre el mismo dataset anterior, ahora construiremos una red neuronal que prediga la presión sanguínea usando como datos de entrada la edad (age), altura (height), peso (weight), nivel de colesterol (cholesterol), glucosa (gluc), fumador (smoke) y si es activo (active). Aísla los datos relevantes, escala los que sean numéricos, codifica (label encoding) los categóricos y crea una red con una capa de entrada, una capa oculta y otra de salida, con las funciones de activacion y parámetros que consideres oportunos. Anota la pérdida final que consigues con la mejor de las configuraciones que hayas obtenido, y cotéjala con el ejemplo simple que hemos hecho en el apartado anterior
Ejercicio 3
Utiliza este dataset sobre cáncer de mama, que se puede consultar en este enunciado de Kaggle. Hay varias columnas que tienen datos sobre los diferentes tumores estudiados: radio, textura, perímetro, concavidades... La columna diagnosis es nuestro objetivo. Tenemos que, dadas las características de un tumor en una mama, identificar si se trata de un tumor benigno (B) o maligno (M). Crea una red neuronal con tantas neuronas de entrada como columnas relevantes haya (todas menos las dos primeras), una capa oculta de tamaño adecuado y una capa de salida que dé el diagnóstico. Anota la precisión final que consigues con las epochs que hayas determinado.
Nota
Al leer el CSV puede que se incluya una última columna Unnamed con valores NaN, que también deberás descartar
4. Usando Keras/TensorFlow en Google Colab¶
Si estamos acostumbrados a trabajar con la herramienta Google Colab, debemos saber que ya tiene incorporadas las librerías de Keras y TensorFlow, con lo que desarrollar, entrenar y probar redes neuronales en ese entorno puede resultar muy sencillo y útil, especialmente si disponemos de un equipo con capacidad de procesamiento limitada.
Una de las ventajas que ofrece el trabajo con Google Colab es que podemos acceder fácilmente a los archivos que tengamos almacenados en nuestra cuenta de Google Drive. Para ello tenemos que montar la unidad (en la ruta /content/drive
) y luego ya acceder a la subcarpeta concreta que queramos. Este fragmento de código nos sitúa en la carpeta Ejercicio dentro de nuestra carpeta de Google Colab.
import os
from google.colab import drive
from keras.models import Sequential
#... Resto de librerías que necesitemos
# Montamos nuestra unidad de Google Drive
drive.mount('/content/drive')
# Accedemos a la ruta que nos interese
path = os.path.join('/content/drive', 'My Drive', 'Colab Notebooks', 'Ejercicio')
os.chdir(path)
# A partir de aquí podemos leer CSV o ficheros de la ruta montada (imágenes, etc)