master-thesis/src/chapter_3.md

20 KiB

Costruzione del dataset e metodologia

Research Questions

Gli obiettivi di questa tesi illustrati nella @sec:goals sono stati racchiusi in cinque \acl{RQ} di seguito elencate.

  • RQ1: come il \ac{ML} e' distribuito sull'architettura dei progetti?

    In questa \ac{RQ} si vuole investigare l'architettura dei progetti. In particolare l'attenzione viene concentratala sui files e sulle directories modificate durante interventi di issues fixing. Obbiettivo di questa domanda è anche individuare la percentuale di files che utilizzano import riconducibili a librerie e framework di \acl{ML}.

  • RQ2: come sono distribuiti i bug sulle diverse fasi di \ac{ML}?

    Il workflow tipico per lo sviluppo di un'applicazione di \acl{ML} si compone di più fasi. L'obiettivo di questa \ac{RQ} è quello di individuare le fasi più critiche per quanto riguarda l'introduzione di difetti all'interno del prodotto software.

  • RQ3: esiste una differenza di entropy tra \ac{ML} bug e altri bug?

    A partire dai lavori precedenti svolti sull'entropia di un cambiamento, si vuole capire se esiste una differenza in termini di entropia generata tra le correzioni dei difetti ascrivibili al \acl{ML} e gli altri difetti.

  • RQ4: come varia il livello di discussione tra \ac{ML} bug e altri bug?

    Questa \ac{RQ} riguarda il livello di discussione dei bug. In particolare si vuole capire se, all'interno dei progetti di \acl{ML}, i bug generici sono discussi con lo stesso livello di approfondimento di quelli specifici del \ac{ML}.

  • RQ5: come varia il time-to-fix tra \ac{ML} bug e altri bug?

    Un altro aspetto caratteristico di un fix è il tempo necessario per poter essere attuato. Questa \ac{RQ} ha lo scopo di verificare l'esistenza di differenze tra i bug generici e quelli di \acl{ML}.

Selezione dei progetti

L'individuazione dei progetti da analizzare è avvenuta mediate l'ausilio dell'\ac{API} messa a disposizione da GitHub. In particolare è stata eseguita una query per ottenere una lista di repository che fanno uso di librerie e framework di \ac{ML} come TensorFlow, Pytorch e scikit-learn. In questo modo è stato possibile ottenere una lista di 26758 repository che è stata successivamente filtrata per individuare solo i progetti d'interesse per il seguente studio.

L'operazione di filtraggio è avvenuta attraverso due fasi; una prima automatica e una seconda manuale. La prima fase ha avuto l'obiettivo di selezionare unicamente i repository popolari. Nella maggior parte dei casi viene utilizzato il numero di stelle come indice della popolarità di un progetto [@borges2016understandingfactorsthat], ma per questo lavoro si è preferito dare maggiore rilevanza ad altri aspetti, come il numero di fork, il numero di contributors e il numero di issues chiuse. Questa scelta è stata dettata dall'esigenza di selezionare non solo repository popolari, ma anche caratterizzati da una forte partecipazione della community.

I progetti che hanno superato questa prima selezione dovevano:

  • essere lavori originali, per cui sono stati esclusi tutti i fork.
  • avere almeno cento issues chiuse.
  • avere almeno dieci contributors.
  • avere almeno venticinque fork.

Alla fine di questa prima selezione il numero di repository si è ridotto a sessantasei e sono stati analizzati manualmente per rimuovere listati associati a libri e/o tutorial, progetti non in lingua inglese e librerie. Alla fine di questa seconda fase il numero di progetti è sceso a trenta.

Fetch di issues e commit

Una volta individuati i progetti da analizzare si è reso necessario recuperare l'intera storia dei progetti e le issues ad essi associate. Per entrambe le operazioni è stato utilizzato il tool perceval [@duenas2018percevalsoftwareproject]. Nel caso delle issues, essendo queste informazioni non direttamente contenute all'interno del repository git, è stato necessario utilizzare nuovamente l'\ac{API} di GitHub. Poiché le chiamate associate ad un singolo token sono limitate nel tempo si è scelto di configurare perseval in modo tale da introdurre in automatico uno ritardo ogni qualvolta veniva raggiunto il limite. Inoltre il codice è stato dispiegato su un \ac{VPS} in modo da poter eseguire il fetch senza che fosse necessario mantenere attiva una macchina fisica.

Con il processo precedentemente illustrato è stato possibile recuperare:

  • 34180 commit.
  • 15267 tra issues e pull request.

Classificazione dei dati

Classificazione delle issues

Al fine di poter eseguire un confronto tra i fix di \ac{ML} e quelli generici è stato necessario classificare sia le issues che i commit. Il numero elevato di elementi non rende praticabile una classificazione manuale per cui si è optato per una classificazione automatica. Per quanto riguarda i primi si è scelto di attuare una classificazione basata sul testo, in particolare considerando il titolo e il corpo della issue, ma escludendo i commenti di risposta in modo da non rendere i dati troppo rumorosi. A tal fine sono stati implementati ed analizzati due classificatori, uno supervisionato e uno non supervisionato.

I due modelli considerati sono:

  • un classificatore statico basato su una lista di vocaboli tipici del \ac{ML}.
  • un modello naïve Bayes [@2021naivebayesclassifier; @harrington2012machinelearningaction].

La classificazione mediate il classificatore statico non necessita di un labeling manuale dei dati, ma richiede la definizione dei vocaboli tipici del \ac{ML}. Lista dei termini caratteristici del \acl{ML} non è stata costruita da zero, ma è basata sul lavoro di Humbatova et al. [@humbatova-2019-taxonomyrealfaults]. In questo modo tutte le issues che utilizzavano almeno un vocabolo tipico del \acl{ML} sono state classificate come issues di \ac{ML}.

Nel caso del modello naïve Bayes, essendo questo un algoritmo di apprendimento supervisionato, si è resa necessaria una classificazione manuale delle issues. A tal scopo è stato eseguito un campionamento stratificato in base al progetto di provenienza di 376 issues che sono state divise tra due lettori e labellate. La label delle issues è stata determinata andando analizzare il titolo, il corpo e i commenti associati alla issue. Durante il labeling si scelto di classificare ulteriormente le issue di \ac{ML} al fine di individuare anche la fase in cui il problema si è palesato. La definizioni delle varie fasi è avvenuta partendo dal lavoro di Amershi et al. [@amershi-2019-softwareengineeringmachine] realizzato nei laboratori di Microsoft.

Le fasi considerate sono:

  • Model Requirements: questa fase comprende tutte le discussioni rispetto all'individuazione del modello più adatto, le funzionalità che questo deve esporre e come adattare un modello esistente per eseguire una diversa funzionalità.
  • Data Collection: comprende le operazioni volte alla definizione di un dataset. Rientrano in questa fase sia la ricerca di dataset già esistenti che la costruzione di nuovi dataset.
  • Data Labeling: questa fase si rende necessaria ogni qual volta si opera con modelli basati su apprendimento supervisionato.
  • Data cleaning: in questa fase non rientrano soltanto le operazioni strettamente di pulizia dei dati come ad esempio rimozione di record rumorosi o incompleti, ma tutte le trasformazioni eseguite sui dati, quindi anche operazioni di standardizzazione, flip di immagini ecc.
  • Feature Engineering: questa fase serve per identificare le trasformazioni da attuare sui dati e le migliori configurazioni degli hyperparametri al fine di migliorare il modello.
  • Model Training: questa fase racchiude il training vero e proprio del modello.
  • Model Evaluation: in questa fase vengono valutate le performance del modello utilizzando metriche standard come precision e recall, ma anche andando a confrontare i risultati ottenuti rispetto a quelli generati da altri modelli o rispetto all'esperienza1.
  • Model Deployment: questa fase riguarda il dispiegamento del modello sul dispositivo target.
  • Model Monitoring: una volta dispiegato il modello deve essere continuamente monitora al fini di assicurasi un corretto comportamento anche sui dati reali.

A partire dal dataset labellato è stato possibile costruire un training e un test set, mediante i quali è stato possibile allenare e valutare le performance del modello bayesiano. Mentre le performance del primo modello sono state valutate sull'intero dataset.

\begin{figure}[!ht] \subfloat[Numero di issues rispetto al tipo\label{fig:labeling-type}]{% \includegraphics[width=0.45\textwidth]{src/figures/count-type.pdf} } \hfill \subfloat[Numero di issues rispetto alla fase\label{fig:labeling-phases}]{% \includegraphics[width=0.45\textwidth]{src/figures/count-phases.pdf} } \caption{Risultati della classificazione manuale delle issues} \label{fig:labeling} \end{figure}

Al fine di poter confrontare i due modelli sono state utilizzate le metriche di precision e recall. Com'è possibile notare dai valori riportati in @tbl:confronto-modelli-classificazione-issues, il modello basato sulla lista di vocaboli è leggermente più preciso del modello bayesiano, ma presenta una recall decisamente più bassa. Dalla @fig:labeling-type si evince la natura minoritaria delle issues di \ac{ML} rispetto alle issues generiche, per questo motivo si è preferito il modello naïve Bayes in modo da perdere quante meno istanze possibili anche a costo di sacrificare leggermente la precisione.

Classificatore statico naïve Bayes
precision 0.46 0.41
recall 0.74 0.94

: Confronto dei due modelli per la classificazione delle issues. {#tbl:confronto-modelli-classificazione-issues}

Classificazione dei commit

Prima di poter classificare i commit si è reso necessaria un'ulteriore fase di filtraggio in modo da poter separare i commit di issue fixing da quelli generici. Sono stati considerati come commit di fix tutti quei commit al cui interno veniva fatto riferimento a delle issues attraverso la notazione "#". Questa operazione ha ridotto il dataset dei commit a 3321 unità la cui distribuzione in base al tipo è riportata in @fig:count-commit.

Da ogni commit sono state estratte le informazioni rilevanti per le analisi. In particolare è stato conservato:

  • Il progetto di appartenenza.
  • L'hash del commit.
  • La data del commit.
  • L'autore del commit.
  • La lista dei files modificati.
  • Le linee modificate.
  • La lista delle issues citate.

A questo punto è stato possibile separare i fix di \acl{ML} da quelli generici. La classificazione è avvenuta attraverso la lista delle issues citate all'interno del commit message e sono stati considerati come commit di \ac{ML} tutti quei commit che facevano riferimento ad almeno una issue di \acl{ML}.

Risultato della classificazione dei commit{#fig:count-commit width=80%}

\newpage

Metodologia

RQ1: come il ML e' distribuito sull'architettura dei progetti?

In questa prima domanda si vuole andare a capire quant'è ampia la superficie del progetto che viene modificata durante gli interventi di fix, facendo distinzione tra le correzioni che riguardano il \ac{ML} e quelle generiche. Inoltre si vuole anche capire quanti file importano librerie tipiche del \acl{ML}.

Per poter svolgere la prima analisi è stato necessario individuare il numero totale di file modificati per fix generici e per i fix specifici del \acl{ML}. A tal fine i commit sono stati raggruppati rispetto al progetto e al tipo di cambiamento (\ac{ML}, no \ac{ML}). All'interno di ogni raggruppamento si è eseguita la concatenazione della lista dei file modificati. Poiché non si è interessati al numero di modifiche che ha subito ogni file le liste sono state trasformate in insiemi per eliminare le ripetizioni. Come output di questa fase si è ottenuto per ogni progetto:

  • l'insieme dei file modificati per fix di \ac{ML}
  • l'insieme dei file modificati per fix generici

Infine eseguendo l'union set tra questi due insiemi si è ottenere l'insieme totale dei files modificati durante i fix. A questo punto per ogni progetto si è calcolata la percentuale di file modificati durante interventi di fix di \ac{ML} (ml_file_ratio) e la percentuale di file modificati durante fix generici (no_ml_file_ratio).

Attraverso la funzione di libreria Python os.path.dirname sono stati ottenuti i tre insiemi sopra citati anche per quanto riguarda le directories. E in modo analogo si è calcolata la percentuale di directories modificate durante interventi di \acl{ML} (ml_dirs_ratio) e interventi generici (no_ml_dirs_ratio). Queste distribuzioni sono state analizzate graficamente attraverso l'ausilio di boxplot.

Per la seconda analisi si è reso necessario conoscere per ogni file la lista degli import utilizzati. Questa informazione è stata recuperata attraverso uno script, che dato in input un progetto restituisce la lista dei files affiancati dalla lista degli import utilizzati all'interno del file stesso. L'individuazione dei file di \acl{ML} è avvenuta mediante la definizione di due gruppi di librerie tipiche del \ac{ML}.

  • Gruppo 1: librerie specifiche del \acl{ML} come ad esempio keras, TensorFlow e Pytorch.
  • Gruppo 2: librerie utilizzate in ambito \ac{ML}, ma anche in altri contesti. Appartengono a questo gruppo librerie come numpy, scipy e pandas.

Ogni file è stato classificato come di \acl{ML} o meno in base a due livelli severità. Nel caso della severità base per rientrare all'interno dei file che fanno uso di librerie di \ac{ML} bastava importare almeno una libreria contenuta in uno dei due gruppi precedentemente descritti. Mentre nel caso di severità strict era necessario importare almeno una libreria presente nel primo gruppo.

Per entrambe le classificazioni si è andato a valutare a quanto ammontava la percentuale di file di \ac{ML} appartenenti ad ogni progetto. Anche i questo caso le distribuzioni sono state analizzate attraverso l'ausilio di un boxplot.

RQ2: come sono distribuiti i bug sulle diverse fasi di ML?

Come illustrato nella @sec:classificazione-commit per poter determinare la natura di un issue fix si è fatto ricorso alla classificazione delle issues ad esso associate. La maggior parte delle issues è stata classificata automaticamente, ma è stato comunque necessario classificarne una porzione in modo manuale per poter avere un train/test set. Come detto precedentemente, nel caso delle issues classificate a mano, oltre all'individuazione della tipologia (\ac{ML}, non \ac{ML}) è stata individuata anche la fase in cui il problema si palesava (si veda @sec:classificazione-issues). In questa \ac{RQ} si vuole andare a valutare come questo dato aggiuntivo sulle fasi viene proiettato sui commit di fix.

Per poter svolgere questa analisi è necessario incrociare i dati sui commit di fix con la classificazione delle issues. A partire dal dataset delle issues è stato creato per ogni progetto un dizionario issue \rightarrow fase. Quindi per ogni commit si è individuata la fase attraverso questo dizionario ausiliario.

In particolare un commit poteva citare:

  • nessuna issues inclusa nel dizionario. In questo caso non è possibile individuare la fase del commit.
  • una issues presente nel dizionario. In questo caso al commit viene assegnata la fase della issue.
  • più di una issues presente nel dizionario. In questo caso al commit venivano associate più fasi2.

L'analisi quantitativa è avvenuta attraverso un barplot in cui venivano riportati unicamente i commit a cui è stato possibile assegnare almeno una fase.

RQ3: esiste una differenza di entropy tra ML bug e altri bug?

La successiva analisi aveva lo scopo di verificare l'esistenza di una differenza tra l'entropia del fix rispetto alla natura di questi. Il lavoro di questa analisi è basato sul modello BCC discusso nella @sec:entropy. L'analisi è stata svolta sia a livello di file, sia a livello di linee quindi per ogni commit del dataset è stato necessario individuare sia il numero di file che hanno subito delle modifiche, sia il numero di linee alterate, considerando in questo modo sia le aggiunte che le rimozioni. Il dato rispetto alle linee modificate è già presente nel dataset di partenza (si veda @sec:classificazione-commit), mentre il numero di file modificati può essere ricavato dalla lista dei files modificati nel commit.

Inoltre per poter calcolare la probabilità di un cambiamento è stato necessario conoscere anche il numero totale di file e di linee di ogni progetto. Questi valori sono stati calcolati attraverso la storia git del branch master3. Per ogni commit sono stati individuati i file aggiunti (+1) e rimossi (-1) in modo tale da poter calcolare il delta-cambiamento del commit. Eseguendo la somma di questo delta su tutti i commit si è ottenuto il numero totale di file del progetto. In modo analogo si è proceduto anche per quanto riguarda le linee.

Le due distribuzioni sono state valutate graficamente attraverso un boxplot. Inoltre sono stati svolti dei test statistici (ranksum e Cliff's delta) per verificare la rilevanza di queste differenze.

RQ4: come varia il livello di discussione tra ML bug e altri bug?

Per rispondere a questa domanda è stato necessario andare a valutare il numero di commenti presenti all'interno di ogni issues. Questo dato non è presente nel dataset dei commit generato inizialmente (si veda @sec:classificazione-commit), ma può essere ricavato a partire dalla lista delle issues citate. Dato un commit si è considerata la lista delle issues citate, e per ogni issue citata si è calcolato il numero di commenti. Poiché un singolo commit può far riferimento a più issues è stato necessario anche calcolare il numero di commenti medi.

Il livello della discussione non viene determinato solo dal numero di commenti, ma anche dalla lunghezza di questi. Quindi per ogni issue è stato calcolato anche il numero medio di parole presenti all'interno di un commento.

I dati per entrambe le distribuzioni sono stati valutati graficamente attraverso l'ausilio di un boxplot e attraverso i test statistici illustrati precedentemente.

RQ5: come varia il time-to-fix tra ML bug e altri bug?

In quest'ultima analisi si vuole andare a valutare se c'è differenza nel tempo necessario per eseguire il fix. Anche in questo caso, per poter rispondere alla domanda, è necessario incrociare i dati dei commit con quelli delle issues attraverso la lista delle issues citate. Dato una issue sono stati individuate la data di apertura e di chiusura. Nel caso in cui ad un commit sono associate più issues è stata presa come data di apertura il minimo tra tutte le date di apertura delle issues e, in modo analogo, si è proceduto anche per la data di chiusura con la differenza che i dati sono stati aggregati attraverso la funzione max.

Una volta noto il momento di apertura e di chiusura della problematica è stato possibile calcolare il numero di giorni intercorsi tra questi due istanti temporali. Le distribuzioni così ottenute sono state analizzate ancora una volta mediante un boxplot, il test ranksum e il test Cliff's delta.


  1. Non sempre è possibile valutare un modello in modo oggettivo, ci sono determinati contesti, come ad esempio la generazione di deep fakes, in cui è comunque necessaria una valutazione umana per determinare la qualità del risultato. ↩︎

  2. Nessun commit di fix presente nel dataset utilizzato è rientrato in questa categoria. ↩︎

  3. Oltre al branch master è stato considerato anche il branch main diventato molto comune dopo le proteste del movimento Black Lives Matter e il branch master-V2 unico branch utilizzato da un progetto. ↩︎