Advertisement
jordanov

СНЗ - Системи за препорака испитна

Sep 1st, 2021
2,084
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 57.99 KB | None | 0 0
  1. '''Во променливата songs_ratings дадени се оценки на 300 корисници за 19 песни. Оцените се во ранг од 0 до 10.
  2. Податочното множество треба да се исфилтрира, така што ќе се отстранат корисниците кои најдобрата песна ја оцениле
  3. со оцена помала од просечната оцената на песната. Просечната оцена на дадена песна се пресметува како вредноста на
  4. сумата на оцените од корисниците, поделена со бројот на корисници кои оставиле оцена за дадената песна. Најдобрата
  5. песна е песната која што има најголема просечна оцена.
  6.  
  7. Потребно е да се направи препорака на песни за нов корисник test_user_id. За овој корисник оцените за песните се
  8. читаат од стандарден влез во променливата test_user_ratings. Потоа, потребно е да се направи препорака на 5 песни
  9. преку препорака од корисници (user-based) со Евклидово растојание, препорака од корисници (user-based) со Пирсонова
  10. корелација и препорака од песни (item-based). На стандарден излез да се испечатат песните кои се препорачуваат со
  11. сите три начини на препорака (сортирани по растечки алфанумерички редослед), заедно со нивната просечна оцена, како
  12. и вредностите за препорака од сите три методи. Доколку нема препорачани песни на стандарден излез треба да се испечати
  13. 'Nema preporachani pesni'
  14.  
  15.  
  16.  
  17. Sample input
  18. 129
  19. 1: 0, 2: 5, 3: 0, 4: 1, 5: 2, 6: 5, 7: 4, 8: 4, 9: 2
  20. Sample output
  21. Preporacani pesni:
  22. Pesna 11, prosecna ocena: 5.82, preporaka so evklidovo rastojanie: 6.183104258859523, preporaka so pirsonova korelacija: 6.246043501983225, preporaka preku pesni: 0.6361729114217167
  23. Pesna 12, prosecna ocena: 5.92, preporaka so evklidovo rastojanie: 6.46977823184295, preporaka so pirsonova korelacija: 6.370226152101855, preporaka preku pesni: 0.568420986600022'''
  24.  
  25.  
  26. from math import sqrt
  27.  
  28.  
  29. def sim_distance(prefs, person1, person2):
  30.     """
  31.    Враќа мерка за сличност базирана на растојание помеѓу person1 и person2
  32.    :param prefs: речник со оцени од корисници
  33.    :param person1: име на корисник1
  34.    :param person2: име на корисник2
  35.    :return: сличност помеѓу корисник1 и корисник2
  36.    """
  37.     # Се прави листа на заеднички предмети
  38.     si = {}
  39.     for item in prefs[person1]:
  40.         if item in prefs[person2]:
  41.             si[item] = 1
  42.     # Ако немаат заеднички рејтинзи, врати 0
  43.     if len(si) == 0:
  44.         return 0
  45.     # Собери ги квадратите на сите разлики
  46.     sum_of_squares = sum([pow(prefs[person1][item] - prefs[person2][item], 2)
  47.                           for item in prefs[person1] if item in prefs[person2]])
  48.     return 1 / (1 + sqrt(sum_of_squares))
  49.  
  50.  
  51. def sim_pearson(prefs, p1, p2):
  52.     """
  53.    Го враќа коефициентот на Пирсонова корелација помеѓу p1 и p2 (личност1 и личност 2).
  54.    Вредностите се помеѓу -1 и 1
  55.    :param prefs: речник со оцени од корисници
  56.    :param p1: име на корисник1
  57.    :param p2: име на корисник2
  58.    :return: сличност помеѓу корисник1 и корисник2
  59.    """
  60.     # Се креира речник во кој ќе се чуваат предметите кои се оценети од двајцата
  61.     # Во речникот ни се важни само клучевите за да ги чуваме имињата на филмовите
  62.     # кои се заеднички, а вредностите не ни се важни
  63.     si = {}
  64.     for item in prefs[p1]:
  65.         if item in prefs[p2]:
  66.             si[item] = 1
  67.  
  68.     # Се пресметува бројот на предмети оценети од двајцата
  69.     n = len(si)
  70.  
  71.     # Ако немаат заеднички предмети, врати корелација 0
  72.     if n == 0:
  73.         return 0
  74.  
  75.     # Собери ги сите оцени за секоја личност посебно
  76.     sum1 = sum([prefs[p1][it] for it in si])
  77.     sum2 = sum([prefs[p2][it] for it in si])
  78.     # Собери ги квадратите од сите оцени за секоја личност посебно
  79.     sum1Sq = sum([pow(prefs[p1][it], 2) for it in si])
  80.     sum2Sq = sum([pow(prefs[p2][it], 2) for it in si])
  81.     # Собери ги производите од оцените на двете личности
  82.     pSum = sum([prefs[p1][it] * prefs[p2][it] for it in si])
  83.  
  84.     # Пресметај го коефициентот на корелација
  85.     num = pSum - (sum1 * sum2 / n)
  86.     den = sqrt((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n))
  87.     if den == 0:
  88.         return 0
  89.     r = num / den
  90.     return r
  91.  
  92.  
  93. def top_matches(prefs, person, n=5, similarity=sim_pearson):
  94.     """
  95.    Ги враќа најсличните n корисници за даден корисник.
  96.    :param prefs: речник со оцени од корисници
  97.    :param person: име на корисник
  98.    :param n: број на слични корисници
  99.    :param similarity: метрика за сличност
  100.    :return: листа со најслични n корисници
  101.    """
  102.     scores = [(similarity(prefs, person, other), other)
  103.               for other in prefs if other != person]
  104.     # Се сортира листата во растечки редослед
  105.     scores.sort()
  106.     # Се превртува за најсличните (со најголема вредност) да бидат први
  107.     scores.reverse()
  108.     return scores[0:n]
  109.  
  110.  
  111. def get_recommendations(prefs, person, similarity=sim_pearson):
  112.     """
  113.    Ги враќа препораките за даден корисник со користење на тежински просек
  114.    со оцените од другите корисници
  115.    :param prefs: речник со оцени од корисници
  116.    :param person: име на корисник
  117.    :param similarity: метрика за сличност
  118.    :return: препораки за даден корисник
  119.    """
  120.     totals = {}
  121.     simSums = {}
  122.     for other in prefs:
  123.         # За да не се споредува со самиот себе
  124.         if other == person:
  125.             continue
  126.         sim = similarity(prefs, person, other)
  127.         # не се земаат предвид резултати <= 0
  128.         if sim <= 0:
  129.             continue
  130.         for item in prefs[other]:
  131.             # за тековниот корисник ги земаме само филмовите што ги нема гледано
  132.             if item not in prefs[person] or prefs[person][item] == 0:
  133.                 # Similarity * Score
  134.                 totals.setdefault(item, 0)
  135.                 totals[item] += prefs[other][item] * sim
  136.  
  137.                 # Сума на сличности
  138.                 simSums.setdefault(item, 0)
  139.                 simSums[item] += sim
  140.  
  141.     # Креирање на нормализирана листа со рејтинзи
  142.     rankings = [(total / simSums[item], item) for item, total in totals.items()]
  143.  
  144.     # Сортирање на листата во растечки редослед. Превртување на листата за најголемите вредности да бидат први
  145.     rankings.sort(reverse=True)
  146.  
  147.     return rankings
  148.  
  149.  
  150. def get_recommendations_item_based(inverted_prefs, person):
  151.     """
  152.    Ги враќа препораките за даден корисник со користење на тежински просек
  153.    со оцените од предметите
  154.    :param inverted_prefs: инвертиран речник со оцени од корисници, item-based
  155.    :param person: име на корисник
  156.    :return: препораки за даден корисник
  157.    """
  158.     similarity_per_item = {}
  159.     person_items = [item for item, values in inverted_prefs.items() if person in values.keys()]
  160.     for item in person_items:
  161.         similar_items = top_matches(inverted_prefs, item, n=None)
  162.         my_rating = inverted_prefs[item][person]
  163.         for similarity, item in similar_items:
  164.             if person in inverted_prefs[item] or similarity <= 0:
  165.                 continue
  166.             similarity_per_item.setdefault(item, [])
  167.             similarity_per_item[item].append(similarity * my_rating)
  168.  
  169.     # Креирање на нормализирана листа со рејтинзи
  170.     similarity_per_item_avg = [(sum(similarity_per_item[item]) / len(similarity_per_item[item]), item) for item in
  171.                                similarity_per_item]
  172.     similarity_per_item_avg.sort(reverse=True)
  173.  
  174.     return similarity_per_item_avg
  175.  
  176.  
  177. def transform_prefs(prefs):
  178.     """
  179.    Ги трансформира рејтинзите така што клучеви ќе бидат филмовите,
  180.    а вредност ќе биде листа со рејтинзи од секој корисник
  181.    :param prefs: речник со оцени од корисници
  182.    :return: инвертиран речник со оцени од корисници
  183.    """
  184.     result = {}
  185.     for person in prefs:
  186.         for item in prefs[person]:
  187.             result.setdefault(item, {})
  188.             # Замени ги местата на корисникот и предметот
  189. '__main__':
  190.     test_user_id = int(input())
  191.     test_user_ratings = {int(x.split(': ')[0]): int(x.split(': ')[1]) for x in input().split(', ')}
  192.    
  193.     inverted = transform_prefs(songs_ratings)                          # najprvo kje go prevrtime setot za polesno da dojdeme do najdobrata prosecna pesna
  194.     maks = sum(inverted[1].values()) / len(inverted[1].values())       # na pocetokot inicijalno maks go postavuvame na prosecnata ocena na prvata pesna
  195.     best_song = None                                                    # tuka kje ja zacuvame pesnata so najdobra prosecna ocena
  196.     avg_songs_ratings = {}                                              # vo ovoj recnik kje gi cuvame prosecnite oceni na sekoja pesna bidejki ni treba toa vo printot
  197.     for song in inverted.keys():                                           # iterirame pesna po pesna
  198.         avg = sum(inverted[song].values()) / len(inverted[song].values())   # naogjame prosecna ocena na pesnata
  199.         avg_songs_ratings[song] = round(avg, 2)                            # ja zapisuvame soodvetno vo recnikot so prosecni oceni za sekoja pesna posebno
  200.         if avg > maks:                                                     # proveruvame dali taa pesna do sega ima najgolema prosecna ocena
  201.             maks = avg                                                     # ja zacuvuvame taa vrednost
  202.             best_song = song                                               # ja zacuvuvame i pesnata koja do togas ja ima najdobrata prosecna ocena
  203.  
  204.     for user in list(songs_ratings.keys()):                                # sega gi izminuvame site korisnici
  205.         if songs_ratings[user][best_song] < maks:                          # proveruvame koj od korisnicite pesnata so najdobra prosecna ocena ja ocenile so ocena pomala od taa vrednost
  206.             songs_ratings.pop(user)                                       # gi otstranuvame takvite korisnici
  207.  
  208.     songs_ratings[test_user_id] = test_user_ratings                          # go dodavame noviot korisnik soodvetno so ocenkite koi toj gi dal za site pesni
  209.     inverted = transform_prefs(songs_ratings)                                # go prevrtuvame setot povtorno po dodavanjeto, ova ni treba za da napravime item_based preporaka
  210.  
  211.     rec_e = get_recommendations(songs_ratings, test_user_id, sim_distance)    # user_based preporaka so ekvildovo rastojanie
  212.     rec_p = get_recommendations(songs_ratings, test_user_id, sim_pearson)    # user_based preporaka so pirsonova korelacija
  213.     rec_i = get_recommendations_item_based(inverted, test_user_id)            # item_based preporaka
  214.  
  215.     songs_e = {tup[1] for tup in rec_e}                                       # id na sekoja pesna od torkite na sekoja preporaka go zemame i stavame vo set
  216.     songs_p = {tup[1] for tup in rec_p}                                       # ova go pravime bidejki so pomosh na set lesno kje dojdeme do zaednickite preporaki
  217.     songs_i = {tup[1] for tup in rec_i}                                         # odnosno do pesnite koi gi ima vo trite poraki so pomosh na intersection
  218.  
  219.     set1 = songs_e.intersection(songs_p)                                      # presekot na onie so ekvildovo rastojanie i onie so pirsonova korelacija t.e zaednickite koi gi ima vo dvete preporaki
  220.     common_songs = set1.intersection(songs_i)                               # na rezultatot od toj presek mu pravime nov presek so onie koi se vo item_based preporakata i gi dobivame pesnite koi se zaednicki za site tri preporaki
  221.  
  222.     rec_e = [tup for tup in rec_e if tup[1] in common_songs]
  223.     rec_p = [tup for tup in rec_p if tup[1] in common_songs]                # vo samite preporaki gi ostavame samo torkite koi sodrzat id na pesna koe se naogja vo site tri preporaki t.e e zaednicka za site preporaki
  224.     rec_i = [tup for tup in rec_i if tup[1] in common_songs]
  225.  
  226.     rec_e.sort(key=lambda x: x[1])
  227.     rec_p.sort(key=lambda x: x[1])                                         # gi sortirame preporakite "alfanumericki" t.e vo rastecki redosled po id na pesna
  228.     rec_i.sort(key=lambda x: x[1])
  229.  
  230.     if len(common_songs) == 0:                                          # dokolku nemalo takvi pesni koi se preporacani so site tri preporaki naednas smetame deka nema preporacani pesni
  231.         print("Nema preporachani pesni")
  232.     else:
  233.         print("Preporacani pesni:")
  234.         i = 0
  235.         while True:
  236.             if i == len(rec_e) or i == 5:                               # dokolku sme ispecatile 5 preporacani pesni ili pak sme dostignale maksimum kolku sto imalo zaednicki preporacani (dokolku bile pomalku od 5) togash zavrsuvame so pecatenje
  237.                 break                                                   # moze da proveruvame so dolzinata na bilo koja preporaka bidejki sekako sekoja od niv ima ist broj torki vnatre t.e onolku kolku sto bile zaednicki pesni vo site tri preporaki
  238.                
  239.             print(f"Pesna {rec_e[i][1]}, prosecna ocena: {avg_songs_ratings[rec_e[i][1]]}, preporaka so evklidovo rastojanie: "
  240.                   f"{rec_e[i][0]}, preporaka so pirsonova korelacija: {rec_p[i][0]}, preporaka preku pesni: {rec_i[i][0]}")
  241.             i += 1
  242.  
  243.  
  244.  
  245.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement