VYHLEDÁVÁNÍ TÉMĚŘ IDENTICKÝCH DOKUMENTŮ V
Transkript
VYHLEDÁVÁNÍ TÉMĚŘ IDENTICKÝCH DOKUMENTŮ V ROZSÁHLÝCH KOLEKCÍCH 1) ÚVODNÍ SLOVO V posledních letech díky rozmachu Internetu a vývoji v oblasti uchovávání dat vznikly rozsáhlé kolekce dokumentů – vezměme pro příklad český internet, jehož obsah je odborníky odhadován na padesát milionů dokumentů. Jako následek řady faktorů (např. redundance/mirroring, spam, plagiátorství) mohou takovéto kolekce obsahovat některé dokumenty více než jednou – za kopii dokumentu v tomto případě považujeme buď naprostou shodu originálu a kopie či podobnost natolik velikou, že oba dokumenty mohou být považovány za stejné. Rozšíření téměř identických kopií dokumentů je často nepředvídatelné a potenciálně s sebou přináší problémy týkající se zvýšených nároků na uchovávání dat, snížení výkonu search enginů a kvality jejich odpovědí apod. Velké koncentrace (téměř) identických dokumentů by též mohly ovlivnit statistiky týkající se kolekcí, následkem čehož by mohly být nepříznivě ovlivněny výsledky některých aplikací založených na strojovém učení, které byly na takové kolekci trénované. Cílem této bakalářské práce bylo navázat na ročníkový projekt, který se svou myšlenkou opíral o poznatky z [1], z hlediska spotřeby procesorového času a paměti optimalizovat jeho kód a datové struktury, přidat mnoho nových funkcí, jejichž nezbytnost vyplynula na povrch při testování ročníkového projektu, a vytvořit tak softwarové dílo implementující nový přístup k řešení výše zmíněné úlohy a umožňující experimentální porovnání tohoto řešení se stávajícími. Text této práce je rozdělen na tři části. První část popisuje teoretický úvod, který tvoří myšlenkovou kostru našeho přístupu k problému, druhou část tvoří detaily týkající se samotných aplikací a to jak z uživatelského, tak z technického hlediska. Třetí část naší práce je věnována provedeným experimentům a konkluzím na jejich základě vytvořeným. 2) TEORETICKÝ ÚVOD 2.1. SPECIFIKACE ÚLOHY Úlohou, jejíž řešení si tato bakalářská práce pokládá za cíl, je efektivní implementace skupiny aplikací, pomocí nichž bude možné ve velmi rozsáhlých kolekcích dokumentů ( řádově až miliony ) nalézt velmi blízké duplicity a to, v porovnání s dnes běžně používanými metodami, ve velmi krátkém čase. 2.2. KLASICKÉ POSTUPY HLEDÁNÍ TÉMĚŘ IDENTICKÝCH DOKUMENTŮ Dvě skupiny do dnešní doby nejpoužívanějších metod na hledání téměř identických dokumentů jsou: 1) Kosinová podobnost vektorových modelů dokumentů – pro každý dokument je vytvořen vektor vyjádřující slovní obsah daného dokumentu. Pro každou dvojici dokumentů je následně určena míra jejich podobnosti pomocí nějaké, nejčastěji kosinové, míry aplikované na odpovídající vektory VÝHODY: - vysoká přesnost výsledků metody NEVÝHODY: - vysoká časová složitost – díky nutnosti porovnat všechny dvojice dokumentů a naplnit matici podobnosti (viz poznámka níže) je časová složitost kvadratická - vysoká paměťová náročnost – po dobu trvání výpočtů je potřeba v paměti uchovávat vektory všech dokumentů celé kolekce 2) Signaturová metoda – pro každý dokument je vytvořena signatura, tj. pomocí nějaké hashovací funkce (zmiňme např. MD5) je z obsahu dokumentu vytvořena posloupnost znaků a číslic dále reprezentujících daný dokument. Shodným dokumentům je přiřazena stejná signatura, což vede k odhalení identických dokumentů VÝHODY: NEVÝHODY: - příznivá lineární časová složitost - v některých případech lze nastavit požadovanou délku signatur dokumentů, čímž je možné příznivě ovlivnit paměťovou náročnost aplikace, stejně jako relevanci nalezených výsledků - generované signatury nejsou schopné zachytit malou změnu dokumentu, neboť není zajištěno, že velká míra podobnosti dokumentů se odrazí na velké míře podobnosti vytvořených signatur Pozn: Matice podobnosti je termín označující dvourozměrnou tabulku, v níž je v buňce s indexy i, j číslo udávající vzájemnou podobnost dokumentů Doci a Docj. Pro obrovské kolekce dokumentů je čas na jejich zpracování pomocí kosinové metody neúnosný ( pro představu, trvá na kolekci obsahující 100 000 dokumentů 10 hodin, počítaje pouze operace v operační paměti, bez účasti pevného disku, což při kvadratické složitosti znamená pro kolekci 10 000 000 dokumentů odhadem necelých 12 let ), na druhou stranu z nevýhod uvedených u signaturových metod vyplývá, že pro použití při hledání téměř identických dokumentů jsou tyto zcela nevhodné, takže podobnostní metoda je jedinou cestou pro získání přesných výsledků. Výhody a nevýhody řešení, jehož implementace byla cílem této bakalářské práce, tvoří kombinaci výhod a nevýhod obou výše zmíněných přístupů, slušelo by se říci “od obou to lepší”, a jsou následující: • příznivá časová složitost ( rozhodně lepší než kvadratická ) • relevance získaných výsledků ( srovnatelná s podobnostní metodou ) Naše řešení navíc nabízí možnost nastavení parametrů pomocí nichž lze plně kontrolovat rychlost a přesnost výpočtu ruku v ruce s paměťovou náročností aplikací. 2.2 ZÁKLADNÍ POJMY V tomto odstavci popíšeme základní pojmy a postupy, konkrétně výpočet kosinu dvou vektorů a výpočet a definici hammingovy podobnosti, resp. vzdálenosti, bez jejichž znalosti je další výklad nemožný. 2.2.1 Kosinus úhlu svíraného dvěma n-dimenzionálními vektory Předpokládejme dva zcela libovolné n-diemnzionální vektory u a v. Pokud budeme chtít vypočítat velikost úhlu jimi svíraného, resp. kosinus tohoto úhlu, využijeme následujícího vztahu: cos u , v =u.v/∣u∣∣v∣ kde u.v značí skalární součin vektorů u a v a je definován jako součet součinů odpovídajících si složek obou vektorů a |u| značí délku vektoru u, která je definována jako odmocnina ze součtu druhých mocnin složek vektoru. 2.2.2. Hammingova podobnost, resp. hammingova vzdálenost Uvažujme dvě libovolné signatury, v našem případě je jejich délka nastavena na hodnotu 20. Hammingovou podobností dvou signatur A a B nazveme počet pozic, na nichž mají obě signatury shodnou hodnotu. Hammingovou vzdáleností dvou signatur A a B nazveme počet pozic, na nichž mají obě sigantury různou hodnotu. V příkladu uvedeném níže je hammingova podobnost signatur A a B rovna dvanácti zatímco jejich vzdálenost je rovna osmi. sig A 00101100100110010010 sig B 01100101111010110000 XOR 10110110100011011101 01001001011100100010 Jak ukazuje obrázek, je hammingova podobnost, resp. vzdálenost signatur A a B rovna počtu nul, resp. jedniček ve výsledku operace XOR aplikované na obě signatury. 2.3 LOCALITY SENSITIVE HASHING Nejprve dobře definujme pojem “schopnost zaznamenat i malou změnu dokumentu”. Jestliže hashovací funkce dokumentu A přiřadí signaturu SA, pak pokud změníme dokument A jen málo, měla by signatura upraveného dokumentu být velmi podobná signatuře SA. Můžeme tuto vlastnost vyjádřit též ekvivalencí dokument A ≈ dokument B ⇔ sig ( A) ≅ sig ( B ) ( ≈ , ≅ ... (V1) je podobno, podobnost dokumentů se určuje kosinovou mírou, podobnost signatur se v našem případě určuje Hammingovou podobností ) Takovou vlastnost splňují hashování označovaná jako Locality Sensitive Hashing, neboli LSH ([1]). Algoritmus, který bude naše řešení při převodu kolekce dokumentů na její signaturový model používat, je následující. V................ slovník - množina termů získaných ze všech dokumentů n................ mohutnost slovníku k................ požadovaná délka výstupních signatur (v bitech) Krok 1: Náhodně zvolme k vektorů délky n – r1,...,rk Krok 2: Normalizujme vektory r1,...,rk na délku jedna Krok 3: Pro každý dokument d vytvořme odpovídající vektor v délky n for (int i = 0; i < n; i++){ if (V[i] is_in BOWd) v[i] = tf_idfV[i]; else v[i] = 0; };//for Krok 4: Pro každý dokument di spočteme LSH(di) následujícím způsobem for (int j = 0; j < k; j++){ LSH ( d i )[ j ] = 1 ⇔ vi .rj > = 0 LSH ( d i )[ j ] = 0 ⇔ vi .rj < 0 };//for ...kde a.b znamená skalární součin vektorů a a b. V [1] je možné se dále dočíst, že při použití LSH platí pro libovolné dva vektory u, v následující rovnost θ (u , v ) π ( h je hashovací funkce popsaná výše) která je jiným vyjádřením faktu, že pro dva podobné dokumenty (reprezentované podobnými vektory u, v – podobnost dokumentů měřena pomocí kosinové míry uplatněné na vektory) budou i odpovídající signatury podobné (podobnost signatur měřena pomocí Hammingovy vzdálenosti). Pr[h(u ) = h(v)] = 1 − Komentář k funkci LSH algoritmu Rozeberme nyní funkci LSH algoritmu krok po kroku. V Kroku1 algoritmu jsou generovány náhodné vektory a to z rozdělení Nk(0,1), čehož cílem je rozmístit náhodné vektory tak, aby se konvexní obal koncových bodů těchto náhodných vektorů ( po normalizaci ) co nejvíce podobal kouli resp. aby byl svým tvarem co nejblíže pravidelnému mnohostěnu.Náhodné vektory tvoří vztažnou soustavu, vůči níž vektory dokumentů kolekce posuzujeme. Vytvářená signatura pro dokument D bude mít na pozici i jedničku právě tehdy, když vektor dokumentu D s i-tým náhodným vektorem svírá úhel menší než 90°, v opačném případě bude na tomto indexu nula ( viz Krok4 Pozn: zmíněný vztah platí právě tehdy, pokud jsou stejně jako náhodné vektory i vektory dokumentů před vytvářením signatur normalizovány. V opačném případě do hry ještě vstupuje délka vektoru dokumentu ). Výsledná signatura dokumentu je tedy jakousi mapou určující polohu vektoru dokumentu D, ale nikoliv absolutně, jak to dělá vyjádření dokumentu pomocí jeho vektoru, ale relativně vůči náhodným vektorům. Při menším zamyšlení nad funkcí LSH algoritmu je zřejmé, že náhodné vektory, které mají všechny složky kladné stejně jako ty, které mají všechny složky záporné, nemají pro vektory dokumentů žádnou rozlišovací schopnost. První zmíněné musí se všemi vektory dokumentů svírat úhel menší než 90°, zatímco druhé zmíněné musejí se všemi vektory dokumentů svírat úhel větší než 90° , neboť vektory dokumentů mohou ve všech svých složkách nabývat pouze nezáporných hodnot. Na obrázcích níže jsou popsány dva patologické jevy, které se mohou v souvislosti s generováním náhodných vektorů a následným vytvářením signatur vyskytnout. Zde jsou tyto jevy popsány ilustrativně v dvourozměrném prostoru ( každá dimenze reprezentuje počet výskytů jednoho termu v dokumentu, jehož vektor je na obrázku zakreslen - postup tvorby vektoru je uveden v Kroku3 LSH algoritmu, viz výše ). V reálném provozu je dimenze tohoto prostoru totožná s velikostí slovníku kolekce ( tedy řádové desítky či stovky tisíc ), nicméně tyto patologické jevy zůstávají stejné nezávisle na dimenzi tohoto prostoru. RND2 RND1 RND3 u O v RND4 term1 term2 Obr. 1: Náhodné vektory jsou rozmístěny nepravidelně Za situace, která je znázorněna na obr. 1, je zřejmé, že vytvořené signatury všech dokumentů budou totožné, neboť náhodné vektory nejsou dostatečně rozprostřeny po celém prostoru v důsledku čehož každý z nich svírá se všemi vektory dokumentů příliš malý úhel. Čárkovanými čarami jsou vyznačeny kolmice na oba vektory dokumentů u a v. K tomu, aby nějaký náhodný vektor měl schopnost rozlišit vektory u a v by musel ležet v oblasti na obrázku vyznačené šrafy. Odstranění tohoto jevu spočívá ve volbě kvalitního generátoru náhodných čísel. RND1 term2 v RND2 u w O term1 RND4 RND3 Obr. 2: Vektory dokumentů kolekce nejsou dostatečně rozmanité Na obr. 2 jsou znázorněny tři vektory u, v, w reprezentující tři různé dokumenty, které jsou si navzájem velice podobné. I přesto, že jsou náhodné vektory RND1 až RND4 rozmístěny, dle konkluze vytvořené na základě prvního patologického jevu, rovnoměrně, nemají nagenerované náhodné vektory žádnou schopnost rozlišit dané vektory dokumentů, neboli bity, kterými tyto náhodné vektory přispějí do signatur, budou zcela zbytečné ( shodné ). Tomuto jevu by se dalo předejít, pokud by náhodné vektory byly generovány tak, jak je vidět na obr. 3 – toto generování ale odporuje zásadě vytvořené na základě prvního patologického jevu ( čárkované čáry nyní představují kolmice na vektory u a w ). RND1 term2 RND2 v u w O term1 RND3 Obr. 3: Náprava druhého patologického jevu Neschopnost odhalit fakt, že vektory mnoha dokumentů jsou velice podobné a že by tudíž náhodné vektory měly být generovány jinak než rovnoměrně, tvoří slabinu LSH algoritmu. Na druhou stranu zjistit, že vektory mnoha dokumentů se liší pouze málo, by vyžadovalo použití stávajících metod vyhledávání velmi blízkých duplicit ještě před tvorbou signatur a jelikož hledání blízkých duplicit je účelem naší práce, byla by po provedení tohoto výpočtu tvorba signatur lichá. Řešení tohoto problému tedy existuje, ale pouze takové, které nepřinese žádnou časovou úsporu. V reálném provozu nám bohužel nezbývá než spoléhat, že vektory dokumentů jsou po celém prostoru rozmístěny ( téměř ) rovnoměrně. V opačném případě bude relevance výsledků získaných pomocí sigantur nižší, neboť velice podobným dokumentům budou přiřazeny signatury více podobné, než by se skutečnosti být měly ( nebo dokonce totéžné ) a tudíž bude podobnost těchto dokumentů nadhodnocena ( na rovných 100% v nejhorším případě ). 2.4 PLEB ALGORITMUS Jak bylo řečeno výše, jednou z vlastností, které od našeho řešení očekáváme, je snížení časové složitosti oproti podobnostní metodě, v opačném případě jeho konstrukce pozbývá významu. Pokud porovnáváme, stejně jako to dělá podobnostní metoda, každé dvě dvojice dokumentů, řešíme úlohu mnohem složitější, než je ta, kterou řešit máme – namísto hledání téměř identických dokumentů řešíme úlohu “Výpočet podobností každých dvou dokumentů kolekce”. Ale pokud je naším záměrem odhalit pouze téměř identické dokumenty, je velmi pravděpodobné, že drtivá většina výpočtů podobností dvojic dokumentů nám do výsledku nepřispěje, neboť téměř identické dokumenty tvoří zpravidla velmi malou část kolekce. Řešení problému časové náročnosti výpočtu tím, že odstraníme výpočet podobností, které nám pro řešení naší úlohy nepřinesou relevantní informace, je uvedeno v [1] pod názvem PLEB – Point Location in Equal Balls. Obecnou podmínkou funkce algoritmu je existence hashovací funkce splňující (V1) z článku 2.2 Krok 1: pro každý dokument vytvoř odpovídající signaturu Krok 2: následuje L iterací samotné výkonné části algoritmu: Iter – begin: Iteracni_Krok1: Lexikograficky setřiď seznam signatur Iteracni_Krok2: Projdi seznam a najdi téměř identické dokumenty Iteracni_Krok3: Zvol libovolnou permutaci s právě jedním cyklem délky dva a aplikuj ji na všechny signatury Iter – end Komentář k Iteracni_Krok1: Pro každou signaturu ze setříděného seznamu zřejmě platí, že v jejím okolí jsou signatury jí podobné a díky (V1) platí, že i odpovídající dokumenty jsou si podobné – pro každou signaturu seznamu je tedy potřeba prohlížet v seznamu výše a níže položené signatury do doby, dokud je jejich podobnost větší než požadovaná prahová hodnota. Průchodem celého seznamu signatur můžeme pro všechny dokumenty najít signatury podobné a tím dostat část požadovaného výstupu, odtud plyne Iteracni_Krok2. Komentář k Iteracni_Krok2: Mějme nyní dvě libovolné signatury: sig ( A) : 01010101011100 sig ( B ) : 01110101011100 Obě signatury se liší v jediném bitu, vycházeje tedy z (V1) se budou i dokumenty A a B lišit málo. V seznamu signatur ale obě signatury leží díky lexikografickému uspořádání daleko od sebe a tato dvojice tedy v Iteracni_Krok2 nebude odhalena a zařazena do výstupu aplikace. K nápravě slouží poslední krok iterace. Náhodně zvolíme permutaci s právě jedním cyklem délky dvě a tuto aplikujeme na všechny signatury. Při vhodné volbě signatury (v našem případě cyklus {3,14}) bude sig´( A) = perm[ sig ( A)] : 01010101011100 sig´(B) = perm[ sig ( B )] : 01010101011101 Podobnost nově vzniklých signatur je veliká a tedy se dvojice (A, B) při další iteraci v Iteracni_Krok2 dostane do výstupu aplikace, což chceme. 3) IMPLEMENTAČNÍ ČÁST 3.1 DEKOMPOZICE ÚLOHY Na implementační úrovni je úkol řešen balíkem aplikací, z nichž každá se bude stará o jinou fázi hledání duplicit. Vstupně výstupní závislosti jednotlivých aplikací ukazuje diagram níže. Každá šipka představuje právě jednu aplikaci, jejíž povinné parametry jsou orámovány plnou čarou, zatímco čárkovanou čarou jsou označeny parametry volitelné. Všechny aplikace balíku v případě volání s parametrem “-h” či bez parametrů podají informaci o způsobu volání a významu svých jednotlivých parametrů. KOLEKCE BOWy CREATE_BOWS ... EMA MÁ MASO ... BOW_DIR DOC_LIST BOW_PATHS ID_FILE DOC_COUNT SAVE_NUMS SHINGLE_SIZE COMPRESS_BOWS SLOVNÍK CREATE_VOC ... EMA(1) MÁ(1) MASO(1) ... BOW_PATHS VOC_NAME ... EMA 5 3 MÁ 2 2 MASO 1 1 ... CREATE_VECS BOW_PATHS VEC_PATHS VEC_DIR VOC_NAME COMPRESS_VECS USE_TF_ONLY REDUCE_VOC MIN MAX či MIN COUNT DOKUMENTY VEKTORY OLD_VOC_NAME NEW_VOC_NAME ... EMA 5 3 MASO 1 1 ... SIMILARITY TFij VEC_PATHS TRESHOLD OUT SIM_METH SAMPLE_LIST i MATICE PODOBNOSTI DOC_IDi Sim(DociDocj) TERMY DOCi j CREATE_SIGS SIG_DIR SIG_LEN VEC_PATHS NO_NORM SIG_PATHS RND_SEED COMPRESS_SIGS USE_HDD COMPRESS_RND_VECS BELOW_ZERO_PERCENT POROVNÁNÍ DOCj COMPARE_RESULTS SIM ID_FILE OUT HAM SIGNATURY HAMMING LSH( VECi ) DOC_IDi LSH( VECj ) DOC_IDj REDUKOVANÝ SLOVNÍK TRESHOLD OUT BEAM SIG_PATHS ITER_COUNT SAMPLE_LIST SHIFT_COUNT MATICE PODOBNOSTI Hamm(DociDocj) ... Results for A: DOC1 59.3 60.8 DOC2 89.8 99.7 ... ... Results for B: DOC1 22.3 33.8 DOC2 45.8 38.7 ... DOCi HAMMING_FULL TRESHOLD OUT SIG_PATHS SAMPLE_LIST DOCj 3.2.SEZNAM KOMPONENT A JEJICH FUNKCE ( UŽIVATELSKÁ DOKUMENTACE ) CREATE_BOWS Pro všechny dokumenty kolekce sestaví bag of words (dále jen BOW), neboli množinu slov dokumentu spolu s počtem výskytů slov v rámci daného dokumentu. CREATE_VOC Vytvoří slovník celé kolekce, ke každému slovu kolekce navíc uchová celkový počet výskytů v celé kolekci a počet dokumentů, v nichž je dané slovo obsaženo. REDUCE_VOC Ze slovníku vytvořeného aplikací create_voc odstraní ta slova, která nevyhovují uživatelem daným podmínkám na TF, DF či TF_IDF. CREATE_VECS Ze slovníku a BOWu každého dokumentu kolekce vytvoří vektor odpovídající danému dokumentu. V závislosti na použitých přepínačích jsou vektory tvořeny hodnotami TF či TF_IDF. CREATE_SIGS Vektory vytvořené v aplikaci create_vecs převede na signatury pomocí LSH algoritmu. SIMILARITY Na vektory vytvořené aplikací create_vecs aplikuje uživatelem danou metodu na porovnávání vektorů dokumentů (implicitně kosinová míra) a průchodem „každý s každým“ odfiltruje ty dvojice dokumentů, které svou podobností (vůči použité porovnávací metodě) vyhovují uživatelem zadané prahové hodnotě. HAMMING Na signatury vytvořené aplikací create_sigs aplikuje, průchodem pomocí algoritmu PLEB, funkci na zjištění hammingovy vzdálenosti a odfiltruje ty dvojice dokumentů, jejichž signatury svou procentuální hammingovou vzdáleností překračují uživatelem zadanou prahovou hodnotu. HAMMING_FULL Na signatury vytvořené aplikací create_sigs aplikuje, průchodem „každý s každým“, funkci na zjištění hammingovy vzdálenosti a odfiltruje ty dvojice dvojice dokumentů, jejichž signatury svou procentuální hammingovou vdzáleností překračují uživatelem zadanou prahovou hodnotu. COMPARE_RESULTS Ze souborů obsahujících výsledek aplikací hamming, resp. hamming_full a similarity vytvoří přehledné porovnání hodnot podobností vytvořených oběma aplikacemi pro jednotlivé dvojice dokumentů. 3.3 VÝZNAM JEDNOTLIVÝCH PŘEPÍNAČŮ KOMPONENT Aplikace, které mají pouze jeden výstup, konkrétně se jedná o aplikace create_voc, reduce_voc, hamming, hamming_full, similarity a compare_results, mohou svůj výstupní soubor rovnou komprimovat, čímž dojde k významné úspoře místa na disku. Tohoto efektu lze dosáhnout použitím koncovky „.gz“ u jména výstupního souboru. U ostatních aplikací je komprimace výstupu nutno vynutit voláním s přepínačem -compress_***. CREATE_BOWS • • • doc_list jméno_souboru : soubor obsahující jména souborů kolekce bow_paths jméno_souboru : soubor k uložení cest k BOW souborům bow_dir jméno_adresáře : jméno adresáře k uložení BOW souborů • compress_bows : BOW soubory budou komprimovnány. Volitelný parametr. • doc_count prirozene_cislo : počet prvních řádků souboru doc_list, které budou zpracovány. Volitelný parametr • shingle_size prirozene_cislo : parametr udávající číslo k takové, že k-tice po sobě jdoucích slov dokumentu jsou považována za “nová ”slova. Implicitní hodnota je nastavena na jedna.Volitelný parametr. • save_nums : při použití tohoto přepínače budou brána v potaz i slova skládající se pouze z číslic. Volitelný parametr • id_file jméno_souboru : při použití toho přepínače bude zavedeno mapování jmen dokumentů na ID, se kterým se bude pracovat v průběhu celého výpočtu a dojde tím k výrazné úspoře velikosti souborů hamming, hamming_full a similarity. Do zadaného souboru bude uloženo vzniklé mapování. Volitelný parametr. POZN: BOW soubory jsou soubory obsahující BOW záznamy dokumentů. Pro úsporu místa na disku, nižší fragmentaci disku a rychlejší přístup k datům jsou tyto soubory velké, s BOWy mnoha dokumentů namísto jednoho BOW souboru pro každý dokument. Soubor doc_list je jediným uživatelským vstupem aplikace. Jeho obsahem jsou cesty (absolutní či relativní) k dokumentům kolekce, každá oddělená bílým znakem. CREATE_VOCABULARY • bow_paths jméno_souboru : soubor obsahující cesty k BOW souborům • voc_name jméno_souboru : jméno souboru k uložení slovníku POZN: Aplikací vytvořený slovník může být rovnou komprimovaný. Tohoto efektu lze dosáhnout tak, že soubor uvedený u parametru voc_name bude mít koncovku .gz. REDUCE_VOCABULARY • • • old_voc_name jméno_souboru : jméno souboru obsahující slovník k redukci new_voc_name jméno_souboru : jméno souboru pro uložení redukovaného slovníku omezení dané následujícími možnostmi: • min_tf float, max_tf float : hranice TF pro výběr slovníku • • • • • min_tf float, count prirozene_cislo : spodní hranice TF a požadovaná mohutnost redukovaného slovníku min_df float, max_df float : hranice DF pro výběr slovníku min_df float, count prirozene_cislo : spodní hranice TF a požadovaná mohutnost redukovaného slovníku min_tf_idf float, max_tf_idf float : hranice TF_IDF pro výběr slovníku min_tf_idf float, count prirozene_cislo : spodní hranice TF_IDF a požadovaná mohutnost redukovaného slovníku POZN: Aplikace automaticky rozpoznává, zda old_voc_name je soubor komprimovaný či nikoliv, bez explicitního uvedení, takže je kompatibilní se všemi možnými výstupy create_voc. CREATE_VECTORS • • • • • • bow_paths jméno_souboru : jméno souboru se jmény BOW souborů voc_name jméno_souboru : jméno souboru se slovníkem vec_dir jméno_adresáře : jméno adresáře k uložení VEC souborů vec_paths jméno_souboru : jméno souboru k uložení cest k VEC souborům use_tf_only : vektory dokumentů budou sestaveny z hodnot tf a nikoliv tf_idf. Volitelný parametr. compress_vecs : při použití tohoto přepínače budou VEC soubory komprimovány.Volitelný parametr. POZN: VEC soubory jsou soubory obsahující samotné vektory jednotlivých dokumentů kolekce. Pro úsporu místa na disku, nižší fragmentaci disku a rychlejší přístup k datům jsou tyto soubory velké, každý obsahuje vektory mnoha dokumentů namísto jednoho VEC souboru pro každý dokument. SIMILARITY • • • • vec_paths jméno_souboru : jméno souboru obsahujícího cesty k VEC souborům out jméno_souboru : jméno souboru k uložení výsledku treshold float : limitní hodnota podobnosti, pouze dvojice s podobností vyšší nebo rovnou treshold budou zařazeny do výsledku sim_meth retezec : určení metody porovnávající vektory dvou dokumentů. Volitelný parametr, implicitní nastavení je kosinus. • sample_list jméno_souboru : soubor obsahující vzorek, tedy podmnožinu dokumentů kolekce, (jenom) vůči kterým se bude podobnost počítat. Volitelný parametr. CREATE_SIGNATURES • • • • • • • • • • vec_paths jméno_souboru : jméno souboru obsahujícího cesty k souborům sig_len prirozene_cislo : požadovaná délka signatur sig_dir jméno_adresáře : jméno adresáře pro uložení SIG souborů sig_paths jméno_souboru : jméno souboru pro uložení cest k SIG souborům no_norm : pokud je tento parametr uveden, vektory dokumentů nebudou před převodem na signatury normalizovány.Volitelný parametr. rnd_seed prirozene_cislo : seed generátoru náhodných čísel.Volitelný parametr. compress_sigs : při použití tohoto přepínače budou SIG soubory komprimovány. Volitelný parametr. use_hdd prirozene_cislo : při použití tohoto přepínače budou náhodné vektory generovány na disk. Hodnota uvedená za přepínačem určuje, kolik MB RAM může být využito k uložení náhodných vektorů. Volitelný parametr. compress_rnd_vecs : při použití tohoto přepínače budou soubory s nagenerovanými náhodnými vektory ukládány komprimovaně. Volitelný parametr. below_zero_percent prirozene_cislo : hodnota tohoto parametru určuje, kolik procent složek náhodných vektorů musí být kladných, resp. záporných. Např. Při hodnotě 30 musí podíl kladných i záporných složek přesáhnout 30%. Volitelný parametr. POZN: SIG soubory jsou soubory obsahující dvojice signatura - jméno souboru. V zájmu úspory místa na disku a následné úspory času při čtení souboru signatur je vhodné ukládat signatury nikoliv jako bitové řetězce, ale jako řetězce šestnáctkových cifer (každá šestnáctková cifra odpovídá čtveřici bitů. Z toho vyplývá podmínka, aby požadované délky signatur byly dělitelné čtyřmi. Navíc je velmi vhodné zvolit délku signatury (alespoň v naší implementaci) jako číslo dělitelné osmi, jinak nebude plně využit paměťový potenciál a tudíž ani potenciál relevance vrácených výsledků. HAMMING • • • • • • iter_count přirozené_číslo : počet iterací PLEB algoritmu sig_paths jméno_souboru : jméno souboru obsahujícího cesty k SIG souborům treshold float : limitní hodnota podobnosti, pouze dvojice s podobností vyšší nebo rovnou treshold budou zařazeny do výsledku out jméno_souboru : jméno souboru k uložení výsledku beam přirozené_číslo : rozpětí, v rámci kterého se v algoritmu PLEBhledají podobné signatury vůči aktuálně zpracovávané signatuře shift_count přirozené_číslo : počet permutací aplikovaných na vektor signatur v rámci jedné iterace PLEB algoritmu. Volitelný parametr, implicitní hodnota rovna jedné. • sample_list jméno_souboru : soubor obsahující vzorek, tedy podmnožinu dokumentů kolekce, (jenom) vůči kterým se bude podobnost počítat. Volitelný parametr. HAMMING_FULL • sig_paths jméno_souboru : jméno souboru obsahujícího cesty k SIG souborům • treshold float : limitní hodnota podobnosti, pouze dvojice s podobností vyšší nebo rovnou treshold budou zařazeny do výsledku out jméno_souboru : jméno souboru k uložení výsledku sample_list jméno_souboru : soubor obsahující vzorek, tedy podmnožinu dokumentů kolekce, (jenom) vůči kterým se bude podobnost počítat. Volitelný parametr. • • COMPARE_RESULTS • sim jméno_souboru : jméno souboru obsahujícího výsledek aplikace similarity • ham jméno_souboru : jméno souboru obsahujícího výsledek aplikace hamming, resp. hamming_full • out jméno_souboru : jméno souboru k vytištění výsledného porovnání • id_file jméno_souboru : jméno souboru obsahujícího mapování ID dokumentů na jejich jména. Volitelný parametr k použití pouze v případě, že aplikace create_bows byla volána s parametrem id_file POZN: Aplikace automaticky rozpoznává, zda jsou soubory ham a sim komprimované či nikoliv a je tedy plně kompatibilní se všemi možnými kombinacemi výstupů aplikací hamming, resp. hamming_full a similarity. 3.4TECHNICKÁ DOKUMENTACE 3.4.1 ÚVOD K TECHNICKÉ DOKUMENTACI Rychlost a správná funkce celého balíku aplikací v naprosté míře závisí na několika hlavních datových strukturách, které, po jedné, tvoří ústřední část některých aplikací Pro aplikaci create_voc je to hashmapa mapující typ struktura na typ char * a pro create_sigs, hamming a hamming_full je to struktura TSignatura uchovávající binární signatury přímo v bitech. Míra efektivity a úspornosti kódu těchto struktur hraje kritickou úlohu pro rychlost celého výpočtu ( implementační detaily viz níže ). Po jedné nyní rozebereme implementaci každé ze jmenovaných struktur. ( Pozn: implmentace celého balíku aplikací samozřejmě zahrnuje mnohem více (datových) struktur. Pro příklad uveďme řídké vektory využívané aplikací create_vecs a create_sigs, či hashmapy mapující v různých aplikacích odlišné typy na typ char * - jejich implementace je však zcela klasická a proto nemá valný smysl se o nich v tomto dokumentu více rozepisovat). 3.4.2 HLAVNÍ DATOVÉ STRUKTURY 3.4.2.1 STRUKTURA TSignatura struct TSignatura{ unsigned int Zaloz(char * Signatura_V_Retezci); //pro inicializaci pomoci prooudu 0 a 1 unsigned int Zaloz(unsigned int Delka_V_Bitech); // pro zalozeni a nastaveni na same nuly void Znic(); //odboda destruktoru, odalokuje naalokovana data void Nastav_Bit(unsigned int Index, unsigned int Hodnota); unsigned int Zjisti_Bit(unsigned int Index) const; void Prohod_Bity(unsigned int Prvni_Index, unsigned int Druhy_Index); // pro PLEB unsigned long* Adresa_Signatury(void){ return _data;}; unsigned long * _data; void Vypis_Signaturu(void); //pouze pro ladici ucely }; Z výše uvedených metod struktury je zajímavá pouze implementace metody Zjisti_Bit a Nastav_Bit, implementace ostatních je většinou závislá na těchto dvou. Struktura TSignatura uchovává svá data v poli typu unsigned long. Jednotlivé bity jsou ve struktuře uloženy přesně v tom pořadí, v jakém za sebou jdou v signatuře. Čtení a nastavení jednotlivých bitů probíhá pomocí aplikace bitových masek na příslušné položky _data (lehce k dohledání ve zdrojovém kódu nejdříve je pomocí operace modulo určena složka _data, ve kterém je hledaný bit uložen a následně je na vybranou složku _data aplikována bitová maska v závislosti na tom, zda se jedná o operaci Zjisti_Bit či Nastav_Bit a na tom,zda je nastavovaná či čtená hodnota rovna jedné či nule). Struktura v rámci úspory času nekotroluje meze, takže přístup na neplatný index vyvolá (dřívě či později) běhovou chybu. Důvodem pro ukládání dat struktury v poli typu unsigned long je snaha o co nejlepší spolupráci struktury TSignatura s procesorem. Datový typ unsigned long z datových typů obsažených v jazyce C nejvíce odráží architekturu daného stroje ( ačkoliv počet bytů toho či onoho datového typu je věcí překladače ) a proto je nejvhodnější použít právě tento, pokud chceme zaručit, že procesor bude moci pracovat vždy s daty velikosti svého slova a nebude tak zpomalován extrakcí menších datových typů z větších, pro daný stroj nativních. V závislosti na použitém nastavení překladače ( což by se volně mohlo chápat též jako „na použité architektuře“ ), resp. v závislosti na konkrétním počtu bytů datového typu unsigned long jsou nadefinovány příslušné konstanty, které struktura TSignatura využívá, aby byla zajištěna správná funkčnost této a též je vybrána správná funkce zjišťující hammingovu vzdálenost signatur, jejíž implementace se jak pro 32b, tak pro 64b unsigned long liší ( více o této implementaci v dokumentaci aplikace hamming ). 3.4.2.2 STRUKTURA typu hash_map < char * , struct X > Tato stuktura, která se od normální implementace hash_mapy poněkud liší, je použita pouze v aplikaci create_voc a compare_results. V ostatních aplikacích, jak již bylo řečeno výše, jsou použity klasické implementace hash_mapy, neboť pouze v aplikaci create_voc má použití této nestandardní implementace odůvodnění z důvodu úspory procesorového času, v aplikaci compare_results je použití této datové struktury spíše záležitostí esteticity kódu, rychlost aplikace compare_results totiž drtivou měrou ovlivňuje rychlost načítání dat z disku.. Všechny implementace hash_mapy, včetně této, používají následující hashovací_funkci: unsigned long Umocni(unsigned int Mocnenec, unsigned int Exponent){ unsigned long Vysledek = (unsigned long)Mocnenec; unsigned int i; for (i = 1; i < Exponent; i++) Vysledek *= Mocnenec; return Vysledek; }; unsigned int Prvociselne_Hashovani(char * Slovo){ unsigned int i; unsigned long Vysledek = 0, Nasobitel; for (i = 0; i < strlen(Slovo); i++){ Nasobitel = Umocni(PRVOCISLO, i); Nasobitel *= (unsigned long)Slovo[i]; Vysledek += Nasobitel; Vysledek = Vysledek % VELIKOST_MAPY; }; return Vysledek; }; Pozn: PRVOCISLO a VELIKOST_MAPY jsou konstanty definované ( za překladu ) v rámci projektu. Při ukládání slova je pomocí funkce (viz výše) spočten index do pole kontejnerů typu vektor, do nějž jsou ukládána data o všech slovech, která na daném indexu kolidují ( princip totožný s klasickou implementací hash_mapy ). Do tohoto vektoru není uloženo samotné slovo ani data ke slovu se vztahující, nýbrž index do vnějších polí, na kterém jsou slovo a k němu se vztahující data uložena. V aplikaci create_voc jsou tato pole konrétně Pole_Slov, Pole_TF, Pole_DF a Pole_Pridano_V_Tomto_Kole (poslední je typu bool * a údaje v něm uložené slouží k indikaci toho, zda právě zpracovávané slovo se již v právě zpracovávaném dokumentu vyskytlo či nikoliv, tedy jako pomůcka při výpočtu DC daného slova). Všechna pole jsou stejně velká (co do hranice indexů) a na jednotlivých indexech jsou ve všech polích údaje týkající se jednoho slova. Důvodem, pro který byla zvolena tato implementace namísto klasické, kdy jsou data ukládána rovnou do vektorů obsahujících kolidující záznamy, je ten, že před zpracováním BOWu každého dokumentu kolekce je nutno položku Pridano_V_Tomto_Kole všech slov nastavit na false. Při klasickém přístupu je tato operace realizovaná jako iterace přes celou mapu a její rychlost silně závisí na velikosti pole, na kterém jsou pověšené vektory obsahující kolidující záznamy a do něhož se pomocí hashovací funkce získává index (viz pozn. na další straně). Při použití našeho přístupu je tato operace řešena jediným memsetem pole Pole_Pridano_V_Tomto_Kole. Experimenty prokázaly, že použití našeho přístupu dokáže oproti použití klasického přístupu zkrátit čas výpočtu z několika hodin na několik vteřin. Nevýhodou našeho řešení je redundance dat, neboť s každým slovem je nezbytně nutné si navíc uchovávat i index do zmíněných vnějších polí ( poznamenejme ale, že tento nedostatek je pouze “teoretického rázu” - pokud je aplikace create_voc používána v rámci celého balíku aplikací, je při velikosti slovníku 1 milion slov paměťový overhead způsobený redundancí dat velký 4MB, což je pro stroje, pro které je naše softwarové dílo primárně určeno, triviální množství paměti ). Pozn. k výkonu klasické hash_mapy v aplikaci create_voc: experimenty s velikostí “nosného pole”, obsahujícím pointery na vektory záznamů kolidujících slov, která je v kódu definována názvem VELIKOST_MAPY, ukázaly velice zvláštní chování. Teoreticky by měly být rychlost klasické implementace hash_mapy a VELIKOST_MAPY ve vztahu přímé uměrnosti, tento vztah ovšem platí pouze do určité meze a to zhruba do VELIKOST_MAPY == 1000. Od této hranice dále je vztah těchto veličin podobný spíše druhu nepřímé úměry, tedy čím větší je VELIKOST_MAPY, tím pomalejší aplikace je. Na OS Windows by se tato skutečnost dala vysvětlit zvýšením počtu výpadků stránek, s OS Unix však toto vysvětlení nepřipadá v úvahu a fakt, že se daný jev vyskytoval i na OS Unix podkopává vysvětlení v případě OS Windows pomocí zvýšení počtu výpadků stránek. Připomeňme, že tuto anomálii vykazovala aplikace create_voc pouze při použití klasické implementace hash_mapy, nikoliv při použití výše popsané – při použití výše popsané implementace je mezi veličinou VELIKOST_MAPY a rychlostí aplikace create_voc vztah velice podobný přímé úměrnosti. 3.4.3. TECHNICKÁ DOKUMENTACE JEDNOTLIVÝCH APLIKACÍ CREATE_BOWS VSTUPNÍ DATA Jediným vstupem aplikace je soubor obsahující cesty ( ať už absolutní či relativní vůči místu spouštění aplikace ) k dokumentům kolekce, které mají být zpracovány. Jednotlivá jména musí být oddělena bílým znakem. VÝSTUPNÍ DATA Výstupem aplikace jsou BOW soubory (tedy soubory obsahující bag of words nějaké podmnožiny dokumentů kolekce) spolu se souborem obsahujícím informace o kolekci a cesty ke všem vytvořeným BOW souborům. V případě, že je aplikace spuštěna s přepínačem id_file, je výstupem aplikace i soubor mapující jména dokumentů kolekce na jejich ID. FORMÁT VÝSTUPNÍCH DAT Formát bag of words jednotlivých dokumentů je následujicí: jméno_souboru:mohutnost_BOWu term1:počet_výskutů_termu1 term2:...\n Formát souboru obsahujícího jména BOW souborů je následující: VELIKOST_KOLEKCE prirozene_cislo \n cesta_k_BOW_souboru1 \n cesta_k_BOW_souboru2 \n ... Cesty k BOW souborům jsou relativní vůči místu spuštění aplikace. Formát ID souboru: id1 jméno_dokumentu1\n id2 jméno_dokumentu2\n... IMPLEMENTACE Aplikace obsah každého dokumentu kolekce uloží do kontejneru typu hash_map < char *, unsigned int> (vlastní implementace, klasický přístup), její obsah po načtení celého dokumentu vytiskne do BOW souboru dbaje na to, aby v žádném BOW souboru nebyl překročen maximální počet zapsaných BOWů (maximální počet BOWů zapsaných do jednoho BOW souboru je definován konstantou za překladu). Pokud je zadán i přepínač id_file, je před zapsáním BOWu do BOW souboru spočteno ID dokumentu, ID se do BOW souboru vytiskne namísto jména dokumentu a dvojice ID - jméno dokumentu je zapsána do id_file. CREATE_VOC VSTUPNÍ DATA Jediným vstupem aplikace je soubor s cestami k BOW souborům vytvořený aplikací create_bows. VÝSTUPNÍ DATA Výstupem aplikace je soubor obsahující slovník celé zpracovávané kolekce. FORMÁT VÝSTUPNÍCH DAT Formát souboru, obsahujícího slovník, je následující: VELIKOST_SLOVNIKU přirozené_číslo \n VELIKOST_KOLEKCE přirozené_číslo \n term1 TC_termu1 DC_termu1 \n term2 TC_termu2 DC_termu2 \n ... IMPLEMENTACE Aplikace postupně prochází všechny BOW soubory a jejich obsah ukládá do kontejneru typu hash_map, který je podrobněji popsán v 1.2. Po přečtení všech BOW souborů je kompletní slovník uložený v hash_mapě vytištěn. REDUCE_VOC VSTUPNÍ DATA Jediným vstupem aplikace je soubor obsahující slovník určený ke zredukování. VÝSTUPNÍ DATA Výstupem aplikace je soubor obsahující redukovaný slovník. FORMÁT VÝSTUPNÍCH DAT Formát souboru, obsahujícího redukovaný slovník, je totožný s formátem výstupu aplikace create_voc. IMPLEMENTACE Implemtace této komponenty jako jediná využívá šablonovaných kontejnerů knihovny STL. Důvodem pro tuto volbu byly nevelké časové úspory získané užitím vlastní implementace těchto kontejnerů. Aplikace, v závislosti na vstupních parametrech, vybere funkci, která redukci slovníku obstará. Její pomocí jsou z neredukovaného slovníku odstraněna ta slova, která nevyhovují uživatelem zadaným požadavkům. V případě, že uživatelem zadaná podmínka na redukovaný slovník obsahuje i velikost redukovaqného slovníku, je vektor obsahující slova vyhovující podmínce setříděn dle veličiny, jejíž spodní hodnota byla uživatelem zadána, a nepotřebná část vektoru je odstraněna. Následně je veškerý obsah vektoru vypsán do výstupního souboru. CREATE_VECS VSTUPNÍ DATA Vstupy aplikace tvoří soubor s cestami k BOW souborům zpracovávané kolekce a soubor se slovníkem, vůči kterému mají být vektory vytvořeny. VÝSTUPNÍ DATA Výstupem aplikace jsou VEC soubory obsahující vektory dokumentů kolekce a soubor obsahující cesty k VEC souborům spolu s informacemi o zpracovávané kolekci. FORMÁT VÝSTUPNÍCH DAT Formát VEC souboru je následující: jméno_dokumentu1:počet_nenulových_složek_vektoru index1:hodnota1 index2:.... \n jméno_dokumentu2:počet_nenulových_složek_vektoru index1:hodnota1 index2:.... \n Formát souboru obsahující cesty k VEC souborům je následující: USING_TF ( nebo USING_TF_IDF) \n VELIKOST_SLOVNIKU přirozené_číslo \n VELIKOST_KOLEKCE přirozené_číslo \n POCET_ZAZNAMU_VE_VEC_SOUBORECH přirozené_číslo \n cesta_k_VEC_souboru1 \n cesta_k_VEC_souboru2 \n... IMPLEMENTACE Prvním krokem běhu aplikace je načtení celého slovníku do kontejneru typu hash_map ( vlastní implementace, klasický přístup ). Do této hash_mapy je ke každému slovu také uloženo odpovídající TC, DC a pořadové číslo slova ve slovníku. Následně jsou tvořeny vektory jednotlivých dokumentů kolekce – pro každé přečtené slovo z BOWu dokumentu je zkontrolována jeho přítomnost ve slovníku ( slovo se mohlo vytratit redukcí slovníku), následně je do vektoru daného slova přidána dvojice sestávající z pořadového čísla tohoto slova ve slovníku následovaného buď TF daného slova vzhledem k právě zpracovávanému dokumentu ( při použití přepínače -use_tf_only ) nebo hodnotou TF_IDF daného slova vzhledem k právě zpracovanému dokumentu a kolekci. Množina dvojic index:hodnota, tvořící vektor zpracovávaného dokumentu je uchovávána v kontejneru typu vektor dvojic – na konci tvorby vektoru každého dokumentu je vektor setříděn pomocí algoritmu QuickSort vzestupně dle pořadových čísel slov ve slovníku a následně zapsán do VEC souboru. Setříděním vektoru zajistíme jednodušší načítání vektorů v komponentách create_sigs a similarity a hlavně mnohem jednodušší operace s vytvořenými vektory v těchto komponentách (např. skalární součin v aplikaci similarity ap). CREATE_SIGS VSTUPNÍ DATA Vstupy aplikace tvoří soubor s cestami k VEC souborům zpracovávané kolekce. VÝSTUPNÍ DATA Výstupem aplikace jsou SIG soubory obsahující signatury dokumentů kolekce a soubor obsahující cesty k SIG souborům spolu s informacemi o zpracovávané kolekci. FORMÁT VÝSTUPNÍCH DAT Formát SIG souboru je následující: signatura1:INFO_signatury_1 seznam_odpovídajících_dokumentů \n signatura2:INFO_signatury_2 seznam_odpovídajících_dokumentů \n ... (kde INFO_signatury_X je počet dokumentů kolekce sdílejících danou signaturu) Formát souboru obsahující cesty k SIG souborům je následující: VELIKOST_KOLEKCE přirozené_číslo \n DELKA_SIGNATUR_V_BITECH přirozené_číslo \n POCET_RUZNYCH_SIGNATUR přirozené_číslo \n cesta_k_SIG_souboru1 \n cesta_k_SIG_souboru2 \n ... IMPLEMENTACE Prvním krokem aplikace je pokus naalokovat pole pro uložení náhodných vektorů. V případě úspěchu následuje naplnění těchto naalokovaných polí náhodnými čísly a po skončení této fáze začíná konverze. Ta probíhá po blocích – v rámci minimalizace paměťových nároků aplikace ( náhodné vektory mohou být opravdu velké, až v řádech desítek GB! ) je vždy načten pouze jeden vektor, pomocí LSH algoritmu je převeden na signaturu, která je okamžitě zapsána do tzv. temp_sig souboru, tj. souboru, který obsahuje dvojice signatura - odpovídající dokument, kde ale soubor vzniklý konkatenací všech temp_sig_souborů ( po skončení konverze všech vektorů ) může obsahovat řádky s toutéž signaturou. Pro každý VEC soubor je vytvářen zvláštní temp_sig soubor. Po konverzi posledního vektoru jsou uvolněna data naalokovaná pro náhodné vektory a začíná načítání temp_sig souborů. Signatury jsou ukládány do hash_mapy (klasický přístup, vlastní implementace) mapující odpovídající dokumenty na signatury (chceme zjistit, jestli nějaké signatuře neodpovídá více než jeden dokument a pokud ano, pak si tuto skutečnost zapamatovat, tato hash_mapa využívá v rámci minimalizace paměťových nároků této fáze výpočtu strukturu uvedenou v 1.1), temp_sig soubory jsou po svém přečtení ihned smazány. Při úspěšném načtení všech signatur do hash_mapy začíná výpis mapy do SIG souborů. Pokud je aplikace volána s přepínačem -use_hdd, jsou nejdříve do tzv. RND_VEC souborů na disku nagenerovány jednotlivé náhodné vektory, počet vektorů uložených do jednoho souboru, potamžo počet těchto souborů je určen konstantou, která následuje za přepínačem -use_hdd. Po nagenerování náhodných vektorů je do paměti načten první RND_VEC soubor a pomocí vektorů obsažených v něm jsou vytvořeny první části signatur všech dokumentů, které jsou následně uloženy do dočasných souborů ( tzv. Partial_Temp_Sig soubory ). Následně je načten druhý RND_VEC soubor, pomocí vektorů v něm uložených jsou spočteny druhé části signatur a tyto jsou připojeny k dříve nagenerovaným začátkům signatur. Tento proces je iterován přes všechny soubory obsahující náhodné vektory. Po skončení čtení RND_VEC souborů jsou tyto smazány a v dočasných ( Partial_Temp_Sig ) souborech dříve obsahujících začátky signatury jsou nyní uloženy signatury celé. Formát těchto souborů je totožný s formátem Temp_Sig souborů vytvořených po nagenerování všech signatur při volání aplikace bez přepínače -use_hdd. Zpracování Partial_Temp_Sig souborů a další výpočet je tedy od této chvíle totožný s tím uvedeným v odstavci výše. SIMILARITY VSTUPNÍ DATA Vstupy aplikace tvoří soubor s cestami k VEC souborům zpracovávané kolekce. VÝSTUPNÍ DATA Výstupem aplikace je soubor obsahující jména dvojic dokumentů, které svou podobností převyšují uživatelem zadanou prahovou hodnotu. FORMÁT VÝSTUPNÍCH DAT Formát výsledku výpočtu je následující: dokument1:podobny_dokument1:podobnost11:podobny_dokument2:podobnost12.. \n dokument2:podobny_dokument1:podobnost21:podobny_dokument2:podobnost22.. \n ... neboli každý řádek obsahuje seznam dokumentů podobných dokumentu, jehož jméno je uvedeno jako první na řádku. IMPLEMENTACE Aplikace načte obsah všech VEC souborů do paměti do kontejeru typu vektor řídkých vektorů. Poté se pomocí dvou indexů, nazvěme je A a B, prohledává prostor dvojic řídkých vektorů. Index A je na počátku nastaven na nulu a ukazuje na první řídký vektor, index B je nastaven vždy na hodnotu indexu A a následně stoupá, vždy po spočtení kosinu řídkých vektorů ležících ve vektoru řídkých vektorů na pozicích A a B. Jestliže hodnota kosinu této dvojice přesáhne prahovou hodnotu, je tato informace připsána do souboru out a zároveň jsou indexy A a B přidány do datové strukty sdružující indexy (strukturu nazvěme Výsledek) do vektoru řídkých vektorů na dvojice, jejichž podobnost převyšuje prahovou hodnotu. Tuto informaci je nutno uložit z toho důvodu, že pokud počítáme podobnost Dokumentu1 s Dokumentem2, je jisté, že podobnost Dokumentu2 vůči Dokumentu1 nikdy počítat nebudeme, neboť bychom za cenu zdvojnásobení doby běhu aplikace přicházeli dvakrát na tytéž informace. Při vypisování dokumentů podobných Dokumentu2 ale Dokument1 vypsat musíme – proto je nutné si indexy na vektory podobných dokumentů spolu s vypočtenou pravděpodobností ukládat a datovou strukturu, kam jsou tato data uložena, při vypisování výsledného souboru procházet. HAMMING VSTUPNÍ DATA Vstupy aplikace tvoří soubor s cestami k SIG souborům zpracovávané kolekce. VÝSTUPNÍ DATA Výstupem aplikace je soubor obsahující jména dvojic dokumentů, které jsou podobností převyšují uživatelem zadanou prahovou hodnotu. FORMÁT VÝSTUPNÍCH DAT Formát výsledku výpočtu je následující: dokument1:podobny_dokument1:podobnost11:podobny_dokument2:podobnost12... \n dokument2:podobny_dokument1:podobnost21:podobny_dokument2:podobnost22... \n ... neboli každý řádek obsahuje seznam dokumentů podobných dokumentu, jehož jméno je uvedeno jako první na řádku.V případě, že má být do výsledku vypsáno jméno dokumentu, který sdílí signaturu s jinými dokumenty, jsou do výsledku zapsána jména všech těchto dokumentů jako celek ve tvaru dok1(dok2 dok3 ... dokN). IMPLEMENTACE Aplikace načte obsah všech SIG souborů do paměti do kontejneru typu vektor signatur. Poté pomocí algoritmu PLEB hledá dvojice signatur, jejichž hammingova vzdálenost vyhovuje uživatelem zadané prahové hodnotě podobnosti. Pokud je taková dvojice nalezena, jsou extrahovány adresy obou signatur a tyto jsou spolu s podobností uloženy do hash_mapy obsahující výsledek podobně jako v aplikaci similarity. S pomocí této mapy je na konci vyhledávání vypsán výsledek (na rozdíl od aplikace similarity je v hamming celý výsledek vypisován až na konci běhu). Zajímavými rysy implementace této aplikace jsou 1/ při algoritmu PLEB je vektor signatur prohledáván pouze jedním směrem, zatímco autoři článku, dle nějž jsme pracovali, prohledávají (zbytečně) směry oběma 2/ funkce zjišťující hammingovu vzdálenost signatur je implementována následovně. Na začátku běhu aplikace je inicializováno pole Pocet_Jednicek_Bin_Zapisu, v němž je na i-té pozici uložen počet jedniček binárního zápisu čísla i. Počet složek tohoto pole je 2^16, neboli dokáže uchovávat počty jedniček binárního zápisu pro všechna šestnáctibitová čísla. Při výpočtu hammingovy vzdálenosti dvou signatur uložených ve struktuře TSignatura (viz 1.1) jsou brány jednotlivé šestnástibitové složky obou unsigned long polí, na ně je aplikována operace XOR a výsledné číslo je použito jako index do pole Pocet_Jednicek_Bin_Zapisu. Hodnota na tomto indexu je přičtena k výsledné hammingově vzdálenosti, která je na počátku výpočtu inicializována hodnotou nula, a tímto postupem se iteruje přes všechny šestnáctibitové složky datových polí obou signatur. Oproti primitvní implementaci ( volání metody Zjisti_Bit() na všechny bity obou signatur ) přináší tato implementace zrychlení cca. pětisetnásobné. V závislosti na počtu bytů datového typu unsigned long je potřeba zvolit správnou implementaci funkce zjišťující hammingovu vzdálenost dvou signatur, neboť v implementaci pro čtyřbajtový unsigned long stačí na každou složku _data struktury TSignatura aplikovat dvě bitové masky, zatímco pokud je typ unsigned long osmibytový, je potřeba aplikovat bitové masky čtyři. HAMMING_FULL Všechny vlastnosti aplikace hamming_full jsou totožné s vlastnostmi aplikace hamming, v této aplikaci se ale nevyužívá algoritmus PLEB, vypisování a princip tvorby výsledku je totožný s tím v aplikaci similarity. COMPARE_RESULTS VSTUPNÍ DATA Vstupy aplikace tvoří soubor s výsledkem výpočtu aplikace similarity, soubor s výsledkem výpočtu aplikace hamming ( či hamming_full ) ( soubory vytvořené aplikacemi similarity a hamming, resp. hamming_full by měly být vytvořeny s použitím stejného sample_listu), při použití přepínače -id_file v aplikaci create_bows tvoří poslední část vstupu id_file, tedy soubor mapující ID dokumentů na jejich jména. VÝSTUPNÍ DATA Výstupem aplikace je soubor obsahující přehledné porovnání podobností vytvořených aplikacemi similarity a hamming, resp. hamming_full pro všechny dvojice dokumentů nalezených ve vstupních souborech. FORMÁT VÝSTUPNÍCH DAT Formát výsledku výpočtu je následující: Results for X: Dok1 Sim_Podobnost_Dok1_Vuci_X Ham_Podobnost_Dok1_Vuci_X \n Dok2 Sim_Podobnost_Dok2_Vuci_X Ham_Podobnost_Dok2_Vuci_X \n ... (kde X je jméno dokumentu ze sample_listu, vůči němuž aplikace similarity a hamming, resp. hamming_full tvořily svůj výsledek) IMPLEMENTACE Aplikace pracuje blokově. V každém bloku načte jednu řádku souboru obsahujícího výsledek aplikace similarity, tedy seznam jmen dokumentů podobných jednomu dokumentu obsaženém v sample_listu, se kterým bylo similarity voláno. Tuto řádku uloží aplikace do hash_mapy ( vlastní implementace, principielně shodná s tou v 1.2). mapující hammingovskou podobnost a similarity podobnost na char *. Po uložení této řádky aplikace načte řádku souboru obsahujícího výsledek aplikace hamming, resp. hamming_full a tuto ukládá do též hash_mapy, do které se ukládala data z výsledku aplikace similarity ( zde je nutno pečlivě ošetřit případná jména dokumentů v závorce v případě, že více dokumentů sdílí jednu signaturu ). Pokud je po načtení řádky souboru vytvořeného aplikací hamming, resp. hamming_full zjištěno, že se oba řádky vztahují k jinému vztaženému souboru ( ze sample_list obou aplikací ), je výpočet ukončen, neboť vstupní soubory aplikací se netýkají stejného sample_listu. V opačném případě je pro každé jméno dokumentu kolekce po zpracování řádků obou vstupních souborů v hash_mapě obsažena hammingovská a similarity podobnost ( či speciální hodnoty, kterými mapu inicializujeme pro indikaci toho, že některá z podobností nebyla pro daný dokument nalezena – typicky v případě, kdy např. v aplikaci similarity dokument odpovídá požadavku treshold a v hamming, resp. hamming_full nikoliv ). Následuje vytištění výsledku ve formátu uvedeném výše, vyprázdnění hash_mapy a čtení následujícího řádku souboru vytvořeného aplikací similarity. EXPERIMENTÁLNÍ ČÁST MOTIVACE ZAVEDENÍ PARAMETRU SHIFT_COUNT V PLEB ALGORITMU V rámci toho, aby bylo uživateli aplikace hamming umožněno sledovat průběh výpočtu „online“, vypisuje aplikace informaci o tom, kolik párů vyhovujících danému tresholdu bylo v průběhu dosavadního výpočtu odhaleno. Díky těmto výpisům lze tedy velmi jednoduše sledovat „úspěšnost“ jednotlivých iterací, tedy počet párů, které byly objeveny právě v poslední iteraci a ne dříve. Pozorováním v době, kdy parametr shift_count ještě nebyl součástí naší modofikace algoritmu PLEB, bylo zjištěno, že velice často dochází k jevu, kdy v několika ( jednotkách až desítkách ) po sobě jdoucích iteracích nejsou objeveny žádné páry, které nebyly objeveny dříve, ačkoliv párů, které na své objevení ještě čekaly, bylo mnoho. Po krátké úvaze jsem zjistil/bylo zjištěno, že důvodem tohoto chování je skutečnost, že jediná aplikace permutace na vektor signatur pořadí signatur nijak dramaticky nezmění a okolí každé signatury se změní jen o málo.Důvodem této malé změny je fakt, že leží-li dvě signatury, vyhovující požadavku na vzájemnou hammingovu podobnost, ve vzájemné vzdálenosti menší nebo rovné beamu, potom jsou dle definice treshold procent bitů jejich signatur totožné a vybráním jakýchkoliv dvou indexů z těchto a následným prohozením se na postavení obou signatur nic nezmění ( jedině v případě, že by se mezi obě signatury dostaly nějaké, které mezi nimi před permutováním nebyly ). Je zřejmé, že pravděpodobobnost takového „špatného“ náhodného výběru indexů k prohození je (treshold / 100)^2. Řešení, spočívající v zavedení dalšího parametru jménem shift_count, který udává počet permutací aplikovaných bezprostředně za sebou před třídením vektoru signatur v rámci každé iterace, bylo nasnadě a později se parametr shift_count při zvyšování efektivity algoritmu PLEB projevil jako mnohem významnější než iter_count a beam. Z výše uvedené pravděpodobnosti plyne, že pro zachování efektivity algoritmu PLEB je nutné zvyšovat shift_count s rostoucím tresholdem. ANALÝZA PARAMETRECH EFEKTIVITY ALGORITMU PLEB V ZÁVISLOSTI NA JEHO Chování algoritmu PLEB ovlivňují tři faktory – počet iterací, velikost beamu a počet permutací aplikovaných před každým třídením vektoru signatur, tedy shift_count. Nastavením různých hodnot těchto parametrů lze ovlivnit efektivitu procesu hledání velice podobných signatur a tím zkrátit čas potřebný k nalezení určité procentuální části všech signatur, které svou vzájemnou podobností převyšují uživatelem zadanou hodnotu treshold, a/nebo zvětšit množinu nalezených párů při zachování časové náročnosti hledání. Cílem této kapitoly bude formulovat taková doporučení získaná na základě měření, s jejichž pomocí budeme moci algoritmus PLEB nastavit tak, aby v co nejkratším čase odhalil co nejvíce párů podobných signatur, resp. dokumentů. Nejdříve pár slov k metodice provádění experimentů. Posuzovat efektivitu nastavení algoritmu PLEB pomocí poměru ( # nalezených párů ) / ( čas běhu v sekundách ) není správným přístupem. To proto, že ( vycházeje z omezených zdrojů cílového stroje, zejména jeho operační paměti ) je nutné udržovat v průběhu celého výpočtu informaci o tom, jaké páry, svou podobností přesahující uživatelem zadaný treshold, už byly objeveny. Pokud bychom si takovou informaci nepoznamenávali a objevené páry rovnou zapisovali na výstup, je zcela možné, že jeden pár bychom objevili vícekrát ( a to například i tisíckrát – řeknemě při každé z jednoho tisíce iterací ) a tím by docházelo k vysoké redundanci dat a plýtvání místa na pevném disku. Je tedy zřejmé, že informace o již objevených podobných párech je nutné si uchovávat. Představme si nyní, že v iteraci IT1 objevíme podobnost páru signatur SA a SB rovnou HAB a tentýž pár objevíme při iteraci IT2. Pokud bychom při druhém objevení do kontejneru, kam si informace o již nalezených podobných párech poznamenáváme, podobnost obou signatur vepsali znovu, docházelo by znovu k redundanci dat, a to o to horší, že zatímco pevné disky mohou dnes být téměř libovolně velké, s operační pamětí tomu tak není a mohlo by se lehce stát, že díky tomuto lehkovážnému přístupu bychom nebyli schopni výpočet dokončit z důvodu nedostatku operační paměti. Je tedy zřejmé, že kdykoliv chceme do zmíněného kontejneru vložit nějakou informaci, je nejdříve nutné se ujistit, že tam tato ještě nebyla dříve zapsána. Tato nutnost tvoří slabinu algoritmu PLEB, neboť ověření, zda je daná informace o podobnosti již uložená či nikoliv může být i za předpokladu, že je kontejner implementován s maximální efektivitou, poměrně dlouhá. Nyní rozeberme dva teoretické průběhy algoritmu PLEB. Mějme prostor signatur, v němž je N párů takových, že vyhovuje našim požadavkům na výši podobnosti. Odhalení těchto N párů pomocí M volání funkce zjišťující hammingovu vzdálenost dvou signatur ( v nejlepším případe M=N ) bude jistě mnohem efektivnější, než odhalení stejného výsledku pomocí k*M; k > 1 volání téže funkce, neboť v prvním případě nalezneme nějaké dvojice vícekrát právě (M - N)-krát, zatímco v druhém případě to bude (k*M – N)-krát a tedy celkem (k-1)*M nalezení podobných dvojic bude v druhém případě zcela zbytečných. Je zřejmé, že počet aplikací výše zmíněné funkce je roven součin iter_count * beam a prvním zcela logickým kriteriem pro efektivní konfiguraci algoritmu PLEB je tedy snaha o co nejnižší součin hodnot iter_count a beam. Následujícím krokem při hledání optimální konfigurace musí být otázka, zda můžeme veličiny beam a iter_count považovat za stejné, co se spotřeby času týče, abychom byli schopni porovnat, zda nastavení iter_count = 10, beam = 5 a iter_count = 5, beam = 10 ( které má stený počet aplikací hammingovské míry), poběží ( teoreticky ) stejně dlouho. Odpověď poskytují série meření uvedené v tabulách níže. Byla provedeny čtyři série měření, každá z těchto sérií obsahovala čtyři běhy PLEB algoritmu. Každá série meření obsahovala jeden tzv. vztažný běh s nějakými hodnotami parametrů iter_count, beam a shift_count a následující tři běhy, v nichž byl vždy jeden parametr nastaven na desetinásobek hodnoty nastavené ve vztažném běhu. Hodnota treshold byla ve všech bězích nastavena na hodnotu 100, aby časový průběh jednotlivých běhů nebyl ovlivněn výše zmíněným kontejnerem uchovávajícím informaci o výsledku výpočtu a aby se tak výpovědní hodnota získaných dat týkala opravdu pouze vlivu velikosti iter_countu, beamu a shift_countu na výslednou délku běhu aplikace. Volání s parametrem treshold 100 vyřadí výše zmíněný kontejner z výpočtu proto, že se v rámci algoritmu PLEB hledají signatury, jejichž podobnost je vyšší či rovna 100% a podobnost 100% mají v případě signatur pouze dokumenty jednu signaturu sdílející, takže v průběhu výpočtu nemohou být žádné dvojice signatur tomuto tresholdu vyhovující nalezeny. iter_count beam shift_count čas iter_count beam shift_count čas 200 50 25 446 1000 15 15 863 2000 50 25 4684 10000 15 15 8706 200 500 25 3191 1000 150 15 4831 200 50 250 1419 1000 15 150 3513 Série měření PLEBu 2 iter_count beam shift_count Série měření PLEBu 1 čas iter_count beam shift_count čas 1000 25 15 1157 200 25 25 299 10000 25 15 12508 2000 25 25 2810 1000 250 15 7893 200 250 25 1710 1000 25 150 4986 200 25 250 1215 Série měření PLEBu 3 Série měření PLEBu 4 iter_count beam shift_count měření 1 10,50 7,154 3,181 měření 2 10,08 5,597 4,070 měření 3 10,81 6,821 4,309 měření 4 9,39 5,719 4,063 aritmetický průměr 10,195 6,322 3,905 Časové koeficienty jednotlivých parametrů Pozn: data uvedená v poslední tabulce jsme získali podílem času, který zabral běh s danou veličinou zvětšenou desetkrát, a času, který trval vztažný běh. Fakt, že koeficienty naměřené v jednotlivých měřeních nejsou v rámci každé veličiny totožné, je lehké vysvětlit, podíváme-li se na vzorec, který zjednodušeně zapisuje počet operací, které je během výpočetu potřeba provést. iter_count*( beam*coll_size + shift_count*coll_size) = = iter_count*beam*coll_size + iter_count*shift_count*coll_size Z roznásobeného tvaru je zřejmě vidět, že jestliže provedeme dvě série měření s hodnotami parametrů vztažných běhů beam = b a iter_count = i a beam = b a iter_count = 10i, bude délka běhu výpočtu ovlivněna druhým sčítancem ve vzroci a proto koeficient veličiny beam nemůže být v obou sériích totožný. Také je zřejmé, že nastavení nejvyššího koeficientu paramtru iter_count je v pořádku, neboť lineárně ovlivňuje oba sčítance. Pro naše účely, kdy se snažíme vyjádřit pro každou veličinu takový koeficient, který by vypovídal o její časové náročnosti, budeme brát jako koeficient desetinu aritmetického průměru naměřených hodnot pro každý parametr. Jednotlivé koeficienty jsou tedy koef_iter_count = 1,0195 koef_beam = 0.6322 koef_shift_count = 0.3905 t_č = iter_count * koef_iter_count + beam * koef_beam (V1) Vzorec (V1) použijeme pro určování jakéhosi teoretického času běhu volání PLEBu s daným nastavením. Parametr shift_count je ze vzorce vynechán záměrně a to proto, že má pro účinnost odhalování podobných signatur kritický význam ( jak uvidíme později ) a snížení hodnoty shift_count motivovaný snahou o rychlejší běh aplikace by se dramaticky odrazil v množství nalezených párů a náprava této ztráty účinnosti pomocí zvýšení hodnoty parametru iter_count a/nebo beam by vedla k mnohem vyšším časovým nárokům, než kdybychom nechali parametr shift_count na původní hodnotě. Na základě stodeseti měření účinnosti algoritmu PLEB provedených na české kolekci 100 000 dokumentů, v níž bylo při nastavení treshold=80 celkem 776345 párů spadajících do výsledku, s různými koeficinety iter_count, beam a shift_count můžeme sestavit žebříček volání s největším výkonem, tedy nejlepším poměrem # nalezených_párů / teoretický_čas_běhu ( viz další strana ). Z něj je vidět, že nejefektivnějšími z tohoto hlediska jsou takové konfigurace PLEBu, v nichž je hodnota iter_count nízká a parametry beam a shift_count jsou poměrně vysoké. Dále je poměrně překvapivým pozorováním, že efektivita PLEB algoritmu s roustoucím umístěním v žebříčku poměrně rychle klesá, což podporuje myšlenku, že správná konfigurace PLEBu je pro efektivitu hledání podobných signatur kritická. iter_count beam shift_count nalezených párů (%) výkon 250 50 40 84,433 2258,7 250 40 40 80,275 2195,8 250 50 30 78,290 2094,2 250 30 40 74,226 2077,2 250 40 30 73,654 2014,7 250 50 20 70,468 1885,0 250 30 30 67,128 1878,5 250 20 40 64,612 1850,9 250 40 20 65,317 1786,6 250 20 30 57,307 1641,6 Žebříček výkonu jednotlivých běhů PLEBu iter_count beam shift_count nalezených párů (%) výkon 1000 50 40 99,754 727,2 1000 40 40 99,459 729,5 1000 50 30 99,278 723,8 750 50 40 99,163 954,4 1000 30 40 98,725 728,5 1000 40 30 98,659 723,6 750 40 40 98,459 955,2 750 50 30 98,190 945,0 1000 50 20 97,513 710,9 1000 30 30 97,292 717,9 Žebříček dle množství nalezených párů V dalším žebříčku ( viz výše ) je uvedeno deset konfigurací PLEBu, které vykázaly nejvyšší schopnost nalézt velké množství signatur, které svou podobností převyšují výše zmíněnou hodnotu treshold. Překvapivou skutečností jsou velmi vysoké hodnoty uvedené ve sloupečku „nalezených párů“. Z pohledu na první sloupeček nikomu neunikne, že na celé čáře v tomto porovnání vítězí běhy s vysokou hodnotou parametru iter_count, které se k velkému výsledku propracují jednoduše hrubou silou. Výkon těchto běhů ale poukazuje na skutečnost, že je velké množství velmi podobných dvojic nalezeno mnohokrát. Zajímavou skutečností je také vztah mezi výkonem a hodnotou parametru iter_count v tomto porovnání. Běhy mající iter_count = 1000 nabývají podobného výkonu okolo 720, zatímco všechny běhy mající iter_count = 750 se mohou pochlubit výkonem nad 940. Stejně jako v předešlém žebříčku, ani zde se běhy s nízkými hodnotami beamu a shift_countu neumístily. A sem udělat nějakej zlatej střed..nějakej kompromis, zavést výkon*nalezených a vzít ty nejlepší. udlat porovnání účinnosti zvednutí parametrů na dvojnásobek – o kolik se zvýšá počet nalezených když danej parametr zvýším na dvonásobek?; Musí se shiftcount nějak přizpůsobovat součinu iters*beam? Něco jako aby součil shift*beams*iters byl konstanta?? Uvest vzorec pro precision HAMM JE OBECNE MENSI NEZ SIMM experimenty tak nejak prokazaly, ze nemá moc cenu generovat signatury delsi nez 1000 SIGLEN nic nezlepsi ani nejzhorsi!! Zminit implantaci RND veců NA PORADNYCH DATECH ( velke BOWy ) s rosotucim siglen klesa l1 l2 atp. Overit.
Podobné dokumenty
Přiznání krále pozdních příchodů
že jich mám mnohem více. A to mám 10 pozdních příchodů tolerovaných! Ano, už od nepaměti výsada pozdních příchodů byla výsada králů. Tak jsem si dovolil se jedním „jmenovat“. Není to sice hezký tit...
VíceDeník Střední východ 1995
Do Rumunska (díry Evropy, jak poznamenal Chlupi) jsme se dostali před šestou SEČ (sedmou VEČ) ráno. Tady začíná naše asi 16hodinová pouť po rumunských „cestách“. Všichni jsme plně při vědomí, jelik...
VíceÚvodní slovo
činností týče, terapie v Takiwasi se může bezpochyby pochlubit rozmanitější šíří provozovaných terapeutických technik (např. muzikoterapie, jóga, rodinné konstelace). Díky shodně aplikovanému systé...
VíceSK Rozvádzač odvodných jednotiek VS 21-150 CG 0-1 VS 180
může nezávisle na ostatních přijímat hodnotu I, II nebo III rychlosti.
Více