Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
SVEUČILIŠTE U ZAGREBU
FAKULTET ELEKTROTEHNIKE I RAČUNARSTVA
DIPLOMSKI RAD br. 1022
PROGRAMSKI MODUL ZA
PREPORUČIVANJE SADRŽAJA NA
WEB-PORTALIMA
Martin Kurtoić
Zagreb, srpanj 2016
Sadržaj
1 Uvod ........................................................................................................................................... 1
2 Osnovne tehnike preporučivanja sadržaja ................................................................................. 2
2.1 Preporuke prema sličnosti sadržaja (engl. Content-based) ............................................... 2
2.2 Preporuke prema suradnji korisnika (engl. Collaborative Filtering) .................................. 4
3 Java knjižnice korištene prilikom razvoja programskog modula ................................................ 7
3.1 Programska knjižnica Apache Lucene ................................................................................. 7
3.2 Programska knjižnica Apache Mahout ............................................................................. 11
4 Implementirane tehnike određivanja sličnosti ........................................................................ 13
4.1 Određivanje sličnosti korisnika (engl. User-based similarity) .......................................... 13
4.2 Određivanje sličnosti članaka prema sadržaju pomoću knjižnice Apache Lucene (engl.
Content-based) ............................................................................................................................. 15
5 Arhitektura i implementacija programskog modula ............................................................... 18
5.1 Opis klasa i metoda razvijenog programskog modula ..................................................... 19
5.1.1 Klase NewsItem i Text ........................................................................................... 19
5.1.2 Klasa Indexer ......................................................................................................... 20
5.1.3 Klasa MatrixGeneratorWithTopDocs................................................................ 24
5.1.4 Klasa ItemSimilarityFromTopDocsMatrix ..................................................... 25
5.1.5 Klasa ItemRecommenderFromTopDocsMatrix .................................................. 27
5.1.6 Klasa ItemRecommenderFromDataModelTopDocs ............................................ 28
5.1.7 Klasa DataModelGenerator ................................................................................. 29
6 Slučajevi uporabe (engl. use cases) i metode sučelja .............................................................. 31
6.1 Inicijalizacija sustava i dodavanje novog članka u sustav ................................................. 31
6.2 Brisanje postojećeg članka iz sustava............................................................................... 32
6.3 Pridjeljivanje ocjene članku .............................................................................................. 33
6.4 Generiranje preporuka ..................................................................................................... 33
7 Zaključak ................................................................................................................................... 35
8 Literatura .................................................................................................................................. 36
9 Sažetak ..................................................................................................................................... 37
10 Dodatak: programski kod ..................................................................................................... 38
1
1 Uvod
Preporučiteljski sustav (engl. recommendation systems) vlasniku određenog
sadržaja omogućuje stvaranje dodatne vrijednosti tog sadržaja. Način na koji
preporučiteljski sustav stvara dodatnu vrijednost sadržaja jest da različitim
metodama usporedbi kreira različite korelacije sličnosti između sadržaja, ali i
korisnika koji na očigled nisu povezani. Takav pristup omogućuje vlasniku
sadržaja da korisniku za kojeg je zabilježeno da je zainteresiran za određen tip
sadržaja, prilikom posjete portalu preporučuje sadržaj sličan onome za koji je
zabilježen korisnikov interes, ali i interes njemu sličnih korisnika. Svaki
preporučiteljski sustav djeluje na temelju pretpostavke da postoji korisnik
pojedinac, izdvojen iz cjelokupne baze korisnika sustava, i podskup sadržaja,
izdvojen iz cjelokupnog skupa sadržaja sustava, koji može biti preporučen tom
korisniku.
Unutar svakog preporučiteljskog sustava postoji skup funkcija čije se
međudjelovanje svodi na računanje faktora korisnosti preporučivanja određenog
sadržaja određenom korisniku portala. Takve funkcije najčešće predstavljaju
Kartezijev produkt skupa korisnika sa skupom sadržaja gdje je svaki par (korisnik,
sadržaj) prikazan kao pozitivan cijeli ili realni broj (R0) u unaprijed određenom
rasponu.
(korisnik, sadržaj) R0
Danas gotovo svaki vlasnik sadržaja koji je ponuđen korisnicima preko Interneta,
teži što boljoj realizaciji preporučivanja sadržaja kao dodatne usluge web-portala.
Preporučivanje sadržaja kao dodatna usluga prvenstveno je namijenjeno web-
portalima s vijestima, portalima za prodaju, distribuciju videa i slično.
Najvažnije funkcije preporučiteljskog sustava (na primjeru portala s vijestima) :
otkriti korisniku vijesti koje ga zanimaju, a koje sam ne bi lako pronašao,
navoditi korisnika da otvori i pročita što više vijesti te se tako što duže
zadrži na portalu,
2
donositi vlasniku što više dobiti koja je posljedica otvaranja što više članaka
uz koje se nalaze razne reklame koji se mogu naplatiti po otvaranju članka
od strane korisnika.
Osnovne karakteristike preporučiteljskog sustava:
personaliziranost – preporuke moraju biti što je bolje moguće prilagođene
korisnikovom profilu,
raznolikost – korisniku moraju biti preporučene sve sfere njegovih interesa.
U preporučiteljskim sustavima postoje dva osnovna pristupa stvaranju preporuka,
a to su preporuke prema sličnosti sadržaja (engl. Content-based recommendation)
i preporuke prema suradnji korisnika (engl. Collaborative Filtering
recommendation). Kod preporuka prema sličnosti sadržaja korisniku se nude
članci slični onima koje korisnik upravo ima otvorene i čita ih jer se za kreiranje
preporuka, kao što sam naziv govori, isključivo koristi sličnost sadržaja. Kod
preporuka prema suradnji, koristi se pak potpuno drugačiji pristup te se korisničke
preporuke kreiraju tako da se u datoteci u kojoj su zabilježene korisničke akcije
pronalaze slični korisnici te se trenutnom korisniku nude oni članci koje su ranije
čitali njemu slični korisnici.
Vrlo je često slučaj da se u preporučiteljskom sustavu kombiniraju dvije navedene
vrste kreiranja preporuka, a takvi sustavi nose naziv hibridni preporučiteljski
sustavi.
2 Osnovne tehnike preporučivanja sadržaja
2.1 Preporuke prema sličnosti sadržaja (engl. Content-based)
Najosnovniji pristup pri stvaranju preporuka u preporučiteljskim sustavima su
preporuke prema sličnosti sadržaja (engl. Content-based). Kao što je u uvodu već
spomenuto, navedena metoda kreira preporuke tako da se korisniku nude članci
koji su prema sadržaju ili određenim opisnim podacima slični onima koje sam
korisnik trenutno čita, odnosno ima otvorene. Opisni podaci svakog članka mogu,
3
ali i ne moraju biti niz unaprijed definiranih atributa kojima se prilikom unosa
članka na portal definiraju vrijednosti. Skup vrijednosti koje mogu biti pridružene
pojedinom atributu također se može unaprijed definirati, iako ni to nije nužno. Na
primjer, svaki članak uz sebe može imati podatke o državi ili gradu za koji je
vezan, kojoj kategoriji (npr. vijest, sport, crna kronika) pripada i slično.
Prednosti preporuka prema sličnosti sadržaja:
nema potrebe za informacijama o drugim korisnicima,
dobre preporuke korisnicima jedinstvenog profila,
dobro preporučuje nove i slabije popularne članke,
korisniku je lako objasniti zašto je članak preporučen izlistavanjem atributa
koji su ključni za stvaranje preporuke.
Nedostaci:
dobro samo za usluge koje mogu biti prikazane zadanim skupom atributa,
tehnike pretraživanja usluga ne mogu uzeti u obzir multimedijske
informacije te ostale aspekte koji utječu na ocjenjivanje poput vremena
preuzimanja sadržaja i slično,
teško je iskoristiti ocjenu kvalitete koju su dali drugi korisnici.
Može se primijetiti da nedostaci kod preporuka na temelju sličnosti sadržaja ne bi
značajno trebali dolaziti do izražaja u slučaju web-portala s različitim člancima te bi
bilo legitimno koristiti i samo takav način preporučivanja.
Kod preporuka na temelju sličnosti sadržaja funkcija korisnosti u(s1,s2) definirana
je kao:
u(s1,s2) = podudarnost(sadržaj(s1), sadržaj(s2))
gdje su i sadržaj s1 prvog dokumenta i sadržaj s2 drugog dokumenta prikazani kao
TF-IDF (engl. Term Frequency-Inverse Document Frequency) vektori težine
ključnih riječi. TF-IDF jest statistički pristup čija je funkcija prikazati koliku važnost
ima određena riječ u svakom pojedinom dokumentu iz skupa dokumenata.
4
TF (engl. Term Frequency) je broj pojavljivanja određenog pojma u dokumentu koji
se u daljnji račun može uzeti kao original ili normaliziran na jedan od više načina
koji postoje za normalizaciju broja TF. Primjer normalizacije jest binarni prikaz gdje
nula označava da pojam uopće ne postoji u dokumentu, a jedan da se nalazi
jedan ili više puta.
𝑤𝑡,𝑑 = log(1 + 𝑡𝑓𝑡,𝑑) ∗ 𝑖𝑑𝑓𝑡
Gdje je tft,d broj pojavljivanja pojma u promatranom dokumentu, a idft je izračun
frekvencije dokumenata u kolekciji u kojima se pojavljuje promatrani pojam.
U slučaju preporuka prema sličnosti sadržaja ti su dokumenti najčešće indeksirani
tekstualni zapisi ključnih riječi ili svih riječi koje se nalaze u sadržaju svih članaka.
Funkcija korisnosti se najčešće implementira pomoću neke od heurističkih metoda
poput, na primjer, kosinusne sličnosti te se u tom slučaju računa na sljedeći način:
𝑢(𝑐, 𝑠) = cos(𝑤𝑐 , 𝑤𝑠) =𝑤𝑐 × 𝑤𝑠
(||𝑤𝑐|| || 𝑤𝑠||)= (∑ 𝑤𝑖𝑐𝑤𝑖𝑠) ÷ (√∑ 𝑤𝑖𝑐
2
𝐾
𝑖=1
𝐾
𝑖=1
∗ √∑ 𝑤𝑖𝑠2
𝐾
𝑖=1
)
Gdje su wc i ws vektori dva članka koje želimo usporediti.
2.2 Preporuke prema suradnji korisnika (engl. Collaborative Filtering)
Preporuke prema suradnji korisnika kreiraju se tako da se skupljaju podaci o
ocjenama koje su ostali, trenutno aktivnom korisniku što sličniji korisnici, pridijelili
od strane trenutno aktivnog korisnika nepročitanim člancima.
Preporučiteljski sustavi koji preporuke kreiraju prema suradnji korisnika za
kreiranje preporuka koriste dio ili cijelu bazu povezanosti korisnika i članaka.
Prema bazi povezanosti korisnika i članaka se za svaka dva korisnika može
odrediti točan koeficijent sličnosti interesa te se prema tom koeficijentu kasnije
kreiraju i same preporuke.
Računanje sličnosti između članaka ili korisnika u preporučiteljskim sustavim s
preporuka prema suradnji korisnika je ključan i kritičan korak.
5
Postoje dva osnovna pristupa kod kreiranja preporuka prema suradnji korisnika, a
to su preporuke zasnovane na sličnosti članaka (engl. item-based
recommendations) i preporuke zasnovane na sličnosti korisnika (engl. user-based
recommendations).
Osnovna ideja kod kreiranja preporuka zasnovanih na sličnosti članaka jest prvo u
bazi korisničke suradnje pronaći korisnike koji imaju ocijenjena oba članka koja se
uspoređuju i nakon toga odrediti funkciju koja će se koristiti za izračun koeficijenta
sličnosti, wi,j, dvaju uspoređivanih članaka koje su korisnici čitali i ocijenili. Nakon
što se odredi sličnost članaka, u sljedećem se koraku kreiranja preporuka
zasnovanih na sličnosti članaka može upotrijebiti proračun prosjeka težina (engl.
simple weighted average) kako bi se odredila korisnost preporučivanja određenog
članka (k) određenom korisniku (u):
𝑃𝑢,𝑖 =∑ 𝑟𝑢,𝑛𝑤𝑘,𝑛𝑛∈𝑁
∑ |𝑤𝑘,𝑛|𝑛∈𝑁
gdje se sumira po svim ostalim ocijenjenim člancima n ∈ N korisnika u, a wk,n je
sličnost između članaka k i n. ru,n je ocjena članka n koju je pridijelio korisnik u.
Kod kreiranja preporuka zasnovanih na sličnosti korisnika potrebno je izračunati
sličnost između dva korisnika, wu,v, koji su oboje ocijenili iste članke.
Kako je danas uglavnom potrebno kreirati više od jedne preporuke pomoću
preporučiteljskog sustava, u nastavku će biti razmotreni načini kreiranja preporuka
top-N zasnovanih na sličnosti korisnika, ali i preporuka top-N zasnovanih na
sličnosti članaka koji rješava problem skalabilnosti koji se pojavljuje kod kreiranja
preporuka top-N zasnovanih na sličnosti korisnika.
Prilikom kreiranja preporuka top-N zasnovanih na sličnosti korisnika prvo je
potrebno pronaći k najsličnijih korisnika (engl. nearest neighbours) trenutno
aktivnom korisniku koristeći Pearsonovu korelaciju (engl. Pearson correlation) ili
model vektor-prostor (engl. vector-space model) u kojem je svaki korisnik prikazan
kao vektor u m-dimenzionalnom prostoru članaka i sličnost između trenutno
aktivnog i ostalih korisnika se računa kao sličnost vektora. Nakon što se odredi k
najsličnijih korisnika, njihovi m-dimenzionalni vektori članaka se objedinjuju u
matricu kako bi se odredio skup članaka, C, korištenih od strane cijele grupe, svaki
6
s pojedinačnom učestalošću. Kada je poznat skup članaka, C, tada preporučitelj
zasnovan na sličnosti korisnika može preporučiti top-N najučestaliji članaka koji se
nalaze u matrici, a da ih aktivni korisnik još nije koristio. Kreiranje preporuka
zasnovanih na sličnosti korisnika ima ograničenja koja se odnose na skalabilnost i
performanse u pružanju usluga u stvarnom vremenu.
Kako bi se riješio problem skalabilnosti koji se javlja kod preporuka zasnovanih na
temelju sličnosti korisnika, razvijen je pristup kreiranja preporuka na temelju
sličnosti članaka. Kod kreiranja preporuka na temelju sličnosti članaka prvo je
potrebno pronaći k najsličnijih članaka za svaki članak prema unaprijed određenim
kriterijima sličnosti. Nakon toga je potrebno kreirati skup, C, članaka za
potencijalno preporučivanje određujući uniju k najsličnijih članaka i uklanjanjem
svakog članka iz skupa, U, koje je aktivni korisnik već čitao. Tada je potrebno
izračunati koeficijent sličnosti između svakog članka iz skupa C sa svakim
člankom iz skupa U. Konačni skup članaka u C, sortiran po sličnosti jest preporuka
top-N temeljna na sličnosti članaka. Slikoviti prikaz korištenja ovakvog pristupa u
kreiranju preporuka može biti preporučivanje prilikom kupovine kino karata. Svaki
novi film se u tom slučaju uspoređuje sa svim filmovima za koje je korisnik do sada
kupovao karte te mu se na temelju parametara usporedbe dodijeli određeni
koeficijent sličnosti, nakon čega se kreira lista novih filmova tako da se oni s
najpovoljnijim koeficijentom sličnosti nalaze najviše na listi, odnosno lista se sortira
padajuće.
7
3 Java knjižnice korištene prilikom razvoja programskog
modula
U programskom modulu kao podloga koriste se programske knjižnice (engl.
library) Apache Lucene i Apace Mahout implementirane u programskom jeziku
Java. Razlog zbog kojeg se koriste baš navedene knjižnice jest da je
povezivanjem tih dviju knjižnica u jedan programski modul moguće vrlo efikasno
kreirati preporuke na web-portalima. Funkcije pojedine knjižnice implementirane u
razvijenom programskom modulu bit će detaljnije razmotrene u nastavku.
3.1 Programska knjižnica Apache Lucene
Apache Lucene je knjižnica za dohvaćanje informacija (engl. information retrieval),
a glavne su joj karakteristike visoke performanse i skalabilnost. Pri razvoju
programskog modula za preporučivanje sadržaja na web-portalima knjižnica
Apache Lucene se koristi za indeksiranje (engl. indexing) članaka s web-portala.
Indeksiranje je proces koji je vrlo preporučljivo provesti prije bilo kakvog daljnjeg
manipuliranja člancima jer se tako omogućuje brže manipuliranje člancima. Indeks
(engl. index) omogućuje brži pristup informacijama iz članaka od slijednog čitanja.
Na taj je način moguće znatno brže postaviti preduvjete za uspoređivanje sličnosti
članaka. Informacije o sličnosti članaka su od velike važnosti kasnije u procesu
preporučivanja kako bi prilikom kreiranja krajnjih preporuka bili u mogućnosti
uključiti faktor koji se odnosi isključivo na međusobnu sličnost članaka.
Apache Lucene može indeksirati i omogućiti pretraživanje bilo kakvih podataka
koje je moguće izvesti iz teksta članaka koji su indeksirani Apache Lucenom.
Apache Lucene također može kreirati indeks nad kolekcijom članaka neovisno o
samim člancima ili njihovom formatu dok god se članci mogu prikazati kao tekst. S
obzirom na tu mogućnost, moguće je indeksirati dokumente koji su pohranjeni
lokalno, jednostavne tekstualne datoteke, Microsoft Word, XML, HTML ili PDF
dokumente i slične.
8
U nastavku će Slika 1 više puta biti korištena kao referenca prilikom opisivanja
funkcije pojedinog modula knjižnice Apache Lucene kako bi kasnije prilikom
opisivanja razvijenog programskog modula bila jasna uloga knjižnice Apache
Lucene u ostvarivanju funkcionalnosti programskog modula.
Neobrađeni
sadržaj
Učitavanje
dokumenta
Indeks
Kreiranje
dokumenta
Analiziranje
dokumenta
Indeksiranje
dokumenta
Izvršavanje
upita
Kreiranje
upita
Povrat
rezultata
Korisničko sučelje
za pretraživanje
Korisnik
Slika 1 Arhitektura knjižnice Apache Lucene
9
Prvi korak prilikom indeksiranja sadržaja klasama knjižnice Apache Lucene na
Slika 1 se nalazi pri dnu slike, odmah iznad „Neobrađenog sadržaja“. Radi se o
učitavanju neobrađenog sadržaja u sustav. Kako bi se omogućilo što efikasnije
učitavanje neobrađenog sadržaja u sustav po mogućnosti je potrebno organizirati
dokumente u neki od strukturiranih formata, poput XML-a. Format XML omogućuje
brže i efikasnije parsiranje dokumenata te samim time od samog početka
povećava efikasnost cjelokupnog sustava. Što je više podataka potrebno učitati u
sustav to efikasnost ove komponente više dolazi do izražaja. Još jedna važna
činjenica jest da ova komponenta, kao važan dio cjelokupnog sustava, mora biti
građena tako da se pomoću nje može efikasno učitati novi sadržaj za vrijeme rada
sustava.
Kada prethodno opisana komponenta sustava izvrši učitavanje neobrađenog
sadržaja u sustav, potrebno je parsirati neobrađeni sadržaj. Učitavanjem
neobrađenog sadržaja u sustav se često učita i velika količina metapodataka od
kojih prilikom preporučivanja nema koristi. Kako bismo izbjegli „smeće“ unutar
sustava koje čak i šteti prilikom kreiranja preporuka potrebno je obratiti pažnju na
dizajn parsera neobrađenog sadržaja. Prilikom dizajniranja parsera neobrađenog
sadržaja potrebno je prepoznati podatke koji će biti korisni prilikom kreiranja
preporuka te je takve podatke potrebno pohraniti u dokumente od kojih se gradi
indeks. Metapodatke za koje se zaključi da nisu od koristi nije potrebno
pohranjivati u sustav kako bi se sačuvala efikasnost sustava. Dokumenti koji se
pohranjuju u sustav kako bi bili korišteni unutar metoda (engl. method) knjižnice
Apache Lucene sastoje se od polja i vrijednosti pridruženih tim poljima. Ako se
sadržaj u sustav učitava u formatu XML, tada imena polja mogu biti oznake XML,
a vrijednosti polja vrijednosti koje se nalaze unutar oznaka. U postupku parsiranja i
kreiranja dokumenata je važno da se, koliko god se može, ima na umu da neka
riječ u polju s puno riječi, odnosno velikim sadržajem, ima puno manju važnost od
riječi unutar polja s malo sadržaja. Tako zbog velike količine sadržaja u pojedinim
poljima neke riječi neopravdano mogu izgubiti na važnosti.
Nakon što su dokumenti kreirani, isti nisu odmah i indeksirani. Kao što se na Slika
1 može primijetiti, indeksiranje dokumenata je posljednja komponenta u nizu
prilikom korištenja knjižnice Apache Lucene. Sljedeći korak jest analiziranje
dokumenata. Analiziranje dokumenata izvršava se kako bi sve bilo spremno za
10
kreiranje indeksa nad dokumentima. Naime, niti jedan sustav za indeksiranje
dokumenata, pa tako ni knjižnica Apache Lucene, ne indeksira tekst direktno iz
dokumenata već ga prije indeksiranja analizira. Analiziranje teksta jest postupak
gdje se tekst lomi na svoje gradivne jedinice, odnosno na riječi jezika u kojem je
pisan. Korištenje knjižnice Apache Lucene ovdje jako dolazi do izražaja, budući da
se kroz Apache Lucene korisniku nudi niz ugrađenih analizatora teksta, gdje je na
korisniku odluka koji analizator želi koristiti ili mogućnost implementacije vlastitog
analizatora u svoj sustav. Nakon što ih se provede kroz analizator, dokumenti su
spremni za kreiranje indeksa.
Na posljetku je od učitanih podataka i kreiranih dokumenta potrebno izgraditi
indeks. U ovom koraku unutar cjelokupnog sustava najviše do izražaja dolaze
prednosti korištenja knjižnice Apache Lucene jer omogućuje efikasno i brzo
kreiranje indeksa nad analiziranim dokumentima za sve sustave kojima je indeks
potreban, a indeksiranje nije primarna funkcionalnost. Unutar programskog
modula za preporučivanje sadržaja na web-portalima potrebno je imati kreirani
indeks nad svim člancima koji se nalaze na web-portalu. Kreirani indeks
omogućuje pretraživanje svih članaka prema različitim kriterijima te kreiranje svih
preduvjeta koji su potrebni kako bi se ostvario kvalitetan programski modul za
preporučivanje sadržaja.
Programski modul za preporučivanje sadržaja na web-portalima nije klasična
aplikacija za pretraživanje sadržaja, gdje korisnik upiše pojam i očekuje da mu
aplikacija vrati određenu količinu sadržaja koji se nalazi unutar određenog sustava
i koji je na neki način povezan s pojmom koji je korisnik unio u aplikaciju. Nakon
što je unutar sustava kreiran indeks svih dokumenata, programski modul se
ponaša kao „Korisnik“ na Slika 1 te koristi metode knjižnice Apache Lucene
pomoću kojih gradi upite (engl. query) nad kreiranim indeksom, a rezultate upita
koristi za daljnje postupke unutar sustava koji će kasnije biti detaljnije objašnjeni.
Tako je u slučaju programskog modula za preporučivanje sadržaja na web-
portalima „Korisnik“ knjižnice Apache Lucene sam modul unutar kojega je
implementirana i funkcionalnost koju pruža knjižnica Apache Lucene, odnosno
Lucene je samo jedan korak u modulu prilikom kreiranja preporuka koje se
poslužuju vanjskom sustavu, odnosno krajnjem korisniku.
11
3.2 Programska knjižnica Apache Mahout
Apache Mahout je knjižnica otvorenog koda za razvoj sustava strojnog učenja.
Algoritmi koji su implementirani unutar biblioteke spadaju u široko područje
strojnog učenja i inteligencije prikupljanja podataka. Kako unutar navedenih
područja postoji velika širina upotrebe u rješavanju različitih problema važno je
napomenuti da se algoritmi implementirani u knjižnici Apache Mahout
upotrebljavaju ponajprije za razvoj preporučiteljskih sustava, grupiranja
(klasteriranja) te klasificiranja podataka. Apache Mahout također ima i podršku za
skalabilnost i ako je potrebno koristiti algoritme strojnog učenja na velikoj količini
podataka, puno većoj od količine podataka koju može obraditi jedno računalo,
Apache Mahout bi mogao biti dobar izbor. Skalabilno strojno učenje Apache
Mahoutom koristi projekt Apache Hadoop za računanje ravnomjerne distribucije
posla poslužiteljima. Korištenje Apache Hadoopa uključuje i korištenje radnog
okvira MapReduce za računanje distribuiranog izvršavanja zadataka na
poslužiteljima, koji je postao popularan kada ga je počeo koristiti Google, uz
Amazon najveći svjetski razvijatelj algoritama strojnog učenja. MapReduce je
programska paradigma koja rješava probleme kod kojih je ulaz u sustav par ključ-
vrijednost. Funkcija Map pretvara parove ključ-vrijednost u druge, posredničke,
parove ključ-vrijednost, dok funkcija Reduce spaja sve vrijednosti za svaki
posrednički ključ kako bi proizvela izlaz. Kako je paradigma MapReduce vrlo
složena i u ovom kontekstu se spominje samo kao prednost knjižnice Apache
Mahout u slučaju da količina podataka prekorači količinu obradivu na jednom
računalu u prihvatljivom vremenu, ovdje je naveden samo osnovni princip
funkcioniranja paradigme i neće se ulaziti u detalje.
12
Slika 2 Arhitektura sustava koji koristi knjižnicu Apache Mahout
Kako bi se dobile što je moguće bolje preporuke, preporučiteljske sustave je
potrebno promatrati kao komponente kreiranja modela preporučivanja, a upravo to
omogućuje Mahoutova komponenta za sličnost sadržaja koja može kreirati
preporuke na načelu „drugi korisnici su također čitali članak poput ovog“ uz pomoć
kojih je u suradnji sa skalabilnim alatom za pretraživanje poput Apache Lucena
moguće kreirati personalizirane preporuke za svakog pojedinog korisnika.
Kod implementiranja Mahouta unutar nekog sustava potrebno je prikupljati
korisničke akcije u bazu podataka u formatu poznatom Mahoutu. Najjednostavniji
način bilježenja korisničkih akcija u formatu poznatom Mahoutu jest bilježenje
korisničkih akcija u datoteku CSV u formatu ''id-korisnika, id-članka, mjera-
zadovoljstva-korisnika-člankom''. U bazi podataka potrebno je čuvati posljednjih n
korisničkih akcija koje čine jedan dio upita prilikom kreiranja nove preporuke. Kada
programski modul unutar kojeg je implementirana knjižnica Apache Mahout ima
potrebu kreirati novu preporuku za pojedinog korisnika, uzima zadnje zapise iz
baze podataka i šalje upit sustavu za pretraživanje s preuzetom poviješću
korisničkih akcija. Sustav pretraživanja vraća poredanu listu id-članaka i to su
tražene preporuke. Dobivenu listu moguće je dodatno uređivati tako da je neke
stavke moguće ukloniti iz liste i slično.
13
4 Implementirane tehnike određivanja sličnosti
Kako bi programski modul imao što bolju efikasnost te kako bi njime osigurali da u
svakom trenutku, svaki korisnik koji otvori određeni članak na web-portalu dobije
na ponudu određeni broj preporuka, potrebno je unutar modula implementirati više
načina određivanja sličnosti. Kvaliteta preporuke poslužene korisniku jednako ovisi
o načinima na koje se unutar preporučiteljskog sustava određuje sličnost kao i o
podacima dostupnim o korisniku koji je otvorio članak te o samom članku koji je
korisnik otvorio. Unutar razvijenog programskog modula implementirana su dva
osnovna načina određivanja sličnosti koja su opisana u nastavku, koje je unutar
modula moguće koristiti odvojeno te kombinirano kao hibridni način kreiranja
preporuka.
4.1 Određivanje sličnosti korisnika (engl. User-based similarity)
Određivanje sličnosti korisnika jest način određivanja sličnosti koji je unutar
razvijenog programskog modula implementiran uz pomoć metoda knjižnice
Apache Mahout, a ovaj je način razmotren prvi budući da taj način određivanja
sličnosti uopće ne uzima u obzir sličnost sadržaja članaka. Naime, iz podataka
skupljenih unutar datoteke u kojoj se bilježe korisničke akcije, pomoću metode
PearsonCorrelationSimilarity knjižnice Apache Mahout, računa se
Pearsonov korelacijski koeficijent (engl. Pearson correlation coefficient) između
akcija svakih dvaju korisnika te se kreira „susjedstvo“ (engl. neighbourhood) među
korisnicima. „Susjedstvo“ se kreira tako da se svakom paru korisnika pridruži
koeficijent sličnosti iz intervala [-1, 1] gdje -1 označava potpunu različitost dvaju
korisnika, a 1 potpunu podudarnost dvaju korisnika, dok je 0 neutralna, odnosno 0
govori da korisnici imaju zabilježene interaktivnosti, odnosno ocjene, za različite
članke i da nije moguće utvrditi njihovu sličnost. Kao što čitatelj može pretpostaviti,
u stvarnom sustavu je izrazito teško dobiti kako -1, tako i 1 kao koeficijent sličnosti,
a vrlo često se taj koeficijent kreće oko vrijednosti 0. Tako je koeficijent sličnosti
već u iznosu od npr. 0.3 između dva korisnika vrlo korisna informacije jer postoji
14
vrlo velika mogućnost da bi korisnicima međusobno mogli biti interesantni članci
koje je onaj drugi čitao i eventualno dobro ocijenio.
Metoda knjižnice Apache Mahout računa Pearsonov koeficijent sličnosti sljedećom
formulom:
𝑟 =∑ 𝑋𝑖𝑖=𝑗 𝑌𝑗
√∑ 𝑋𝑖2
𝑖 ∗ ∑ 𝑌𝑗2
𝑗
Gdje su X i Y vektori korisničkih interakcija zabilježenih unutar datoteke CSV koja
je dio sustava. U brojniku navedene formule nalazi se suma umnožaka svih ocjena
za članke koji su u danom trenutku izračuna sličnosti ocijenjeni od strane jednog i
drugog korisnika koje promatramo. U nazivniku se pak nalaze sume kvadrata svih
ocjena jednog i drugog korisnika, koje su spojene u produkt, iz kojeg je na
posljetku izračunan kvadratni korijen.
Važno je napomenuti da podatke koje metoda dobije, prvo pretvori tako da im
srednja vrijednost iznosi 0. Drugim riječima, ako su unutar datoteke s korisničkim
interakcijama zabilježene ocjene za pojedine članke u rasponu [1, 5], metoda prvo
pretvori te podatke u raspon [-2.5, 2.5]. Navedeno centriranje podataka je nužno
kako metoda ne bi ovisila o rasponu ocjena koje se bilježe unutar datoteke s
korisničkim interakcijama, već koji god raspon bio, metoda ga uvijek pretvori u
raspon kojem je srednja vrijednost 0. S obzirom na navedenu pretvorbu podataka
vrijednost koju je gornjom formulom moguće dobiti nalazi se upravo u ranije
navedenom intervalu [-1, 1] i predstavlja Pearsonov koeficijent sličnosti korisnika.
15
4.2 Određivanje sličnosti članaka prema sadržaju pomoću knjižnice
Apache Lucene (engl. Content-based)
Kao što je ranije navedeno, osim što pruža mogućnost indeksiranja sadržaja,
knjižnica Apache Lucene također pruža mogućnost pretraživanja indeksiranog
sadržaja prema traženom upitu unesenom iz korisničkog sučelja, kao što se i
može vidjeti iz arhitekture na Slika 1. U slučaju razvijenog programskog modula ne
postoji korisničko sučelje za pretraživanje indeksa, već pretraživanje izvršava sam
programski modul prilikom kreiranja matrice sličnosti sadržaja. Iako će
funkcionalnosti implementirane unutar pojedinih metoda biti detaljno opisane
kasnije u radu, kako je generiranje matrice sličnosti ključ ovog poglavlja, opis
metode unutar koje se generiranje izvršava slijedi.
Generiranje matrice sličnosti izvršava se unutar metode initialGenerator (int
num) klase (engl. class) MatrixGeneratorWithTopDocs. Unutar metode
initalGenerator vrši se prolazak kroz sve članke koji se nalaze u indeksu te se
za svaki članak dohvaća num najsličnijih članka, gdje se za traženi upit prvo uzima
tekst indeksiranog članka. Ako zbog složenosti upita unutar kojeg se nalazi cijeli
tekst indeks ne može pronaći niti jedan članak sličan članku za koji se pretraga
vrši, slijedi novo pretraživanje gdje se kao upit uzima naslov članka. Kako je
naslov članka puno jednostavniji upit od cjelokupnog teksta, s gotovo potpunom
sigurnošću se može tvrditi da će unutar indeksa zadovoljavajuće veličine postajati
barem jedan prema naslovu sličan članak članku za kojeg izvršavamo pretragu.
Na temelju pretrage indeksa dobivamo num najsličnijih dokumenata dokumentu za
kojeg smo izvršili upit. Vrijednosti pozicija iz indeksa na kojima se nalaze
pronađeni najsličniji članci se pohranjuju unutar retka članka za koji se izvršila
pretraga u matricu sličnih indeksa, a koeficijenti sličnosti se pohranjuju unutar istog
retka u matricu koeficijenata sličnosti. Na taj se način generiraju dvije matrice,
matrica koja sadrži pozicije iz indeksa za num najsličnijih članaka svakom članku i
matrica koja sadrži koeficijente sličnosti num najsličnijih članaka za svaki članak u
indeksu.
16
Nakon što je kreiran upit nad indeksom s tekstom ili naslovom određenog članka
za kojeg se u danom trenutku dohvaća num najsličnijih članaka, upit se i izvršava,
a rezultat izvršavanja upita pohranjuje se u instancu klase TopDocs programske
knjižnice Apache Lucene. Klasa TopDocs omogućava efikasno manipuliranje i
dohvaćanje pojedinih članaka iz cijele skupine članaka koji su vraćeni kao rezultat.
Rezultati su unutar instance klase TopDocs poredani padajuće prema
relevantnosti, odnosno reference na najsličnije članke nalaze se na početnim
pozicijama polja TopDocs. Klasa TopDocs ima implementirane tri metode koje
omogućuju dohvaćanje rezultata pretraživanja. Metoda totalHits, kao što i samo
ime metode kaže, vraća ukupan broj članaka koji su pronađeni kao slični traženom
članku. Metoda scoreDocs vraća polje svih članaka koji su pronađeni kao slični
članku za kojeg je izvršena pretraga, a članak je prema metodi scoreDocs sličan
ako mu je sličnost s upitom prema zadanim kriterijima veća od nule. Svaka
instanca polja scoreDocs sadrži dvije vrijednosti, score koja je tipa float i sadrži
koeficijent sličnosti članaka te doc koja je tipa int i sadrži referencu na poziciju
pronađenog sličnog članka u indeksu. Posljednja metoda klase TopDocs je
getMaxScore, kao što i sam čitatelj prema imenu metode može pretpostaviti,
navedena metoda vraća koeficijent sličnosti najsličnijeg članka s upitom prema
String q = reader.document(i).getField("text").stringValue();
...
if (q.length()!=0) {
Query query = new QueryParser("text", new
StopAnalyzer()).parse(QueryParser.escape(q));
...
solution = 1;
} else {
q = reader.document(i).getField("title").stringValue();
Query query = new QueryParser("title", new
StopAnalyzer()).parse(QueryParser.escape(q));
...
solution = 2;
}
if (docs.scoreDocs.length<(num+1) && solution == 1) {
q = reader.document(i).getField("title").stringValue();
Query query = new QueryParser("title", new
StopAnalyzer()).parse(QueryParser.escape(q));
...
}
. . .
Kod 1 Logika kojom se pokušava osigurati minimalno num TopDocs sličnih članaka svakom članku u matrici sličnosti
17
zadanim kriterijima, odnosno koeficijent sličnosti članka koji se nalazi na prvom
mjestu u polju koje vraća metoda scoreDocs.
Formula prema kojoj knjižnica Apache Lucene na temelju upita računa koeficijente
n najsličnijih članaka, a provede se nad svakim člankom (d) koji sadrži barem
jednu riječ iz upita (q), jest:
∑ (𝑡𝑓(𝑡 𝑖𝑛 𝑑) ∗ 𝑖𝑑𝑓(𝑡)2
𝑡 𝑖𝑛 𝑞∗ 𝑏𝑜𝑜𝑠𝑡(𝑡. 𝑓𝑖𝑒𝑙𝑑 𝑖𝑛 𝑑) ∗ 𝑙𝑒𝑛𝑔𝑡ℎ𝑁𝑜𝑟𝑚 (𝑡. 𝑓𝑖𝑒𝑙𝑑 𝑖𝑛 𝑑)
∗ 𝑐𝑜𝑜𝑟𝑑(𝑞, 𝑑) ∗ 𝑞𝑢𝑒𝑟𝑦𝑁𝑜𝑟𝑚 (𝑞)
Gdje je tf (t in d) faktor TF (Term Frequency) za riječ t u članku d, odnosno broj
ponavljanja riječi t u članku d, idf(t) je ranije, u poglavlju 2.1, objašnjen faktor IDF
(Inverse Document Frequency), koeficijent težine riječi u dokumentu, odnosno
mjera koliko je riječ jedinstvena. Ovaj koeficijent je važan jer riječi koji su česte
imaju niži koeficijent težine budući da nisu toliko relevantne ako se nalaze u
dokumentu, od riječi koje su rijetke, odnosno specifične za određenu temu. Boost
(t.field in d) jest mogućnost povećanja težine važnosti određenog polja u
indeksiranom članku. Koeficijent Boost je inicijalno postavljen na jedan, što znači
da ne utječe na ukupan produkt, a inicijalna vrijednost je korištena i u razvijenom
programskom modulu. lengthNorm (t.field in d) jest normalizacijska vrijednost
polja, a odnosi se na količinu teksta indeksiranog u pojedinom polju. Navedena
vrijednost se generira prilikom indeksiranja, a kraća polja, koja sadrže manje riječi,
imaju pridruženu veću normalizacijsku vrijednost upravo kroz ovaj član u produktu.
Coord (q,d) jest koordinacijski faktor koji se temelji na broju riječi iz upita (q) koje
sadrži članak (d). Koordinacijski faktor je veći upravo za članke koji sadrže više
riječi iz upita te im tako dodatno povećava važnost. queryNorm (q) jest
normalizacijska vrijednost za upit, a vrijednost joj ovisi od sumi kvadrata težina
svih riječi koje se nalaze u upitu. Koeficijent QueryNorm u razvijenom
programskom modulu nije relevantan budući da je jednak za sve članke iz
rezultata jer se unutar programskog modula za svaki članak koristi jedinstven upit
na temelju kojeg se dobivaju njemu slični članci, a koeficijent queryNorm dolazi do
izražaja samo kad se koriste složeni upiti.
18
5 Arhitektura i implementacija programskog modula
Slika 3 Arhitektura programskog modula za preporučivanje
Kao što se može vidjeti na Slika 3, razvijeni programski modul pruža sučelje
prema web-portalu uz pomoć kojeg se njegove funkcionalnosti integriraju unutar
portala, a željena akcija se pokreće preko javnih metoda koje su izložene preko
sučelja.
Funkcionalnosti koje su implementirane unutar programskog modula su učitavanje
članka u formatu XML u indeks te generiranje matrice sličnosti članaka iz
prethodno generiranog indeksa, bilježenje korisničkih ocjena za članke, brisanje
članka iz indeksa i matrice sličnosti te brisanje zapisa korisničkih ocjena iz
datoteke CSV korisničkih interakcija. Najvažnija akcija, prema kojoj je programski
modul dobio i ime, jest generiranje preporuke koja će se prikazati korisniku na
poziv metode od strane web-portala, a preporuka se generira na temelju ranije
generirane matrice sličnosti članaka te zapisa zabilježenih unutar datoteke CSV
korisničkih interakcija.
19
5.1 Opis klasa i metoda razvijenog programskog modula
Programski modul za preporučivanje podijeljen je u više klasa prema
funkcionalnostima koje su implementirane unutar modula. Klase koje su
implementirane unutar modula su NewsItem, Text, Indexer,
MatrixGeneratorWithTopDocs, ItemRecommenderFromDataModelTopDocs,
ItemRecommenderFromTopDocsMatrix, ItemSimilarityFromTopDocsMatrix te
DataModelGenerator. Klase NewsItem i Text predstavljaju članke i tekst koji se u
njima nalazi. Unutar klase Indexer implementirane su metode koje se odnose na
spremanje članaka u indeks, metode klase MatrixGeneratorWithTopDocs
koriste se koriste za generiranje matrica sličnosti na temelju podataka iz indeksa.
Zatim slijede klase ItemRecommenderFromDataModelTopDocs i
ItemRecommenderFromTopDocsMatrix koje generiraju preporuke na temelju
podataka iz matrice sličnosti i datoteke CSV korisničkih interakcija, odnosno na
temelju sličnosti definirane u klasi ItemSimilarityFromTopDocsMatrix. Unutar
klase DataModelGenerator implementirane su metode za manipuliranje
datotekom CSV korisničkih interakcija.
5.1.1 Klase NewsItem i Text
Klase NewsItem i Text su vrlo jednostavne klase koje su implementirane jer su
potrebne kako bi se unutar programskog modula mogla koristiti Javina
programska knjižnica Apache Commons Digester kao parser za parsiranje ulaznih
članka u formatu XML. Klasa Text se sastoji od privatne varijable _p tipa String
koja predstavlja odlomak u tekstu koji se parsira budući da je u testnim podacima
zamijećeno da se tekst koji se nalazi unutar oznake text u datoteci XML članka
često sastoji od više odlomaka odvojenih oznakama p. Osim privatne varijable
unutar klase Text još su implementirane i javne metode getP i setP, gdje metoda
getP vraća sav tekst zapisan u privatnu varijablu _p i stoga je tipa String, a
metoda setP na postojeći tekst koji se nalazi zapisan u varijabli _p konkatenira
novi tekst koji joj se preda kao parametar pri pozivu. Metoda setP je tipa void,
odnosno ne vraća nikakav rezultat.
20
Klasa NewsItem se sastoji od dvije privatne varijable, _title tipa String i _txt
tipa Text, te također javnih metoda getTitle, setTitle i getText, setText,
gdje je metoda getTitle tipa String i vraća naslov članka koji se nalazi zapisan
u _title varijabli. Metoda getText je tipa Text, odnosno vraća instancu klase
Text koja je prethodno opisana, a koja je pohranjena u varijabli _txt. Metode Set
u oba slučaja ne vraćaju nikakav rezultat te su tipa void, a ovisno o tome radi li se
o metodi setTitle ili setText kao parametar primaju varijablu tipa String,
odnosno Text te primljenu vrijednost pohranjuju u privatnu varijablu _title
odnosno _txt.
5.1.2 Klasa Indexer
Unutar klase Indexer implementirane su metode koje pokrivaju čak pet procesa
navedenih na Slika 3. Procesi koje pokrivaju metode klase Indexer su učitavanje
članka u formatu XML, parsiranje članka, kreiranje dokumenta Apache Lucene,
indeksiranje dokumenta te djelomično brisanje članka.
Unutar klase Indexer su, osim navedenih metodi, implementirane i dvije
konstante kojima se postavlja konfiguracija indeksa te globalna varijabla tempMap
koja je tipa HashMap i koja se koristi prilikom inicijalnog kreiranja indeksa, kako bi
identifikatore članaka u indeksu povezali s potpunim putanjama do članaka koje se
predaju kao parametar prilikom indeksiranja, ali i prilikom poziva svih ostalih akcija
koje su povezane s člancima, poput dohvaćanja preporuke za određeni članak i
slično.
Javna metoda koja se koristi prilikom učitavanja članka te aktivira sve akcije sa
Slika 3 do generiranja matrice sličnosti je metoda addNewsItem (String
dataPath). Metoda addNewsItem ne vraća nikakav rezultat, a kao parametar
prima ili putanju prema direktoriju unutar kojeg se može nalaziti čitava struktura
private static final StandardAnalyzer analyzer = new
StandardAnalyzer();
private static final IndexWriterConfig config = new
IndexWriterConfig(analyzer);
Kod 2 Konstante konfiguracije indeksa Apache Lucene
21
datoteki za indeksiranje i direktorija u kojima se one nalaze ili prema datoteci XML
koju se želi pohraniti u indeks te dodati u matricu sličnosti. Unutar addNewsItem
metode otvara se kanal IndexWriter za pisanje u indeks, s konfiguracijom koja je
određena ranije navedenim konstantama.
Metodom je razvijena logika koja sprječava kreiranje indeksa od jedne datoteke
budući da takav indeks, odnosno matrica sličnosti koja se kreira iz takvog indeksa
nema smisla. Tako je unutar metode postavljen uvjet da je prilikom prvog poziva,
dok u sustavu još ne postoji kreirana matrica sličnosti, kao parametar potrebno
predati putanju do direktorija koji sadrži skup datoteka XML od kojih je onda
moguće kreirati smislenu matricu sličnosti. Nakon što je u sustavu jednom kreirana
matrica sličnosti metoda se može pozivati samo s datotekama XML prilikom
dodavanja novog članka na web-portal. To je napravljeno kako ne bi došlo do
nekonzistencije web-portala s indeksom članaka, budući da se članci na web-
portal ne dodaju u folderima, već pojedinačno.
Nakon što provjeri konzistentnost, metoda addNewsItem poziva privatnu metodu
index (IndexWriter writer, File dataDir), koja je samo kontrolna metoda
unutar koje se računa i ispisuje vrijeme potrebno za indeksiranje predane strukture
direktorija ili datoteke, izvršava pohranu promjena iz spremnika u indeks te poziva
privatnu rekurzivnu metodu indexDirectory (IndexWriter writer, File
if (dataDir.getName().endsWith(".xml") && matrix != null) {
...
} else if(dataDir.isDirectory() && matrix == null) {
...
} else {
...
System.out.println ("You must set folder as path for fisrt
time. Otherwise XML file is needed.");
}
Kod 4 Uvjeti kojima se postiže konzistentnost
Directory indexDir = getIndexDir();
IndexWriter writer = new IndexWriter (indexDir, config);
Kod 3 Otvaranje kanala za pisanje u indeks Apache Lucene
22
dataDir) koja prolazi kroz cijelu predanu strukturu i kad u strukturi pronađe
datoteku XML poziva privatnu metodu indexFile (IndexWriter writer, File
f). Kada se izvrši metoda indexDirectory, metoda index poziva metodu
addingNewsItem() odnosno initialGenerator(n), kojom ili dodaje novo
indeksirani dokument u postojeću matricu sličnosti ili kreira matricu sličnosti u kojoj
će za svaki članak biti zapisano n najsličnijih članaka, što ovisi o tome postoji li već
matrica sličnosti u sustavu ili ne te je li putanja koja je predana u metodu
addNewsItem putanja do datoteke ili putanja do direktorija, što se može vidjeti u
uvjetima koji su navedeni u Kod 4 Uvjeti kojima se postiže konzistentnost.
U metodi indexFile je implementirana sva logika indeksiranja datoteke, od
parsiranja XML-a, izdvajanja bitnog sadržaja, kreiranja dokumenta Apache
Lucene, upisivanja izdvojenog sadržaja u polja dokumenta Apache Lucene te
pohranjivanja kreiranog dokumenta u indeks.
U Kod 5 je prikazano na koji način se koriste implementirane klase
NewsitemNewsItem i Text prilikom parsiranja datoteka XML, također je prikazano
korištenje dvije različite vrste polja koja se mogu pohraniti u indeks. Za polje tipa
digester.addObjectCreate ("newsitem", Newsitem.class);
digester.addObjectCreate ("newsitem/text", Text.class);
...
Newsitem newsitem = (Newsitem) digester.parse(f);
...
ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS
_AND_OFFSETS);
...
ft2.setIndexOptions(IndexOptions.DOCS);
Field titleField = new Field ("title", newsitem.getTitle(),
ft);
doc.add(titleField);
...
Field nameField = new Field ("name", f.getName(), ft2);
doc.add(nameField);
writer.addDocument(doc);
Kod 5 Osnovna logika indexFile metode
23
ft se indeksira cjelokupni sadržaj, frekvencije pojavljivanja riječi u tekstu, pozicije
riječi i odmaci, a za polje tipa ft2 indeksira se samo sadržaj jer ostali podaci u
slučaju razvijenog modula nisu bitni za sadržaj koji indeksiramo u poljima tog tipa.
Sljedeća metoda implementirana unutar klase Indexer jest javna metoda sučelja
programskog modula koju je potrebno pozvati prilikom brisanja članka s web-
portala. Metoda removeNewsItem (String absolutePath) iz indeksa briše
članak čija joj je apsolutna putanja proslijeđena kao parametar. Nakon što obriše
članak iz indeksa, poziva metodu kojom se članak briše iz matrice sličnosti
članaka te metodu koja briše sve zapise o ocjenjivanju članka iz datoteke CSV s
korisničkim interakcijama, također briše zapis o članku iz HashMape u kojoj su
apsolutne putanje do članka povezane s identifikatorom članka u indeksu. Važno
je napomenuti kako iz indeksa Apache Lucene nije moguće brisati prema
identifikatoru članka u indeksu, već se brisanje izvršava prema određenoj
vrijednosti u polju dokumenta u indeksu, pa je tako potrebno imati jedno polje
jedinstvene vrijednosti po kojem će biti omogućeno brisanje dokumenata iz
indeksa.
U klasi Indexer implementirane su još tri pomoćne metode koje služe za
održavanje konzistencije sustava te dohvaćanje i spremanje datoteka koje su
potrebne za pravilan rad sustava. Metoda getIndexDir() tipa Directory vraća
apsolutnu putanju do direktorija u kojem se nalazi indeks, metoda setIndexMap
(HashMap <String,Integer> outputMap) zapisuje HashMapu u kojoj su
povezane apsolutne putanje do članaka s identifikatorima članaka u indeksu u
datotečni sustav, a metoda getIndexMap() dohvaća navedenu HashMapu iz
datotečnog sustava, na temelju čega i sam čitatelj može zaključiti da navedena
metoda mora biti tipa HashMap <String,Integer>. Tri posljednje metode su
javne jer se koriste za dohvaćanje i postavljanje navedenih datoteka i iz drugih
klasa. Na taj se način čuva konzistencija sustava.
24
5.1.3 Klasa MatrixGeneratorWithTopDocs
Klasa MatrixGeneratorWithTopDocs sadrži metode unutar kojih je
implementirana sva manipulacija matricom sličnosti članaka. Sadrži tri glavne
javne metode, jednu pomoćnu metodu koja se koristi za dohvaćanje TopDocs
najsličnijih članaka svakom članku koji se smješta u matricu i tri pomoćne metode
koje se koriste za pohranjivanje matrica u datotečni sustav i dohvaćanje istih iz
datotečnog sustava.
Javna metoda initialGenerator(int n) služi za inicijalno kreiranje matrice
sličnosti i poziva se prilikom prvog poziva metode addNewsItem kada je kao
parametar pri pozivu metode addNewsItem predana apsolutna putanja do
direktorija koji sadrži inicijalni skup članaka koje želimo indeksirati i smjestiti u
matricu sličnosti. Metoda initialGenerator prima parametar tipa integer kojim
se pri inicijalnom generiranju matrice može odabrati za svaki članak u matrici s
koliko će članaka biti definiran koeficijent sličnosti, odnosno broj stupaca matrice.
Jednom kad je taj broj definiran pri inicijalnom generiranju matrice, svaki novi
članak koji se dodaje u matricu sličnosti će u matrici imati pohranjeno n najsličnijih
članaka.
Javna metoda addingNewsItem() ne prima nikakve parametre jer se u index
metodi klase Indexer poziva neposredno nakon indeksiranja članka te samo
uzima posljednji članak iz indeksa i kreira redak s njemu sličnim člancima u matrici
sličnosti.
for (i=0; i<reader.numDocs(); i++) {
TopDocs docs = getTopDocs (reader, is, i, num);
for (j=1; j<docs.scoreDocs.length; j++) {
System.out.println (docs.scoreDocs[j].doc+"
"+docs.scoreDocs[j].score);
similarityIndexesMatrix[i][j-1]=docs.scoreDocs[j].doc;
similarityValuesMatrix[i][j-1]=docs.scoreDocs[j].score;
}
}
Kod 6 Petlje za generiranje inicijalne matrice sličnosti
25
U javnoj metodi removingNewsItem (docId) implementirano je brisanje zapisa iz
matrice sličnosti za članak koji se u indeksu nalazi kao dokument pod docId
identifikatorom. Ovakav način brisanja članka iz matrice sličnosti je omogućen jer
su članci u retke matrice sličnosti pohranjeni istim redoslijedom kao i dokumenti u
indeksu, te je identifikator dokumenta iz indeksa moguće iskoristiti kao identifikator
retka u kojem se članak nalazi u matrici.
Sljedeća metoda koja je implementirana u navedenoj klasi jest privatna metoda
getTopDocs (IndexReader reader, IndexSearcher is, int i, int num)
koja je tipa TopDocs te vraća minimalno num TopDocs najsličnijih članaka za
članak koji se u indeksu iz kojeg čitamo readerom nalazi pod identifikatorom i.
Detalji logike kojom se kreira TopDocs polje najsličnijih članaka su opisani ranije u
poglavlju 4.2.
Unutar dane klase implementirane su još privatna metoda outputtingMatrix
(int[][] similarityIndexesMatrix,float[][] similarityValuesMatrix)
te javne metode getSimilarityIndexesOutput() i
getSimilarityValuesOutput(), koje se koriste za pohranjivanje u, odnosno
dohvaćanje matrica sličnosti iz datotečnog sustava.
5.1.4 Klasa ItemSimilarityFromTopDocsMatrix
Unutar klase ItemSimilarityFromTopDocsMatrix implementirane su metode
sučelja Apache Mahout ItemSimilarity. Osnovne metode koje su
implementirane su javne metode itemSimilarity(long itemID1, long
itemID2), itemSimilarities(long itemID1, long[] itemID2s) te metoda
allSimilarItemIDs(long itemID). Klasa sadrži još i varijablu matrixIndexes
koja je dvodimenzionalno polje tipa integer te varijablu matrixValue koja je
dvodimenzionalno polje tipa float. Budući da će kasnije pri kreiranju preporuka
IndexReader reader = DirectoryReader.open(fsDir);
IndexSearcher is = new IndexSearcher (reader);
...
TopDocs docs = getTopDocs (reader, is, reader.numDocs()-1,num);
Kod 7 Dohvaćanje TopDocs najsličnijih za posljednji dokument u indeksu
26
biti potrebno koristiti objekt tipa ItemSimilarty, unutar klase
ItemSimilarityFromTopDocs implementiran je i konstruktor
ItemSimilarityFromTopDocsMatrix (int[][] matrixIndexes, float[][]
matrixValues) pri pozivu kojeg je potrebno predati implementaciju matrice
sličnosti, odnosno dvije ranije već više puta opisane matrice, unutar kojih se
nalaze identifikatori iz indeksa za slične članke svakom članku, odnosno
vrijednosti tih sličnosti. U konstruktoru klase predane matrice pohranjuju se u
ranije navedene privatne varijable.
Metoda itemSimilarity (long itemID1, long itemID2) prima identifikatore
iz indeksa za dva članka te prolazi kroz redak članka koji ima referencu itemID1 u
matrici identifikatora (matrixIndexes) i traži postoji li u tom retku identifikator
itemID2. Ako postoji, prolazi kroz redak članka itemID1 u matrici vrijednosti
sličnosti (matrixValues) i traži vrijednost koja se nalazi na poziciji članka
itemID2 te vraća tu vrijednost. Ako pak zapis za itemID2 ne postoji u retku
članka itemID1 unutar matrice identifikatora, metoda vraća nulu.
Metoda itemSimilarities (long itemID1, long[] itemID2s) funkcionira na
sličan način kao i metoda itemSimilarity. Princip je preuzet iz metode
ItemSimilarity, a razlika je u tome što ova metoda u retku članka s
identifikatorom itemID1 unutar matrice identifikatora traži niz referenci iz polja
identifikatora itemID2s te ako identifikator postoji vraća vrijednosti sličnosti s iste
pozicije iz matrice vrijednosti sličnosti, a ako ne postoji vraća nulu.
for (int i=0; i<length; i++) {
contains = false;
for (int j=0; i<matrixIndexesRow.length; i++) {
if (matrixIndexesRow[i]==(((int)itemID2s[j])-1)) {
ind = i;
contains = true;
}
}
if (contains) {
result[i] = matrixValuesLocal[((int)itemID1)-1][ind];
} else {
result[i] = 0;
}
}
return result;
Kod 8 Logika implementirana u itemSimilarites metodi
27
Metoda allSimilarItemIDs (long itemID) jednostavno izdvoji redak članka
itemID iz matrice identifikatora (matrixIndexes) i vrati navedeni redak, unutar
kojeg se nalaze identifikatori svih članaka koji su zabilježeni kao slični članku
itemID, kao rezultat.
5.1.5 Klasa ItemRecommenderFromTopDocsMatrix
Unutar klase ItemRecommenderFromTopDocsMatrix implementirana je metoda
getTopNRecommendationsFromMatrix (String absolutePath, int N) koja
na temelju apsolutne putanje do članka, i broja N koji je broj preporuka koji se želi
dohvatiti, dohvaća preporuke na temelju sadržaja matrice identifikatora. Naime,
na temelju apsolutne putanje do članka, metoda iz ranije kreirane HashMape koja
povezuje identifikatore članaka i njihove apsolute putanje dohvaća identifikator
članka iz indeksa te budući da je taj identifikator jednak retku matrice identifikatora
u kojoj se nalaze identifikatori članaka sličnih traženom članku, dohvaća prvih N
identifikatora iz tog retka. Nakon dohvaćanja pretražuje HashMapu te nalazi
putanje koje su povezane s pronađenim identifikatorima i sprema ih u skup (engl.
set) koji na kraju izvršavanja vraća korisniku. Vraćeni skup tada se može iskoristiti
za posluživanje preporuka korisniku. Važno je napomenuti da je način
implementiran unutar metode getTopNRecommendationsFromMatrix
ekvivalentan načinu koji je teoretski opisan ranije u poglavlju 2.1, odnosno radi se
čisto o preporukama prema sličnosti sadržaja.
for (int j=0; j< numOfRecommends; j++) {
for (String absPath : indexMap.keySet()) {
int recId = inMatrix[itemId][j];
if (indexMap.get(absPath).equals(recId)) {
shouldRecommend.add(absPath);
}
}
}
Kod 9 Osnova logika ItemRecommenderFromTopDocsMatrix
28
5.1.6 Klasa ItemRecommenderFromDataModelTopDocs
Unutar klase ItemRecommenderFromDataModelTopDocs implementirana je
metoda getTopNRecommendations, koja s obzirom na primljeni broj parametara
ima različite implementacije. Navedene mogućnosti poziva metode
getTopNRecommendationsFromDataModelTopDocs su poziv s apsolutnom
putanjom članka i brojem željenih preporuka, jednak kao poziv metode
getTopNRecommendationsFromMatrix u prethodnom poglavlju, poziv s
identifikatorom korisnika (user-id iz datoteke CSV interakcija) i brojem željenih
preporuka te poziv s apsolutnom putanjom članka, identifikatorom korisnika i
brojem željenih preporuka kao kombinacija prethodna dva poziva.
Prilikom poziva metode na prvi navedeni način dohvaćaju se preporuke samo na
temelju predane putanje do članka te se radi o preporukama sličnim onima iz
metode getTopNRecommendationsFromMatrix iz prethodnog poglavlja, iako ne
nužno istima. Naime klasa ItemRecommenderFromDataModelTopDocs instancira
preporučitelje knjižnice Apache Mahout te u stvaranju preporuka na ovaj način nije
bitna samo sličnost članaka, već je uvijek potrebno imati i datoteku CSV
korisničkih interakcija odnosno DataModel. Osim datoteke CSV korisničkih
interakcija potrebno je instancirati i objekt klase koja implementira klasu Apache
Mahout ItemSimilarity, što je u slučaju razvijenog programskog modula objekt
klase ItemSimilarityFromTopDocsMatrix, pri pozivu čijeg je konstruktora
potrebno predati ranije kreirane matrice sličnosti članaka. Na temelju DataModela
i ItemSimilarity objekta instancira se objekt klase
GenericItemBasedRecommender čijom se metodom mostSimilarItems
(itemID, N) koristi logika preporučitelja Apache Mahout i korisniku poslužuje N
najsličnijih članaka članku čija je apsolutna putanja predana. Kao rezultat također
se vraćaju apsolutne putanje do najsličnijih članaka kako bi se zadržala
konzistencija sustava i omogućilo što jednostavnije serviranje preporuka korisniku.
Kada se metoda pozove s parametrima koji su navedeni kao drugi način poziva,
preporuke se kreiraju čisto na temelju sličnosti korisnika iz datoteke CSV
korisničkih interakcija (DataModel). Na temelju modela se instancira objekt klase
UserSimilarity, a sličnost korisnika koja se pohranjuje u instancirani objekt
29
računa se na temelju izračuna Pearsonovog koeficijenta korelacije koji je opisan
ranije u poglavlju 4.1. Pri pozivu metode s ovakvim parametrima instancira se
objekt klase Apache Mahout UserBasedRecommender koji nudi metodu
recommend (userId, N), koja na temelju izračunatih Pearsonovih koeficijenata
sličnosti iz DataModela korisniku vraća potencijalno interesantne članke čisto na
temelju sličnosti njegovih interesa s ostalim korisnicima.
Pri pozivu metode s parametrima koji su navedeni kao treći način poziva,
instanciranje objekata Apache Mahout kao logike koja kreira preporuke odvija se
na jednak način kao u prvom slučaju, ali se nad objektom klase
GenericBasedRecommender također pozove metoda recommend (userId, N)
koja za željenog korisnika na temelju DataModela i instance klase
ItemSimilarityFromTopDocsMatrix kreira preporuke koje bi mu trebale biti
interesantne. U ovom slučaju radi se o preporukama koje kombiniraju sličnost
korisnika na temelju DataModela i sličnost članaka na temelju objekta
ItemSimilarity, odnosno o preporukama na temelju hibridne sličnosti (engl.
Hybrid similarity).
Važno je napomenuti da ako metoda getTopNRecommnedations iz nekog razloga
ne uspije dohvatiti željeni broj preporuka, a pri pozivu je kao parametar predana i
apsolutna putanja do članka, sustav se spušta logičku razinu niže i zove metodu
getTopNRecommendationsFromMatrix s predanom apsolutnom putanjom te
vraća preporuke čisto na temelju sličnosti članaka.
5.1.7 Klasa DataModelGenerator
Funkcionalnosti koje su implementirane unutar klase DataModelGenerator ne
moraju biti nužno dio razvijenog programskog modula, već se mogu odvijati
negdje odvojeno, u tom je slučaju modulu potrebno osigurati pristup prema
datoteci CSV koja je generirana od treće strane. Za testiranje programskog
modula bilo je potrebno razviti i ovakvu komponentu kako bi se mogla generirati
testna datoteka CSV korisničkih interakcija. Budući da su razvijene i metode
pomoću kojih se mogu dodavati retci u datoteku CSV korisničkih interakcija, ali i
30
brisati retci za one članke koji su obrisani iz sustava, ova je komponenta sasvim
legitimna za korištenje kao dio programskog modula.
Unutar klase DataModelGenerator implementirane su tri glavne metode,
initialModel(), rateNewsItem (userId, absolutePath, rating) i
removeRatingsForDeletedNewsItem (absolutePath).
Metoda initialModel() implementirana je kako bi se kreirala testna datoteka
CSV korisničkih interakcija (DataModel) kojom je omogućeno kvalitetno testiranje
modula.
Unutar metode rateNewsItem (userId, absolutePath, rating)
implementirana je logika dodavanja novog retka u datoteku CSV korisničkih
interakcija na temelju predanog korisničkog identifikatora (userId), apsolutne
putanje do članka (absolutePath) i ocjene koju je korisnik pridijelio članku
(rating).
Metodom removeRatingsForDeletedNewsItem (aboslutePath) se na temelju
apsolutne putanje članka pri brisanju članka iz sustava mogu obrisati svi zapisi
korisničkih ocjena pridijeljenih za članak čija je apsolutna putanja predana.
Brisanje funkcionira tako da metoda pronalazi identifikator obrisanog članka iz
HashMape koja povezuje apsolutne putanje sa identifikatorima te briše sve retke u
datoteci CSV koji na mjestu identifikatora članka imaju upravo pronađeni
identifikator.
Unutar klase je implementirana još i privatna metoda emptyFile koje se koristi u
prethodnoj metodi jer brisanje redaka iz datoteke CSV funkcionira na način da se
datoteka CSV isprazni te se ponovno puni samo onim retcima iz stare datoteke u
kojima identifikator članka nije jednak pronađenom identifikatoru članka.
emptyFile (dataModel); ...
for (String fileContentLine : fileContentLines) {
if (fileContentLine.contains(","+newsItemId+",")) {
continue;
}
newContent.append(fileContentLine +
System.lineSeparator());
}
Kod 10 Logika brisanja redaka iz datoteke CSV
31
6 Slučajevi uporabe (engl. use cases) i metode sučelja
Implementacijska logika metoda koje su izložene (engl. expose) web-portalu i koje
će biti spominjane prilikom opisivanja slučajeva uporabe opisana je u prethodnoj
cjelini. U nastavku će biti opisano kako na pravilan način koristiti izložene metode
te dobivati što efikasnije preporuke na web-portalu unutar kojeg je integriran
razvijeni programski modul.
6.1 Inicijalizacija sustava i dodavanje novog članka u sustav
Slika 4 Dijagram za akcije inicijalizacije sustava i dodavanja članka
32
Inicijalizacija sustava i dodavanje novog članka u sustav vrše se metodom
addNewsItem (String absolutePath). Kao što je i ranije objašnjeno, kada u
sustavu još ne postoji generiran indeks, niti matrica sličnosti, metodu
addNewsItem potrebno je pozvati s apsolutnom putanjom prema direktoriju koji
sadrži skup članaka kojima će se sustav inicijalizirati. Jednom kad je sustav
inicijaliziran, metoda addNewsItem prihvaća isključivo apsolutne putanje do
članaka, odnosno datoteka XML članaka, koje indeksira i smješta u novi redak
matrice sličnosti kako bi se mogli pojaviti kao preporuke. Poziv iste metode
prilikom inicijalne konfiguracije sustava i prilikom dodavanja novog članka
olakšava posao administratoru sustava te smanjuje mogućnost inicijalizacije
sustava malim brojem članaka ili čak samo jednim člankom, što se želi izbjeći, jer
ako sustav ima mali broj članaka u matrici sličnosti, kreirane preporuke nemaju
smisla, a često i nije moguće pronaći članak dovoljno sličan da bi se uopće
preporučio.
6.2 Brisanje postojećeg članka iz sustava
Slika 5 Dijagram za akciju brisanja članka iz sustava
33
Prilikom brisanja članka iz sustava potrebno je pozvati metodu removeNewsItem
(String absolutePath). Metoda briše članak koji je zabilježen pod predanom
apsolutnom putanjom iz indeksa, kao i sve zapise koji su povezani s člankom, a
nalaze se u datotekama koje sustav koristi. To su zapisi u matricama sličnosti,
zapisi u datoteci CSV korisničkih interakcija te zapis iz HashMape indeksa.
6.3 Pridjeljivanje ocjene članku
Slika 6 Dijagram akcije korisničkog ocjenjivanja članka
Kada korisnik web-portala koji unutar kojeg je integriran programski modul za
preporučivanje pridoda ocjenu određenom članku, potrebno je pozvati metodu
rateNewsItem (int userId, String absolutePath, int rating), te joj
predati identifikator korisnika koji je pridijelio ocjenu, apsolutnu putanju do članka
koji je ocijenjen i ocjenu koja je pridijeljena za članak. Navedena metoda će iz
predanih parametara generirati novi redak u datoteci CSV korisničkih interakcija u
obliku <userId, newsItemId, rating>.
6.4 Generiranje preporuka
Slika 7 Dijagram akcije generiranja preporuka
34
Posljednji slučaj korištenja razvijenog programskog modula jest onaj prema kojem
je sam modul dobio i ime, a to je generiranje preporuka. Preporuke se, kao što je i
ranije opisano, generiraju na poziv metode getTopNRecommendations gdje
navedena metoda ima tri implementacije s obzirom na broj i vrstu predanih
parametara pri pozivu. Tako je metodu getTopNRecommendations moguće
pozvati s apsolutnom putanjom članka i željenim brojem preporuka (String
absolutePath, int N), gdje metoda vraća preporuke koje ovise o sličnosti
članaka koristeći logiku knjižnice Apache Mahout, s apsolutnom putanjom članka,
identifikatorom korisnika i željenim brojem preporuka (String absolutePath,
int userId, int N). U ovom slučaju metoda vraća preporuke na temelju
kombinacije sličnosti članaka i korisnika te s identifikatorom korisnika i željenim
brojem preporuka (int userId, int N), gdje metoda vraća preporuke
isključivo na temelju sličnosti korisnika. Pri prva dva načina poziva metode
getTopNRecommendations omogućeno je spuštanje sustava na jednu logičku
razinu niže, ako zbog nekog razloga nije moguće kreirati preporuke na temelju
logike knjižnice Apache Mahout. U tom slučaju sustav na temelju apsolutne
putanje članka traži slične članke direktno u matrici sličnosti te vraća preporuke
prema najsličnijim člancima direktno iz matrice sličnosti, ne koristeći logiku
knjižnice Apache Mahout.
Programski modul preporuke vraća isključivo kao skup stringova koji predstavljaju
apsolutne putanje do članaka koji su preporučeni te tako olakšava manipuliranje
generiranim preporukama, odnosno njihovu prezentaciju.
Također je važno primijetiti da sve metode koje su izložene iz programskog
modula prema web-portalu kao referencu za članke koriste apsolutnu putanju
članka te se tako osigurava potpuna konzistencija suradnje između programskog
modula i web-portala.
35
7 Zaključak
Vrlo velika količina podataka dostupna korisnicima na web-portalima i Internetu
općenito često kod korisnika ne izaziva znatiželju čija bi posljedica bilo
pretraživanje svih dostupnih informacija, već upravo suprotno. Kada korisnik
primijeti veliku količinu međusobno nepovezanog sadržaja, često izgubi motivaciju
i odustaje od daljnjeg korištenja portala na kojem je doživio navedeno iskustvo.
Kako zbog prethodno navedenog ne bi došlo do gubitka korisnika, a samim time i
smanjenja zarade od klikova na različite reklame i slično, vrlo je važno u sklopu
web-portala imati integriran sustav koji će korisniku posluživati onaj sadržaj koji će
korisnika zadržati na portalu što više vremena, zbog kojeg će korisnik biti
zadovoljniji i vraćati se na isti web-portal ponovno.
Programski modul za preporučivanje sadržaja na web-portalima je upravo takav
inteligentan sustav, koji na temelju sličnosti sadržaja te na temelju korisničkih
interakcija pruža mogućnost posluživanja prijedloga korisniku upravo onog
sadržaja koji je sličan sadržaju koji korisnik u trenutku čita, ali i sadržaju koji su
čitali drugi korisnici koji imaju slične korisničke preferencije kao trenutni korisnik.
Ovakav način kreiranja prijedloga zanimljivog sadržaja unutar programskog
modula omogućen je integracijom dviju vrlo moćnih programskih knjižnica
otvorenog koda Apache. Pomoću programske knjižnice Apache Lucene
omogućeno je indeksiranje članaka koji se unesu u web-portal te kreiranje matrica
sličnosti isključivo po sadržaju članaka. Pomoću programske knjižnice Apache
Mahout omogućena je logika traženja sličnosti među korisnicima te kreiranje
preporuka za članke na temelju sadržajne sličnosti, koja je izračunata ranije
Lucenom, i na temelju sličnosti korisnika.
Vrlo je važno napomenuti kako sve metode koje su izložene web-portalu koji bi
integrirao programski modul koriste konzistentne parametre, te da administrator
web-portala za pravilan rad s integriranim modulom mora znati samo apsolutnu
putanju svakog članka koji se nalazi u indeksu te identifikator svakog korisnika koji
je registriran na web-portalu. Nakon integracije i inicijalizacije modula s web-
portalom administrator koji posjeduje gore navedene podatke na najjednostavniji
način može dohvaćati prijedloge preporučljivog sadržaja.
36
8 Literatura
[1] Ted Dunning, Ellen Friedman, Practical Machine Learning. O’Reilly Media,
Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472, 2014.
[2] Sean Owen, Robin Anill, Ted Dunning, Ellen Friedman, Mahout in action.
Manning Publications Co., 20 Baldwin Road, PO Box 261, Shelter Island,
NY 11964, 2012.
[3] Michael McCandless, Erik Hatcher, Otis Gospodnetić, Lucene in action –
second edition. Manning Publications Co., 180 Broad St., Suite 1323,
Stamford, CT 06901, 2010.
[4] https://lucene.apache.org/core/5_3_1/core/overview-summary.html,
13.6.2016.
37
9 Sažetak
Programski modul za preporučivanje sadržaja na web-portalima
Na početku ovog diplomskog rada opisane su osnovne tehnike kreiranja
preporuka koje su implementirane u razvijenom programskom modulu, a to su
tehnike preporučivanja prema sličnost sadržaja i prema suradnji korisnika. U
sljedećem poglavlju slijedi opis programskih knjižnica Apache Lucene i Mahout
čija je integracija implementirana unutar programskog modula, koje je omogućila
osnovnu funkcionalnost modula. Zatim slijedi poglavlje u kojem su opisane tehnike
određivanja sličnosti koje su implementirane unutar razvijenog programskog
modula, a na kraju se nalaze poglavlja u kojima su opisane arhitektura, klase i
metode razvijenog programskog modula i slučajevi korištenja programskog
modula iz web-portala unutar kojeg bi modul bio integriran.
Ključne riječi: Preporuka, Lucene, Mahout, integracija.
Software module for content recommendations at web-portals
Summary
At the beginning of this master's thesis there is a description of basic
recommendation creating techniques that are integrated into the developed
software, and these are Content-based and Collaborative filtering recommendation
techniques. In the following chapter Apache Lucene and Mahout software libraries
are described, whose integration is incorporated into the software module,
enabling basic functioning of the module. Following this, similarity defining
technics that are integrated into the developed software are discussed. In the final
chapters architecture, classes and methods of the developed software are
depicted, along with the software module usage cases from a web-portal into
which the software module is designed to be integrated.
Keywords: Recommendation, Lucene, Mahout, integration.
38
10 Dodatak: programski kod
package recs.parse;
/**
*
* @author joe
*/
public class NewsItem {
private String _title="";
private Text _txt;
public String getTitle() {
return _title;
}
public void setTitle(String title) {
_title = title;
}
public Text getText() {
return _txt;
}
public void setText(Text txt) {
_txt = txt;
}
}
package recs.parse;
/**
*
* @author joe
*/
public class Text {
private String _p="";
public String getP() {
return _p;
}
public void setP (String p) {
_p = _p + p;
}
}
39
package recs.parse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
import org.apache.commons.digester3.Digester;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexWriter;
import org.xml.sax.SAXException;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import static
recs.basics.DataModelGenerator.removeRatingsForDeletedNewsItem;
import static recs.parse.MatrixGeneratorWithTopDocs.addingNewsItem;
import static
recs.parse.MatrixGeneratorWithTopDocs.getSimilarityIndexesOutput;
import static
recs.parse.MatrixGeneratorWithTopDocs.initialGenerator;
import static
recs.parse.MatrixGeneratorWithTopDocs.removingNewsItem;
public class Indexer {
private static final StandardAnalyzer analyzer = new
StandardAnalyzer();
private static final IndexWriterConfig config = new
IndexWriterConfig(analyzer);
private static HashMap <String,Integer> tempMap = new HashMap<>
();
public static void main (String[] args) throws Exception {
//addNewsItem ("/home/joe/recs/dataset");
//addNewsItem ("/home/joe/recs/2286newsML.xml");
//removeNewsItem ("/home/joe/recs/2286newsML.xml");
}
40
public static void addNewsItem (String dataPath) throws IOException,
SAXException, Exception {
File dataDir = new File (dataPath);
if (!dataDir.exists()) {
throw new IOException (dataDir + " does not
exist");
}
Directory indexDir = getIndexDir();
IndexWriter writer = new IndexWriter (indexDir, config);
//DELETION OF OLD DATA IN INDEX
//writer.deleteAll();
int [][] matrix = null;
try {
matrix = getSimilarityIndexesOutput();
} catch (FileNotFoundException e) {
System.out.println (e);
}
if (dataDir.getName().endsWith(".xml") && matrix != null)
{
index (writer, dataDir);
addingNewsItem();
HashMap <String,Integer> map = getIndexMap();
map.put(dataDir.getAbsolutePath(),
writer.numDocs()-1);
setIndexMap (map);
writer.close();
} else if(dataDir.isDirectory() && matrix == null) {
index (writer, dataDir);
initialGenerator (100);
setIndexMap (tempMap);
writer.close();
} else {
writer.close();
System.out.println ("You must set folder as path
for fisrt time. Otherwise XML file is needed.");
}
}
private static void index (IndexWriter writer, File dataDir) throws
SAXException, Exception {
long start = new Date().getTime();
indexDirectory (writer, dataDir);
long end = new Date().getTime();
writer.commit();
int numIndexed = writer.maxDoc();
System.out.println("Indexing file(s) took " + (end - start) + "
miliseconds");
System.out.println("Index contains " + numIndexed + "
file(s)");
}
41
//recursive method that calls itself when it finds a directory
private static void indexDirectory (IndexWriter writer, File dir)
throws IOException, SAXException, Exception {
if (dir.getName().endsWith(".xml")) {
indexFile (writer, dir);
} else {
File[] files = dir.listFiles();
for (File f : files) {
if (f.isDirectory()) {
indexDirectory (writer, f);
} else if (f.getName().endsWith(".xml")) {
indexFile (writer, f);
}
}
}
}
//method to index a file using Lucene
private static void indexFile (IndexWriter writer, File f) throws
IOException, SAXException, FileNotFoundException,
ClassNotFoundException {
if (f.isHidden() || !f.exists() || !f.canRead()) {
return;
}
System.out.println("Indexing " + f.getCanonicalPath());
Digester digester = new Digester();
digester.setValidating(false);
digester.addObjectCreate ("newsitem", NewsItem.class);
digester.addObjectCreate ("newsitem/text", Text.class);
digester.addBeanPropertySetter("newsitem/text/p", "p");
digester.addSetNext("newsitem/text", "setText");
digester.addBeanPropertySetter("newsitem/title", "title");
NewsItem newsItem = (NewsItem) digester.parse(f);
Document doc = new Document();
final FieldType ft = new FieldType();
final FieldType ft2 = new FieldType();
ft.setStored(true);
ft.setStoreTermVectors(true);
ft.setTokenized(true);
ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AN
D_OFFSETS);
ft2.setStored(true);
ft2.setStoreTermVectors(true);
ft2.setTokenized(true);
ft2.setIndexOptions(IndexOptions.DOCS);
Field titleField = new Field ("title", newsItem.getTitle(),
ft);
doc.add(titleField);
Field contentField = new Field ("text",
newsItem.getText().getP(), ft);
doc.add(contentField);
Field nameField = new Field ("name", f.getName(), ft2);
doc.add(nameField);
writer.addDocument(doc);
int docId = writer.numDocs()-1;
tempMap.put(f.getAbsolutePath(),docId);
System.out.println(docId);
//pretty much slower with committing after every file
//writer.commit();
}
42
public static void removeNewsItem (String absolutePath) throws
IOException, ParseException, Exception {
Directory indexDir = getIndexDir();
IndexReader reader = DirectoryReader.open(indexDir);
IndexSearcher is = new IndexSearcher (reader);
Path path = Paths.get (absolutePath);
String name = path.getFileName().toString();
System.out.println (name);
Query query = new QueryParser("name", new
StandardAnalyzer()).parse(name);
TopDocs docs = is.search(query, 1);
ScoreDoc[] hits = docs.scoreDocs;
System.out.println (hits.length);
reader.close();
IndexWriter writer = new IndexWriter (indexDir, config);
if (hits.length == 1) {
int docId = hits[0].doc;
System.out.println("Deleted document id " + docId);
writer.deleteDocuments (query);
writer.commit();
System.out.println(writer.maxDoc());
writer.close();
//Method of MatrixGeneratorWithTopDocs class
removingNewsItem (docId);
removeRatingsForDeletedNewsItem (absolutePath);
//Removing from HashMap
HashMap <String,Integer> map = getIndexMap();
map.remove(absolutePath);
setIndexMap (map);
} else {
System.out.println ("There's no hits");
}
}
public static Directory getIndexDir () throws IOException {
String indexPath = "/home/joe/recs/index";
Directory indexDir = FSDirectory.open(Paths.get(indexPath));
return indexDir;
}
43
public static HashMap<String,Integer> getIndexMap() throws
FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("indexMapOutput"));
HashMap <String,Integer> map = (HashMap<String, Integer>)
in.readObject();
return map;
}
public static void setIndexMap (HashMap <String, Integer> outputMap)
throws IOException {
ObjectOutputStream out = new ObjectOutputStream ( new
FileOutputStream ("indexMapOutput"));
out.writeObject(outputMap);
out.close();
}
}
44
package recs.parse;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.apache.lucene.analysis.core.StopAnalyzer;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import static recs.parse.Indexer.getIndexDir;
/**
*
* @author joe
*/
public class MatrixGeneratorWithTopDocs {
public static void main (String[] args) throws Exception {
//initialGenerator (100);
//have to call this method after indexind new newsitem
//addingNewsItem ();
//have to call this method after removing newsitem from index
//removingNewsItem (20999);
}
//getting indexPath and number TopDocs for every item (number of
columns in matrix)
public static void initialGenerator (int num) throws Exception {
int i,j;
Directory fsDir = getIndexDir();
IndexReader reader = DirectoryReader.open(fsDir);
IndexSearcher is = new IndexSearcher (reader);
float [][] similarityValuesMatrix = new
float[reader.numDocs()][num];
int [][] similarityIndexesMatrix = new
int[reader.numDocs()][num];
BooleanQuery.setMaxClauseCount(10000);
for (i=0; i<reader.numDocs(); i++) {
TopDocs docs = getTopDocs (reader, is, i, num);
for (j=1; j<docs.scoreDocs.length; j++) {
System.out.println (docs.scoreDocs[j].doc+"
"+docs.scoreDocs[j].score);
similarityIndexesMatrix[i][j-
1]=docs.scoreDocs[j].doc;
similarityValuesMatrix[i][j-
1]=docs.scoreDocs[j].score;
}
}
outputtingMatrix (similarityIndexesMatrix,
similarityValuesMatrix);
}
45
// need to INDEX Newsitem first so numDocs IS ALREADY BIGGER FOR ONE
public static void addingNewsItem () throws Exception {
int i,j,num;
Directory fsDir = getIndexDir();
IndexReader reader = DirectoryReader.open(fsDir);
IndexSearcher is = new IndexSearcher (reader);
int [][] inIndexes = getSimilarityIndexesOutput();
float [][] inValues = getSimilarityValuesOutput();
//number od TopDocs in a row (number of columns)
num = inIndexes[0].length;
int [][] similarityIndexesMatrix = new
int[reader.numDocs()][num];
float [][] similarityValuesMatrix = new
float[reader.numDocs()][num];
for (i=0; i<reader.numDocs()-1; i++) {
for (j=0; j<num; j++) {
similarityIndexesMatrix [i][j] = inIndexes[i][j];
similarityValuesMatrix[i][j]= inValues[i][j];
}
}
//got numDocs() documents in index, from 0 to numDocs()-1
TopDocs docs = getTopDocs (reader, is, reader.numDocs()-1,num);
for (j=1; j<docs.scoreDocs.length; j++) {
System.out.println (docs.scoreDocs[j].doc+"
"+docs.scoreDocs[j].score);
similarityIndexesMatrix [reader.numDocs()-1][j-1] =
docs.scoreDocs[j].doc;
similarityValuesMatrix [reader.numDocs()-1][j-1] =
docs.scoreDocs[j].score;
}
outputtingMatrix (similarityIndexesMatrix,
similarityValuesMatrix);
}
46
//need to remove Newsitem from index first so numDocs IS ALREADY LESS
FOR ONE
public static void removingNewsItem (int docId) throws Exception {
int i,j,num;
Directory fsDir = getIndexDir();
IndexReader reader = DirectoryReader.open(fsDir);
int [][] inIndexes = getSimilarityIndexesOutput();
float [][] inValues = getSimilarityValuesOutput();
//number od TopDocs in a row (number of columns)
num = inIndexes[0].length;
int [][] similarityIndexesMatrix = new
int[reader.numDocs()][num];
float [][] similarityValuesMatrix = new
float[reader.numDocs()][num];
//removing element from matrix with checking if element is in
last row or somewhere earlier
System.out.println (reader.numDocs());
if (docId == reader.numDocs()) {
for (i=0; i<reader.numDocs(); i++) {
for (j=0; j<num; j++) {
similarityIndexesMatrix [i][j] = inIndexes
[i][j];
similarityValuesMatrix [i][j] = inValues
[i][j];
}
}
} else {
for (i=docId; i<reader.numDocs(); i++) {
for (j=0; j<num; j++) {
similarityIndexesMatrix [i][j] =
inIndexes[i+1][j];
similarityValuesMatrix[i][j]=
inValues[i+1][j];
}
}
}
outputtingMatrix (similarityIndexesMatrix,
similarityValuesMatrix);
}
47
private static TopDocs getTopDocs (IndexReader reader, IndexSearcher
is, int i, int num) throws IOException, ParseException {
String q = reader.document(i).getField("text").stringValue();
TopDocs docs;
int solution;
if (q.length()!=0) {
Query query = new QueryParser("text", new
StopAnalyzer()).parse(QueryParser.escape(q));
docs = is.search(query, num+1);
System.out.println (i+" "+docs.scoreDocs.length);
solution = 1;
} else {
q =
reader.document(i).getField("title").stringValue();
Query query = new QueryParser("title", new
StopAnalyzer()).parse(QueryParser.escape(q));
docs = is.search(query, num+1);
System.out.println (i+" "+docs.scoreDocs.length);
solution = 2;
}
if (docs.scoreDocs.length<(num+1) && solution == 1) {
q =
reader.document(i).getField("title").stringValue();
Query query = new QueryParser("title", new
StopAnalyzer()).parse(QueryParser.escape(q));
docs = is.search(query, num+1);
System.out.println (i+" "+docs.scoreDocs.length);
}
return docs;
}
private static void outputtingMatrix (int[][]
similarityIndexesMatrix, float[][] similarityValuesMatrix) throws
IOException {
ObjectOutputStream out = new ObjectOutputStream ( new
FileOutputStream ("similarityIndexesOutput"));
out.writeObject(similarityIndexesMatrix);
out.close();
ObjectOutputStream out1 = new ObjectOutputStream ( new
FileOutputStream ("similarityValuesOutput"));
out1.writeObject(similarityValuesMatrix);
out1.close();
}
48
public static int [][] getSimilarityIndexesOutput() throws
FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("similarityIndexesOutput"));
int [][] inIndexes = (int [][]) in.readObject();
in.close();
return inIndexes;
}
public static float [][] getSimilarityValuesOutput() throws
IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("similarityValuesOutput"));
float [][] inValues = (float [][]) in.readObject();
in.close();
return inValues;
}
}
49
package recs.mahout.itemsimilarity;
import com.google.common.base.Preconditions;
import java.util.Collection;
import org.apache.mahout.cf.taste.common.Refreshable;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.RefreshHelper;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
/**
*
* @author joe
*/
public final class ItemSimilarityFromTopDocsMatrix implements
ItemSimilarity{
private final int[][] matrixIndexes;
private final float[][] matrixValues;
private final RefreshHelper refreshHelper;
public ItemSimilarityFromTopDocsMatrix (int [][] matrixIndexes,
float[][] matrixValues) {
Preconditions.checkArgument(matrixIndexes != null,
"matrixIndexes is null");
Preconditions.checkArgument(matrixValues != null,
"matrixValues is null");
this.matrixIndexes = matrixIndexes;
this.matrixValues = matrixValues;
this.refreshHelper = new RefreshHelper(null);
}
public int[][] getMatrixIndexes(){
return matrixIndexes;
}
public float[][] getMatrixValues(){
return matrixValues;
}
@Override
public double itemSimilarity(long itemID1, long itemID2) throws
TasteException {
int [][] matrixIndexesLocal = getMatrixIndexes();
float [][] matrixValuesLocal = getMatrixValues();
int [] matrixIndexesRow = matrixIndexesLocal[(int)itemID1-1];
boolean contains = false;
int ind = 0;
for (int i=0; i<matrixIndexesRow.length; i++) {
if (matrixIndexesRow[i]==(((int)itemID2)-1)) {
ind = i;
contains = true;
}
}
if (contains) {
//int index =
Arrays.asList(matrixIndexesRow).indexOf((int)itemID2-1);
return matrixValuesLocal[((int)itemID1)-1][ind];
} else {
return 0;
}
}
50
@Override
public double[] itemSimilarities(long itemID1, long[] itemID2s)
throws TasteException {
int [][] matrixIndexesLocal = getMatrixIndexes();
float [][] matrixValuesLocal = getMatrixValues();
int length = itemID2s.length;
double[] result = new double[length];
int [] matrixIndexesRow = matrixIndexesLocal[(int)itemID1-1];
int ind = 0;
boolean contains;
for (int i=0; i<length; i++) {
contains = false;
for (int j=0; i<matrixIndexesRow.length; i++) {
if (matrixIndexesRow[i]==(((int)itemID2s[j])-1)) {
ind = i;
contains = true;
}
}
if (contains) {
//int index =
Arrays.asList(matrixIndexesRow).indexOf((int)itemID2-1);
result[i] = matrixValuesLocal[((int)itemID1)-
1][ind];
} else {
result[i] = 0;
}
}
return result;
}
@Override
public long[] allSimilarItemIDs(long itemID) throws TasteException {
//throw new UnsupportedOperationException("Not supported
yet."); //To change body of generated methods, choose Tools |
Templates.
long[] result = null;
result = null;
int [][] matrixIndexesLocal = getMatrixIndexes();
int [] middleResult = matrixIndexesLocal[(int)itemID-1];
for (int i=0; i<middleResult.length; i++) {
long help = new Long (middleResult[i]);
result[i] = help;
}
return result;
}
@Override
public void refresh(Collection<Refreshable> alreadyRefreshed) {
refreshHelper.refresh(alreadyRefreshed);//throw new
UnsupportedOperationException("Not supported yet."); //To change body
of generated methods, choose Tools | Templates.
}
}
51
package recs.parse;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.queryparser.classic.ParseException;
import static recs.parse.Indexer.getIndexMap;
import static
recs.parse.MatrixGeneratorWithTopDocs.getSimilarityIndexesOutput;
/**
*
* @author joe
*/
public class ItemRecommenderFromTopDocsMatrix {
public static void main (String[] args) throws Exception {
//getTopNRecommendationsFromMatrix
("/home/joe/recs/2286newsML.xml", 6);
}
public static Set<String> getTopNRecommendationsFromMatrix
(String absolutePath, int N) throws IOException,
ClassNotFoundException, ParseException {
int numOfRecommends = N;
int [][] inMatrix = getSimilarityIndexesOutput();
HashMap <String,Integer> indexMap = getIndexMap();
int itemId = indexMap.get(absolutePath);
Set<String> shouldRecommend = new HashSet<>();
for (int j=0; j< numOfRecommends; j++) {
for (String absPath : indexMap.keySet()) {
int recId = inMatrix[itemId][j];
if (indexMap.get(absPath).equals(recId)) {
shouldRecommend.add(absPath);
}
}
}
System.out.println (shouldRecommend.toString());
return (shouldRecommend);
}
}
52
package recs.parse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import
org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhoo
d;
import
org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommend
er;
import
org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommend
er;
import
org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilari
ty;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.UserBasedRecommender;
import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import static recs.basics.DataModelGenerator.getDataModelFileName;
import static recs.basics.DataModelGenerator.getDataModelFullPath;
import recs.mahout.itemsimilarity.ItemSimilarityFromTopDocsMatrix;
import static recs.parse.Indexer.getIndexMap;
import static
recs.parse.ItemRecommenderFromTopDocsMatrix.getTopNRecommendationsFro
mMatrix;
import static
recs.parse.MatrixGeneratorWithTopDocs.getSimilarityIndexesOutput;
import static
recs.parse.MatrixGeneratorWithTopDocs.getSimilarityValuesOutput;
/**
*
* @author joe
*/
public class ItemRecommenderFromDataModelTopDocs {
public static void main (String[] args) throws Exception {
//getTopNRecommendations ("/home/joe/recs/2286newsML.xml",
6);
//getTopNRecommendations (50,6);
//getTopNRecommendations ("/home/joe/recs/2286newsML.xml",
50, 6);
}
53
public static Set<String> getTopNRecommendations (String
absolutePath, int N) throws IOException, ClassNotFoundException,
ParseException {
HashMap <String,Integer> indexMap = getIndexMap();
int itemId = indexMap.get(absolutePath);
Set<String> shouldRecommend = new HashSet<>();
DataModel dm = new FileDataModel (new
File(getDataModelFileName()));
int [][] inIndexes = getSimilarityIndexesOutput();
float [][] inValues = getSimilarityValuesOutput();
ItemSimilarity sim2 = new
ItemSimilarityFromTopDocsMatrix(inIndexes, inValues);
GenericItemBasedRecommender recommender = new
GenericItemBasedRecommender (dm, sim2);
List<RecommendedItem> recommendations;
try {
recommendations = recommender.mostSimilarItems(itemId+1,
N);
for (RecommendedItem recommendation : recommendations) {
for (String absPath : indexMap.keySet()) {
int recId = (int)
(recommendation.getItemID());
recId = recId-1;
if (indexMap.get(absPath).equals(recId)) {
System.out.println (recId);
System.out.println (absPath);
shouldRecommend.add(absPath);
}
}
}
if (!shouldRecommend.isEmpty()) {
System.out.println (shouldRecommend.toString());
return (shouldRecommend);
} else {
shouldRecommend = getTopNRecommendationsFromMatrix
(absolutePath, N);
return (shouldRecommend);
}
} catch (TasteException ex) {
System.out.println (ex.toString());
shouldRecommend = getTopNRecommendationsFromMatrix
(absolutePath, N);
return (shouldRecommend);
}
}
54
public static Set<String> getTopNRecommendations (String
absolutePath, int userId, int N) throws IOException,
ClassNotFoundException, ParseException {
HashMap <String,Integer> indexMap = getIndexMap();
int itemId = indexMap.get(absolutePath);
Set<String> shouldRecommend = new HashSet<>();
DataModel dm = new FileDataModel (new
File(getDataModelFileName()));
int [][] inIndexes = getSimilarityIndexesOutput();
float [][] inValues = getSimilarityValuesOutput();
ItemSimilarity sim2 = new
ItemSimilarityFromTopDocsMatrix(inIndexes, inValues);
GenericItemBasedRecommender recommender = new
GenericItemBasedRecommender (dm, sim2);
List<RecommendedItem> recommendations;
try {
recommendations = recommender.recommend(userId, N);
for (RecommendedItem recommendation : recommendations) {
for (String absPath : indexMap.keySet()) {
int recId = (int)
(recommendation.getItemID());
recId = recId-1;
if (indexMap.get(absPath).equals(recId)) {
System.out.println (recId);
System.out.println (absPath);
shouldRecommend.add(absPath);
}
}
}
if (!shouldRecommend.isEmpty()) {
System.out.println (shouldRecommend.toString());
return (shouldRecommend);
} else {
shouldRecommend = getTopNRecommendationsFromMatrix
(absolutePath, N);
return (shouldRecommend);
}
} catch (TasteException ex) {
System.out.println (ex.toString());
shouldRecommend = getTopNRecommendationsFromMatrix
(absolutePath, N);
return (shouldRecommend);
}
}
55
public static Set<String> getTopNRecommendations (int userId, int N)
throws IOException, FileNotFoundException, ClassNotFoundException,
TasteException, ParseException{
HashMap <String,Integer> indexMap = getIndexMap();
Set<String> shouldRecommend = new HashSet<>();
DataModel model = new FileDataModel(new
File(getDataModelFullPath()));
UserSimilarity similarity = new
PearsonCorrelationSimilarity(model);
UserNeighborhood neighborhood = new
ThresholdUserNeighborhood(0.2, similarity, model);
UserBasedRecommender recommender = new
GenericUserBasedRecommender(model, neighborhood, similarity);
List<RecommendedItem> recommendations;
try {
recommendations = recommender.recommend(userId, N);
for (RecommendedItem recommendation : recommendations) {
for (String absPath : indexMap.keySet()) {
int recId = (int)
(recommendation.getItemID());
recId = recId-1;
if (indexMap.get(absPath).equals(recId)) {
System.out.println (recId);
System.out.println (absPath);
shouldRecommend.add(absPath);
}
}
}
System.out.println (shouldRecommend.toString());
return shouldRecommend;
} catch (TasteException ex) {
System.out.println (ex.toString());
System.out.println (shouldRecommend.toString());
return shouldRecommend;
}
}
}
56
package recs.basics;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.io.FileUtils;
import static recs.parse.Indexer.getIndexMap;
/*
* To change this license header, choose License Headers in Project
Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author joe
*/
public class DataModelGenerator {
public static void main (String[] args) throws
FileNotFoundException, UnsupportedEncodingException, IOException,
ClassNotFoundException {
initialModel ();
//rateNewsItem (1111, "/home/joe/recs/2286newsML.xml", 1111);
//rateNewsItem (1111, "/home/joe/recs/2286newsML.xml", 1112);
//rateNewsItem (1111, "/home/joe/recs/2286newsML.xml", 1112);
//removeRatingsForDeletedNewsItem
("/home/joe/recs/2286newsML.xml");
}
57
//creatnig initial random dataModel.csv file
public static void initialModel () throws FileNotFoundException,
UnsupportedEncodingException {
try (PrintWriter writer = new
PrintWriter(getDataModelFileName(), "UTF-8")) {
int n = 21000;
for (int u=1; u<51; u++) {
ArrayList<Integer> arrayRandom = new
ArrayList<>(100);
for (int i=0; i<1030; i++)
{
arrayRandom.add(ThreadLocalRandom.current().nextInt(1, n
+ 1));
}
Set <Integer> set = new LinkedHashSet<>
(arrayRandom);
Object[] objRandoms = set.toArray();
for (Object objRandom : objRandoms) {
if (u==50 &&
objRandom==objRandoms[objRandoms.length-1]) {
writer.print(u+"," + objRandom + "," +
ThreadLocalRandom.current().nextInt(1,6));
} else {
writer.println(u+"," + objRandom + ","
+ ThreadLocalRandom.current().nextInt(1,6));
}
}
}
} catch (FileNotFoundException | UnsupportedEncodingException
e) {
System.out.println (e.toString());
}
}
//adding new line into dataModel.csv file
public static void rateNewsItem (int userId, String absolutePath, int
rating) throws IOException, FileNotFoundException,
ClassNotFoundException {
String csvFile = getDataModelFullPath();
BufferedWriter bw = null;
Path path = Paths.get (absolutePath);
String name = path.getFileName().toString();
HashMap <String,Integer> indexMap = getIndexMap();
int newsItemId = indexMap.get(name);
//file writer with append parameter setted to true
try {
bw = new BufferedWriter(new FileWriter(csvFile, true));
bw.append("\n" + userId + "," + newsItemId + "," +
rating);
bw.close();
} catch (IOException e) {
System.out.println (e.toString());
System.out.println ("No such file :" + csvFile);
}
}
58
//removing lines of deleted newsitem in dataModel
public static void removeRatingsForDeletedNewsItem (String
absolutePath) throws IOException, FileNotFoundException,
ClassNotFoundException {
File dataModel = new File (getDataModelFullPath());
String fileContents = FileUtils.readFileToString(dataModel);
String [] fileContentLines =
fileContents.split(System.lineSeparator());
emptyFile (dataModel);
Path path = Paths.get (absolutePath);
HashMap <String,Integer> indexMap = getIndexMap();
int newsItemId = indexMap.get(absolutePath);
StringBuilder newContent = new StringBuilder ();
for (String fileContentLine : fileContentLines) {
if (fileContentLine.contains(","+newsItemId+",")) {
continue;
}
newContent.append(fileContentLine +
System.lineSeparator());
}
FileUtils.writeStringToFile (dataModel,
newContent.toString().trim());
}
//help method for removing files
private static void emptyFile (File file) throws IOException {
try (RandomAccessFile randomAccessFile = new RandomAccessFile
(file, "rw")) {
randomAccessFile.setLength(0);
randomAccessFile.close();
} catch(IOException e) {
System.out.println(e.toString());
System.out.println ("No such file: " + file);
}
}
public static String getDataModelFullPath () {
String fullPath = "/home/joe/recs/dataModel.csv";
return fullPath;
}
public static String getDataModelFileName () {
String fileName = "dataModel.csv";
return fileName;
}
}