Kuidas teha FastAI veelgi kiiremini

Kiirendage eeltöötlust, kasutades funktsiooni fastai sisseehitatud funktsiooni

Paralleelse töötlemise jõud. Foto autor Marc-Olivier Jodoin saidil Unsplash

Saan osa oma esimesest Kaggle võistlusest, juba lõppenud
Tensor Flow kõnetuvastuse väljakutse, mis hõlmab töötlemist
ühesekundilised töötlemata heliklipid, et mõista / ennustada, millist sõna öeldakse.

Minu meetod hõlmab töötlemata heli teisendamist spektrogrammideks enne pildi klassifitseerimist. Olin ehitanud toimiva ja täpse mudeli ning proovisin põnevusega seda proovikomplektis ja oma esimese esituse, kui nägin, et testikomplekt oli 150 000+ wav-faili. Praegu tootan umbes 4 spektrogrammi sekundis suurusega 224x224. See on umbes 10 tundi, seal peab olema parem viis.

Kohe tuleb meelde mitmeprotsessiline töötlemine ja mitmekordne keermestamine, kuid ma ei tea, kuidas neid teha, need näevad välja hirmutavad ja nõuavad rohkem õppimist, kui mu plaat on juba tõesti täis. Õnneks on fastail „paralleelne” funktsioon, mida Jeremy 7. õppetunnis juhuslikult mainib. Esimene samm on alati dokumentide kontrollimine.

dok (paralleelne)
Sülearvuti (põhiline) dokumentatsioon saidile fastai.core.parallel

Kuva dokumentidena paralleelselt [allikas] [test]

Vinge, see näeb nii lihtne välja kui võimalik. Ma annan sellele funktsioonile ja argumentide kogumile paralleelselt täidetava funktsiooni, ülejäänud osa haldab fastai.

Mängu näide paralleelist

Allpool olev funktsioon võtab numbri ja prindib selle ruudu välja.

def print_square (num):
    print (number ** 2)

Järgmisena saame genereerida loendi, mis sisaldab numbrite loendeid, millest tahame saada loendist arusaamise kasutamise summad.

num_list = [x x vahemikus (10)]
paralleelne (print_square, num_list)

Käivitage see kood ja näete järgmist:

Paistab, et see töötas suurepäraselt, kuid kus on meie väljakud? Me pidime need välja printima. Kui loete lähemalt siin, näete toimuvat:

Kiire dokumentatsioon saidile fastai.core.parallel

“Func peab aktsepteerima iga arr-elemendi väärtust ja indeksit”

Teie sisestatud funktsioon peab olema eritüüp, aktsepteerides ainult kahte argumenti:

  1. Väärtus (seda sisaldab arr ja see on teie funktsiooni tavaline argument)
  2. Väärtuse indeks * väärtuses arr (märkus: teie funktsioon ei pea tegelikult indeksiga midagi tegema, see peab lihtsalt funktsiooni määratluses olema)
* Sylvain Gugger teatas mulle kiirete foorumite kaudu, et register_images toimimiseks on vajalik register. Paralleelselt mõeldakse ka sisemiseks kasutamiseks mõeldud mugavusfunktsioonina, nii et kui olete rohkem arenenud, saate rakenduse ProcessPoolExecutor abil oma versiooni rakendada.

Kirjutame indeksi aktsepteerimiseks ümber meie funktsiooni. Jällegi ei pea meie funktsioon indeksiga midagi tegema ja võime selle funktsiooni määratluses isegi asendada _.

def print_square (num, indeks):
    print (num ** 2)

Ja nüüd proovime oma paralleelkõnet uuesti ...

See töötab! Kui teil on lihtne funktsioon, mis võtab ühe argumendi, on see kõik tehtud. Nüüd teate, kuidas kasutada fastai paralleelset funktsiooni 2–10 korda kiiremini! * Muutke indeksi aktsepteerimiseks lihtsalt parameetreid ja viige oma funktsioon paralleelselt argumentide kogumiga.
* See on mänguasja näide ja paralleelselt kasutamine on umbes 1000x aeglasem, vaata altpoolt, kus on päris näide reaalsete etalonidega

Kuidas see praktikas välja näeb, koos realistlikuma näitega…

Siin on minu tegelik kood spektrogrammide genereerimiseks ja salvestamiseks.
Algne kood, mille on esitanud John Hartquist ja Kajetan Olszewski
TLDR: loe wav-faili aadressil src_path / fname, loo spektrogramm, salvesta dst_path.

def gen_spec (fname, src_path, dst_path):
    y, sr = librosa.load (src_path / fname)
    S = librosa.funktsioon.melspektrogramm (y, sr = sr, n_fft = 1024,
                                       humala pikkus = 512, n_meel = 128,
                                      võimsus = 1,0, fmin = 20, fmax = 8000)
    plt.figure (figsize = (2.24, 2.24))
    pylab.axis ('välja')
    pylab.axes ([0., 0., 1., 1.], frameon = vale, xticks = [], yticks = [])
    librosa.display.specshow (librosa.power_to_db (S, ref = np.max),
                             y_axis = 'mel', x_axis = 'aeg')
    save_path = f '{dst_path / fname} .png'
    pylab.savefig (save_path, bbox_inches = puudub, pad_inches = 0, dpi = 100)
    pylab.close ()

Enne kui läheme kaugemale, vaatame paralleelselt lähtekoodi.

Lähtekood saidile fastai.core.parallel pole sugugi nii hirmutav kui tundub

See tundub hirmutav, kuid kõik, mis siin tegelikult toimub, on see, et saame argumentide kogumist iga väärtuse ja salvestame selle o-sse ning salvestame ka nimetatud väärtuse indeksi i-s ja seejärel callfunc (o, i).
Meie jaoks tähendab see paralleeli kutsumist gen_spec (fname, index) iga esitatud kollektsiooni nime (meie puhul loendi) jaoks ja see töötleb meie jaoks paralleelset töötlemist, mille tulemuseks on palju kiirem töötlemine.

Mida teha, kui meie funktsioon aktsepteerib rohkem kui ühte argumenti?

Nagu näete, võtab meie funktsioon gen_spec 3 argumenti ja paralleelselt ootab funktsiooni, mis võtab kaks. Lahendus sõltub sellest, kas meie täiendavad argumendid on alati samad nagu failitee või konstant või kas need erinevad.

A. Kui täiendavad argumendid on fikseeritud / staatilised, looge uus funktsioon vaikimisi väärtustega või kasutage pythoni osalist, et luua funktsioon, mis sobib paralleelide mudeliga. Ma eelistan kasutada osalist, mida näitan allpool.

B. Kui teil on mitu argumenti, mis muutuvad iga funktsioonikõnega, edastage need argumentide kogumina ja tehke seejärel pakk lahti.

Lahendus A: kõik 150 000 minu wav-faili asuvad samas src_path ja ma annan kõik spektrogrammid samale dst_path-le, nii et ainus muutuv argument on fname. See on ideaalne koht osalise osa kasutamiseks

Kuna paralleel ei nimeta funktsioonikõnes selgesõnaliselt indeksit, peab see meie määratluses alati olema teine ​​argument. Parandame selle ära. Nüüd on meil:

def gen_spec (nimi, register, src_path, dst_path):

Järgmisena teeme uue funktsiooni gen_spec_partial, läbides staatilisi teid

gen_spec_partial = osaline (gen_spec, src_path = path_audio,
                           dst_path = tee_spektrogramm)

See on see, oleme valmis. Loome 1000 spektrogrammi, kasutades nii gen_spec_partial enda kohta kui ka paralleelselt ja võrrelgem, kui kaua see võtab.

296 sekundit ilma paralleelita, 104 sekundit paralleeliga, peaaegu 3x kiiremini.

Lahendus B: Mida me nüüd oma viimase juhtumi puhul teeme, kui meie täiendavad argumendid pole staatilised? Kirjutame ümber oma funktsiooni, et aktsepteerida paar argumenti ja indeksit, ning seejärel edastame argumente sisaldava jaotise kogumi. Meie spektrogrammi näite puhul näeb see välja järgmine:

def gen_spec_parallel_tuple (arg_tuple, register):
    fname, src_path, dst_path = arg_tuple
    # järelejäänud kood on sama ja see on ära jäetud

Seejärel pakime kõik argumendid, mida tahame edastada, paraja suurusega tuplisse ning seejärel edastame gen_spec_parallel_tuple ja arg_tuple_list paralleelselt

See töötab! Nüüd teate, kuidas võtta funktsioone suvalise arvu argumentidega ja käivitada neid paralleelselt, et kiirendada oma eeltöötlust ja kulutada rohkem aega koolitusele.