TSM - Generarea de date sintetice folosind librăria Be-GReaT

Darius Matiesi - Junior Data Engineer @ Accesa


Big Data a devenit una dintre cele mai însemnate resurse utilizate în creșterea valorii unei companii, având cinci caracteristici reprezentative: volum, varietate, viteză, veridicitate și valoare. 

A lucra într-un mediu în care sunt implicate customer data echivalează cu întâmpinarea unui anumit grad de dificultate în extragerea informațiilor de calitate, care să îndeplinească toate criteriile enunțate anterior. Datele reale conțin, de obicei, informații sensibile, incomplete sau insuficiente, ceea ce duce la obținerea unor contexte de business mai puțin relevante. 

Una dintre potențialele soluții care există pentru a rezolva această problemă este generarea de date sintetice. Procesul de creare a unor astfel de informații a început cu sinteza audio și vocală și continuă să evolueze spre sinteza de imagini, video și text. 

Pentru a asigura integritatea datelor sintetice generate, există posibilitatea de a fi necesară considerarea de seturi uriașe de date de intrare, prin urmare, dorindu-se algoritmi avansați, preciși și rapizi. Acești algoritmi trebuie să genereze date consistente, adică, pe lângă generarea de date cu aceeași structură, ar trebui să descopere și modelele ascunse disponibile în datele de intrare și să le aplice prin procesul de generare a datelor. Astfel, se poate crește volumul, varietatea și valoarea datelor, făcându-le potrivite pentru testarea sau îmbunătățirea, respectiv, extinderea modelelor de machine learning

Generarea seturilor de date potrivit regulilor 

Drept model, am construit de la zero un set de date, respectând anumite reguli predefinite. Aceste reguli sunt utilizate ulterior pentru validarea setului generat de date sintetice. Drept punct de pornire au servit două seturi de date disponibile public, unul conținând prenume existente asociate cu unul dintre sexe: masculin sau feminin, iar al doilea set de date conținând nume de familie existente. Aceste seturi de date au fost apoi unite, standardizate și asociate cu mai multe câmpuri noi. 

Etapele pe care le-am aplicat pentru a obține setul de date dorit au fost următoarele: 

  1. Primul caracter atât al prenumelui, cât și al numelui de familie a fost scris cu majusculă, adică toate numele încep cu majusculă urmată de minuscule - exemplu: Ioan. 

  2. A fost adăugată coloana e-mail, care a combinat versiunea cu minusculă a numelui și prenumelui, separate de "." și urmate de sufixul @gmail.com, de exemplu: (John, Smith) => john.smith@gmail.com .

  3. Coloana birth_year a fost generată drept un număr întreg cuprins între 1950 și 2022 - anul curent. 

  4. Coloana age a derivat din coloana birth_year => 2022 (anul curent) - birth_year

  5. Coloana card_number a fost generată prin definirea unui șir de 19 caractere, conținând 4 grupuri, fiecare cu 4 cifre, separate prin "-". Prima cifră din primul grup este un număr din setul {3,4,5,6}. De exemplu: 6789-0123-4567-8910. 

  6. Ultima coloană numită card_type a rezultat din folosirea primei cifre din coloana card_number. La fel ca în viața reală, cardurile au o anumită corespondență între prima cifră și emitent: 

Pentru cei care vor să observe mai îndeaproape, fără a intra prea mult în detalii, următorul script Scala-Spark a fost utilizat pentru a genera modelul de date create după regulile menționate mai sus: 

// random integer between [min, max) 
def randomNumber(min: Column, max: Column): 
Column = min + ((max - min) * rand).cast(IntegerType) 

def randomDigits(nDigits: Int): Column = regexp_extract(rand()
  .cast(StringType), s".(\\d{$nDigits})", 1)

def generateCardNumber: 
 Column = concat_ws(sep ="-",
 concat(randomNumber(3, 7), randomDigits(3)),
  randomDigits(4),
  randomDigits(4),
  randomDigits(4)
)
def getCardType(cardNumber: Column): Column = {
  val firstDigit = substring(cardNumber, 1, 1) 
  when(firstDigit === 3, ''American Express'')
    .when(firstDigit === 4, "Visa")
   .when(firstDigit ===5, "Mastercard")
   .when(firstDigit ===6, "Discover")
}
val trainingDF = surnamesDF. select( LastName, "id")
.join(babyNamesDF.select(FirstName, Sex), 
   usingColumns = Seq("id")) 
.drop("id") 
.withColumn(FirstName, initcap(col(FirstName))) 
.withColumn(LastName, initcap(col(LastName))) 
.withColumn(Email, concat(lower(col(FirstName)),
    lit("."), lower(col(LastName)), 
    lit("@gmail.com"))) .withColumn(BirthYear, 
    randomNumber(lit(1950), year(current_date))) 
.withColumn(Age, year(current_date) - col(BirthYear)) 
.withColumn(CardNumber, generateCardNumber) 
.withColumn(CardType, getCardType(col(CardNumber)))

Modelul de date poate fi văzut în Tabelul 1.

Tabelul 1

Soluții pentru date tabelare. Be-GReaT 

Librăria CTGan - Python 

Este o colecție de generatoare de date sintetice bazate pe Deep Learning pentru date tabulare, care are la bază date reale și generează pe baza acestora cu o precizie ridicată. 

În demo-ul ilustrat, sunt folosite 9 coloane conținând date discrete cu 26 000 de rânduri. Modelul este format pe baza a 10 epoci. Pentru exemplul nostru, am avut 10 000 de rânduri cu 8 coloane, dar care nu a funcționat, rămânând fără spațiu pe disc pe setupul local cu Python 3.7 sau 3.9 și pe Google Colab folosind Python 3.7. 

SDV - CTGAN 

Este modelarea probabilității de distribuție a rândurilor și generarea de rezultate realiste pe baza GAN, sintetizator de date deep learning, prin utilizarea unui amestec de coloane cu date continue și discrete. 

Modelul CTGan - Conditional generative adversarial network - a fost antrenat cu 10.000 de rânduri și 8 coloane și am generat 1.000 de rânduri pentru testare. După ce am verificat coloanele și valorile acestora, am descoperit că fie lipseau date, fie coloane precum vârsta, numărul de card, tipul de card aveau valori greșite. 

După ce am încercat cu SDV-CTGan și neavând rezultate, am căutat librăria care să remedieze problema. 

 Be-GReaT 

Be-GReaT (Generation of Realistic Tabular data) este o librărie care exploatează un LLM generativ autoregresiv pentru a eșantiona date tabulare sintetice. Modelul LLM utilizat provine de la hugginface.co și se numește DistilGPT2, care are 82 de milioane de parametri, un model instruit cu supravegherea celei mai mici versiuni de GPT-2 antrenată cu 124 de milioane de parametri. 

La început, datele tabulare sunt transformate în text relevant, pentru a fi utilizate pentru ajustarea modelului lingvistic, LLM - DistilGPT2. Pentru a antrena modelul, datele de intrare sunt transformate în perechi de caracteristici-valori, șiruri de caractere precum "Jimmie Anderson este un băiat, e-mailul este jimmie.anderson@gmail.com, vârsta este 27 de ani... " și ulterior în date tabelare pentru eșantionare. 

Având un model bine pus la punct pe datele tabulare textuale care funcționează cu rețele neuronale bazate pe transformatoare, acesta permite, de asemenea, condiționarea arbitrară fără a reeduca modelul, oferindu-vă avantajul de a nu utiliza coloane discrete sau numerice înainte, precum și libertatea pe care acest fapt o implică. 

from be_great import GReaT
import pandas as pd

data=pd.read_csv('training_data.csv')
model=GReaT(11m='distilgpt2', epochs=5)
model.fit(data)

Modelul a fost pregătit pe setupul local cu Intel(R) Core™ i7 de a 11-a generație - 11850h @2,50GHz - a durat aproximativ 5 ore pentru 10 000 de rânduri cu Python 3.9. 

synthetic_data=model.sample(n_samples=5000, device="cpu")

Eșantionarea a 5000 de rânduri din modelul testat a durat 45 de minute. După ce modelul împreună cu analiza au fost finalizate, am constatat că Be-GReaT este o alegere optimă pentru generarea de date sintetice pentru a ne antrena modelele de machine learning atunci când lipsesc date pentru problemele pe care încercăm să le rezolvăm în diferite contexte. 

Rezultate 

După generarea setului de date, pregătirea modelului și ajustarea datelor noastre, obținem Tabel 2.

Tabelul 2

Următorul pas a fost să vedem cât de mult bias implică eșantioanele din cadrul modelului. Am calculat procentul pentru tipul de card și distribuția pe sexe, precum și media de vârstă pentru ambele seturi de date - acest lucru a arătat că tiparele în datele de antrenament derivă din eșantioanele inițiale doar într-o mică măsură. 

Pentru a aprofunda investigarea relației dintre datele din modelul prezentat și datele sintetice, am verificat, de asemenea, corespondențele exacte ale coloanelor. Putem vedea că modelul nu numai că a mixat unele dintre numele și prenumele pentru a crea noi e-mailuri, ci a generat și nume noi. De asemenea, autenticitatea este de 100% pentru numerele de card generate. 

 Pentru a demonstra acuratețea a cât mai multora dintre datele generate, am stabilit un set de reguli pentru fiecare caracteristică. 

Acum trebuie să verificăm dacă fiecare eșantion respectă toate regulile. După rularea regulilor de validare, dacă presupunem că încălcarea oricărei reguli duce la aceeași penalizare atunci când calculăm scorul fiecărui eșantion, obținem scorul 1, dacă toate regulile au fost aplicate și -0,1 pentru fiecare regulă care nu a fost aplicată - există 10 reguli în total. Am obținut un scor mediu de 0,982/1. Aproximativ 83% din date nu conțin nicio greșeală, iar 99,52% au rânduri cu cel mult o greșeală. Doar 2 rânduri au fost generate cu 3 greșeli, vezi Tabelul 3.

Tabelul 3

 Regula e-mailului a fost cel mai dificil de învățat de către model - doar aproximativ 85% din date respectă formatul existent în training data. Au existat, de asemenea, reguli care au fost aplicate în totalitate pe cele 5 000 de eșantioane generate: variabila de sex care conține doar valorile "băiat" și "fată", variabila de prenume, care începe întotdeauna cu o majusculă și toți anii de naștere care sunt cuprinși între 1950 și 2022. 

Concluzii

Datele sintetice adăugate la datele originale vor avea un impact benefic asupra modelului, generând business value.

Be-GReaT ne oferă o soluție mai mult decât potrivită pentru studiul nostru de caz, dar trebuie validat cu atenție în cazuri de utilizare mai complexe, văzând dacă se poate adapta la seturi de date sursă mai complexe. Încurajarea noastră e de a o utiliza și de a ne trimite rezultatele și observațiile făcute, pentru a putea integra îmbunătățirile necesare unei utilizări mai eficiente. 

Acesta a fost un început promițător pentru un trend aflat în curs de dezvoltare.