Mini projekat: sistem preporuka
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 princ | Hajdi | Pinokio | Robinzon Kruso | Orlovi rano lete | Alisa u zemlji čuda | |
|---|---|---|---|---|---|---|
| Ana | 5 | 4 | 2 | 1 | – | – |
| Marko | – | 1 | 2 | 5 | 5 | – |
| Jelena | 5 | 5 | – | – | 2 | 4 |
| Luka | 2 | – | – | 4 | 4 | 2 |
| Sara | 4 | 4 | 3 | – | – | 5 |
| Nikola | – | – | 4 | 5 | 4 | – |
Ideja
- 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.
- 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“.
- 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.
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.
- Dodaj sebe u rečnik
ocenesa ocenama tri knjige i ispiši preporuke za sebe. - Promeni granicu sličnosti sa 0,5 na 0,3. Šta se menja u preporukama za Anu?
- 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?
