Artículos

Predecir la fusión de pull requests de Node.js con aprendizaje automático

En este artículo explicaré un uso sencillo del aprendizaje automático (ML) para evaluar si puede predecirse con precisión la aceptación de un pull request (PR) al crearlo. El experimento busca generar un programa fiable que ayude al integrador a gestionarlos en un proyecto. Como los proyectos grandes reciben PR continuamente, exploramos un modelo replicable para filtrar cuanto antes los no deseados.

Los trabajos de Yu et al.1 y Gousios et al.2 proponen modelos entrenados con varios repositorios para predecir la aceptación incluso en proyectos distintos. Esto reduce la exactitud, especialmente si se entrena con proyectos A y B y se predice en C, cuyos criterios pueden ser diferentes. Nuestro enfoque usa solo datos del mismo proyecto. Predecimos PR de Nodejs con datos disponibles al enviarlos para evaluar inmediatamente si se fusionarán.

Construir el predictor

Construir el predictor
Construir el predictor

Debemos crear un predictor y encontrar los parámetros adecuados. Seguimos estos pasos:

  1. Definir características relevantes para evaluar un PR.
  2. Recopilar datos de las fuentes de GitHub del proyecto.
  3. Normalizar los datos en un almacén persistente para los modelos ML.
  4. Elegir el modelo que mejor funcione con los datos.
  5. Evaluar el predictor en un conjunto separado.

Definir características

En el paso 1 nos basamos en Yu et al. y usamos estas características:

description_complexity Número de palabras en la descripción y el título. Una descripción más larga puede indicar un PR complejo con muchas funciones, más difícil de aprobar.
log_hotness Se refiere al número H de actividad del área del proyecto: archivos modificados en los últimos 3 meses que también están en el PR. Toma el valor log(1 + H). Una mayor actividad puede indicar demasiados PR que afectan archivos centrales y generan controversia.
log_churn Se refiere al volumen de cambios del PR C: suma de líneas eliminadas, modificadas y añadidas. Toma el valor log(1 + H).
is_integrator Indica si el autor es integrador: alguien que fusionó un PR al menos una vez en la historia del proyecto. Es más probable que sus PR tengan éxito. Vale 1 o 0 según sea integrador.
has_tests Indica si el PR incluye pruebas, lo que puede favorecer su aprobación. Vale 1 o 0 según tenga pruebas. Se determina comparando el nombre de cada archivo añadido o modificado con expresiones regulares para identificar pruebas.
success_rate Número relacionado con la aceptación previa de los PR del autor: proporción entre aprobados y enviados. Una tasa mayor puede indicar aceptación.
social_conn Suma de seguidores y seguidos del autor. Más conexiones sociales pueden indicar mayor éxito.
requested_reviewers Indica si el autor solicitó revisores. Sugiere interés en la aceptación del PR. Incluir un revisor puede aumentar la probabilidad de aceptación.
created_friday Las solicitudes creadas el viernes tienen más probabilidades de no aceptarse o aceptarse más tarde por el fin de semana, aumentando la cola de PR.

En el futuro podríamos incluir cumplimiento de requisitos de mensajes de commit, pruebas unitarias superadas o cantidad de PR abiertos. También datos cronológicos: los requisitos cambian y un PR antiguo se evalúa con estándares distintos. Clasificar datos temporales es un problema común en ML porque los criterios dependen de cuándo se crearon y clasificaron. Brownlee3 muestra una forma de abordar pronósticos.

Carga ETL

Diagrama del proceso ETL
Diagrama del proceso ETL

Diagrama del proceso ETL

Los pasos 2 y 3 usan extracción, transformación y carga (ETL), que consume la API de GitHub REST (V3), tanto REST como GraphQL, y guarda características en MongoDB. Recupera datos de los PR mediante la API. Para calcular volumen de cambios y actividad usamos un clon del repositorio y un cliente git para comparar ramas de origen y destino. La API GraphQL (V4) obtiene información de usuarios, incluidas conexiones sociales (social_conn). Permite consultas más ricas entre entidades, como las conexiones de todos los autores, y reduce solicitudes URL. Este código obtiene datos paginados de MergedEvents de un PR para detectar si se fusionó.

{
 repository(owner: nodejs, name: node){
   name,
   pullRequests(first: 100, states: MERGED) {
     nodes {
       id,
       url,
       timeline(last: 100) {
         nodes {
             ... on MergedEvent {
            id,
             actor {
               login
             }
             createdAt,
             url,
           }
         }
       }
     },
     pageInfo {
       hasNextPage,
       hasPreviousPage,
       endCursor,
       startCursor,
     },
     totalCount
   }
  }
}

No pudimos usar GraphQL en todos los casos por límites de solicitudes más bajos que ralentizan la recuperación. Las llamadas complejas también fueron más lentas que sus equivalentes V3. Diseñamos ETL para reanudarse y reiniciarse, pues puede tardar horas. Lo dividimos en estas tareas:

1 fetchCommits Obtiene todos los commits de la rama master mediante la API REST
2 fetchPullRequests Obtiene los metadatos de todos los PR mediante la API REST
3 fetchCommitDiffs Clona localmente el repositorio git y las ramas de origen de los PR para calcular actividad y volumen de cambios
4 fetchUserEvents Guarda los eventos recientes del usuario para calcular conexiones sociales. No se usó en el conjunto final de características.
5 fetchUserInfo Obtiene metadatos de usuario mediante la API GraphQL
6 fetchIntegrators Obtiene los integradores consultando MergedEvents del pull request
7 computeHotness Usa el repositorio local para comparar las ramas de origen y destino del PR y calcular
8 guessCommitPRRefs Deduce eventos de fusión mediante el historial. Es específico de NodeJS, que cierra PR y los fusiona en un commit separado en lugar de usar la función de GitHub.
9 setUsersId Decodifica información de usuario en base64 para relacionar datos de PR y usuarios.
10 Normalization Combina los datos para generar información lista para entrenar: una tabla de características y clasificación (fusionado/no fusionado)

Tareas ETL para cargar y normalizar datos

Construcción y evaluación del predictor

XKCD: sobreajuste de precedentes electorales
XKCD: sobreajuste de precedentes electorales

Sobreajuste de precedentes electorales (fuente: XKCD)

Con los datos listos, los pasos 4 y 5 usan scikit-learn. Para evitar sobreajuste, dividimos en entrenamiento y validación: 90% de los PR para entrenar y 10% para validar. Hacemos validación cruzada dividiendo el entrenamiento en 10 grupos con k-fold. Probamos estos predictores:

  • Naive Bayes (Bernoulli y gaussiano)
  • Árboles de decisión
  • K vecinos más cercanos
  • Análisis discriminante lineal
  • Regresión logística
  • Perceptrones multicapa
  • Centroides más cercanos
  • Análisis discriminante cuadrático
  • Regresión Ridge
  • Máquinas de vectores de soporte (SVC), con soporte C y lineales
  • Descenso de gradiente estocástico (SGD)
  • Algoritmos pasivo-agresivos
Diagrama de validación k-fold con 4 grupos
Diagrama de validación k-fold con 4 grupos

Diagrama de validación k-fold con 4 grupos (fuente: Wikipedia)

Recopilamos 10,200 PR del repositorio nodejs entre noviembre de 2014 y noviembre de 2017. Los datos brutos tenían esta forma. Cerca del 76% de los MR recuperados se fusionaron; es un conjunto considerable para entrenar la mayoría de los predictores.

created_ friday description_ complexity log_hotness log_churn is_integrator
Media

0.172

134.500

1.147

5.944

0.498

Desv. estándar

0.377

389.832

0.915

3.901

0.500

Mín.

0.000

1.000

0.000

0.000

0.000

Máx.

1.000

19,825.000

4.043

15.523

1.000

Análisis de datos de PR
has_tests success_rate social_conn requested_review was_merged
Media

0.721

0.514

4.973

0.019

0.758

Desv. estándar

0.448

0.221

1.801

0.138

0.428

Mín.

0.000

0.000

0.000

0.000

0.000

Máx.

1.000

1.000

11.823

1.000

1.000

Probamos 21 clasificadores y obtuvimos exactitud del 77% al 88% en entrenamiento. Elegimos SVC con soporte C, con una media del 87% en 10 validaciones cruzadas. Es bueno para entrenamiento, pero la evaluación real usa datos de prueba. Allí alcanzó una precisión algo menor, del 78%, todavía aceptable.

Precisión Exhaustividad Puntuación F1 Soporte
No fusionado

0.76

0.22

0.34

264

Fusionado

0.78

0.98

0.87

753

Promedio

0.78

0.78

0.73

1017

Métricas de precisión sobre datos de prueba

Conclusiones y mejoras futuras

Los experimentos muestran que podemos predecir MR con exactitud y aliviar la carga de integradores ante grandes colas. Los datos temporales necesitan tratamiento especial: los criterios cambian y quizá convenga filtrar MR antiguos. Más características mejorarán las predicciones. Extraer datos de GitHub puede llevar mucho tiempo y requiere manejar desconexiones y límites de API para recuperar el proceso. Cada proyecto también tiene mecanismos de fusión distintos, a veces sin la función «merge» de GitHub, y puede exigir CI para aceptar un MR. Hay que considerarlo. No hay una solución universal, pero con las herramientas adecuadas puede crearse una aplicación predictora para cada proyecto con suficientes MR. El código está en https://github.com/sophilabs/pullreq-ml.

Referencias


  1. Y. Yu, H. Wang, V. Filkov, P. Devanbu y B. Vasilescu, «Espera: factores de latencia en la evaluación de pull requests en GitHub», 2015 IEEE/ACM 12th Working Conference on Mining Software Repositories, Florencia, 2015, pp. 367-371. ↩

  2. Gousios, Georgios y Pinzger, Martin y Deursen, Arie van, «Estudio exploratorio del modelo de desarrollo de software basado en pull requests», Proceedings of the 36th International Conference on Software Engineering, pp. 345-355 ↩

  3. Jason Brownlee, Cómo convertir una serie temporal en un problema de aprendizaje supervisado en Python, consultado en machinemastery.com, 8 de mayo de 2017. ↩