Mini projekat: sistem preporuka

Modul 3 · Lekcija 6 od 7 · Veštačka inteligencija

YouTube, prodavnice i aplikacije za muziku stalno nam predlažu šta dalje. Kako znaju šta bi nam se dopalo? Jedna od osnovnih ideja je: preporuči mi ono što se dopalo ljudima sličnog ukusa. U ovom projektu pravimo sistem koji učenicima preporučuje knjige iz lektire.

Podaci: ocene knjiga

Šest učenika ocenilo je knjige ocenama od 1 do 5. Knjiga koje nema u nečijem spisku – taj učenik je nije pročitao. Ovakvi podaci se u Pythonu lepo čuvaju kao rečnik rečnika.

Mali princHajdiPinokioRobinzon KrusoOrlovi rano leteAlisa u zemlji čuda
Ana5421––
Marko–1255–
Jelena55––24
Luka2––442
Sara443––5
Nikola––454–

Ideja

  1. Sličnost dva učenika: pogledamo knjige koje su obojica ocenili i izračunamo prosečnu razliku ocena. Razlika 0 → sličnost 1 (isti ukus), razlika 4 → sličnost 0.
  2. Preporuka: za svaku knjigu koju učenik nije pročitao gledamo ocene sličnih učenika (sličnost veća od 0,5). Očekivana ocena je prosek njihovih ocena, pri čemu sličniji učenik „vredi više“.
  3. Preporučujemo knjige sa najvećom očekivanom ocenom.

Program

# Sistem preporuka: koje knjige da pročita učenik, na osnovu ocena sličnih učenika
import math

# ocene od 1 do 5; knjiga koje nema u rečniku učenik nije pročitao
ocene = {
    "Ana":    {"Mali princ": 5, "Hajdi": 4, "Pinokio": 2, "Robinzon Kruso": 1},
    "Marko":  {"Robinzon Kruso": 5, "Orlovi rano lete": 5, "Pinokio": 2, "Hajdi": 1},
    "Jelena": {"Mali princ": 5, "Hajdi": 5, "Alisa u zemlji čuda": 4, "Orlovi rano lete": 2},
    "Luka":   {"Orlovi rano lete": 4, "Robinzon Kruso": 4, "Alisa u zemlji čuda": 2, "Mali princ": 2},
    "Sara":   {"Mali princ": 4, "Alisa u zemlji čuda": 5, "Hajdi": 4, "Pinokio": 3},
    "Nikola": {"Robinzon Kruso": 5, "Pinokio": 4, "Orlovi rano lete": 4},
}


def slicnost(a, b):
    """Koliko su dva učenika slična (od 0 do 1), samo po knjigama koje su obojica ocenili."""
    zajednicke = [k for k in ocene[a] if k in ocene[b]]
    if not zajednicke:
        return 0
    # prosečna razlika u ocenama: 0 = isto ocenjuju, 4 = potpuno različito
    razlika = sum(abs(ocene[a][k] - ocene[b][k]) for k in zajednicke) / len(zajednicke)
    return 1 - razlika / 4


def preporuci(ucenik):
    """Za svaku nepročitanu knjigu računa očekivanu ocenu iz ocena sličnih učenika."""
    zbir = {}          # knjiga -> zbir (sličnost · ocena)
    tezina = {}        # knjiga -> zbir sličnosti
    for drugi in ocene:
        if drugi == ucenik:
            continue
        s = slicnost(ucenik, drugi)
        if s <= 0.5:                       # gledamo samo dovoljno slične učenike
            continue
        for knjiga, ocena in ocene[drugi].items():
            if knjiga not in ocene[ucenik]:
                zbir[knjiga] = zbir.get(knjiga, 0) + s * ocena
                tezina[knjiga] = tezina.get(knjiga, 0) + s
    procena = {k: zbir[k] / tezina[k] for k in zbir}
    return sorted(procena.items(), key=lambda p: -p[1])


for drugi in ocene:
    if drugi != "Ana":
        print(f"sličnost Ana – {drugi}: {slicnost('Ana', drugi):.2f}")

print("Preporuke za Anu:")
for knjiga, ocena in preporuci("Ana"):
    print(f"  {knjiga}: očekivana ocena {ocena:.1f}")

print("Preporuke za Marka:")
for knjiga, ocena in preporuci("Marko"):
    print(f"  {knjiga}: očekivana ocena {ocena:.1f}")

Ispis programa:

sličnost Ana – Marko: 0.42
sličnost Ana – Jelena: 0.88
sličnost Ana – Luka: 0.25
sličnost Ana – Sara: 0.83
sličnost Ana – Nikola: 0.25
Preporuke za Anu:
  Alisa u zemlji čuda: očekivana ocena 4.5
  Orlovi rano lete: očekivana ocena 2.0
Preporuke za Marka:
  Alisa u zemlji čuda: očekivana ocena 2.0
  Mali princ: očekivana ocena 2.0
  • Ana je najsličnija Jeleni i Sari (vole Malog princa i Hajdi). Obe su Alisu u zemlji čuda ocenile visoko – pa je to prva preporuka za Anu.
  • Marko voli avanture (Robinzon Kruso, Orlovi rano lete); njemu slični učenici Alisu i Malog princa nisu ocenili visoko, pa su očekivane ocene niske – sistem mu zapravo ne preporučuje ništa posebno.
  • Ovo je jednostavna verzija postupka koji se zove kolaborativno filtriranje. Pravi sistemi imaju milione korisnika i koriste i druge podatke (koliko dugo si gledao, šta si preskočio).

O čemu treba razmisliti

  • Novi korisnik nema nijednu ocenu, pa ne znamo kome je sličan – to se zove problem „hladnog starta“ (zadatak 3).
  • „Mehur“: ako ti se uvek preporučuje samo ono što je slično onome što već voliš, retko ćeš otkriti nešto sasvim novo. Dobri sistemi namerno ubace i poneku drugačiju preporuku.
  • Privatnost: ocene i navike govore mnogo o čoveku. Kod pravih sistema korisnici treba da znaju koji se podaci koriste.
Za čas (nastavnicima): trajanje 45–90 minuta; pribor: računari.
Odeljenje pravi svoju tabelu ocena (knjige, filmovi ili igre), unosi je u program i proverava da li su preporuke „pogodile“. Diskusija o mehuru i privatnosti.

Česte zablude

  • „Preporuke prave ljudi koji te poznaju“ – prave ih programi na osnovu ponašanja mnogo korisnika.
  • „Ako ti nešto preporuči, to je najbolje za tebe“ – preporuka je samo procena na osnovu sličnih korisnika – i može da te zatvori u „mehur“.

Zadaci za vežbu

Pokušaj prvo sam, pa tek onda otvori rešenje.

  1. Dodaj sebe u rečnik ocene sa ocenama tri knjige i ispiši preporuke za sebe.
  2. Promeni granicu sličnosti sa 0,5 na 0,3. Šta se menja u preporukama za Anu?
  3. Napiši program koji novom učeniku (bez ocena) preporučuje knjige po prosečnoj oceni svih učenika.
Rešenje 1: moje preporuke

U rečnik dodaj red, npr.: "Ja": {"Mali princ": 4, "Robinzon Kruso": 5, "Pinokio": 3}, a na kraju programa: print(preporuci("Ja")). Za ove ocene sistem preporučuje Alisu u zemlji čuda (oko 3,9), Orlove rano lete (3,8) i Hajdi (oko 3,3).

Rešenje 2: druga granica

Zameni if s <= 0.5: sa if s <= 0.3:. Sada se računa i Marko (sličnost 0,42), pa Orlovi rano lete dobijaju očekivanu ocenu oko 3,0 umesto 2,0, a Alisa ostaje prva (oko 4,5). Niža granica znači više „glasova“, ali i manje slične učenike.

Rešenje 3: hladni start
# Zadatak: preporuka za novog učenika koji još nije ocenio nijednu knjigu
# (ne znamo kome je sličan, pa preporučujemo knjige sa najboljom prosečnom ocenom)
ocene = {
    "Ana":    {"Mali princ": 5, "Hajdi": 4, "Pinokio": 2, "Robinzon Kruso": 1},
    "Marko":  {"Robinzon Kruso": 5, "Orlovi rano lete": 5, "Pinokio": 2, "Hajdi": 1},
    "Jelena": {"Mali princ": 5, "Hajdi": 5, "Alisa u zemlji čuda": 4, "Orlovi rano lete": 2},
    "Luka":   {"Orlovi rano lete": 4, "Robinzon Kruso": 4, "Alisa u zemlji čuda": 2, "Mali princ": 2},
    "Sara":   {"Mali princ": 4, "Alisa u zemlji čuda": 5, "Hajdi": 4, "Pinokio": 3},
    "Nikola": {"Robinzon Kruso": 5, "Pinokio": 4, "Orlovi rano lete": 4},
}

sve = {}                                   # knjiga -> lista svih ocena
for ucenik in ocene.values():
    for knjiga, ocena in ucenik.items():
        sve.setdefault(knjiga, []).append(ocena)

prosek = {k: sum(o) / len(o) for k, o in sve.items()}
for knjiga, p in sorted(prosek.items(), key=lambda x: -x[1]):
    print(f"{knjiga:22} prosek {p:.1f} ({len(sve[knjiga])} ocena)")

Ispis programa:

Mali princ             prosek 4.0 (4 ocena)
Robinzon Kruso         prosek 3.8 (4 ocena)
Orlovi rano lete       prosek 3.8 (4 ocena)
Alisa u zemlji čuda    prosek 3.7 (3 ocena)
Hajdi                  prosek 3.5 (4 ocena)
Pinokio                prosek 2.8 (4 ocena)

Kada o korisniku ne znamo ništa, preporučujemo ono što je popularno – kao spisak „najgledanije“ na sajtovima.

Proveri znanje

Otvori kviz

Kviz: sistem preporuka

1. Na osnovu čega sistem preporučuje knjige?




2. Šta je problem „hladnog starta“?




3. Kako se zove ovaj postupak preporuka?




4. Šta je „mehur“ kod preporuka?