i “output” — 2018/6/21 — 7:43 — page 1 — #1 i i i

POLITECHNIKA WARSZAWSKA

WYDZIAŁ MATEMATYKI I NAUK INFORMACYJNYCH

Rozprawa doktorska mgr inż. Maciej Bartoszuk

System do oceny podobieństwa kodów źródłowych w językach funkcyjnych oparty na metodach uczenia maszynowego i agregacji danych

Promotor dr hab. inż. Marek Gągolewski, prof. PW

WARSZAWA 2018

i i

i i i “output” — 2018/6/21 — 7:43 — page 2 — #2 i i i

i i

i i i “output” — 2018/6/21 — 7:43 — page 3 — #3 i i i

Streszczenie

Rozprawa poświęcona jest wykrywaniu podobnych kodów źródłowych (zwanych także klo- nami) w językach funkcyjnych na przykładzie języka . Detekcja klonów ma dwa główne zastosowania: po pierwsze, służy nauczycielom i trenerom, którzy prowadzą zajęcia z pro- gramowania, wskazując prace, których samodzielne wykonanie być może należy zakwestio- nować. Po drugie, pomaga zapewniać wysoką jakość kodu w projektach informatycznych, poprzez unikanie powtórzeń. Po pierwsze, w pracy zostaje zaproponowana operacyjna definicja podobieństwa pary kodów źródłowych, która służy jako formalne sformułowanie problemu. Polega ona na wymienieniu możliwych modyfikacji kodu, a następnie stwierdzeniu, że kody A i B są do siebie podobne, jeśli A może powstać przy użyciu dobrze określonych przekształceń B. Co więcej, jako że obecnie spotykane w literaturze sposoby badania skuteczności proponowa- nych metod nie są zadowalające, zaproponowano nowe podejście, pozwalające na rzetelną ocenę ich jakości. Stworzono zbiory benchmarkowe, poprzez wylosowanie zgromadzonych uprzednio funkcji z popularnych pakietów, a następnie przekształcono losowo wybrane z nich przy użyciu wymienionych wcześniej modyfikacji. Pozwala to na kontrolę zarówno rozmiaru badanej grupy funkcji, jak i jej cech: frakcji klonów czy liczby dokonywanych przekształceń. Na potrzeby pracy ustalono 108 zestawów wartości poszczególnych para- metrów sterujących cechami zbioru, a następnie dla każdego z nich, aby wyeliminować wpływ czynnika losowego, wygenerowano po 10 instancji. Po drugie, proponowane do tej pory algorytmy działają na pewnym z góry określo- nym sposobie reprezentacji kodu, np. na tokenach. Nie dokonywano porównań, jak dobrze sprawiłby się taki sam algorytm dla innego ciągu, np. surowych znaków. W tej pracy od- dzielono algorytmy, jako sposoby porównywania pewnych ciągów lub grafów, od samej reprezentacji, a następnie zbadano skuteczność wszystkich tak uzyskanych kombinacji. W szczególności zaproponowano nowy sposób reprezentacji, skupiający się na użytych wywołaniach funkcji. Takie podejście pozwala na precyzyjne określenie, w jakim stopniu na skuteczność danej metody składa się sam algorytm, który jest być może już obecny w literaturze, a w jakim – samo przekształcenie kodu do konkretnej postaci. W pracy przetestowano cztery reprezentacje (trzy sekwencyjne i jedną grafową) oraz siedem algo- rytmów porównujących (cztery dla postaci sekwencyjnych oraz trzy dla grafowych).

i i

i i i “output” — 2018/6/21 — 7:43 — page 4 — #4 i i i

Po trzecie, dotychczasowe podejścia do porównywania kodów źródłowych oparte na grafie zależności programu (ang. Program Dependence Graph, PDG) nie są efektywne i opierają się na algorytmach, których złożoność obliczeniowa jest wykładnicza. W tej pracy zbadano po raz pierwszy w kontekście detekcji klonów skuteczność algorytmu o zło- żoności wielomianowej opartego na metodzie Weisfeilera–Lehmana służącego do znajdo- wania podobnych grafów, a także zaproponowano własną metodę o nazwie SimilaR, która jest lepiej dostosowana pod specyfikę problemu znajdowania podobnych kodów źródło- wych. Wyniki eksperymentalne potwierdzają jej lepszą skuteczność. Dodatkowo przedsta- wiono algorytm tworzenia grafu zależności programu dostosowany do specyfiki języków funkcyjnych, ilustrując jego użycie na przykładzie języka R. Zaproponowana metoda po- zwala w łatwiejszy sposób wykrywać modyfikacje stosowane przez osoby dopuszczające się plagiatu. Ponieważ różne algorytmy porównywania kodów źródłowych koncentrują się na róż- nych jego cechach, dodatkowo praca skupia się na agregacji wyników kilku takich podejść w kontekście rozwiązywania problemu klasyfikacji binarnej oraz regresji. Znane z litera- tury metody, takie jak lasy losowe czy maszyna wektorów podpierających, mają jednak zasadniczą wadę: nie pozwalają na interpretację wpływu poszczególnych metod na osta- teczny wynik, a także nie gwarantują takich własności, jak monotoniczność ze względu na poszczególne składowe czy idempotentność. Co więcej, nie pozwalają na bezpośrednie po- równanie generowanych przez nie wyników. Innymi słowy, np. wartość 0,76 zwrócona przez jedną metodę nie musi reprezentować takiego samego stopnia podobieństwa jak wartość 0,76 uzyskana przy użyciu innego algorytmu. Zaproponowano więc własną metodę agrega- cji wyników zwracanych przez algorytmy, aby podjąć pojedynczą decyzję o podobieństwie dwóch kodów źródłowych. Jest ona oparta o krzywe i płatki B-sklejane. Podejście to po- zwala zachować dobrze interpretowalną postać modelu, poprzez sprowadzenie wyników poszczególnych składowych do jednej skali. Eksperymentalnie wykazano, że lepsze wyniki są osiągane dla zestawu metod niż każdej z nich użytej pojedynczo, a także, że zapropono- wana metoda agregacji daje lepsze wyniki niż inne modele uczenia maszynowego, w tym m.in. algorytm lasów losowych. Warto ponadto zauważyć, że podejścia prezentowane w literaturze posługują się syme- tryczną postacią podobieństwa: zwracana jest jedna wartość, która opisuje, jak podobne są obie funkcje do siebie. W pracy tej uznano taki sposób za niewystarczający i zbadano podejście, w którym metoda porównująca dwie funkcje zwraca parę wartości: jak bardzo pierwsza zawiera się w drugiej (A ⊂ B) oraz jak bardzo druga zawiera się w pierwszej (A ⊃ B). Następnie zaproponowano sposoby agregacji tych dwóch wartości przy uży- ciu t-norm (minimum, produktowej i Łukasiewicza), średniej arytmetycznej lub t-konorm (dualnych do wymienionych t-norm). Dzięki odpowiedniej agregacji wyników kilku metod

4

i i

i i i “output” — 2018/6/21 — 7:43 — page 5 — #5 i i i

osiągnięto lepsze wyniki, niż w przypadku podejścia symetrycznego. Stworzone zostało także oprogramowanie, które pozwala na skorzystanie z dokonań niniejszej pracy w praktyce: publicznie dostępny pakiet w języku R umieszczony w re- pozytorium CRAN, a także serwis internetowy, dzięki któremu można porównać kody źródłowe.

Słowa kluczowe: Język R, podobieństwo kodu, graf zależności programu, uczenie ma- szynowe, agregacja danych

5

i i

i i i “output” — 2018/6/21 — 7:43 — page 6 — #6 i i i

Abstract

Title: A source code similarity assessment system for functional programming languages based on machine learning and data aggregation methods

This thesis deals with the problem of detecting similar source codes (also known as clones) in functional languages, especially in R. Code clones’ detection is useful in programming tutoring, where teachers and trainers wish to identify suspiciously similar works, as well as in assuring source code quality, so that repeated code is avoided. This work introduces an operational definition of code similarity, which is used as a for- mal formulation of the problem: given a list of possible source code modifications, we say that two code fragments A and B are similar if A can be derived from B by means of these transformations. Based on the observation that currently employed assessment methods of clone detection algorithms are insufficient, a new approach to evaluate their perfor- mance and accuracy is proposed. Namely, the benchmark sets were created by randomly choosing functions, extracted from popular packages and transforming them by means of the aforementioned modifications. This allows to constrain the size of similar functions’ clusters, as well as the fraction of clones and number of performed transformations. 10 random sets of functions generated according to 108 different parameter scenarios were considered. One may note that the algorithms proposed so far use a fixed source code represen- tation, e.g., tokens. There were no detailed studies on how accurate an algorithm would be if it was run against a different sequence, e.g., of raw characters. In this work, the algorithms, understood as methods to compare sequences or graphs, are separated from particular representations of source codes. By means of such an approach, the effectiveness of each algorithm could have been examined with respect to each possible code represen- tation. In particular, a new way of code representation was proposed, namely the one that focuses on R function calls. In this study, four representations (three sequential and one graph-based) and seven comparison algorithms (four for sequential and three for graph data) were examined. The current state-of-the-art code clone detection approaches based on Program De- pendence Graphs (PDG) rely on some exponential-time subroutines. In this work, the performance of a polynomial-time algorithm based on the Weisfeier–Lehman method for

i i

i i i “output” — 2018/6/21 — 7:43 — page 7 — #7 i i i

finding similar graphs is examined. What is more, its novel modification – the SimilaR algorithm has been empirically shown to outperform all the other inspected methods. Since all the algorithms for comparing source codes focus on different code similarity aspects, the current work also ephasizes the need for a proper aggregation of the results generated by different approaches, especially in the context of solving the problem of bi- nary classification and regression. However, one may note that many supervised learning algorithms like random forests or support vector machines, have the following disadvan- tage: one cannot easily quantify the influence of each particular method on the final result. Moreover, such models do not obey desirable properties such as the monotonicity or idem- potence. What is more, they do not allow to compare the results of individual methods directly. In other words, e.g., a value of 0.76 returned by one of the algorithms does not necessarily represent the same degree of similarity as a value of 0.76 obtained using an- other method. Thus, a new similarity aggregation method (based on B-splines curves and surfaces) was proposed. The new models not only posses an intuitive interpretation, but also were experimentally shown to yield better results than, among others, the random forests algorithm. It is also worth noting that the approaches presented in the literature rely on a sym- metrical form of similarity: they return a single value describing how similar both func- tions are to each other. In this work, non symmetric measures were proposed, quantifying the degree to which one function is contained within a second one (A ⊂ B) and vice versa (A ⊃ B). Due to the appropriate aggregation of results (by means of t-norms (like the minimum, product and Łukasiewicz), the arithmetic mean or t-conorms (dual to the above-mentioned t-norms), several methods have achieved better performance than in the case of a symmetrical approach. The thesis is accompanied by a publicly available open source published in the CRAN repository, as well as web service, both implementing the proposed algorithm.

Keywords: R programming language, source code similarity, Program Dependence Graph, machine learning, data aggregation

7

i i

i i i “output” — 2018/6/21 — 7:43 — page 8 — #8 i i i

8

i i

i i i “output” — 2018/6/21 — 7:43 — page 9 — #9 i i i

Podziękowania

Podziękowania w pierwszej kolejności należą się oczywiście mojemu promotorowi, dr. hab. inż. Markowi Gągolewskiemu. Gdyby nie jego wsparcie naukowe i pomoc w wyborze obie- cujących kierunków badań, a także duże wsparcie w przygotowywaniu i redakcji artykułów naukowych, ale przede wszystkim nieustanne motywowanie do dalszej pracy, ta rozprawa nigdy by nie powstała. Dziękuję także prof. Jackowi Mańdziukowi, kierownikowi mojego zakładu Sztucznej Inteligencji i Metod Obliczeniowych wydziału Matematyki i Nauk Informacyjnych Poli- techniki Warszawskiej, za stworzenie mi dogodnych warunków do prowadzenia badań oraz wypełniania obowiązku dydaktycznego. Dziękuję także za zainteresowanie mnie algoryt- mami sztucznej inteligencji, w szczególności Monte Carlo Tree Search oraz grami. Nie mógłbym również nie podziękować uczestnikom Seminarium Methods for Analysis of Data – Algorithms and Modeling1 (MADAM), w szczególności Annie Cenie, na któ- rej nigdy się nie zawiodłem i na którą zawsze mogłem liczyć, a także Janowi Laskowi, Barbarze Żogale-Siudem i Grzegorzowi Siudemowi, za owocne dyskusje. Dziękuję uczestnikom Seminarium Inteligencji Obliczeniowej2 na Wydziale Matema- tyki i Nauk Informacyjnych Politechniki Warszawskiej, zwłaszcza Stanisławowi Kaźmier- czakowi i Michałowi Okulewiczowi, za pomocne wskazówki oraz dużo wsparcia meryto- rycznego i pozamerytorycznego, a także Janowi Karwowskiemu, który pokazał mi fascy- nujący świat języków funkcyjnych. Dziękuję koleżankom i kolegom ze studiów doktoranckich Technologie informacyjne: badania i ich interdyscyplinarne zastosowania, za mile spędzone chwile oraz wiele nauko- wych ciekawostek, jakich mogłem podczas ich trwania usłyszeć, w szczególności Julianowi Zubkowi, który także przyczynił się do mojego zainteresowania mniej typowymi językami programowania. Dziękuję wszystkim wykładowcom, z którymi miałem przyjemność mieć zajęcia w ciągu całego toku moich studiów. Nade wszystko chciałbym podziękować dr. inż. Pawłowi Ko- towskiemu i dr. inż. Janowi Bródce, za wpływ jaki mieli na moje zainteresowanie algo- rytmami w ogólności oraz algorytmami grafowymi w szczególności. Dziękuję prof. Jac- kowi Koronackiemu i dr. Pawłowi Teisseyrowi za przystępne wprowadzenie do statystyki,

1https://madam-research-group.github.io/seminar/ 2http://www.mini.pw.edu.pl/~mandziuk/sem_IO.htm

i i

i i i “output” — 2018/6/21 — 7:43 — page 10 — #10 i i i

a także dr. hab. Szymonowi Jaroszewiczowi za wprowadzenie w świat uczenia maszy- nowego. Na koniec dziękuję dr Joannie Porter-Sobieraj, która choć uczyła mnie mate- riału niezwiązanego bezpośrednio z tematyką tej pracy, okazała się olbrzymim wsparciem na początku mojej kariery naukowej, gdy rozpoczynałem doktorat czy pisałem jedną z pierwszych swoich prac. Przeprowadzenie badań przedstawionych w tej rozprawie nie byłoby możliwe w tak du- żym zakresie, gdyby nie wsparcie finansowe w postaci stypendium doktoranckiego w ra- mach projektu POKL.04.01.01–00–051/10–00 Technologie informacyjne: badania i ich interdyscyplinarne zastosowania oraz grantu 2014/13/D/HS4/01700 (Construction and analysis of methods of information resources producers’ quality management) Narodowego Centrum Nauki. Praca została wykonana z wykorzystaniem Infrastruktury PLGrid3. Z niezrozumiałych mi powodów w tego typu pracach podziękowania dla najważniej- szych osób umieszczane są na samym końcu. Dostosowując się do konwencji, dopiero w tym miejscu dziękuję swoim rodzicom, którzy byli dla mnie olbrzymim wsparciem przez całe życie, wychowali mnie oraz byli bardzo cierpliwi i wyrozumiali przez całe 5 lat trwania tych badań. Następnie dziękuję całej reszcie swojej rodziny, a także wszystkim tym, z któ- rymi utrzymywałem kontakt przez cały ten czas, a wykazali się olbrzymią cierpliwością i zapewnili mi ogromne wsparcie, którego potrzebowałem: przyjaciołom ze wszystkich etapów mojej edukacji, wolontariuszom ATD oraz towarzystwu zebranemu wokół Teatru Powszechnego. Osobne podziękowania należą się Izabeli Wróbel-Komańskiej.

3http://www.plgrid.pl/

10

i i

i i i “output” — 2018/6/21 — 7:43 — page 11 — #11 i i i

Spis treści

1 Wprowadzenie 15 1.1. Przedmiot i zakres rozprawy ...... 15 1.2. Hipotezy badawcze ...... 17 1.3. Oryginalne wyniki rozprawy ...... 18 1.4. Opublikowane częściowe wyniki ...... 18 1.5. Układ rozprawy ...... 19

2 Podobieństwo par funkcji i generowanie danych testowych 23 2.1. Okoliczności plagiatu ...... 23 2.2. Podobieństwo kodu ...... 26 2.3. Zbiory benchmarkowe ...... 28 2.4. R jako język funkcyjny ...... 30 2.5. Typy wierzchołków w drzewie składniowym ...... 32 2.6. Ataki na kod ...... 35 2.6.1. Zmiana operatora przypisania ...... 36 2.6.2. Zmiana nazw zmiennych ...... 37 2.6.3. Zmiana nazw funkcji ...... 38 2.6.4. Rozwijanie zagnieżdzonych wywołań funkcji ...... 39 2.6.5. Zamiana miejscami niezależnych wierszy kodu ...... 41 2.6.6. Zamiana jednego typu pętli na drugi ...... 42 2.6.7. Przemienność argumentów w operatorach binarnych ...... 44 2.6.8. Wykorzystanie tautologii do modyfikacji warunków logicznych . . . 45 2.6.9. Dodanie wierszy kodu ...... 46 2.6.10. Atak przy użyciu operatora potokowego %>% ...... 47 2.6.11. Odwrotny atak przy użyciu operatora potokowego %>% ...... 49 2.6.12. Podstawienie kodu wywoływanej funkcji ...... 49 2.7. Generator zbiorów benchmarkowych ...... 51

i i

i i i “output” — 2018/6/21 — 7:43 — page 12 — #12 i i i

2.8. Zbiory benchmarkowe używane w dalszej części pracy ...... 54

3 Metody porównywania par funkcji 59 3.1. Sposoby reprezentacji funkcji w języku R ...... 61 3.1.1. Sekwencyjne sposoby reprezentacji ...... 63 3.1.2. Graf zależności programu ...... 65 3.2. Symetryczne a niesymetryczne miary podobieństwa ...... 69 3.3. Algorytmy porównujące ...... 72 3.3.1. Odległości edycyjne ...... 72 3.3.2. Zachłanne kafelkowanie ciągów ...... 75 3.3.3. Odległość q-gramowa ...... 76 3.3.4. Największy wspólny podgraf ...... 76 3.3.5. Algorytm oparty na teście Weisfeilera–Lehmana ...... 79 3.3.6. Algorytm SimilaR ...... 82 3.4. Miary jakości używane przy problemach niezbalansowanych ...... 85 3.5. Wyniki eksperymentalne ...... 88 3.5.1. Odległość Levenshteina ...... 89 3.5.2. Algorytm Smitha–Watermana ...... 90 3.5.3. Zachłanne kafelkowanie ciągów ...... 96 3.5.4. Odległość q-gramowa ...... 97 3.5.5. Algorytmy grafowe ...... 98 3.5.6. Podsumowanie wyników ...... 99

4 Wykrywanie podobnych par funkcji 115 4.1. Proponowana metoda agregacji modeli ...... 116 4.1.1. Funkcja agregująca ...... 116 4.1.2. Krzywe B-sklejane ...... 117 4.1.3. Przypadek jednowymiarowy ...... 118 4.1.4. Przypadek dwuwymiarowy ...... 119 4.2. Wyniki eksperymentalne ...... 121 4.2.1. Scenariusze testowe ...... 121 4.2.2. Testowane modele i miary błędu ...... 126 4.2.3. Wyniki ...... 127 4.3. Analiza statystyczna ...... 132 4.3.1. Analiza zależności między zmiennymi ...... 132 4.3.2. Redukcja wymiaru przestrzeni ...... 135 4.3.3. Wpływ poszczególnych metod na ostateczną decyzję ...... 152

12

i i

i i i “output” — 2018/6/21 — 7:43 — page 13 — #13 i i i

5 Raportowanie wyników użytkownikowi końcowemu 157 5.1. Pakiet SimilaR ...... 157 5.2. Interfejs webowy ...... 159 5.3. Analiza zbioru rzeczywistego ...... 160

6 Podsumowanie 171 6.1. Weryfikacja celów i hipotez badawczych ...... 171 6.2. Kierunki dalszych badań ...... 172

Bibliografia 175

13

i i

i i i “output” — 2018/6/21 — 7:43 — page 14 — #14 i i i

14

i i

i i i “output” — 2018/6/21 — 7:43 — page 15 — #15 i i i

Wprowadzenie 1

1.1 Przedmiot i zakres rozprawy

Większość nauczycieli akademickich oraz trenerów staje przed zadaniem wykrywania po- dobieństwa w kodach źródłowych podczas prowadzenia zajęć z programowania. Co więcej, zadanie to pojawia się także podczas wytwarzania oprogramowania, gdy chcemy zapewnić wysoką jakość kodu bez zbędnych powtórzeń. W nauczycielskiej praktyce bardzo często można się spotkać z przypadkiem, gdy otrzy- mujemy dwie podobne prace, które zostały stworzone niezależnie od siebie. Powodów ta- kiego zjawiska może być wiele. Pierwszym jest opis zadania, który może być tendencyjny, przykładowo nazwy zmiennych mogą być podane w treści, czasami nawet pętla główna algorytmu albo podejście do problemu jest narzucone, np. przez podany pseudokod. In- nym prozaicznym powodem jest to, że dwóch studentów uczęszcza na ten sam przedmiot, gdzie ten sam nauczyciel tłumaczy to samo lub podobne zagadnienie. Dalej, przyczyną może być budowanie swojego rozwiązania w oparciu o ten sam kod źródłowy dostarczony przez prowadzącego. Ostatnim dość popularnym powodem, dla którego dwie prace mogą być podobne, choć były tworzone niezależnie, jest kiedy rozwiązanie podobnego zadania można znaleźć w Internecie. Wtedy wielu studentów pozyskuje ten sam kod na własną rękę, np. z serwisu StackOverflow1, by potem dostosować go do wymagań konkretnego zadania. Poza tym nie da się wykluczyć, że studenci rozmawiają między sobą na temat pracy domowej, uzgadniają podobne podejście do problemu, a następnie implementują rozwiązania samodzielnie. Jest to zjawisko możliwe, co więcej wcale nie tak rzadkie, a na- uczyciele nie powinni za nie karać tak samo jak za zwykły plagiat. Tak więc to zawsze po stronie prowadzącego leży odpowiedzialność, jak opierając się na kodzie źródłowym, ale także na relacjach międzyludzkich w grupie, traktować podej- rzane pary kodów źródłowych. Automatyczny system antyplagiatowy może jedynie takie pary wskazać.

1https://stackoverflow.com/

i i

i i i “output” — 2018/6/21 — 7:43 — page 16 — #16 i i i

Innym powodem dla którego może zachodzić potrzeba wykrywania podobnych frag- mentów kodu, jest chęć utrzymania kodu wysokiej jakości w projekcie informatycznym. Wytwarzanie i testowanie oprogramowania staje się coraz częściej obszarem zaintereso- wań naukowych [85–87], a w literaturze znajdują się propozycje zapewniania jego wyso- kiego poziomu w sposób automatyczny [56, 64, 69]. Jednym z takich sposobów może być zamknięcie powtarzających się fragmentów kodu w łatwych do ponownego użycia funk- cjach [115]. W takim przypadku przywiązujemy większą wagę do znalezienia podobnych fragmentów kodu niż do decydowania, czy dane fragmenty zostały stworzone przez jedną lub więcej osób. Te rozważania tłumaczą, czemu zautomatyzowany system powinien być traktowany jedynie jako narzędzie pomocnicze, które po prostu wykrywa bliźniaczy kod, ale nie decy- duje, czy dwóch studentów ściągało czy nie. Ale nawet kiedy system jest używany jedynie w tym celu, nie jest to koniec problemów z dokładną definicją podobieństwa. Problem, czy dwa fragmenty kodu są podobne czy nie, jest równie trudny jak podjęcie decyzji, czy dwoje ludzi ściągało czy nie. Najprostszym przypadkiem jest, gdy dwa kody są dokład- nie takie same. Oczywiście wtedy żadne wyspecjalizowanie narzędzie nie jest potrzebne, ponieważ wystarczy tu zwykły program do wyszukiwania tekstowego, np. diff. Kolejną prostą zmianą w kodzie źródłowym jest zmiana nazw zmiennych. Ale taką modyfikację także łatwo jest wykryć i dobrze znane są algorytmy dla tego problemu [107, 116], a które będą szerzej opisane w rozdziale 3. Możemy rozciągnąć nasz proces myślowy do kolejnych, coraz bardziej wymyślnych metod używanych przez osoby dopuszczające się plagiatu kodu źródłowego. Ostatecznie zakończymy z konkluzją, że każda para kodów źródłowych, która przynosi ten sam efekt (np. oblicza dokładnie to samo), jest podobna, nawet jeśli sam kod jest kompletnie odmienny. Ale w takim przypadku wszystkie poprawne rozwiązania za- dania są podobne i powinny zostać zwrócone przez system. Oczywiście nie to jest celem takiego narzędzia. Tak więc idealny system powinien zwrócić takie pary, które nie tylko wykonują tę samą pracę, ale także ich kod źródłowy jest do siebie zbliżony. Już te bardzo wstępne rozważania pokazują, że problem jest złożony, a dokładne defi- nicje jego poszczególnych składowych są bardzo potrzebne. Warto jeszcze raz podkreślić: czym innym jest plagiat, a czym innym podobieństwo kodu. O ile definicje tego drugiego są w literaturze spotykane, choć nierzadko dość pobieżne, o tyle naprawdę rzadko spotyka się prace, które dokładniej zgłębiają tematykę plagiatu. Przy próbie utworzenia takich de- finicji napotykamy na wiele trudności, o których piszemy w rozdz. 2. Dochodzimy tam do operacyjnej definicji podobieństwa kodu, z której korzystamy w reszcie pracy. Przegląd literatury wykazał także duży ubytek, jeśli chodzi o sposób badania metod wykrywania podobieństwa w kodzie, którym jest brak odpowiednich zbiorów testowych wraz z pełną informacją o zawartych w nim parach podobnych fragmentów kodu, które

16

i i

i i i “output” — 2018/6/21 — 7:43 — page 17 — #17 i i i

zapewniłyby rzetelne wyniki. Zazwyczaj autorzy testują swoje podejście na otwartych kodach źródłowych, oceniając jedynie, czy zwrócone wyniki wydają się zadowalać ich ocenę „na oko”, bez dostatecznej informacji o tym, czy poza nimi były w tym kodzie inne podobieństwa, których ich metoda nie zdołała znaleźć. Jednym z celów tej pracy jest zaproponowanie bardziej rzetelnego, naukowego i usystematyzowanego podejścia. Większość zaproponowanych w literaturze podejść bazuje na próbie porównywania dwóch ciągów tokenów wygenerowanych z zadanych kodów źródłowych. Uważamy ta- kie podejście za mało efektywne i niepozwalające na dostatecznie skuteczną ocenę po- dobieństwa w przypadku bardziej skomplikowanych modyfikacji. Jednocześnie istniejące prace, oparte na bardziej złożonych strukturach danych, jak graf zależności programu (ang. Program Dependence Graph, PDG), proponują metody o złożoności wykładniczej, które nie nadają się do praktycznych zastosowań. Głównym osiągnięciem tej pracy jest zaproponowanie algorytmu SimilaR, używającego tejże struktury danych, a którego zło- żoność obliczeniowa jest wielomianowa. Inni autorzy wychodzą z założenia, że podobieństwo należy opisywać jedną liczbą, określającą ogólne podobieństwo obu funkcji. W naszej pracy próbujemy innego podejścia: obliczamy dwie liczby, reprezentujące jak bardzo pierwsza funkcja zawiera się w drugiej, oraz jak bardzo druga zawiera się w pierwszej. Następnie staramy się podjąć decyzję o podobieństwie, używając tej pary liczb lub odpowiednio ją agregując. Dotychczasowe podejścia są oparte jedynie na pojedynczym algorytmie. Celem tej pracy było zbadanie, czy użycie większej liczby metod, które koncentrują się na różnych aspektach kodu, pozwoli na zwiększenie skuteczności naszego podejścia.

1.2 Hipotezy badawcze

Podstawowym celem rozprawy jest zweryfikowanie hipotez badawczych dotyczących sku- teczności algorytmów dotyczących porównywania kodów źródłowych, a także proponowa- nego podejścia agregacji ich wyników. Badana jest zatem prawdziwość poniższych stwier- dzeń: 1) Możliwe jest stworzenie jasno zdefiniowanego podejścia do testowania algorytmów porównywania podobieństwa kodów źródłowych, które pozwoli dokładnie określić ich skuteczność względem różnych cech danych testowych. 2) Możliwe jest rozdzielenie sposobu reprezentacji badanych kodów źródłowych od al- gorytmu porównującego, por. tab. 1.1. 3) Możliwe jest stworzenie algorytmu porównującego kody źródłowe, opartego na gra- fie zależności programu, który będzie skuteczniejszy niż dotychczas zaproponowane metody, jednocześnie nie wykonując się dłużej niż inne algorytmy.

17

i i

i i i “output” — 2018/6/21 — 7:43 — page 18 — #18 i i i

4) Możliwe jest skonstruowanie podejścia agregacyjnego łączącego różne metody po- równywania kodów źródłowych, uzyskując metodę skuteczniejszą od każdego z tych podejść stosowanego samodzielnie. 5) Podejście polegające na badaniu niesymetrycznego podobieństwa (por. podrozdz. 3.2), a następnie ewentualna agregacja dwóch wartości w jedną, może dać lepsze wyniki, niż powszechnie stosowane symetryczne podejście. 6) Odpowiednia fuzja wyników z poszczególnych algorytmów pozwoli na zachowanie dobrze interpretowalnych własności modelu.

1.3 Oryginalne wyniki rozprawy

W ramach weryfikacji powyższych hipotez podczas prowadzonych badań określono serię celów cząstkowych, stanowiących jednocześnie autorskie rezultaty prac. Obejmowały one: 1) Stworzenie algorytmu, który na podstawie danych funkcji bazowych tworzy zbiory testowe o zadanych parametrach, gdzie klony funkcji powstają przy użyciu jasno zde- finiowane przekształcenia. Zbiory te pozwalają w rzetelny sposób ocenić skuteczność poszczególnych algorytmów porównywania kodów źródłowych. 2) Rozdzielenie sposobu reprezentacji funkcji od algorytmów porównujących. 3) Zaproponowanie nowego sposobu reprezentacji funkcji, dostosowanego do specyfiki języka z centralnym repozytorium pakietów: ciąg wywoływanych funkcji. 4) Stworzenie algorytmu SimilaR, który przy użyciu grafu zależności programu daje lepsze wyniki, niż dotychczas zaproponowane metody w sensie miary F, jednocześnie wykonując się w porównywalnym, a często nawet krótszym czasie, niż one. 5) Zaproponowanie metody agregacyjnej, opartej o średnią ważoną oraz krzywe i płatki B-sklejane, która pozwala na zachowanie dobrze interpretowalnych własności mo- delu, a także pozwala na uzyskanie lepszych wyników, niż inne modele. 6) Zaproponowanie podejścia niesymetrycznego, w którym każda metoda porównywa- nia zwraca dwie wartości. Następnie są one agregowane przy użyciu t-norm, średniej lub t-konorm, aby uzyskać lepsze wyniki, niż przy użyciu jednej, symetrycznej war- tości podobieństwa.

1.4 Opublikowane częściowe wyniki

Wybrane wyniki badań omawianych w rozprawie zostały przedstawione w następujących pracach: 1) Bartoszuk M., Gagolewski M., A fuzzy R code similarity detection algorithm, W: Laurent A. i in. (red.), Information Processing and Management of Uncertainty in

18

i i

i i i “output” — 2018/6/21 — 7:43 — page 19 — #19 i i i

Knowledge-Based Systems, Part III (CCIS 444), Springer-Verlag, Heidelberg, 2014, s. 21–30. doi:10.1007/978-3-319-08852-5_3 [10] – jest to pierwszy artykuł na ten temat, który wprowadza trzy metody porównywania pary funkcji, a następnie używa znanych modeli, aby dokonać predykcji. 2) Bartoszuk M., Gagolewski M., Detecting similarity of R functions via a fusion of multiple heuristic methods, W: Alonso J.M., Bustince H., Reformat M. (red.), Proc. IFSA/EUSFLAT 2015, Atlantis Press, 2015, s. 419–426. doi:10.2991/ifsa-eusflat- 15.2015.61 [11] – w tym artykule po raz pierwszy wprowadziliśmy graf zależności programu, jednak algorytm używany do porównywania grafów był bardzo niewy- dajny. 3) Bartoszuk M., Beliakov G., Gagolewski M., James S., Fitting aggregation functions to data: Part I – Linearization and regularization, W: Carvalho J.P. i in. (red.), Information Processing and Management of Uncertainty in Knowledge-Based Sys- tems, Part II (Communications in Computer and Information Science 611), Sprin- ger, 2016, s. 767–779. doi:10.1007/978-3-319-40581-0_62 [8] – pierwszy z dwóch artykułów omawiających autorskie metody dopasowywania funkcji agregujących do danych w ramach problemu uczenia nadzorowanego. Część pierwsza koncentruje się na linearyzacji i regularyzacji. 4) Bartoszuk M., Beliakov G., Gagolewski M., James S., Fitting aggregation functions to data: Part II – Idempotentization, W: Carvalho J.P. i in. (red.), Information Processing and Management of Uncertainty in Knowledge-Based Systems, Part II (Communications in Computer and Information Science 611), Springer, 2016, s. 780– 789. doi:10.1007/978-3-319-40581-0_63 [9] – część druga wprowadza własną metodę agregacji wyników poszczególnych algorytmów opartą o krzywe B-sklejane. 5) Bartoszuk M., Gagolewski M., Binary aggregation functions in software plagiarism detection, W: Proc. FUZZ–IEEE’17, IEEE, 2017. doi:10.1109/FUZZ-IEEE.2017- .8015582 [12] – rozszerzenie własnej metody agregacji wyników o przypadek dwu- wymiarowy, oparty na płatkach B-sklejanych.

1.5 Układ rozprawy

Rozprawa podzielona jest na sześć rozdziałów. Poniżej przedstawiony jest szczegółowy układ dalszej części rozprawy. Rozdział 2 na początku określa, jakie problemy mogą być rozwiązywane przy użyciu opisywanego systemu, a jakie pozostają poza jego zasięgiem. Poza tym wskazuje na oko- liczności, jakie zazwyczaj towarzyszą popełnianiu plagiatu. Następnie wskazuje na brak istnienia w literaturze dobrych zbiorów benchmarkowych, dzięki którym można by było

19

i i

i i i “output” — 2018/6/21 — 7:43 — page 20 — #20 i i i

porównywać różne podejścia i algorytmy, by potem zdefiniować możliwe modyfikacje kodu i sposób tworzenia takich danych testowych. Rozdział 3 najpierw wprowadza uogólniony model porównywania par funkcji, w której osobno traktuje się sposoby reprezentacji funkcji w języku R, a osobno same algorytmy porównujące np. ciągi czy grafy. Następnie wprowadza możliwe sposoby reprezentacji, w tym autorską metodę koncentrowania się na wywołaniach funkcji czy sposób tworzenia grafu zależności programu dostosowany do specyfiki tego języka, a także algorytmy po- równujące, w tym autorski algorytm SimilaR. Poza tym wprowadzone zostaje podejście niesymetryczne do mierzenia podobieństwa kodu. Rozdział 4 koncentruje się na podejmowaniu ostatecznej decyzji o podobieństwie dwóch porównywanych funkcji, zarówno w sposób binarny, jak i przez przydzielenie war- tości z zakresu [0, 1]. Zostaje zaproponowana autorska metoda agregacji, która ma ciekawe i pożądane własności. Dalsza część rozdziału analizuje zależności pomiędzy poszczegól- nymi algorytmami i stara się znaleźć odpowiedź na pytanie, na ile badają one niezależne własności kodu, a na ile są one skorelowane. Rozdział 5 pokazuje zastosowania rezultatów uzyskanych w poprzednich rozdziałach: opisujemy ogólnodostępny pakiet w języku R implementujący algorytm SimilaR, a także omawiamy serwis internetowy, który pozwala na porównanie kodów źródłowych osobom niezaznajomionym ze środowiskiem R. Na koniec pokazane jest działanie systemu na przy- kładzie prawdziwego zbioru danych: dwóch publicznie dostępnych pakietów języka R. Rozdział 6 zawiera podsumowanie oraz prezentuje potencjalne dalsze kierunki badań w tym obszarze.

20

i i

i i i “output” — 2018/6/21 — 7:43 — page 21 — #21 i i i . została wprowadzona w tej pracy NICAD [112] nowy JPlag [107] – -gramowa MOSS [116] nowy MOSS [116] – q Zestawienie naszego podejścia z metodami proponowanymi w literaturze. „–” oznacza, że algorytm nie ma zastosowania dla danego reprezentacjaalgorytm odległość Levenshteinaalgorytm Smitha–Watermannanajdłuższy wspólnychłanne podciąg, kafelkowanie nie ciągów stosowano za- odległość Simianalgorytm [1] McGregora, VF2 litery nowyalgorytm W–Lalgorytm SimilaR wywołania funkcji nowy nie stosowano tokeny – – [79] PDG – – – – – – – GPLAG [83] – – nowy nowy sposobu reprezentacji, „nie stosowano” – algorytm nie był używany w kontekście wykrywania klonów, a „nowy” – algorytm lub postać funkcji Tabela 1.1.

21

i i

i i i “output” — 2018/6/21 — 7:43 — page 22 — #22 i i i

22

i i

i i i “output” — 2018/6/21 — 7:43 — page 23 — #23 i i i

Podobieństwo par funkcji i generowanie danych testowych 2

Zanim przejdziemy do opisu poszczególnych komponentów budowanego systemu, bardzo ważnym jest, aby najpierw zastanowić się, jaki jest cel systemu antyplagiatowego w ogól- ności, jakie problemy jesteśmy w stanie rozwiązać przy jego użyciu, a jakie pozostają poza naszym zasięgiem. Najpierw dokonamy dość ogólnych rozważań wprowadzających w tę tematykę, by później dokonać podziału problemu na mniejsze podproblemy i dokonać przeglądu literatury.

2.1 Okoliczności plagiatu

Najpierw zajmijmy się definicją plagiatu. Ta jest dużo rzadziej poruszana w kontek- ście zadań programistycznych, jednak możemy spotkać się z rozważaniami na ten temat m.in. w pracy [33]. Bardzo obszernym fragmentem artykułu jest przeanalizowanie na- tury plagiatu oraz sytuacji mu towarzyszących. Zacytowano ciekawe badania dotyczące tej tematyki, jak i zaprezentowano wyniki ankiety przeprowadzonej przez autorów na 59 pracownikach dydaktycznych wyższych uczelni w Anglii i Szkocji. Pierwszym wątkiem są powody, dla których studenci ściągają. Autor powołuje się na pracę [21], która wymie- nia następujące czynniki (dotyczą one nie tylko zadań programistycznych): — możliwości – fakt, że zasoby są łatwo dostępne dla studentów przez Internet, w tym także istnienie stron, które piszą prace dla studentów za pieniądze; — powody osobiste – chęć uzyskania wyższych ocen, strach przed porażką, naciski ze strony rodziny, pragnienie uzyskania perspektyw zawodowych w przyszłości, a także współzawodnictwo wśród studentów; — sytuacja osobista – studenci, którzy muszą pracować, aby finansować studia mają mniej czasu na studiowanie; — brak integracji ze środowiskiem akademickim – studenci, którzy nie umieją się wpa- sować w życie akademickie i czują się z tego powodu wyalienowani, także obarczeni są większą szansą na ściąganie;

i i

i i i “output” — 2018/6/21 — 7:43 — page 24 — #24 i i i

— inne czynniki – np. poprzednie doświadczenie ze ściąganiem przed udaniem się na uczelnię wyższą, narodowość (istnieje większe prawdopodobieństwo, że studenci zagraniczni, którzy mają problemy z językiem, w którym ma być napisana praca, dopuszczą się plagiatu, niż studenci, dla których jest to język ojczysty).

Wspomniana wcześniej ankieta została wysłana do 120 pracowników, którzy choć raz prowadzili zajęcia, w których zadawane były prace programistyczne studentom. Otrzy- mano 59 odpowiedzi, gdzie 43 uczonych podało nazwę instytucji, w której dany wykła- dowca był zatrudniony. Ci pracownicy dydaktyczni byli zatrudnieni w 37 jednostkach w 34 różnych instytucjach. Były to 31 angielskie uniwersytety oraz 3 szkockie uczelnie wyższe. Sama ankieta opisywała różne sytuacje, w wyniku których studenci pozyskali, użyli i (być może) zacytowali materiał pozyskany od strony trzeciej. Respondenci mieli w każ- dym przypadku odpowiedzieć, czy dane działanie jest według nich plagiatem, innym prze- stępstwem akademickim, nie jest nim wcale, czy też nie są w stanie udzielić odpowiedzi na zadane pytanie. Ankieta obejmowała siedem różnych obszarów tematycznych:

— zakres plagiatowanego materiału – czy plagiat w zadaniu programistycznym może obejmować jedynie kod źródłowy programu, czy też komentarze w kodzie, projekt kodu, dokumentację, interfejs użytkownika, a także dane wejściowe, np. do testowa- nia programu; — kopiowanie, adaptowanie, przepisywanie kodu z jednego języka programowania do drugiego, używanie oprogramowania do automatycznego generowania kodu – czy plagiatem jest jedynie przesłanie niezmienionego kodu źródłowego osoby trzeciej bez żadnego cytowania, czy też adaptacja czyjegoś kodu do własnych potrzeb, prze- pisanie go do innego języka programowania, a także wygenerowanie kodu przez uży- cie odpowiedniego oprogramowania (wszystko to bez odpowiednich cytowań); — metody używane przez studentów do pozyskania materiału i zaprezentowania go jako ich własna praca – tutaj rozważane były scenariusze, w których student płaci innej osobie (z tej samej grupy lub też nie), aby wykonała zadanie za niego, pozyskuje bez wiedzy i zgody kod innego studenta (edytując go lub nie), a także podobne działanie, ale za zgodą innego studenta; — prace grupowe, autoplagiat, zmowa – tutaj badane były przypadki, gdy student używa fragmentów kodu, których użył w innym projekcie podlegającym ocenie. Zmowa (ang. collusion) jest wtedy, gdy dwóch studentów pracuje razem nad za- daniem, które powinni rozwiązywać indywidualnie, a w przypadku prac grupowych pytano respondentów, czy plagiatem jest, gdy studenci z dwóch różnych grup wy- mieniają się kodem (za wiedzą lub też nie pozostałych członków grup); — intencjonalne i nieintencjonalne dopuszczanie się plagiatu w ocenianych i nieocenia- nych zadaniach – tutaj analizie podlegało pytanie, czy to, że dane zadanie nie jest

24

i i

i i i “output” — 2018/6/21 — 7:43 — page 25 — #25 i i i

oceniane wpływa na fakt, że praca jest plagiatem czy też nie. Poza tym badane było, co respondenci sądzą o przypadku, gdy student niecelowo nie zacytował źró- dła, z którego korzystał; — plagiat przez podawanie fałszywych lub niepoprawnych odnośników – rozważane były scenariusze, w których student nie podał żadnego cytowania, podał referencje, które nie istnieją, albo które istnieją, ale nie pasują do kodu, który opisują, a także przypadek, gdy modyfikowane są wyniki programu tak, aby wyglądało, że działa; — rozważania na temat częstości występowania plagiatu i studenckiej współpracy – w tym obszarze poddawane analizie było, co badani wykładowcy uważają za do- zwoloną i niedozwoloną współpracę pomiędzy studentami. Czy wymiana pomysłów jest już plagiatem, czy dopiero skopiowanie konkretnego kawałka kodu? Poza tym zbierane były uwagi na temat tego, w jakich przypadkach częściej dochodzi do pla- giatowania pracy.

Opisanie szczegółowych wyników tej ankiety wykracza poza ramy niniejszej pracy, można je znaleźć w [33]. Jednak już po streszczeniu samych obszarów zainteresowania widać, że tematyka plagiatów okazuje się być znacznie bardziej rozległa, niż może się wydawać na pierwszy rzut oka. O ile zdecydowana większość wymienionych wyżej działań była uznawana przez re- spondentów za rodzaj przestępstwa akademickiego, to niekoniecznie był to plagiat. W szcze- gólności chodzi tu o podawanie fałszywych lub nieistniejących odnośników, fabrykowanie wyników programu, ale, co ciekawe, także scenariusz, gdy jeden student pozwala drugiemu na skopiowanie swojej pracy. W tym ostatnim przypadku głównie chodzi o nazewnictwo: około połowa respondentów uznawała takie działanie za plagiat, podczas gdy druga część nazywała takie zjawisko zmową. Okazuje się, że w niektórych jednostkach akademickich te dwa działania są rozróżniane i mają różne nazwy. Ciekawym zagadnieniem jest także fakt, jak oceniać plagiaty w przypadku, gdy prace nie podlegają ocenie lub mają niewielki wpływ na ocenę, a także, gdy student niecelowo nie zacytuje źródła. Okazuje się, że respondenci w cytowanym badaniu jednoznacznie stwierdzili, że plagiat jest zawsze plagiatem, niezależnie od oceny i intencji, jednak sama kara za wykrycie takiego działania może się różnić. Co więcej, nauczyciele zgodnie opowie- dzieli się za polityką „zero tolerancji”, gdzie należy ścigać plagiaty także w przypadkach, gdy praca ma niewielki wpływ na ostateczną ocenę z przedmiotu. Ostatnim wartym odnotowania wnioskiem z ankiety są sytuacje, w których częściej dochodzi do plagiatów. Nauczyciele raportowali, że podczas zajęć z testowania i debugo- wania oprogramowania częściej dochodzi do plagiatowania podczas testów strukturalnych (ang. white-box testing, gdy testujący ma dostęp do kodu programu, który testuje), niż podczas testów funkcjonalnych (ang. black-box testing, gdy testujący ma dostęp jedy-

25

i i

i i i “output” — 2018/6/21 — 7:43 — page 26 — #26 i i i

nie do interfejsu programu, który testuje). Co więcej, wykładowcy zauważyli, że rozkład punktów za poszczególne składowe zadania także ma wpływ na to, która część zadania zostanie poddana plagiatowi. I tak, przykładowo, jeśli projekt programu jest stosunkowo wysoko punktowany w stosunku do samego kodu, to studenci bardziej prawdopodobnie będą kopiować część poświęconą projektowi. Ostatecznie autorzy [33] dochodzą do dość rozbudowanej definicji plagiatu, która za- wiera w sobie podsumowanie wyników ankiet oraz uwzględnienie wszystkich wymienio- nych wyżej sytuacji. Jeszcze raz powtórzmy, że algorytm nie jest w stanie wykryć ludzkich intencji oraz wziąć pod uwagę ogromu zawiłości, które, zgodnie z przytoczoną ankietą, towarzyszą zadaniu programistycznemu. Dlatego będziemy w dalszej części pracy koncen- trować się na wykrywaniu podobieństwa kodu jako takiego.

2.2 Podobieństwo kodu

Teraz przejdźmy do podobieństwa kodu. Od tego momentu, aby uniknąć sugerowania powodów, dla których dwa fragmenty kodu są do siebie podobne, będziemy posługiwać się powszechnie przyjętym w literaturze określeniem klonu (ang. code cloning). Jeżeli dwa fragmenty kodu są do siebie podobne, w szczególności jeśli jeden z nich powstał przez jakieś przekształcenie drugiego, są one klonami. Rozważaniom, co dokładnie oznacza, że dwa fragmenty kodu są do siebie podobne, poświęcamy resztę tego podrozdziału. Dość powszechnie przyjętą definicją podobieństwa w literaturze jest taksonomia klo- nów składająca się z czterech kategorii. Można ją spotkać np. w [113]: — Typ 1: Dokładna kopia, różnice tylko w białych znakach i komentarzach. — Typ 2: Tak samo jak typ 1, ale możliwe jest także zmienianie nazw zmiennych. — Typ 3: Tak samo jak typ 2, ale możliwe jest także dodawanie lub zmienianie paru wyrażeń w kodzie. — Typ 4: Semantycznie identyczne, ale przy użyciu innej składni. O ile podział ten jest zwięzły i elegancki, wydaje się, że nie jest on dostatecznie szcze- gółowy i nie daje dostatecznie dokładnej definicji przestrzeni, w której działamy. Zacytujmy w tym miejscu pracę [32], w której przeprowadzono ankietę, gdzie m.in. pytano o to, na jakie okoliczności zwracają uwagę nauczyciele akademiccy, gdy zastana- wiają się, co może wywołać podobieństwo dwóch fragmentów kodu źródłowego. Głównymi czynnikami okazały się: — wymagania zadania – np. jednym z jawnie postawionych ograniczeń może być użycie specyficznej struktury danych (np. wektorów zamiast tablic); — kody źródłowe podane na zajęciach jako pomoce dydaktyczne – wśród nich może być np. szkielet kodu źródłowego, który będzie użyty podczas rozwiązywania zadania;

26

i i

i i i “output” — 2018/6/21 — 7:43 — page 27 — #27 i i i

— na jak wiele sposobów można było napisać dany fragment kodu; — natura problemu przedstawionego w zadaniu, a także cechy języka programowa- nia – przykładowo, małe metody w językach zorientowanych obiektowo mogą być podobne, a liczba sposobów na ich napisanie może być mocno ograniczona.

Co więcej, wyniki cytowanej ankiety zawierają cechy, na jakie zwracają uwagę wykła- dowcy, gdy oceniają samo podobieństwo kodów źródłowych:

— brak, złe lub takie same wcięcia, — zmiana identyfikatorów, ale sama struktura i logika jest taka sama, — logika programu jest taka sama, — struktura kodu źródłowego jest taka sama, — takie samo (w tym tak samo złe) formatowanie kodu, np. w obu kodach spacje pomiędzy słowami są w tych samych miejscach, — podobne komentarze w kodzie, — taka sama liczba wierszy, a każdy wiersz ma taką samą funkcjonalność, — podobne błędy, — podobne nietypowe wiersze kodu, — leksykalne, syntaktyczne, gramatyczne i strukturalne podobieństwa.

O ile te obserwacje są ciekawe, to aby dało się przeprowadzić naukowe badania, które pozwolą nam na ocenę jakości zwracanych wyników przez różne metody antyplagiatowe, a także jego wydajność, musimy mieć jakąś bardziej konkretną definicję podobieństwa kodów źródłowych. Potrzebna jest nam operacyjna definicja podobieństwa kodu. Stosowanym podejściem, zwłaszcza przez twórców systemów antyplagiatowych, jest (zazwyczaj lakoniczne i pozbawione przykładów) wymienienie możliwych ataków, jak „zmiana komentarzy”, „zmiana formatowania kodu” czy „zmiana kolejności bloków kodu”. Taki sposób definiowania podobieństwa wydaje się sensowny, a jedynym problemem jest fakt, że większość autorów pomija szczegóły czy przykłady takich ataków. Dlatego po- stanowiliśmy pójść tym śladem, jednak dołączając dużo dokładniejsze wyjaśnienia, jakie transformacje są dopuszczalne, aby mówić o klonach. W tej pracy przyjęliśmy następujące podejście: definiujemy pewne sposoby modyfikacji kodu, które pozostawiają go semantycznie identycznym (program po takiej zmianie nadal wykonuje tę samą pracę w ten sam sposób). Następnie mówimy, że dwa kody źródłowe są do siebie podobne (jeden jest klonem drugiego), jeśli jeden z nich powstał przez za- stosowanie ciągu tych dobrze zdefiniowanych modyfikacji na drugim. Same modyfikacje będziemy nazywać atakami na kod, lub po prostu atakami. Ataki zostały zaprojektowane na podstawie wieloletniego doświadczenia autorów w pracy ze studentami na przedmiotach związanych ściśle z programowaniem. Na tych

27

i i

i i i “output” — 2018/6/21 — 7:43 — page 28 — #28 i i i

przedmiotach zadawano prace domowe, projekty i zadania, które wymagały samodziel- nego rozwiązania przez studentów.

2.3 Zbiory benchmarkowe

Poza dokładną operacyjną definicją podobieństwa kodu, pozostaje jeszcze jeden problem do rozwiązania, aby móc w sposób ścisły badać skuteczność proponowanych metod. Cho- dzi o zbiory kodów źródłowych, na których można przeprowadzać testy eksperymentalne. Pewnym wyjściem jest użycie realnych rozwiązań przesłanych przez studentów. Jednak takie podejście ma szereg wad: przede wszystkim wcale nie wiemy, które prace są podobne, a które nie, tak więc trudno jest uczyć model np. w binarnej klasyfikacji, w problemie uczenia nadzorowanego. Poza tym nie mamy bezpośredniego wpływu na to, w jakim stop- niu niezbalansowany jest problem: czy mamy w zbiorze duży, czy mały procent klonów. Ostatnią kwestią jest to, że trudno kontrolować „trudność” takiego zbioru, czyli jak bardzo ściągający postarał się, aby jego kod był odmienny od pierwotnego kodu (innymi słowy, jak wiele ataków zastosował). Dlatego zdecydowaliśmy się zaimplementować własny ge- nerator zbiorów benchmarkowych, w którym wszystkie wymienione wcześniej parametry mielibyśmy pod kontrolą. W dotychczasowych pracach, zarówno dotyczących bezpośrednio plagiatów, jak i in- nych metod automatycznej analizy kodu, nie odnotowaliśmy takiego systematycznego podejścia. Zdecydowana większość polegała na wzięciu kodu źródłowego pojedynczego programu (lub paru arbitralnie wybranych), zastosowania metody wykrywania podobień- stwa kodu, a następnie zaprezentowanie wyników. Autorzy dość często pomijali fakt, że nie wiedzą tak naprawdę, ile było wszystkich podobnych fragmentów w takim kodzie. O po- dobnych problemach, jak brak rzetelnych zbiorów benchmarkowych czy problem z wiedzą, ile tak naprawdę jest klonów w zbiorze, można przeczytać także w [49]. I tak np. w pracy [84], która jest poświęcona zagadnieniu wykrywania fragmentów kodu odpowiedzialnych za zgłoszenie błędu (ang. bug), część eksperymentalna sprowadza się do przetestowania zgłoszeń niewielkiej liczby błędów w trzech programach: Rhino (35 błędów), Eclipse (3 błędy), Mozilla (5 błędów). Miarą jakości jest to, na jak wysokiej pozycji zgłoszona jest metoda, która rzeczywiście wymaga poprawy i porównanie tej war- tości z wynikami alternatywnego podejścia. W tej pracy autorzy dobrze wiedzieli, jakie powinny być poprawne wyniki, ponieważ mieli całą historię tych błędów, łącznie z in- formacjami, jakie fragmenty kodu zostały naprawione w wyniku ich zgłoszenia. Jednak nie zawsze tak jest. W pracy [94], która skupia się na ekstrahowaniu i grupowaniu fragmentów kodu, które odpowiadają poszczególnym zastosowaniom biznesowym, znów zamieszczone są wyniki

28

i i

i i i “output” — 2018/6/21 — 7:43 — page 29 — #29 i i i

eksperymentalne, polegające na przeanalizowaniu trzech programów: Apache, Petstore i jądro systemu Linux. Jednak brakuje tu informacji, jak mierzymy dokładność systemu, albo czego oczekujemy. W tej pracy autorzy wymieniają od dwóch do trzech tematów dla każdego programu, argumentując, że wydają się one sensowne i grupują słowa dotyczące tego samego zagadnienia. Jednak trudno jest powiedzieć, czy wszystkie tematy są tak samo informatywne, a po drugie ocena „na oko” wydaje się niedostatecznie naukowa. Warto zacytować fragment z [77], pracy zajmującej się znajdowaniem podobnych frag- mentów kodu: Due to lack of time it was impossible to manually verify all reported du- plicates. However, all reported duplicates we checked were correct (100% precision). Znów pojawia się ten sam problem: nietrywialnym jest dobrze zbadać materiał, z którym się pracuje, aby mieć pewność, jaką dokładność ma nasze narzędzie. Co więcej, w tej samej pracy opisywana metoda używa pewnego parametru k. Im większe k, tym dłuższy czas obliczeń, ale jednocześnie większa szansa, że znalezione zostaną wszystkie pary podobnego kodu. W cytowanej pracy przebadano różne wartości k, a następnie wyciągnięto wniosek, że najpierw liczba zwracanych par rośnie wraz ze wzrostem k, by potem się stabilizować. Dlatego wystarczy k o wartości takiej, dla której zaczynamy dostawać tę stabilną wartość. Nie ma jednak jednak jakiejkolwiek wzmianki o tym, jak się mają te wyniki do prawdziwej liczby par w badanym kodzie. Jest to jedynie porównywanie wyników tej samej metody dla jednej wartości parametru do jej wyników dla innej wartości tego parametru. Nawet w pracy dotyczącej MOSS-a [116], bardzo popularnego narzędzia do wykry- wania podobnych kodów źródłowych powstałego na uniwersytecie Stanford, znajdziemy w sekcji eksperymentalnej tak ogólne zdania, jak „We have no formal experiments on this topic, but we have informally experimented with MOSS by simply examining the results of tests on sample data” czy „There is some value of k (dependent on the document type) for which the reported matches are likely to be the result of copying, and for a slightly smaller value of k significant numbers of obvious false positives appear in the results.” W pracy [67] dotyczącej narzędzia DECKARD, główną miarą używaną przez autorów jest liczba wierszy kodu, które zostały zakwalifikowane jako klony. Także, podobnie jak w poprzednich pracach, możemy przeczytać, że twórcy dokonywali inspekcji jedynie lo- sowej podpróbki zwróconych wyników, aby zorientować się w ich jakości. Były to wyniki dla takich parametrów metody, które zapewniały najmniej błędów pierwszego rodzaju (parametr similarity ustawiony na 1). Oczywiście nie można tutaj mówić o jakiejkolwiek analizie błędów drugiego rodzaju. Co ciekawe, jest tu także poruszana kwestia tego, że cza- sami trudno jest zadecydować, czy dwa fragmenty są swoimi klonami czy też nie, jednak temat nie zostaje głębiej zbadany.

Podsumowując, tak naprawdę mamy do czynienia z dwoma problemami: pierwszym jest dokonywanie plagiatów przez studentów w ramach zadań programistycznych na uczel-

29

i i

i i i “output” — 2018/6/21 — 7:43 — page 30 — #30 i i i

niach wyższych. Dopuszczanie się plagiatu dotyczy różnego rodzaju pozyskiwania kodu źródłowego, a następnie używania go bez odpowiedniego odnośnika do oryginalnego źró- dła. Jest to problem, którego nie jest w stanie rozwiązać maszyna. Drugim, bardziej technicznym zagadnieniem, jest kwestia znajdowania podobnych fragmentów kodu w zadanym zbiorze. Jest to zadanie osiągalne dla programu komputero- wego, jednak mamy tu do czynienia z dwoma kolejnymi poważnymi problemami. Pierw- szym z nich jest dokładne określenie, co oznacza sformułowanie „podobne kody źródłowe”. W tej pracy wymieniamy możliwe sposoby transformacji kodu, definiując, że dwa kody źródłowe są do siebie podobne, jeśli można przekształcić jeden z nich przez ciąg takich modyfikacji w drugi. Jest to sposób używany w podobnych pracach, przy czym nasz opis zdaje się być dokładniejszy i bardziej sformalizowany. Inną kluczową kwestią przy tworzeniu oprogramowania do wykrywania podobnych fragmentów kodu jest sposób oceny zwracanych przez nie wyników. Aby badania były dokładne, a wyniki rzetelne, zachodzi potrzeba kontrolowania wszystkich cech zbioru, na którym przeprowadzamy testy, w szczególności potrzebna jest dokładna informacja, które fragmenty są podobne, a które nie. Dodatkowymi parametrami są „trudność” zbioru, jego rozmiar, czy zbalansowanie problemu. Dzięki temu bardziej dogłębne i szczegółowe badania różnych metod są możliwe. Według naszej najlepszej wiedzy nigdy nie zasto- sowano takiego podejścia w innych pracach dotyczących tej tematyki, gdzie zazwyczaj przeprowadzano testy na kilku wybranych arbitralnie zbiorach, bez dodatkowej, rzetelnej wiedzy, jak wiele fragmentów (i których) powinno zostać zwróconych.

Pozostała część rozdziału jest uporządkowana następująco: najpierw przedstawiamy pewne cechy języka R jako języka funkcyjnego, które pozwalają lepiej zrozumieć dalszą część rozdziału. Potem przechodzimy do możliwych ataków, by zakończyć rozdział opisem generatora zbiorów benchmarkowych.

2.4 R jako język funkcyjny

Język R [22, 45, 51, 109] zdobywa coraz większe zainteresowanie wraz ze wzrostem po- pularności statystyki, uczenia maszynowego oraz technik big data, co jest spowodowane zarówno wzrostem mocy obliczeniowej komputerów, jak i coraz większą cyfryzacją ludz- kiej działalności, w tym przechowywaniu gigantycznych ilości informacji w olbrzymich hurtowniach i bazach danych. Świadczyć może o tym liczba pakietów dostępnych w repo- zytorium CRAN1 (The Comprehensive R Archive Network), wiodącej bazie pakietów dla

1https://cran.r-project.org/

30

i i

i i i “output” — 2018/6/21 — 7:43 — page 31 — #31 i i i

języka R (innymi popularnymi miejscami ich publikacji jest Bioconductor2 czy Github3): w 2010 było to 2000, w 2012 – 3500, w 2014 już 6000, 2016 – 9000, a w 2018 – 12500. Ję- zyk ten jest od początku tworzony przez statystyków rozumiejących codzienne problemy i przypadki użycia takiego narzędzia przez ludzi zajmujących się tymi dziedzinami wie- dzy, przez co mogli zaproponować język odpowiadający ich potrzebom. O tym, jak bardzo odnieśli sukces, niech świadczy fakt, że ich konkurencja nie jest zbyt silna: głównie jest to język Python [47, 126], który również pozwala na podobne działania na danych wraz z podobnie wielkim środowiskiem pakietów tworzonych przez niezależnych autorów. Jest on popularniejszy w środowiskach programistów, podczas gdy statystycy wydają się woleć język R [70]. Rosnąca popularność tego języka znajduje odwzorowanie w coraz większym zapotrze- bowaniu na jego naukę. Zarówno na kierunkach uczelni wyższych, jak i komercyjnych kursach, dotyczących tematyki uczenia maszynowego i statystyki, coraz częściej jednym z głównych punktów jest nauka języka R. Sprawia to, że zwiększone jest także zapotrzebo- wanie na uczciwą ocenę studentów, w tym także sprawdzanie ich prac pod kątem plagiatu. Co więcej, olbrzymia, scentralizowana baza pakietów open source skłania ku badaniom, jak bardzo ich autorzy korzystają z kodów innych autorów bez podania odpowiedniej informacji. Język R, choć składniowo wydaje się być językiem imperatywnym [2], pozwala na zastosowanie wielu konstrukcji znanych z języków funkcyjnych, ponieważ jest oparty na języku Scheme [38], który jest pochodzą języka Lisp [6, 39]. Daje to nowe, do tej pory nie- przebadane możliwości: po pierwsze, tworzenie zbiorów benchmarkowych będzie znacznie ułatwione, ponieważ wszystko jest ujednolicone: każda konstrukcja, łącznie z pętlą for czy nawiasem klamrowym jest tak naprawdę funkcją (dokładniej wywołaniem, ang. call). Pozwala to na znacznie łatwiejszą implementację poszczególnych modyfikacji. O szczegó- łach takiego podejścia piszemy w podrozdz. 2.5. Po drugie można konstruować ciekawe algorytmy porównywania, które będą wykorzy- stywać te funkcyjne własności języka R. O ile proste algorytmy, znane z literatury, które także testujemy w tej pracy, używające np. tokenów (por. p. 3.1.1), nie będą w stanie wy- korzystać tych cech, o tyle nasza autorska implementacja generowania PDG (por. p. 3.1.2) jest świadoma takich atrybutów języka: — funkcja dla tego samego zestawu argumentów zwraca zawsze tę samą wartość (nie ma efektów ubocznych) – oznacza to, że niezależnie od tego, ile razy zostanie ona wywołana, jeśli algorytm wykryje, że argumenty nie zmieniły się w międzyczasie, to tworzony jest jedynie jeden wierzchołek reprezentujący takie wywołanie (nie dotyczy

2https://www.bioconductor.org/ 3https://github.com/

31

i i

i i i “output” — 2018/6/21 — 7:43 — page 32 — #32 i i i

generatorów liczb pseudolosowych); — istnienie apply(), lapply(), sapply(), mapply() itp. – są to odpowiedniki ogól- nego map() występującego w każdym języku funkcyjnym. Wiążą się one z istnieniem funkcji anonimowych, a samo ich użycie jest odpowiednikiem pętli (w szczególno- ści pętli for). Nasza implementacja tworzy analogiczną reprezentację grafową dla zwykłych pętli oraz wywołań z tej rodziny funkcji; — ostatni wiersz ciała oznacza wartość zwracaną, niezależnie, czy użyto return() czy też nie – nasza implementacja tworzy taką samą reprezentację grafową dla obu sposobów zwracania wartości; — składanie funkcji – typowym scenariuszem w języku funkcyjnym jest składanie funk- cji, czyli przekazywanie wartości zwracanej z jednego wywołania jako argument na- stępnego. Oczywiście nic nie stoi na przeszkodzie, aby poszczególne wyniki przypisać do zmiennych. Więcej o tym sposobie zmylenia systemu wykrywania podobieństwa kodu piszemy w p. 2.6.4. Nasza implementacja w obu przypadkach tworzy takie same grafy; — operator potokowy %>% – operator ten przypomina np. operator bind »= znany z języka Haskell [91]. Pozwala on w inny sposób zapisać przekazanie wartości jako argument w złożeniach funkcji (por. p. 2.6.10). Nasza implementacja wykrywa takie konstrukcje i ujednolica je w formie grafu. Wymienione cechy występują we wszystkich językach funkcyjnych, np. Scali [102] lub Haskellu oraz występują w pewnym stopniu w popularnych bibliotekach (np. LINQ [34, 98] w C# [62], kolekcjach w Javie [103], oraz bibliotece standardowej [133] w C++ [123] pojawia się przetwarzanie wyniku działania poprzedniej funkcji kolejnym wywołaniem, a także funkcje anonimowe i odpowiedniki map()). Co więcej, język R przejawia jeszcze jedną cechę, która, choć nie jest domeną języ- ków funkcyjnych, znalazła odwzorowanie w pewnym zaproponowanym przez nas sposobie reprezentacji funkcji. Istnienie scentralizowanego repozytorium pakietów (CRAN), z któ- rych wszyscy korzystają, sprawia, że wywołania tych samych funkcji znajdują się w wielu różnych kodach źródłowych. Więcej o tym piszemy w p. 3.1.1. Taka centralizacja pojawia się coraz częściej, czego przykładem może być także język Python.

2.5 Typy wierzchołków w drzewie składniowym

Ataki przedstawione w dalszej części będą opierać się na drzewie składniowym (ang. abs- tract syntax tree, AST). Jest to drzewo powstałe w wyniku przeprowadzenia analizy skła- dniowej na kodzie źródłowym, w którym każdy wierzchołek odpowiada pewnej konstrukcji

32

i i

i i i “output” — 2018/6/21 — 7:43 — page 33 — #33 i i i

języka R, a jego dzieci to składowe tej konstrukcji. I tak wierzchołkiem może być np. wy- wołanie funkcji, a jego dziećmi argumenty wywołania. Wierzchołki drzewa składniowego mogą mieć jeden z trzech typów:

1) stała, 2) symbol, 3) wywołanie.

Stała to po prostu wartość w kodzie, taka jak 1.0, TRUE, czy “napis”. Symbole to identyfikatory zmiennej lub nazwy funkcji, np. x czy sum. Wierzchołki reprezentujące wy- wołanie stanowią centralną część języka R, który jest językiem funkcyjnym: są to wierz- chołki wywołania funkcji. Warto tutaj zauważyć, że zalicza się do nich wiele konstrukcji, które niekoniecznie są wywołaniami w innych językach: definiowanie funkcji, wywołanie pętli czy instrukcji warunkowej, a nawet użycie nawiasów klamrowych. Każdą funkcję można wywołać, stosując zapis:

1 nazwa_funkcji(parametr1 , parametr2 , ... , parametrN)

i o ile nie dziwi zapis:

1 rep (2 , 3)

o tyle nie takim oczywistym wywołaniem jest:

1 "for"(i, ":"(1,5), "{"(print("i = "),print(i)))

które znaczy dokładnie tyle samo, co zapis:

1 for(i in 1:5) { print("i = "); print(i) }

Wcześniejszy zapis okazuje się bardzo wygodny, gdy zachodzi potrzeba zamienienia drzewa wyrażeń na kod. Dzięki niemu wszystkie wierzchołki tego typu można traktować w ten sam, ogólny sposób, bez rozpatrywania przypadków szczególnych. Po zapisaniu go w tej formie, wystarczy na takim napisie wywołać funkcję deparse(), aby otrzymać sformatowany kod, w którym pętle są zapisane w „przyjazny” sposób, taki, do którego je- steśmy przyzwyczajeni (funkcja quote() służy zapobiegnięciu wykonania kodu podanego jako argument):

33

i i

i i i “output” — 2018/6/21 — 7:43 — page 34 — #34 i i i

1 deparse(quote("for"(i , ":"(1,5), "{"(print("i = "),print(i))))) 2 # [1] "for (i in 1:5) {" " print(\"i = \")" " print(i)" "}"

W bibliotece pryr zaimplementowano funkcję ast, która pozwala na proste wyryso- wanie na konsoli drzewa wyrażeń. Przykładowo:

1 ast ( y <− x ∗ 10) 2 #\− () #wywołanie 3 #\− ‘<− # nazwa wywoływanej funkcji 4 #\− ‘ y # symbol 5 #\− () #wywołanie − drugi argument poprzedniego wywołania 6 #\− ‘ ∗ # nazwa wywoływanej funkcji 7 #\− ‘ x # symbol 8 #\− 10 # sta ł a

Teraz zilustrujmy przykładową pętlę, która, jak wspomnieliśmy, jest zwykłym wywoła- niem funkcji:

1 ast(for(i in 1:5) { print("i = "); print(i) }) 2 #\− () # wywołanie 3 #\− ‘for # nazwa wywoływanej funkcji 4 #\− ‘ i # symbol 5 #\− () # wywołanie 6 #\− ‘: # nazwa wywoływanej funkcji 7 #\− 1 # sta ł a 8 #\− 5 # sta ł a 9 #\− () # wywołanie 10 #\− ‘{ 11 #\− () # wywołanie 12 #\− ‘print # nazwa wywoływanej funkcji 13 #\− " i = " # sta ł a 14 #\− () # wywołanie 15 #\− ‘print # nazwa wywoływanej funkcji 16 #\− ‘ i # symbol

Poniżej przykładowe drzewo dla definicji funkcji:

34

i i

i i i “output” — 2018/6/21 — 7:43 — page 35 — #35 i i i

1 ast ( dodaj <− function(x,y=5) {x+y}) 2 #\− () # wywołanie 3 #\− ‘<− # nazwa wywoływanej funkcji 4 #\− ‘ dodaj 5 #\− () # wywołanie 6 #\− ‘function # nazwa wywoływanej funkcji 7 #\− [] # specjalny typ pairlist , służący jedynie przechowywaniu parametrów funkcji 8 # \ x =‘MISSING # parametr x bez wartości domyślnej 9 # \ y= 5# parametr y z wartością domyślną równą 5 10 #\− () # wywołanie 11 #\− ‘{ # nazwa wywoływanej funkcji 12 #\− () # wywołanie 13 #\− ‘+ # nazwa wywoływanej funkcji 14 #\− ‘ x # symbol 15 #\− ‘ y # symbol

2.6 Ataki na kod

Zacznijmy od zdefiniowana, jaki fragment kodu, stanowiący spójną całość, będziemy po- równywać. Nasz wybór padł na funkcję. Taka jednostka kodu wydaje się mieć odpowiedni rozmiar: nie jest ani wyrwanym z kontekstu fragmentem, ani zbyt dużym, zawierającym wiele podprogramów, modułem lub pakietem. Pojedynczy atak to dobrze zdefiniowany sposób takiej modyfikacji kodu, która pozo- stawia program semantycznie niezmienionym (kod nadal można użyć w celu, w którym go pierwotnie napisano). Wszystkie ataki działają, co do idei, w podobny sposób: najpierw wczytujemy i par- sujemy kod funkcji wejściowej, otrzymując w ten sposób drzewo składniowe. Następnie w zależności od rodzaju ataku, pewne wierzchołki mogą być zmodyfikowane (np. wierz- chołki reprezentujące symbol, pętlę czy wywołanie funkcji). Przeprowadzenie ataku polega na przekształceniu takiego podatnego wierzchołka. Ataki, które zostaną przedstawione, to: — zmiana operatora przypisania (podrozdz. 2.6.1), — zmiana nazw zmiennych (podrozdz. 2.6.2), — zmiana nazw funkcji (podrozdz. 2.6.3), — rozwijanie zagnieżdżonych wywołań funkcji (podrozdz. 2.6.4), — zamiana miejscami niezależnych wierszy kodu (podrozdz. 2.6.5),

35

i i

i i i “output” — 2018/6/21 — 7:43 — page 36 — #36 i i i

— zamiana jednego typu pętli na drugi (podrozdz. 2.6.6), — przemienność argumentów w operatorach binarnych (podrozdz. 2.6.7), — wykorzystanie tautologii do modyfikacji warunków logicznych (podrozdz. 2.6.8), — dodanie wierszy kodu (podrozdz. 2.6.9), — atak przy użyciu operatora potokowego %>% (podrozdz. 2.6.10), — odwrotny atak przy użyciu operatora %>% (podrozdz. 2.6.11), — podstawienie kodu wywoływanej funkcji (podrozdz. 2.6.12).

2.6.1 Zmiana operatora przypisania

Pierwszy opisywany atak polega na obserwacji, że w R mamy dwa sposoby zapisania operatora przypisania: <- oraz =. Atak ten ma zastosowanie tylko dla wierzchołków, które reprezentują przypisania. Sam algorytm polega na zamianie jednego symbolu przypisania na drugi. Warto zauważyć, że <- może być użyty w dwóch formach:

1 zmienna <− warto ś ć

lub:

1 warto ś ć −> zmienna

Jednak w wyniku parsowania oba takie zapisy dają taki sam wierzchołek w drzewie wyrażeń. Dlatego też rozważamy jedynie przypadek <- oraz =, a w kodzie wyjściowym nigdy nie znajdzie się ->. Przykładowy kod poddany temu atakowi to (wszystkie możliwe wierzchołki są podda- wane atakowi):

1 # kod przed transformacją 2 sumuj <− function(x) 3 { 4 suma <− 0 5 for(element in x) 6 { 7 suma <− suma + element 8 } 9 suma 10 }

36

i i

i i i “output” — 2018/6/21 — 7:43 — page 37 — #37 i i i

1 # kod po transformacji 2 sumuj = function(x) { 3 suma = 0 4 for (element in x) { 5 suma = suma + element 6 } 7 suma 8 }

2.6.2 Zmiana nazw zmiennych

Drugi rodzaj ataku polega na konsekwentnej zmianie nazw zmiennych. Podatnymi na tę transformację są wierzchołki w AST reprezentujące symbole w kodzie. Nowe oznaczenie jest generowane w sposób losowy. Poprawnym identyfikatorem w R jest ciąg liter, cyfr, kropki (.) oraz podkreślenia (_), przy czym pierwszym znakiem musi być litera lub kropka, po której nie następuje cyfra (aby uzyskać informacje o poprawnych nazwach zmiennych w języku R można sprawdzić dokumentację: ?make.names). Przykładowy kod poddany temu atakowi to (wszystkie możliwe wierzchołki są podda- wane atakowi):

1 # kod przed transformacją 2 sumuj <− function(x) 3 { 4 suma <− 0 5 for(element in x) 6 { 7 suma <− suma + element 8 } 9 suma 10 }

1 # kod po transformacji 2 sumuj <− function(ryLrLUeQ) { 3 K4rgnV7U <− 0 4 for (fTL1V13S in ryLrLUeQ) { 5 K4rgnV7U <− K4rgnV7U + fTL1V13S} 6 K4rgnV7U}

37

i i

i i i “output” — 2018/6/21 — 7:43 — page 38 — #38 i i i

2.6.3 Zmiana nazw funkcji

Głównym elementem języka R są funkcje. Wyjątkowo łatwo jest w nim zmienić ich nazwy na inne. Wystarczy napisać:

1 nazwaZastępcza <− sum

aby móc używać dalej w kodzie:

1 sumaElementów <− nazwaZastępcza(x)

Warto zwrócić uwagę, że w innych, statycznie typowanych językach, jak C++, C# czy Java, potrzeba byłoby utworzyć wskaźnik na funkcję lub delegację, co natychmiast wzbudziłoby podejrzenia sprawdzającego. Tutaj jest to dużo prostsze. Atak ten przetwarza wszystkie wierzchołki będące wywołaniami funkcji. Zmiana nazwy polega na wstawieniu przypisania nazwy funkcji do nowo wygenerowanego identyfikatora na początku kodu. Następnie wszelkie wywołania tej funkcji są podmieniane na nowe oznaczenie. Atak ten różni się subtelnie od poprzedniego. W przypadku zmiany nazw zmiennych, nie musimy dokonywać żadnych przypisań. Wystarczy zmienić ich identyfikatory. Tutaj poza zmianą wszystkich wywołań na nowe oznaczenia, należy jeszcze na początku przy- pisać starą nazwę funkcji do nowej. Zaznaczmy, że w tym ataku nie rozważamy podmiany nazw funkcji takich jak if, for, while, +, & czy {. Wynika to z faktu, że taki atak w naszej ocenie jest nierealistyczny i wzbudziłby natychmiastowe podejrzenia sprawdzającego. Poniższy kod ilustruje opisywany atak (wszystkie możliwe wierzchołki są poddawane atakowi):

1 # kod przed transformacją 2 parzysta <− function(liczba) 3 { 4 stopifnot(length(liczba) == 1) 5 stopifnot(is .numeric(liczba))

6

7 liczba %% 2 == 0 8 }

38

i i

i i i “output” — 2018/6/21 — 7:43 — page 39 — #39 i i i

1 # kod po transformacji 2 parzysta <− function(liczba) 3 { 4 FLcroXCO <− s t o p i f n o t 5 tCu4142t <− length 6 RX3u12y6 <− i s . numeric 7 FLcroXCO(tCu4142t(liczba) == 1) 8 FLcroXCO(RX3u12y6( liczba ))

9

10 liczba %% 2 == 0 11 }

2.6.4 Rozwijanie zagnieżdzonych wywołań funkcji

Kolejny atak wykorzystuje następujące zagnieżdżone wywołania w kodzie R (jako argu- ment wywołania funkcji podajemy wywołanie innej funkcji):

1 zmienna <− funkcja1(funkcja2(argument1, argument2) , funkcja3( argument3))

Istotą języka R jest wywoływanie poszczególnych funkcji, a następnie przekazywanie ich wyników dalej jako argumenty do kolejnych funkcji. Wynika to z faktu, że jest to język funkcyjny, gdzie zdecydowana większość funkcji nie ma efektów ubocznych (np. wypisania tekstu na ekran). Oczywiście nic nie stoi na przeszkodzie, aby przypisać wartości zwracane przez funkcje do zmiennych:

1 zmienna_pomocnicza1 <− funkcja2(argument1 , argument2) 2 zmienna_pomocnicza2 <− funkcja3(argument3) 3 zmienna <− funkcja1(zmienna_pomocnicza1 , zmienna_pomocnicza2)

Opisywany atak polega na takim właśnie rozbiciu zagnieżdżonych wywołań na kolejne przypisania do zmiennych, które są następnie przekazywane jako argumenty. Atak działa dla wierzchołków typu wywołanie. Tak jak poprzednio, nie bierzemy pod uwagę wierzchołków, dla których taki atak nie ma sensu, jak funkcja {, for, while, if, <-, = czy function. Dla danego wywołania musimy się upewnić, że jest zagnieżdzone (a więc nie jest bez- pośrednio argumentem przypisania czy nawiasu klamrowego). Następnie należy znaleźć

39

i i

i i i “output” — 2018/6/21 — 7:43 — page 40 — #40 i i i

miejsce w kodzie, gdzie możemy wywołać tę funkcję, a wynik przypisać do zmiennej. Zazwyczaj jest to najbliższy nawias klamrowy, jednak może się zdarzyć, że funkcja jest zagnieżdżona w funkcji wywoływanej bezpośrednio np. w pętli for:

1 for(i in 1:5) 2 print(pow(i ,2))

Wtedy należy dodać nawiasy klamrowe, a dopiero w ich obrębie dokonać przypisania:

1 for(i in 1:5) 2 { 3 zmienna_pomocnicza <− pow( i , 2 ) 4 print (zmienna_pomocnicza) 5 }

Nazwy zmiennych pomocniczych generowane są na tej samej zasadzie, co wcześniej: są to 8-znakowe napisy, zawierające małe i wielkie litery oraz cyfry. Poniżej znajdziemy ilustrację opisywanego ataku (wszystkie możliwe wierzchołki są pod- dawane atakowi):

1 # kod przed transformacją 2 k o r e l a c j a <− function(x, y) 3 { 4 sum ( ( x−mean( x ) ) ∗ ( y−mean(y)))/(sqrt(sum((x−mean(x))^2)) ∗ s q r t (sum ( ( y−mean(y))^2))) 5 }

1 # kod po transformacji 2 k o r e l a c j a <− function(x) { 3 qz3u32r6 <− mean( x ) 4 a4f36h4f <− x−qz3u32r6 5 ZA3d1rr1 <− mean( y ) 6 j3f25hab <− y−ZA3d1rr1 7 Qvb6h7Ab <− ( a4f36h4f ) ∗ ( j3f25hab ) 8 nbF2346h <− sum(Qvb6h7Ab)

9

10 az3uR2T2 <− mean( x ) 11 USD24Bby <− x−az3uR2T2

40

i i

i i i “output” — 2018/6/21 — 7:43 — page 41 — #41 i i i

12 EUR1rbSa <− USD24Bby^2 13 T41X4Br3 <− sum(EUR1rbSa) 14 VDSdf311<− sqrt (T41X4Br3)

15

16 t13z5Br4 <− mean( y ) 17 UCx21Bby <− y−t13z5Br4 18 ef314bSa <− UCx21Bby^2 19 v4s5bre2 <− sum(ef314bSa) 20 xv3w53df<− sqrt(v4s5bre2) 21 vcbre654 <− VDSdf311 ∗ xv3w53df 22 nbF2346h/vcbre654 23 }

Już teraz zwróćmy uwagę na dwukrotnie pojawiające się x-mean(x) czy y-mean(y). Wykrywanie sytuacji, w których dwukrotnie wywoływana jest ta sama funkcja z tymi samymi argumentami poruszane jest przy opisie autorskiego sposobu generowania PDG w p. 3.1.2.

2.6.5 Zamiana miejscami niezależnych wierszy kodu

Istnieją sytuacje, gdy dwa wyrażenia w kodzie można wykonać niezależnie od siebie i nie ma znaczenia, czy najpierw będzie to pierwsze, czy drugie. Opisywany w tym podroz- dziale atak polega na wykrywaniu takich sytuacji i zamianie miejscami takich dwóch niezależnych fragmentów kodu. Aby lepiej zrozumieć naturę tego ataku, rozważmy następujący wiersz:

1 y <− f ( x )

Możemy go przemieścić dowolnie w dół lub górę, w granicach wyznaczonych przez nastę- pujące typy wierszy kodu:

1 # do argumentu x przypisywana jest wartość 2 x <− ... 3 # wartość zwracana y jest używana w innym wierszu 4 . . . <− y 5 # wartość zwracana y jest ponownie nadpisywana 6 y <− ...

Każdy z wymienionych trzech typów tworzy swego rodzaju granicę, za którą nie mo- żemy przenieść rozważanego wiersza. Atak może zostać przeprowadzony na dowolnym

41

i i

i i i “output” — 2018/6/21 — 7:43 — page 42 — #42 i i i

wierzchołku reprezentującym przypisanie. Sam atak polega na wyznaczeniu dwóch najbar- dziej oddalonych wierzchołków w drzewie (wśród rodzeństwa rozważanego wierzchołka), reprezentujących najwcześniejszą i najpóźniejszą instrukcję, przy których można umie- ścić rozważane przypisanie, aby nie zmienić znaczenia kodu. Wybieramy to miejsce, które znajduje się dalej od atakowanego i tam wstawiamy atakowany wierzchołek. Poniżej znajduje się kod przed i po przeprowadzeniu opisywanego ataku:

1 # kod przed transformacją 2 k o r e l a c j a <− function(x, y) 3 { 4 l i c z n i k <− sum ( ( x−mean( x ) ) ∗ ( y−mean( y ) ) ) 5 mianownik <− sqrt(sum((x−mean(x))^2)) ∗ sqrt(sum((y−mean(y))^2)) 6 licznik/mianownik 7 }

1 # kod po transformacji 2 k o r e l a c j a <− function(x, y) 3 { 4 mianownik <− sqrt(sum((x−mean(x))^2)) ∗ sqrt(sum((y−mean(y))^2)) 5 l i c z n i k <− sum ( ( x−mean( x ) ) ∗ ( y−mean( y ) ) ) 6 licznik/mianownik 7 }

Przy okazji nadmieńmy, że operatory ˆ oraz ** oznaczają potęgowanie i w wyniku par- sowania zamieniane są na to samo wewnętrzne oznaczenie. Tokeny występujące w języku R są zaprezentowane w tab. 3.1, przy okazji omawiania tej formy reprezentacji funkcji w p. 3.1.1.

2.6.6 Zamiana jednego typu pętli na drugi

W języku R mamy do czynienia z trzema rodzajami pętli: for, while i repeat. for jest pętlą, która iteruje po elementach danego ciągu, while wykonuje się, dopóki pe- wien warunek jest spełniony, zaś repeat to pętla, która wykonuje nieograniczoną liczbę razy pewien fragment kodu, nie sprawdzając przy tym żadnego warunku (należy ręcznie wykonać break lub return() w ciele pętli, aby ją przerwać). Zdecydowaliśmy się na zamianę pętli for na pętlę while. Decyzja głównie bierze się z faktu, że pętla for jest najpopularniejszą w języku R i to najczęściej właśnie ona będzie zamieniania na inną pętlę. Za to pętla repeat jest bardzo mało popularna. Poza tym warto

42

i i

i i i “output” — 2018/6/21 — 7:43 — page 43 — #43 i i i

zauważyć, że pętla for w języku R jest nieco odmienna od pętli for znanej z innych języków, jak np. C++ czy Java. W R pętla ta służy do iterowania po ciągach, takich jak wektory czy listy i bardziej przypomina konstrukcję typu foreach znaną z innych języków. Tak więc zbiór jej zastosowań jest podzbiorem zbioru możliwych zastosowań pętli ogólnego przeznaczenia while. To kolejny powód, dla którego zdecydowaliśmy o zamianie w tę stronę. Przyjrzyjmy się samej pętli for. Jest to konstrukcja:

1 for(element in ciag) wyrazenie

Atak działa na każdym wierzchołku w AST reprezentującym pętlę for. Zamienia on powyższą pętlę na następujący, równoważny kod:

1 zmienna_iterujaca <− 1 2 dlugosc <− length(ciag) 3 while(zmienna_iterujaca <= dlugosc) 4 { 5 # każde użycie element jest zamieniane 6 # na ciag [[zmienna_iterujaca ]] 7 zmienna_iterujaca <− zmienna_iterujaca + 1 8 }

Dodatkowo, wprowadziliśmy optymalizację: jeśli w oryginalnej pętli w miejscu ciągu użyto wywołania funkcji, to wywołanie jest przypisywane do zmiennej:

1 for(element in f(ciag)) 2 { 3 wyraż enia 4 }

jest przekształcane na:

1 zmienna_iterujaca <− 1 2 c i a g_f <− f ( c i a g ) 3 dlugosc <− length(ciag_f) 4 while(zmienna_iterujaca <= dlugosc){ 5 # użycie element jest zamieniane na ciag_f [[zmienna_iterujaca ]] 6 zmienna_iterujaca <− zmienna_iterujaca + 1}

43

i i

i i i “output” — 2018/6/21 — 7:43 — page 44 — #44 i i i

2.6.7 Przemienność argumentów w operatorach binarnych

Następny atak jest oparty na obserwacji, że jest całkiem wiele używanych operatorów w ję- zyku R (i nie tylko), które zwracają tę samą wartość niezależnie od kolejności podawanych argumentów. Przykładowo wyrażenie a+b daje ten sam wynik, co b+a. Innymi tego typu operatorami są & (operator odpowiadający zwektoryzowanej logicz- nej koniunkcji: jego argumentami są dwa równej długości wektory wartości logicznych, a wynikiem jest wektor wartości logicznych o tej samej długości, gdzie i-ta pozycja jest wynikiem logicznej koniunkcji i-tych elementów wektorów wejściowych), | (zwektoryzo- wana logiczna alternatywa), *, ==. Warto zauważyć, że możemy także w podobny sposób dokonać zamiany argumentów dla operatorów typu <=, <, >=, >, przy czym oczywiście należy pamiętać, że trzeba nie tylko zamienić argumenty, ale także sam operator na kom- plementarny. Warto zauważyć, że && (logiczna koniunkcja) oraz || (logiczna alternatywa) nie są ope- ratorami symetrycznymi. W przypadku operatora &&, jeśli pierwszy argument okaże się równy wartości logicznej fałsz, to nie zostanie już obliczony drugi argument (który może być choćby czasochłonnym wywołaniem funkcji). Analogiczna obserwacja ma zastosowa- nie dla operatora ||. Przykładowy kod, poddany temu atakowi, to (wszystkie możliwe wierzchołki są pod- dawane atakowi):

1 # kod przed transformacją 2 sumaDwoch <− function(x) 3 { 4 stopifnot(length(x) > 0 && is.numeric(x)) 5 stopifnot(length(y) > 0 && is.numeric(y)) 6 x+y 7 }

1 # kod po transformacji 2 sumaDwoch <− function(x) 3 { 4 stopifnot(0 < length(x) && is.numeric(x)) 5 stopifnot(0 < length(y) && is.numeric(y)) 6 y+x 7 }

44

i i

i i i “output” — 2018/6/21 — 7:43 — page 45 — #45 i i i

2.6.8 Wykorzystanie tautologii do modyfikacji warunków logicznych

Często, zwłaszcza w instrukcjach warunkowych, mamy do czynienia ze złożeniami warun- ków logicznych. Opisywany atak operuje na wierzchołkach AST reprezentujących operacje logiczne: &, &&, |, ||. Sam atak polega na wykorzystaniu praw de Morgana. Zmieniamy następujące wyrażenie:

1 a && b

na:

1 ! ( ! a | | ! b)

i oczywiście:

1 a | | b

na:

1 ! ( ! a && ! b)

Jednocześnie, gdy tylko jest to możliwe, operacja !a jest rozwijana. I tak np. negacja negacji wyrażenia daje nam po prostu to wyrażenie, a negacja znaku mniejszości < polega na zamianie go na >=. Poniżej przedstawiony jest kod przed i po zastosowaniu opisywanego ataku (wszystkie możliwe wierzchołki są poddawane atakowi):

1 # kod przed transformacją 2 obliczBmi <− function(waga, wzrost) { 3 bmi <− waga/wzrost^2 4 if(bmi <= 18.5) { "niedowaga" } 5 else if (bmi > 18.5 && bmi <= 25) { "norma" } 6 else { "nadwaga" } 7 }

45

i i

i i i “output” — 2018/6/21 — 7:43 — page 46 — #46 i i i

1 # kod po transformacji 2 obliczBmi <− function(waga, wzrost) 3 { 4 bmi <− waga/wzrost^2 5 if(!(bmi > 18.5)) { 6 "niedowaga" 7 } else if (!(bmi<= 18.5 || bmi> 25)) { 8 "norma" 9 } e l s e { 10 "nadwaga" 11 } 12 }

2.6.9 Dodanie wierszy kodu

Opisywany teraz atak polega na obserwacji, że w celu zmylenia niektórych systemów an- typlagiatowych, należy wstawić w środek kodu wyrażenie nie mające wpływu na resztę programu, które zmodyfikuje ciąg operacji, który w przeciwnym wypadku zostałby wy- kryty. Na taki atak jest podatna np. metoda porównująca tokeny. W wyniku dodania wiersza sekwencja tokenów zmienia się, przez co wykrycie dwóch identycznych ciągów to- kenów staje się utrudnione. Tokeny oraz podatne algorytmy są opisane szerzej w p. 3.1.1, 3.3.1–3.3.3. Atak transformuje każdy wierzchołek reprezentujący nawias klamrowy. Poniżej znaj- duje się kod przed i po przeprowadzeniu opisywanego ataku:

1 # kod przed transformacją 2 k o r e l a c j a <− function(x, y) 3 { 4 l i c z n i k <− sum ( ( x−mean( x ) ) ∗ ( y−mean( y ) ) ) 5 mianownik <− sqrt(sum((x−mean(x))^2)) ∗ sqrt(sum((y−mean(y))^2)) 6 licznik/mianownik 7 }

1 # kod po transformacji 2 k o r e l a c j a <− function(x, y) 3 { 4 l i c z n i k <− sum ( ( x−mean( x ) ) ∗ ( y−mean( y ) ) )

46

i i

i i i “output” — 2018/6/21 — 7:43 — page 47 — #47 i i i

5 l i c z n i k <− l i c z n i k 6 mianownik <− sqrt(sum((x−mean(x))^2)) ∗ sqrt(sum((y−mean(y))^2)) 7 mianownik <− mianownik 8 licznik/mianownik 9 }

2.6.10 Atak przy użyciu operatora potokowego %>%

Operator potokowy %>% z pakietu magrittr jest popularnym sposobem pisania czytel- nego kodu (w chwili pisania pracy ponad 618 pakietów w jakiś sposób zależy od niego, a tygodniowa liczba ściągnięć sięga 88.000), gdy zachodzi potrzeba użycia wielokrotnie za- gnieżdżonego wywołania funkcji. Aby lepiej zrozumieć, jak działa operator %>%, rozważmy następujący przykład. Zacznijmy od zdefiniowania paru funkcji pomocniczych:

1 dodaj1 <− function(x) 2 { 3 x+1 4 }

5

6 suma <− function(x,y) 7 { 8 x+y 9 }

10

11 r o z n i c a <− function(x,y) 12 { 13 x−y 14 }

Teraz załóżmy, że chcemy wykonać następujące złożone wywołanie funkcji:

1 dodaj1(roznica(suma(5,2) ,8))

Oczywiście możemy tak zrobić, jednak jest to dość nieczytelne. Dużo lepiej jest napisać, używając operatora %>%:

1 5 %>% suma(2) %>% roznica(8) %>% dodaj1()

47

i i

i i i “output” — 2018/6/21 — 7:43 — page 48 — #48 i i i

Istnieje wiele sposobów przekazania wartości wyliczanej w lewym operandzie jako ar- gumentu do funkcji będącej prawym operandem4 przy użyciu operatora %>%, a także two- rzenia w locie funkcji jednoargumentowych, jednak my skupimy się na jego najbardziej podstawowym zastosowaniu, zakładając, że osoba popełniająca plagiat będzie chciała osiągnąć efekt jak najmniejszym kosztem (to dość popularne podejście, spotkane też np. w [107]: „Thus, JPlag’s success in finding plagiarisms critically depends on the plagiarists’ lazyness (...)”, „Few plagiarists were creative enough to find enough disguising techniques that could be applied all over the given program.”, „Even if plagiarists found enough tech- niques, few were eager enough to apply them sufficiently often.”). W najprostszym przy- padku opisywany operator przyjmuje jako prawy operand wywołanie funkcji, zaś jako lewy operand przyjmuje pierwszy argument tego wywołania (jeśli funkcja nie przyjmuje żadnych parametrów, to jest on ignorowany). Reszta argumentów wywołania jest po pro- stu podana w prawym operandzie. Tekstowo, w kodzie, wygląda to tak, jakby wyniki poszczególnych wywołań „przepływały” do kolejnych wywołań. Atak operuje na wierzchołkach AST, które są wywołaniami funkcji. W wierzchołku poddawanym atakowi dodajemy użycie operatora %>%, gdzie lewym operandem jest pierw- szy argument dotychczasowego wywołania, zaś ono samo, z odjętym pierwszym argumen- tem, jest drugim operandem. Przykładowy kod, poddany temu atakowi, to (wszystkie możliwe wierzchołki są pod- dawane atakowi):

1 # kod przed transformacją 2 f <− function(x,y) 3 { 4 dodaj1(roznica(suma(x,y) ,x)) 5 }

1 # kod po transformacji 2 f <− function(x,y) 3 { 4 x %>% suma(y) %>% roznica(x) %>% dodaj1() 5 }

4zob. https://magrittr.tidyverse.org/.

48

i i

i i i “output” — 2018/6/21 — 7:43 — page 49 — #49 i i i

2.6.11 Odwrotny atak przy użyciu operatora potokowego %>%

Kolejny atak jest bardzo podobny do poprzedniego, z tym, że w tym wypadku modyfiku- jemy wierzchołki reprezentujące użycie operatora %>% w kodzie i zamieniamy je na kla- syczne wywołania funkcji. Poniżej kod przed i po transformacji (wszystkie możliwe wierzchołki są poddawane atakowi):

1 # kod przed transformacją 2 f <− function(x,y) 3 { 4 x %>% suma(y) %>% roznica(x) %>% dodaj1() 5 }

1 # kod po transformacji 2 f <− function(x,y) 3 { 4 dodaj1(roznica(suma(x,y) ,x)) 5 }

2.6.12 Podstawienie kodu wywoływanej funkcji

Następny atak polega na wstawieniu kodu wywoływanej funkcji w miejsce tego wywo- łania. Aby przeprowadzić ten atak, musimy posiadać pewien zbiór definicji funkcji wraz z ich nazwami. Następnie ekstrahujemy z AST wierzchołki oznaczające wywołanie funkcji. Jeśli funkcja, która jest wywoływana, znajduje się w zbiorze definicji funkcji, atak będzie polegał na podmianie tego wywołania na ciało funkcji. Warto zauważyć, że bezpośrednie wklejenie dowolnego ciała funkcji nie da oczekiwa- nego efektu. Dlatego zdecydowaliśmy się na następujące rozwiązanie: funkcje, które wkle- jamy, korzystają z własności języka R, dzięki któremu ostatnia instrukcja jest jednocześnie wartością zwracaną. Dzięki temu, gdy wstawimy ciało takiej funkcji w nawiasy klamrowe, to całe wyrażenie (wywołanie nawiasów klamrowych) będzie miało wartość tej funkcji. Oczywiście pozostaje jeszcze kwestia przekazania argumentów do wywoływanej funkcji. Rozwiązujemy to przez przypisanie im wartości w kodzie jeszcze przed wstawieniem ciała funkcji. Rozważmy następującą funkcję, której kod (ciało) będzie wstawiany do drugiej funk- cji, f.

49

i i

i i i “output” — 2018/6/21 — 7:43 — page 50 — #50 i i i

1 sumuj <− function(wektor) 2 { 3 suma <− 0 4 for(element in wektor) 5 { 6 suma <− suma + element 7 } 8 suma 9 }

Jak widzimy, jest to funkcja, której ostatnia instrukcja to wartość zwracana. Poniżej znajduje się kod funkcji, która zostanie poddana atakowi:

1 s r e d n i a <− function(x) 2 { 3 l i c z n i k <− sumuj ( x ) 4 licznik/length(x) 5 }

Możemy zaobserwować, sumuj() jest wywoływana z argumentem x. W wyniku wstawienia funkcji, otrzymamy poniższy kod:

1 s r e d n i a <− function(x) 2 { 3 # przekazanie wartości parametrom 4 wektor <− x 5 # wklejenie ciała funkcji 6 l i c z n i k <− { 7 suma <− 0 8 for(element in wektor) 9 { 10 suma <− suma + element 11 } 12 suma 13 } 14 # pozostała część funkcji 15 licznik/length(x) 16 }

W praktyce, aby mieć pewność, że nie pokryją nam się nazwy używanych zmiennych,

50

i i

i i i “output” — 2018/6/21 — 7:43 — page 51 — #51 i i i

zmienne w ciele wstawianej funkcji są zamieniane zgodnie z atakiem zmiana nazw zmien- nych (por. p. 2.6.2).

2.7 Generator zbiorów benchmarkowych

W celu uniezależnienia się od żmudnego i być może obciążonego subiektywnością procesu pozyskiwania i oceniania par funkcji pochodzących z rzeczywistych źródeł np. z prac do- mowych, zaimplementowany został algorytm, który generuje na podstawie danej grupy funkcji zbiór benchmarkowy w którym występuje zadany z góry procent klonów. Możli- wość utworzenia takiego zbioru (lub zbiorów) daje nam wiele korzyści: po pierwsze wiemy dokładnie, które funkcje są wynikiem klonowania. Po drugie możemy kontrolować sze- reg różnych jego parametrów: jego wielkość, procent funkcji będących klonami, a także poziom trudności wykrycia podobieństwa (liczby ataków). Dzięki wygenerowaniu takich dobrze zdefiniowanych zbiorów, możemy lepiej i rzetelniej dostosować parametry różnych metod, a także lepiej oszacować dokładność poszczególnych komponentów systemu. Zdefiniujmy na podstawie jakich funkcji tworzyć takie zbiory. Wybraliśmy funkcje z istniejących, dobrze ugruntowanych, dojrzałych pakietów języka R. Przy wyborze kie- rowaliśmy się statystykami ze strony https://www.r-pkg.org/, która agreguje różne informacje o pakietach, takich jak liczba pobrań, „gwiazdek” na GitHubie czy liczba pa- kietów zależnych. Pakiety, z których wyekstrahowaliśmy funkcje, były pakietami ze stron https://www.r-pkg.org/depended (pakiety, od których zależy najwięcej innych pa- kietów), https://www.r-pkg.org/downloaded (najczęściej ściągane pakiety), https: //www.r-pkg.org/starred (najlepiej oceniane pakiety na GitHubie). Poza tym doda- liśmy funkcje z pakietów bazowych każdej instalacji pakietu R, a także rekomendowane przez autorów R. Takich pakietów było łącznie 233. Ich listę możemy znaleźć w tab. 2.2, 2.3 i 2.4. Z tych pakietów wybraliśmy wszystkie te funkcje, które mają co najmniej 5 wierszy kodu. Było ich 7089. Mając bazę funkcji, na których będą przeprowadzane ataki, należy zdefiniować para- metry generatora. Są to: — n – liczba funkcji w zbiorze wynikowym; — p – wektor prawdopodobieństw ataków, odpowiedzialny za poziom trudności wy- krycia podobieństwa; — r – procent klonów w zbiorze wynikowym. Zbiór wynikowy będzie miał n funkcji. Będzie to (1−r)n oryginalnych, niezmienionych funkcji, a także rn funkcji, które powstały przez ciąg ataków na oryginalne funkcje. Innymi słowy, spośród 7 tysięcy funkcji, losujemy (bez zwracania) pewien podzbiór o liczności (1 − r)n. Następnie, wśród tego podzbioru losujemy funkcje które zostaną sklonowane

51

i i

i i i “output” — 2018/6/21 — 7:43 — page 52 — #52 i i i

(a także liczbę ich klonów w poszczególnych przypadkach), tak, aby uzyskać łącznie rn klonów. Pierwszym pytaniem jest, jak wybierać funkcje, które zostaną poddane plagiatowi i w jakiej liczności. Prostym podejściem jest wylosowanie rn razy indeksu funkcji (z roz- kładu jednostajnego), która zostanie poddana ciągowi ataków. Jednak ciekawszym i bar- dziej oddającym rzeczywistość modelem jest użycie rozkładu Zipfa. Aby uzasadnić użycie rozkładu Zipfa, przywołajmy dołączanie preferencyjne (ang. pre- ferential attachment) [100, 105, 119]. Jest to zjawisko często występujące w naturze, które można potocznie opisać słowami „bogaci stają się jeszcze bogatsi”. Załóżmy, że mamy pewną skończoną liczbę urn. Niech kule reprezentują jednostki pewnego zasobu, zaś urny posiadaczy tegoż. W procesie dołączania preferencyjnego w kolejnych jednostkach czasu pojawiają się nowe kule, które w sposób losowy przydzielane są do urn. To, co charaktery- zuje dołączanie preferencyjne jest fakt, że prawdopodobieństwo przydzielenia kuli do danej urny jest funkcją rosnącą względem liczby już przechowywanych kul w urnie. Procesem tym opisuje się rozmiary miast, bogactwo osób prywatnych, liczbę cytowań w publikacjach naukowych czy liczbę linków do strony internetowych. Dołączanie preferencyjne generuje rozkłady o tzw. ciężkich ogonach, które spełniają potęgowe prawo skalowania (ang. power law). Rozkład Zipfa jest dyskretnym odpowied- nikiem rozkładu Pareto. Funkcja masy prawdopodobieństwa jest dana wzorem:

−s fs(k) = k /ζ(s) k = 1, 2,... gdzie ζ(s) to funkcja zeta Riemanna:

∞ X 1 ζ(s) = s . n=1 n gdzie s > 1 jest parametrem rozkładu. Narzućmy pewną losową kolejność funkcjom ze zbioru, na bazie którego tworzymy zbiór benchmarkowy. Użycie rozkładu Zipfa powoduje, że pewna pierwsza funkcja zo- stanie poddana klonowaniu wielokrotnie, podczas gdy kolejne funkcje w tej kolejności będą poddawane temu procesowi już o wiele rzadziej, w szczególności większość funkcji nie zostanie zaatakowana wcale. Takie podejście wydaje się być bliższe rzeczywistym przy- padkom, kiedy jedno rozwiązanie szczególnie trudnego zadania jest propagowane wśród znacznej części grupy studentów. Po zdefiniowaniu sposobu wyboru funkcji w celu sklonowania, przyjrzyjmy się przetwa- rzaniu pojedynczej funkcji. Przypiszmy każdemu typowi ataku pewne stałe prawdopodo- bieństwo p. Z takim też prawdopodobieństwem każdy z wierzchołków, który jest podatny na dany atak, jest poddawany transformacji, która wyraża dany atak. Wygenerowany zbiór, oprócz kodów źródłowych funkcji, zawiera także informacje

52

i i

i i i “output” — 2018/6/21 — 7:43 — page 53 — #53 i i i

o tym, które z nich są do siebie podobne i w jakim stopniu, aby dało się następnie na ta- kim zbiorze trenować i testować różne metody i modele. Stopień podobieństwa jest liczbą z zakresu [0, 1], gdzie 1 oznacza maksymalne podobieństwo. Podobieństwo jest obliczane w następujący sposób: każdemu z m ataków przypisana

jest pewna arbitralna wartość di, jak bardzo zmienia on funkcję. Zauważmy, co zostanie wyjaśnione poniżej, że same wartości liczbowe nie są tak ważne, jak ich wzajemne stosunki.

Niech liczba wykonanych ataków i-tego typu będzie równa ci, a maksymalna liczba ataków (a) i-tego typu, jaką dało się przeprowadzić na danej funkcji, wynosi ci . Wtedy wartość podobieństwa wynosi:

Pm c d y = 1 − 0,5 i=1 i i . Pm (a) i=1 ci di Idea stojąca za tym wzorem jest następująca: wartość podobieństwa nie powinna wy- nosić mniej niż 0,5. Przyjmujemy z góry, że funkcja, nawet po wielu przekształceniach, nadal jest podobna przynajmniej w 0,5 do pierwowzoru. Wartości poniżej 0,5 powinny być przypisane parom funkcji, gdzie jedna funkcja nie jest klonem drugiej. Drugą kwestią jest sama wartość. Prześledźmy przykład, który zilustruje, jakie kwe- stie powinna brać pod uwagę dobra miara podobieństwa na bazie wykonanych ataków. Rozważmy atak polegający na zamianie jednego typu pętli na drugi. Załóżmy, że do- konamy jednej takiej zamiany. Sama taka informacja nie wystarczy, aby stwierdzić, jak dużym spadkiem podobieństwa powinna być ona odzwierciedlona. Dodatkową informacją powinna być liczba pętli, które mogliśmy w ten sposób zamienić. Jeśli zamieniliśmy jedną pętlę z dwóch, to może sugerować, że dokonano poważnej modyfikacji. Jednak taki spo- sób myślenia może okazać się mylny. Weźmy pod uwagę, że kod funkcji był bardzo długi, a te dwie pętle były zaledwie jego niewielkim fragmentem. W takim wypadku zamiana dwóch pętli nie powinna szczególnie wpłynąć na zmniejszenie miary podobieństwa. Stąd też nie sprawdzi się podejście, w którym zakładamy, że podmiana wszystkich pętli w funk- cji obniża podobieństwo o pewną arbitralną wartość, np. 0,1 (a połowę wszystkich pętli obniża o jej połowę). Dlatego przyjęliśmy założenie, że wszystko zależy od tego, jak liczba możliwych podmian pętli ma się do liczby innych możliwych ataków. Oczywiście pod- miana pętli powinna mieć większy wpływ na obniżenie podobieństwa, niż np. taka sama

liczba podmian nazw zmiennych. Skutkiem tego jest wprowadzenie wartości di. Tabela 2.1

przedstawia przyjęte wartości di. Osobne podejście zastosowano w przypadku, gdy używamy ostatniego ataku, podsta- wienie kodu wywoływanej funkcji. Gdy ciało jednej funkcji zostaje wstawione do drugiej, musimy informację o ich wzajemnym podobieństwie także zapisać w zbiorze. Przyjęliśmy następującą konwencję: miara podobieństwa funkcji wstawianej jest równa 1 względem tej, do której została wstawiona. Analogiczna wartość w odwrotnym kierunku wynosi c vi , va

53

i i

i i i “output” — 2018/6/21 — 7:43 — page 54 — #54 i i i

Tabela 2.1. Arbitralnie przyjęte wartości di

Nazwa ataku di

Zmiana operatora przypisania 0,01 Zmiana nazw zmiennych 0,03 Zmiana nazw funkcji 0,06 Rozwijanie zagnieżdżonych wywołań funkcji 0,08 Zamiana miejscami niezależnych wierszy kodu 0,07 Zamiana jednego typu pętli na drugi 0,10 Przemienność argumentów w operatorach binarnych 0,04 Wykorzystanie tautologii do modyfikacji warunków logicznych 0,05 Dodanie wierszy kodu 0,04 Atak przy użyciu operatora potokowego %>% 0,08 Odwrotny atak przy użyciu użyciem operatora potokowego %>% 0,08 Podstawienie kodu wywoływanej funkcji 0,30

gdzie c to liczba wstawień tej funkcji do drugiej, vi to liczba wierzchołków AST wstawianej

funkcji, a va to liczba wierzchołków AST drugiej funkcji, po wstawieniu do niej pierwszej (być może więcej niż raz). Zauważmy przy okazji, że teoretycznie moglibyśmy dla danej funkcji wygenerować zbiór wszystkich jej klonów, przez zastosowanie opisanych wcześniej ataków we wszel- kich możliwych konfiguracjach. Następnie moglibyśmy sprawdzić, czy druga sprawdzana funkcja znajduje się w takim zbiorze. Jednak takie podejście jest niemożliwe do zrealizo- wania ze względów praktycznych. Dlatego potrzebne są algorytmy, które będą sprawdzać podobieństwo mniej dokładnie, za to przy użyciu skończonych zasobów.

2.8 Zbiory benchmarkowe używane w dalszej części pracy

Zapoznawszy się ze sposobem tworzenia zbiorów benchmarkowych, możemy opisać, jakie dokładnie będą to zbiory, których będziemy używać w dalszej części pracy. Zauważmy, że o ile znaczenie parametrów n i p jest oczywiste, o tyle większej uwagi z naszej strony wymaga r. Wpływa on bezpośrednio na to, jaka liczba funkcji w zbiorze wyjściowym będzie oryginalna, a jaka znajdzie się tam w wyniku zastosowania ataków. Jednak to, co jest bardziej interesujące z naszego punktu widzenia, to nie sama liczba sklonowanych funkcji, ale liczba par funkcji, które są swoimi klonami. Wynika to z tego, że podejmujemy decyzję o podobieństwie dla dwójki, a nie pojedynczej funkcji. Niestety, ich liczba nie wynika bezpośrednio z samego r, ale zależy także od parametru s z rozkładu Zipfa, ponieważ nie tylko ważna jest dla nas liczba sklonowanych funkcji, ale także jak

54

i i

i i i “output” — 2018/6/21 — 7:43 — page 55 — #55 i i i

wiele z nich jest klonami tej samej funkcji (należą więc do tego samego skupienia, w którym to skupieniu każda para funkcji jest swoimi klonami), a o tym decyduje parametr s. Co więcej, dochodzi tutaj czynnik losowy (który ma znaczący wpływ zwłaszcza dla małych zbiorów), dlatego nie można dokładnie przewidzieć, jaka frakcja wszystkich dwójek funkcji w zbiorze będzie parami podobnymi. Dlatego w opisywanych zbiorach testowych, dla każdej trójki (n, p, s) wygenerowaliśmy zbiory o trzech poziomach stosunku par podobnych do wszystkich par w zbiorze. Stosunek ten waha się od 1% do 5%, co odpowiada stosunkowi w prawdziwych zbiorach danych i odzwierciedla niezbalansowanie problemu. Zdecydowaliśmy się na użycie parametrów ze zbiorów n = {50; 200; 500; 1000}, p = {(0,05; ... ; 0,05), (0,1; ... ; 0,1), (0,25; ... ; 0,25)} (wszystkie ataki miały jedno, stałe, z góry zadane prawdopodobieństwo), s = {1; 1,5; 2}, a także odpowiednio dobrane 3 poziomy r. Dla każdego możliwego zestawu parametrów (iloczyn kartezjański) wygenerowaliśmy 10 zbiorów. Tworzenie więcej niż jednego zbioru dla każdego zestawu parametrów ma służyć zmniejszeniu wpływu czynnika losowego. W ten sposób otrzymaliśmy 108 różnych zestawów parametrów, a w konsekwencji 1080 różnych zbiorów. W kolejnych rozdziałach właśnie na tychże zbiorach będziemy testować przedstawiane metody.

55

i i

i i i “output” — 2018/6/21 — 7:43 — page 56 — #56 i i i

Tabela 2.2. Pakiety języka R, których funkcje zostały wykorzystane do generowania zbiorów testowych, cz. 1

Liczba Pakiet Opis Autor użytych funkcji

abind Combine Multidimensional Arrays Tony Plate 1 acepack ACE and AVAS for Selecting Multiple Regression Transformations Shawn Garbett 2 addinslist Discover and Install Useful RStudio Addins Dean Attali 1 ape Analyses of Phylogenetics and Evolution Emmanuel Paradis 293 base64enc Tools for base64 encoding Simon Urbanek 3 bigrquery An Interface to Google’s ’BigQuery’ ’API’ 16 bitops Bitwise Operations Martin Maechler 1 bookdown Authoring Books and Technical Documents with R Markdown 13 boot Bootstrap Functions (Originally by Angelo Canty for S) Brian Ripley 32 broom Convert Statistical Analysis Objects into Tidy Data Frames David Robinson 6 car Companion to Applied Regression John Fox 32 caret Classification and Regression Training Max Kuhn 81 caretEnsemble Ensembles of Caret Models Zachary A. Deane-Mayer 3 caTools Tools: moving window statistics, GIF, Base64, ROC AUC, etc. Harutyun Khachatryan 18 checkmate Fast and Versatile Argument Checks Michel Lang 25 chron Chronological Objects which can Handle Dates and Times Kurt Hornik 12 cluster "Finding Groups in Data": Cluster Analysis Extended Rousseeuw et al. Martin Maechler 13 coda Output Analysis and Diagnostics for MCMC Martyn Plummer 26 colorspace Color Space Manipulation Achim Zeileis 20 corpcor Efficient Estimation of Covariance and (Partial) Correlation Korbinian Strimmer 20 covr Test Coverage for Packages Jim Hester 11 cowplot Streamlined Plot Theme and Plot Annotations for ’’ Claus O. Wilke 8 curl A Modern and Flexible Web Client for R Jeroen Ooms 16 d3heatmap Interactive Heat Maps Using ’htmlwidgets’ and ’D3.js’ Joe Cheng 1 d3Network Tools for creating D3 JavaScript network, tree, dendrogram, and Sankey Christopher Gandrud 7 graphs from R data.table Extension of ‘data.frame‘ Matt Dowle 36 DBI R Database Interface Kirill Müller 6 devtools Tools to Make Developing R Packages Easier Hadley Wickham 85 DiagrammeR Create Graph Diagrams and Flowcharts Using R Richard Iannone 153 dichromat Color Schemes for Dichromats Thomas Lumley 1 digest Create Compact Hash Digests of R Objects 3 doParallel Foreach Parallel Adaptor for the ’parallel’ Package Rich Calaway 1 dplyr A Grammar of Data Manipulation Hadley Wickham 46 DT A Wrapper of the JavaScript Library ’DataTables’ Yihui Xie 10 dygraphs Interface to ’Dygraphs’ Interactive Time Series Charting Library JJ Allaire 19 e1071 Misc Functions of the Department of Statistics, Probability Theory David Meyer 37 Group (Formerly: E1071), TU Wien evaluate Parsing and Evaluation Tools that Provide More Details than the De- Yihui Xie 4 fault fields Tools for Spatial Data Douglas Nychka 185 flexdashboard R Markdown Format for Flexible Dashboards JJ Allaire 3 forcats Tools for Working with Categorical Variables (Factors) Hadley Wickham 15 foreach Provides Foreach Looping Construct for R Rich Calaway 2 forecast Forecasting Functions for Time Series and Linear Models Rob Hyndman 78 foreign Read Data Stored by Minitab, S, SAS, SPSS, Stata, Systat, Weka, R Core Team 13 dBase, ... formatR Format R Code Automatically Yihui Xie 2 formattable Create ’Formattable’ Data Structures Kun Ren 3 future Unified Parallel and Distributed Processing in R for Everyone Henrik Bengtsson 36 gdata Various R Programming Tools for Data Manipulation Gregory R. Warnes 28 ggalt Extra Coordinate Systems, ’Geoms’, Statistical Transformations, Scales Bob Rudis 5 and Fonts for ’ggplot2’ ggfortify Data Visualization Tools for Statistical Analysis Results Masaaki Horikoshi 8 ggmap Spatial Visualization with ggplot2 David Kahle 25 ggplot2 Create Elegant Data Visualisations Using the Grammar of Graphics Hadley Wickham 45 ggthemes Extra Themes, Scales and Geoms for ’ggplot2’ Jeffrey B. Arnold 13 git2r Provides Access to Git Repositories Stefan Widgren 2 glmnet Lasso and Elastic-Net Regularized Generalized Linear Models Trevor Hastie 32 googlesheets Manage Google Spreadsheets from R Jennifer Bryan 34 googleVis R Interface to Google Charts Markus Gesmann 19 gplots Various R Programming Tools for Plotting Data Gregory R. Warnes 16 gridExtra Miscellaneous Functions for "Grid"Graphics Baptiste Auguie 7 gtable Arrange ’Grobs’ in Tables Hadley Wickham 12 gtools Various R Programming Tools Gregory R. Warnes 18 h2o R Interface for H2O Tom Kraljevic 98 haven Import and Export ’SPSS’, ’Stata’ and ’SAS’ Files Hadley Wickham 3 highcharter A Wrapper for the ’Highcharts’ Library Joshua Kunst 55 highr Syntax Highlighting for R Source Code Yihui Xie 2 Hmisc Harrell Miscellaneous Frank E Harrell Jr 416 hms Pretty Time of Day Kirill Müller 1

56

i i

i i i “output” — 2018/6/21 — 7:43 — page 57 — #57 i i i

Tabela 2.3. Pakiety języka R, których funkcje zostały wykorzystane do generowania zbiorów testowych, cz. 2

Liczba Pakiet Opis Autor użytych funkcji

hrbrthemes Additional Themes, Theme Components and Utilities for ’ggplot2’ Bob Rudis 3 htmlTable Advanced Tables for Markdown/HTML Max Gordon 5 htmltools Tools for HTML Joe Cheng 22 htmlwidgets HTML Widgets for R JJ Allaire 6 httpuv HTTP and WebSocket Server Library Joe Cheng 3 httr Tools for Working with URLs and HTTP Hadley Wickham 29 igraph Network Analysis and Visualization Gabor Csardi 521 iterators Provides Iterator Construct for R Rich Calaway 11 janitor Simple Tools for Examining and Cleaning Dirty Data Sam Firke 7 jiebaR Chinese Text Segmentation Qin Wenfeng 16 jsonlite A Robust, High Performance JSON Parser and Generator for R Jeroen Ooms 5 KernSmooth Functions for Kernel Smoothing Supporting Wand & Jones (1995) Brian Ripley 7 A General-Purpose Package for Dynamic Report Generation in R Yihui Xie 55 knitrBootstrap Knitr Bootstrap Framework Jim Hester 4 labeling Axis Labeling Justin Talbot 9 largeVis High-Quality Visualizations of Large, High-Dimensional Datasets Amos Elberg 12 lattice Trellis Graphics for R Deepayan Sarkar 72 latticeExtra Extra Graphical Utilities Based on Lattice Deepayan Sarkar 44 lazyeval Lazy (Non-Standard) Evaluation Hadley Wickham 6 LDAvis Interactive Visualization of Topic Models Carson Sievert 4 leaflet Create Interactive Web Maps with the JavaScript ’Leaflet’ Library Joe Cheng 27 lintr Static R Code Analysis Jim Hester 11 lme4 Linear Mixed-Effects Models using ’Eigen’ and S4 Ben Bolker 44 lmtest Testing Linear Regression Models Achim Zeileis 18 lubridate Make Dealing with Dates a Little Easier Vitalie Spinu 38 magrittr A Forward-Pipe Operator for R Stefan Milton Bache 6 maps Draw Geographical Maps Alex Deckmyn 14 maptools Tools for Reading and Handling Spatial Objects 63 markdown ’Markdown’ Rendering for R Yihui Xie 5 MASS Support Functions and Datasets for Venables and Ripley’s MASS Brian Ripley 39 Matrix Sparse and Dense Matrix Classes and Methods Martin Maechler 40 MatrixModels Modelling with Sparse And Dense Matrices Martin Maechler 2 mclust Gaussian Mixture Modelling for Model-Based Clustering, Classification, Luca Scrucca 189 and Density Estimation memoise Memoisation of Functions Jim Hester 4 Metrics Evaluation metrics for machine learning Ben Hamner 7 mgcv Mixed GAM Computation Vehicle with GCV/AIC/REML Smoothness Simon Wood 129 Estimation mime Map Filenames to MIME Types Yihui Xie 2 minqa Derivative-free optimization algorithms by quadratic approximation Katharine M. Mullen 3 mlr Machine Learning in R Bernd Bischl 139 modelr Modelling Functions that Work with the Pipe Hadley Wickham 10 munsell Utilities for Using Munsell Colours Charlotte Wickham 14 mvtnorm Multivariate Normal and t Distributions Torsten Hothorn 7 networkD3 D3 JavaScript Network Graphs from R Christopher Gandrud 10 nlme Linear and Nonlinear Mixed Effects Models R-core 48 nloptr R interface to NLopt Jelmer Ypma 23 nnet Feed-Forward Neural Networks and Multinomial Log-Linear Models Brian Ripley 5 opencpu Embedded Scientific Computing and Reproducible Research with R Jeroen Ooms 1 openssl Toolkit for Encryption, Signatures and Certificates Based on OpenSSL Jeroen Ooms 20 openxlsx Read, Write and Edit XLSX Files Alexander Walker 50 packrat A Dependency Management System for Projects and their R Package Kevin Ushey 18 Dependencies pander An R Pandoc Writer Gergely Daróczi 14 pandocfilters Pandoc Filters for R Florian Schwendinger 5 pbkrtest Parametric Bootstrap and Kenward Roger Based Methods for Mixed Søren Højsgaard 30 Model Comparison plotly Create Interactive Web Graphics via ’plotly.js’ Carson Sievert 26 plotrix Various Plotting Functions Jim Lemon 129 plyr Tools for Splitting, Applying and Combining Data Hadley Wickham 33 ProjectTemplate Automates the Creation of New Statistical Analysis Projects Kenton White 6 psych Procedures for Psychological, Psychometric, and Personality Research William Revelle 276 purrr Functional Programming Tools Hadley Wickham 17 quadprog Functions to solve Quadratic Programming Problems. Berwin A. Turlach 2 quanteda Quantitative Analysis of Textual Data Kenneth Benoit 29 quantmod Quantitative Financial Modelling Framework Joshua M. Ulrich 115 quantreg Quantile Regression Roger Koenker 100 R6 Classes with Reference Semantics Winston Chang 1 randomForest Breiman and Cutler’s Random Forests for Classification and Regression Andy Liaw 8 ranger A Fast Implementation of Random Forests Marvin N. Wright 4

57

i i

i i i “output” — 2018/6/21 — 7:43 — page 58 — #58 i i i

Tabela 2.4. Pakiety języka R, których funkcje zostały wykorzystane do generowania zbiorów testowych, cz. 3

Liczba Pakiet Opis Autor użytych funkcji

raster Geographic Data Analysis and Modeling Robert J. Hijmans 83 rbokeh R Interface for Bokeh Ryan Hafen 51 RColorBrewer ColorBrewer Palettes Erich Neuwirth 3 Rcpp Seamless R and C++ Integration Dirk Eddelbuettel 14 RcppArmadillo ’Rcpp’ Integration for the ’Armadillo’ Templated Linear Algebra Li- Dirk Eddelbuettel 1 brary RcppEigen ’Rcpp’ Integration for the ’Eigen’ Templated Linear Algebra Library Dirk Eddelbuettel 1 RCurl General Network (HTTP/FTP/...) Client Interface for R Duncan Temple Lang 25 readr Read Tabular Data Hadley Wickham 15 ReporteRs Microsoft Word and PowerPoint Documents Generation David Gohel 36 reprex Prepare Reproducible Example Code for Sharing Jennifer Bryan 2 reshape Flexibly Reshape Data Hadley Wickham 33 reshape2 Flexibly Reshape Data: A Reboot of the Reshape Package Hadley Wickham 4 Rfacebook Access to Facebook API via R Pablo Barbera 20 rgeos Interface to Geometry Engine - Open Source (GEOS) Roger Bivand 38 rgl 3D Visualization Using OpenGL Duncan Murdoch 110 RGoogleAnalytics R Wrapper for the Google Analytics API Kushan Shah 3 rio A Swiss-Army Knife for Data I/O Thomas J. Leeper 3 rJava Low-Level R to Java Interface Simon Urbanek 2 rjson JSON for R Alex Couture-Beil 2 rmarkdown Dynamic Documents for R JJ Allaire 36 rmdformats HTML Output Formats and Templates for ’rmarkdown’ Documents Julien Barnier 6 RMySQL Database Interface and ’MySQL’ Driver for R Jeroen Ooms 1 robustbase Basic Robust Statistics Martin Maechler 41 roxygen2 In-Line Documentation for R Hadley Wickham 6 rpart Recursive Partitioning and Regression Trees Brian Ripley 11 rplos Interface to the Search ’API’ for ’PLoS’ Journals Scott Chamberlain 15 rprojroot Finding Files in Project Subdirectories Kirill Müller 6 R.rsp Dynamic Generation of Scientific Reports Henrik Bengtsson 21 RSQLite ’SQLite’ Interface for R Kirill Müller 3 rstan R Interface to Stan Ben Goodrich 28 rstudioapi Safely Access the RStudio API JJ Allaire 1 RUnit R Unit Test Framework Roman Zenka 16 rvest Easily Harvest (Scrape) Web Pages Hadley Wickham 6 scales Scale Functions for Visualization Hadley Wickham 14 shiny Web Application Framework for R Winston Chang 72 shinydashboard Create Dashboards with ’Shiny’ Winston Chang 11 shinyjs Easily Improve the User Experience of Your Shiny Apps in Seconds Dean Attali 9 sp Classes and Methods for Spatial Data Edzer Pebesma 69 sparklyr R Interface to Apache Spark Javier Luraschi 57 SparseM Sparse Linear Algebra Roger Koenker 16 sqldf Perform SQL Selects on R Data Frames G. Grothendieck 2 stringi Character String Processing Facilities Marek Gagolewski 14 stringr Simple, Consistent Wrappers for Common String Operations Hadley Wickham 7 survival Survival Analysis Terry M Therneau 44 syuzhet Extracts Sentiment and Sentiment-Derived Plot Arcs from Text Matthew Jockers 9 testthat Unit Testing for R Hadley Wickham 31 text2vec Modern Text Mining Framework for R Dmitriy Selivanov 19 tibble Simple Data Frames Kirill Müller 9 tidyr Easily Tidy Data with ’spread()’ and ’gather()’ Functions Hadley Wickham 4 tidytext Text Mining using ’dplyr’, ’ggplot2’, and Other Tidy Tools Julia Silge 5 Easily Install and Load ’Tidyverse’ Packages Hadley Wickham 4 timevis Create Interactive Timeline Visualizations in R Dean Attali 2 tm Text Mining Package Ingo Feinerer 26 tweenr Interpolate Data for Smooth Animations Thomas Lin Pedersen 15 vegan Community Ecology Package Jari Oksanen 159 viridis Default Color Maps from ’matplotlib’ Simon Garnier 1 waffle Create Waffle Chart Visualizations in R Bob Rudis 1 wesanderson A Wes Anderson Palette Generator Karthik Ram 1 whisker mustache for R, logicless templating Edwin de Jonge 1 withr Run Code ’With’ Temporarily Modified Global State Jim Hester 2 wordcloud2 Create Word Cloud by htmlWidget Dawei Lang 2 xaringan Presentation Ninja Yihui Xie 3 xgboost Extreme Gradient Boosting Tong He 27 XML Tools for Parsing and Generating XML Within R and S-Plus Duncan Temple Lang 53 xml2 Parse XML James Hester 1 xtable Export Tables to LaTeX or HTML David Scott 10 xts eXtensible Time Series Jeffrey A. Ryan 27 zoo S3 Infrastructure for Regular and Irregular Time Series (Z’s Ordered Achim Zeileis 20 Observations)

58

i i

i i i “output” — 2018/6/21 — 7:43 — page 59 — #59 i i i

Metody porównywania par funkcji 3

W poprzednim rozdziale sprecyzowaliśmy operacyjną definicję podobieństwa kodu, opi- saliśmy sposoby jego przekształcania, a także sposób generowania zbiorów benchmarko- wych, które pozwalają w rzetelny sposób przetestować metody wykrywania podobieństwa. Ten rozdział jest poświęcony algorytmom, które pozwalają na porównanie pary funkcji pod tym kątem. W szczególności zostanie zaproponowane nowe podejście, o nazwie Simi- laR, które okazało się być najskuteczniejsze. Do tej pory w literaturze zaproponowano kilka sposobów porównywania dwóch frag- mentów kodu, por. tab. 1.1. Pracami, które dokonują obszernego przeglądu tego typu algorytmów są [3, 54, 92, 110, 113]. Podział, jaki tutaj prezentujemy, opiera się głównie na [110] oraz [113] i obejmuje podejścia:

— tekstowe – w tych algorytmach praktycznie wcale nie przekształca się kodu źródło- wego, ale działa się bezpośrednio na jego tekście. Typowym sposobem jest użycie funkcji mieszającej (innymi nazwami są funkcja skrótu czy haszująca, ang. hash function), która przekształci w skrót dany fragment kodu (np. pojedynczy wiersz), a następnie nastąpi porównanie takich wartości funkcji haszującej. Taki sposób jest używany w [68, 88, 111]. Innym sposobem jest użycie Latent Semantic Analysis (LSI). W pracy [90] ta technika jest używana na tekście komentarzy i nazw zmien- nych. — leksykalne (zwane także bazującym na tokenach) – tutaj kod źródłowy jest prze- kształcany na tokeny (więcej o tokenach piszemy w p. 3.1.1), czyli ciąg elementów powstałych w wyniku analizy leksykalnej kompilatora. Są one czymś ogólniejszym od liter i powszechnie uważa się, że są odporniejsze od podejść tekstowych na niewiel- kie zmiany kodu takie jak formatowanie, spacje, zmiana nazw identyfikatorów. Na- stępnie następuje porównanie dwóch ciągów tokenów, aby odnaleźć części wspólne. Ten sposób jest używany w [66, 71, 81, 107, 116, 124, 129]. — syntaktyczne – w tym przypadku proponowane metody opierają się na opisanym w podrozdz. 2.5 drzewie syntaktycznym (AST). Dzielą się one na dwie rodziny:

i i

i i i “output” — 2018/6/21 — 7:43 — page 60 — #60 i i i

dopasywowanie drzew bezpośrednio, a także używanie różnych metryk. W pierw- szym przypadku klony poszukiwane są poprzez znajdowanie podobnych poddrzew. Jedną z pierwszych prac w tym zakresie jest [13], gdzie poddrzewa są najpierw prze- kształcane funkcją skrótu do pojedynczej wartości, aby przyporządkować je do tzw. kubełków (ang. bucket), a następnie jedynie drzewa, które trafiają do tego samego kubełka są porównywane w sposób dokładny. W [127] AST są konwertowane do pli- ków XML, a następnie używane są techniki eksploracji danych (ang. data mining) w celu odnalezienia klonów. Różnice i zmiany na dowolnym poziomie poddrzewa, nie tylko w liściach, są dopuszczalne w [40], zaś w podejściu opisanym w [41] wy- korzystywane jest drzewo sufiksowe. W [67] pewne wektory charakteryzujące AST są konstruowane, a następnie dokonuje się analizy skupień tych wektorów w prze- strzeni euklidesowej. W sposobie używającym metryk nie porównuje się drzew bezpośrednio, ale oblicza się pewną liczbę metryk (statystyk dotyczących kodu, np. liczba unikatowych ope- ratorów), a następnie porównuje ich wektory. Zazwyczaj kod dzielony jest na pewne jednostki syntaktyczne, takie jak klasy, funkcje czy metody. W [95] oblicza się kilka metryk bazujących na nazwach, rozkładzie, wyrażeniach i prostym przebiegu kon- troli w funkcji. Bardzo podobne podejście dla Javy jest opisane w [104]. Praca [74] używa dwóch sposobów na wykrywanie klonów, gdzie jeden z nich opiera się na ob- liczaniu pięciu metryk, które oddają przepływ kontroli oraz danych. — semantyczne – w tej rodzinie sposobów wykrywania klonów używana jest statyczna analiza programu, aby uzyskać bardziej precyzyjne informacje niż przy zwykłym podobieństwie syntaktycznym. Zazwyczaj tworzone jest PDG (opisywane dokład- niej w p. 3.1.2), w którym następuje odseparowanie od kolejności wyrażeń w ko- dzie na tyle, na ile są one od siebie semantycznie niezależne. Problem znajdowania klonów jest zamieniony na znajdowanie izomorficznych podgrafów. Różne sposoby są stosowane w literaturze, aby znaleźć podobne podgrafy, jak program slicing [72], podejście iteracyjne [77], czy używanie algorytmu VF2 przez [83, 108]. Przy okazji zaznaczmy, że AST jest podzbiorem PDG, nieposiadającym krawędzi dotyczących przepływu danych.

Powyższe zestawienie pokazuje, że istnieje pewna liczba zaproponowanych różnych metod porównywania dwóch fragmentów kodu źródłowego. Jedne metody są szybsze, ale koncentrują się na wykrywaniu zaledwie klonów 1 i 2 typu (zmienione co najwyżej identyfikatory zmiennych, por. podrozdz. 2.2), podczas gdy inne są wolniejsze, ale pozwa- lają na dokładniejsze sprawdzenie pary i wykrycie bardziej złożonych modyfikacji. Jak się jednak okaże, żadna z metod nie daje zawsze idealnych wyników, a wynika to z faktu, że każda z nich koncentruje się na innych aspektach kodu źródłowego.

60

i i

i i i “output” — 2018/6/21 — 7:43 — page 61 — #61 i i i

Ze względu na istniejące bogactwo różnych algorytmów w literaturze w naszej pracy zdecydowaliśmy się na podejście hybrydowe: porównać parę funkcji poprzez zastosowanie wielu różnych metod, a następnie dokonać odpowiedniej agregacji ich wyników, aby otrzy- mać pojedynczy werdykt. Rozdział 4 w całości jest poświęcony metodom podejmowania pojedynczej decyzji na podstawie wyników wielu algorytmów, za to w tym rozdziale sku- piamy się na dokładnym opisie użytych technik porównywania dwóch funkcji. Podstawowe wersje metod użyte przez nas w większości zostały zaproponowane wcześniej w literatu- rze, jednak my dostosowywaliśmy je do specyfiki języka R. Wyjątkiem jest proponowany autorski algorytm SimilaR, który był od początku projektowany pod kątem tego języka. Co więcej, dokonaliśmy nowatorskiego uogólnienia, które pozwoliło oddzielić prze- kształcone kody źródłowe od algorytmów ich porównywania i stosowania ich wymien- nie. Uogólnienie polega na obserwacji, że kod źródłowy prawie zawsze jest przekształ- cany na postać jakiegoś obiektu matematycznego reprezentującego dany kod: może to być ciąg tokenów, wywołań funkcji, drzewo syntaktyczne czy graf zależności programu (wszystkie opisane w podrozdz. 3.1). Następnie dwa takie obiekty są ze sobą porówny- wane przy użyciu jakiegoś algorytmu. Okazuje się, że często możemy użyć tego samego algorytmu, aby porównywać obiekty różnego typu, np. możemy użyć odległości Levenshte- ina (p. 3.3.1), aby porównać ciąg znaków Unicode dwóch kodów źródłowych, ale możemy jej użyć także aby porównać dwa ciągi tokenów. I na odwrót: jedna sekwencja, np. toke- nów, może być porównana zarówno przy użyciu odległości Levenshteina, jak i algorytmu Smitha–Watermana (p. 3.3.1). Opis możliwych sposobów reprezentacji funkcji w języku R znajduje się w podrozdz. 3.1, a zastosowane algorytmy porównujące zostały przybliżone w podrozdz. 3.3. Ostatnią innowacyjną częścią badań opisanych w tym rozdziale jest podejście do po- dobieństwa nie tylko jako miary symetrycznej, ale także jako do zawierania się (które nie jest symetryczne), o czym piszemy więcej w podrozdz. 3.2.

3.1 Sposoby reprezentacji funkcji w języku R

Zbiór funkcji, wśród których znajdujemy podobne do siebie pary oznaczamy przez F = S∞ k {f1, f2, . . . , fn}. fi to kod źródłowy (napis) funkcji, tzn. fi ∈ k=1 Σ , gdzie Σ to zbiór znaków Unicode. Gdy otrzymujemy zbiór funkcji do sprawdzenia, pierwszą czynnością, jaką wyko- nujemy, to ujednolicenie formatowania kodu. W języku R należy podjąć w tym celu następujące czynności: jeśli f reprezentuje kod funkcji, to operacje takie, jak usunię- cie komentarzy, zbędnych białych znaków, wprowadzenie jednoznaczności przy użyciu znaków nowej linii i wcięciu klamerek uzyskamy, wywołując wbudowane funkcje: f <- deparse(parse(text=f)).

61

i i

i i i “output” — 2018/6/21 — 7:43 — page 62 — #62 i i i

Przykładowo, niech będzie dana funkcja:

1 suma <− function(x, minus=FALSE) { # funkcja sumuje elementy w x, opcjonalnie przemnażając wynik przez −1 2 l<− length(x) # długość wektora 3 s=0 # zmienna, w której sumujemy 4 for (i in 1:l) 5 { 6 s=s+x [ i ] 7 }

8

9 if(minus) {return(−s ) } 10 e l s e 11 s }

Po przekształceniu w celu ujednolicenia formatowania kodu otrzymamy:

1 suma <− function(x, minus = FALSE) { 2 l <− length ( x ) 3 s = 0 4 for (i in 1:l) { 5 s = s + x [ i ] 6 } 7 i f ( minus ) { 8 return (−s ) 9 } 10 e l s e s 11 }

Zwróćmy uwagę na usunięte komentarze, jednolitą liczbę spacji we wcięciach, umiejsco- wienie nawiasów klamrowych otwierających i zamykających, a także spacje pomiędzy operatorami infiksowymi (dodawanie, operator przypisania). Postaci wejściowe użyte w naszym systemie są dwojakiego typu: pierwszym są postaci sekwencyjne, które wymiennie możemy porównywać pewną rodziną algorytmów dla tego typu danych, podczas gdy drugim typem jest graf i dla niego inne typy algorytmów porównujących mają swoje zastosowanie.

62

i i

i i i “output” — 2018/6/21 — 7:43 — page 63 — #63 i i i

3.1.1 Sekwencyjne sposoby reprezentacji

Litery. Litery są najprostszą z możliwych postaci. Kod źródłowy funkcji podlega tylko prostym przekształceniom opisanym powyżej, takim jak ujednolicone wcięcia oraz usunięcie komentarzy. Zbiór ciągów liter, które mogą powstać ze zbioru funkcji F, ozna- czamy L(F).

Tokeny. Tokeny są popularną metodą uogólniania kodu źródłowego. Ich główną zaletą jest odseparowanie się od nazw identyfikatorów. Co więcej, każdy token jest pojedynczym elementem sekwencji, na której operujemy. Przykładowo, ciąg tokenów uzyskanych dla funkcji:

1 f <− function(x) 2 { 3 stopifnot(is .numeric(x)) 4 y <− sum( x ) 5 y 6 }

to:

1 expr , SYMBOL, expr , LEFT_ASSIGN, expr , FUNCTION, ’ ( ’ , SYMBOL_FORMALS, ’ ) ’ , expr , 2 ’{’, 3 expr , SYMBOL_FUNCTION_CALL, expr , ’ ( ’ , expr , SYMBOL_FUNCTION_CALL, expr, ’(’, SYMBOL, expr, ’)’, ’)’, 4 expr , SYMBOL, expr , LEFT_ASSIGN, expr , SYMBOL_FUNCTION_CALL, expr , ’ ( ’ , SYMBOL, expr , ’ ) ’ , 5 SYMBOL, expr , 6 ’}’

W języku R uzyskanie ciągu tokenów jest trywialne dzięki wywołaniu funkcji wbudo- wanych: getParseData(parse(text=as.character(as.expression(code))))$token, gdzie code to sparsowane drzewo wyrażeń uzyskane z kodu funkcji przy użyciu funk- cji parse. Zestawienie tokenów występujących w języku R znajduje się w tab. 3.1. Zbiór ciągów tokenów, które mogą powstać ze zbioru funkcji F, oznaczamy T(F).

Wywołania funkcji. Przy kolejnym sposobie reprezentacji zdecydowaliśmy się oprzeć na następującej obserwacji: jedną ze specyficznych cech języka R jest istnienie central- nego repozytorium CRAN, które zawiera wszystkie pakiety, jakich użytkownik może użyć.

63

i i

i i i “output” — 2018/6/21 — 7:43 — page 64 — #64 i i i

Tabela 3.1. Tokeny w języku R

Token Opis

- odejmowanie lub unarny minus ! operator „!” $ ekstrakcja składowych nazwanych list ( okrągły nawias otwierający ) okrągły nawias zamykający * mnożenie , przecinek / dzielenie : dwukropek, stosowany przy tworzeniu wektorów [ kwadratowy nawias otwierający, używany przy indeksowaniu wektorów ] kwadratowy nawias zamykający, używany przy indeksowaniu wektorów ^ potęgowanie, ten sam token także dla użytego „**” w kodzie { nawias klamrowy otwierający } nawias klamrowy zamykający + dodawanie AND operator „&” AND2 operator „&&” BREAK słowo kluczowe break, używane w pętlach ELSE słowo kluczowe else, używane w instrukcji warunkowej EQ operator „==” EQ_ASSIGN operator przypisania „=” EQ_FORMALS operator = używany w definicji wartości domyślnej parametru funkcji EQ_SUB operator = używany podczas przekazywania argumentu przez nazwę w wywołaniu expr sztucznie wprowadzony token oznaczający, że następne tokeny tworzą wyrażenie FOR słowo kluczowe for, używane w definicji pętli for forcond początek warunku w pętli for FUNCTION słowo kluczowe function, używane przy definiowaniu funkcji GE operator „>=” GT operator „>” IF słowo kluczowe if, używane w instrukcji warunkowej IN słowo kluczowe in, używane w pętli for LBB operator [[, używany do wyboru elementu w liście LE operator „<=” LEFT_ASSIGN operator przypisania „<-” LT operator „<” NE operator „!=” NEXT słowo kluczowe next, używane w pętlach NS_GET operator „::” NUM_CONST numeryczna wartość stała, np. 3, ale też TRUE OR operator „|” OR2 operator „||” REPEAT słowo kluczowe repeat, używane w definicji pętli repeat SPECIAL operator infiksowy zawarty w dwóch znakach procent, np. %in% STR_CONST stała napisowa, np. „napis” SYMBOL identyfikator zmiennej SYMBOL_FORMALS symbol oznaczający parametr funkcji (w jej definicji) SYMBOL_FUNCTION_CALL wywołanie funkcji SYMBOL_PACKAGE symbol oznaczający pakiet SYMBOL_SUB nazwa argumentu przekazywanego przez nazwę w wywołaniu WHILE słowo kluczowe while, używane w definicji pętli while

64

i i

i i i “output” — 2018/6/21 — 7:43 — page 65 — #65 i i i

Tak więc dwie osoby, rozwiązujące podobny problem, będą używać podobnych pakietów z tego samego repozytorium. Dlatego uznaliśmy, że cechą, którą można wykorzystać przy porównywaniu dwóch fragmentów kodu, będą nazwy wywoływanych funkcji. Wobec tego jeśli jedna funkcja używa funkcji operujących na tekście, podczas gdy druga wywołuje funkcje dotyczące algebry liniowej, to prawdopodobnie nie są one do siebie podobne. Jednak jeśli obie używają funkcji z tej samej rodziny, istnieje szansa, że są do siebie podobne. Dla wspomnianego wyżej kodu uzyskamy następujący ciąg:

1 function, {, stopifnot , is.numeric, <− , sum

Oczywiście warto zauważyć, że różne kody mogą zostać sprowadzone do tego samego ciągu. Zbiór ciągów nazw wywołań funkcji, które mogą powstać ze zbioru funkcji F, oznaczamy C(F).

3.1.2 Graf zależności programu

Graf zależności programu (ang. Program Dependence Graph, PDG) jest grafem zależności między konkretnymi instrukcjami w funkcji R. Jest to najbardziej zaawansowana struk- tura danych (przynajmniej w kontekście wykrywania podobieństwa funkcji), do jakiej można przekształcić kod źródłowy funkcji. Pierwszy raz wprowadzono PDG w pracy [42]. Pracami reprezentatywnymi, związanymi z wykrywaniem podobnych funkcji przy użyciu PDG, są [43, 63, 72, 77, 83, 108]. PDG jest grafem, w którym wierzchołkami są pojedyncze instrukcje (np. przypisanie, wywołanie funkcji, początek pętli). Każdy wierzchołek ma przyporządkowany kolor, który definiuje typ reprezentowanej przez niego instrukcji (będziemy mówić wymiennie „kolor” lub „typ” wierzchołka). Wszystkie typy wierzchołków zostały zilustrowane w tab. 3.2. Warto zauważyć, że duża różnorodność typów wierzchołków ma ogromne znaczenie przy późniejszym porównywaniu grafów, co potwierdziły wstępne eksperymenty. W PDG występują dwa typy krawędzi: zależności sterowania (ang. control dependency) i zależności danych (ang. data dependency). Pierwsze mówią o strukturze pętli i instrukcji warunkowych. Podgraf PDG, w którym występują wszystkie wierzchołki i tylko krawędzie zależności sterowania, nazywamy podgrafem zależności sterowania (ang. Control Depen- dence Subgraph, CDS). Drugi typ krawędzi mówi nam o zależnościach w danych między

instrukcjami. Krawędź zależności danych od wierzchołka vi do wierzchołka vj oznacza,

że zmienna, do której przypisano jakąś wartość w vi, jest używana w wyrażeniu repre-

zentowanym przez vj. Podgraf PDG, w którym występują wszystkie wierzchołki i tylko

65

i i

i i i “output” — 2018/6/21 — 7:43 — page 66 — #66 i i i

krawędzie zależności danych, nazywamy podgrafem zależności danych (ang. Data Depen- dence Subgraph, DDS). Tworzenie grafu na podstawie drzewa wyrażeń opisano w [58]. Przykładowe kody źró- dłowe funkcji wraz z odpowiadającymi im grafami zamieszczono na rys. 3.1 i 3.2. My bardziej skupimy się na zmianach, które wprowadziliśmy, aby PDG lepiej oddawał pewne cechy specyficzne dla języka R. Tak jak wspomnieliśmy w p. 2.6.4, charakterystyczną cechą języka R jest rozwijanie i zagnieżdżanie jednych wywołań funkcji w innych. Postanowiliśmy, że niezależnie od tego, czy w kodzie zostanie użyta pomocnicza zmienna przechowująca wynik wywołania funk- cji, czy też nie, zawsze otrzymamy taki sam PDG. Dlatego każde zagnieżdżone wywo- łanie traktujemy tak, jakby zostało wywołane wcześniej, a jego wynik został przypisany do zmiennej. Kolejną modyfikacją było wykrywanie funkcji typu apply. Są to funkcje, które działają podobnie do pętli for: jako argumenty przyjmują wektor lub macierz, a także funkcję, którą następnie wykonują na każdym elemencie wspomnianego wektora lub macierzy. Dlatego postanowiliśmy nie rozróżniać ich od zwyczajnych pętli for lub while i traktować je w ten sam sposób. W związku z tym ciało funkcji, która ma się wykonać dla każdego elementu wektora, traktujemy jako ciało pętli. Podczas tworzenia wierzchołków instrukcji warunkowych lub pętli zwracamy uwagę na to, że warunek (w if lub while) lub wyrażenie obliczające wektor, po którym będziemy iterować (for), może być wywołaniem funkcji (być może zagnieżdżonych). Wierzchołki odpowiadające tym wywołaniom są dodawane na tym samym poziomie głębokości w CDS, co rozważana instrukcja warunkowa lub pętla. Dość częstą praktyką w języku R jest używanie funkcji stopifnot. Ponieważ nie można zadeklarować typów danych parametrów, jakie przyjmuje funkcja, używa się konstrukcji stopifnot, aby na początku funkcji sprawdzić, czy otrzymaliśmy dane spełniające nasze oczekiwania. Sama funkcja stopifnot przyjmuje dowolną liczbę argumentów, a następnie sprawdza, czy każdy z nich po obliczeniu jest wartością TRUE. Następujące trzy sposoby zapisu oznaczają dokładnie to samo:

1 stopifnot(is.numeric(x), length(x) > k)

1 stopifnot(is.numeric(x) && length(x) > k)

66

i i

i i i “output” — 2018/6/21 — 7:43 — page 67 — #67 i i i

1 stopifnot(is .numeric(x)) 2 stopifnot(length(x) > k)

W naszej implementacji wykrywamy te przypadki i wszystkie sprowadzamy do trzeciej z wymienionych postaci (szereg wywołań stopifnot z jednym argumentem). W naszej implementacji jesteśmy świadomi, że ostatnia instrukcja funkcji jest rów- noważna wartości zwracanej explicite przy użyciu funkcji return. Dlatego, przykładowo, x (będące ostatnią instrukcją) oraz return(x) skutkuje wygenerowaniem tego samego wierzchołka typu return. Aby zapobiec instrukcjom typu identyfikator2 <- identyfikator1, które mają na celu jedynie zmianę nazwy wywoływanej dalej funkcji lub używanej zmiennej, zabiegi takie są wykrywane i nie mają swego bezpośredniego odwzorowania w wynikowym grafie. Zamiast tego używany jest słownik, który pamięta takie „aliasy”. Gdy następnie używany jest identyfikator2, w naszej implementacji korzystamy z identyfikator1, co pozwala prawidłowo wygenerować krawędzie zależności danych. Aby nie pozwolić na wprowadzenie w błąd algorytmów porównywania grafów przez wprowadzenie instrukcji, które nie mają żadnego wpływu na wynik, tzw. martwego kodu, wszelkie tego typu instrukcje są wykrywane, a odpowiadające im wierzchołki usuwane z ostatecznego grafu. Odbywa się to przez usunięcie tych wierzchołków, których stopień wyjściowy jest równy zero. Oznacza to, że odpowiadająca mu instrukcja ani nie wpływa na żadną wartość wyliczaną dalej, ani nie jest niepustą pętlą lub instrukcją warunkową. Oczy- wiście wierzchołek odpowiadający ostatniej instrukcji, czyli wartości zwracanej, choć ma stopień wyjściowy równy zero, nie jest usuwany. Proces przeglądania i usuwania wierzchoł- ków należy powtarzać, dopóki nie będzie już żadnej zmiany w grafie. Wynika to z faktu, że może być kilka instrukcji, które od siebie zależą, ale ostatecznie nie mają wpływu na wartość zwracaną funkcji. Kolejną optymalizacją, którą przeprowadzamy na wygenerowanym grafie, jest wykry- wanie pary instrukcji, które dokonują tych samych obliczeń i scalanie ich w jeden wierzcho- łek. Dwie instrukcje dokonują tych samych obliczeń, jeśli są wywołaniami funkcji, które mają krawędzie wchodzące zależności danych pochodzące z tego samego zbioru wierzchoł- ków, czyli zależą od tych samych zmiennych, a także mają ten sam typ. W przypadku wykrycia pary takich wierzchołków, jeden z nich zostaje usunięty, zaś do drugiego zostają dodane brakujące krawędzie wychodzące. Pętla for jest przetwarzana w taki sposób, aby jej reprezentacja w grafie jak naj- bardziej odpowiadała pętli while. Wykrywane są zmienne, które są używane w warunku oraz towarzyszące im instrukcje: te, które przypisują im początkowe wartości przed roz-

67

i i

i i i “output” — 2018/6/21 — 7:43 — page 68 — #68 i i i

sum <− function ( x ) { s <− 0 s <- -s m <− 1 for ( i in x ) { print ("Negative s")

s <− s + i s < 0 s <- 0

m <− m ∗ i x } If

Entry return(s) If i f ( s < 0) { m <- -m

s <− −s For m < 0 print ("Negative␣s") m <- 1

} i : x print ("Negative m") i f (m < 0) { m <− −m m <- m*i print ("Negative␣m") s <- s+i } return ( s ) (b) Podgraf zależności sterowania funkcji; krawędzie } zależności danych oznaczono na czarno (a) Kod źródłowy

Rysunek 3.1. Przykładowa funkcja wraz z odpowiadającym CDS

poczęciem pętli, a także te, które przypisują tym zmiennym nowe wartości w ciele pętli. Następnie odpowiadające im wierzchołki są usuwane, a odpowiednie krawędzie zależności danych są przenoszone do wierzchołka reprezentującego samą pętlę. Wykrywane jest użycie operatora potokowego %>%. Nie zostaje tworzony osobny wierz- chołek, który by go reprezentował, ale tworzone jest zwykłe wywołanie funkcji, gdzie lewy operand jest używany jako pierwszy argument wywołania będącego prawym operandem. Głównymi zaletami korzystania z PDG jest fakt, że otrzymujemy ten sam graf nie- zależnie od kolejności wywoływania funkcji (niezależnych od siebie), użycia operatora potokowego %>%, przekazywania argumentów do operatorów binarnych, zagnieżdżania lub rozwijania funkcji, użytej pętli, a także obecności instrukcji, które nie mają wpływu na zwracaną wartość. Zbiór grafów, które mogą powstać ze zbioru funkcji F, oznaczamy G(F).

68

i i

i i i “output” — 2018/6/21 — 7:43 — page 69 — #69 i i i

Tabela 3.2. Typy wierzchołków w PDG

Typ Opis

Entry sztuczny wierzchołek oznaczający początek funkcji Header pętla Next instrukcja next (w innych językach znana jako continue) Break instrukcja break If instrukcja warunkowa If_part część instrukcji warunkowej (ta, gdy warunek jest spełniony lub nie) assignment instrukcja przypisania parameter parametr funkcji oneBracketSingle użycie [i] oneBracketDouble użycie [i,j] oneBracketTripleOrMore użycie [i,j,k,...] twoBrackets użycie [[i]] dollar wierzchołek reprezentujący użycie $ functionZeroArgument wywołanie funkcji bezparametrowej functionOneArgument wywołanie funkcji jednoparametrowej functionTwoArguments wywołanie funkcji dwuparametrowej functionThreeArguments wywołanie funkcji trzyparametrowej functionFourOrMoreArguments wywołanie funkcji o czterech lub więcej parametrach stopifnot wierzchołek reprezentujący wywołanie stopifnot logicalOperator użycie operatora logicznego arithmeticOperator użycie operatora arytmetycznego comparisonOperator użycie operatora porównania return instrukcja return colon instrukcja : symbol symbol constant stała wartość

3.2 Symetryczne a niesymetryczne miary podobień- stwa

Wszystkie podejścia do wykrywania klonów znane w literaturze zakładają między innymi spełnienie warunku symetrii: jeśli kod A jest podobny w jakimś stopniu do kodu B, to kod B jest tak samo podobny do kodu A. Oznacza to, że można dotychczas zaproponowane metody traktować jako algorytmy obliczające miary podobieństwa, µ: F × F → [0, 1],

69

i i

i i i “output” — 2018/6/21 — 7:43 — page 70 — #70 i i i

Entry

a <- 5 sum <− function ( x ) b <- 6 For {

a <− 5 x b <− 6 ab <- a+b for ( i in x ) i : x { c <− a + b − i }

} c <- ab-i (a) Kod źródłowy (b) Graf zależności programu funkcji; krawędzie zależności danych oznaczono na czarno, a zależności danych na niebiesko

Rysunek 3.2. Przykładowa funkcja wraz z odpowiadającym PDG

takie, że:

— ∀f∈F µ(f, f) = 1;

— ∀f,g∈F µ(f, g) = µ(g, f). My proponujemy użycie także drugiego, niespotykanego do tej pory podejścia, opar- tego o zawieranie się jednej funkcji w drugiej. Takie podejście nie jest symetryczne i za- kłada, że jedna funkcja może być bardziej podobna do drugiej, niż w drugą stronę. Mo- tywacją niech będzie następujący przykład: Funkcja f:

1 s <− 0 2 for(i in x){s <− s + i }

Funkcja g:

1 s <− 0 2 for(i in x){s <− s + i } 3 m <− 0 4 for(i in x){m<− m ∗ i }

Intuicyjnie jesteśmy raczej zainteresowani taką miarą podobieństwa µ˜, która zwróci

70

i i

i i i “output” — 2018/6/21 — 7:43 — page 71 — #71 i i i

µ˜(f, g) = 1 i µ˜l(g, f) = 0,5. Pierwszą zaletą takiego podejścia jest fakt, że kiedy ktoś weźmie długą i skomplikowaną funkcję z jakiejś biblioteki i skopiuje jedynie jej część, to taka miara pozwoli nam wykryć taki przypadek, nawet jeśli jedna funkcja będzie bardzo długa, a druga krótka. Co więcej, jeśli jedna osoba napisze trzy krótkie funkcje, podczas gdy druga dokona ich złączenia w jedną długą funkcję (lub na odwrót, długa funkcja zostanie rozbita na wiele krótkich), system, który postrzega podobieństwo w sposób sy- metryczny, nie odkryje tego rodzaju klonów. Wszystkie dalej zaproponowane algorytmy, wraz ze zwracanymi przez nie miarami podobieństwa, prezentujemy w dwóch wersjach: symetrycznej (µ) i niesymetrycznej (µ˜). Często jednak, pomimo ciekawej interpretacji wyniku będącego parą liczb, zachodzić będzie potrzeba sprowadzenia wyniku do pojedynczej wartości. Dlatego proponujemy kompromis: użycie pewnej funkcji agregującej A, zob. [19, 50], która sprowadza miary zawierania się do pewnej wartości z przedziału [0, 1]: µˆ(f, g) = A(˜µ(f, g), µ˜(g, f)), gdzie A jest jedną z poniższych funkcji:

— Tm(x, y) = min(x, y) (t-norma minimum);

— Tp(x, y) = xy (produktowa);

— TŁ(x, y) = max(0, x + y − 1) (Łukasiewicza); x+y — M(x, y) = 2 (średnia arytmetyczna);

— Sm(x, y) = max(x, y) (t-konorma dualna do Tm);

— Sp(x, y) = 1 − (1 − x)(1 − y) (t-konorma dualna do Tp);

— SŁ(x, y) = min(1, x + y) (t-konorma dualna do TŁ). Pierwsze trzy funkcje są t-normami (normami trójkątnymi). T-norma to funkcja T : [0, 1] × [0, 1] → [0, 1], która dla ∀a, b, c, d spełnia następujące warunki: — przemienność: T (a, b) = T (b, a); — monotoniczność: T (a, b) 6 T (c, d) jeśli zachodzi a 6 c i b 6 d; — łączność: T (a, T (b, c)) = T (T (a, b), c); — 1 jest elementem neutralnym: T (a, 1) = a. Intuicyjnie t-norma jest uogólnionym minimum. Ostatnie trzy funkcje są T-konormami (nazywane też S-normami): S : [0, 1] × [0, 1] → [0, 1]. Intuicyjnie są to funkcje będące uogólnionym maksimum. Mając daną t-normę,

można wyprowadzić dualną do niej t-konormę: S(a, b) = 1 − T (1 − a, 1 − b). I tak Sm

jest dualne do Tm, Sp jest dualne do Tp, a SŁ jest dualne do TŁ. T-konormy dla ∀a, b, c, d spełniają następujące warunki: — przemienność: S(a, b) = S(b, a); — monotoniczność: S(a, b) 6 S(c, d) jeśli zachodzi a 6 c i b 6 d; — łączność: S(a, S(b, c)) = S(S(a, b), c); — 0 jest elementem neutralnym: T (a, 0) = a.

71

i i

i i i “output” — 2018/6/21 — 7:43 — page 72 — #72 i i i

Funkcja M jest zwykłą średnią arytmetyczną i stanowi pewną funkcję „pośrednią” pomiędzy t-normami a t-konormami. Zauważmy, że jest ona: — przemienna; — monotoniczna; — idempotentna: ∀a M(a, a) = a.

3.3 Algorytmy porównujące

W tym podrozdziale przejdziemy do opisu algorytmów użytych do porównywania różnych postaci wejściowych kodu źródłowego funkcji. Niech f, g ∈ F będą od tej pory daną parą funkcji, które chcemy porównać. Przez f i g będziemy oznaczać powstałe z nich ciągi, które należą do tego samego zbioru: L(F), T(F) albo C(F). Oznaczenia dotyczące grafów wprowadzamy w p. 3.3.4.

3.3.1 Odległości edycyjne

Odległość Levenshteina. W wersji symetrycznej używaną przez nas odległością edy- cyjną jest odległość Levenshteina [80]. Nieformalnie odległość Levenshteina to minimalna liczba działań prostych (tj. wstawienie, usunięcie lub zamiana) przeprowadzających jeden ciąg w drugi.

Algorytm dla dwóch ciągów A = a1a2 ··· an i B = b1b2 ··· bm można przedstawić w sposób następujący: 1) Stwórz macierz L o n + 1 wierszach i m + 1 kolumnach (indeksowaną od zera). Przypisz j-tej pozycji zerowego wiersza wartość j. Przypisz i-tej pozycji zerowej kolumny wartość i. 2)  L + 1,  i−1,j  Lij = min Li,j−1 + 1, (1 6 i 6 n, 1 6 j 6 m)   Li−1,j−1 + s(ai, bj),

gdzie s(ai, bj) jest równe 1 gdy ai 6= bj i 0 w przeciwnym wypadku.

3) Wartość odległości Levenshteina równa się Lnm. Oznaczmy odległość Levenshteina między dwoma ciągami f i g przez lev(f, g). Wtedy miarą podobieństwa zwracaną przez ten algorytm będzie:

lev(f, g) µ (f, g) = 1 − , 1 max(|f|, |g|) gdzie |f| to długość ciągu f.

72

i i

i i i “output” — 2018/6/21 — 7:43 — page 73 — #73 i i i

Dla pary takich samych ciągów otrzymujemy wartość równą 1. Z drugiej strony, dla np. „abc” i „defghi” dostaniemy 0, jako że lev(„abc”, „defghi”) = 6. W wersji niesymetrycznej używamy wbudowanej w R funkcji adist() z parametrem partial=TRUE. Funkcja pochodzi z biblioteki TRE1 i operuje na tzw. rozmytych wyraże- niach regularnych. W tym wypadku jeden z ciągów staje się tekstem, a drugi wzorcem. Znalezione dopasowanie wzorca w tekście nie musi się dokładnie zgadzać, ale być w pew- nej odległości Levenshteina od niej. Każde dodanie, odjęcie czy zamiana znaku, użyte, aby otrzymać szukany wzorzec, powoduje wzrost tej odległości. I tak, przykładowo (lewy argument jest wzorcem, a prawy tekstem):

1 adist("abcxxx", "abc", partial=TRUE)

zwraca 3, podczas gdy:

1 adist("abc", "abcxxx", partial=TRUE)

zwraca 0. Niesymetryczna wersja tej miary podobieństwa jest zdefiniowana jako:

dist(f, g) µ˜ (f, g) = 1 − . 1 |f| Łatwo zobaczyć, że dla pary takich samych ciągów otrzymujemy tak jak poprzednio wartość 1, a dla „abc” i „defghi” dostajemy 0, jako że dist(„abc”, „defghi”) = 3.

Algorytm Smitha–Watermana Algorytm Smitha–Watermana [122], używany za- zwyczaj w bioinformatyce do znajdowania podobnych fragmentów sekwencji DNA, jest algorytmem porównywania dwóch ciągów, polegającym na lokalnym dopasowaniu. O ile dopasowanie globalne znajduje zastosowanie głównie wtedy, gdy badamy dwie sekwencje o podobnych rozmiarach (i które, liczymy na to, są w całości podobne), o tyle dopaso- wanie lokalne jest użyteczne w przypadku, gdy ciągi nie wykazują w całości większego podobieństwa, ale być może zawierają podobne fragmenty. Sama implementacja algorytmu bazuje na programowaniu dynamicznym i jest po- dobna do obliczania odległości Levenshteina. Algorytm opiera się na macierzy zamian (ang. substitution matrix) oraz na systemie punktacji luk (ang. gap-scoring scheme). Macierz zamian zawiera informację, jak punktować zamianę jednego elementu sekwencji na drugi. Zazwyczaj zamiana podobnych lub takich samych elementów skutkuje dodatnimi

1https://laurikari.net/tre/

73

i i

i i i “output” — 2018/6/21 — 7:43 — page 74 — #74 i i i

punktami, podczas gdy zamiana elementu na niepodobny daje wynik ujemny. Najprost- szym sposobem punktacji może być przydzielenie wartości w, gdy elementy są identyczne oraz −w w przeciwnym wypadku. Wartość macierzy dotyczącej zamiany elementu a na b zapisujemy poprzez s(a, b). Operacje dodawania lub usuwania znaków skutkują wprowadzeniu luk, zazwyczaj re- prezentowanych myślnikami. Przykładowo, dopasowanie ciągów AAACAC i CACA da nam:

1 AAACAC 2 CA−CA−

Sposobów interpretacji dokonanych operacji w miejscu myślników jest kilka: wstawi- liśmy litery A i C do krótszej sekwencji, usunęliśmy je z dłuższej, albo wstawiliśmy luki do CACA. W przypadku algorytmu Smitha-Watermana ta trzecia interpretacja okazuje

się najwygodniejsza. Parametrem algorytmu jest punktacja Wk, jaką przydzielamy luce o długości k (ponieważ luki nie są pożądane, punkty te są ujemne). Najprostszym spo-

sobem punktacji jest ustalenie pewnej kary W1, a następnie zdefiniować Wk = k · W1. Ciekawszym sposobem jest podejście afiniczne, gdzie osobno rozważamy rozpoczęcie luki

i jej kontynuację: Wk = u(k−1)+v. Takie podejście ma zwłaszcza zastosowanie w bioinfor- matyce, gdzie jedna dłuższa luka jest bardziej pożądana, niż wiele krótkich luk. Wynika to z faktu, że dłuższa luka może wynikać z pojedynczej mutacji, która wkradła się do jednej z sekwencji DNA. Aby odwzorować ten scenariusz, dobiera się v > u.

Sam algorytm dla dwóch ciągów A = a1a2 ··· an i B = b1b2 ··· bm można przedstawić w sposób następujący: 1) Stwórz macierz H o n + 1 wierszach i m + 1 kolumnach (indeksowaną od zera). Niech pierwszy wiersz i pierwsza kolumna zostaną wypełnione zerami. 2)   Hi−1,j−1 + s(ai, bj),    maxk>1{Hi−k,j − Wk}, Hij = max (1 6 i 6 n, 1 6 j 6 m) max {H − W },  l>1 i,j−l l   0, gdzie:

Hi−1,j−1 + s(ai, bj) jest punktacją za dopasowanie ai i bj,

Hi−k,j − Wk jest punktacją, jeśli ai jest końcem luki o długości k,

Hi,j−l − Wl jest punktacją, jeśli bj jest końcem luki o długości l,

0 oznacza brak podobieństwa aż do ai i bj. 3) Aby odzyskać najlepsze dopasowanie z tak powstałej macierzy H, należy zacząć

74

i i

i i i “output” — 2018/6/21 — 7:43 — page 75 — #75 i i i

od komórki macierzy z największą punktacją, następnie, dla każdej komórki reku- rencyjnie przechodzić do komórki, która do niej doprowadziła, by zakończyć na ko- mórce z punktacją równą zeru. Warto zauważyć, że algorytm ten nie pozwala na ujemne wartości w macierzy H. Wła- śnie ta cecha świadczy o lokalności tego algorytmu: tam, gdzie algorytm wprowadziłby ujemną wartość, wstawiamy zero, interpretując je w ten sposób, że do tej pory nie było podobnych fragmentów w obu sekwencjach. Tak samo końcowe fragmenty mogą nie za- wierać żadnych podobieństw i dlatego zaczynamy odzyskiwanie najlepszego dopasowania od największej wartości w H. Implementacja użyta w naszej pracy przyjmuje 4 parametry: wartość dopasowania w = s(a, a) (gdy elementy są identyczne), wartość dopasowania w0 = s(a, b) (gdy elementy są różne), wartość rozpoczęcia luki v oraz kontynuacji luki u. Niech punktacja znalezionego dopasowania wynosi s. Wtedy miarę podobieństwa de- finiujemy jako:

s µ (f, g) = , 2 (|f| + |g|) · w − s

s µ˜ (f, g) = . 2 |f| · w Według naszej najlepszej wiedzy nie ma żadnej pracy, która wykorzystywałaby algo- rytm Smitha–Watermana w kontekście wykrywania podobnych kodów źródłowych.

3.3.2 Zachłanne kafelkowanie ciągów

W artykule [107] opisującym system wykrywania podobieństwa kodów źródłowych oparty na tokenach użyto algorytmu opisanego w [130]. Sam problem jest bliźniaczy do pro- blemu znajdowania najdłuższego wspólnego podciągu (ang. longest common subsequence), który znajduje swoje zastosowanie w wielu dziedzinach, przykładowo porównywanie różnic w szeregach czasowych [52, 65, 82, 93], jednak tutaj wszystkie elementy muszą występować bezpośrednio po sobie, jak w problemie znajdowania najdłuższego wspólnego podłańcu- cha (ang. longest common substring). Głównym celem metody jest iteracyjne znajdowanie możliwie długich wspólnych podciągów dwóch ciągów. W pierwszej iteracji jest znajdo- wany najdłuższy możliwy wspólny podciąg. Elementy wchodzące w skład tego podciągu zostają zaznaczone jako wykorzystane i nie będą mogły być ponownie użyte. W kolejnych iteracjach kolejne, coraz to krótsze wspólne podciągi są znajdowane z wykorzystaniem pozostałych, jeszcze niewykorzystanych elementów. Argumentem algorytmu jest mini- malna długość dopasowania d. Wspólne podciągi krótsze niż d nie mogą zostać dopaso- wane. Zapobiega to dopasowywaniu bardzo krótkich sekwencji, które są identyczne raczej

75

i i

i i i “output” — 2018/6/21 — 7:43 — page 76 — #76 i i i

przez przypadek niż świadome działanie. Pseudokod algorytmu można znaleźć w alg. 1. Miarę podobieństwa definiujemy jako:

2pokrycie(kafelki) µ (f, g) = , 3 |f| + |g|

pokrycie(kafelki) µ˜ (f, g) = , 3 |f| gdzie:

pokrycie(kafelki) = X długość. (a,b,długość)∈kafelki

3.3.3 Odległość q-gramowa

Niech będzie dany ciąg A. q-gramem [73] ciągu A nazywamy dowolne q następujących po sobie elementów tego ciągu. Niech p będzie wektorem, którego i-ta współrzędna równa jest liczbie występowań i-tego q-gramu w ciągu f, a q będzie analogicznym wektorem dla ciągu g. Niech długość obu wektorów wynosi m. Odległość pomiędzy nimi wyraża się wzorem:

Pm k=1 |pk − qk| µ4(p, q) = 1 − Pm Pm . k=1 |pk| + k=1 |qk|

Warto zauważyć, że wykorzystujemy tutaj metrykę Manhattan (L1, taksówkowa), z tą różnicą, że jest ona znormalizowana (wartości zawierają się w przedziale [0, 1]). Dla niesymetrycznej wersji rozważamy jedynie te q-gramy, które występują w f. Niech q0 oznacza taki podzbiór q, że są w nim jedynie liczby wystąpień takich q-gramów, które występują w f. Niech długość obu wektorów wynosi m. Wtedy niesymetryczną miarą q-gramową jest:

Pm 0 0 k=1 |pk − qk| µ˜4(p, q ) = 1 − Pm . k=1 |pk|

3.3.4 Największy wspólny podgraf

Do tej pory opisywaliśmy algorytmy, które działają na ciągach tokenów, liter lub wywo- łań funkcji. Teraz przejdziemy do trzech algorytmów, które działają na PDG, opisanym dokładnie w p. 3.1.2. Wprowadźmy następujące oznaczenia: PDG powstały dla funkcji f oznaczymy jako F = G(f), analogicznie graf dla funkcji g oznaczymy jako G = G(g).

Niech N1 oznacza liczbę wierzchołków grafu F , a N2 oznacza liczbę wierzchołków grafu G.

76

i i

i i i “output” — 2018/6/21 — 7:43 — page 77 — #77 i i i

Algorytm 1: Zachłanne kafelkowanie ciągów Wejście: 1. ciąg A 2. ciąg B 3. MinimalnaDlugoscDopasowania – stała liczba całkowita Wyjście: kafelki

1 do

2 maksdopasowanie ← MinimalnaDlugoscDopasowania

3 dopasowania ← pusty zbiór trójek

4 for nieoznaczony element Aa ∈ A do

5 for nieoznaczony element Bb ∈ B do

6 j=0

7 while Aa+j = Bb+j ∧ nieoznaczony(Aa+j) ∧ nieoznaczony(Bb+j) do

8 j ← j + 1

9 if j = maksdopasowanie then

10 dopasowania = dopasowania ∪ (a,b,j)

11 else

12 if j > maksdopasowanie then

13 dopasowania ← (a,b,j)

14 maksdopasowanie ← j

15 for (a, b, maksdopasowanie) ∈ dopasowania do

16 for j ← 1,..., maksdopasowanie do

17 oznacz(Aa+j)

18 oznacz(Bb+j)

19 kafelki ← kafelki ∪ (a, b, maksdopasowanie)

20 while maksdopasowanie > MinimalnaDlugoscDopasowania

21 return kafelki

77

i i

i i i “output” — 2018/6/21 — 7:43 — page 78 — #78 i i i

Zaczniemy od algorytmu McGregora [97], który służy znajdowaniu największego wspólnego podgrafu. Szkic algorytmu znajduję się w alg. 2. Algorytm może być łatwo opisany poprzez przestrzeń stanów (ang. State Space Representation) [101]. Każdy stan s reprezentuje do tej pory skonstruowany wspólny podgraf dwóch grafów. Ten do tej pory skonstru- owany wspólny podgraf jest częścią maksymalnego wspólnego podgrafu, który jest szu- kany. W każdym stanie para wierzchołków jeszcze nieprzeanalizowana (n1, n2), gdzie pierwszy należy do pierwszego grafu, a drugi do drugiego, zostaje wybrana (pod wa- runkiem, że istnieje) przez funkcję NastepnaPara(s, n1, n2). Następnie wybrana para wierzchołków jest rozpatrywana przez MozliwaPara(s, n1, n2), która sprawdza, czy jest możliwe rozszerzyć wspólny podgraf reprezentowany przez aktualny stan przez tę parę. Jeśli tak, to funkcja DodajPare(s, n1, n2) rzeczywiście rozszerza aktualne częściowe rozwiązanie przez tę parę. Potem, jeśli rozważany stan s nie jest liściem drzewa poszuki- wań, tzn. jeśli istnieje co najmniej jeden wierzchołek w pierwszym grafie, który nie został jeszcze wybrany przez funkcję NastepnaPara(), to ten wierzchołek jest wybierany i nowy stan jest analizowany. Dalej, gdy nowy stan zostanie przetworzony, funkcja Powrot() jest wywoływana, aby odtworzyć wspólny podgraf poprzedniego stanu oraz wybrać inny nowy stan. Używając tej strategii przeszukiwania, każda wybrana ścieżka w drzewie zostanie przeszukana tak głęboko, jak to możliwe, aż zostanie napotkany liść albo zostanie speł- niony warunek służący obcięciu zbędnych obliczeń (ang. pruning condition). Algorytm przechowuje aktualny poziom w drzewie poszukiwań. Jego wartość jest zawsze mniejsza lub równa rozmiarowi mniejszego z dwóch rozpatrywanych grafów. Liczba wierzchołków maksymalnego wspólnego podgrafu również jest mniejsza lub równa temu rozmiarowi, zatem warunek służący obcięciu zbędnych obliczeń sprawdza, czy liczba poziomów od ak- tualnego do najbardziej odległego liścia w drzewie poszukiwań jest wystarczająco duży, aby móc skonstruować większy wspólny podgraf niż do tej pory znaleziony. Warto zauważyć pewien techniczny aspekt: potrzebny jest specjalny rodzaj wierz- chołka, null node. Po wykonaniu wszystkich prób przyporządkowania wierzchołka n1 do wszystkich wierzchołków n2, n1 zostaje przyporządkowany do null node, tak aby móc sprawdzić także te rozwiązania, w których n1 nie wchodzi do wspólnego podgrafu.

Załóżmy, że N1 i N2 są odpowiednio liczbą wierzchołków pierwszego i drugiego roz-

ważanego grafu oraz N1 6 N2. W pesymistycznym przypadku, tj. gdy oba grafy to grafy pełne, a wierzchołki i krawędzie są nierozróżnialne (nie mają żadnych związanych z nimi atrybutów jak np. kolor), liczba stanów odwiedzanych przez algorytm to:

1 1 ! S = N2! · + ... + . (N2 − N1)! (N2 − 1)!

W przypadku N1 = N2 = N oraz N  1, możemy oszacować S przez [28]:

78

i i

i i i “output” — 2018/6/21 — 7:43 — page 79 — #79 i i i

S ∼= e · N · N!

Warto zauważyć, że złożoność pamięciowa jest o wiele mniejsza i wynosi O(N1), po- nieważ jedynie informacje związane ze stanami na aktualnie rozpatrywanej ścieżce drzewa poszukiwań muszą być przechowywane w pamięci.

Algorytm 2: Algortym McGregora znajdowania największego wspól- nego podgrafu Wejście: stan s

1 while NastepnaPara(s,n1,n2) do

2 if MozliwaPara(s,n1,n2) then

3 s’ ← DodajPare(s, n1, n2)

4 if rozmiar(s’) > AktualnyRozmiarMCS then

5 ZapiszAktualnyMCS(s’)

6 AktualnyRozmiarMCS ← rozmiar(s’)

7 if ~Liść(s’) ∧ ~WarunekCięcia(s’) then

8 McGregor_MCS(s’)

9 Powrot(s’)

Załóżmy, że algorytm zwrócił największy wspólny podgraf o liczbie wierzchołków rów- nej s. Miarę podobieństwa dla tej metody definiujemy jako:

s µ5(F,G) = , N1 + N2 − s

s µ˜5(F,G) = . N1

3.3.5 Algorytm oparty na teście Weisfeilera–Lehmana

Powyżej opisany algorytm, pomimo, że wydaje się być oczywistym wyborem, ma sze- reg wad: po pierwsze, ma wykładniczą złożoność czasową. Po drugie, w wyniku ekspe- rymentów empirycznych okazało się, że poszukiwanie dokładnego rozwiązania nie było optymalnym podejściem, ponieważ czasem niewielkie zmiany w kodzie powodowały takie zmiany w grafie PDG, które nie pozwalały na odnalezienie wspólnego podgrafu w sposób dokładny. Dlatego postanowiliśmy przetestować algorytm opierający się na jądrach grafowych opisany w [120] i używany z powodzeniem np. w informatyce chemicznej. Jądra gra- fowe są używane w uczeniu na ustrukturyzowanych danych (ang. learning on structured

79

i i

i i i “output” — 2018/6/21 — 7:43 — page 80 — #80 i i i

data), wykorzystując topologię grafów, ale jednocześnie ograniczając się do porównywa- nia podstruktur dających się obliczyć w czasie wielomianowym. Jądra grafowe stały się łącznikiem między danymi o strukturze grafu i dużym zakresem metod uczenia maszy- nowego, zwanych metodami jądrowymi (ang. kernel methods), które zawierają algorytmy takie jak maszyna wektorów podpierających (SVM), regresja jądrowa czy jądrowa analiza głównych składowych (ang. kernel PCA). Należy w tym miejscu określić, gdzie znajduje się nasza implementacja (będąca pewną modyfikacją cytowanej pracy) w kontekście testowania izomorfizmu lub podobieństwa dwóch grafów. W pracy [128] Weisfeiler i Lehman zaproponowali algorytm (opisywany dokładniej w sposób formalny w dalszej części pracy), który wykonuje h iteracji (gdzie h jest parametrem). Iteracje mają złożoność wielomianową i polegają głównie na obliczaniu nowych etykiet wierzchołków i krawędzi na podstawie dotychczasowych etykiet ich sąsia- dów. Po zakończeniu ich wykonywania, możliwy jest jeden z dwóch wyników: pewność, że grafy nie są izomorficzne (zbiory etykiet dla obu grafów różniły się na którymś etapie), albo brakiem rozstrzygnięcia, czy grafy są izomorficzne. Pewności, że są one izomorficzne, nie możemy mieć nigdy. O ile praca [29] pokazała grafy, które nie mogą być rozróżnione tym podejściem, o tyle praca [5] pokazuje, że jest to odpowiedni test izomorfizmu dla prawie każdego grafu. Jednak, jak wspomnieliśmy wcześniej, w wielu dziedzinach coraz częściej zachodzi po- trzeba oceny częściowego podobieństwa dwóch nieidentycznych grafów. Praca [120], na której oparliśmy naszą implementację, korzysta z testu Weisfeilera–Lehmana, jednak do- puszcza możliwość, że grafy będą się różnić między sobą (ich zbiory etykiet będą różne). Głównym oryginalnym wkładem pracy jest zaproponowanie oceny podobieństwa zbiorów etykiet w każdej z h iteracji algorytmu oraz łączna ich agregacja. Ocena podobieństwa polega na zliczaniu poszczególnych etykiet, a następnie obliczanie iloczynu skalarnego między dwoma wektorami liczności etykiet. Użycie iloczynu skalarnego wynika z chęci pozyskania odpowiednich własności matematycznych potrzebnych (chodzi głównie o ją- dra dodatnio określone, positive definite kernels), aby móc użyć tej techniki w dalszych metodach uczenia maszynowego opartego na jądrach (ang. kernel trick). Jednak ponieważ nam zależy jedynie na uzyskaniu intuicyjnej wartości podobieństwa, zdecydowaliśmy się zamienić iloczyn skalarny na miarę podobną do odległości q-gramowej. Co więcej, imple- mentacja tego algorytmu była inspiracją do zaproponowania algorytmu SimilaR, który także opiera się na obliczaniu etykiet wierzchołków w kolejnych iteracjach. Przedstawmy metodę Weisfeilera–Lehmana na testowanie izomorfizmu dwóch grafów. Algorytm jest oparty na etykietach przyporządkowanych wierzchołkom. Taka etykieta wy- raża się poprzez liczbę naturalną. Zbiór możliwych etykiet oznaczamy przez Σ i nazywamy alfabetem. Główną ideą algorytmu jest zebranie etykiet sąsiadów danego wierzchołka

80

i i

i i i “output” — 2018/6/21 — 7:43 — page 81 — #81 i i i

(zbiór sąsiadów wierzchołka v oznaczamy przez N(v)) w jeden multizbiór, który następ- nie sortujemy (tak więc staje się on ciągiem, gdzie kolejność ma znaczenie). Tak otrzy- manemu ciągowi przyporządkowujemy w sposób jednoznaczny nową etykietę (liczbę). Te kroki są powtarzane, dopóki zbiór etykiet wierzchołków grafów F i G nie różni się lub wy- konanych została założona z góry liczba iteracji h. Funkcję, która przydziela nowe etykiety podanym ciągom, oznaczmy przez f. Funkcja ma licznik, w którym przechowuje informację, ile unikatowych etykiet przydzieliła do tej pory. Jeśli funkcja ma przydzielić etykietę napisowi, który kompresowała już wcześniej, to przydziela mu tę samą etykietę. Jeśli jednak jest to nowy napis, zwiększa wartość licznika i przydziela mu jego wartość. Algorytm kończy swoje działanie, gdy zbiory etykiet wierzchołków nie są identyczne po którejś z iteracji. Oznacza to, że grafy nie są izomorficzne. Jeśli po danych z góry h iteracjach grafy mają te same zbiory etykiet wierzchołków, wyciągnięty wniosek może być jeden z dwóch: albo grafy są izomorficzne, albo algorytm nie był w stanie rozstrzygnąć o ich nieizomorficzności. Pojedyncza iteracja algorytmu widoczna jest w alg. 3. Złożoność czasowa algorytmu o h iteracjach dla grafu o N wierzchołkach wynosi O(hN). Definiowanie multizbiorów w kroku 1 dla wszystkich wierzchołków jest operacją o koszcie O(N). Sortowanie mul- tizbiorów także jest operacją o koszcie O(N), co może zostać osiągnięte przez użycie sortowania przez zliczanie. Możemy użyć tego algorytmu sortowania, ponieważ zakres ele- mentów w multizbiorze jest ograniczony. Sortowanie otrzymanych napisów także wynosi O(N) dzięki sortowaniu pozycyjnemu (ang. radix sort). Kompresja etykiet wymaga jed- nego przejścia przez wszystkie napisy i ich znaki, czyli O(N). Zatem łącznie otrzymujemy złożoność O(hN) dla wszystkich h iteracji. Teraz pokażemy, jak powyższy algorytm jest używany, aby obliczyć podobieństwo między dwoma PDG. Wszystkie kroki są takie same, ale nie przerywamy obliczeń, jeśli zbiory etykiet różnią się od siebie. Zawsze wykonujemy h iteracji, która to liczba jest parametrem algorytmu. Za to po każdej iteracji obliczamy, jak bardzo różnią się zbiory i etykiet i robimy to bardzo podobnie do odległości q-gramowej. Niech L1 oznacza taki wektor, że wartość na j-tej pozycji oznacza liczbę wierzchołków o j-tej etykiecie w grafie i F po i-tej iteracji. Analogicznie dla grafu G oznaczmy L2. Niech długość tych wektorów równa się m. Wartość podobieństwa dla i-tej iteracji wynosi:

Pm i i i i k=1 |L1,k − L2,k| µ6(L1, L2) = 1 − Pm i Pm i , k=1 |L1| + k=1 |L2|

i i i i min(L1, L2) µ˜6(L1, L2) = 1 − Pm i , k=1 |L1| gdzie min jest operatorem minimum po współrzędnych.

81

i i

i i i “output” — 2018/6/21 — 7:43 — page 82 — #82 i i i

Algorytm 3: Pojedyncza, i-ta iteracja testu Weisfeilera–Lehmana na testowanie izomorfizmu dwóch grafów 1) Wyznaczanie multizbioru etykiet:

— Dla i = 0 przypisz Mi(v) = l0(v) = l(v).

— Dla i > 0 przypisz multizbiór etykiet Mi(v) każdemu wierzchołkowi v w F

i G, który składa się z multizbioru {li−1(u)|u ∈ N(v)}. 2) Sortowanie każdego multizbioru:

— Posortuj elementy w Mi(v) w porządku rosnącym i połącz je w jeden napis

si(v).

— Dodaj li−1(v) jako prefiks do si(v) i wynik nazwij si(v). 3) Kompresja etykiet:

— Posortuj wszystkie napisy si(v) dla wszystkich v z F i G w porządku rosnącym.

— Przekształć każdy napis si(v) w nową skompresowaną etykietę, używając ∗ funkcji f:Σ → Σ w taki sposób, że f(si(v)) = f(si(w)) wtedy i tylko wtedy

gdy si(v) = si(w). 4) Przeetykietowanie:

— Przypisz li(v) = f(si(v)) dla wszystkich wierzchołków w F i G.

Ostateczną wartością zwracaną przez algorytm jest suma wartości podobieństwa dla wszystkich iteracji, podzielona przez liczbę iteracji:

h i i X µ6(L1, L2) µ6(F,G) = , i=1 h

h i i X µ˜6(L1, L2) µ˜6(F,G) = . i=1 h W dalszej części pracy, skrótowo, będziemy odwoływać się do przedstawionego algo- rytmu jako algorytmu Weisfeilera–Lehmana lub algorytmu W–L.

3.3.6 Elastyczna wersja algorytmu Weisfeilera–Lehmana (algorytm SimilaR)

Wyżej opisany algorytm oparty na teście Weisfeilera–Lehmana podczas wstępnych ba- dań okazał się być o wiele szybszy niż algorytm znajdowania największego wspólnego podgrafu. Jednak, pomimo dość dobrych zwracanych wyników, nadal był zbyt wrażliwy na grafy, które różniły niezbyt znaczące miejsca. Dlatego postanowiliśmy dokonać autor- skiej modyfikacji tego podejścia, aby grafy, które do pewnego stopnia się różnią, mimo

82

i i

i i i “output” — 2018/6/21 — 7:43 — page 83 — #83 i i i

wszystko dostawały wysoką wartość miary podobieństwa. Głównym autorskim wkładem jest:

1) obserwacja, że miara podobieństwa spada, gdy dwa wierzchołki otrzymują różne etykiety (ponieważ wektory zliczeń etykiet różnią się pomiędzy sobą); 2) propozycja, aby dwa wierzchołki, pomimo różnych sąsiadów (w sensie etykiet), mo- gły otrzymać tę samą etykietę, jeśli ta różnica nie jest zbyt wielka.

Wprowadźmy pojęcie ważności wierzchołka w grafie. Podstawową, najmniejszą war- tość ważności, jaką może posiadać wierzchołek, jest δ > 0 (jej dokładna wartość liczbowa nie ma znaczenia). Taką wartość przypisujemy wszystkim wierzchołkom, których stopień wychodzący wynosi 0. Wierzchołek, którego stopień wychodzący przyjmuje wartość więk- szą od zera, ma ważność równą δ plus suma ważności jego sąsiadów (w sensie krawędzi wychodzących). Gdy przydzielimy poziomy ważności wszystkim wierzchołkom, dokonu- jemy ich normalizacji: dzielimy je przez sumę ważności wszystkich wierzchołków w gra- fie. Następnie obliczamy medianę z wartości ważności wierzchołków w grafie. Oznaczmy

przez m1 medianę dla grafu F i przez m2 medianę dla grafu G. Pojęcie ważności będzie używane, aby ocenić, jak bardzo dwa wierzchołki są do siebie podobne. Jeśli w sąsiedz- twie jednego z nich będzie brakowało ważnego wierzchołka (lub wierzchołków), to będą one oceniane jako niepodobne. Ważny wierzchołek, intuicyjnie, to np. reprezentacja pętli, w której znajduje się wiele instrukcji, lub przypisanie, na podstawie którego obliczanych jest wiele wartości w dalszej części kodu. Kolejną zmianą względem oryginalnego algorytmu jest przydzielanie nowych etykiet.

Tworzenie napisów si(v) dla wierzchołka v przebiega co do idei tak samo jak w oryginal- nym algorytmie, przy czym poza etykietą sąsiada, pamiętamy także jego ważność. W inny

sposób za to przekształcamy si(v) w nową, skompresowaną etykietę. Sposób ten ma za- pewnić, że wierzchołki, które nie różnią się w sposób znaczący pomiędzy sobą, otrzymają tą samą etykietę, przyczyniając się do większej wartości podobieństwa grafów. CDS, czyli podgraf PDG, zawierający wszystkie wierzchołki PDG, ale jedynie krawę- dzie zależności sterowania, jest drzewem o korzeniu w sztucznie wprowadzonym wierz- chołku wejściowym. W takim drzewie każdemu wierzchołkowi możemy przyporządkować jego głębokość (odległość od korzenia). Gdy przydzielamy nowe etykiety, dokonujemy po- równań między wierzchołkami w F i G na tych samych głębokościach, a także tych, które różnią się od siebie o nie więcej niż 1. Intuicyjnie, każdy poziom zagłębienia w drzewie to kolejne zagnieżdżenie w pętli lub instrukcji warunkowej. Heurystycznie zakładamy, że o ile łatwo pominąć lub dodać pojedynczą instrukcję, o tyle istnieje małe prawdopodobieństwo, aby ktoś napisał funkcję, obliczającą tę samą wartość, pomijając dwie zagnieżdżone pę- tle. Jednak, dla zachowania pewnej elastyczności, dopuszczamy wystąpienie tych samych instrukcji, które np. są zagnieżdżone w instrukcji warunkowej lub też nie.

83

i i

i i i “output” — 2018/6/21 — 7:43 — page 84 — #84 i i i

Opiszemy teraz porównanie dwóch wierzchołków, v1 i v2, mające na celu przydziele- nie tych samych skompresowanych etykiet podobnym wierzchołkom. Przede wszystkim, pierwszym warunkiem koniecznym, żeby przydzielić obu wierzchołkom tę samą etykietę, jest przydzielona ta sama etykieta w poprzedniej iteracji (początkowo jest to po pro-

stu typ wierzchołka). Razem z tymi wierzchołkami mamy skojarzone dwa napisy, si(v1)

oraz si(v2). Jeśli warunek konieczny jest spełniony, iterujemy po obu napisach, od lewej do prawej. Jeśli napotykamy taką samą etykietę w obu napisach, to przechodzimy w nich do następnego elementu. Załóżmy jednak bez straty ogólności, że etykiety różnią się i że mniejsza z nich (wg porządku, po którym posortowaliśmy oba napisy) znajduje się w napi-

sie si(v1). Wtedy zwiększamy licznik różnicy (na początku zainicjowany zerem) d między wierzchołkami o poziom ważności przyporządkowany tej mniejszej etykiecie. Następnie

przechodzimy do następnego elementu w si(v1), jednocześnie nie zmieniając rozpatrywa-

nego elementu w si(v2). Analogicznie postępujemy, jeśli mniejsza etykieta znajduje się

w si(v2). Jeśli w jednym z napisów skończyły się elementy do rozważenia, wszystkie pozo- stałe znaki w drugim napisie przyczyniają się do zwiększenia różnicy d w ten sam sposób, co do tej pory. Jest to sposób inspirowany sortowaniem przez scalanie (ang. merge sort). Ma on na celu porównanie, jak bardzo różnią się sąsiedzi obu wierzchołków. Zwróćmy uwagę, że jeśli np. porównujemy dwa wierzchołki reprezentujące pętle, znacznie większą wagę przywiążemy do tego, czy obie mają zagnieżdżoną pętlę wewnętrzną o wielu instruk- cjach, podczas gdy zignorujemy brak pojedynczego wywołania funkcji w jednej z nich.

Jeśli poziom różnicy d jest mniejszy niż obie mediany m1 i m2, uznajemy, że oba wierzchołki są dostatecznie podobne i należy im przyporządkować taką samą etykietę. W przeciwnym wypadku oba dostają różne etykiety. Wszystkim wierzchołkom, które nie zostały sparowane, bo np. występowały na głę- bokości, której nie było w drugim grafie, dostają nowe, wcześniej nieprzyporządkowane etykiety. Pseudokod algorytmu znajduje się w alg. 4 (całość algorytmu) i alg. 5 (parowanie dostatecznie podobnych wierzchołków). Należy zwrócić uwagę na to, czy takie podejście zapewnia, że wszystkie wierzchołki, które są do siebie podobne zgodnie z opisaną oceną podobieństwa, otrzymują tę samą etykietę. Problem w rzeczywistości przypomina hierarchiczną analizę skupień z odmien- nością najbliższego sąsiada (ang. single linkage, por. [46, 48, 51, 75, 76]). Załóżmy, że mamy już pewien zbiór wierzchołków, który otrzymał pewną etykietę l oraz pewien wierzchołek, który nie otrzymał jeszcze etykiety. Nasze podejście zakłada, że jeśli taki wierzchołek oka- zał się podobny do choć jednego wierzchołka z tego zbioru, otrzymuje tę samą etykietę l. Aby efektywnie przechowywać zbiory wierzchołków z przyporządkowaną nową etykietą oraz łączyć je, gdy zajdzie taka potrzeba (znajdą się dwa wierzchołki, należące do dwóch zbiorów, które są do siebie podobne), użyto struktury zbiorów rozłącznych (ang. disjoint

84

i i

i i i “output” — 2018/6/21 — 7:43 — page 85 — #85 i i i

set data structure), używanych także np. przy problemie minimalnego drzewa rozpinają- cego. Gdy przydzieliliśmy wszystkim wierzchołkom nowe, skompresowane etykiety w danej iteracji, pozostała część algorytmu przebiega tak jak oryginalnie: obliczamy podobieństwo w i-tej iteracji, by ostatecznie obliczyć całkowitą miarę ze wszystkich iteracji. Dodatkową modyfikacją algorytmu jest sprawdzanie, czy wektor skompresowanych etykiet jest taki sam jak w poprzedniej iteracji dla obu grafów. Jeśli tak, algorytm jest przerywany i wykonuje się mniej niż założone h iteracji. Przejdźmy teraz do oceny złożoności algorytmu. Większość kroków, tak jak w ory- ginalnej wersji, udaje się utrzymać w złożoności O(N). Należy zwrócić uwagę, że sor- towanie etykiet nie może się już odbywać przez zliczanie. Wynika to z faktu, że są one stowarzyszone z ważnościami wierzchołków. Dlatego należy użyć algorytmu sortowania opartego na porównaniach, a nie zliczaniu, np. sortowania szybkiego (ang. quick sort). Jednak to, co najbardziej zwiększa pesymistyczną złożoność algorytmu, to porównywanie par wierzchołków, które odbywa się w O(N 2). Zaznaczmy jednak, że badanie jedynie par wierzchołków występujących na podobnej głębokości (różnicącej się o nie więcej niż 1) zmniejsza ich liczbę. Poza tym dokładniejsze porównywanie odbywa się jedynie dla wierz- chołków, które mają tę samą etykietę, co także znacząco zmniejsza liczbę wykonywanych instrukcji. Wyniki eksperymentalne, raportowane w p. 3.5.5 potwierdzają, że algorytm Si- milaR wykonuje się jedynie nieznacznie wolniej niż algorytm W–L dla niskiego h, podczas gdy dla wysokiego h wykonuje się szybciej, co jest powodowane przerywaniem działania algorytmu, gdy wektor skompresowanych etykiet nie zmienia się.

3.4 Miary jakości używane przy problemach niezbalansowanych

Problem wykrywania funkcji podobnych jest problemem klasyfikacji binarnej o niezba- lansowanej liczbie klas, por. np. [59, 61, 76, 78], czyli takim, w którym w danych uczących i testowych jest znacznie więcej instancji jednej klasy (zazwyczaj oznaczanej jako klasa 0, jest to klasa, która nas nie interesuje) niż drugiej (zazwyczaj jest to klasa nas interesująca i oznacza się ją przez 1). W przypadku opisywanego problemu para funkcji podobnych należy do klasy 1, podczas gdy para funkcji niepodobnych należy do klasy 0. Warto zauważyć, że typowa miara jakości, jaką jest dokładność (ang. accuracy), czyli jaki procent wszystkich obserwacji został zaklasyfikowany do właściwej klasy (nie rozróż- niamy klasy 0 i 1), nie znajdzie tutaj zastosowania. Typowym przykładem, przywoływa- nym aby zilustrować problem z taką miarą oceny, jest model, który zawsze klasyfikuje obserwację do klasy 0. Jeśli mamy problem, w którym obserwacji klasy 1 jest zaledwie

85

i i

i i i “output” — 2018/6/21 — 7:43 — page 86 — #86 i i i

Algorytm 4: Algorytm SimilaR Wejście: 1. graf F 2. graf G

1 przydzielWażnośćWierzchołkom(F )

2 przydzielWażnośćWierzchołkom(G)

3 maksymalnaGłębokośćF ← przydzielGłębokości(F )

4 maksymalnaGłębokośćG ← przydzielGłębokości(G)

5 maksymalnaGłębokość ← max(maksymalnaGłębokośćF , maksymalnaGłębokośćG)

6 mF ← medianaWażności(F )

7 mG ← medianaWażności(G)

8 s ← 0

9 for iteracja i ∈ {1, 2, . . . , h} do // te wywołania przechodzą po sąsiadach każdego wierzchołka v i zapisują ich etykiety w v

10 zbierzEtykietySąsiadówWwierzchołkach(F )

11 zbierzEtykietySąsiadówWwierzchołkach(G)

12 for głębokość ∈ {maksymalnaGłębokość, maksymalnaGłębokość − 1,..., 1} do // te wywołania przydzielają te same etykiety wierzchołkom, które są dostatecznie podobne, pseudokog w alg. 5

13 parujWierzchołkiNaGłębokościach(głębokość, głębokość)

14 parujWierzchołkiNaGłębokościach(głębokość − 1, głębokość)

15 parujWierzchołkiNaGłębokościach(głębokość, głębokość − 1) // oblicz, jak bardzo różnią się etykiety w obu grafach

16 si ← obliczPodobieństwoWiteracji(F , G)

17 s += si

18 if brak zmiany w licznościach etykiet then

19 break

20 return s

86

i i

i i i “output” — 2018/6/21 — 7:43 — page 87 — #87 i i i

Algorytm 5: Parowanie dostatecznie podobnych wierzchołków

Wejście: 1. głębokośćF

2. głębokośćG

1 for wierzchołek vF ∈ wierzchołki w F na głębokości głębokośćF do

2 for wierzchołek vG ∈ wierzchołki w G na głębokości głębokośćG do

3 if vF i vG mają inne etykiety then

4 continue

F 5 E ← etykietySąsiadów(vF ) G 6 E ← etykietySąsiadów(vG)

7 różnicaF ← 0

8 różnicaG ← 0

9 iF ← 0

10 iG ← 0 F G 11 while iF < |E | ∧ iG < |E | do F G 12 if E = E then iF iG

13 iF += 1

14 iG += 1

F G 15 if E < E then iF iG F 16 różnica += ważność(E ) F iF F 17 różnica += ważność(E ) G iF

18 iF += 1

G F 19 if E < E then iG iF G 20 różnica += ważność(E ) F iG G 21 różnica += ważność(E ) G iG

22 iG += 1

F 23 while iF < |E | do F 24 różnica += ważność(E ) F iF F 25 różnica += ważność(E ) G iF

26 iF += 1

G 27 while iG < |E | do G 28 różnica += ważność(E ) F iG G 29 różnica += ważność(E ) G iG

30 iG += 1

31 if różnicaF < mF ∧ różnicaG < mG then // przydziel wierzchołkom tę samą etykietę

87

i i

i i i “output” — 2018/6/21 — 7:43 — page 88 — #88 i i i

2%, to otrzymamy dokładność na poziomie 98%, choć oczywiście sam model będzie bez- użyteczny. Potrzebne nam są zatem inne miary. Wprowadźmy następujące oznaczenia: — true positive (tp), trafienie (ang. hit) – para podobnych funkcji została zakwalifiko- wana jako podobna, — false positive (fp), fałszywy alarm (false alarm) – para niepodobnych funkcji została zakwalifikowana jako podobna, — true negative (tn), poprawne odrzucenie (correct rejection) – para niepodobnych funkcji została zakwalifikowana jako niepodobna, — false negative (fn), pudło (ang. miss) – para podobnych funkcji została zakwalifiko- wana jako niepodobna. Oczywiście widzimy, że określenie positive dotyczy klasy 1 (mniej licznej, bardziej interesującej), a określenie negative dotyczy klasy 0 (liczniejszej). Typowe miary używane przy problemach niezbalansowanych to (por. [76]): tp — precyzja = tp + fp tp — czułość = tp + fn 2 precision · recall — miara Fβ = (1 + β ) · β2·precision + recall , gdzie najczęściej używa się β = 1. Precyzja (ang. precision) intuicyjnie odpowiada na pytanie „jak wiele (stosunkowo) obserwacji zakwalifikowanych do klasy 1 jest rzeczywiście tym, czego szukamy?”, a czu- łość (ang. recall) na „jak wiele (stosunkowo) obserwacji należących do klasy 1 zostało rzeczywiście zakwalifikowanych jako klasa 1?”. Ponieważ w przypadku niektórych proble- mów trudno jest zdecydować, czy bardziej nam zależy na wysokiej czułości czy precyzji,

a także porównać dwa modele opisane tymi dwiema wartościami, stosuje się miarę Fβ

(ang. Fβ-measure), która dla β = 1 taką samą wagę przykłada do obu tych wartości i jest ich średnią harmoniczną (ozn. F ).

3.5 Wyniki eksperymentalne

Po opisaniu wszystkich metod i zdefiniowaniu miar jakości dla problemów niezbalanso- wanych, przechodzimy do wyników eksperymentalnych. Każdy algorytm porównywania był testowany dla wszystkich kompatybilnych dla niego sposobów reprezentacji funkcji oraz odpowiedniego zakresu parametrów sterujących metodą. Tak więc jeden scenariusz testowy składa się z następującej trójki: (algorytm, sposób reprezentacji, wartości parame- trów). Testy przeprowadzono osobno na każdym z 1080 zbiorów benchmarkowych, opisa- nych w podrozdz. 2.8. Zapisano wyniki zarówno dla wersji symetrycznych, jak i niesyme- trycznych. Te ostatnie zagregowano przy użyciu 7 funkcji wymienionych w podrozdz. 3.2. Nazwijmy więc scenariuszem testowym zagregowanym czwórkę (algorytm, sposób repre-

88

i i

i i i “output” — 2018/6/21 — 7:43 — page 89 — #89 i i i

zentacji, wartości parametrów, funkcja agregująca), przy czym funkcją agregującą może się okazać także skorzystanie z wersji symetrycznej danej metody. Wprowadźmy wartość progową θ. Jest to taka wartość, że pary o mierze podobień- stwa mniejszej niż ten próg były kwalifikowane jako niepodobne (klasa 0), a te o większej zostały uznane za podobne (1). Następnie dla każdego scenariusza testowego zagregowa- nego została znaleziona wartość θ ∈ {0,01; 0,02; ... 0,99}, która maksymalizuje miarę F na wszystkich zbiorach testowych jednocześnie. We wszystkich zestawieniach wyników pokazujemy najlepsze wartości parametrów wraz z progiem θ dla następującej trójki: (algorytm, sposób reprezentacji, funkcja agregu- jąca). Wartości precyzji, czułości i miary F są uśrednione po wszystkich zbiorach testowych w ten sposób, że podajemy medianę wraz z medianą odchyleń bezwzględnych (ang. me- dian absolute deviation) wokół mediany. Ponadto wybieraliśmy najlepszą wartość każdej z trzech miar (w sensie mediany), a następnie dokonywaliśmy testu sumy rang Wilcoxona, aby stwierdzić, których wyników nie jesteśmy w stanie odrzucić jako statystycznie zna- cząco gorszych od najlepszego wyniku. Takie wyniki są pogrubione. Jako poziom istotności przyjęliśmy α = 0,05. Następnie prezentujemy zestawienie miar jakości w zależności od wartości parame- trów sterujących daną metodą w rozbiciu na sposoby reprezentacji. Poza tym dołączamy wykresy czasu działania danej metody w zależności od rozmiaru zbioru oraz wartości parametrów (także w rozbiciu na sposoby reprezentacji). Na końcu tego podrozdziału znajduje się zestawienie podsumowujące wszystkie me- tody.

3.5.1 Odległość Levenshteina

Odległość Levenshteina nie jest w żaden sposób parametryzowana. Wartości precyzji, czu- łości oraz miary F dla każdego z możliwych ciągów wejściowych oraz funkcji agregującej zostały zebrane w tab. 3.3. Metoda osiągnęła dobre wyniki, mając jedynie w dwóch przy-

padkach wartość miary F poniżej 0,91. Gorsze wyniki odnotowano jedynie dla TŁ w po-

łączeniu z literami, SŁ w połączeniu z tokenami oraz Sm w połączeniu z wywołaniami funkcji. Niestety czas działania nie jest zaletą tej metody. Dokładne czasy zostały zilustrowane na rys. 3.3 oraz 3.4. Przy największych zbiorach, mających n = 1000 funkcji, dla wywołań funkcji jest to 464 sekund, dla tokenów 12871 sekund, a dla liter aż 41128 sekund. Oczy- wiście różnice te wynikają z faktu, że dowolny kod ma o wiele więcej liter niż tokenów, których z kolei jest mniej niż wywołań funkcji. Gdybyśmy więc chcieli używać tej metody jako jedynej, prawdopodobnie najlepiej

byłoby uruchamiać ją dla tokenów, używając M lub TŁ jako funkcji agregującej. Miara

89

i i

i i i “output” — 2018/6/21 — 7:43 — page 90 — #90 i i i

Tabela 3.3. Wartości miar jakości dla odległości Levenshteina . θ czułość precyzja miara F funkcja agregująca sekwencja wejściowa

symetrycznie litery 0,30 0, 941 ± 0, 116 0, 941 ± 0, 052 0, 928 ± 0,077 wywołania funkcji 0,45 0,892 ± 0,150 0, 961 ± 0,035 0,917 ± 0, 099 tokeny 0,63 0,909 ± 0,120 0,966 ± 0,032 0,929 ± 0,077

TŁ litery 0,01 0,784 ± 0,287 0,979 ± 0,063 0,866 ± 0,258 wywołania funkcji 0,01 0,910 ± 0,199 0,939 ± 0,053 0,918 ± 0,148 tokeny 0,29 0,916 ± 0,119 0,960 ± 0,036 0,931 ± 0,077

Tm litery 0,31 0,937 ± 0,135 0,936 ± 0,059 0,922 ± 0,093 wywołania funkcji 0,44 0,900 ± 0,158 0,954 ± 0,041 0,919 ± 0,107 tokeny 0,63 0,899 ± 0,126 0,968 ± 0,032 0,927 ± 0,081

Tp litery 0,11 0,942 ± 0,136 0,926 ± 0,065 0,918 ± 0,096 wywołania funkcji 0,24 0,899 ± 0,189 0,950 ± 0,044 0,919 ± 0,136 tokeny 0,41 0,915 ± 0,118 0,959 ± 0,037 0,930 ± 0,075

M litery 0,33 0,950 ± 0,131 0,919 ± 0,067 0,917 ± 0,094 wywołania funkcji 0,49 0,926 ± 0,181 0,929 ± 0,055 0,919 ± 0,129 tokeny 0,64 0,922 ± 0,114 0,955 ± 0,038 0,931 ± 0,074

SŁ litery 0,67 0,948 ± 0,136 0,922 ± 0,067 0,918 ± 0,097 wywołania funkcji 0,98 0,926 ± 0,181 0,929 ± 0,055 0,919 ± 0,129 tokeny 1,00 0,989 ± 0,040 0,142 ± 0,047 0,247 ± 0,069

Sm litery 0,44 0,909 ± 0,225 0,928 ± 0,074 0,908 ± 0,174 wywołania funkcji 0,61 0,814 ± 0,263 0,845 ± 0,150 0,812 ± 0,223 tokeny 0,71 0,916 ± 0,180 0,914 ± 0,064 0,902 ± 0,130

Sp litery 0,58 0,941 ± 0,152 0,925 ± 0,065 0,917 ± 0,108 wywołania funkcji 0,77 0,910 ± 0,213 0,921 ± 0,073 0,901 ± 0,163 tokeny 0,88 0,941 ± 0,110 0,943 ± 0,047 0,932 ± 0,075

F jest w tym wypadku na najwyższym poziomie, podczas gdy ta sekwencja wejściowa zajmuje drugie miejsce pod względem czasu działania.

3.5.2 Algorytm Smitha–Watermana

Algorytm Smitha–Watermana posiada najwięcej parametrów ze wszystkich opisanych me- tod. Są to: s(a, a) – wartość macierzy zamian dla dwóch tych samych elementów, s(a, b) – wartość macierzy zamian dla dwóch różnych elementów, v – kara za otworzenie luki, u – kara za kontynuację luki. Wszystkie te parametry zostały przetestowane dla warto- ści ze zbioru {1,..., 5}, przy czym założyliśmy, że nie ma sensu testować przypadków, gdy u > v, zgodnie z zasadą, że lepiej posiadać jedną dłuższą przerwę pomiędzy dopaso- wanymi sekwencjami, niż wiele krótszych. Warto zauważyć, że dla czytelności wszystkie wartości parametrów są raportowane jako liczby dodatnie, jednak s(a, b), u i v wpływają na obniżenie wartości dopasowania.

90

i i

i i i “output” — 2018/6/21 — 7:43 — page 91 — #91 i i i

40000 litery 35000 tokeny wywo ania funkcji 30000

25000

20000 czas [s] 15000

10000

5000

0 0 200 400 600 800 1000 n

Rysunek 3.3. Czas działania algorytmu obliczającego odległość Levenshteina w zależności od rozmiaru zbioru n

litery tokeny wywo ania funkcji symetrycznie niesymetrycznie

0 5000 10000 15000 20000

Rysunek 3.4. Czas działania algorytmu obliczającego odległość Levenshteina w zależności od sposobu reprezentacji funkcji i wersji (symetrycznej lub niesymetrycznej)

Wyniki dla najlepszych wartości parametrów (w rozbiciu na funkcje agregujące i ro- dzaje sekwencji wejściowych) są zebrane w tab. 3.4. Wyniki są nieco lepsze, niż dla odle-

91

i i

i i i “output” — 2018/6/21 — 7:43 — page 92 — #92 i i i

głości Levenstheina. Trudno wyciągnąć jednoznaczne i wspólne dla wszystkich sekwencji wejściowych wnioski dotyczące doboru parametrów, jednak możemy zauważyć, że wartość dopasowania tych samych elementów s(a, a) jest zawsze większa niż kara za kontynuowanie luki u. Co więcej, w większości przypadków zachodzi nierówność s(a, a) > s(a, b) (jedy- nie w dwóch przypadkach na 24 tak nie jest). Analogiczna nierówność, v > u, zachodzi w około połowy przypadków. Widać, że metoda działa najlepiej dla ciągów tokenów. Nie było powodów, aby odrzucić

którąkolwiek funkcję agregującą (poza Sm) jako statystycznie znacząco gorszą od najlep-

szej dla tego sposobu reprezentacji funkcji. Co więcej, w dwóch przypadkach (M i Sp), litery okazały się równie dobre, jak tokeny. Aby lepiej zrozumieć, jak wartości parametrów wpływają na wydajność metody, zamie- ściliśmy tab. 3.5 oraz 3.6. Przedstawiamy tam uśrednioną wartość miary F dla poszczegól- nych funkcji agregujących, w zależności od u i v (tab. 3.5) oraz s(a, a) i s(a, b) (tab. 3.6). Najlepsze wyniki uzyskujemy dla stosunkowo dużych wartości s(a, a), dużych lub małych (w zależności od funkcji agregującej) wartościach s(a, b), i małych wartościach u, v. Czas wykonania algorytmu w zależności od wielkości zbioru testowego można znaleźć na rys. 3.5. Widzimy, że czasy są bardzo małe. Jest to związane z faktem, że algorytm ten jest popularny w bioinformatyce, przez co można znaleźć naprawdę zaawansowane, wydajne implementacje metody Smitha-Watermana.

92

i i

i i i “output” — 2018/6/21 — 7:43 — page 93 — #93 i i i

Tabela 3.4. Wartości uzyskane dla algorytmu Smitha–Watermana. s(a, a) – wartość macierzy zamian dla dwóch tych samych elementów, s(a, b) – wartość macierzy zamian dla dwóch różnych elementów, v – kara za otworzenie luki, u – kara za kontynuację luki

s(a, a) s(a, b) v u θ czułość precyzja miara F funkcja agregująca reprezentacja

symetrycznie litery 4 1 4 1 0,16 0,934 ± 0,130 0,938 ± 0,053 0,925 ± 0,091 wywołania funkcji 4 1 1 1 0,31 0,908 ± 0,133 0,951 ± 0,039 0,919 ± 0,087 tokeny 4 3 4 1 0,39 0,908 ± 0,126 0,974 ± 0,027 0,938 ± 0,083

TŁ litery 4 1 1 1 0,16 0,647 ± 0,318 0,982 ± 0,082 0,780 ± 0,299 wywołania funkcji 4 1 1 1 0,13 0,837 ± 0,233 0,962 ± 0,044 0,892 ± 0,185 tokeny 4 4 4 1 0,15 0,920 ± 0,113 0,962 ± 0,032 0,939 ± 0,075

Tm litery 4 1 4 1 0,20 0,940 ± 0,114 0,938 ± 0,053 0,927 ± 0,077 wywołania funkcji 4 1 1 1 0,43 0,914 ± 0,120 0,942 ± 0,044 0,921 ± 0,079 tokeny 4 4 4 1 0,50 0,914 ± 0,123 0,971 ± 0,032 0,935 ± 0,080

Tp litery 4 1 1 1 0,13 0,894 ± 0,186 0,955 ± 0,051 0,911 ± 0,136 wywołania funkcji 4 1 1 1 0,22 0,923 ± 0,112 0,936 ± 0,045 0,924 ± 0,074 tokeny 4 4 4 1 0,30 0,926 ± 0,111 0,958 ± 0,035 0,937 ± 0,072

M litery 4 1 4 1 0,24 0,984 ± 0,097 0,904 ± 0,067 0,930 ± 0,076 wywołania funkcji 4 1 1 1 0,45 0,959 ± 0,098 0,905 ± 0,058 0,921 ± 0,068 tokeny 3 3 3 1 0,52 0,950 ± 0,090 0,940 ± 0,048 0,938 ± 0,064

SŁ litery 3 1 1 1 0,44 0,990 ± 0,101 0,893 ± 0,072 0,925 ± 0,081 wywołania funkcji 3 1 1 1 0,86 0,943 ± 0,144 0,909 ± 0,061 0,904 ± 0,098 tokeny 3 4 4 1 0,84 0,968 ± 0,076 0,920 ± 0,051 0,935 ± 0,056

Sm litery 4 1 4 1 0,33 0,990 ± 0,159 0,902 ± 0,075 0,925 ± 0,117 wywołania funkcji 4 1 1 1 0,66 0,827 ± 0,253 0,880 ± 0,124 0,842 ± 0,210 tokeny 3 4 3 1 0,6 0,957 ± 0,140 0,906 ± 0,065 0,910 ± 0,098

Sp litery 4 1 4 1 0,47 0,988 ± 0,101 0,902 ± 0,068 0,930 ± 0,079 wywołania funkcji 4 1 1 1 0,70 0,969 ± 0,096 0,882 ± 0,081 0,898 ± 0,076 tokeny 4 4 4 1 0,81 0,963 ± 0,081 0,931 ± 0,050 0,938 ± 0,059

93

i i

i i i “output” — 2018/6/21 — 7:43 — page 94 — #94 i i i – – – – 0 0 0 0 , , , , 293 294 287 263 , , , , 0 0 0 0 ± ± ± ± 387 657 693 647 , , , , – –– –– – 0 5 0 5 0 5 0 5 , , , , 285 0 286 0 289 0 264 0 267 287 262 265 , , , , , , , , 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± 365 638 687 645 377 643 801 651 , , , , , , , , – kara za kontynuację luki u 0––– 4 0––– 4 0––– 4 0––– 4 , , , , 282 0 299 0 273 0 265 0 283 0 301 0 274 0 263 0 292 295 279 281 , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 p ± ± ± ± ± ± ± ± ± ± ± ± p Ł Ł T S T S 341 624 673 640 361 657 832 673 396 671 689 658 , , , , , , , , , , , , (f) (d) (h) (b) 0–––– 3 0–––– 3 0 3 0–––– 3 , , , , 254 0 283 0 277 0 274 0 266 0 292 0 276 0 223 0 309 0 283 0 224 0 279 0 256 268 328 270 , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 405 679 698 698 413 712 841 702 415 721 709 691 665 739 712 734 , , , , , , , , , , , , , , , , 0 0 2 0 2 0 2 0 2 , , , , 1 1 1 1 312 0 273 0 229 0 274 0 276 0 266 0 227 0 276 0 308 0 270 0 213 0 271 0 287 0 271 0 265 0 334 280 295 323 267 , , , , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 – kara za otworzenie luki, 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± v ± ± ± ± ± 561 728 738 718 581 730 853 719 555 730 712 721 674 741 741 , , , , , , , , , , , , , , , 731 762 770 741 764 , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 v v v v u u u u , , , , , , , , , , , , , , , , , , , , 5 5 5 5 4 4 4 4 3 3 3 3 2 2 2 2 1 1 1 1 – – – 0 0 0 0 , , , , 257 260 280 268 , , , , 0 0 0 0 ± ± ± ± 694 715 674 527 , , , , –– – – 0–– 5 –– 0 5 0 5 0 5 , , , , 258 281 247 246 0 262 0 279 0 258 0 , , , , , , , 0 0 0 0 0 0 0 ± ± ± ± ± ± ± 703 668 511 694 712 667 522 , , , , , , , ––– ––– ––– 0––– 4 0 4 0 4 0 4 , , , , 265 260 0 278 293 0 277 276 0 261 259 261 0 264 0 286 0 271 0 , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± m m M S T 720 713 682 673 536 523 705 695 693 709 665 519 , , , , , , , , , , , , (e) (c) (g) Symetrycznie 0–––– 3 0–––– 3 0–––– 3 0–––– 3 , , , , 261 0 249 0 276 0 253 0 254 0 276 0 259 0 267 267 0 276 212 0 309 267 0 251256 0 0 295 , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (a) ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 733 701 563 721 721 556 718 753 723 697 681 589 536 702 700 769 , , , , , , , , , , , , , , , , 0 0 2 0 2 0 2 0 2 , , , , 1 1 1 1 259 0 269 0 277 0 262 0 276 0 286 0 264 0 278 0 262 0 263 0 272 0 301 0 280 0 254276 0 0 303 284 295 308 310 , , , , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 740 735 594 754 733 588 729 761 741 753 732 601 583 729 728 Uśredniona po sekwencjach wejściowych, zbiorach testowych i reszcie parametrów miara F dla algorytmu Smitha–Watermana , , , , , , , , , , , , , , , 795 775 799 784 633 , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 v v v v u u u u , , , , , , , , , , , , , , , , , , , , 3 3 3 4 4 4 1 2 3 1 2 5 1 2 5 1 2 5 4 5 w wersji symetrycznej oraz przy użyciu funkcji agregujących w zależności od Tabela 3.5.

94

i i

i i i “output” — 2018/6/21 — 7:43 — page 95 — #95 i i i 0 , 244 262 298 257 260 , , , , , 0 0 0 0 0 0 0 0 , , , ± ± ± ± ± 274 271 274 245 241 279 261 252 246 258 260 295 267 269 234 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 718 704 709 808 808 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± , , , , , 738 785 804 776 810 801 657 769 778 259 438 500 591 597 783 , , , , , , , , , , , , , , , 0 0 0 0 5 , 234 0 259 0 328 0 272 0 258 0 , , , , , 0 0 0 0 0 0 5 0 5 0 5 , , , ± ± ± ± ± 271 0 267 0 271 0 258 0 249 0 254 0 260250 0 0 295275 0 0 269239 0 0 302 265 266 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 719 718 728 815 813 ± ± ± ± ± ± ± ± ± ± ± ± , , , , , ± ± ± 0 745 798 808 784 815 807 268 466 515 637 603 749 , , , , , , , , , , , , 837 848 852 , , , 0 4 , 0 0 0 273 0 230 0 271 0 250 0 263 , , , , , 0 0 0 0 0 0 4 0 4 0 4 , , , ± ± ± ± ± 263 0 257 0 239 0 236 0 247 0 232 0 300 258 260 263276 0 0 296260 0 0 267273 0 0 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Ł 723 721 807 818 , , , , 824 S ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± , p p Ł T S T 753 803 806 795 813 812 281 681 534 805 615 789 453 667 762 , , , , , , , , , , , , , , , (f) 0 3 , (d) (h) (b) 237 0 233 0 247 0 271 0 238 0 , , , , , 0 0 0 0 0 3 0 3 0 3 0 , , , ± ± ± ± 235 0 267 0 244 0 266 0 233 0 257 0 261 0 271 0 271 0 262 0 246 0 273 0 461 0 273 0 241 0 , , , , , , , , , , , , , , , ± 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 736 748 812 821 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± , , , , 828 0 0 , 761 809 807 341 789 614 812 647 808 487 718 675 809 775 793 , , , , , , , , , , , , , , , 0 2 , 244 0 246 0 260 242 0 230 0 2 0 2 0 2 , , , , , , , , 0 0 0 0 0 257 0 251 0 255 0 278 0 241 0 292 0 275 0 259 0 232 0 257285 0 0 260243 0 0 276243 0 0 , , , , , , , , , , , , , , , ± ± 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 748 781 , , 824 831 826 , , , 784 811 810 376 800 658 810 693 813 521 753 682 819 783 814 0 0 0 , , , , , , , , , , , , , , , – wartość macierzy zamian dla dwóch tych samych elementów, ) ) ) 1 0 0 0 0 0 0 0 , , , , , ) ) ) ) 1 ) 1 ) 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 0 0 00 0 0 00 0 0 1 2 3 4 5 , , , , , , , , , , , , , , , a, b 5 5 5 1 1 1 4 4 4 2 3 2 3 2 3 a, a a, a ( ( a, b a, b a, b a, a a, a a, a ( s s ( ( ( ( ( ( s s s s s s s 0 , 0 0 0 , , , 271 263 216 238 267 , , , , , 235 241 266 239 249 261 264 231 289 278 231 258 232 271 232 0 0 0 0 0 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 814 809 769 699 604 , , , , , 805 791 808 811 814 803 673 658 653 652 798 680 758 471 588 0 , , , , , , , , , , , , , , , 0 0 0 0 5 , 0 5 0 5 0 5 , , , 268 0 251 0 273 0 266 0 258 , , , , , 270273 0 0 259256 0 0 253287 0 0 277254 0 0 256274 0 0 271266 0 0 0 0 0 0 256 250 273 , , , , , , , , , , , , 0 , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 817 811 708 609 , , , , 835 801 805 815 813 660 659 663 771 686 761 483 583 , , , , , , , , , , , , , 847 846 769 0 , , , 0 0 0 0 4 , 0 4 0 4 0 4 , , , 255 0 251 0 257 232 0 265 0 , , , , , – wartość macierzy zamian dla dwóch różnych elementów. 238243 0 0 240239 0 0 258233 0 0 275243 0 249 0 255246 0 243 0 271000 0 277 0 0 0 0 0 0 , , , , , , , , , , , , , , , ) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± m m M a, b 815 815 805 701 620 S T , , , , , ( 808 808 812 809 669 667 679 786 808 698 770 812 495 598 679 , , , , , , , , , , , , , , , s (e) (c) (g) 0 3 Symetrycznie , 0 3 0 3 0 3 , , , 247 0 236 0 270 0 000 0 237 0 , , , , , 237246 0 0 252272 0 0 244269 0 0 276254 0 241 0 0 239127 0 238 0 0 267259 0 236 0 0 0 0 0 0 0 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (a) ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 816 818 817 769 687 , , , , , 810 809 812 811 677 672 712 799 812 737 776 816 510 608 681 0 , , , , , , , , , , , , , , , 0 2 , 0 2 0 2 0 2 , , , 252 0 262 0 256 0 262 0 241 , , , , , 249247 0 0 000237 0 0 235278 0 0 248233 0 236 0 0 252272 0 235 0 0 272240 0 261 0 0 0 0 0 0 , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 817 819 783 707 , , , , 823 812 810 811 807 686 682 739 806 817 755 783 818 573 613 683 , , , , , , , , , , , , , , , , 0 Uśredniona po sekwencjach wejściowych, zbiorach testowych i reszcie parametrów miara F dla algorytmu Smitha–Watermana ) ) 1 ) 1 ) 1 ) 1 ) ) ) 0 0 0 0 0 0 0 00 0 0 00 0 0 00 0 0 00 0 0 0 0 00 0 0 0 0 0 0 00 0 0 0 0 , , , , , , , , , , , , , , , , , , , , 5 4 3 2 4 5 4 5 4 5 1 2 3 1 2 3 1 1 2 3 a, b a, b a, b a, b a, a a, a a, a a, a ( ( ( ( ( ( ( ( s s s s s s s s Tabela 3.6. w wersji symetrycznej oraz przy użyciu funkcji agregujących w zależności od

95

i i

i i i “output” — 2018/6/21 — 7:43 — page 96 — #96 i i i

300 litery tokeny 250 wywo ania funkcji

200

150 czas [s]

100

50

0 0 200 400 600 800 1000 n

Rysunek 3.5. Czas działania algorytmu Smitha–Watermana w zależności od rozmiaru zbioru n

3.5.3 Zachłanne kafelkowanie ciągów

Algorytm zachłannego kafelkowania ciągów ma jeden parametr: jest to minimalna długość dopasowania d. W testach przyjęliśmy d ∈ {1, 2,..., 20}. Wyniki dla najlepszej wartości d, dla każdego sekwencyjnego sposobu reprezentacji kodu i funkcji agregującej zostały ze- brane w tab. 3.7. W przypadku tej metody typ sekwencji wejściowej zdaje się nie mieć aż tak wielkiego znaczenia, jak sposób agregacji wyników. Dla podejścia symetrycznego,

a także TŁ, Tp, M, SŁ przynajmniej dwa sposoby reprezentacji były niegorsze od pary o najlepszym wyniku (litery, M). Wartości miar jakości dla tej metody dla wszystkich parametrów ilustrują rys. 3.6, 3.7 i 3.8. Widać, że zawsze istnieje jedna wartość d, która maksymalizuje miarę F. Wszystkie wartości niższe oraz wyższe dają niższą miarę F. Dla liter jest to wartość około 5, wywołań funkcji 1, a tokenów 11–19. Metoda działa szybciej niż odległość edycyjna, choć nadal jest to jedna z wolniejszych metod. Wykres czasu działania od rozmiaru zbioru i wartości parametru d można zobaczyć na rys. 3.9 i 3.10. Warto zwrócić uwagę, że im większa wartość d, tym krótszy jest czas działania metody. To naturalne, biorąc pod uwagę, że nie szukamy powtarzających się wzorców krótszych niż d. To tym bardziej ważna obserwacja, że w wielu przypadkach tokeny okazywały się niegorsze od najlepszego wyniku (podczas gdy najlepsze d dla nich potrafiło sięgać nawet wartości 16).

96

i i

i i i “output” — 2018/6/21 — 7:43 — page 97 — #97 i i i

Tabela 3.7. Wartości uzyskane dla zachłannego kafelkowania ciągów

d θ czułość precyzja miara F funkcja agregująca reprezentacja

symetrycznie litery 4 0,26 0,945 ± 0,087 0,950 ± 0,057 0,938 ± 0,063 wywołania funkcji 1 0,49 0,909 ± 0,107 0,970 ± 0,039 0,933 ± 0,071 tokeny 13 0,27 0,945 ± 0,077 0,951 ± 0,042 0,939 ± 0,052

TŁ litery 2 0,23 0,885 ± 0,174 0,958 ± 0,051 0,908 ± 0,127 wywołania funkcji 1 0,33 0,930 ± 0,092 0,961 ± 0,042 0,937 ± 0,062 tokeny 8 0,28 0,925 ± 0,095 0,964 ± 0,036 0,937 ± 0,063

Tm litery 7 0,21 0,951 ± 0,080 0,941 ± 0,063 0,934 ± 0,062 wywołania funkcji 1 0,65 0,870 ± 0,131 0,984 ± 0,029 0,921 ± 0,087 tokeny 13 0,34 0,929 ± 0,091 0,957 ± 0,042 0,937 ± 0,060

Tp litery 3 0,25 0,931 ± 0,116 0,962 ± 0,048 0,933 ± 0,079 wywołania funkcji 1 0,45 0,916 ± 0,103 0,969 ± 0,039 0,936 ± 0,069 tokeny 11 0,26 0,934 ± 0,088 0,964 ± 0,036 0,941 ± 0,058

M litery 6 0,44 0,953 ± 0,111 0,955 ± 0,050 0,946 ± 0,081 wywołania funkcji 1 0,65 0,945 ± 0,078 0,939 ± 0,052 0,935 ± 0,057 tokeny 11 0,51 0,974 ± 0,064 0,939 ± 0,049 0,944 ± 0,048

SŁ litery 7 0,78 0,978 ± 0,073 0,930 ± 0,062 0,943 ± 0,062 wywołania funkcji 4 0,75 0,937 ± 0,217 0,897 ± 0,076 0,896 ± 0,156 tokeny 16 0,74 0,990 ± 0,074 0,917 ± 0,059 0,934 ± 0,055

Sm litery 7 0,51 0,991 ± 0,129 0,904 ± 0,074 0,928 ± 0,099 wywołania funkcji 4 0,52 0,872 ± 0,269 0,839 ± 0,124 0,828 ± 0,210 tokeny 19 0,48 0,944 ± 0,193 0,895 ± 0,075 0,895 ± 0,135

Sp litery 7 0,71 0,961 ± 0,122 0,944 ± 0,056 0,945 ± 0,091 wywołania funkcji 3 0,74 0,929 ± 0,227 0,895 ± 0,096 0,889 ± 0,172 tokeny 14 0,75 0,975 ± 0,128 0,929 ± 0,056 0,930 ± 0,084

3.5.4 Odległość q-gramowa

Odległość q-gramowa ma jeden parametr: q. Była ona testowana dla wartości q ∈ {1,..., 20}. Wyniki dla najlepszych wartości tego parametru przedstawia tab. 3.8, a wy- niki dla wszystkich wartości q zostały pokazane na rys. 3.11, 3.12 i 3.13. Metoda, pomimo swojej prostoty, okazuje się być bardzo skuteczna. Najlepsze wyniki uzyskano dla toke- nów oraz wywołań funkcji, dla q z zakresu 4–7. Jeśli chodzi o funkcje agregujące, miarę

F maksymalizowały TŁ,Tp,M i podejście symetryczne. Pomimo testowania metody dla szerokiego zakresu q, w zdecydowanej większości przy- padków najlepsze wyniki uzyskiwano dla małych wartości (mniejszych równych pięć). Warto zwrócić uwagę na wywołania funkcji, gdzie q = 1, czyli bardzo dobre wyniki dało zwykłe zliczanie wywołań funkcji. Jest to najszybsza metoda. Tak jak poprzednio, zebraliśmy czasy działań metody w zależności od rozmiaru zbioru (tab. 3.14) i parametru q (tab. 3.15). Czas działania nawet dla największych zbiorów nie przekracza 200 sekund.

97

i i

i i i “output” — 2018/6/21 — 7:43 — page 98 — #98 i i i

Tabela 3.8. Wartości uzyskane dla q-gramów

q θ czułość precyzja miara F funkcja agregująca reprezentacja

symetrycznie litery 3,0 0,41 0,952 ± 0,112 0,955 ± 0,045 0,942 ± 0,075 wywołania funkcji 1,0 0,72 0,925 ± 0,103 0,981 ± 0,022 0,948 ± 0,065 tokeny 5,0 0,63 0,938 ± 0,095 0,973 ± 0,026 0,950 ± 0,060

TŁ litery 2,0 0,14 0,906 ± 0,199 0,962 ± 0,047 0,923 ± 0,148 wywołania funkcji 1,0 0,39 0,943 ± 0,091 0,974 ± 0,027 0,952 ± 0,058 tokeny 4,0 0,33 0,951 ± 0,083 0,970 ± 0,030 0,953 ± 0,054

Tm litery 3,0 0,32 0,960 ± 0,085 0,946 ± 0,051 0,941 ± 0,059 wywołania funkcji 1,0 0,66 0,910 ± 0,114 0,982 ± 0,022 0,940 ± 0,073 tokeny 8,0 0,42 0,931 ± 0,102 0,974 ± 0,024 0,948 ± 0,065

Tp litery 3,0 0,16 0,965 ± 0,094 0,944 ± 0,052 0,941 ± 0,066 wywołania funkcji 1,0 0,45 0,948 ± 0,088 0,969 ± 0,030 0,950 ± 0,057 tokeny 4,0 0,44 0,942 ± 0,089 0,974 ± 0,028 0,951 ± 0,057

M litery 3,0 0,39 0,978 ± 0,087 0,931 ± 0,057 0,941 ± 0,065 wywołania funkcji 1,0 0,70 0,943 ± 0,091 0,974 ± 0,027 0,952 ± 0,058 tokeny 7,0 0,51 0,973 ± 0,073 0,947 ± 0,038 0,952 ± 0,049

SŁ litery 3,0 0,78 0,978 ± 0,087 0,931 ± 0,057 0,941 ± 0,065 wywołania funkcji 2,0 0,84 0,975 ± 0,119 0,918 ± 0,053 0,929 ± 0,078 tokeny 8,0 0,89 0,981 ± 0,076 0,938 ± 0,043 0,948 ± 0,052

Sm litery 7,0 0,24 0,999 ± 0,083 0,879 ± 0,078 0,922 ± 0,074 wywołania funkcji 4,0 0,31 0,780 ± 0,280 0,858 ± 0,129 0,800 ± 0,230 tokeny 16,0 0,32 0,943 ± 0,173 0,870 ± 0,078 0,886 ± 0,124

Sp litery 4,0 0,63 0,980 ± 0,108 0,925 ± 0,059 0,938 ± 0,080 wywołania funkcji 2,0 0,74 0,937 ± 0,197 0,917 ± 0,061 0,905 ± 0,138 tokeny 11,0 0,60 0,976 ± 0,118 0,918 ± 0,056 0,928 ± 0,081

3.5.5 Algorytmy grafowe

W tym punkcie zbieramy wyniki z trzech metod: znajdowanie największego wspólnego podgrafu algorytmem McGregora, algorytm Weisfeilera–Lehmana oraz jego modyfikacja zaproponowana przez nas (algorytm SimilaR). Pierwszy z algorytmów nie jest parametry- zowany, podczas gdy pozostałe dwa mają ten sam parametr h, oznaczający liczbę iteracji. Testowaliśmy te metody dla wartości h ∈ {1, 2,..., 20}. Wyniki dla parametru maksymalizującego miarę F, w rozbiciu na funkcje agregujące, zostały zebrane w tab. 3.9. Widzimy, że najgorsze wyniki dał algorytm McGregora. Zdecy- dowanie lepsze wyniki dał algorytm Weisfeilera–Lehmana oraz SimilaR, przy czym Simi- laR okazał się dawać statystycznie znacząco lepsze wyniki. Co więcej, wyniki algorytmu SimilaR okazały się być najlepszymi wynikami wśród wszystkich testowanych metod,

nie tylko grafowych. Miarę F maksymalizowały funkcje agregujące TŁ, Tp, M oraz podej- ście symetryczne. Potrzebna liczba iteracji okazała się mała: optymalne h było zazwyczaj wartością rzędu 2–3.

98

i i

i i i “output” — 2018/6/21 — 7:43 — page 99 — #99 i i i

Tabela 3.9. Wartości uzyskane dla algorytmów grafowych

h θ czułość precyzja miara F funkcja agregująca metoda

symetrycznie algorytm McGregora – 0,45 0,783 ± 0,184 0,952 ± 0,062 0,849 ± 0,138 algorytm W–L 3,0 0,71 0,932 ± 0,111 0,978 ± 0,027 0,946 ± 0,071 SimilaR 2,0 0,63 0,957 ± 0,089 0,988 ± 0,017 0,967 ± 0,055

TŁ algorytm McGregora – 0,42 0,683 ± 0,218 0,968 ± 0,062 0,791 ± 0,179 algorytm W–L 2,0 0,63 0,923 ± 0,114 0,984 ± 0,023 0,946 ± 0,074 SimilaR 2,0 0,39 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053

Tm algorytm McGregora – 0,61 0,768 ± 0,189 0,965 ± 0,052 0,853 ± 0,143 algorytm W–L 2,0 0,75 0,916 ± 0,121 0,985 ± 0,022 0,943 ± 0,078 SimilaR 2,0 0,66 0,941 ± 0,108 0,993 ± 0,013 0,963 ± 0,068

Tp algorytm McGregora – 0,40 0,770 ± 0,187 0,947 ± 0,066 0,844 ± 0,142 algorytm W–L 2,0 0,58 0,936 ± 0,109 0,977 ± 0,028 0,948 ± 0,069 SimilaR 2,0 0,44 0,957 ± 0,088 0,990 ± 0,016 0,968 ± 0,054

M algorytm McGregora – 0,65 0,764 ± 0,193 0,939 ± 0,078 0,824 ± 0,149 algorytm W–L 2,0 0,82 0,923 ± 0,114 0,984 ± 0,023 0,946 ± 0,074 SimilaR 2,0 0,71 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053

SŁ algorytm McGregora – 0,85 0,944 ± 0,087 0,195 ± 0,092 0,319 ± 0,113 algorytm W–L 20,0 0,85 0,916 ± 0,148 0,581 ± 0,150 0,692 ± 0,135 SimilaR 6,0 0,82 0,988 ± 0,038 0,645 ± 0,116 0,771 ± 0,090

Sm algorytm McGregora – 0,80 0,578 ± 0,225 0,266 ± 0,219 0,355 ± 0,184 algorytm W–L 3,0 0,84 0,881 ± 0,146 0,679 ± 0,153 0,748 ± 0,133 SimilaR 2,0 0,83 0,965 ± 0,068 0,961 ± 0,041 0,955 ± 0,048

Sp algorytm McGregora – 0,85 0,825 ± 0,168 0,367 ± 0,227 0,497 ± 0,191 algorytm W–L 6,0 0,85 0,923 ± 0,126 0,752 ± 0,139 0,811 ± 0,117 SimilaR 3,0 0,87 0,981 ± 0,048 0,946 ± 0,046 0,956 ± 0,040

Wyniki w zależności od parametru h przedstawiają rys. 3.16 i 3.17. Tak jak wspomnie- liśmy wcześniej, najlepsze wyniki uzyskiwano już dla małej liczby h i dalsze zwiększanie liczby iteracji nie poprawiało już wyników. Podsumowując czasy działania, naturalnie najdłużej wykonuje się algorytm McGre- gora. Za to pozostałe dwa algorytmy działają bardzo szybko, dając się wyprzedzić jedynie metodzie q-gramowej i algorytmowi Smitha–Watermana, co tym bardziej sprawia, że al- gorytm SimilaR wydaje się być najlepszym wyborem ze wszystkich testowanych podejść. Dokładne zestawienie można zobaczyć na rys. 3.18 i 3.19. Ze względu na bardzo długi czas obliczeń metody McGregora, wyniki przedstawione są jedynie do n = 500.

3.5.6 Podsumowanie wyników

W tym rozdziale przetestowaliśmy 7 metod. Wyniki możemy rozpatrywać w dwóch ka- tegoriach: skuteczności, a także czasu wykonania. Zestawienie wyników znajduje się w tab. 3.10, 3.11 i 3.12. W tabeli 3.10 znajdują się wszystkie pary (funkcja agregująca, me-

99

i i

i i i “output” — 2018/6/21 — 7:43 — page 100 — #100 i i i

Tabela 3.10. Podsumowanie wyników – każda funkcja agregująca osobno . θ czułość precyzja miara F czas [s] fun. agreg. metoda reprez.

symetrycznie odległość edycyjna tokeny 0,63 0,909 ± 0,120 0,966 ± 0,032 0,929 ± 0,077 965 symetrycznie algorytm Smitha–Watermana tokeny 0,39 0,908 ± 0,126 0,974 ± 0,027 0,938 ± 0,083 65 symetrycznie zachłanne kafelkowanie ciągów tokeny 0,27 0,945 ± 0,077 0,951 ± 0,042 0,939 ± 0,052 337 symetrycznie q-gramy tokeny 0,63 0,938 ± 0,095 0,973 ± 0,026 0,950 ± 0,060 61 symetrycznie SimilaR graf 0,63 0,957 ± 0,089 0,988 ± 0,017 0,967 ± 0,055 122

TŁ odległość edycyjna tokeny 0,29 0,916 ± 0,119 0,960 ± 0,036 0,931 ± 0,077 4658 TŁ algorytm Smitha–Watermana tokeny 0,16 0,920 ± 0,113 0,962 ± 0,032 0,939 ± 0,075 59 TŁ zachłanne kafelkowanie ciągów wywoł. fun. 0,33 0,930 ± 0,092 0,961 ± 0,042 0,937 ± 0,062 10 TŁ q-gramy tokeny 0,33 0,951 ± 0,083 0,970 ± 0,030 0,953 ± 0,054 27 TŁ SimilaR graf 0,39 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053 122

Tm odległość edycyjna tokeny 0,63 0,899 ± 0,126 0,968 ± 0,032 0,927 ± 0,081 4658 Tm algorytm Smitha–Watermana tokeny 0,50 0,914 ± 0,123 0,971 ± 0,032 0,935 ± 0,080 61 Tm zachłanne kafelkowanie ciągów tokeny 0,34 0,929 ± 0,091 0,957 ± 0,042 0,937 ± 0,060 337 Tm q-gramy tokeny 0,42 0,931 ± 0,102 0,974 ± 0,024 0,948 ± 0,065 90 Tm SimilaR graf 0,66 0,941 ± 0,108 0,993 ± 0,013 0,963 ± 0,068 122

Tp odległość edycyjna tokeny 0,41 0,915 ± 0,118 0,959 ± 0,037 0,930 ± 0,075 4658 Tp algorytm Smitha–Watermana tokeny 0,30 0,926 ± 0,111 0,958 ± 0,035 0,937 ± 0,072 63 Tp zachłanne kafelkowanie ciągów tokeny 0,26 0,934 ± 0,088 0,964 ± 0,036 0,941 ± 0,058 459 Tp q-gramy tokeny 0,44 0,942 ± 0,089 0,974 ± 0,028 0,951 ± 0,057 27 Tp SimilaR graf 0,44 0,957 ± 0,088 0,990 ± 0,016 0,968 ± 0,054 122

M odległość edycyjna tokeny 0,64 0,922 ± 0,114 0,955 ± 0,038 0,931 ± 0,074 4658 M algorytm Smitha–Watermana tokeny 0,52 0,950 ± 0,090 0,940 ± 0,048 0,938 ± 0,064 68 M zachłanne kafelkowanie ciągów litery 0,44 0,953 ± 0,111 0,955 ± 0,050 0,946 ± 0,081 1389 M q-gramy tokeny 0,51 0,973 ± 0,073 0,947 ± 0,038 0,952 ± 0,049 82 M SimilaR graf 0,71 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053 122

SŁ odległość edycyjna wywoł. fun. 0,98 0,926 ± 0,181 0,929 ± 0,055 0,919 ± 0,129 193 SŁ algorytm Smitha–Watermana tokeny 0,84 0,968 ± 0,076 0,920 ± 0,051 0,935 ± 0,056 69 SŁ zachłanne kafelkowanie ciągów litery 0,78 0,978 ± 0,073 0,930 ± 0,062 0,943 ± 0,062 1152 SŁ q-gramy tokeny 0,89 0,981 ± 0,076 0,938 ± 0,043 0,948 ± 0,052 90 SŁ SimilaR graf 0,82 0,988 ± 0,038 0,645 ± 0,116 0,771 ± 0,090 201

Sm odległość edycyjna litery 0,44 0,909 ± 0,225 0,928 ± 0,074 0,908 ± 0,174 12783 Sm algorytm Smitha–Watermana litery 0,33 0,990 ± 0,159 0,902 ± 0,075 0,925 ± 0,117 121 Sm zachłanne kafelkowanie ciągów litery 0,51 0,991 ± 0,129 0,904 ± 0,074 0,928 ± 0,099 1152 Sm q-gramy litery 0,24 0,999 ± 0,083 0,879 ± 0,078 0,922 ± 0,074 255 Sm SimilaR graf 0,83 0,965 ± 0,068 0,961 ± 0,041 0,955 ± 0,048 122

Sp odległość edycyjna tokeny 0,88 0,941 ± 0,110 0,943 ± 0,047 0,932 ± 0,075 4658 Sp algorytm Smitha–Watermana tokeny 0,81 0,963 ± 0,081 0,931 ± 0,050 0,938 ± 0,059 63 Sp zachłanne kafelkowanie ciągów litery 0,71 0,961 ± 0,122 0,944 ± 0,056 0,945 ± 0,091 1152 Sp q-gramy litery 0,63 0,980 ± 0,108 0,925 ± 0,059 0,938 ± 0,080 95 Sp SimilaR graf 0,87 0,981 ± 0,048 0,946 ± 0,046 0,956 ± 0,040 144

Tabela 3.11. Podsumowanie wyników – każda metoda, dla najlepszej funkcji agregującej i najlepszego sposobu reprezentacji . θ czułość precyzja miara F czas [s] funkcja agregująca metoda reprez.

Sp odległość edycyjna tokeny 0,88 0,941 ± 0,110 0,943 ± 0,047 0,932 ± 0,075 4658 TŁ algorytm Smitha–Watermana tokeny 0,16 0,920 ± 0,113 0,962 ± 0,032 0,939 ± 0,075 59 M zachłanne kafelkowanie ciągów litery 0,44 0,953 ± 0,111 0,955 ± 0,050 0,946 ± 0,081 1389

TŁ q-gramy tokeny 0,33 0,951 ± 0,083 0,970 ± 0,030 0,953 ± 0,054 27 M SimilaR graf 0,71 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053 122

toda), wraz z tym sposobem reprezentacji funkcji. który okazał się najlepszy pod względem miary F. Tabela 3.11 zawiera wszystkie metody, dla których wybrano maksymalizującą miarę F parę (funkcja agregująca, sposób reprezentacji). Najczęściej pojawiły się tokeny,

a także funkcje agregujące M i TŁ. W tab. 3.12 zebrano wszystkie funkcje agregujące,

100

i i

i i i “output” — 2018/6/21 — 7:43 — page 101 — #101 i i i

Tabela 3.12. Podsumowanie wyników – każda funkcja agregująca, dla najlepszej metody i najlepszego sposobu reprezentacji . θ czułość precyzja miara F czas [s] funkcja agregująca metoda reprez.

symetrycznie SimilaR graf 0,63 0,957 ± 0,089 0,988 ± 0,017 0,967 ± 0,055 122

TŁ SimilaR graf 0,39 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053 122 Tm SimilaR graf 0,66 0,941 ± 0,108 0,993 ± 0,013 0,963 ± 0,068 122

Tp SimilaR graf 0,44 0,957 ± 0,088 0,990 ± 0,016 0,968 ± 0,054 122 M SimilaR graf 0,71 0,956 ± 0,086 0,992 ± 0,014 0,970 ± 0,053 122

SŁ q-gramy tokeny 0,89 0,981 ± 0,076 0,938 ± 0,043 0,948 ± 0,052 90 Sm SimilaR graf 0,83 0,965 ± 0,068 0,961 ± 0,041 0,955 ± 0,048 122

Sp SimilaR graf 0,87 0,981 ± 0,048 0,946 ± 0,046 0,956 ± 0,040 144

dla których wybrano optymalną parę (metoda, sposób reprezentacji). Warto odnotować, że w 7 przypadkach na 8 najlepszą metodą okazał się algorytm SimilaR. Pod względem czasu wykonania, zdecydowanie da się rozróżnić algorytmy szybkie oraz wolne. Do tych pierwszych należą algorytm Smitha–Watermana, odległość q-gramowa oraz algorytmy Weisfeilera–Lehmana i SimilaR. Pozostałe, takie jak odległość Levenshte- ina, zachłanne kafelkowanie ciągów czy algorytm McGregora znajdowania największego wspólnego podgrafu były o wiele wolniejsze. Warto zaznaczyć, że dwa algorytmy, które okazały się zarówno szybkie, jak i sku- teczne, czyli algorytm Smitha-Watermana i algorytm Weisfeilera–Lehmana, choć znane z literatury w innych zastosowaniach, nigdy nie były używane w kontekście wykrywania podobieństwa kodu, zaś algorytm SimilaR, stworzony specjalnie na potrzeby tej pracy, sprawił, że można było wykorzystać PDG zarówno szybko, jak i skutecznie (najlepiej pod tym względem ze wszystkich metod), w kontraście do pozostałych dwóch testowa- nych algorytmów działających na tej strukturze danych. Gdy rozważamy sekwencyjne sposoby reprezentacji, to najczęściej najlepszym okazy- wały się tokeny. Nie jest to zaskakujące: są one ogólniejsze niż litery, jednak nie tracą aż tak wielu informacji, jak same wywołania funkcji. Zwłaszcza zdają się być dobrym wy- borem dla algorytmów, których celem jest znalezienie wspólnych, odpowiednio długich, podciągów. Warto jednak zaznaczyć, że bardzo dobre wyniki dały też 1-gramy dla wywo- łań funkcji, czyli ich zwykłe zliczanie. Okazuje się, że tak prosta i szybka metoda niewiele ustępowała bardziej skomplikowanym i bardziej czasochłonnym algorytmom. Jednak dość dużym zaskoczeniem było istnienie metod, które dawały dobre wyniki, opierając się na li- terach. Wydawać się może, że litery niedostatecznie dobrze uogólniają kod, jednak wyniki, przynajmniej w niektórych przypadkach, temu przeczą. Dobór sekwencji wejściowych ma także duży wpływ na czas wykonania poszczególnych algorytmów. Tutaj, co oczywiste, obliczenia dla liter trwały najdłużej, a te dla wywołań funkcji najkrócej.

101

i i

i i i “output” — 2018/6/21 — 7:43 — page 102 — #102 i i i

Na koniec warto zauważyć, że niektóre z metod, jak np. zachłanne kafelkowanie ciągów użyte dla tokenów, są bezpośrednio zaczerpnięte z innych systemów służących wykrywa- niu podobnego kodu (w tym wypadku jest to JPlag [107]). Dlatego prezentowana praca, choć pozornie nie porównuje się z innymi tego typu narzędziami, w rzeczywistości do- konuje takiego porównania. Co więcej, narzędzia te nie są zazwyczaj przystosowane dla języka R (mogą jedynie traktować go jako czysty tekst). W naszym zestawieniu dokonali- śmy odpowiedniej adaptacji dla każdego podejścia. W tab. 1.1 dokonujemy szczegółowego zestawienia, które pary (algorytm, sposób reprezentacji) mają swoje odzwierciedlenie w li- teraturze.

102

i i

i i i “output” — 2018/6/21 — 7:43 — page 103 — #103 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(g) Sm (h) Sp

Rysunek 3.6. Miary jakości zachłannego kafelkowania ciągów dla liter w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

103

i i

i i i “output” — 2018/6/21 — 7:43 — page 104 — #104 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(g) Sm (h) Sp

Rysunek 3.7. Miary jakości zachłannego kafelkowania ciągów dla wywołań funkcji w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

104

i i

i i i “output” — 2018/6/21 — 7:43 — page 105 — #105 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 d d

(g) Sm (h) Sp

Rysunek 3.8. Miary jakości zachłannego kafelkowania ciągów dla tokenów w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

105

i i

i i i “output” — 2018/6/21 — 7:43 — page 106 — #106 i i i

14000 litery 12000 tokeny wywo ania funkcji

10000

8000

czas [s] 6000

4000

2000

0 0 200 400 600 800 1000 n

Rysunek 3.9. Czas działania algorytmu zachłannego kafelkowania ciągów w zależności od rozmiaru zbioru n

8000 litery 7000 tokeny wywo ania funkcji 6000

5000

4000 czas [s] 3000

2000

1000

0 0 5 10 15 20 d

Rysunek 3.10. Czas działania algorytmu zachłannego kafelkowania ciągów w zależności od minimalnej długości dopasowania d

106

i i

i i i “output” — 2018/6/21 — 7:43 — page 107 — #107 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(g) Sm (h) Sp

Rysunek 3.11. Miary jakości q-gramów dla liter w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

107

i i

i i i “output” — 2018/6/21 — 7:43 — page 108 — #108 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(g) Sm (h) Sp

Rysunek 3.12. Miary jakości q-gramów dla wywołań funkcji w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

108

i i

i i i “output” — 2018/6/21 — 7:43 — page 109 — #109 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 q q

(g) Sm (h) Sp

Rysunek 3.13. Miary jakości q-gramów dla tokenów w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

109

i i

i i i “output” — 2018/6/21 — 7:43 — page 110 — #110 i i i

1000 litery tokeny 800 wywo ania funkcji

600

czas [s] 400

200

0 0 200 400 600 800 1000 n

Rysunek 3.14. Czas działania algorytmu q-gramowego w zależności od rozmiaru zbioru n

450 litery 400 tokeny wywo ania funkcji 350

300

250

200 czas [s]

150

100

50

0 0 5 10 15 20 q

Rysunek 3.15. Czas działania algorytmu q-gramowego w zależności od q

110

i i

i i i “output” — 2018/6/21 — 7:43 — page 111 — #111 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(g) Sm (h) Sp

Rysunek 3.16. Miary jakości algorytmu W–L w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

111

i i

i i i “output” — 2018/6/21 — 7:43 — page 112 — #112 i i i

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(a) Symetrycznie (b) TŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(c) Tm (d) Tp

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(e) M (f) SŁ

1.0 1.0 czu o czu o precyzja precyzja miara F miara F 0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0 0 5 10 15 20 0 5 10 15 20 h h

(g) Sm (h) Sp

Rysunek 3.17. Miary jakości algorytmu SimilaR w podziale na różne funkcje agregujące (oraz w wersji symetrycznej)

112

i i

i i i “output” — 2018/6/21 — 7:43 — page 113 — #113 i i i

105 Algorytm McGregora Algorytm W­L 104 SimilaR

103

czas [s] 102

101

100 0 200 400 600 800 1000 n (a) Wszystkie trzy metody

700 Algorytm W­L 600 SimilaR

500

400

czas [s] 300

200

100

0 0 200 400 600 800 1000 n (b) Metody wielomianowe

Rysunek 3.18. Czas działania algorytmów grafowych w zależności od rozmiaru zbioru n

350 Algorytm W­L SimilaR 300

250

200 czas [s]

150

100

50 0 5 10 15 20 h

Rysunek 3.19. Czas działania wielomianowych algorytmów porównujących grafy w zależności od parametru h

113

i i

i i i “output” — 2018/6/21 — 7:43 — page 114 — #114 i i i

114

i i

i i i “output” — 2018/6/21 — 7:43 — page 115 — #115 i i i

Wykrywanie podobnych par funkcji 4

W poprzednim rozdziale opisaliśmy, jakimi metodami możemy porównać pary funkcji. Każdy opisany algorytm zwraca pewną miarę podobieństwa z przedziału [0, 1]. Jednak dla końcowego użytkownika szereg n liczb pochodzących z różnych sposobów porówna- nia może być przytłaczający i nieinformatywny. Dlatego ważnym elementem opisywa- nego systemu jest podjęcie jednoznacznej decyzji na podstawie kilku miar podobieństwa, czy zakwalifikować daną parę funkcji jako podobną czy nie. Możemy spojrzeć na każdą taką metodę jako pojedynczy klasyfikator, które następnie będą łączone w celu osią- gnięcia lepszej dokładności, które to podejście jest powszechnie spotykane w literatu- rze [24, 25, 53, 106, 131, 132]. Problem kwalifikacji pary funkcji jako wartych wyświetlenia użytkownikowi możemy rozpatrywać na dwa sposoby: jako problem klasyfikacji binarnej lub regresji. W tym pierwszym przypadku podejmujemy jedną z dwóch decyzji: funkcje są podobne (i warte przedstawienia użytkownikowi końcowemu) albo nie. Nie ma żadnej gradacji pomiędzy tymi dwoma grupami. W drugim przypadku taka gradacja występuje. Każda para otrzy- muje ostateczną miarę podobieństwa z przedziału [0, 1], a następnie są one wyświetlane w sposób posortowany, ewentualnie pary funkcji poniżej pewnego progu podobieństwa nie są zwracane wcale. Pozostała część rozdziału jest zorganizowana w następujący sposób: w podrozdz. 4.1 opisujemy proponowaną przez nas metodę agregacji, która ma pożądane własności, a która we wstępnych publikowanych pracach dawała obiecujące efekty [9, 12]. Dokładny opis, które metody opisane w poprzednim rozdziale zostały użyte w celu podjęcia ostatecznej decyzji, sposób testowania nasze podejścia, a także wyniki eksperymentalne znajdują się w podrozdz. 4.2. Analiza statystyczna, pozwalająca na lepsze zrozumienie, jak poszcze- gólne metody zależą od siebie, znajduje się w podrozdz. 4.3.

i i

i i i “output” — 2018/6/21 — 7:43 — page 116 — #116 i i i

4.1 Proponowana metoda agregacji modeli

Wszystkie metody i scenariusze będą opierać się na pewnym typowym dla uczenia maszy- nowego podejściu: najpierw zbierzemy i dostarczymy próbę uczącą, dzięki której nauczymy model, dla jakich wartości poszczególnych metod należy podejmować decyzję o wyświetle- niu funkcji użytkownikowi końcowemu (czy to przez klasyfikację binarną czy za pomocą wartości z przedziału [0, 1]). Pojedyncza obserwacja w próbie uczącej składa się z pary funkcji, a także n wartości z przedziału [0, 1], które zwróciło n wybranych metod. Poza tym znajduje się informacja o tym, czy para funkcji jest podobna (klasa 0 lub 1), a także oszacowany stopień podobieństwa (wartość z przedziału [0, 1]). Dokładny opis tworzenia zbiorów uczących w tej pracy znajduje się w p. 4.2.1. W przypadku metod klasyfikacji binarnej użyjemy znanych z literatury algorytmów, takich jak drzewo decyzyjne, lasy losowe, maszyny wektorów podpierająych, k najbliż- szych sąsiadów (dla k = 5), regresja logistyczna czy Naiwny Bayes, zaś w przypadku war- tości z przedziału [0, 1] użyjemy regresji. Więcej o wybranych metodach piszemy w pod- rozdz. 4.2. Zauważmy jednak, że powyższe metody mają pewną wadę: nie spełniają one pewnych użytecznych formalnych własności, jak bycie funkcją niemalejącą ze względu na każdą zmienną, idempotentność, symetria itp. Sprawia to, że są trudne w analizie i interpre- tacji. Często nie możemy stwierdzić, jak ważna jest pojedyncza cecha dla ostatecznej decyzji, ani jak zmieni się ona w zależności od zwiększenia o jakąś wartość pojedynczej cechy. Dlatego w tym rozdziale, poza testowaniem znanych z literatury modeli, proponu- jemy nasz własny, który spełnia wszystkie te cechy. Definicje wymienionych cech funkcji agregujących (a także jej definicję) zamieszczamy w p. 4.1.1. Co więcej, zwróćmy uwagę na pewną kwestię związaną z agregacją wyników z różnych metod: każda z nich posługuje się jakąś swoją miarą podobieństwa, a które nie są porów- nywalne. Jeśli jedna metoda zwróciła wartość 0,66, to wcale nie oznacza takiego samego stopnia podobieństwa, jak wtedy, gdy wartość 0,66 zwróci inna metoda. Dlatego pożądany byłby mechanizm, który dokonywałby ujednolicenia wartości zwracanych przez poszcze- gólne metody. Taki mechanizm znajduje się w proponowanym modelu. Jednak zanim przejdziemy do jego opisu, wprowadźmy pewne pojęcia dotyczące funkcji agregujących.

4.1.1 Funkcja agregująca

Funkcją agregującą o k argumentach F : [0, 1]k → [0, 1] jest przekształcenie które spełnia co najmniej następujące warunki: — jest niemalejąca ze względu na każdą zmienną, tj. dla każdego x, y ∈ [0, 1]k speł-

niających x 6k y (po wszystkich współrzędnych), mamy F (x) 6 F (y),

116

i i

i i i “output” — 2018/6/21 — 7:43 — page 117 — #117 i i i

— F (0, 0,..., 0) = 0, — F (1, 1,..., 1) = 1, zob. [18, 44, 50]. Co więcej, funkcję nazywamy idempotentną jeśli F (x, x, . . . , x) = x dla wszystkich

x ∈ [0, 1] i symetryczną gdy F (x1, x2, . . . , xk) = F (xσ(1), xσ(2), . . . , xσ(k)) dla dowolnej per- mutacji σ zbioru {1, 2, . . . , k}. Funkcje agregujące są bardzo przydatne w wielu praktycz- nych zastosowaniach, jak rozpoznawanie wzorców, wielokryterialne podejmowanie decyzji, statystyka, logika rozmyta itd. [30, 55, 89]

4.1.2 Krzywe B-sklejane

W dopasowywaniu średniej quasi-arytmetycznej, Beliakov et al. [15, 17, 19, 20] używał krzywych B-sklejanych aby modelować nieznane funkcje generujące. My także zastoso- waliśmy ten sposób do opisywanego problemu, a wyniki raportowaliśmy w [8, 9, 12]. Przechowywanie krzywych czy powierzchni przy użyciu wielomianów w bazie Bernsteina lub jako krzywe B-sklejane znajduje także wiele innych zastosowań [7, 36, 134].

Niech p > 1 (stopień wielomianu) oraz t = (t1, . . . , tk) będzie rosnąco uporządkowanym wektorem węzłów o długości k dla pewnego k > 0 takim że 0 < ti < ti+1 < 1 dla wszystkich

i = 1, . . . , k. Dla uproszczenia notacji, przyjmijmy, że ti = 0 dla i < 1 i ti = 1 dla i > k. Funkcja bazowa splajnów dla j = 0, . . . , p i x ∈ [0, 1] jest zdefiniowana rekurencyjnie jako:

 t  1 jeśli x ∈ [ti−1, ti[, Ni,j(x) = (j = 0)  0 w przeciwnym wypadku,

t x − ti−1 t ti+j − x t Ni,j(x) = Ni,j−1(x) + Ni+1,j−1(x), (j > 0) ti+j−1 − ti−1 ti+j − ti przy użyciu konwencji ·/0 = 0. Zauważmy, że wektor równoodległych węzłów jest popularnym wyborem. Na rys. 4.1 możemy zobaczyć przykładowe funkcje bazowe splajnów. Dodatkowo, niech v ∈ [0, 1]η będzie wektorem punktów kontrolnych, gdzie η = p+k+1. t Funkcja Bv : [0, 1] → [0, 1] dana jako: η t X t Bv(x) = viNi−p,p(x), (4.1) i=1 jest nazywana nieokresową krzywą B-sklejaną stopnia p opartą o wektor węzłów t i gene- rowaną przez wektor punktów kontrolnych v, patrz np. [117]. W szczególności, dla p = 1 otrzymujemy kawałkami liniową funkcję interpolującą

(0, v1), (t1, v2),..., (tk, vη−1), (1, vη). Z drugiej strony, dla p = 3 otrzymujemy kubiczne krzywe B-sklejane.

117

i i

i i i “output” — 2018/6/21 — 7:43 — page 118 — #118 i i i

1.0 1.0

0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0

0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0 (a) p = 1, k = 2 (b) p = 3, k = 2

t Rysunek 4.1. Przykładowe funkcje bazowe krzywych B-sklejanych Nj−p,p(x) jako funkcja x, j = 1, ....., p + k + 1; t jest wektorem równoodległych węzłów, k jest liczbą węzłów wewnętrznych, podczas gdy p jest stopniem wielomianu

Wprowadźmy teraz pojęcie powierzchni B-sklejanej. Mając dane p, q > 1, dwa wektory

węzłów u = (u1, . . . , uh) i w = (w1, . . . , wl) oraz macierz punktów kontrolnych V ∈

η1×η2 [0, 1] , η1 = p + h + 1, η2 = q + l + 1, krzywą B-sklejaną jest iloczyn tensorowy dwóch u,w 2 krzywych B-sklejanych, Bv : [0, 1] → [0, 1] takich, że:

η1 η2 u,w X X u w BV (x, y) = vi,jNi−p,p(x)Nj−q,q(y). i=1 j=1

4.1.3 Przypadek jednowymiarowy

Niech n > 1 oznacza liczbę metod, których wyniki chcemy zagregować. Załóżmy, że mamy (1) (m) n×m dane m > n wektorów wejściowych w postaci X = [x ,..., x ] ∈ [0, 1] razem z m pożądanymi wartościami wyjściowymi Y = [y(1), . . . , y(m)] ∈ [0, 1]1×m. Zadanie zaproponowanej idempotyzacji oraz dopasowywania ważonej średniej aryt- metycznej polega na znalezieniu funkcji przypominającej operator OMA (ang. Ordered Modular Averages) [99]: n X Fϕ,w(x) = wiϕi(xi), i=1 która minimalizuje sumę różnic podniesionych do kwadratu pomiędzy wektorem Y

i Fϕ,w(X). Tutaj w oznacza wektor wag, a ϕ1, . . . , ϕn : [0, 1] → [0, 1] są pewnymi ostro rosnącymi ciągłymi bijekcjami.

118

i i

i i i “output” — 2018/6/21 — 7:43 — page 119 — #119 i i i

Oczywiście, ponieważ istnieje nieskończenie wiele funkcji które mogą zostać użyte w modelu który szukamy, musimy ograniczyć przeszukiwaną przestrzeń, aby zadanie dało się rozwiązać. W naszym przypadku, dla ustalonego p oraz wektora węzłów t o długości k,

zakładamy, że ϕi – użyte, aby znormalizować i-tą zmienną, i = 1, . . . , n – jest nieokresową krzywą B-sklejaną: η X t ϕi(x) = ciNj−p,p(x), j=1 dla pewnego wektora punktów kontrolnych c o długości η = p + k + 1 posortowanego

rosnąco. Zauważmy, że warunek 0 = c1 < c2 < ··· < cη−1 < cη = 1 zapewnia, że ϕi jest ostro rosnąca, ciągła i na [0, 1]. Zatem, zbiór możliwych do otrzymania funkcji możemy zapisać jako: n n η X X X (i) t Fc,w(x) = wix˜i = wi cj Nj−p,p(xi), i=1 i=1 j=1

Pn (i) (i) (i) (i) (i) (i) gdzie w1, . . . , wn > 0, i=1 wi = 1, c1 = 0, cη = 1, c2 − c1 > 0,..., cη − cη−1 > 0

dla wszystkich i = 1, . . . , n. Zauważmy, że Fc,w jest idempotentna i niemalejąca względem (j) (j) (j) każdej zmiennej x˜ = (ϕ1(x1 ), . . . , ϕn(xn )). Aby uniknąć przeuczenia na zbiorze testowym, chcielibyśmy rozważyć pewną formę regularyzacji modelu.

Podsumowując, dla pewnego ustalonego współczynnika regularyzacji Tiknohova λw ∈ R, interesuje nas następujące zadanie optymalizacyjne: 2 m  n η   n X X X (i) t  (l) (l) X 2 minw,c  wi cj Nj−p,p xi  − y  + λw wi , l=1 i=1 j=1 i=1 pod warunkiem wyżej wymienionych ograniczeń.

Aby rozwiązać powyższe zadanie w praktyce, musimy je przepisać na zadanie minima- lizacji dwuetapowej. Wewnętrzna część, dla ustalonego w, optymalizuje c i może być za- pisana w formie standardowego zadania programowania kwadratowego (z ograniczeniami liniowymi, przy okazji zauważmy, że możemy policzyć raz, na początku, wartości funkcji bazowych splajnów dla każdego elementu w X i później używać ich bez ponownego oblicza- nia). Zewnętrzny poziom minimalizacji odpowiada za znalezienie optymalnych w i może zostać rozwiązane przez dowolny algorytm optymalizacji nieliniowej – w naszym przy- padku użyliśmy metody CMA-ES [57] oraz logarytmicznych funkcji barierowych, które zapewniły spełnienie warunków na wektor w.

4.1.4 Przypadek dwuwymiarowy

W przypadku dwuwymiarowym zmienia się macierz X = [x(1),..., x(m)] ∈ [0, 1]2n×m: zamiast jednej wartości zwracanej przez każdą metodę, rozważamy dwie. Wektor Y =

119

i i

i i i “output” — 2018/6/21 — 7:43 — page 120 — #120 i i i

[y(1), . . . , y(m)] ∈ [0, 1]1×m nie ulega zmianie. (j) (j) Proponujemy agregację dwóch niesymetrycznych miar podobieństwa xi i xn+i aby (j) otrzymać jedną wartość xˆi . Każda miara powinna zostać rozpatrzona osobno dla każdego i = 1, . . . , n, jako że poszczególne miary nie są porównywalne względem siebie. Następ- nie użyjemy średniej ważonej (która, jak wspomnieliśmy, ma bardzo wiele użytecznych (j) własności), aby połączyć wszystkie xˆi w jedną wartość. Chcemy rozwiązać następujące zadanie optymalizacji:

m n !2 X X (j) (j) minw wixˆi − y , j=1 i=1

T (j)  (j) (j)  ze spełnieniem warunków 1 w = 1 i w >n 0, patrz [16]. Co więcej, xˆi = ϕi xi , xn+i , 2 gdzie ϕ1, . . . , ϕn : [0, 1] → [0, 1] są pewnymi automatycznie wygenerowanymi symetrycz- nymi binarnymi funkcjami agregującymi. Zatem, nasza funkcja powinna przyjąć następu- jącą postać: n  (j) X  (j) (j)  Dϕ,w x = wiϕi xi , xn+i . i=1

Zauważmy, że Dϕ,w jest funkcją agregującą przyjmującą 2n argumentów. Co więcej, jest ona idempotentną funkcją agregującą przyjmującą n argumentów ze względu na zmienną (j) (j) (j) (j) (j) xˆ = (ϕ1(x1 , xn+1), . . . , ϕn(xn , xn+n)).

Tak jak poprzednio, użyjemy funkcji B-sklejanych, aby modelować ϕ1, . . . , ϕn. Ponie- waż są to funkcje dwóch zmiennych, użyjemy powierzchni B-sklejanych, gdzie, przy użyciu odpowiednich warunków, zapewnimy odpowiednie własności takiej funkcji: aby była nie- malejąca, symetryczna i spełnianiała dwa warunki brzegowe. Aby zapobiec niepotrzebnej komplikacji modelu, użyjemy iloczynu tensorowego dwóch krzywych B-sklejanych posiadających ten sam stopień, p, opartych na wspólnym wektorze

węzłów t o długości k. Każda ϕi jest zatem dana wzorem:

η η  (j) (j)  X X (i) t  (j) t  (j)  ϕi xi , xn+i = vα,βNα−p,p xi Nβ−p,p xn+i , α=1 β=1

dla pewnej macierzy punktów kontrolnych V(i) ∈ [0, 1]η×η, η = p + k + 1, spełniających następujące warunki: (i) (i) (i) (i) — vα,β 6 vα+1,β i vα,β 6 vα,β+1 dla każdego α, β, (i) (i) — v1,1 = 0 i vη,η = 1, (i) (i) — vα,β = vβ,α dla każdego α, β,

które zapewniają, że ϕi jest symetryczną funkcją agregującą.

120

i i

i i i “output” — 2018/6/21 — 7:43 — page 121 — #121 i i i

Ostatecznie docieramy do problemu optymalizacyjnego:

m  X minw,V(1),...,V(n)  j=1 n η η X X X (i) t  (j) t  (j)  (j) wi vα,βNα−p,p xi Nβ−p,p xn+i − y i=1 α=1 β=1 2  ,

T (i) (i) (i) (i) (i) (i) z warunkami w >n 0, 1 w = 1, v1,1 = 0, vη,η = 1, vα,β = vβ,α, vα+1,β − vα,β > 0, (i) (i) vα,β+1 − vα,β > 0, dla wszystkich α, β, i.

Tak jak poprzednio, dzielimy powyższy problem na dwa poziomy: w wewnętrznym, dla ustalonego w obliczamy wartości punktów kontrolnych (tak jak poprzednio, da się ten problem sprowadzić do programowania kwadratowego), podczas gdy zewnętrzna część, zajmująca się optymalizacją w, jest rozwiązywana metodą CMA-ES [57] (oczywiście do- wolny inny algorytm optymalizacji nieliniowej może zostać tutaj użyty).

4.2 Wyniki eksperymentalne

Po wprowadzeniu podstaw teoretycznych i przedstawieniu proponowanego modelu agre- gacji, w tym podrozdziale przedstawimy sposoby testowania poszczególnych modeli pre- dykcyjnych, a także sposoby tworzenia zbiorów testowych w różnych scenariuszach.

4.2.1 Scenariusze testowe

Zbiory testowe. W przypadku testowania metod agregacji i podejmowania decyzji, po- trzebowaliśmy więcej zbiorów niż poprzednio. Potrzebowaliśmy dobrze wydzielonej próby uczącej, a także próby testowej. Dlatego stworzyliśmy osobną, dodatkową, rodzinę zbio- rów, taką jak opisana w podrozdz. 2.8 (1080 zbiorów), z której korzystaliśmy na etapie nauki, podczas gdy dotychczasowa rodzina zbiorów, z której korzystaliśmy w rozdziale 3, posłużyła jako zbiór testowy. Następnie obliczyliśmy wartości wybranych metod porównywania funkcji dla każdej pary funkcji w każdym zbiorze, zarówno w próbie uczącej, jak i testowej (oczywiście wyniki w próbie testowej to te same, które przedstawialiśmy w poprzednim rozdziale). Przyjęliśmy następujące podejście: model uczy się na wszystkich wynikach z próby uczącej (czyli wszystkich parach funkcji w 1080 zbiorach), jednak potem dokonujemy predykcji na każdym zbiorze z próby testowej z osobna (tak więc liczymy miary błędów osobno dla każdego zbioru), by ostatecznie dokonać ich agregacji (tak jak poprzednio, raportujemy medianę i medianę odchyleń bezwzględnych, ang. median absolute deviation).

121

i i

i i i “output” — 2018/6/21 — 7:43 — page 122 — #122 i i i

Dobór metod porównywania. Podziału metod obliczania podobieństwa kodu, któ- rych używamy przy predykcji, dokonujemy dwojako: po pierwsze wybieramy, z wyników których algorytmów skorzystamy w modelu. Po drugie, gdy są one już ustalone, okre- ślamy, jak chcemy interpretować ich wyniki, por. podrozdz. 3.2 dotyczący symetryczności oraz agregacji wyników niesymetrycznych. W testach użyliśmy następujących zbiorów metod, opisanych w rozdz. 3: — Cały zbiór metod, będziemy sie do niego odwoływać przez określenie wszystkie. Jest to komplet algorytmów, dla wszystkich kompatybilnych dla nich sposobów repre- zentacji funkcji. Jedynym wyjątkiem jest algorytm McGregora znajdowania naj- większego wspólnego podgrafu, który ma złożoność wykładniczą i liczy się o wiele za długo, aby mógł być brany pod uwagę w praktycznym zastosowaniu, a wyniki zwracane przez niego nie były obiecujące. Oczywiście każdą metodę uruchamiamy z najlepszymi wartościami parametrów, zgodnie z wnioskami z rozdz. 3. Nie po- dajemy tych wartości w tym miejscu celowo, gdyż wpływ na wybór parametrów ma sposób interpretacji wyników metod, o którym piszemy w dalszej części tego podrozdziału. Tak więc są to: 1) odległość edycyjna – litery; 2) odległość edycyjna – wywołania funkcji; 3) odległość edycyjna – tokeny; 4) algorytm Smitha–Watermana – litery; 5) algorytm Smitha–Watermana – wywołania funkcji; 6) algorytm Smitha–Watermana – tokeny; 7) zachłanne kafelkowanie ciągów – litery; 8) zachłanne kafelkowanie ciągów – wywołania funkcji; 9) zachłanne kafelkowanie ciągów – tokeny; 10) odległość q-gramowa – litery; 11) odległość q-gramowa – wywołania funkcji; 12) odległość q-gramowa – tokeny; 13) algorytm Weisfeilera–Lehmana; 14) algorytm SimilaR. — Kolejnym zbiorem metod są wszystkie metody, ale jedynie dla takiego sposobu re- prezentacji funkcji, dla której dany algorytm zwracał najlepsze wyniki zgodnie z po- przednim rozdziałem. Jedynym wyjątkiem są algorytmy grafowe, gdzie zdecydowali- śmy się wybrać jedynie algorytm SimilaR, który dawał najlepsze wyniki. Taki zbiór nazywamy najlepszy ciąg: 1) odległość edycyjna – tokeny; 2) algorytm Smitha–Watermana – tokeny;

122

i i

i i i “output” — 2018/6/21 — 7:43 — page 123 — #123 i i i

3) zachłanne kafelkowanie ciągów – litery/tokeny (wersja używająca funkcji agre- gującej/symetryczna, więcej informacji w dalszej części); 4) odległość q-gramowa – tokeny; 5) algorytm SimilaR. — Ostatnim rodzajem metod w tym podziale są metody, których czas wykonania jest najmniejszy. Przy okazji zdecydowaliśmy, aby w odległości q-gramowej użyć wy- wołań funkcji: ma to służyć zapewnieniu, pomimo małej liczby metod, skupienie się na różnych aspektach badanego kodu. Taki zbiór algorytmów będziemy nazy- wać szybkimi: 1) algorytm Smitha–Watermana – tokeny; 2) odległość q-gramowa – wywołania funkcji; 3) algorytm SimilaR. Po opisaniu zbiorów metod, przejdźmy do drugiego sposobu podziału scenariuszy te- stowych. Tak jak napisaliśmy w podrozdz. 3.2, każda metoda została skonstruowana w ten sposób, że może wracać jedną, symetryczną wartość, która opisuje ogólne podobieństwo dwóch funkcji, ale też może zwrócić dwie wartości: jak bardzo pierwsza funkcja zawiera się w drugiej oraz jak bardzo druga zawiera się w pierwszej. Zaproponowaliśmy też różne funkcje, którymi można agregować takie dwie wartości do jednej. Podział, jakiego doko- nujemy w tym wymiarze, wygląda następująco: — Wszystkie metody zwracają wartości symetryczne. Nie dokonujemy żadnej agregacji. Takie podejście nazywamy symetrycznym. — Dla każdej metody wybieramy funkcję agregującą, która maksymalizowała medianę miary F w poprzednim rozdziale. Może się zdarzyć, że podejście symetryczne mak- symalizowało medianę miary F. Jeśli jednak tak nie było, metoda zwraca dwie war- tości, które są agregowane do jednej przy użyciu odpowiedniej funkcji agregującej. Takie podejście nazwiemy agregującym. — Ostatni sposób polega na uruchomieniu wszystkich metod w wersji niesymetrycznej. Następnie model opiera się bezpośrednio na obu wartościach, przy czym upewniamy się, że jedna cecha zawsze zawiera wartość mniejszą, a druga większą ze zwróconych. Takie podejście nazwiemy niesymetrycznym. Gdy już wymieniliśmy podziały scenariuszy, możemy przedstawić dokładnie, które metody, z jakimi parametrami sterującymi, towarzyszącymi sposobami reprezentacji oraz funkcjami agregującymi znalazły się w poszczególnych scenariuszach. — W grupie wszystkich metod, znalazły się: – w podejściu symetrycznym: 1) odległość edycyjna – litery – brak parametrów; 2) odległość edycyjna – wywołania funkcji – brak parametrów;

123

i i

i i i “output” — 2018/6/21 — 7:43 — page 124 — #124 i i i

3) odległość edycyjna – tokeny – brak parametrów; 4) algorytm Smitha–Watermana – litery – s(a, a) = 5, s(a, b) = 1, v = 5, u = 1; 5) algorytm Smitha–Watermana – wywołania funkcji – s(a, a) = 5, s(a, b) = 1, v = 1, u = 1; 6) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 7) zachłanne kafelkowanie ciągów – litery – d = 4; 8) zachłanne kafelkowanie ciągów – wywołania funkcji – d = 1; 9) zachłanne kafelkowanie ciągów – tokeny – d = 13; 10) odległość q-gramowa – litery – q = 3; 11) odległość q-gramowa – wywołania funkcji – q = 1; 12) odległość q-gramowa – tokeny – q = 5; 13) algorytm Weisfeilera–Lehmana – h = 3; 14) algorytm SimilaR – h = 2; – w podejściu agregującym: 1) odległość edycyjna – litery – brak parametrów – wersja symetryczna;

2) odległość edycyjna – wywołania funkcji – brak parametrów –Tm;

3) odległość edycyjna – tokeny – brak parametrów – Sp; 4) algorytm Smitha–Watermana – litery – s(a, a) = 5, s(a, b) = 1, v = 5, u = 1 – M; 5) algorytm Smitha–Watermana – wywołania funkcji – s(a, a) = 5, s(a, b) =

1, v = 1, u = 1 – Tp; 6) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5,

u = 1 – TŁ; 7) zachłanne kafelkowanie ciągów – litery – d = 6 – M;

8) zachłanne kafelkowanie ciągów – wywołania funkcji – d = 1 – TŁ; 9) zachłanne kafelkowanie ciągów – tokeny – d = 11 – M; 10) odległość q-gramowa – litery – q = 3 – wersja symetryczna; 11) odległość q-gramowa – wywołania funkcji – q = 1 – M;

12) odległość q-gramowa – tokeny – q = 4 – TŁ;

13) algorytm Weisfeilera–Lehmana – h = 2 – Tp; 14) algorytm SimilaR – h = 2 – M; – w podejściu niesymetrycznym: 1) odległość edycyjna – litery – brak parametrów; 2) odległość edycyjna – wywołania funkcji – brak parametrów; 3) odległość edycyjna – tokeny – brak parametrów;

124

i i

i i i “output” — 2018/6/21 — 7:43 — page 125 — #125 i i i

4) algorytm Smitha–Watermana – litery – s(a, a) = 5, s(a, b) = 1, v = 5, u = 1; 5) algorytm Smitha–Watermana – wywołania funkcji – s(a, a) = 5, s(a, b) = 1, v = 1, u = 1; 6) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 7) zachłanne kafelkowanie ciągów – litery – d = 6; 8) zachłanne kafelkowanie ciągów – wywołania funkcji – d = 1; 9) zachłanne kafelkowanie ciągów – tokeny – d = 11; 10) odległość q-gramowa – litery – q = 3; 11) odległość q-gramowa – wywołania funkcji – q = 1; 12) odległość q-gramowa – tokeny – q = 4; 13) algorytm Weisfeilera–Lehmana – h = 2; 14) algorytm SimilaR – h = 2. — W grupie metod najlepszy ciąg są: – w podejściu symetrycznym: 1) odległość edycyjna – tokeny – brak parametrów; 2) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 3) zachłanne kafelkowanie ciągów – tokeny – d = 13; 4) odległość q-gramowa – tokeny – q = 5; 5) algorytm SimilaR – h = 2; – w podejściu agregującym:

1) odległość edycyjna – tokeny – brak parametrów – Sp; 2) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5,

u = 1 – TŁ; 3) zachłanne kafelkowanie ciągów – litery – d = 6 – M; 4) odległość q-gramowa – tokeny – q = 4 – M; 5) algorytm SimilaR – h = 2 – M; – w podejściu niesymetrycznym: 1) odległość edycyjna – tokeny – brak parametrów; 2) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 3) zachłanne kafelkowanie ciągów – litery – d = 6; 4) odległość q-gramowa – tokeny – q = 4; 5) algorytm SimilaR – h = 2. — W grupie szybkich metod wymieniamy:

125

i i

i i i “output” — 2018/6/21 — 7:43 — page 126 — #126 i i i

– w podejściu symetrycznym: 1) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 2) odległość q-gramowa – wywołania funkcji – q = 1; 3) algorytm SimilaR – h = 2; – w podejściu agregującym: 1) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5,

u = 1 – TŁ; 2) odległość q-gramowa – wywołania funkcji – q = 1 – M; 3) algorytm SimilaR – h = 2 – M; – w podejściu niesymetrycznym: 1) algorytm Smitha–Watermana – tokeny – s(a, a) = 5, s(a, b) = 4, v = 5, u = 1; 2) odległość q-gramowa – wywołania funkcji – q = 1; 3) algorytm SimilaR – h = 2. Co więcej, w każdym wariancie problemu możemy dokonywać klasyfikacji binarnej, jak i regresji liniowej. Wartość z przedziału [0, 1] potrzebna w regresji została wyliczona zgodnie z opisem zawartym w podrozdz. 2.7.

4.2.2 Testowane modele i miary błędu

Zaproponowaną metodę porównywaliśmy w przypadku klasyfikacji binarnej z następują- cymi modelami: 1) Naiwny Bayes [14, 114] (funkcja naiveBayes z pakietu e1071 w języku R), 2) drzewo decyzyjne [27] (funkcja rpart z pakietu rpart), 3) metoda k najbliższych sąsiadów [4] dla k = 5 (funkcja knn z pakietu class), 4) regresja logistyczna [35, 60, 96, 125] (funkcja glm z pakietu stats), 5) SVM [31, 37, 118] (funkcja svm z pakietu e1071), 6) lasy losowe [25, 26] (funkcja randomForest z pakietu randomForest). W przypadku wartości z przedziału [0, 1] porównywaliśmy nasze podejście z regresją liniową [121]. Jakość modeli mierzymy następująco: dla klasyfikacji binarnej jest to, tak jak wcze- śniej, czułość, precyzja oraz miara F, opisane w podrozdz. 3.4. Dla problemu regresji badamy błąd średniokwadratowy (ang. Root Mean Squared Error, RMSE) i średnie od-

chylenie bezwzględne (ang. Mean Absolute Deviation, MAD). Niech yˆi oznacza wartość

przewidzianą przez model, podczas gdy yi będzie wartością referencyjną, do której dążymy, dla i-tej obserwacji. Wtedy, jeśli łącznie jest m obserwacji, MAD jest dany wzorem:

126

i i

i i i “output” — 2018/6/21 — 7:43 — page 127 — #127 i i i

m 1 X MAD = |yi − yˆi|, m i=1 a RMSE przez:

v u m u 1 X 2 RMSE = t (yi − yˆi) . m i=1 W przypadku naszej metody raportujemy trzy zestawy parametrów: takie, które mak- symalizują miarę F dla klasyfikacji binarnej, minimalizują MAD, a także RMSE. W przy- padku klasyfikacji binarnej wartości zwrócone przez nią interpretujemy w następujący sposób: wartości większe lub równe 0,5 klasyfikujemy jako klasę 1 (podobne funkcje), a wartości mniejsze od 0,5 klasyfikujemy jako klasę 0 (niepodobne funkcje).

4.2.3 Wyniki

Klasyfikacja binarna. Wyniki dla klasyfikacji binarnej zamieszczono w tab. 4.1 (dla wszystkich metod), tab. 4.2 (dla metod najlepszego ciągu) i tab. 4.3 (szybkie metody). W każdej tabeli zebrane są wyniki dla podejścia symetrycznego, agregującego i niesyme- trycznego. Analizę zacznijmy od tego, jak wybór liczby agregowanych metod wpływa na ogólną jakość predykcji. Widoczny jest trend, że modele lepiej sobie radzą, gdy mają więcej cech, na podstawie których mogą podejmować decyzję. I tak przeciętnie najlepsze wyniki znajdziemy w zestawieniu dla wszystkich metod, następnie będą metody najlepszego ciągu, a na końcu znajdą się metody szybkie. Następnie przejdźmy do rozważań nad tym, jak podejścia symetryczne, agregujące i niesymetryczne wpływają na ostateczne wyniki. Zdecydowanie widać, że podejście nie- symetryczne zawsze (poza jednym wyjątkiem, dotyczącym naszej funkcji agregacyjnej, dla metod najlepszego ciągu) pozwala osiągnąć lepsze wyniki, niż pozostałe dwa sposoby. Z kolei widać, że podejście agregujące jest korzystniejsze, niż symetryczne. Po zauważeniu takich ogólnych tendencji, przeanalizujmy różnice pomiędzy poszcze- gólnymi modelami predykcyjnymi. Dla każdej grupy metod (wszystkich, najlepszego ciągu, szybkich) da się wskazać takie podejście (agregujące lub niesymetryczne), w którym za- proponowana przez nas metoda podejmowania decyzji osiąga najlepsze wyniki w sensie miary F. Poza tym metodami, których wyniki nie są statystycznie znacząco gorsze, są lasy losowe i SVM. Jednak zauważmy, że te metody nie generują interpretowalnych modeli, ani nie mają wspomnianych wcześniej pożądanych własności.

Zadanie regresji. Wyniki dla zadania regresji zostały zebrane w tab. 4.4. Porównu- jemy zwykłą regresję liniową z naszą metodą w dwóch wersjach: optymalizowaną dla

127

i i

i i i “output” — 2018/6/21 — 7:43 — page 128 — #128 i i i

Tabela 4.1. Klasyfikacja binarna, wszystkie metody

klasyfikator podejście precyzja czułość miara F

zaproponowana metoda 1D symetryczne 0,978 ± 0,033 0,967 ± 0,061 0,975 ± 0,037 agregujące 0,977 ± 0,041 0,991 ± 0,056 0,987 ± 0,043

zaproponowana metoda 2D niesymetryczne 0,988 ± 0,036 0,995 ± 0,046 0,991 ± 0,058

naiwny klasyfikator bayesowski symetryczne 0,927 ± 0,067 0,952 ± 0,076 0,928 ± 0,061 agregujące 0,918 ± 0,068 0,984 ± 0,045 0,943 ± 0,050 niesymetryczne 0,946 ± 0,057 0,998 ± 0,025 0,966 ± 0,037

SVM symetryczne 0,987 ± 0,025 0,970 ± 0,057 0,974 ± 0,038 agregujące 0,992 ± 0,024 0,989 ± 0,036 0,983 ± 0,025 niesymetryczne 0,996 ± 0,018 0,994 ± 0,031 0,989 ± 0,021

5 najbliższych sąsiadów symetryczne 0,990 ± 0,023 0,949 ± 0,075 0,963 ± 0,048 agregujące 0,990 ± 0,025 0,976 ± 0,051 0,978 ± 0,034 niesymetryczne 0,995 ± 0,018 0,982 ± 0,040 0,984 ± 0,026

regresja logistyczna symetryczne 0,991 ± 0,023 0,936 ± 0,089 0,958 ± 0,057 agregujące 0,989 ± 0,025 0,981 ± 0,051 0,976 ± 0,034 niesymetryczne 0,994 ± 0,019 0,983 ± 0,047 0,982 ± 0,030

lasy losowe symetryczne 0,996 ± 0,019 0,934 ± 0,087 0,960 ± 0,055 agregujące 0,996 ± 0,020 0,967 ± 0,058 0,976 ± 0,037 niesymetryczne 1,000 ± 0,012 0,982 ± 0,049 0,986 ± 0,029

drzewo decyzyjne symetryczne 0,995 ± 0,017 0,895 ± 0,125 0,939 ± 0,082 agregujące 0,996 ± 0,015 0,887 ± 0,126 0,935 ± 0,082 niesymetryczne 0,986 ± 0,025 0,955 ± 0,072 0,963 ± 0,045

miary MAD lub RMSE. Dla wszystkich scenariuszy okazało się, że wyniki proponowanej metody są lepsze, niż te w regresji prostej. Ponownie da się odnotować tendencję, że wy- niki są tym lepsze, im więcej metod jest branych pod uwagę. W większości przypadków utrzymuje się także zauważona w klasyfikacji binarnej zależność, że najlepsze rezultaty da się otrzymać dla podejścia niesymetrycznego, następnie agregującego, a dopiero na końcu symetrycznego.

Dopasowane wielomiany dla metod szybkich zostały zilustrowane na rys. 4.2 (przypa- dek jednowymiarowy, podejścia symetryczne i agregujące) oraz rys. 4.3 (przypadek dwu- wymiarowy, podejście niesymetryczne).

128

i i

i i i “output” — 2018/6/21 — 7:43 — page 129 — #129 i i i

Tabela 4.2. Klasyfikacja binarna, metody najlepszego ciągu

klasyfikator podejście precyzja czułość miara F

zaproponowana metoda 1D symetryczne 0,987 ± 0,031 0,953 ± 0,066 0,964 ± 0,045 agregujące 0,983 ± 0,028 0,998 ± 0,017 0,985 ± 0,019

zaproponowana metoda 2D niesymetryczne 0,983 ± 0,032 0,990 ± 0,022 0,980 ± 0,023

naiwny klasyfikator bayesowski symetryczne 0,955 ± 0,047 0,940 ± 0,087 0,937 ± 0,059 agregujące 0,938 ± 0,058 0,979 ± 0,050 0,949 ± 0,047 niesymetryczne 0,960 ± 0,049 0,972 ± 0,055 0,956 ± 0,043

SVM symetryczne 0,987 ± 0,024 0,951 ± 0,073 0,962 ± 0,046 agregujące 0,989 ± 0,025 0,970 ± 0,057 0,972 ± 0,037 niesymetryczne 0,996 ± 0,019 0,985 ± 0,042 0,983 ± 0,027

5 najbliższych sąsiadów symetryczne 0,987 ± 0,025 0,950 ± 0,076 0,963 ± 0,049 agregujące 0,989 ± 0,024 0,957 ± 0,067 0,966 ± 0,043 niesymetryczne 0,995 ± 0,017 0,968 ± 0,054 0,976 ± 0,033

regresja logistyczna symetryczne 0,990 ± 0,024 0,929 ± 0,096 0,951 ± 0,060 agregujące 0,982 ± 0,029 0,952 ± 0,079 0,960 ± 0,050 niesymetryczne 0,991 ± 0,022 0,978 ± 0,053 0,976 ± 0,034

lasy losowe symetryczne 0,993 ± 0,021 0,932 ± 0,087 0,956 ± 0,055 agregujące 0,995 ± 0,020 0,959 ± 0,066 0,971 ± 0,042 niesymetryczne 0,998 ± 0,017 0,979 ± 0,050 0,982 ± 0,031

drzewo decyzyjne symetryczne 0,995 ± 0,017 0,895 ± 0,125 0,939 ± 0,082 agregujące 0,996 ± 0,015 0,887 ± 0,126 0,935 ± 0,082 niesymetryczne 0,986 ± 0,025 0,955 ± 0,072 0,963 ± 0,045

129

i i

i i i “output” — 2018/6/21 — 7:43 — page 130 — #130 i i i

Tabela 4.3. Klasyfikacja binarna, metody szybkie

klasyfikator podejście precyzja czułość miara F

zaproponowana metoda 1D symetryczne 0,993 ± 0,023 0,931 ± 0,093 0,954 ± 0,059 agregujące 0,985 ± 0,036 0,969 ± 0,047 0,970 ± 0,036

zaproponowana metoda 2D niesymetryczne 0,983 ± 0,028 0,990 ± 0,026 0,983 ± 0,023

naiwny klasyfikator bayesowski symetryczne 0,973 ± 0,038 0,927 ± 0,104 0,939 ± 0,068 agregujące 0,961 ± 0,041 0,951 ± 0,084 0,946 ± 0,056 niesymetryczne 0,984 ± 0,028 0,926 ± 0,100 0,948 ± 0,063

SVM symetryczne 0,996 ± 0,016 0,889 ± 0,127 0,936 ± 0,083 agregujące 0,992 ± 0,020 0,916 ± 0,102 0,946 ± 0,064 niesymetryczne 0,994 ± 0,019 0,982 ± 0,046 0,982 ± 0,029

5 najbliższych sąsiadów symetryczne 0,987 ± 0,020 0,895 ± 0,123 0,933 ± 0,079 agregujące 0,987 ± 0,023 0,924 ± 0,099 0,948 ± 0,063 niesymetryczne 0,995 ± 0,018 0,977 ± 0,048 0,981 ± 0,031

regresja logistyczna symetryczne 0,995 ± 0,018 0,895 ± 0,126 0,940 ± 0,083 agregujące 0,990 ± 0,022 0,922 ± 0,109 0,946 ± 0,069 niesymetryczne 0,988 ± 0,023 0,973 ± 0,060 0,973 ± 0,038

lasy losowe symetryczne 0,997 ± 0,014 0,889 ± 0,126 0,935 ± 0,082 agregujące 0,996 ± 0,016 0,904 ± 0,114 0,945 ± 0,072 niesymetryczne 0,998 ± 0,013 0,970 ± 0,054 0,979 ± 0,032

drzewo decyzyjne symetryczne 0,995 ± 0,017 0,895 ± 0,125 0,939 ± 0,082 agregujące 0,996 ± 0,015 0,890 ± 0,128 0,937 ± 0,083 niesymetryczne 0,993 ± 0,021 0,946 ± 0,078 0,962 ± 0,048

130

i i

i i i “output” — 2018/6/21 — 7:43 — page 131 — #131 i i i 009 024 017 015 022 022 024 026 018 027 020 015 021 026 019 025 018 021 019 025 020 015 013 013 013 013 013 , , , , , , , , , , , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 080 071 099 055 045 065 046 050 074 050 042 052 043 048 058 049 038 068 041 040 039 , , , , , , , , , , , , , , , , , , , , , 028 028 028 028 028 028 0 , , , , , , 005 0 005 0 006 0 004 0 003 0 005 0 004 0 004 0 005 0 004 0 003 0 004 0 003 0 004 0 004 0 004 0 004 0 005 0 003 0 003 0 002 0 002 0 002 0 002 0 004 002 0 002 0 , , , , , , , , , , , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± ± 056 020 062 026 005 019 004 005 034 005 006 023 005 006 018 004 004 026 004 005 , , , , , , , , , , , , , , , , , , , , 003 003 003 003 003 003 003 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 , , , , , , , 0 0 0 0 0 0 0 niesymetryczne agregujące niesymetryczne agregujące agregujące agregujące agregujące niesymetryczne agregujące agregujące agregujące agregujące Zadanie regresji szybkie symetryczne szybkie symetryczne szybkieszybkie niesymetryczne niesymetryczne najlepszego ciągu symetryczne szybkie symetryczne najlepszego ciągu symetryczne najlepszego ciągu niesymetryczne najlepszego ciągu niesymetryczne najlepszego ciągu symetryczne grupa metod podejście MAD RMSE Tabela 4.4. regresja liniowazaproponowana metoda 1D – optymalizowane MAD wszystkie wszystkie symetryczne symetryczne zaproponowana metoda 1D – optymalizowane RMSE wszystkie symetryczne zaproponowana metoda 2D – optymalizowane MAD wszystkiezaproponowana metoda 2D - optymalizowane RMSE niesymetryczne wszystkie niesymetryczne

131

i i

i i i “output” — 2018/6/21 — 7:43 — page 132 — #132 i i i

4.3 Analiza statystyczna

W tym podrozdziale opiszemy wyniki naszych badań, które miały na celu wyjaśnienie, jak poszczególne metody zależą od siebie. W szczególności, czy badają one to samo, czyli są mocno skorelowane, czy też koncentrują się na różnych aspektach kodu. Czy są al- gorytmy, które mają większy wpływ na podjęcie ostatecznej decyzji niż inne, czy też wszystkie są tak samo ważne. A także, czy poza samym faktem, że dwie funkcje są do sie- bie podobne, można, przy użyciu więcej niż jednej metody, stwierdzić sposób, rodzaj modyfikacji, użyty, aby przekształcić jedną w drugą. Ze względu na czytelność, w tabelach i na rysunkach wprowadziliśmy skrótowe ozna- czenia na poszczególne algorytmy: — odległość Levenshteina — Levensh.; — algorytm Smitha–Watermana — S–W; — zachłanne kafelkowanie ciągów — ZKC; — odległość q-gramowa — q-gramy; — algorytm Weisfeilera-Lehmana — W–L; — algorytm SimilaR — SimilaR; a także sposoby reprezentacji funkcji: — ciągi liter — litery.; — wywołania funkcji — wyw. fun.; — ciągi tokenów — tokeny.

4.3.1 Analiza zależności między zmiennymi

Na rysunkach 4.4 (podejście symetryczne) i 4.5 (podejście agregujące) zamieszczono wy- kresy punktowe wraz z wartościami współczynników korelacji rang Spearmana, przy czym niebieskie punkty oznaczają pary, które nie są do siebie podobne, a zielone odpowiadają parom sklonowanych funkcji. Skupmy się na początku na podejściu symetrycznym. Zacznijmy od metod, które są wy- jątkowo mocno skorelowane. Zwróćmy uwagę na następujące metody, działające na cią- gach wywołań funkcji: zachłanne kafelkowanie ciągów oraz odległość q-gramowa mają współczynnik korelacji na poziomie 0,99. Wynika to z faktu, że w przypadku zachłan- nego kafelkowania sekwencji parametr oznaczający najkrótszą sekwencję, która jest brana pod uwagę wynosi d = 1. Tak więc w tym wypadku ta metoda sprowadza się do zwykłego zliczania funkcji, którym jest odległość q-gramowa dla q = 1. Inne metody działające na wywołaniach funkcji, jak odległość Levenshteina czy algorytm Smitha–Watermana także są dość mocno skorelowane, ale nie na aż tak wysokim poziomie (korelacje powyżej 0,76, 0,87 i 0,90).

132

i i

i i i “output” — 2018/6/21 — 7:43 — page 133 — #133 i i i

Inną parą dobrze skorelowanych metod są algorytm Smitha–Watermana oraz odległość Levenshteina dla tokenów (współczynnik równy 0,93). Nie jest to dużym zaskoczeniem, gdyż oba algorytmy są odległościami edycyjnymi. Warto zaznaczyć, że czasy wykonania algorytmu Smitha–Watermana są znacznie mniejsze niż drugiej metody. Dość zaskakującą parą jest algorytm zachłannego kafelkowania ciągów oraz odległość q-gramowa dla liter. Ich współczynnik korelacji wynosi 0,96. Warto zauważyć, że znów występuje duża zbieżność parametrów: q = 3 i d = 4, tak więc wnioski są analogiczne do tych dotyczących tych algorytmów na wywołaniach funkcji. Zwróćmy teraz uwagę na pary o niskim współczynniku korelacji. Metoda zachłan- nego kafelkowania ciągów dla tokenów ma niskie wartości dla wszystkich pozostałych me- tod (największą wartością jest 0,65, jednak większość nie przekracza 0,44). Sugeruje to, że może ona oceniać funkcje w inny sposób, niż reszta algorytmów. Inną metodą, która ma wszystkie współczynniki na niskim poziomie, jest algorytm grafowy Weisfeilera–Lehmana. Skupmy się teraz na podejściu agregującym. Okazuje się, że wszystkie współczynniki są o wiele mniejsze, niż poprzednio. I tak nawet para o największej korelacji w poprzednim podejściu, zachłanne kafelkowanie ciągów oraz odległość q-gramowa dla ciągów wywołań funkcji mają w tym wypadku wartość równą 0,47. Wynika to z innych funkcji agregu-

jących: M dla q-gramów i TŁ dla zachłannego kafelkowania ciągów. Za to stosunkowo wysoki współczynnik korelacji utrzymał się pomiędzy odległością Levenshteina (użyto

Tm) i algorytmem Smitha–Watermana (Tp)– 0,76. Odległość Levenshteina jest dość dobrze skorelowana sama ze sobą, dla różnych ro- dzajów sekwencyjnych sposobów reprezentacji: 0,69, 0,72, 0,82. Wysoką wartością (0,63) odznacza się także para metod, które zdają się nie mieć wiele ze sobą wspólnego: odległość Levenshteina dla tokenów i algorytm Smitha–Watermana dla wywołań funkcji. Niską wartością korelacji do wszystkich innych metod znów ma zachłanne kafelkowanie ciągów dla tokenów, ale także dla liter. Ciekawym przypadkiem jest ujemna korelacja (−0,02), jaka występuje między za- chłannym kafelkowaniem ciągów dla tokenów a odległością Levenshteina dla liter. Do- dajmy, że wszystkie wartości korelacji są statystycznie znacząco różne od zera. Przyjrzyjmy się teraz wykresom punktowym, zaczynając od podejścia symetrycznego. Tak jak w przypadku korelacji, uwagę zwraca wizualizacja dla zachłannego kafelkowa- nia ciągów oraz odległości q-gramowej dla ciągów wywołań funkcji. Punkty układają się w krzywą, która jest bliska zwykłej prostej dla dokładnie skorelowanych wartości, przy czym wartości dla zachłannego kafelkowania ciągów są mniejsze dla odpowiednich warto- ści w odległości q-gramowej. Dość podobny rysunek możemy zobaczyć dla tych samych dwóch metod, gdy ciągiem wejściowym są litery, choć w tym wypadku jest mniej punk-

133

i i

i i i “output” — 2018/6/21 — 7:43 — page 134 — #134 i i i

tów, których wartości współrzędnych są w okolicy wartości 0,5. Większość wartości jest albo małych, w okolicy 0, albo dużych, w okolicy 1. Metodą, która miała małe wartości korelacji z innymi, było zachłanne kafelkowanie ciągów dla tokenów. Gdy przyjrzymy się wykresom, zobaczymy, że wynika to z faktu, że zazwyczaj ta metoda zwraca mniejsze wartości niż reszta. W szczególności dotyczy to par niepodobnych funkcji, gdzie kafelkowanie zwraca wartość 0 lub do niej zbliżoną, pod- czas gdy inne algorytmy zwracają wartości większe, aż do 0,5. Świadczy to dobrze o cesze algorytmu, którą jest niezwracanie uwagi na zbyt krótkie wspólne podciągi. Jednak należy pamiętać, że wartości dla podobnych funkcji także są zaniżone, co może wynikać m.in. z faktu, że przy użyciu reprezentacji tokenowej możliwe jest niedoszacowanie podobień- stwa w przypadku użycia bardziej zaawansowanych modyfikacji. Zapoznanie się z wykresami dla algorytmu SimilaR prowadzi do wniosku, że punkty w większości przypadków nie układają się w pojedynczą krzywą, ale raczej tworzą chmurę, co sugeruje, że metoda ta inaczej ocenia pary, niż pozostałe metody. Punkty odpowiada- jące klonom dostają zdecydowanie większe wartości przy algorytmie SimilaR, niż w przy- padku reszty algorytmów. Oznacza to, że większość, nawet bardziej zaawansowanych mo- dyfikacji kodu, jest przez zaproponowany przez nas algorytm wykrywana. Skupmy się teraz na podejściu agregującym. Gdy ponownie przyjrzymy się zachłan- nemu kafelkowaniu ciągów oraz odległości q-gramowej dla ciągów wywołań funkcji, zoba- czymy, że nasze podejrzenie podczas analizy współczynników korelacji było prawdziwe: punkty tworzą charakterystyczny „zawias”, oznacza to, że wszystkim punktom, którym odległość q-gramowa przyporządkowała wartości z przedziału [0, 0,5], kafelkowanie przy-

dzieliło miarę równą 0. Wynika to z wybranej funkcji agregującej (TŁ). Pozostałe punkty układają się w kształt prostej. Ciekawą anomalią są wykresy dla odległości Levenstheina dla tokenów: o ile na wszyst- kich pozostałych rysunkach punkty odpowiadające funkcjom niepodobnym są w lewym dolnym rogu, oznaczając, że obie metody przydzieliły niewielkie wartości podobieństwa, o tyle dla tej metody widać, że wartości przez nią przydzielone są większe. Wynika to z faktu, że to jedyna metoda, która jako funkcji agregującej nie używa t-normy czy śred-

niej, ale t-konormy (dokładniej Sp), która jest uogólnionym maksimum. Zwróćmy uwagę na następujące metody: algorytm Smitha–Watermana dla tokenów, zachłanne kafelkowanie ciągów dla wywołań funkcji, odległość q-gramowa dla tokenów. Wszystkie one przydzielają bardzo wiele wartości równych 0, podczas gdy pozostałe me- tody zwracają w tych samych przypadkach miary ostro większe od zera. Wynika to z faktu,

że wyniki wszystkich trzech algorytmów są agregowane przez TŁ. Wnioski dotyczące algorytmu SimilaR są podobne do tych dla przypadku symetrycz- nego: W przypadku par niepodobnych mamy do czynienia z chmurą punktów nie ukła-

134

i i

i i i “output” — 2018/6/21 — 7:43 — page 135 — #135 i i i

dających się w regularny kształt pojedynczej krzywej, a wartości dla podobnych funkcji są większe w przypadku zaproponowanej metody niż innych.

4.3.2 Redukcja wymiaru przestrzeni

Kolejnym etapem statystycznej analizy danych jest zastosowanie analizy składowych głów- nych (principal component analysis, PCA). Celem było przekonanie się, czy da się wybrać taki podzbiór wszystkich metod, aby wyjaśniał on zdecydowaną większość zbioru par funkcji, a także, czy da się dokonać jakiejś ciekawszej interpretacji: nie tylko, czy dana para jest podobna, ale także w jaki sposób. Wartości składowych głównych, wraz z odchyleniem standardowym i proporcją wa- riancji, którą tłumaczą, zamieszczone są dla wszystkich metod w tab. 4.5 (podejście sy- metryczne) i tab. 4.6 (podejście agregujące). Analogiczne zestawienia dla algorytmów naj- lepszego ciągu przedstawione są w tab. 4.7 (podejście symetryczne) i tab. 4.8 (podejście agregujące), a dla metod szybkich w tab. 4.9 (podejście symetryczne) i tab. 4.10 (podejście agregujące). Przyjrzyjmy się wszystkim metodom. W podejściu symetrycznym pierwsze cztery skła- dowe główne tłumaczą 95% zmienności zbioru. Pierwsza składowa jest w przybliżeniu zwykłą średnią arytmetyczną, gdzie wszystkie składowe mają dość równy wkład. Me- toda SimilaR wraz z odległością q-gramową na wywołaniach funkcji mają największe wagi, podczas gdy algorytm Smitha–Watermanna na literach ma najniższy udział w osta- tecznej wartości. Druga składowa jest trudna w interpretacji, niektóre metody otrzymały dodatnie współczynniki (największe dodatnie wartości przydzielone zostały odległości Le- venstheina na tokenach i odległość q-gramowa na wywołaniach funkcji), podczas gdy inne ujemne (metody o najmniejszych wartościach to algorytm Smitha–Watermana na literach, zachłanne kafelkowanie ciągów na literach i tokenach, a także q-gramy na literach). Trzeci wymiar bierze pod uwagę głównie algorytmy grafowe, przydzielając im duże (co do war- tości bezwzględnej) współczynniki ujemne. Czwarty wymiar to głównie dwa algorytmy: odległość Levenstheina na tokenach (dodatnia wartość) i q-gramy na funkcjach (ujemny współczynnik). W podejściu agregującym pierwsze pięć składowych głównych wyjaśnia 95% zmien- ności zbioru, jednak składowe podlegają podobnej interpretacji: pierwsza składowa jest średnią ważoną, gdzie wszystkie algorytmy mają mniej więcej taki sam wkład, druga skła- dowa jest trudną w interpretacji mieszaniną dodatnich i ujemnych współczynników, zaś trzecia i czwarta przydzielają dużą wagę algorytmom grafowym. Piąta jest różnicą po- między zachłannym kafelkowaniem ciągów dla tokenów i q-gramów dla wywołań funkcji. W przypadku metod najlepszego ciągu, w obu podejściach pierwsze trzy składowe główne tłumaczą przynajmniej 95% zmienności zbioru. Pierwsza składowa ponownie jest

135

i i

i i i “output” — 2018/6/21 — 7:43 — page 136 — #136 i i i

średnią ważoną wszystkich pięciu metod, o podobnym wkładzie. Druga składowa przy- dziela największa wagę odległości Levenstheina, przy czym w przypadku symetrycznym jest ona równoważona przez sumę zachłannego kafelkowania ciągów dla tokenów oraz algorytm SimilaR. Trzecia składowa przydziela największą wagę algorytmowi SimilaR. Metody szybkie są najprostsze w interpretacji ze względu na ich małą liczbę. Pierw- sze dwie składowe tłumaczą przynajmniej 93% zmienności zbioru. Pierwsza składowa raz jeszcze jest średnią ważoną o podobnym stopniu ważności każdej metody, przy czym al- gorytm Smitha–Watermana ma trochę mniejszą wagę, zaś algorytm SimilaR jest bardziej preferowany. Współczynniki drugiej składowej zaś w przybliżeniu sumują się do zera. Jest to odległość q-gramowa o dodatniej wartości, a także algorytm Smitha–Watermana wraz z algorytmem SimilaR, które mają ujemne współczynniki (przy czym algorytm SimilaR ma co do wartości bezwzględnej większą wartość czynnika).

136

i i

i i i “output” — 2018/6/21 — 7:43 — page 137 — #137 i i i

1.0 1.0

0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0

0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0

(a) S–W – tokeny, podejście symetryczne (b) S–W – tokeny, podejście agregujące

1.0 1.0

0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0

0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0

(c) q-gramy – wyw. fun., podejście (d) q-gramy – wyw. fun., podejście agregujące symetryczne

1.0 1.0

0.8 0.8

0.6 0.6

0.4 0.4

0.2 0.2

0.0 0.0

0.0 0.2 0.4 0.6 0.8 1.0 0.0 0.2 0.4 0.6 0.8 1.0

(e) SimilaR, podejście symetryczne (f) SimilaR, podejście agregujące

Rysunek 4.2. Wielomiany dopasowane dla metod szybkich, przypadek jednowymiarowy

137

i i

i i i “output” — 2018/6/21 — 7:43 — page 138 — #138 i i i

1.00

0.75 level 1.00

0.75

z y 0.50 0.50

0.25

0.25 0.00

x

y 0.00 0.00 0.25 0.50 0.75 1.00 x

(a) S–W – tokeny, wykres trójwymiarowy (b) S–W – tokeny, mapa ciepła

1.00

0.75 level 1.00

0.75

z y 0.50 0.50

0.25

0.25 0.00

x

y 0.00 0.00 0.25 0.50 0.75 1.00 x

(c) q-gramy – wyw. fun., wykres (d) q-gramy – wyw. fun., mapa ciepła trójwymiarowy

1.00

0.75 level 1.00

0.75

z y 0.50 0.50

0.25

0.25 0.00

x

y 0.00 0.00 0.25 0.50 0.75 1.00 x

(e) SimilaR, wykres trójwymiarowy (f) SimilaR, mapa ciepła

Rysunek 4.3. Wielomiany dopasowane dla metod szybkich, przypadek dwuwymiarowy

138

i i

i i i “output” — 2018/6/21 — 7:43 — page 139 — #139 i i i

Rysunek 4.4. Wykres punktowy wraz ze współczynnikami korelacji rang Spearmana dla metod przy podejściu symetrycznym; niebieskie punkty oznaczają pary, które nie są do siebie podobne, a zielone odpowiadają parom sklonowanych funkcji

139

i i

i i i “output” — 2018/6/21 — 7:43 — page 140 — #140 i i i

Rysunek 4.5. Wykres punktowy wraz ze współczynnikami korelacji rang Spearmana dla metod przy podejściu agregującym; niebieskie punkty oznaczają pary, które nie są do siebie podobne, a zielone odpowiadają parom sklonowanych funkcji

140

i i

i i i “output” — 2018/6/21 — 7:43 — page 141 — #141 i

i i kaoagón 14 główna składowa 00 06 00 03 00 01 35 06 02 75 12 01 51 09 00 00 00 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0

− − − − − − kaoagón 13 główna składowa 00 0 07 0 00 0 16 99 1 00 15 21 0 03 0 06 0 74 06 0 10 56 0 04 0 01 02 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0

− − − − − − − kaoagón 12 główna składowa 01 0 16 00 0 04 99 0 15 60 0 20 0 58 0 09 04 07 0 33 0 08 0 23 01 04 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 11 główna składowa 01 0 04 0 00 0 28 0 99 0 12 46 68 34 0 07 08 06 0 25 0 12 0 01 01 01 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 10 główna składowa 18 01 0 00 0 65 0 99 0 14 22 0 21 45 0 10 0 26 24 25 08 0 04 01 0 00 , , , , , , , , , , , , , , , , , symetryczne 0 0 0 0 0 0

− − − − − − kaoagón 9 główna składowa 71 0 02 0 00 0 04 99 0 02 03 0 16 0 28 0 28 00 0 42 29 12 0 09 0 01 06 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0

− − − − − − − − − − kaoagón 8 główna składowa 44 0 02 0 00 0 35 99 0 28 05 16 03 05 20 60 0 03 06 0 37 04 05 0 , , , , , , , , , , , , , , , , , metod, podejście 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 7 główna składowa 23 0 04 0 00 0 30 0 99 0 07 04 32 0 30 29 10 25 40 0 23 01 0 24 44 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

wszystkich − − − − − − − − kaoagón 6 główna składowa 15 04 0 01 0 06 0 98 0 26 10 08 0 08 0 15 00 33 0 15 04 54 0 28 57 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0

− − − − − − kaoagón 5 główna składowa 07 05 0 01 0 37 0 97 0 05 13 37 0 07 0 07 10 0 14 0 19 08 0 60 0 29 0 39 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0

− − − − − − − − − kaoagón 4 główna składowa 25 07 0 02 0 07 95 0 55 14 02 28 06 0 38 07 0 03 0 53 09 0 00 25 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0

− − − − − kaoagón 3 główna składowa 04 0 08 0 03 0 16 93 0 20 0 03 0 08 0 05 0 03 0 22 03 0 03 0 29 09 0 80 34 , , , , , , , , , , , , , , , , , 0 0

− − kaoagón 2 główna składowa 15 0 12 0 07 0 01 0 89 0 56 0 34 0 20 0 03 0 37 0 02 0 34 0 33 0 25 0 14 0 15 03 , , , , , , , , , , , , , , , , , Współczynniki składowych głównych dla 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 1 główna składowa 20 42 0 82 0 25 82 0 32 0 16 20 25 21 29 0 21 23 33 0 29 0 32 0 33 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Tabela 4.5. Levensh.—litery odchylenie standardowe proporcja wariancji Levensh.—wyw. fun. skumulowana proporcja Levensh.—tokeny S–W—litery S–W—wyw. fun. S–W—tokeny ZKC—litery ZKC—wyw. fun. ZKC—tokeny q-gramy—litery q-gramy—wyw. fun. q-gramy—tokeny algorytm W–L SimilaR

141

i i

i i i “output” — 2018/6/21 — 7:43 — page 142 — #142 i

i i kaoagón 14 główna składowa 15 01 20 00 00 01 54 71 24 23 06 02 01 01 05 00 00 , , , , , , , , , , , , , , , , , 0 0 0 0 0

− − − − − kaoagón 13 główna składowa 35 01 0 08 0 00 0 99 1 00 40 0 38 32 0 43 08 0 00 0 49 0 00 0 01 00 0 00 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0

− − − − − − − kaoagón 12 główna składowa 33 0 01 0 03 0 00 0 03 99 0 40 04 66 0 04 0 07 0 00 0 43 01 0 28 01 03 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 11 główna składowa 17 02 0 26 0 00 0 03 0 99 0 17 08 0 35 0 09 71 07 10 0 21 38 01 0 00 , , , , , , , , , , , , , , , , , 0 0 0 0 0

− − − − − kaoagón 10 główna składowa 47 0 02 0 45 00 0 04 0 99 0 13 0 11 0 17 0 35 27 06 06 21 0 49 0 01 0 01 0 agregujące , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0

− − − − − − − − − − kaoagón 9 główna składowa 21 03 0 23 0 00 0 02 99 0 39 36 12 31 0 23 05 38 0 09 52 0 00 07 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0

− − − − − − − − − kaoagón 8 główna składowa 53 0 04 0 45 00 0 34 98 0 06 01 07 15 33 0 15 29 0 20 0 18 0 17 15 metod, podejście , , , , , , , , , , , , , , , , , 0 0 0 0 0

− − − − − kaoagón 7 główna składowa 11 04 0 28 00 0 04 0 97 0 13 0 27 0 24 0 56 0 22 0 06 45 12 0 27 0 15 0 22 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0

wszystkich

− − − − − − − kaoagón 6 główna składowa 00 0 05 0 14 01 0 34 0 96 0 03 0 08 01 14 0 01 26 11 0 07 0 01 53 0 67 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 5 główna składowa 03 07 0 26 02 0 36 0 95 0 02 0 06 03 01 0 07 0 58 00 0 65 07 06 03 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0

− − − − − − kaoagón 4 główna składowa 01 0 07 0 22 02 0 39 0 93 0 01 0 03 04 0 00 01 33 0 02 0 39 00 0 57 0 43 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0

− − − − − − − − − kaoagón 3 główna składowa 21 08 0 00 03 0 07 90 0 27 0 19 27 0 14 24 0 43 12 41 17 28 0 43 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0

− − − − − − − − kaoagón 2 główna składowa Współczynniki składowych głównych dla 19 12 0 32 0 07 0 66 0 86 0 09 01 09 21 11 41 0 00 08 0 10 37 0 09 0 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0

− − − − − − − − − − kaoagón 1 główna składowa 22 0 41 0 25 0 79 0 14 0 79 0 25 23 25 28 32 27 27 24 30 32 0 28 , , , , , , , , , , , , , , , , , 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Tabela 4.6. Levensh.—litery odchylenie standardowe Levensh.—wyw. fun. proporcja wariancji Levensh.—tokeny skumulowana proporcja S–W—litery S–W—wyw. fun. S–W—tokeny ZKC—litery ZKC—wyw. fun. ZKC—tokeny q-gramy—litery q-gramy—wyw. fun. q-gramy—tokeny algorytm W–L SimilaR

142

i i

i i i “output” — 2018/6/21 — 7:43 — page 143 — #143 i i i

Tabela 4.7. Współczynniki składowych głównych dla metod najlepszego ciągu, podejście symetryczne składowa główna 1 składowa główna 2 składowa główna 3 składowa główna 4 składowa główna 5

Levensh.—tokeny 0,53 −0,74 0,07 −0,26 0,30 S–W—tokeny 0,38 0,11 0,27 −0,38 −0,78 ZKC—tokeny 0,31 0,58 0,44 −0,26 0,53 q-gramy—tokeny 0,46 0,02 0,26 0,84 −0,09 SimilaR 0,50 0,30 −0,80 −0,03 0,02

odchylenie standardowe 0,27 0,09 0,05 0,04 0,02 proporcja wariancji 0,84 0,09 0,03 0,02 0,00 skumulowana proporcja 0,84 0,93 0,97 0,99 1,00

Tabela 4.8. Współczynniki składowych głównych dla metod najlepszego ciągu, podejście agregujące składowa główna 1 składowa główna 2 składowa główna 3 składowa główna 4 składowa główna 5

Levensh.—tokeny 0,24 −0,96 0,00 0,08 0,02 S–W—tokeny 0,43 0,09 −0,32 −0,31 0,77 ZKC—litery 0,49 0,19 −0,24 0,80 −0,07 q-gramy—tokeny 0,51 0,07 −0,30 −0,48 −0,63 SimilaR 0,49 0,11 0,85 −0,06 0,04

odchylenie standardowe 0,24 0,09 0,06 0,03 0,02 proporcja wariancji 0,78 0,13 0,05 0,01 0,00 skumulowana proporcja 0,78 0,91 0,97 0,99 1,00

143

i i

i i i “output” — 2018/6/21 — 7:43 — page 144 — #144 i i i

Tabela 4.9. Współczynniki składowych głównych dla szybkich metod, podejście symetryczne składowa główna 1 składowa główna 2 składowa główna 3

S–W—tokeny 0,44 −0,17 −0,87 q-gramy—wyw. fun. 0,63 0,75 0,16 SimilaR 0,63 −0,62 0,45

odchylenie standardowe 0,22 0,06 0,04 proporcja wariancji 0,90 0,06 0,03 skumulowana proporcja 0,90 0,96 1,00

Tabela 4.10. Współczynniki składowych głównych dla szybkich metod, podejście agregujące składowa główna 1 składowa główna 2 składowa główna 3

S–W—tokeny 0,51 −0,28 0,80 q-gramy—wyw. fun. 0,56 0,82 −0,06 SimilaR 0,64 −0,49 −0,58

odchylenie standardowe 0,19 0,06 0,05 proporcja wariancji 0,83 0,09 0,06 skumulowana proporcja 0,83 0,93 1,00

Ze względu na stosunkowo prostą interpretację ostatniego, najmniejszego zestawu me- tod, postanowiliśmy zaprezentować wizualizację pierwszych dwóch składowych szybkich metod na rys. 4.6 (podejście symetryczne) oraz na rys. 4.7 (podejście agregujące). Kolorem czarnym oznaczyliśmy klasę 0 (niepodobne funkcje), a kolorem czerwonym klasę 1 (funk- cje będące swoimi klonami). Dodajmy, że wizualizacje biorące pierwsze dwie składowe dla innych zestawów metod są podobne i prowadzą do zbliżonych wniosków. Pierwszym wnioskiem, jaki możemy wyciągnąć, jest zdecydowana liniowa separowal- ność klas. Oczywiście zdarzają się pomyłki w klasyfikacji, jednak zdecydowanie możemy

144

i i

i i i “output” — 2018/6/21 — 7:43 — page 145 — #145 i i i

powiedzieć, że mała wartość na pierwszym wymiarze oznacza z bardzo dużą dozą praw- dopodobieństwa klasę 0, zaś stosunkowo duża wartość jest mocno skorelowana z klasą 1. Wniosek ten uogólnia się na inne zestawy metod. Spróbujmy dokonać interpretacji drugiego wymiaru (na rysunkach pionowego). War- tości bliskie zera będą oznaczały podobne wartości podobieństwa dla wszystkich trzech metod. Wartości dodatnie (na rysunku u góry) świadczą o większej wartości odległości q-gramowej dla wywołań funkcji, niż sumie wartości pozostałych dwóch metod. Warto- ści ujemne wynikają z większych wartości algorytmu SimilaR (i ew. algorytmu Smitha– Watermanna dla tokenów) niż w odległości q-gramowej. Przyjrzyjmy się paru przykładom, które pozwolą nam lepiej zrozumieć, kiedy która metoda zwraca większe wyniki.

Klony, duża wartość SimilaR, mała wartość pozostałych dwóch metod. Roz- ważmy następującą parę funkcji:

1 spatial_Kaver <− function(fs, nsim, ...) { 2 VHgOVrIE <− e v a l 3 FNc6269K <− Kfn 4 jbJQjNOg <− as.expression 5 iERiZfuJ <− match . c a l l 6 UBr2S3Xz <− iERiZfuJ ( ) 7 hucMow5N <− DV6YtknH 8 dot.expression <− ... %>% substitute() %>% jbJQjNOg() 9 h <− FNc6269K(pp = dot. expression %>% VHgOVrIE() , fs) 10 FcR6Jqq1 <− h$x 11 LGEm4jzg <− h$y 12 zxfaDhu4 <− LGEm4jzg^2 13 for (i in 2:nsim) { 14 yuzwreW5 <− FNc6269K(pp = VHgOVrIE(dot. expression) , fs) 15 h <− yuzwreW5$y 16 KP7MtGDt <− h^2 17 zxfaDhu4 <− KP7MtGDt + zxfaDhu4 18 } 19 list(x = FcR6Jqq1, y = (zxfaDhu4/nsim) %>% sqrt(), call = UBr2S3Xz) 20 }

145

i i

i i i “output” — 2018/6/21 — 7:43 — page 146 — #146 i i i

1 spatial_Kaver <− function(fs, nsim, ...) { 2 Lx16TEYY <− s u b s t i t u t e 3 T6gIcLUA <− match . c a l l 4 tV4BKa3e <− Kfn 5 dot.expression <− as. expression(Lx16TEYY(...) ) 6 h <− tV4BKa3e(pp = dot.expression %>% eval() , fs) 7 RYJrzIpW <− h$x 8 tJt34pWh <− h$y^2 9 for (i in 2:nsim) { 10 S6OEEbcf <− dot.expression %>% eval() 11 h = tV4BKa3e(pp = S6OEEbcf, fs)$y 12 tJt34pWh = tJt34pWh + h^2 13 } 14 EY2StkYF <− sqrt (tJt34pWh/nsim) 15 list(x = RYJrzIpW, y = EY2StkYF, call = T6gIcLUA()) 16 }

Wartości poszczególnych metod to: 1) algorytm Smitha–Watermanna dla tokenów: 0,455; 2) odległość q-gramowa dla wywołań funkcji: 0,684; 3) algorytm SimilaR: 0,917. Wartość na drugim wymiarze to −0,133. Trudność tej pary polega głównie na zmianie wielu nazw funkcji poprzez przypisania na początku kodu. Stąd wynika dość niska wartość zwrócona przez odległość q-gramową. W przypadku algorytmu Smitha–Watermana działającego na tokenach, to sekwencje to- kenów różnią się pomiędzy funkcjami z dwóch powodów: przypisania na początku, ma- jące na celu zmylenie zliczeń wywołań funkcji są w innej liczbie. Po drugie, zagnieżdżone wywołania funkcji zostały rozbite, po części na wewnętrzne wywołania z przypisaniem do zmiennej, a po części na wywołania przy użyciu operatora potokowego %>%. Jednak dzięki odpowiedniemu sposobowi generowania PDG, zagnieżdżone wywołania, a także wy- wołania przy użyciu operatora potokowego %>% prowadzą do powstania takiego samego grafu. Co więcej, przypisania klasy identyfikator1 <- identyfikator2, choć są brane pod uwagę, nie znajdują bezpośredniego odwzorowania w grafie w postaci wierzchołka.

Klony, mała wartość SimilaR, duża wartość pozostałych dwóch metod. Roz- ważmy następującą parę funkcji:

146

i i

i i i “output” — 2018/6/21 — 7:43 — page 147 — #147 i i i

1 DiagrammeR_add_to_series <− function(graph, graph_series) { 2 if (graph_object_valid(graph) == FALSE) { 3 stop("The graph object is not valid.") 4 } 5 series_type <− graph_series$series_type 6 if (!(series_type %in% c("sequential", "temporal"))) { 7 stop("The graph series type is neither ’sequential ’ nor ’ temporal ’") 8 } 9 cXls4MZZ <− graph_series$graphs 10 graph_series$graphs[[ length(cXls4MZZ) + 1]] <− graph 11 return(graph_series) 12 }

1 DiagrammeR_add_to_series <− function(graph , UBtJKNKu) { 2 D3Va4stx <− graph_object_valid(graph) == FALSE 3 if (D3Va4stx) { 4 stop("The graph object is not valid.") 5 } 6 series_type <− UBtJKNKu$ s e r i e s_type 7 if (!(series_type %in% c("sequential", "temporal"))) { 8 stop("The graph series type is neither ’sequential ’ nor ’ temporal ’") 9 } 10 UBtJKNKu$graphs [[ length(UBtJKNKu$graphs) + 1]] <− graph 11 return (UBtJKNKu) 12 }

Wartości poszczególnych metod to:

1) algorytm Smitha–Watermanna dla tokenów: 0,714; 2) odległość q-gramowa dla wywołań funkcji: 1,000; 3) algorytm SimilaR: 0,894.

Wartość na drugim wymiarze to 0,074. W tej parze wszystkie nazwy wywoływanych funkcji są takie same, dlatego odległość q- gramowa zwróciła wartość równą 1. Metoda oparta na tokenach nie wykryła pełnego podo- bieństwa przez typowe ataki mylące taki sposób reprezentacji funkcji: wyliczenie warunku instrukcji warunkowej wcześniej czy dodatkowe instrukcje przypisania, aby posłużyć się sztucznie wprowadzoną zmienną. To, co sprawiło, że algorytm SimilaR nie zwrócił wartości równej 1, to wiersz cXls4MZZ <- graph_series$graphs. Powoduje on, że w wyrażeniu

147

i i

i i i “output” — 2018/6/21 — 7:43 — page 148 — #148 i i i

length(cXls4MZZ) nie operujemy bezpośrednio na zmiennej graph_series, ale na zmien- nej cXls4MZZ, będącej składową tamtej zmiennej. Powoduje to wygenerowanie innych krawędzi zależności danych, przez co wartość podobieństwa jest mniejsza niż 1.

Niepodobna para funkcji, stosunkowo duża wartość SimilaR, mała wartość pozostałych dwóch metod. Rozważmy następującą parę funkcji:

1 ggplot2_discrete_scale <− function (aesthetics , scale_name, palette , name = waiver(), breaks = waiver(), 2 labels = waiver(), limits = NULL, expand = waiver(), na.translate = TRUE, 3 na.value = NA, drop = TRUE, guide = "legend", position = "left", 4 super = ScaleDiscrete) 5 { 6 check_breaks_labels(breaks , labels) 7 p o s i t i o n <− match.arg(position , c("left", "right", "top", 8 "bottom" ) ) 9 if (is.null(breaks) && !is_position_aes(aesthetics) && guide != 10 "none" ) { 11 guide <− "none" 12 } 13 ggproto(NULL, super, call = match.call(), aesthetics = aesthetics , 14 scale_name = scale_name, palette = palette , range = discrete_ range ( ) , 15 limits = limits , na.value = na.value, na.translate = na. t r a n s l a t e , 16 expand = expand, name = name, breaks = breaks, labels = l a b e l s , 17 drop = drop, guide = guide, position = position) 18 }

1 mlr_makeCustomResampledMeasure <− function (measure.id, aggregation. id, minimize = TRUE, properties = character(0L), 2 fun, extra.args = list(), best = NULL, worst = NULL, measure.name = measure.id , 3 aggregation.name = aggregation.id, note = "") 4 { 5 assertString(measure.id) 6 assertString(aggregation.id) 7 assertFlag(minimize)

148

i i

i i i “output” — 2018/6/21 — 7:43 — page 149 — #149 i i i

8 assertCharacter(properties , any.missing = FALSE) 9 assertFunction(fun) 10 assertList(extra.args) 11 assertString(measure.name) 12 assertString(aggregation.name) 13 assertString(note) 14 f o r c e ( fun ) 15 fun1 = function(task, model, pred, feats , extra.args) NA_real_ 16 custom = makeMeasure(id = measure.id , minimize, properties , 17 fun1, extra.args, best = best, worst = worst, name = measure. name , 18 note = note ) 19 fun2 = function(task, perf.test , perf.train , measure, group, 20 pred) fun(task, group, pred, extra.args) 21 aggr = makeAggregation(id = aggregation.id, name = aggregation. name , 22 properties = character(0L), fun = fun2) 23 setAggregation(custom, aggr) 24 }

Wartości poszczególnych metod to: 1) algorytm Smitha–Watermanna dla tokenów: 0,266; 2) odległość q-gramowa dla wywołań funkcji: 0,151; 3) algorytm SimilaR: 0,513. Wartość na drugim wymiarze to −0,250. Choć na pierwszy rzut oka funkcje nie przejawiają większego podobieństwa, oka- zuje się, że z punktu widzenia wygenerowanych grafów taka zbieżność istnieje. Wy- nika ona z dużej liczby parametrów, jakie mają obie badane funkcje. Około połowa wierzchołków w obu grafach to wierzchołki reprezentujące parametry. Innymi podobień- stwami jest występowanie stałych napisowych, a także czteroargumentowej funkcji: c w ggplot2_discrete_scale oraz makeAggregation w mlr_makeCustomResampledMea- sure.

Niepodobna para funkcji, stosunkowo duża wartość odległości q-gramowej. Rozważmy następującą parę funkcji:

1 ape_as.phylo.hclust <− function (x, ...) 2 { 3 N <− dim(x$merge) [1] 4 edge <− matrix(0L, 2 ∗ N, 2)

149

i i

i i i “output” — 2018/6/21 — 7:43 — page 150 — #150 i i i

5 edge.length <− numeric (2 ∗ N) 6 node <− i n t e g e r (N) 7 node [N] <− N + 2L 8 cur . nod <− N + 3L 9 j <− 1L 10 for (i in N:1) { 11 edge[j:(j + 1), 1] <− node [ i ] 12 for (l in 1:2) { 13 k <− j + l − 1L 14 y <− x$merge[i, l] 15 i f ( y > 0) { 16 edge [ k , 2 ] <− node [ y ] <− cur . nod 17 cur . nod <− cur . nod + 1L 18 edge.length[k] <− x$height[i] − x$height[y] 19 } 20 e l s e { 21 edge [ k , 2 ] <− −y 22 edge.length[k] <− x$height[i] 23 } 24 } 25 j <− j + 2L 26 } 27 if (is.null(x$labels)) 28 x$ l a b e l s <− as.character(1:(N + 1)) 29 obj <− list(edge = edge, edge.length = edge.length/2, tip.label = x$ l a b e l s , 30 Nnode = N) 31 class(obj) <− " phylo " 32 reorder(obj) 33 }

1 mgcv_Predict .matrix.gp.smooth <− function (object , data) 2 { 3 nk <− nrow(object$knt) 4 for (i in 1:object$dim) { 5 xx <− data[[ object$term[i ]]] 6 i f ( i == 1) { 7 n <− length ( xx ) 8 x <− matrix(xx, n, object$dim) 9 } 10 e l s e { 11 if (n != length(xx))

150

i i

i i i “output” — 2018/6/21 — 7:43 — page 151 — #151 i i i

12 stop("arguments of smooth not same dimension") 13 x [ , i ] <− xx 14 } 15 } 16 x <− sweep(x, 2, object$shift) 17 i f (n > nk ) { 18 n . chunk <− n%/%nk 19 for (i in 1:n.chunk) { 20 ind <− 1 : nk + ( i − 1) ∗ nk 21 Xc <− gpE(x = x[ind, , drop = FALSE], xk = object$knt, 22 object$gp.defn) 23 Xc <− cbind (Xc %∗% object$UZ, gpT(x = x[ind, , drop = FALSE ] ) ) 24 i f ( i == 1) 25 X <− Xc 26 e l s e { 27 X <− rbind(X, Xc) 28 rm(Xc) 29 } 30 } 31 if (n> ind[nk]) { 32 ind <− (ind[nk] + 1):n 33 Xc <− gpE(x = x[ind, , drop = FALSE], xk = object$knt, 34 object$gp.defn) 35 Xc <− cbind (Xc %∗% object$UZ, gpT(x = x[ind, , drop = FALSE ] ) ) 36 X <− rbind(X, Xc) 37 rm(Xc) 38 } 39 } 40 e l s e { 41 X <− gpE(x = x, xk = object$knt, object$gp.defn) 42 X <− cbind (X %∗% object$UZ, gpT(x = x)) 43 } 44 X}

151

i i

i i i “output” — 2018/6/21 — 7:43 — page 152 — #152 i i i

0.2

0.1

0.0 Sk ł adowa g ówna 2 -0.1

-0.2

0.0 0.5 1.0

Składowa główna 1

Rysunek 4.6. Zbiór par funkcji w przestrzeni po zastosowaniu PCA, podejście symetryczne

Wartości poszczególnych metod to: 1) algorytm Smitha–Watermanna dla tokenów: 0,191; 2) odległość q-gramowa dla wywołań funkcji: 0,644; 3) algorytm SimilaR: 0,243. Wartość na drugim wymiarze to 0,300. Jest to bardzo ewidentny przykład ilustrujący, dlaczego odległość q-gramowa na wy- wołaniach funkcji, zwłaszcza z q = 1, nie powinna być używana samodzielnie, a jedynie jako metoda pomocnicza. Zaprezentowana para współdzieli wiele podobnych wywołań nazw funkcji. Co warte odnotowania, nie są to „typowe” funkcje, ale w większości takie jak operator dodawania (+), dolar ($), pętla for czy instrukcja warunkowa if.

4.3.3 Wpływ poszczególnych metod na ostateczną decyzję

Ciekawym zagadnieniem towarzyszącym agregacji było stwierdzenie, jaki wpływ poszcze- gólne metody porównywania kodu mają na podjęcie ostatecznej decyzji. W tym celu zebraliśmy wagi znalezione w naszym podejściu (wyniki w tab. 4.12), a także zbadali- śmy miarę MDA (ang. mean decrease accuracy) w nauczonym modelu drzew losowych (tab. 4.11). MDA oznacza następujący wskaźnik: badamy, jak bardzo spadnie jakość pre- dykcji, jeśli daną cechę spermutujemy w sposób losowy w zbiorze testowym. Im większa wartość spadku, tym ważniejsza jest dana cecha. W obu przypadkach zdecydowanie największą wagę otrzymał nasz algorytm, SimilaR. Jego waga potrafi być od prawie 2 do 6 razy większa, niż następnej pod względem ważności metody.

152

i i

i i i “output” — 2018/6/21 — 7:43 — page 153 — #153 i i i

0.2

0.1

0.0

-0.1 Sk ł adowa g ówna 2

-0.2

-0.3

0.0 0.5 1.0

Składowa główna 1

Rysunek 4.7. Zbiór par funkcji w przestrzeni po zastosowaniu PCA, podejście agregujące

Gdy wszystkie metody były brane pod uwagę, dużą wagę otrzymała także metoda grafowa Weisfeilera–Lehmana, odległość Levenshteina dla tokenów oraz zachłanne kafel- kowanie ciągów i odległość q-gramowa dla liter. W przypadku metod najlepszego ciągu zdecydowanie dużą wagę uzyskało zachłanne kafelkowanie ciągów działające na tokenach. Następną w kolejności jest odległość Levenshteina dla tokenów przy podejściu symetrycz- nym i agregującym, ale dla podejścia niesymetrycznego ważniejszy okazał się algorytm Smitha–Watermana dla tokenów. Gdy rozważamy metody szybkie, odległość q-gramowa na wywołaniach funkcji zdaje się być ważniejsza, gdy rozważamy lasy losowe, za to algo- rytm Smitha–Watermana dla tokenów w przypadku zaproponowanej metody.

153

i i

i i i “output” — 2018/6/21 — 7:43 — page 154 — #154 i

i i SimilaR 13 ,

36,16 47,07 35,14 43,62 40,03 40,45 41,38 37,60 loymW–L algorytm

34 49 49 92 , , , q-gramy—tokeny

9736 0 0 95 23 23 23 75 15 63 0 , , , , , , -rm—y.fun. q-gramy—wyw.

9846 0 0 0 0 39 9 69 8 26 0 0 , , , , , q-gramy—litery

54 11 85 11 05 9 7

, , , ZKC—tokeny 4 14

,

67 18 47 14 20 0 0 19 , , , K—y.fun. ZKC—wyw.

54 21 27 9 28 9 , , , ZKC—litery

71 12 7960 0 0 0 0 0 0 0 13 0 12 74 12 34 9 , , , , , S–W—tokeny

09 19 85 0 0 0 0 24 38 0 0 43 76 0 0 0 0 27 05 0 0 0 0 17 83 32 36 20 36 17 35 14 , , , , , , , , , –—y.fun. S–W—wyw. 48 15 89 9 39 11

, , ,

5 S–W—litery 15 16 29 5

, ,

11,6 Levensh.—tokeny

65 28 90 0 0 28 0615 0 0 0 7 0 15 71 39 8 , , , , , , ees.ww fun. Levensh.—wyw. 57 21 52 16 31 10 , , ,

6 Levensh.—litery 0 0 0 0 0 14 0 0 41 0 0 0 0 0 10 0 0 0 0 0 17 0 0 22 0 0 12 43 8 84 8 , , 8 19 MDA przydzielone poszczególnym metodom przez lasy losowe; im większa wartość, tym ważniejsza cecha agregujące 13 agregujące agregujące niesymetryczne niesymetryczne niesymetryczne Tabela 4.11. szybkie symetryczne wszystkie symetryczne grupa metod podejście najlepszego ciągu symetryczne

154

i i

i i i “output” — 2018/6/21 — 7:43 — page 155 — #155 i

i i SimilaR 23

,

0,24 0,72 0,16 0,67 0,87 0,84 0,84 0,88 loymW–L algorytm

08 11 0 09 , , , q-gramy—tokeny

04 0 04 0 04 0 03 0 06 0 01 0 , , , , , , -rm—y.fun. q-gramy—wyw.

05 0 06 0 04 0 0 04 0 03 0 0 07 0 0 , , , , , , q-gramy—litery

06 0 07 0 08 0 , , , ZKC—tokeny

05 0 08 0 04 0 19 0 0 0 , , , , K—y.fun. ZKC—wyw.

05 0 05 0 06 0 , , , ZKC—litery

08 0 08 0 07 0 10 0 0 0 0 0 03 0 0 0 0 0 , , , , , S–W—tokeny

04 0 06 0 06 0 02 0 0 0 02 0 09 0 0 0 0 0 11 0 13 0 0 0 0 0 05 0 0 0 0 0 , , , , , , , , , –—y.fun. S–W—wyw.

06 0 04 0 04 0 , , , S–W—litery

10 0 05 0 05 0 , , , Levensh.—tokeny

08 0 03 0 0 0 08 0 06 0 18 0 0 0 01 0 0 0 , , , , , , ees.ww fun. Levensh.—wyw. 04 0 04 0 05 0 , , ,

Wagi przydzielone przez zaproponowaną przez nas metodę Levensh.—litery 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 07 0 06 0 05 0 , , , 0 0 0 Tabela 4.12. agregujące agregujące agregujące niesymetryczne niesymetryczne niesymetryczne szybkie symetryczne wszystkie symetryczne grupa metod podejście najlepszego ciągu symetryczne

155

i i

i i i “output” — 2018/6/21 — 7:43 — page 156 — #156 i i i

156

i i

i i i “output” — 2018/6/21 — 7:43 — page 157 — #157 i i i

Raportowanie wyników użytkownikowi końcowemu 5

W poprzednich rozdziałach opisaliśmy sposób generowania danych benchmarkowych, algo- rytmy porównywania funkcji, sposoby ostatecznego przypisywania podobieństwa i przy- dzielania danej pary do odpowiedniej klasy, a także dokonaliśmy analizy statystycznej przedstawionych metod. W tym rozdziale opiszemy zastosowania niniejszej pracy: opi- szemy pakiet w języku R, który implementuje algorytm SimilaR, a także serwis interne- towy, który pozwala w wygodny sposób porównać funkcje także osobom, które nie progra- mują w tym języku. Na końcu prześledzimy przykład działania systemu na rzeczywistych, publicznie dostępnych danych.

5.1 Pakiet SimilaR

Pakiet implementujący nasz autorski algorytm grafowy nazywa się SimilaR i znajduje się w repozytorium CRAN1, dlatego instalacja go w środowisku R odbywa się poprzez pojedyncze wywołanie install.packages(“SimilaR”). Główną funkcją pakietu jest SimilaR_fromDirectory(), która przyjmuje jako para- metr dirname ścieżkę do katalogu, w którym znajdują się pliki źródłowe języka R. Funkcje zdefiniowane w tych plikach będą porównywane. Parametr fileTypes może przyjmować jedną z dwóch wartości: — function – każda funkcja jest porównywana z każdą, także te, które są zdefiniowane w tym samym pliku; — file – dwie funkcje są porównywane ze sobą tylko wtedy, gdy nie są zdefiniowane w tym samym pliku. Parametr aggregation odpowiada za sposób interpretacji wyników metod i ich ew. agregacji, zgodnie z podrozdz. 3.2 i p. 4.2.1. I tak możliwe wartości tego parametru to: — sym – algorytm SimilaR jest uruchamiany w wersji symetrycznej, zwracana jest

1https://CRAN.R-project.org/package=SimilaR

i i

i i i “output” — 2018/6/21 — 7:43 — page 158 — #158 i i i

jedna wartość opisująca, jak podobna jest dana para; — aver – algorytm SimilaR jest uruchamiany w wersji niesymetrycznej, jednak jego dwie wartości są agregowane przez średnią arytmetyczną (wybór tej funkcji agregu- jącej wynika z wyników opisanych w podrozdz. 3.5.5); — both – algorytm SimilaR jest uruchamiany w wersji niesymetrycznej, obie wartości są zwracane. Ostatnim parametrem jest returnType, którym decydujemy o sposobie zwrócenia wyników: — data.frame – wynikiem jest ramka danych, w której każdy wiersz odpowiada jednej parze badanych funkcji. Pierwsze dwie kolumny zawierają nazwy tychże, następna kolumna (lub dwie, jeśli parametr aggregation był równy both) zawiera miarę podobieństwa zwróconą przez algorytm SimilaR, a ostatnia zawiera binarną decyzję: 1, jeśli para jest podobna i 0 w przeciwnym wypadku; — matrix – wynikiem jest kwadratowa macierz podobieństwa S, której liczba wierszy

i kolumn odpowiada liczbie badanych funkcji. Wartość Sij odpowiada temu, jak po- dobne są funkcje i-ta oraz j-ta (gdy aggregation jest równy sym lub aver) lub jaką częścią funkcji j-tej jest funkcja i-ta (gdy aggregation jest równe both). Funkcja wykrywa sytuację, kiedy występuje błąd składniowy w którymś z plików. W takim wypadku wyświetla informację, w którym pliku oraz w którym wierszu nastąpił błąd parsowania wraz z odpowiednim komunikatem. Pakiet zawiera także drugą funkcję, SimilaR_fromTwoFunctions, która pozwala na proste przetestowanie algorytmu bez potrzeby tworzenia plików na dysku twardym. Wszystkie parametry są takie same, jak opisane powyżej, poza dirname i fileTypes. Zamiast nich są dwa inne: function1 oraz function2. Przyjmują one obiekty funkcji zdefiniowanych w języku R, które mają zostać porównane. Przykładowo:

1 # install .packages("SimilaR")

2

3 library(SimilaR)

4

5 znajdzMinimum <− function(wektor) 6 { 7 stopifnot(is .numeric(wektor), length(wektor) >= 1)

8

9 mini <− wektor [ 1 ] 10 indeks <− 2 11 while(indeks <= length(wektor)) 12 {

158

i i

i i i “output” — 2018/6/21 — 7:43 — page 159 — #159 i i i

13 if(wektor[indeks] < mini) 14 { 15 mini = wektor[indeks] 16 } 17 indeks <− indeks + 1 18 } 19 mini 20 }

21

22 znajdzMaksimum <− function(wektor) 23 { 24 stopifnot(is .numeric(wektor)) 25 stopifnot(length(wektor) >= 1)

26

27 maks <− wektor [ 1 ] 28 for(element in wektor) 29 { 30 if(element > maks) 31 { 32 maks = element 33 } 34 } 35 return(maks) 36 }

37

38 SimilaR_fromTwoFunctions(znajdzMinimum , 39 znajdzMaksimum , 40 aggregation = "both", 41 returnType = "data.frame")

42

43 # name1 name2 SimilaR12 SimilaR21 decision 44 # 1 znajdzMinimum znajdzMaksimum 0.7333333 0.8461538 1

5.2 Interfejs webowy

Dla osób, które nie mają doświadczenia programistycznego przeznaczony jest serwis inter- netowy SimilaR2, który opisywany był m.in. we wpisie na blogu3. Rozszerza on możliwości pakietu: używane są metody szybkie (definicja w p. 4.2.1), a także oferuje wizualizację analizy skupień. Aby skorzystać z serwisu należy najpierw założyć konto. Jest ono weryfikowane, aby

2http://www.ibspan.waw.pl/~similar/ lub http://similar.rexamine.com/ 3https://www.r-bloggers.com/similar/

159

i i

i i i “output” — 2018/6/21 — 7:43 — page 160 — #160 i i i

dostęp do usługi mieli jedynie nauczyciele, w odróżnieniu od studentów, którzy mogliby próbować dostosować swój kod w ten sposób, aby ominąć wykrycie przez system. Po pozytywnej weryfikacji można przesłać pliki z funkcjami do porównania. Pliki po- winny mieć rozszerzenie .R. Serwis daje możliwość wyboru, czy chcemy, aby porównywane były funkcje niezależnie od tego, w jakim pliku zostały zdefiniowane, funkcje z różnych plików czy też różnych grup plików. Ten etap został zilustrowany na rys. 5.1. Po przesłaniu plików, następuje ich wstępna weryfikacja. Są one parsowane i jeśli zosta- nie wykryty błąd składniowy, odpowiedni komunikat z informacją o miejscu występowania błędu zostanie wyświetlony. Jeśli jednak pliki były poprawne, zostanie wyświetlona in- formacja, jakie funkcje zostały wykryte, wraz z przydziałem do plików lub grupy plików. Ta faza jest przedstawiona na rys. 5.2. W dalszej kolejności użytkownik powraca do spisu swoich zgłoszeń (w serwisie sub- missions). Jest w nim wyświetlany pasek postępu dla każdego z nich (rys. 5.3). Po po- prawnym przetworzeniu, możliwe jest obejrzenie wyników. Dzielą się one na dwie główne części: pierwsza to ta, w której można obejrzeć pary funkcji, posortowane od najbardziej podobnej. W tym wypadku funkcje są wyświetlane po uspójnieniu ich formatowania (por. podrozdz. 3.1). Przy każdej parze można wybrać, czy uważamy, że jest ona rzeczywiście podobna i jak bardzo (do wyboru są definitely similar, similar, hard to say, dissimilar, to- tally different). Decyzje te mogą zostać użyte do nauki nowych modeli podczas przyszłych badań. Sposób wyświetlania par zobrazowany jest na rys. 5.4. Drugą częścią wyników jest zobrazowanie analizy skupień w grafie. W tym wypadku interpretujemy wierzchołki jako pojedyncze funkcje, zaś krawędź pomiędzy dwoma wierz- chołkami oznacza decyzję systemu o zaklasyfikowaniu danej pary do klasy 1. Użyty al- gorytm wykrywania skupień to Louvain community detection algorithm [23], którego im- plementacja jest oparta na tej zawartej pod adresem https://bitbucket.org/taynaud/ python-louvain/overview. Wierzchołki należące do tego samego skupienia oznaczone są tym samym kolorem, a nad nimi znajduje się nazwa funkcji, którą reprezentują. Ist- nieje możliwość przesuwania wierzchołków kursorem myszy w celu zapewnienia więk- szej przejrzystości. Przykładowa wizualizacja skupień w badanym zgłoszeniu znajduje się na rys. 5.5.

5.3 Analiza zbioru rzeczywistego

Przejdźmy teraz do omówienia przykładu działania systemu na rzeczywistych, publicz- nie dostępnych danych. Porównamy dwa pakiety dla języka R, dostępne w repozytorium

160

i i

i i i “output” — 2018/6/21 — 7:43 — page 161 — #161 i i i

Rysunek 5.1. Serwis internetowy SimilaR, tworzenie nowego zgłoszenia

CRAN: pakiet nortest4 oraz pakiet DescTools5. Pierwszy pakiet autorzy opisują jako zbiór pięciu testów statystycznych, podczas gdy drugi ma być zbiorem różnych użytecz- nych narzędzi statystycznych potrzebnych przy typowych zadaniach statystyki opisowej, takich jak liczenie poszczególnych statystyk, rysowanie graficznych podsumowań, tworze- nia raportów itp., przy czym autorzy zastrzegają się: Many of the included functions can be found scattered in other packages and other sources written partly by Titans of R. Kody źródłowe obu pakietów są dostępne pod podanymi adresami, bezpośrednio z re- pozytoriów CRAN. Proces zgłaszania obu pakietów celem znalezienia podobnych funkcji został przedstawiony na rysunkach dotyczących serwisu internetowego SimilaR. Same ob- liczenia, dla 3900 par (5 funkcji z pakietu nortest oraz 780 funkcji z pakietu DescTools) zajęły 3 minuty 40 sekund. W wyniku znaleziono 3 pary oraz jedną trójkę podobnych funkcji. Są to (ze względu na podobieństwo rzędu 1 do 1, raportowana jest jedynie jedna funkcja): — sf.test() w pliku sf.test.R z pakietu nortest oraz ShapiroFranciaTest w pliku

4https://CRAN.R-project.org/package=nortest 5https://CRAN.R-project.org/package=DescTools

161

i i

i i i “output” — 2018/6/21 — 7:43 — page 162 — #162 i i i

Tests.R z pakietu DescTools:

1 ShapiroFranciaTest <− function(x) { 2 DNAME <− deparse(substitute(x)) 3 x <− sort(x[complete.cases(x)]) 4 n <− length ( x ) 5 if ((n< 5 || n> 5000)) 6 stop("sample size must be between 5 and 5000") 7 y <− qnorm(ppoints(n, a~= 3/8)) 8 W~<− cor ( x , y ) ^2 9 u~<− l o g (n) 10 v <− l o g (u) 11 mu~<− −1.2725 + 1.0521 ∗ ( v − u) 12 s i g <− 1.0308 − 0.26758 ∗ ( v + 2/u) 13 z~<− ( l o g (1 − W) − mu) / s i g 14 pval <− pnorm(z, lower. tail = FALSE) 15 RVAL <− list(statistic = c(W=W), p.value = pval, method = " Shapiro−Francia normality test", 16 data . name = DNAME) 17 class (RVAL) <− " h t e s t " 18 return (RVAL) 19 }

— pearson.test() w pliku pearson.test.R z pakietu nortest oraz PearsonTest w pliku Tests.R z pakietu DescTools:

1 PearsonTest <− function(x, n.classes = ceiling(2 ∗ (n^(2/5))) , 2 adjust = TRUE) { 3 DNAME <− deparse(substitute(x)) 4 x <− x[complete.cases(x)] 5 n <− length ( x ) 6 if (adjust) { 7 dfd <− 2 8 } 9 e l s e { 10 dfd <− 0 11 } 12 num <− floor(1 + n.classes ∗ pnorm(x, mean(x), sd(x))) 13 count <− tabulate(num, n.classes) 14 prob <− rep(1/n.classes , n.classes) 15 xpec <− n ∗ prob 16 h <− ( ( count − xpec)^2)/xpec

162

i i

i i i “output” — 2018/6/21 — 7:43 — page 163 — #163 i i i

17 P <− sum(h) 18 pvalue <− pchisq(P, n.classes − dfd − 1, lower.tail = FALSE) 19 RVAL <− list(statistic = c(P = P), p.value = pvalue, method = "Pearson chi−square normality test", 20 data.name = DNAME, n.classes = n.classes , df = n.classes −

21 1 − dfd ) 22 class (RVAL) <− " h t e s t " 23 return (RVAL) 24 }

— lillie.test() w pliku lillie.test.R z pakietu nortest oraz LillieTest w pliku Tests.R z pakietu DescTools:

1 LillieTest <− function(x) { 2 DNAME <− deparse(substitute(x)) 3 x <− sort(x[complete.cases(x)]) 4 n <− length ( x ) 5 i f (n < 5) 6 stop("sample size must be greater than 4") 7 p <− pnorm ( ( x − mean(x))/sd(x)) 8 Dplus <− max(seq(1:n)/n − p) 9 Dminus <− max(p − ( seq ( 1 : n) − 1) /n) 10 K <− max(Dplus , Dminus) 11 if (n<= 100) { 12 Kd <− K 13 nd <− n 14 } 15 e l s e { 16 Kd <− K ∗ ((n/100)^0.49) 17 nd <− 100 18 } 19 pvalue <− exp ( −7.01256 ∗ Kd^2 ∗ (nd + 2.78019) + 2.99587 ∗ 20 Kd ∗ sqrt(nd + 2.78019) − 0.122119 + 0.974598/sqrt(nd) + 21 1.67997/nd) 22 if (pvalue > 0.1) { 23 KK <− ( s q r t (n) − 0.01 + 0.85/sqrt(n)) ∗ K 24 if (KK<= 0.302) { 25 pvalue <− 1 26 } 27 else if (KK<= 0.5) { 28 pvalue <− 2.76773 − 19.828315 ∗ KK + 80.709644 ∗

163

i i

i i i “output” — 2018/6/21 — 7:43 — page 164 — #164 i i i

29 KK^2 − 138.55152 ∗ KK^3 + 81.218052 ∗ KK^4 30 } 31 else if (KK<= 0.9) { 32 pvalue <− −4.901232 + 40.662806 ∗ KK − 97.490286 ∗ 33 KK^2 + 94.029866 ∗ KK^3 − 32.355711 ∗ KK^4 34 } 35 else if (KK<= 1.31) { 36 pvalue <− 6.198765 − 19.558097 ∗ KK + 23.186922 ∗ 37 KK^2 − 12.234627 ∗ KK^3 + 2.423045 ∗ KK^4 38 } 39 e l s e { 40 pvalue <− 0 41 } 42 } 43 RVAL <− list(statistic = c(D = K), p.value = pvalue, method = "Lilliefors (Kolmogorov−Smirnov) normality test", 44 data . name = DNAME) 45 class (RVAL) <− " h t e s t " 46 return (RVAL) 47 }

— została także znaleziona następująca trójka: ad.test() w pliku ad.test.R z pakietu nortest, cvm.test() w pliku cvm.test.R z pakietu nortest oraz ShapiroFrancia- Test w pliku Tests.R z pakietu DescTools. Zastanawiać może, czemu w tym zesta- wieniu znalazł się ad.test() (test Andersona-Darlinga), który jest innym testem. Wynika to z faktu, że jedyna różnica pomiędzy nim, a testem CVM (test Craméra- von Misesa) są stałe liczbowe oraz tekstowe użyte w implementacji. Wszystkie metody ignorują konkretne wartości dowolnych stałych, traktując je identycznie, co w ogólnym przypadku pozwala na wykrycie podobnych kodów o trywialnych mo- dyfikacjach. W tym wypadku jednak to właśnie one decydują o tym, że funkcje te są inne.

164

i i

i i i “output” — 2018/6/21 — 7:43 — page 165 — #165 i i i

Poniżej implementacja testu CVM:

1 CramerVonMisesTest <− function(x) { 2 DNAME <− deparse(substitute(x)) 3 x <− sort(x[complete.cases(x)]) 4 n <− length ( x ) 5 i f (n < 8) 6 stop("sample size must be greater than 7") 7 p <− pnorm ( ( x − mean(x))/sd(x)) 8 W~<− (1 / (12 ∗ n) + sum ( ( p − (2 ∗ seq ( 1 : n) − 1) / (2 ∗ n) ) ^2) ) 9 WW<− (1 + 0 . 5 /n) ∗ W 10 if (WW< 0.0275) { 11 pval <− 1 − exp ( −13.953 + 775.5 ∗ WW − 12542.61 ∗ WW^2) 12 } 13 else if (WW< 0.051) { 14 pval <− 1 − exp ( −5.903 + 179.546 ∗ WW − 1515.29 ∗ WW^2) 15 } 16 else if (WW< 0.092) { 17 pval <− exp ( 0 . 8 8 6 − 31.62 ∗ WW+ 10.897 ∗ WW^2) 18 } 19 else if (WW< 1.1) { 20 pval <− exp ( 1 . 1 1 1 − 34.242 ∗ WW+ 12.832 ∗ WW^2) 21 } 22 e l s e { 23 warning ( "p−value is smaller than 7.37e −10, cannot be computed more accurately") 24 pval <− 7.37 e−10 25 } 26 RVAL <− list(statistic = c(W=W), p.value = pval, method = "Cramer−von Mises normality test", 27 data . name = DNAME) 28 class (RVAL) <− " h t e s t " 29 return (RVAL) 30 }

165

i i

i i i “output” — 2018/6/21 — 7:43 — page 166 — #166 i i i

oraz testu ad.test():

1 "ad . t e s t " <− function(x) { 2 DNAME <− deparse(substitute(x)) 3 x <− sort(x[complete.cases(x)]) 4 n <− length ( x ) 5 i f (n < 8) 6 stop("sample size must be greater than 7") 7 logp1 <− pnorm ( ( x − mean(x))/sd(x) , log.p = TRUE) 8 logp2 <− pnorm(−(x − mean(x))/sd(x) , log.p = TRUE) 9 h <− (2 ∗ seq ( 1 : n) − 1) ∗ (logp1 + rev(logp2)) 10 A~<− −n − mean(h) 11 AA <− (1 + 0.75/n + 2.25/n^2) ∗ A 12 if (AA< 0.2) { 13 pval <− 1 − exp ( −13.436 + 101.14 ∗ AA − 223.73 ∗ AA^2) 14 } 15 else if (AA< 0.34) { 16 pval <− 1 − exp ( −8.318 + 42.796 ∗ AA − 59.938 ∗ AA^2) 17 } 18 else if (AA< 0.6) { 19 pval <− exp (0 .9177 − 4.279 ∗ AA − 1.38 ∗ AA^2) 20 } 21 else if (AA< 10) { 22 pval <− exp (1 .2937 − 5.709 ∗ AA + 0.0186 ∗ AA^2) 23 } 24 e l s e pval <− 3 . 7 e−24 25 RVAL <− list(statistic = c(A = A), p.value = pval, method = "Anderson−Darling normality test", 26 data . name = DNAME) 27 class (RVAL) <− " h t e s t " 28 return (RVAL) 29 }

Podsumowując, udało się stworzyć ogólnodostępny system, który działa szybko także dla dużych ilości danych oraz znajduje podobne funkcje nie tylko dla sztucznie wygenero- wanych zbiorów testowych, ale także w rzeczywistych aplikacjach. Pozwala on wygodnie sprawdzić grupę plików bez obciążania swojego komputera, a także obejrzeć zależności „z lotu ptaka”, poprzez wizualizację analizy skupień. Jeśli jednak z jakiegoś powodu użycie interfejsu webowego nie jest możliwe, np. przy braku dostępu do Internetu lub chęci wprowadzenia testowania podobieństwa kodu na lokalnym komputerze jako część większego procesu, dostępny pakiet SimilaR pozwala na łatwe użycie tej funkcjonalności w swoim programie.

166

i i

i i i “output” — 2018/6/21 — 7:43 — page 167 — #167 i i i

Rysunek 5.2. Serwis internetowy SimilaR, podgląd funkcji wykrytych przy zgłoszeniu

167

i i

i i i “output” — 2018/6/21 — 7:43 — page 168 — #168 i i i

Rysunek 5.3. Serwis internetowy SimilaR, lista zgłoszeń wraz z paskami postępu

Rysunek 5.4. Serwis internetowy SimilaR, wyniki dla danego zgłoszenia, część wyświetlająca pary funkcji wraz z ich oceną podobieństwa

168

i i

i i i “output” — 2018/6/21 — 7:43 — page 169 — #169 i i i

Rysunek 5.5. Serwis internetowy SimilaR, wyniki dla danego zgłoszenia, część wyświetlająca wizualizację analizy skupień

169

i i

i i i “output” — 2018/6/21 — 7:43 — page 170 — #170 i i i

170

i i

i i i “output” — 2018/6/21 — 7:43 — page 171 — #171 i i i

Podsumowanie 6

6.1 Weryfikacja celów i hipotez badawczych

Wyniki testów przedstawione w pracy stanowią czytelne przesłanki prawdziwości posta- wionych w rozdz. 1 hipotez badawczych. W ramach prac nad niniejszą rozprawą przeprowadzono serię badań i eksperymentów dotyczących wykrywania podobnych fragmentów kodu źródłowego. Zaproponowano autorski sposób generowania danych testowych, który pozwala na kon- trolowanie różnych parametrów zbioru benchmarkowego, takich jak jego rozmiar, liczba funkcji, które są klonowane, czy liczba użytych modyfikacji. Pozwoliło to na ustalenie rzetelnego sposobu prowadzenia dalszych badań. Możemy zatem pozytywnie zweryfiko- wać hipotezę głoszącą, że możliwe jest stworzenie jasno zdefiniowanego podejścia do testowania algorytmów porównywania podobieństwa kodów źródłowych, która pozwoli dokładnie określić ich skuteczność oraz sterować różnymi wła- snościami danych testowych. Następnie zaproponowano rozdzielenie sposobu reprezentacji kodu funkcji od algo- rytmu porównującego, jednocześnie proponując sposób reprezentacji oparty o użyte wy- wołania funkcji. Opisano również sposób generowania grafu zależności programu odpo- wiednio dostosowany do języków funkcyjnych, w szczególności do języka R. Prawdziwą zatem jest hipoteza, że możliwe jest rozdzielenie sposobu reprezentacji funkcji od algorytmu porównującego. W rozprawie zaprezentowano algorytm SimilaR, oparty na porównywaniu grafów za- leżności programu, którego mediana miary F na badanych zbiorach okazała się być równa 0,970, co było najlepszą wartością wśród rozważanych metod, a test statystyczny sumy rang Wilcoxona wskazał pozostałe mediany jako statystycznie znacząco gorsze. Jednocze- śnie metoda ta okazała się zajmować środkowe miejsce pod względem czasu wykonania po- śród opisywanych algorytmów. Wyniki te świadczą o prawdziwości hipotezy, że możliwe jest stworzenie algorytmu porównującego kody źródłowe, opartego na grafach

i i

i i i “output” — 2018/6/21 — 7:43 — page 172 — #172 i i i

zależności programu, który będzie skuteczniejszy niż dotychczas zapropono- wane metody, jednocześnie nie wykonując się dłużej niż inne algorytmy. Przetestowano podejście niesymetryczne, które jest oparte na dwóch wartościach zwra- canych przez każdy z algorytmów. O ile testy statystyczne nie potwierdziły lepszych wy- ników takiego podejścia na poziomie pojedynczych metod względem podejścia symetrycz- nego, o tyle w przypadku agregacji wyników zbioru algorytmów statystycznie znacząco lepsze wyniki udało się uzyskać w każdym przypadku opierając się na dwóch warto- ściach niesymetrycznych (dokonując ich agregacji lub nie). Możemy zatem pozytywnie zweryfikować hipotezę, że podejście polegające na badaniu niesymetrycznego po- dobieństwa, a następnie ewentualna agregacja dwóch wartości w jedną, może dać lepsze wyniki, niż powszechnie stosowane symetryczne podejście. Zaproponowano autorską metodę agregacji wyników poszczególnych metod, która oka- zała się zachowywać dobrze interpretowalne własności modelu, a jej zdolności predykcyjne przerosły znane w literaturze modele. Prawdziwą zatem jest hipoteza, że odpowiednia fuzja wyników z poszczególnych algorytmów pozwoli na zachowanie dobrze interpretowalnych własności modelu oraz, że możliwe jest skonstruowanie po- dejścia agregacyjnego łączącego różne metody porównywania kodów źródło- wych, uzyskując metodę skuteczniejszą od każdego z tych podejść stosowanego samodzielnie.

6.2 Kierunki dalszych badań

Uzyskane wyniki mogą stanowić punkt wyjścia do dalszych badań, które kontynuować można w kilku obszarach. Sposób tworzenia zbiorów testowych można by było rozszerzyć o nowe modyfikacje, np. wykrywanie niezależnych całości w kodzie, które mogłyby być zamykane w osobnych funkcjach. W tym celu można by było posłużyć się techniką znaną pod nazwą program slicing. Ciekawą optymalizacją byłby niezłożony obliczeniowo wstępny wybór par funkcji, które warte są dalszego, dokładniejszego zbadania. W ten sposób system działałby jeszcze szybciej, co pozwoliłoby także na stworzenie pewnej przyrastającej bazy funkcji dotych- czas przebadanych, np. prac studentów z zeszłych lat dotyczących tego samego zagad- nienia, z której przy użyciu takiej metody można by było wybierać funkcje potencjal- nie podobne do aktualnie badanych. Prawdopodobnie taka funkcjonalność opierałaby się na jakimś rodzaju odcisku palca (ang. fingerprint) i korzystała z np. użytych wywołań funkcji. Przebadanie technik znanych z information retrieval czy locality sensitive hashing mogłoby okazać się pomocne.

172

i i

i i i “output” — 2018/6/21 — 7:43 — page 173 — #173 i i i

Opisany system decyduje o tym, czy cała funkcja jest podobna do drugiej lub nie, jed- nak niekoniecznie informuje, które fragmenty pozwoliły na podjęcie takiej decyzji. Stwo- rzenie przyjaznego interfejsu, który uzasadnia, które części kodu są podobne w obu funk- cjach stanowiłoby znaczący wkład w kierunku polepszenia doświadczenia użytkownika. Na pewno należy zastanowić się, czy algorytm SimilaR można uogólnić do skutecznego znajdowania podobnych grafów w innych zastosowaniach niż porównywanie PDG, np. informatyce chemicznej. Zaproponowany algorytm dość silnie korzysta z faktu, że CDS ma strukturę drzewiastą, a także, że wierzchołki na różnych poziomach głębokości nie mają szans być tymi samymi wierzchołkami. Dlatego nadaje się do porównywania dwóch grafów o zasadniczo podobnej strukturze. Oczywiście, nic nie stoi na przeszkodzie, aby w innym zastosowaniu albo w inny sposób wyznaczyć podgraf będący drzewem, albo zrezygnować z tego ograniczenia i porównywać wszystkie wierzchołki ze sobą, niezależnie od ich położenia. Inną kwestią jest zastosowanie wprowadzonej ważności wierzchołka. W przypadku opisywanego problemu, interpretacja jest dość jasna: możemy pominąć wierzchołek ozna- czający pojedynczą instrukcję czy wywołanie, ale brak pętli z wieloma instrukcjami po- woduje znaczącą różnicę. To, ile instrukcji to już wiele, jest skalowane ogólną wielkością grafu i rozkładem ważności w wierzchołkach, ponieważ bierzemy pod uwagę ich medianę. Pomysł wydaje się być możliwy do przeniesienia na inne problemy, jednak dokładna in- terpretacja mogłaby być inna. Podsumowując, możliwe, że wprowadzając niewielkie zmiany w algorytmie, dałoby się go z sukcesem zastosować przy innych problemach, np. w chemioinformatyce, jednak wymagałoby to bliższego przyjrzenia się konkretnej specyfice problemu i przeprowadzenia odpowiednich badań. Zaproponowana w rozdz. 4 metoda agregacji mogłaby zostać rozwinięta. W szczegól-

ności zapis ϕi jako złożenie ψi ◦ Ti dla pewnego rosnącego ψi oraz pewnej t-normy (lub

uninormy, nullnormy, t-konormy) Ti mogłoby zapewnić jeszcze bardziej intuicyjną inter- pretację uzyskanego modelu. Innym kierunkiem byłoby użycie funkcji przynależności zna- nych z logiki rozmytej do prezentowania bardziej stopniowego określania przewidywanej klasy podczas predykcji. Co więcej, przebadanie wpływu współczynnika regularyzującego na współczynniki przy punktach kontrolnych wielomianów mogłoby prowadzić do lepszych wyników.

173

i i

i i i “output” — 2018/6/21 — 7:43 — page 174 — #174 i i i

174

i i

i i i “output” — 2018/6/21 — 7:43 — page 175 — #175 i i i

Bibliografia

[1] Simian. https://www.harukizaemon.com/simian/. [2] H. Abelson, G. J. Sussman. Structure and Interpretation of Computer Programs. MIT Press, Cambridge, MA, USA, wydanie 2, 1996. [3] A. T. Ali, H. M. Abdulla, V. Snasel. Overview and comparison of plagiarism de- tection tools. Proceedings of the Dateso 2011: Annual International Workshop on Databases, Texts, Specifications and Objects, Dateso ’11, strony 161–172, 2011. [4] N. S. Altman. An introduction to kernel and nearest-neighbor nonparametric re- gression. The American Statistician, 46(3):175–185, 1992. [5] L. Babai, L. Kucera. Canonical labelling of graphs in linear average time. Proce- edings of the 20th Annual Symposium on Foundations of Computer Science, SFCS ’79, strony 39–46, Washington, DC, USA, 1979. IEEE Computer Society. [6] C. Barski. Land of Lisp: Learn to Program in Lisp, One Game at a Time! No Starch Press, San Francisco, CA, USA, wydanie 1, 2010. [7] M. Bartoszuk. Solving systems of polynomial equations: A novel end condition and root computation method. 2014 Federated Conference on Computer Science and Information Systems, strony 543–552, 2014. [8] M. Bartoszuk, G. Beliakov, M. Gagolewski, S. James. Fitting aggregation functions to data: Part I - linearization and regularization. J. P. Carvalho, i in., redakto- rzy, Information Processing and Management of Uncertainty in Knowledge-Based Systems, strony 767–779. Springer International Publishing, 2016. [9] M. Bartoszuk, G. Beliakov, M. Gagolewski, S. James. Fitting aggregation functions to data: Part II - idempotization. J. P. Carvalho, i in., redaktorzy, Information Processing and Management of Uncertainty in Knowledge-Based Systems, strony 780–789. Springer International Publishing, 2016. [10] M. Bartoszuk, M. Gagolewski. A fuzzy R code similarity detection algorithm. A. Laurent, i in., redaktorzy, Information Processing and Management of Uncerta- inty in Knowledge-Based Systems, strony 21–30. Springer International Publishing,

i i

i i i “output” — 2018/6/21 — 7:43 — page 176 — #176 i i i

2014. [11] M. Bartoszuk, M. Gagolewski. Detecting similarity of R functions via a fusion of multiple heuristic methods. IFSA-EUSFLAT, strony 419–426, 2015. [12] M. Bartoszuk, M. Gagolewski. Binary aggregation functions in software plagiarism detection. 2017 IEEE International Conference on Fuzzy Systems (FUZZ-IEEE), strony 1–6, 2017. [13] I. D. Baxter, A. Yahin, L. Moura, M. Sant’Anna, L. Bier. Clone detection using abstract syntax trees. Proceedings of the International Conference on Software Ma- intenance, ICSM ’98, strony 368–378, Washington, DC, USA, 1998. IEEE Computer Society. [14] T. Bayes. LII. An essay towards solving a problem in the doctrine of chances. By the late Rev. Mr. Bayes, F. R. S. communicated by Mr. Price, in a letter to John Canton, A. M. F. R. S. Philosophical Transactions, 53:370–418, 1763. [15] G. Beliakov. Monotone approximation of aggregation operators using least squ- ares splines. International Journal of Uncertainty, Fuzziness and Knowledge-based Systems, 10:659–676, 2002. [16] G. Beliakov. How to build aggregation operators from data. International Journal of Intelligent Systems, 18:903–923, 2003. [17] G. Beliakov. Learning weights in the generalized OWA operators. Fuzzy Optimiza- tion and Decision Making, 4:119–130, 2005. [18] G. Beliakov, H. Bustince, T. Calvo. A practical guide to averaging functions. Sprin- ger, 2016. [19] G. Beliakov, A. Pradera, T. Calvo. Aggregation functions: A guide for practitioners. Springer-Verlag, 2007. [20] G. Beliakov, J. Warren. Appropriate choice of aggregation operators in fuzzy deci- sion support systems. IEEE Transactions on fuzzy systems, 9(6):773–784, 2001. [21] R. Bennett. Factors associated with student plagiarism in a post-1992 university. Assessment & Evaluation in Higher Education, 30(2):137–162, 2005. [22] P. Biecek. Przewodnik po pakiecie R. GiS, Wrocław, 2017. [23] V. D. Blondel, J.-L. Guillaume, R. Lambiotte, E. Lefebvre. Fast unfolding of commu- nities in large networks. Journal of Statistical Mechanics: Theory and Experiment, 2008(10):P10008, 2008. [24] L. Breiman. Bagging predictors. Machine Learning, 24(2):123–140, 1996. [25] L. Breiman. Random forests. Machine Learning, 45(1):5–32, 2001. [26] L. Breiman. Manual on setting up, using, and understanding random forests v3.1. http://oz.berkeley.edu/users/breiman/Using_random_forests_V3.1.

176

i i

i i i “output” — 2018/6/21 — 7:43 — page 177 — #177 i i i

pdf, 2002. [27] L. Breiman, J. H. Friedman, R. A. Olshen, C. J. Stone. Classification and Regres- sion Trees. Statistics/Probability Series. Wadsworth Publishing Company, Belmont, California, U.S.A., 1984. [28] H. Bunke, P. Foggia, C. Guidobaldi, C. Sansone, M. Vento. A comparison of algo- rithms for maximum common subgraph on randomly connected graphs. T. Caelli, i in., redaktorzy, Structural, Syntactic, and Statistical Pattern Recognition, strony 123–132, Berlin, Heidelberg, 2002. Springer Berlin Heidelberg. [29] J.-Y. Cai, M. Fürer, N. Immerman. An optimal lower bound on the number of variables for graph identification. Combinatorica, 12(4):389–410, 1992. [30] T. Calvo, G. Mayor, R. Mesiar. Aggregation Operators: New Trends and Applica- tions. Physica-Verlag, Heidelberg, 2002. [31] C. Cortes, V. Vapnik. Support-vector networks. Machine Learning, 20(3):273–297, 1995. [32] G. Cosma, M. Joy. Source-code plagiarism: A UK academic perspective, 2006. [33] G. Cosma, M. Joy. An approach to source-code plagiarism detection and investiga- tion using latent semantic analysis. IEEE Transactions on Computers, 61(3):379– 394, 2012. [34] A. Davies. LINQ Jump Start. CreateSpace Independent Publishing Platform, USA, 2016. [35] A. J. Dobson. An introduction to generalized linear models. Chapman Hall/CRC Boca Raton, wydanie 2, 2002. [36] E. Doha, A. Bhrawy, M. Saker. Integrals of Bernstein polynomials: An applica- tion for the solution of high even-order differential equations. Applied Mathematics Letters, 24(4):559–565, 2011. [37] H. Drucker, C. J. C. Burges, L. Kaufman, A. Smola, V. Vapnik. Support vector regression machines. Proceedings of the 9th International Conference on Neural Information Processing Systems, NIPS’96, strony 155–161, Cambridge, MA, USA, 1996. MIT Press. [38] M. B. Eisenberg. Programming in Scheme. Course Technology Press, Boston, MA, United States, wydanie 1, 1993. [39] S. Ennis. The key of lisp. SIGPLAN Lisp Pointers, 1(1):40–41, 1987. [40] W. S. Evans, C. W. Fraser, F. Ma. Clone detection via structural abstraction. 14th Working Conference on Reverse Engineering (WCRE 2007), strony 150–159, 2007. [41] R. Falke, P. Frenzel, R. Koschke. Empirical evaluation of clone detection using syntax suffix trees. Empirical Software Engineering, 13(6):601–643, 2008.

177

i i

i i i “output” — 2018/6/21 — 7:43 — page 178 — #178 i i i

[42] J. Ferrante, K. J. Ottenstein, J. D. Warren. The program dependence graph and its use in optimization. ACM Transactions on Programming Languages and Systems, 9(3):319–349, 1987. [43] M. Gabel, L. Jiang, Z. Su. Scalable detection of semantic clones. Proceedings of the 30th International Conference on Software Engineering, ICSE ’08, strony 321–330, New York, NY, USA, 2008. ACM. [44] M. Gagolewski. Data fusion: Theory, methods, and applications. Instytut Podstaw Informatyki PAN, Warszawa, 2015. [45] M. Gagolewski. Programowanie w języku R. Analiza danych. Obliczenia. Symulacje. Wydawnictwo Naukowe PWN, Warszawa, wydanie 2, 2016. [46] M. Gagolewski, M. Bartoszuk, A. Cena. Genie: A new, fast, and outlier-resistant hierarchical clustering algorithm. Information Sciences, 363:8–23, 2016. [47] M. Gagolewski, M. Bartoszuk, A. Cena. Przetwarzanie i analiza danych w języku Python. Wydawnictwo Naukowe PWN, 2016. [48] M. Gagolewski, A. Cena, M. Bartoszuk. Hierarchical clustering via penalty-based aggregation and the genie approach. V. Torra, i in., redaktorzy, Modeling Decisions for Artificial Intelligence, strony 191–202. Springer International Publishing, 2016. [49] R. Gauci. Smelling out code clones: Clone detection tool evaluation and correspon- ding challenges. CoRR, abs/1503.00711, 2015. [50] M. Grabisch, J.-L. Marichal, R. Mesiar, E. Pap. Aggregation functions. Cambridge University Press, 2009. [51] T. Górecki. Podstawy statystyki z przykładami w R. Wydawnictwo BTC, Legionowo, 2011. [52] T. Górecki. Using derivatives in a longest common subsequence dissimilarity me- asure for time series classification. Pattern Recognition Letters, 45:99–105, 2014. [53] T. Górecki, M. Krzyśko. Regression methods for combining multiple classifiers. Communications in Statistics - Simulation and Computation, 44(3):739–755, 2015. [54] J. Hage, P. Rademaker, N. van Vugt. Plagiarism detection for Java: A tool compari- son. Computer Science Education Research Conference, CSERC ’11, strony 33–46. Open Universiteit, Heerlen, 2011. [55] H. Hajimirsadeghi, G. Mori. Multiple instance real boosting with aggregation func- tions. Proceedings of the 21st International Conference on Pattern Recognition (ICPR2012), strony 2706–2710, 2012. [56] T. Hall, S. Beecham, D. Bowes, D. Gray, S. Counsell. A systematic literature review on fault prediction performance in software engineering. IEEE Transactions on Software Engineering, 38(6):1276–1304, 2012.

178

i i

i i i “output” — 2018/6/21 — 7:43 — page 179 — #179 i i i

[57] N. Hansen. The CMA evolution strategy: A comparing review. J. Lozano, i in., redaktorzy, Towards a new evolutionary computation. Advances in estimation of distribution algorithms, strony 75–102. Springer, 2006. [58] M. J. Harrold, B. Malloy, G. Rothermel. Efficient construction of Program Depen- dence Graphs. Raport instytutowy, ACM International Symposium on Software Testing and Analysis, 1993. [59] T. Hastie, R. Tibshirani, J. Friedman. The elements of statistical learning: Data mining, inference, and prediction. Springer, 2009. [60] T. J. Hastie, D. Pregibon. Statistical Models in S, rozdział Generalized linear models. Wadsworth Brooks/Cole, 1992. [61] H. He, E. A. Garcia. Learning from imbalanced data. IEEE Transactions on Know- ledge and Data Engineering, 21(9):1263–1284, 2009. [62] A. Hejlsberg, S. Wiltamuth, P. Golde. C# Language Specification. Addison-Wesley Longman Publishing Co., Inc., Boston, MA, USA, 2003. [63] S. Horwitz, T. Reps. Efficient comparison of program slices. Acta Informatica, 28(8):713–732, 1991. [64] J. Hryszko, L. Madeyski. Assessment of the Software Defect Prediction Cost Effecti- veness in an Industrial Project. L. Madeyski, i in., redaktorzy, Software Engineering: Challenges and Solutions, wolumen 504 serii Advances in Intelligent Systems and Computing, strony 77–90. Springer, 2017. [65] C.-J. Hsu, K.-S. Huang, C.-B. Yang, Y.-P. Guo. Flexible dynamic time warping for time series classification. Procedia Computer Science, 51:2838–2842, 2015. Interna- tional Conference on Computational Science, ICCS 2015. [66] B. Hummel, E. Juergens, D. Steidl. Index-based model clone detection. Proceedings of the 5th International Workshop on Software Clones, IWSC ’11, strony 21–27, New York, NY, USA, 2011. ACM. [67] L. Jiang, G. Misherghi, Z. Su, S. Glondu. Deckard: Scalable and accurate tree- based detection of code clones. Proceedings of the 29th International Conference on Software Engineering, ICSE ’07, strony 96–105, Washington, DC, USA, 2007. IEEE Computer Society. [68] J. H. Johnson. Identifying redundancy in source code using fingerprints. Proce- edings of the 1993 Conference of the Centre for Advanced Studies on Collaborative Research: Software Engineering – Volume 1, CASCON ’93, strony 171–183. IBM Press, 1993. [69] E. Juergens, F. Deissenboeck, M. Feilkas, B. Hummel, B. Schaetz, S. Wagner, C. Do- mann, J. Streit. Can clone detection support quality assessments of requirements

179

i i

i i i “output” — 2018/6/21 — 7:43 — page 180 — #180 i i i

specifications? 2010 ACM/IEEE 32nd International Conference on Software Engi- neering, wolumen 2, strony 79–88, 2010. [70] Kaggle. The state of data science & machine learning. https://www.kaggle.com/ surveys/2017, 2017. [71] T. Kamiya, S. Kusumoto, K. Inoue. CCFinder: A multilinguistic token-based code clone detection system for large scale source code. IEEE Transactions on Software Engineering, 28(7):654–670, 2002. [72] R. Komondoor, S. Horwitz. Using slicing to identify duplication in source code. Proceedings of the 8th International Symposium on Static Analysis, SAS ’01, strony 40–56, London, UK, UK, 2001. Springer-Verlag. [73] G. Kondrak. N-gram similarity and distance. Proceedings of the 12th International Conference on String Processing and Information Retrieval, SPIRE’05, strony 115– 126, Berlin, Heidelberg, 2005. Springer-Verlag. [74] K. A. Kontogiannis, R. Demori, E. Merlo, M. Galler, M. Bernstein. Reverse Engine- ering. rozdział Pattern Matching for Clone and Concept Detection, strony 77–108. Kluwer Academic Publishers, Norwell, MA, USA, 1996. [75] J. Koronacki, J. Mielniczuk. Statystyka. Wydawnictwa Naukowo-Techniczne, War- szawa, 2006. [76] J. Koronacki, J. Ćwik. Statystyczne systemy uczące się. Wydanie drugie. Akade- micka Oficyna Wydawnicza EXIT, 2015. [77] J. Krinke. Identifying similar code with program dependence graphs. Proceedings of the Eighth Working Conference on Reverse Engineering (WCRE’01), WCRE ’01, strony 301–307, Washington, DC, USA, 2001. IEEE Computer Society. [78] M. Krzyśko, W. Wołyński, T. Górecki, M. Skorzybut. Systemy uczące się. WNT, Warszawa, 2008. [79] T. M. Lavoie, E. Merlo. Levenshtein edit distance-based type III clone detection using metric trees. Technical report, EPM-RT-2011-01, 2011. [80] V. Levenshtein. Binary Codes Capable of Correcting Deletions, Insertions and Re- versals. Soviet Physics Doklady, 10:707, 1966. [81] Z. Li, S. Lu, S. Myagmar, Y. Zhou. CP-Miner: finding copy-paste and related bugs in large-scale software code. IEEE Transactions on Software Engineering, 32(3):176– 192, 2006. [82] J. Lines, A. Bagnall. Time series classification with ensembles of elastic distance measures. Data Mining and Knowledge Discovery, 29(3):565–592, 2015. [83] C. Liu, C. Chen, J. Han, P. S. Yu. GPLAG: Detection of software plagiarism by program dependence graph analysis. Proceedings of the 12th ACM SIGKDD Inter-

180

i i

i i i “output” — 2018/6/21 — 7:43 — page 181 — #181 i i i

national Conference on Knowledge Discovery and Data Mining, KDD ’06, strony 872–881, New York, NY, USA, 2006. ACM. [84] S. K. Lukins, N. A. Kraft, L. H. Etzkorn. Source code retrieval for bug localization using latent Dirichlet allocation. Proceedings of the 2008 15th Working Conference on Reverse Engineering, WCRE ’08, strony 155–164, Washington, DC, USA, 2008. IEEE Computer Society. [85] L. Madeyski. Test-Driven Development: An Empirical Evaluation of Agile Practice. Springer, (Heidelberg, London, New York), 2010. [86] L. Madeyski, P. Karwaczyński. Działania projakościowe w procesie wytwarzania oprogramowania. Prace Naukowe Akademii Ekonomicznej we Wrocławiu, 1044 (No- woczesne Technologie Informacyjne w Zarzadzaniu):389–397, 2004. [87] L. Madeyski, M. Ochodek. Wstęp. L. Madeyski, M. Ochodek, redaktorzy, Inżynieria oprogramowania: badania i praktyka, strony 9–10. Nakom, 2014. [88] U. Manber. Finding similar files in a large file system. USENIX Winter 1994 Technical Conference, strony 1–10, 1994. [89] K. Mandal, K. Basu. Multi criteria decision making method in neutrosophic environ- ment using a new aggregation operator, score and certainty function. F. Smaranda- che, S. Pramanik, redaktorzy, New Trends in Neutrosophic Theory and Applications, strony 141–160. 2016. [90] A. Marcus, J. I. Maletic. Identification of high-level concept clones in source code. Proceedings of the 16th IEEE International Conference on Automated Software En- gineering, ASE ’01, strony 107–114, Washington, DC, USA, 2001. IEEE Computer Society. [91] S. Marlow. Haskell 2010 language report. [92] V. T. Martins, D. Fonte, P. R. Henriques, D. da Cruz. Plagiarism Detection: A Tool Survey and Comparison. M. J. V. Pereira, i in., redaktorzy, 3rd Symposium on Lan- guages, Applications and Technologies, OpenAccess Series in Informatics (OASIcs), strony 143–158, Dagstuhl, Germany, 2014. Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik. [93] A. A. Maruf, H. H. Huang, K. Kawagoe. Time series classification method based on longest common subsequence and textual approximation. Seventh International Conference on Digital Information Management (ICDIM 2012), strony 130–137, 2012. [94] G. Maskeri, S. Sarkar, K. Heafield. Mining business topics in source code using latent Dirichlet allocation. Proceedings of the 1st India Software Engineering Conference, ISEC ’08, strony 113–120, New York, NY, USA, 2008. ACM. [95] J. Mayrand, C. Leblanc, E. M. Merlo. Experiment on the automatic detection of

181

i i

i i i “output” — 2018/6/21 — 7:43 — page 182 — #182 i i i

function clones in a software system using metrics. 1996 Proceedings of International Conference on Software Maintenance, strony 244–253, 1996. [96] P. McCullagh, J. Nelder. Generalized Linear Models, Second Edition. Chapman and Hall/CRC Monographs on Statistics and Applied Probability Series. Chapman & Hall, 1989. [97] J. J. Mcgregor. Backtrack search algorithm and the maximal common subgraph problem. Software | Practice and Experience 12, strony 23–34, 1982. [98] E. Meijer. The world according to LINQ. Commun. ACM, 54(10):45–51, 2011. [99] R. Mesiar, A. Mesiarová-Zemánková. The ordered modular averages. IEEE Trans- actions on Fuzzy Systems, 19(1):42–50, 2011. [100] M. Newman. Power laws, Pareto distributions and Zipf’s law. Contemporary Phy- sics, 46(5):323–351, 2005. [101] N. J. Nilsson. Principles of Artificial Intelligence. Springer-Verlag, 1982. [102] M. Odersky, L. Spoon, B. Venners. Programming in Scala: A Comprehensive Step- by-Step Guide, 3rd Edition. Artima Incorporation, USA, wydanie 3, 2016. [103] Oracle. Java Standard Edition 8 API Specification, 2016. [104] J. F. Patenaude, E. Merlo, M. Dagenais, B. Lague. Extending software quality assessment techniques to java systems. Proceedings Seventh International Workshop on Program Comprehension, strony 49–56, 1999. [105] T. Pham, P. Sheridan, H. Shimodaira. Pafit: A statistical method for measuring preferential attachment in temporal complex networks. PLOS ONE, 10(9):1–18, 2015. [106] M. P. Ponti. Combining classifiers: From the creation of ensembles to the deci- sion fusion. 2011 24th SIBGRAPI Conference on Graphics, Patterns, and Images Tutorials, strony 1–10, 2011. [107] L. Prechelt, G. Malpohl, M. Philippsen. JPlag: Finding plagiarisms among a set of programs. Technical report, University of Karlsruhe, Department of Informatics, 2000. [108] W. Qu, Y. Jia, M. Jiang. Pattern mining of cloned codes in software systems. Inf. Sci., 259:544–554, 2014. [109] R Core Team. R: A language and environment for statistical computing. R Foun- dation for Statistical Computing, Vienna, Austria, 2016. R-project.org. [110] D. Rattan, R. Bhatia, M. Singh. Software clone detection: A systematic review. Information and Software Technology, 55(7):1165–1199, 2013. [111] M. Rieger. Effective clone detection without language barriers. Praca doktorska, University of Bern, Switzerland, 2005.

182

i i

i i i “output” — 2018/6/21 — 7:43 — page 183 — #183 i i i

[112] C. K. Roy, J. R. Cordy. Nicad: Accurate detection of near-miss intentional clones using flexible pretty-printing and code normalization. 2008 16th IEEE International Conference on Program Comprehension, strony 172–181, 2008. [113] C. K. Roy, J. R. Cordy, R. Koschke. Comparison and evaluation of code clone detection techniques and tools: A qualitative approach. Sci. Comput. Program., 74(7):470–495, 2009. [114] S. J. Russell, P. Norvig. Artificial Intelligence – A Modern Approach. Pearson Education, 2010. [115] F. V. Rysselberghe, S. Demeyer. Evaluating clone detection techniques from a refactoring perspective. Proceedings. 19th International Conference on Automated Software Engineering, 2004., strony 336–339, 2004. [116] S. Schleimer, D. S. Wilkerson, A. Aiken. Winnowing: Local algorithms for document fingerprinting. Proceedings of the 2003 ACM SIGMOD International Conference on Management of Data, SIGMOD ’03, strony 76–85, New York, NY, USA, 2003. ACM. [117] L. Schumaker. Spline Functions: Basic Theory. Cambridge University Press, 2007. [118] B. Schölkopf, A. Smola, K.-R. Müller, C. Burges, V. Vapnik. Support vector me- thods in learning and feature extraction, 1998. [119] P. Sheridan, T. Onodera. A preferential attachment paradox: How does preferential attachment combine with growth to produce networks with log-normal in-degree distributions? CoRR, abs/1703.06645, 2017. [120] N. Shervashidze, P. Schweitzer, E. J. van Leeuwen, K. Mehlhorn, K. M. Borgwardt. Weisfeiler-Lehman graph kernels. Journal of Machine Learning Research, 12:2539– 2561, 2011. [121] L. Skovgaard. Applied regression analysis., wiley, new york, 1998. no. of pages: xvii+706. isbn 0471170828. Statistics in Medicine, 19(22):3136–3139. [122] T. Smith, M. Waterman. Identification of common molecular subsequences. Journal of Molecular Biology, 147(1):195–197, 1981. [123] B. Stroustrup. The C++ Programming Language. Addison-Wesley Professional, wydanie 4, 2013. [124] Y. Ueda, T. Kamiya, S. Kusumoto, K. Inoue. On detection of gapped code clones using gap locations. Ninth Asia-Pacific Software Engineering Conference, 2002., strony 327–336, 2002. [125] W. N. Venables, B. D. Ripley. Modern Applied Statistics with S. Springer, 2010. [126] G. von Rossum. Python reference manual. Raport instytutowy, Amsterdam, The Netherlands, The Netherlands, 1995. [127] V. Wahler, D. Seipel, J. Wolff, G. Fischer. Clone detection in source code by frequent

183

i i

i i i “output” — 2018/6/21 — 7:43 — page 184 — #184 i i i

itemset techniques. Fourth IEEE International Workshop on Source Code Analysis and Manipulation, strony 128–135, 2004. [128] B. Weisfeiler, A. A. Lehman. A reduction of a graph to a canonical form and an algebra arising during this reduction. Nauchno-Technicheskaya Informatsia, 2(9), 1968. [129] M. J. Wise. Detection of similarities in student programs: Yap’ing may be preferable to plague’ing. SIGCSE Bull., 24(1):268–271, 1992. [130] M. J. Wise. String similarity via greedy string tiling and running Karp-Rabin matching. Raport instytutowy, Dept. of CS, University of Sydney, 1993. [131] L. Xu, A. Krzyzak, C. Y. Suen. Methods of combining multiple classifiers and their applications to handwriting recognition. IEEE Transactions on Systems, Man, and Cybernetics, 22(3):418–435, 1992. [132] Y. Xu, H. Wu. Decision fusion for block linear regression classification based on confidence index. The First Asian Conference on Pattern Recognition, strony 199– 203, 2011. [133] B. Yablonsky. C++11 Standard Library: Usage and Implementation. CreateSpace Independent Publishing Platform, USA, 2013. [134] S. Yousefi, M. Behroozifar. Operational matrices of Bernstein polynomials and their applications. International Journal of Systems Science, 41(6):709–716, 2010.

184

i i

i i