RapidMiner tippek – A Filter és a Loop operátorok
Mind a klasszikus adatbányászati, mind pedig a tágabb teret lefedő data science feladatok megoldásainak egyik alapvető eleme a sorok szűrése, ami a RapidMiner-ben a Filter Examples operátorral valósítható meg.
Kevésbé gyakori eljárás az adattáblák sorainak iteratív feldolgozása, amit hacsak lehet, kerülni kell pl. új attribútumok generálásával és ezek alapján a sorok szűrésével majd a keletkezett új és a régi adattáblák összekapcsolásával, hiszen pl. néhány millió sor iterációja igencsak költséges megoldást eredményezhet egy Generate Attributes, Filter Examples és Join operátorhármas alkalmazásához képest. Tegyük fel, hogy kisebb méretű adattáblával dolgozunk és a feladat sajátossága miatt elkerülhetetlen a RapidMiner valamely Loop operátorának használata. Ekkor a sorszintű iterációra alapvetően három lehetőségünk van: a Loop, a Loop Values és a Loop Examples operátorok (a többi speciális Loop operátor részletezésétől most eltekintünk). Kérdés, hogy mikor melyiket válasszuk illetve, hogy helyesen döntöttünk-e?
Sajnos a RapidMiner dokumentációja – amire még visszautalok a posztban – túl sokat nem árul el ezekről az operátorokról sem, kivéve ha kezdő adatbányász az ember. A többiek a tapasztalataikra hagyatkozhatnak, illetve az igazán expertek tanulmányozhatják a forráskódot is, hogy némi benyomásuk legyen a működési mechanizmust, az ismeretlen feature-öket (vagy éppen bugokat?) illetve a hatékonyságot illetően. Kérdés persze, hogy ez mennyiben feladata egy adatbányásznak, amire a rövid és gyors válasz, hogy semennyire, így ilyen részletekbe mi sem megyünk bele.
A dokumentáció alapján tehát a sima Loop operátor előnye a másik kettővel szemben az, hogy mi állíthatjuk be az iterációszámot illetve megadhatunk egy iterációs makrót valamint annak kezdő értékét is, ami adott esetben meglehetősen hasznos tud lenni. Komolyabb feladatoknál jellemzően a kezdőérték beállítása is egy makróval történik, aminek az értékét a folyamat során származtatjuk, így egészen jól automatizált processzeket készíthetünk. Egyébiránt makró használatára mindhárom Loop operátornál van lehetőség: a sima Loop operátor iterációs makrója a kezdőértéktől induló szám, ami minden lépésben eggyel nő, a Loop Examples operátornál a makró az aktuális sorszámot tartalmazza és mindig egytől indul, míg a Loop Values esetén az iterációs makró a kiválasztott attribútum következő értékét veszi fel.
Tegyük fel, hogy mi az adattáblánk összes (egyedi) sorát fel szeretnénk dolgozni, pl. mindenféle kritérium mentén elmenteni azokat egy adattárházba, így nem a normál Loop operátort választjuk. A Loop Examples operátor a sorszám alapján iterál végig, míg a Loop Values operátor sokkal hatékonyabbnak tűnik, ha az iteráció alapja egy konkrét – nominális – attribútum, és ebben vannak ismétlődő elemek (a Loop Values esetén meg kell adni egy nominális attribútumot, ami mentén történik az iteráció). Ekkor ugyanis pl. egy 100 sort tartalmazó adattábla esetén, melynek a kiválasztott attribútumában csak 3 lehetséges érték szerepelhet, a Loop Values csak háromszor fog végigiterálni, ami a Loop Examples 100-as futásához képest két nagyságrenddel gyorsabb futást ígér. Ez alapján gondolhatnánk is, hogy milyen nagyszerű dolgunk van, a lehetőségekhez mérten egy igazán hatékony processzt állítottunk össze, ami mehet is az éles rendszerbe. Aztán egyszer csak az igazság egy kellemetlenebb pillanatában felhív bennünket a megrendelő, hogy nem stimmelnek a rekordok az adattárházban.
Mi lehet a gond? Mit csináltunk rosszul? A kérdésekre álljon itt a hosszabb válasz. Az adattábla sorszintű iterációjának lényege, hogy minden lépésben általában csak egy sort szeretnénk feldolgozni, ahol a sorban lévő valamely értékektől függ az adott sor további sorsa: pl. legenerálunk hozzá egy újabb attribútumot és így beírjuk az egészet az adattárházba vagy eldobjuk a teljes sort. És itt jön a képbe a bevezetőben említett Filter Examples operátor, melynek ‘condition class’ paraméterében az ‘attribute_value_filter’ opciót választva elérhetővé válik a ‘parameter string’ beállítás, ahol megadható a kívánt szűrési feltétel. A Loop Values operátor esetén ez jellemzően az alábbi alakú: ‘<kiválasztott attribútum neve> = %{<loop values iterációs makrója>}’, vagyis minden iterációban a megadott attribútum mentén csak azokat a sorokat hagyjuk meg, melyek értéke azonos az iterációs makró aktuális értékével. Ebben a formában tehát a továbbiakban nem egy darab sorral dolgozunk, hanem akár többel, de ezek a kiválasztott attribútum mentén azonosak, így feltehetően a legenerált új attribútum értéke is azonos lesz ezeknél a soroknál. A Loop operátorok után pedig általában egy Append operátor szokott állni, ami összefésüli az egyes iterációk kimeneteit, ami így egy adattáblaként egy lépésben mehet be az adattárházba. Az Append operátornál különös figyelemmel kell lennünk a bemeneti adattáblák attribútumneveinek és -típusainak egyezésére, ami összetettebb processzek esetén nem magától értetődő.
A fenti feladatra a Loop Examples operátor esetén választhatjuk a hosszabb utat, amikor még a Loop előtt legenerálunk egy id mezőt a Generate ID operátorral, majd a Loop belsejében a Filter Examples operátornál az alábbi alakú szűrést használjuk: ‘id = %{<loop examples iterációs makrója>}’. Az egyszerűbb megoldás a Filter Example Range operátor használata, ahol az első- és utolsó sorhoz beállítjuk az iterációs makrót. A Loop Examples operátor esetén érdemes arra odafigyelni, hogy ne az ‘exa’ kimeneti portra kössük az iterációk eredményét, hanem az első ‘out’ portra, amit átadhatunk az Append operátor bemenetének. Ugyanis az ‘exa’ portot használva az első iterációt leszámítva minden iteráció az előző iteráció kimeneti adattábláját kapja bemenetül, ami jelen esetben a második iterációnál csak az első sorból fog állni, amiből pedig nem lehetséges meghagyni a második sort. Bizonyos esetekben viszont kifejezetten előnyös az ‘exa’ port használata.
Mi azonban a Loop Values operátort használtuk, tehát itt kell keresni a problémát: miért nem töltődtek be az adattárházba olyan sorok, melyeknek be kellett volna töltődniük? Például azért, mert a Filter Examples operátor ‘parameter string’ helyén megadott kifejezése automatikusan trimmelésre kerül, ami a RapidMiner egy nem dokumentált feature-e akar lenni, ami így valójában egy bug. A trimmelést feltehetően azért építették be a fejlesztők, mert nem feltételezték a professzionális felhasználást el szerették volna kerülni a felhasználók figyelmetlenségéből adódó azon diszfunkcionalitásokat, amikor a kifejezés végén véletlenül ott marad néhány láthatatlan whitespace karakter. Tehát a helyzet az, hogy a Loop Values operátor bemeneti adattáblájának kiválasztott – nominális – attribútuma a legtermészetesebb módon tartalmazhat pl. szóközre végződő értékeket, melyeket – nominális attribútumról lévén szó – az iterációs makró is tartalmazni fog a végződő szóközzel együtt, a ‘<kiválasztott attribútum neve> = %{<loop values iterációs makrója>}’ szűrési feltétel azonban mégsem fogja átengedni a kívánt sorokat, mert a kifejezés jobb oldala a beépített trimmelés áldozata lesz (természetesen előbb a makró értéke kerül behelyettesítésre, s csak utána következik a sorvégi szóköz levágása). Próbálkozhatunk persze a jobb oldalt idézőjelek vagy aposztrófok közé tenni, illetve a szóköz escape karakterrel történő direkt megadásával is, az eredmény nem változik.
Mi lehet tehát a megoldás? Egyrészt a processz készítése előtt szerezzük be a bemeneti attribútumok intervallumait és lehetséges értékeit, és a megoldás során használjunk minden eshetőséget tartalmazó mintafájlt, hogy adott esetben ne egy éles rendszer több hónapos működése után derüljenek ki az ilyen és ehhez hasonló hibák (ez természetesen minden egyéb fejlesztésre igaz, nemcsak RapidMiner-re). Másrészt a Loop Values helyett használjuk a Loop Examples operátort, ami ugyanolyan hatékony lesz, mint a Loop Values, ha előtte alkalmazunk egy Remove Duplicates operátort a kiválasztott attribútumra, vagyis a fenti példánál maradva így csak három sor kerül a Loop Examples bemenetére, a kimenetet pedig – az Append operátor után – a kiválasztott attribútum mentén összekapcsolhatjuk az eredeti adattáblával, hogy megkapjuk az eredeti sorszámú adattáblát. A Loop Examples operátorban pedig az említett Filter Example Range operátort használjuk. Ha szükségünk van a kiválasztott nominális attribútum aktuális értékére, akkor ez elérhető egy Extract Macro operátor segítségével a Filter Example Range után.
Ha pedig a Filter Examples operátort önmagában kell használnunk egy nominális attribútumra, amiben szintén lehetnek whitespace karakterre végződő értékek, akkor létrehozhatunk egy új segédattribútumot, mely pl. 1-es értéket vesz fel, ha a nominális attribútum értéke whitespace karakterre végződik illetve 0-át, ha nem. Ezt az alábbi, nem teljes kifejezéssel érhetjük el: ‘if(ends(<kiválasztott attribútum neve>, ” “), 1, 0)’. A probléma az ‘ends()’ függvénnyel “mindössze” annyi, hogy nem kezeli a reguláris kifejezéseket, így nem adható meg második paraméterében a ‘\\s’, vagy még speciálisabb illetve teljesebb megoldásként a ‘\\s+’ kifejezés, vagyis nekünk kell manuálisan kibővíteni az ‘if()’ függvény ‘else’ ágát újabb ‘if()’ függvényekkel, hogy a szóköz mellett a többi whitespace karakter esetén is 1-es érték kerüljön az segédattribútumba.
Zárásként pedig megint csak annyit mondhatunk, hogy kíváncsian várjuk, melyik verziószámú RapidMiner-ben kerül javításra ez a bug (tippek érkezhetnek kommentben), illetve mikor jelennek meg az ilyen rejtett “feature-ök” a dokumentációban. A legjobb persze az volna, ha lenne lehetőség a Filter Examples operátorban a trimmelés opció deaktiválására.