forked from jonchisko/seminar2-os
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbranje_ocen.py
1112 lines (850 loc) · 44.5 KB
/
branje_ocen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# import pandas, datetime and numpy
import pandas as pd
from datetime import date
import numpy as np
import operator
import time
from numba import cuda
#Branje ocen (6)
class UserItemData:
#konstruktor
def __init__(self, path, **kwargs):
#**kwargs, from_date, to_date, min_ratings
#from_date, to_date input format day.month.year
#min_ratings input format integer
if path != "saved_user":
self.data_frame = pd.read_csv(path, sep="\t")
#dodaj stolpec date!
#apliciramo funkcijo date na vsako vrstico -> axis=1, da dobimo na vrstice
date_col = self.data_frame.loc[:,["date_year", "date_month", "date_day"]].apply(lambda row: date(row[0], row[1], row[2]), axis=1)
#self.data_frame = pd.concat([self.data_frame, date_col], axis=1)
self.data_frame = self.data_frame.assign(dated = date_col)
if("start_date" in kwargs):
date_arr_from = kwargs.get("start_date").split(".")
#date(year, month, day)
from_date = date(int(date_arr_from[-1]), int(date_arr_from[-2]), int(date_arr_from[-3]))
self.data_frame = self.data_frame.loc[self.data_frame["dated"] > from_date, :]
if("end_date" in kwargs):
date_arr_to = kwargs.get("end_date").split(".")
to_date = date(int(date_arr_to[-1]), int(date_arr_to[-2]), int(date_arr_to[-3]))
self.data_frame = self.data_frame.loc[self.data_frame["dated"] < to_date, :]
if("min_ratings" in kwargs):
min_ratings = kwargs.get("min_ratings")
#grupa določenega movieIDija mora biti večja od specificirane dolžine oz. števila ocen
self.data_frame = self.data_frame.groupby("movieID").filter(lambda x: len(x) > min_ratings)
else:
UserItemData.read_data(self)
#method how many movies were read
def nratings(self):
return self.data_frame.shape[0]
#method save read data
def save_data(self):
import pickle
#save file pickle
self.data_frame.to_pickle(path="saved_user")
def read_data(self):
self.data_frame = pd.read_pickle(path="saved_user")
#TEST1
"""uim = UserItemData('data/user_ratedmovies.dat')
print("Prvi test:", uim.nratings())
uim = UserItemData('data/user_ratedmovies.dat', start_date = '12.1.2007', end_date='16.2.2008', min_ratings=100)
print("Drugi test:", uim.nratings())
uim.save_data()"""
#Branje filmov (6)
class MovieData:
def __init__(self, path):
if path != "saved_movie":
self.data_frame = pd.read_csv(path, sep="\t", encoding="latin1")
else:
MovieData.read_data(self)
def get_title(self, movieID):
#return title of movie ID
return self.data_frame.loc[self.data_frame["id"] == movieID, "title"].values[0] #.values[0], zato, da vrne le string title, drugače dobiš še vrstico in kateri tip je
def save_data(self):
self.data_frame.to_pickle(path="saved_movie")
def read_data(self):
self.data_frame = pd.read_pickle(path="saved_movie")
#TEST2
"""md = MovieData('data/movies.dat')
print(md.get_title(1))"""
"""
Prediktorji:
fit - učenje parametrov
predict - napovedovanje prediktorjev
"""
#Naključni prediktor (6)
class RandomPredictor:
def __init__(self, min_rate, max_rate):
#nakljucni prediktor, ki vrne oceno med min in max
self.min_r = min_rate
self.max_r = max_rate
def fit(self, x):
#kjer je x tipa UserItemData
#nauci se na podatkih - ucnih, itak so random predikcije, zakaj bi rabil ucenje sploh?
#grupiraj po filmih
#česa se bom naučil pri random prediktorju ???
self.user_data = x.data_frame
def predict(self, user_id):
#vrni predikcije filmov za user-ja
#metoda predict pa vrne naključno vrednost (med min in max) za VSAK produkt.
df = MovieData("saved_movie").data_frame
predikcije = dict()
for movieID in df["id"]:
predikcije[movieID] = np.random.randint(self.min_r, self.max_r+1, 1)
return predikcije
#TEST3
#md = MovieData('data/movies.dat')
#uim = UserItemData('data/user_ratedmovies.dat')
#uim.save_data()
#md.save_data()
"""
md = MovieData("saved_movie")
uim = UserItemData("saved_user")
rp = RandomPredictor(1, 5)
rp.fit(uim)
path = 'data/movies.dat'
pred = rp.predict(78)
print(type(pred))
items = [1, 3, 20, 50, 100]
for item in items:
print("Film: {}, ocena: {}".format(md.get_title(item), pred[item]))"""
#Priporočanje (6)
class Recommender:
def __init__(self, prediktor):
self.prediktor = prediktor
def fit(self, x):
#kjer je x tipa UserItemData
#za ucenje modela
self.prediktor.fit(x)
def recommend(self, userID, n=10, rec_seen=True):
# vrne urejen seznam priporočenih produktov za uporabnika userID.
# Parameter n določa število priporočenih filmov, z rec_seen pa določimo ali hočemo med priporočenimi tudi
# že gledane (tiste, ki jim je uporabnik že dal oceno) ali ne."""
ocene = self.prediktor.predict(userID)
#sedaj imamo slovar s ključi, ki so movie ID in vrednostjo, ki je napoved
user_data = self.prediktor.user_data
#spremeni v list, drugače if x in videni_filmi ne deluje korektno, verjetno ker je to neka pandas "vrsta"
videni_filmi = list(user_data.loc[user_data["userID"] == userID, "movieID"])
if not rec_seen:
# za kopiranje tistih, ki rabimo, če je rec_seen False
final_grade = dict()
for m_id in ocene:
if m_id not in videni_filmi:
final_grade[m_id] = ocene[m_id]
ocene = final_grade
urejeno = sorted(ocene.items(), key=operator.itemgetter(1), reverse=True)
return urejeno[:n]
#NALOGA EVALUATE OCENA 7
def evaluate(self, test_data, n):
# test_data = sprejme testne podatke
# izračuna povprečne MAE, RMSE, priklic, natančnost, F1
# Za priklic, natančnost in F1 boste morali za vsakega uporabnika izbrati nekaj priporočenih produktov. Jaz sem se odločil, da vzamem tiste,
# ki jih je uporabnik ocenil bolje od svojega povprečja. Pri tem upoštevajte, da
# ne priporočate že gledanih produktov in da parameter
# n označuje število priporočenih produktov.
#pridobi napovedi, ki si jih dobil iz učnih
#podatki so ločeni po datumu na učne in testne, glej teste spodaj v kodi
test_frame = test_data.data_frame
#users - > spodnja koda vrže nepodvojene uporabinške IDije
users = self.prediktor.user_data["userID"].unique()
mse, mae, count, precision, recall, count2 = 0, 0, 0.000001, 0, 0, 0.000001
for user_id in users:
#produkti v testni množici, ki jih je uporabnik ocenil in jim je dal oceno, ki je višja od njegovega povprečja.
# S tem določimo produkte, ki so uporabniku všeč. Te produkte rabimo, da lahko merimo precision in recall.
user_data_test = test_frame.loc[test_frame["userID"] == user_id, ["movieID","rating"]]
#predicikcije na učnih
predikcija = Recommender.recommend(self, user_id, n=n, rec_seen=False) #to je seznam terk movie id in vrednost
if(len(user_data_test) == 0 or len(predikcija) == 0):
#ta user očitno ni ocenil nobenega filma po 2008
#oziroma v drugem primeru, ta user je ocenil vse filme, ki mu jih recommendamo in se zato vrne prazno
continue
#za precizijo, priklic in f vzemi le n
ppf = predikcija
#priporcam tiste vecje od uporabnikovega povprecja
avg = np.mean([value for id, value in ppf]) #avg napovedana ocena
priporocam = [id for id,value in ppf if value >= avg] #if value >= avg]
#Start
#iz testne pa zdaj vzames tudi le tiste, ki so visji od povprecja :)
avg2 = np.mean(user_data_test["movieID"].values)
v = [x for x in user_data_test["movieID"].values if x >= avg] #avg naucen iz ucnih vzamemo?
#End
presek = len(np.intersect1d(priporocam, v)) #kaj priporocas in kaj je user pogledal
precision += presek / len(priporocam)
recall += presek / len(user_data_test["movieID"])
count2 += 1
#primerjaj predikcije le po tistih, ki jih ima uporabnik zdaj ocenjene
ocenjeni = user_data_test["movieID"].values
for movie_id, value in predikcija:
#poglej, ce je ta movie_id sedaj ocenjen!
if movie_id in ocenjeni:
mse += (value - user_data_test.loc[user_data_test["movieID"] == movie_id, "rating"].item())**2
mae += np.abs(value - user_data_test.loc[user_data_test["movieID"] == movie_id, "rating"].item())
count+=1
return (mse/count, mae/count, precision/count2, recall/count2, 2*precision*recall/(precision+recall+0.000001) * 1/count2)
#TEST4
"""
md = MovieData("saved_movie")
uim = UserItemData("saved_user")
rp = RandomPredictor(1, 5)
rec = Recommender(rp)
rec.fit(uim)
rec_items = rec.recommend(78,n = 5, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
#Napovedovanje s povprečjem (6)
class AveragePredictor:
def __init__(self, b=0):
# je parameter formule za povprečje. Če je b=0, gre za navadno povprečje
self.b = b
def fit(self, x):
self.user_data = x.data_frame
#nastavi parametre
#VS je vsota vseh ocen za ta film
#n je stevilo ocen, ki jih je ta film dobil
#g_avg je povprecje cez vse
self.filmi_weighted_avg = dict()
g_avg = np.mean(self.user_data["rating"])
grouped = self.user_data.groupby("movieID")
vs = grouped["rating"].agg(np.sum)
for name, group in grouped:
n = len(group)
if not n:
self.filmi_weighted_avg[name] = 0
else:
#name uporabimo, name je namrec kar movieID, ker smo po temu grupirali
self.filmi_weighted_avg[name] = (vs[name] + self.b * g_avg) / (n + self.b)
def predict(self, user_id):
#ker v recommenderju poskrbimo za to, ali damo ven tudi filme, ki jih je user gledal ali ne
#tukaj lej vrnemo cel dict
return self.filmi_weighted_avg
#TEST5
"""md = MovieData("saved_movie")
uim = UserItemData("saved_user")
rec = Recommender(AveragePredictor(b=100))
rec.fit(uim)
rec_items = rec.recommend(78, n = 5, rec_seen=False)
for idmovie, val in rec_items:
print("Id: {}, Film: {}, ocena: {}".format(idmovie, md.get_title(idmovie), val))"""
#Priporočanje najbolj gledanih filmov (6)
class ViewsPredictor:
def __init__(self):
#za vsak film vrne število ogledov posameznega filma. To je priporočanje najbolj gledano
self.film_gledano = dict()
def fit(self, x):
self.user_data = x.data_frame
#grupiram po filmih
grouped = x.data_frame.groupby("movieID")
for name, x in grouped:
#"ocena" je kar velikost grupe filma oziroma stevilo ocen
self.film_gledano[name] = len(x)
def predict(self, user_id):
return self.film_gledano
#TEST6
"""md = MovieData("saved_movie")
uim = UserItemData("saved_user")
rec = Recommender(ViewsPredictor())
rec.fit(uim)
rec_items = rec.recommend(78, n = 5, rec_seen=False)
for idmovie, val in rec_items:
print("Id: {}, Film: {}, ocena: {}".format(idmovie, md.get_title(idmovie), val))"""
#Priporočanje kontroverznih filmov (7)
"""Kako bi ocenili kontroverznost (produkti, ki imajo veliko dobrih in veliko slabih ocen) ? Ali je "nepersonaliziran"
način priporočanja primeren za take produkte?
Napišite prediktor za najbolj kontroverzne produkte, kjer je film lahko kontroverzen, če ima vsaj n ocen.
Za mero uporabite standardno deviacijo ocen. Če mora film imeti vsaj 100 ocen, dobimo: """
"""
ODGOVOR:
Kontroverznost produktov bi gledal glede na razpršenost ocen, ki so mu bile dane. Najbolj kontroverzni produkti bodo imeli največjo razpršenost ocen - torej 1 in 5 v našem primeru.
To pa moramo še utežiti oziroma uporabiti le filme, ki imajo dovolj ocen, saj bi lahko v primeru neupoštevanja tega dobil film z dvema ocenama (1 in 5), maksimalno razpršenost in
bi bil zelo kontroverzen, čeprav je verjetnost tega majhna, saj je premalo ocen.
Ne vem kaj točno vprašanje sprašuje.
"""
class STDPredictor:
def __init__(self, n):
self.min_num = n
def fit(self, x):
self.user_data = x.data_frame
#grupiram po filmih
grouped = self.user_data.groupby("movieID")
self.dict_predikcij = dict()
for name, group in grouped:
if len(group)>self.min_num:
#ce je grupa filma vecja od min, potem aggregiraj standardno diviacijo po ocenah od grupe tega filma
self.dict_predikcij[name] = group["rating"].agg(np.std)
def predict(self, user_id):
return self.dict_predikcij
#TEST7
"""md = MovieData("saved_movie")
uim = UserItemData("saved_user")
rp = STDPredictor(100)
rec = Recommender(rp)
rec.fit(uim)
rec_items = rec.recommend(78, n=5, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
######## 2. VAJE #########
#Napovedovanje ocen s podobnostjo med produkti (6)
class ItemBasedPredictor:
def __init__(self, min_values=0, threshold=0):
self.mv = min_values
self.th = threshold
def fit(self, x):
self.user_data = x.data_frame
#dict filmov, kjer je kljuc film id, vrednost pa slovar kljuc film id in podobnost
self.dict_sim = dict()
#self.movie_data = MovieData("saved_movie").data_frame
#user data je zdesetkan na le tiste vrstice, kjer so filmi imeli več kot 1000 ocen
#grupiram po movieID
#g = [id for id, _ in self.user_data.groupby("movieID")]
g = list(self.user_data.groupby("movieID").apply(lambda x: x.name)) #spremenim v list, ker drugace je series in je g[indeks] v bistvu g[ključ] in ne indeks
#^ nič hitreje, tudi če ne spremenim v list in uporabim drugačen način zanke spodaj
#Da bi naredil matriko stevilo filmov x stevilo filmov, bi to bili v tem primeru pri filmih z nad 1000 ocenami, 81x81
#to bi bilo okoli 6000 računanj, vsakega z vsakim, a ker vem, da je par a b isto kot par b a, lahko računam le "trikotniško"
#število se zmanjša na: prvo 81, nato drugi film ni treba s prvim zato 80, nat 79 et cetera
#to pa je število operacij = n(n+1)/2
#v mojem primeru okoli 3000, prostorsko pa bom vseeno šel na 2x in zasedel še spodnjo matriko
self.movie_panda = pd.DataFrame(np.zeros((len(g), len(g))), index=g, columns=g)
self.pari_podobnosti = []
for i in range(len(g)):
for j in range(i+1, len(g)):
self.movie_panda.iloc[i, j] = ItemBasedPredictor.similarity(self, g[i], g[j])
#še spodnjo diagonalo zapolnim, mi bo lažje potem v predict, ne bo časovno računanje, ker je že poračunan
self.movie_panda.iloc[j, i] = self.movie_panda.iloc[i, j]
#dodam še v par podobnosti
#self.pari_podobnosti[ (g[i], g[j]) ] = self.movie_panda.iloc[i, j]
self.pari_podobnosti.append( (g[i], g[j], self.movie_panda.iloc[i, j]))
#v bistvu podobno kot zgoraj, po matriki uporabim funkcijo similartiy, ki prejme element in vstavi njegovo vrstico id in stolpec id v izračun.
#self.movie_panda = self.movie_panda.apply(lambda row: ItemBasedPredictor.similarity(self, row.index, row.name), axis=1)
""" 0
1 1 1
2 2
3 3
2 1 2
2 10
3 6
3 1 3
2 6
3 9
name[0] = 1, name[1] = 1 vrstica 1
name[0] = 1, name[1] = 2 vrstica 1
name[0]= 1, name[1] = 3, vrstica 1
name[0] =2, name[1] = 1, vrstica 2
name[0]= 2, name[1] = 2, vrstica 2
itd.
prvi stolpec so indeksi vrstic
drugi stolpec so names oz stolpci,
zadnji stolpec so vrednosti
tako dobim vsak indeks vrstice z vsakim stolpcem
in element wise, sam pač je počasneje od double fora v bistvu... vsaj v mojem primeru ???
"""
#self.movie_panda = self.movie_panda.stack().to_frame().apply(lambda x: ItemBasedPredictor.similarity(self, x.name[0], x.name[1]), axis=1).unstack() #počasneje
def predict(self, user_id):
#formula,
#pred(user5, produkt6) = vsota_od vseh itemsov ( podobnost(item, produkt6) * rating od item ) uteženo z vsoto podobnosti med item in p6
#skratka gremo čez vse produkte, ki jih je ocenil user5. Bolj kot je item podoben produkt6, bolj velja ocena, ki jo je dal user5 temu itemu.
#samo da tukaj ne vrnes le da produkt6, ampak za vse produkte, ki jih ta user še ni gledal -> oziroma napovej kar za vse filme, selekcijo nato itak delaš
#v recommender
napovedi = dict()
ocenjeni = self.user_data.loc[self.user_data["userID"] == user_id, ["movieID", "rating"]]
vsi_filmi = self.movie_panda.index
for film in vsi_filmi:
#izberemo prvo vrstice, ki vsebujejo filme, ki jih je ocenil uporabnik
#nato izberemo le tisti stolpec, za kateri film trenutno računamo, tako dobimo v selekciji array podobnosti, to so utezi
selekcija = self.movie_panda[self.movie_panda.index.isin(ocenjeni["movieID"])][film] #prvo izberemo le vrstice, filmi, ki so bili ocenjeni, nato pa le stolpec filma, ki ga "napovedujemo"
#->ce ne bi delal z boolean, bi takole slo [stolpec_ime][vrstica_ime]
#tole bo verjetno vedno delalo, saj bo isin šel po vrsti po matriki gledat in bodo filmi vedno od manjsega k vecjemu indeksu, ravno tako bodo v ocenjeni[rating], tako da
#se bodo vedno pravilno zmnozili istolezni(utez in ocena)
zgornja_vsota = np.sum(np.array(selekcija)*np.array(ocenjeni["rating"]))
spodnja_vsota = np.sum(np.array(selekcija))
if spodnja_vsota == 0:
#vsota bo nic takrat ko bo selekcija le en film, in bo film zanka prisel do tega istega filma (:
napovedi[film] = 0
else:
napovedi[film] = zgornja_vsota/spodnja_vsota
return napovedi
def return_podobne(self):
#vrni najbolj podobne
#sortirano = sorted(self.pari_podobnosti.items(), key=operator.itemgetter(1), reverse=True)
sortirano = sorted(self.pari_podobnosti, key=lambda x: x[2], reverse=True)
return sortirano
def similarItems(self, item, n):
#Kaj bi pokazali v kategoriji "Gledalci, ki so gledali A, so gledali tudi B"?
#vrne n najbolj podobnih filmov izbranemu filmu.
#i je index oziroma movieId, self.movie_panda[item][i] je pa stolpec od izbranega filma [i] pa movieId vrstica od filma s katerimi primerjamo - skratka podobnost je to
podobni_izbranemu = [ (i, self.movie_panda[item][i]) for i in self.movie_panda[item].index] #izberemo stolpec, ki ima item ime, to dobimo zdej vrsto podobnosti
return sorted(podobni_izbranemu, key=lambda x: x[1], reverse=True)[:n] #vrni le n najbolj
def similarity(self, p1, p2):
#Podobnost izračunajte s popravljeno cosinusno razdaljo
#Če je izračunana podobnost med produktoma manjša od threshold ali če imamo manj
#kot min_values uporabnikov, ki je ocenilo oba filma, naj bo podobnost med produktoma 0
#self.user_data = uim.data_frame
#pridobi vektorje, p1 in p2 sta enako dolga, najdi le skupne uporabnike in jih zlozi istolezno
first_data = self.user_data.loc[self.user_data["movieID"] == p1, ["userID", "rating"]]
second_data = self.user_data.loc[self.user_data["movieID"] == p2, ["userID", "rating"]]
#presek userjev, ki so v obeh
users = np.intersect1d(first_data["userID"], second_data["userID"])
if len(users) <= self.mv:
return 0
#dobim skupine userjev, ki so ocenili oba produkta. So zdruzene vrstice po userjih, torej, ce je user1 ocenil oba filma, je tukaj notri v svoji skupini in predstavlja n vrstic
grupe_user = self.user_data.groupby("userID")
#izracunam povprecje na skupino(user), njegova povprecna ocena
povprecje_user = grupe_user["rating"].agg(np.mean)
#ker ni uporabljen iloc, je to vbistvu po imenu vrstice, in to je indeks!!!
#print(povprecje_user[78])
# filtriram tiste userje, ki niso prisotni v obeh filmih
#dam se v numpy, ker ce odstejem pandas series od series dobim napacen rezultat
povprecje_user = np.array(povprecje_user[povprecje_user.index.isin(users)])
#vektorji
#avg_user = np.mean(self.user_data.loc[self.user_data["userID"].isin(users), "rating"])
# ne pozabi odšteti user bias-a od ratinga, torej odštej od ratinga povprečno user oceno
p1v = first_data.loc[first_data["userID"].isin(users), "rating"] - povprecje_user #ce bi bil scalar, sepravi da bi selecta eno vrednost, bi vrednost izluscil z .item()
p2v = second_data.loc[second_data["userID"].isin(users), "rating"] - povprecje_user
#dot
p1p2 = np.dot(np.array(p1v), np.array(p2v))
#dolzini
lengthp1 = np.sqrt(np.dot(p1v, p1v))
lengthp2 = np.sqrt(np.dot(p2v, p2v))
sim = p1p2 / (lengthp1 * lengthp2)
if sim < self.th:
return 0
return sim
#TEST8
"""md = MovieData('saved_movie')
uim = UserItemData('saved_user')
rp = ItemBasedPredictor()
rec = Recommender(rp)
t1 = time.time()
#rec.fit(uim)
print(time.time()-t1)
print("Podobnost med filmoma 'Men in black'(1580) in 'Ghostbusters'(2716): ", rp.similarity(1580, 2716))
print("Podobnost med filmoma 'Men in black'(1580) in 'Schindler's List'(527): ", rp.similarity(1580, 527))
print("Podobnost med filmoma 'Men in black'(1580) in 'Independence day'(780): ", rp.similarity(1580, 780))
print("\n")
rec_items = rec.recommend(78, n=15, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))
najbolj_podobni = rp.return_podobne()
print("\n")
for par1, par2, value in najbolj_podobni[:20]:
print("Film 1: %s, Film2: %s, podobnost: %f" % (md.get_title(par1), md.get_title(par2), value))
rec_items = rp.similarItems(4993, 10)
print('\nFilmi podobni "The Lord of the Rings: The Fellowship of the Ring": ')
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
######################
# Priporočilo zase (6)
# Naredite še priporočilo zase z metodo "item-based"; izberite si cca. 20 filmov,
# ki jih poznate in jih ročno ocenite. Dodajte svoje ocene v movielens bazo in si priporočite 10 filmov.
md = MovieData('saved_movie')
uim = UserItemData('saved_user')
#userID movieID rating date_day date_month date_year date_hour date_minute date_second
#75 3 1 29 10 2006 23 17 16
moje_ocene = pd.DataFrame([[123456789, 5952, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 7153, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 4993, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 586, 2, 25, 12, 2017, 10, 10, 10],
[123456789, 6874, 3.5, 25, 12, 2017, 10, 10, 10],
[123456789, 260, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 1196, 4.5, 25, 12, 2017, 10, 10, 10],
[123456789, 344, 1, 25, 12, 2017, 10, 10, 10],
[123456789, 367, 1, 25, 12, 2017, 10, 10, 10],
[123456789, 1210, 3.5, 25, 12, 2017, 10, 10, 10],
[123456789, 2628, 2.5, 25, 12, 2017, 10, 10, 10],
[123456789, 780, 2, 25, 12, 2017, 10, 10, 10],
[123456789, 2571, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 541, 4, 25, 12, 2017, 10, 10, 10],
[123456789, 527, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 1136, 5, 25, 12, 2017, 10, 10, 10],
[123456789, 2, 2.5, 25, 12, 2017, 10, 10, 10],
[123456789, 1, 3.5, 25, 12, 2017, 10, 10, 10],
[123456789, 48, 3, 25, 12, 2017, 10, 10, 10],
[123456789, 364, 4.5, 25, 12, 2017, 10, 10, 10]])
#te indeksi so col namesi, zato so ubistvu kr isti ..... wtf lol ta row je kr series al kaj, mogoče .iloc drgač vrne kokr če po .loc
moje_ocene = moje_ocene.assign(dated=moje_ocene.iloc[:,[5, 4, 3]].apply(lambda row: date(row[5], row[4], row[3]), axis=1))
moje_ocene.columns = uim.data_frame.columns
moje_ocene = moje_ocene.sort_values(["movieID"])
#ignorira indekse da se ne ponavljajo, zdruzim dataframea
uim.data_frame = uim.data_frame.append(moje_ocene, ignore_index=True)
"""rp = ItemBasedPredictor()
rec = Recommender(rp)
t1 = time.time()
rec.fit(uim)
print("\n")
print(time.time()-t1)
print("\n")
rec_items = rec.recommend(123456789, n=10, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
###################################
###################################
###################################
class SlopeOnePredictor:
# Izbereš i film in j film
# Greš sum po uprabnikih:
# izbereš uporabnika in če ima oba filma ocenjena, izračunaš razliko
# greš na naslednjega uporabnika in storiš isto
# -> dobljeni sum še normaliziraš s številom uporabnikov, ki so ocenili oba filma
# -> DIF i,j ... to je "razlika" za en par
def __init__(self):
pass
def fit(self, x):
#user data
self.user_data = x.data_frame
#SESTAVIM MATRIKO MOVIE BY MOVIE PO ČISTO ENAKI LOGIKI KOT V ITEMBASED
g = list(self.user_data.groupby("movieID").apply(lambda x: x.name)) #spremenim v list, ker drugace je series in je g[indeks] v bistvu g[ključ] in ne indeks
self.movie_panda = pd.DataFrame(np.zeros((len(g), len(g))), index=g, columns=g) #dev za vsak par film
self.users_panda = pd.DataFrame(np.zeros((len(g), len(g))), index=g, columns=g) #stevilo uporabnikov, ki so ocenili ta par filmov
for i in range(len(g)):
for j in range(i+1, len(g)):
self.movie_panda.iloc[i, j], self.users_panda.iloc[i, j] = SlopeOnePredictor.dif(self, g[i], g[j])
#še spodnjo diagonalo zapolnim, mi bo lažje potem v predict, ne bo časovno računanje, ker je že poračunan
#DEV skopiramo v spodnji trikotnik matrike z minus predznakom. DEV nam namreč pove koliko je par1 boljsi od par2, torej ko kopiras v polje kjer je par2,par1 in ne par1,par2
#moras dati nasprotni predznak
self.movie_panda.iloc[j, i], self.users_panda.iloc[j, i] = -self.movie_panda.iloc[i, j], self.users_panda.iloc[i, j]
def predict(self, user_id):
napovedi = dict()
#user je ocenil tele
ocenjeni = self.user_data.loc[self.user_data["userID"] == user_id, ["movieID","rating"]]
#vsi filmi, ki sploh so
vsi_filmi = self.movie_panda.index
for film in vsi_filmi:
dev_ocenjeni_film = self.movie_panda[film][ocenjeni["movieID"]]
st_ocen_user = self.users_panda[film][ocenjeni["movieID"]]
#če bo user mel le eno oceno, bo ta pri film = ocena filma
#np.sum(st_ocen_user) == 0, kar bo division err
if np.sum(st_ocen_user) == 0:
napovedi[film] = 0
else:
napovedi[film] = np.sum((ocenjeni["rating"].values - dev_ocenjeni_film.values) * st_ocen_user.values) / np.sum(st_ocen_user.values)
return napovedi
def dif(self, j, i):
#arg j je film j
#arg i je film i
#selekcioniraj ven userje, ki so ocenili j in i
#>skupina po userID, v vsaki skupini obdrzim tiste, ki imajo notri j in i
df_f = self.user_data.loc[np.logical_or(self.user_data["movieID"] == i, self.user_data["movieID"] == j), ["userID", "rating", "movieID"]]
#če imaš in I in J, potem sta dva vnosa v df_f in nsi duplikat
#df_uniq vsebuje le ne duplicirane, torej une userje, ki imajo ocenjen le bodisi i ali bodisi j
df_uniq = df_f.drop_duplicates(subset="userID", keep=False)
# se je userid iz org df_f v uniq, potem ma ta le en vnos in takih nočemo
df_f = df_f.loc[np.logical_not(df_f["userID"].isin(df_uniq["userID"])), :]
#^tole je tko 100kratna pohitritev vsaj v primerjavi z filtriranjem groupby objetka
#ce applyam takole filter funkcijo, traja tole namesto skoraj 0, 1/3 sekunde, kar je absolutno preveč tudi za množico filmov z vsaj 1000 ocenami
#groupd_users_df = self.user_data.groupby("userID").filter(lambda x: j in x["movieID"].values and i in x["movieID"].values)
#groupd_users_df = df_f.groupby("userID")#.filter(lambda x: len(x)==2) #tako sem selekcioniral prej data, da ce bo grupa bila dolzine 2, bo imela i oceno in j oceno, drugace ne
#to je sedaj 3x hitreje kot prejsnji nacin filtra
#print([len(g) for _,g in groupd_users_df])
# izberem ocene userjev za j
users_df_j = df_f.loc[df_f["movieID"] == j, "rating"]
#izberem ocene userjev za i
users_df_i = df_f.loc[df_f["movieID"] == i, "rating"]
#koncni izracun sum po userjih razlik ocen za j in i, nato pa sum normaliziran s stevilom userjev, ki je dalo ocene obema
dev = np.sum((users_df_j.values - users_df_i.values))/len(users_df_j)
#vrni dev vrednosti in stevilo uporabnikov, ki so ocenil i in j
return (dev, len(users_df_j))
#TEST
"""md = MovieData('saved_movie')
uim = UserItemData('saved_user')
rp = SlopeOnePredictor()
rec = Recommender(rp)
rec.fit(uim)
#rp.dif(32, 110)
print("Predictions for 78: ")
rec_items = rec.recommend(78, n=15, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
class HybridPredictor:
#uporabi slopeone, itembased in najbolje ocenjeno - average predictor???
def __init__(self, min_values=0, threshold=0, b=0):
self.mv = min_values
self.th = threshold
self.b = b
self.item_based = ItemBasedPredictor(self.mv, self.th)
self.slope = SlopeOnePredictor()
self.avg = AveragePredictor(self.b)
def fit(self, x):
self.user_data = x.data_frame
self.item_based.fit(x)
self.slope.fit(x)
self.avg.fit(x)
def predict(self, user_id):
pred_ib = self.item_based.predict(user_id)
pred_s = self.slope.predict(user_id)
pred_avg = self.avg.predict(user_id)
napovedi_final = dict()
#le navadno povprecje vseh napovedi
for film_id in pred_ib:
napovedi_final[film_id] = (pred_ib[film_id] + pred_s[film_id] + pred_avg[film_id])/3
return napovedi_final
#TEST
"""print("\nTest hibrid")
md = MovieData('saved_movie')
uim = UserItemData('saved_user')
rp = HybridPredictor()
rec = Recommender(rp)
rec.fit(uim)
#rp.dif(32, 110)
print("Predictions for 78: ")
rec_items = rec.recommend(78, n=15, rec_seen=False)
for idmovie, val in rec_items:
print("Film: {}, ocena: {}".format(md.get_title(idmovie), val))"""
#TEST RECOMMENDER EVALUATE METHOD
#md = MovieData('data/movies.dat')
#uim = UserItemData('data/user_ratedmovies.dat', min_ratings=1000, end_date='1.1.2008')
#uim_test = UserItemData('data/user_ratedmovies.dat', min_ratings=200, start_date='2.1.2008')
#SLOPE
#rp = SlopeOnePredictor()
#rec = Recommender(rp)
#rec.fit(uim)
#time1 = time.time()
#mse, mae, prec, rec, f = rec.evaluate(uim_test, 20)
#print("time:", time.time()-time1)
#print("SLOPE ONE", mse, mae, prec, rec, f)
#ITEM BASED
"""rp = ItemBasedPredictor()
rec = Recommender(rp)
rec.fit(uim)
#mse, mae, precision, recall, f = rec.evaluate(uim_test, 20)
time1 = time.time()
mse, mae, prec, rec, f = rec.evaluate(uim_test, 20)
print("time:", time.time()-time1)
print("Item Based predictor", mse, mae, prec, rec, f)
#HybridPredictor
rp = HybridPredictor()
rec = Recommender(rp)
rec.fit(uim)
#mse, mae, precision, recall, f = rec.evaluate(uim_test, 20)
time1 = time.time()
mse, mae, prec, rec, f = rec.evaluate(uim_test, 20)
print("time:", time.time()-time1)
print("Hybrid", mse, mae, prec, rec, f)"""
#time: 50.384504079818726
#SLOPE ONE 0.737326152762 0.631210888812 0.18542510880173224 0.08135736372584979 0.1130936217642868
#time: 19.427669286727905
#Item Based predictor 1.01097199952 0.697650666469 0.2000134266029619 0.08813128088987293 0.12235129796461698
#time: 68.26756930351257
#Hybrid 0.684466698395 0.611345915174 0.19345986304860757 0.0871357496992999 0.12015348378671341
#BEST
#Hybrid 0.684466698395 0.611345915174 0.19345986304860757 0.0871357496992999 0.12015348378671341
#Povezovalna pravila (8)
""" ČE ti je všeč film X in če ti je všeč film Y, POTEM ti bo zelo verjetno všeč tudi film Z.
Ker so povezovalna pravila namenjena podatkom z implicitnimi ocenami (dogodki), a pri filmih
imamo eksplicitne vrednosti, moramo le-te najprej spremeniti v dogodke. Predlagam, da za dogodek označimo
vse ocene višje od uporabnikovega povprečja. """
#uim = UserItemData('data/user_ratedmovies.dat')
#movies = MovieData('data/movies.dat')
#uim.save_data()
#movies.save_data()
#print("done")
#Moram narediti stolpce za film ID in ? tam kjer stolpec nima vrednosti in 1 tam kjer jo ima
#Vsaka vrstica so userji
data_frame = UserItemData('saved_user').data_frame
mobject = MovieData('saved_movie')
movie_frame = mobject.data_frame
user = data_frame.groupby("userID")
avg_user = user["rating"].mean() #average ocena za vsakega userja
indeksi = list(data_frame["userID"].unique()) #user IDs, zaradi lepsega v list
def asocpravila():
#filmi ki so ocenjeni
film_id = data_frame.groupby("movieID")
columns_names = list(film_id.groups.keys()) #imena grup, prvo dobim od grup kljuce oz idije, nato pretvorim v list
#ker je filmov okoli 10k, njhivoi indeksi pa gredo do 60k, je smiselno, da pretvorim ta njihov id v indeks do 10k in tega uporabim v matriki
def movie_id2indeks(movie_id):
return movie_frame.loc[movie_frame["id"].isin(movie_id), "id"].index.values #vrne indeks
print("indeks filam s tem idijem 65133, to +1 je tudi stevilo stolpecv ", movie_id2indeks([65133]))
maksimalni = np.max(columns_names)
st_stolpcev = movie_id2indeks([maksimalni]) +1
matrika = pd.DataFrame(np.zeros((len(indeksi), st_stolpcev[0])), index=indeksi, columns=movie_frame["id"]) #inekdsi len so vrstica, len columnsi so stolpci
vrstica_filmi = dict() #ne bi bilo potrebno dicta vzet (: sam sej ni pomembn vrstni red, tako da vseeno, vazno je le da filme naprave
for u_i in indeksi:
vrstica_filmi[u_i] = data_frame.loc[np.logical_and(data_frame["userID"] == u_i, data_frame["rating"] > avg_user[u_i]), "movieID"].values #tam kjer se user id ujema in da je rating večji od avg user ratinga, vzemi ven
#seznamsek vrednosti film idov
t1 = time.time()
for i, u_i in enumerate(vrstica_filmi):
# TUKAJ MATRIKA.ILOC, NASTAVIM TISTE STOLPCE NA 1, KJER SO FILMI , to ubistvu tukaj deluje kot indeks, vsak film je svoj indeks. ker pa je film indeks mnogo vecji od
#filmi = np.apply_along_axis(movie_id2indeks, axis=0, arr=vrstica_filmi[u_i]) #dam vrstico filmov na u_i in ta vrne array id indeksov, skratka vrstica filmov se preslika v vrstico film indeksov
filmi = vrstica_filmi[u_i]
matrika.loc[u_i, filmi] = 1 #nastavim na gledano
print(time.time()-t1)
print("toystory number of ones", np.sum(matrika.iloc[:, 0]))
print("Pred", matrika.shape)
for col in matrika.columns:
if matrika[col].sum()/matrika.shape[0] < 0.3:
matrika = matrika.drop(col, 1)
matrika[matrika==0] = np.nan
print("Po removu samih praznih?", matrika.shape)
matrika.to_csv("/users/js5891/Desktop/matrika.csv")
#Izpišite pravilo z največjo podporo, zaupanjem in dvigom.
#asocpravila()
def rezultat():
print(mobject.get_title(4993)+" -> "+mobject.get_title(5952))
print("Supp:", 0.522, "Conf:",0.879, "Lift:",1.567)
print(mobject.get_title(7153)+" -> "+mobject.get_title(5952))
print("Supp:", 0.502, "Conf:",0.895, "Lift:",1.610)
#rezultat()
"""
The apriori principle can reduce the number of itemsets we need to examine. Put simply, the apriori principle states that if an itemset is infrequent,
then all its subsets must also be infrequent. This means that if {beer} was found to be infrequent, we can expect {beer, pizza} to be equally or even more infrequent.
So in consolidating the list of popular itemsets, we need not consider {beer, pizza}, nor any other itemset configuration that contains beer.
"""
# VIZUALIZACIJA DISTANC
# * MATRIKA RAZDALJ MED FILMI
#Če so naši elementi filmi, potem so to razdalje med filmi, ki jih lahko izračunamo npr. kot 1 - kosinusna podobnost
# potem daj v orange in shrani par vizualizacij ;)
class visualDistance:
def __init__(self, df, mf):
self.user_data = df
self.mf = mf
def matrikaRazdalj(self):
# vzemi le filme, ki imajo več kot 1000 ocen
grouped = list(self.user_data.groupby("movieID").apply(lambda x: x.name)) #get names
#make matrix
film_matrika = pd.DataFrame(np.zeros((len(grouped), len(grouped))))
for i in range(len(grouped)):
for j in range(len(grouped)):
podobnost = self.podobonost(grouped[i], grouped[j])
film_matrika.iloc[i, j] = 1 - podobnost
film_matrika.iloc[j, i] = 1 - podobnost
return film_matrika
def podobonost(self, p1, p2):
#Podobnost izračunajte s popravljeno cosinusno razdaljo
#Če je izračunana podobnost med produktoma manjša od threshold ali če imamo manj
#kot min_values uporabnikov, ki je ocenilo oba filma, naj bo podobnost med produktoma 0
#self.user_data = uim.data_frame
#pridobi vektorje, p1 in p2 sta enako dolga, najdi le skupne uporabnike in jih zlozi istolezno
first_data = self.user_data.loc[self.user_data["movieID"] == p1, ["userID", "rating"]]
second_data = self.user_data.loc[self.user_data["movieID"] == p2, ["userID", "rating"]]
#presek userjev, ki so v obeh
users = np.intersect1d(first_data["userID"], second_data["userID"])
if len(users) <= 0:
return 0
#dobim skupine userjev, ki so ocenili oba produkta. So zdruzene vrstice po userjih, torej, ce je user1 ocenil oba filma, je tukaj notri v svoji skupini in predstavlja n vrstic
grupe_user = self.user_data.groupby("userID")
#izracunam povprecje na skupino(user), njegova povprecna ocena
povprecje_user = grupe_user["rating"].agg(np.mean)
#ker ni uporabljen iloc, je to vbistvu po imenu vrstice, in to je indeks!!!
#print(povprecje_user[78])
# filtriram tiste userje, ki niso prisotni v obeh filmih
#dam se v numpy, ker ce odstejem pandas series od series dobim napacen rezultat
povprecje_user = np.array(povprecje_user[povprecje_user.index.isin(users)])
#vektorji
#avg_user = np.mean(self.user_data.loc[self.user_data["userID"].isin(users), "rating"])
# ne pozabi odšteti user bias-a od ratinga, torej odštej od ratinga povprečno user oceno
p1v = first_data.loc[first_data["userID"].isin(users), "rating"] - povprecje_user #ce bi bil scalar, sepravi da bi selecta eno vrednost, bi vrednost izluscil z .item()
p2v = second_data.loc[second_data["userID"].isin(users), "rating"] - povprecje_user
#dot
p1p2 = np.dot(np.array(p1v), np.array(p2v))
#dolzini
lengthp1 = np.sqrt(np.dot(p1v, p1v))
lengthp2 = np.sqrt(np.dot(p2v, p2v))
sim = p1p2 / (lengthp1 * lengthp2)
if sim < 0:
return 0
return sim
def save2orange(self):
import Orange.misc.distmatrix
matrika = self.matrikaRazdalj()
o = Orange.misc.distmatrix.DistMatrix(matrika)
o.save("/users/js5891/Desktop/orange_razd.dst")
matrika.to_csv("/users/js5891/Desktop/film_matrika.dst")
#df = UserItemData("data/user_ratedmovies.dat", min_ratings=1000).data_frame
#mf = MovieData("data/movies.dat").data_frame
#df = UserItemData('saved_user').data_frame
#mf= MovieData('saved_movie').data_frame
#vd = visualDistance(df, mf)
#vd.save2orange()
"""
1)
*Inkrementalno testiranje in prečno preverjanje (cross-validation)
Namesto enkratnega testiranja je bolje, če razdelitev in ocenjevanje večkrat ponovimo. Pri prečnem preverjanju
vse ocene razdelimo v nekaj delov (angl. fold). Npr. predpostavimo, da ocene razdelimo na deset delov. Potem desetkrat
ponovimo učenje na devetih delih in testiranje na desetem (vsakič drugem). Končne vrednosti
statistik so povprečne vrednosti čez posamezna testiranja.
Inkrementalno testiranje je najboljši približek delovanja realnega sistema, kadar imamo na voljo datume dogodkov (npr. ocen).
Najprej izberemo začetni datum in se učimo samo na ocenah do tega datuma. Ta sistem testiramo na nekem oknu, npr. ocenah,
ki so bile oddane v naslednjem tednu. Potem vključimo te ocene v učno množico in testiramo na tednu, ki sledi. To ponavljamo,
dokler ne zmanjka ocen. Na koncu rezultate povprečimo. """
#rabil bom evaluate metodo
def predikt(uim, uim_test):