Esta ha sido la primera competición de Kaggle en la que he participado y estoy muy contento con el resultado obtenido. Dediqué muchas horas a analizar datos, escribir código y generar mi plantilla Data Science que me ayudó a lograr un gran resultado. Durante los últimos días de la competición estaba cerca de la posición número 500, sin embargo, cuando el dataset de pruebas cambió del público al privado, gané muchas posiciones hasta la #121, lo que significa estar en el 9% del ranking de la competición.
Empecé escribiendo algunos artículos sobre la competición en Medium (1), (2). En ellos explicaba una solución muy primitiva con la que no obtuve una buena puntuación. Después de la segunda publicación, comencé a trabajar en mi plantilla de Data Science y la apliqué a esta competición. Sólo haciendo esto conseguí un salto en accuracy de 0,39 a 0,65. Ese fue el salto más importante que conseguí en toda la competición y pude conseguirlo tras unos pocos días de trabajo. A partir de ese momento, seguí trabajando para mejorar mi solución, pero nunca obtuve una mejora de más de un 1% de con cada paso.
Los pasos más importantes que hice y que aumentaron más mi puntuación fueron agregar una gran cantidad de feature engineering y hacer feature selection.
La competición
El objetivo de la competición era predecir en qué tipo de superficie se movía un robot (madera, cemento, etc.). Para predecir esto, proporcionaron datos del movimiento de los robots, incluida la orientación dada en cuaterniones (x, y, z, w), velocidad angular (x, y, z) y aceleración lineal (x, y, z). Estos datos eran dados en forma de serie temporal, agrupados en sets de 128 medidas.
Es muy interesante seguir los kernels publicados en Kaggle de otros competidores. Justo después de que acabara la competición, los participantes con mejores resultados mostraron cómo los obtuvieron. Estoy un poco decepcionado con los resultados finales porque uno de los factores principales que dieron la victoria fue darse cuenta que el conjunto de datos que dieron dividido en fragmentos de 128 mediciones, esos fragmentos de mediciones formaban parte de sesiones de grabación más grandes también disponibles dentro del conjunto de datos . Básicamente, solo tienes que comparar los diferentes fragmentos entre sí y ver cuáles pertenecen al mismo fragmento grande.
Decidí que no implementaría esta función en mi código ya que esta era mi primera competición y mi objetivo era aprender Data Science, no ganar la competición. Este tipo de característica no daría ningún resultado relevante en un proyecto real, por lo que realmente no me enseñaría nada útil para mis proyectos futuros.
El código
Tras esta introducción, vamos a sumergirnos en el código y las técnicas utilizadas. Implementé mi solución utilizando Google Colab. Solo mostraré las partes importantes del proyecto, no entraré en detalles sobre cómo cargué el dataset o sobre el preprocesado de los datos. Solo me enfocaré en las partes más importantes que marcaron la diferencia en el resultado.
En una primera exploración de datos, lo más interesante fue la matriz de correlación:
#Correlation analysis sns.heatmap(df_train.corr(), annot=True, fmt='.2f')
Como puedes ver, algunas variables estaban altamente correlacionadas, como orientation_W y orientation_X. Durante los últimos pasos de la competición, esto me llevó a eliminar algunas de esas características altamente correlacionadas, aumentando la precisión del modelo.
Otro paso importante fue el feature engineering. Agregué muchas funciones basadas en los datos de series temporales originales, aunque perdí la información de las series temporales, obtuve un conjunto de nuevas funciones basadas en ellas que eran aún más informativas. Las características incluyen la media de la serie temporal, el valor máximo, el valor mínimo y muchas, muchas más. Un conjunto importante de características fue la Transformada de Fourier, que agrega la transformación al dominio de la frecuencia a los datos de serie temporal.
Para la validación del modelo, utilicé una estrategia muy simple, dividir los datos dados por la Kaggle en un conjunto de training y otro de validación. Utilicé el 70% de los datos para el training y el 30% restante para la validación del modelo, esto lo hice estratificando ambos conjuntos (teniendo el mismo porcentaje de cada categoría en ambos conjuntos).
El algoritmo
Luego vino la fase de selección del modelo. Probé muchos modelos, incluyendo la regresión lineal, Random Forest, XGBoost, LightGBM. El que me dio los mejores resultados en la primera tanda de pruebas fue el algoritmo Random Forest, así que elegí trabajar con él. Mirando en retrospectiva, lamento no haber utilizado también el algoritmo XGBoost, ya que creo que ajustando sus hiper-parámetros podría haber dado grandes resultados.
Usando Grid Search, encontré los mejores valores para los hiper-parámetros de un Random Forest con este dataset. Esos valores fueron:
n_estimators=5000 min_samples_leaf=1 max_features=0.3
Fueron encontrados utilizando Grid Search, probando distintos valores. Esta es la lista de valores probados:
n_estimators:[100,500,1000,2500,5000,10000] min_samples_leaf:[1, 3, 5, 10, 15, 20] max_features:['auto', 0.1, 0.3, 0.5]
Feature selection
Después de entrenar el modelo Random Forest, excluí las características menos importantes del modelo. Esto me dio un salto importante en accuracy.
important_features = feature_importances[feature_importances['importance']>0.001].index.values X_train = X_train[important_features]
Utilizando el dataFrame solo con las características importantes, reentrené el modelo y obtuve un aumento de accuracy.
Después de tener un modelo de trabajo, escribí una función para generar el archivo de envío.
Envío
Tras tener el modelo funcionando, escribí una función para generar el archivo de envío.
testpreds = rfc.predict(df_test) testpreds = le.inverse_transform(testpreds)dfres = pd.DataFrame() dfres['series_id'] = df_test.index dfres['surface'] = testpredsdfresf = dfres.groupby('series_id').agg(lambda x:x.value_counts().index[0])dfresf.to_csv('out.csv')
Ya solo quedaba cargar los resultados a Kaggle y disfrutar de la emoción y el nerviosismo mientras esperaba a la evaluación de los resultados y la correspondiente clasificación en el ranking. Es muy emocionante ver cómo vas subiendo posiciones en el ranking, incluso se podría decir que es adictivo.
Conclusión
La solución descrita en este post, no fue la primera solución que obtuve para la competición. El proyecto necesitó muchas iteraciones y probar diferentes ideas, muchas de ellas buenas y muchas no.
En general, fue un desafío muy interesante y una gran oportunidad para participar en un proyecto de Data Science con un objetivo definido y una gran experiencia de aprendizaje. Recomiendo totalmente seguir una competición de Kaggle desde el principio y luchar para conseguir las primeras posiciones del ranking.