---
title: 'Öneri Problemi'
source: 'https://academia.sh/tr/kurslar/pekistirmeli-ve-oneri/oneri-problemi'
course: 'Pekiştirmeli Öğrenme ve Öneri Sistemleri'
language: tr
updated: '2026-08-17T18:10:16+00:00'
license: 'CC BY-SA 4.0'
---

# Öneri Problemi

Kurgu abone-kalem etkileşim dizeyinde önerinin ölçülmesi: 360 abone ve 24 hizmet kalemi 8.640 hücrenin yalnız 577'sinde buluşur, seyreklik 0,9332'dir ve 80 abonenin hiçbir etkileşimi yoktur. Taban çizgisi eğitimde en çok seçilen kalemi herkese sunmaktır; ilk beş isabeti 0,6527, rastgele sıranınki 0,2036'dır. Aynı veri örtük okunduğunda ve açık puanla okunduğunda ilk beş kalemin yalnız biri ortaktır, ortalama puana göre öneri 0,1497 isabet verir, 24 kalemin 16'sının on taneden az puanı vardır ve hiç gösterilmemiş 5.638 hücrenin puan sayısı sıfırdır.

Yedi derste ajan kendi eylemini seçti ve sonucunu ortamdan öğrendi: bir aboneye ekip gönderilince
ne olduğu, gönderilmeyince ne olduğu adım adım kayda geçti. Şimdi eylem el değiştiriyor. Sistemin
yapabildiği tek şey bir aboneye **bir seçenek sunmak**, ve sonucu ancak o abone seçerse görünür.
Sunulmayan seçeneğin sonucu hiç doğmaz; sunulan ama seçilmeyen seçeneğin niçin seçilmediği kayda
geçmez. Toplanan veri, sistemin kendi sunum kararlarının izidir.

Bu ders o düzenin verisini kurar ve iki sayı üretir: herkese aynı kalemi sunan tabanın ilk beş
isabeti, ve aynı veriyi örtük ile açık okumanın ürettiği iki sıralamanın ne kadar ayrıldığı.
Kursun kuralı burada ilk kez ödenir: **gösterilmeyen kalem puanlanmaz, puanlanmayan kalem
gösterilmez.**

- **ON1.** Küme **kurgudur**, tohum 20260218: 360 abone, 24 hizmet kalemi, 8.640 hücre. Kalemler
  bölgesel ölçüm ağının hizmet başvurularıdır (tarife değişikliği, sayaç değişimi, taksitlendirme,
  uzaktan okuma başvurusu gibi).
- **ON2.** Üç olay ayrı ayrı üretilir: **gösterim**, **seçim**, **puan**. Bir kalem gösterilmeden
  seçilemez, seçilmeden puanlanamaz. Zincir kurgunun içine bilerek konmuştur.
- **ON3.** Abonenin bir kaleme gerçek eğilimi üretecin içindedir ve **yalnız ölçüm için** okunur;
  hiçbir yöntem onu görmez.
- **ON4.** **Örtük geri bildirim** seçimin kendisidir ve 0/1'dir. **Açık geri bildirim** 1 ile 5
  arasında bir puandır ve yalnız seçimlerin bir kısmında bırakılır.
- **ON5.** Geçmişte hangi kalemin kime gösterildiği **payına göre yanlıdır**: sık başvurulan kalem
  çok, seyrek olan az sunulmuştur. Bu yanlılık kurguya bilerek konmuştur.
- **ON6.** Ayrılmış küme: en az iki etkileşimi olan her abonenin **son** etkileşimi. Kalanı eğitim
  kümesidir.
- **ON7.** Ölçüt **ilk k isabetidir**: abonenin ayrılmış kalemi ilk k öneri içinde mi. Kesinlik ve
  duyarlılık tanımları M27/K03'te kuruldu ve burada tekrarlanmaz.
- **ON8.** İki taban çizgisi kurulur: **rastgele sıra** ve **eğitimde en çok seçilen kalem sırası**.
  İkincisi asıl tabandır, çünkü hiçbir kişiselleştirme içermez ve yine de kataloğun eğrisinden
  beslenir.

## Etkileşim Dizeyi ve Seyreklik

Dizeyin satırı abone, sütunu hizmet kalemidir. Bir hücrenin dolu olması abonenin o kalemi
seçtiğini söyler; boş olması iki ayrı şeyin karışımıdır ve ayırt edilemez: kalem sunulmuş ama
seçilmemiş olabilir, ya da hiç sunulmamış olabilir. Kurgu bu ikisini ayrı ayrı üretir, böylece
ölçüm sırasında hangisinin ne kadar olduğu sayılabilir.

```python
# ORTAK -- KURGUDUR. Abone x hizmet kalemi etkilesim dizeyi ders icinde kurulur.
# Gosterim, secim ve puan ayri ayri uretilir: bir kalem once GOSTERILIR, sonra
# secilebilir, ancak secilirse puanlanabilir.
TOHUM, M32 = 20260218, 0xFFFFFFFF


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


KALEM = [  # (ad, alan, kanal, ucretli, taahhut, randevu, taban_pay)
    ("otomatik_odeme_talimati", "odeme", "cevrimici", 0, 0, 0, 0.90),
    ("e_fatura_gecisi", "bildirim", "cevrimici", 0, 0, 0, 0.85),
    ("odeme_hatirlatma_sms", "bildirim", "cevrimici", 0, 0, 0, 0.78),
    ("taksitlendirme", "odeme", "sube", 0, 1, 0, 0.62),
    ("uzaktan_okuma_basvurusu", "olcum", "saha", 0, 1, 1, 0.55),
    ("kesinti_uyari_aboneligi", "bildirim", "cevrimici", 0, 0, 0, 0.50),
    ("borc_yapilandirma", "odeme", "sube", 0, 1, 0, 0.44),
    ("adres_guncelleme", "sozlesme", "cevrimici", 0, 0, 0, 0.40),
    ("sayac_degisimi", "olcum", "saha", 1, 0, 1, 0.36),
    ("tuketim_raporu_aboneligi", "bildirim", "cevrimici", 0, 0, 0, 0.32),
    ("fatura_itiraz_kaydi", "odeme", "sube", 0, 0, 0, 0.28),
    ("abonelik_devri", "sozlesme", "sube", 0, 0, 0, 0.25),
    ("kacak_kontrol_randevusu", "olcum", "saha", 0, 0, 1, 0.22),
    ("tesisat_bakim_paketi", "tesisat", "saha", 1, 1, 1, 0.19),
    ("ikinci_sayac_talebi", "olcum", "saha", 1, 0, 1, 0.16),
    ("isyeri_tarifesine_gecis", "sozlesme", "sube", 0, 1, 0, 0.14),
    ("basinc_duzenleyici_montaji", "tesisat", "saha", 1, 0, 1, 0.12),
    ("bahce_sulama_tarifesi", "sozlesme", "sube", 0, 1, 0, 0.10),
    ("sayac_yeri_degisikligi", "olcum", "saha", 1, 0, 1, 0.09),
    ("ortak_alan_paylasim_kaydi", "sozlesme", "sube", 0, 1, 0, 0.08),
    ("tesisat_uygunluk_raporu", "tesisat", "saha", 1, 0, 1, 0.07),
    ("gecikme_faizi_muafiyeti", "odeme", "sube", 0, 0, 0, 0.06),
    ("mobil_erisim_izni", "bildirim", "cevrimici", 0, 0, 0, 0.05),
    ("ek_hat_tesisati", "tesisat", "saha", 1, 1, 1, 0.04),
]
KESIM = {  # kesimin alanlara egilimi; yalniz uretecin icinde kullanilir
    "tasarruf": {"odeme": 0.5, "olcum": 1.7, "sozlesme": 0.6, "bildirim": 1.5, "tesisat": 0.6},
    "odeme":    {"odeme": 1.9, "olcum": 0.5, "sozlesme": 0.6, "bildirim": 1.1, "tesisat": 0.3},
    "isyeri":   {"odeme": 0.7, "olcum": 0.9, "sozlesme": 1.6, "bildirim": 0.7, "tesisat": 1.9},
    "yeni":     {"odeme": 0.8, "olcum": 0.7, "sozlesme": 1.8, "bildirim": 1.3, "tesisat": 0.7},
}
KESIM_AD = list(KESIM)
rk = uretec(TOHUM + 101)
KALITE = [0.25 + 0.70 * rk() for _ in KALEM]   # kalemin kendi doyuruculugu

N_ABONE, N_KALEM = 360, len(KALEM)
ABONE, GOSTERIM, ETKILESIM, PUAN, EGILIM = [], {}, {}, {}, {}
for i in range(N_ABONE):
    r = uretec(TOHUM + 7919 * i)
    a, kesim = 20001 + i, KESIM_AD[int(r() * 4)]
    etkinlik = 0.35 + 1.15 * r() ** 1.6
    ABONE.append(a)
    for j, (ad, alan, kanal, uc, ta, ra, pay) in enumerate(KALEM):
        egilim = min(0.95, KESIM[kesim][alan] * pay * etkinlik * 0.55)
        EGILIM[(a, j)] = egilim
        if r() < min(0.95, pay ** 0.55 * 0.72):        # sunulmus mu
            GOSTERIM[(a, j)] = 1
            if r() < egilim:                            # secilmis mi
                ETKILESIM[(a, j)] = r()                 # deger: sira damgasi
                if r() < 0.32:                          # puan birakmis mi
                    ham = (0.16 + 0.50 * KESIM[kesim][alan] / 1.9
                           + 0.42 * KALITE[j] + 0.26 * (r() - 0.5))
                    PUAN[(a, j)] = max(1, min(5, int(round(ham * 5))))
        else:
            r(), r(), r()

HUCRE = N_ABONE * N_KALEM
sayi = {}
for (a, j) in ETKILESIM:
    sayi[a] = sayi.get(a, 0) + 1
print("dizey", N_ABONE, "abone x", N_KALEM, "kalem =", HUCRE, "hucre")
for ad, d in (("gosterim", GOSTERIM), ("etkilesim", ETKILESIM), ("puan", PUAN)):
    print(f"  {ad:10s} {len(d):5d}  doluluk {len(d)/HUCRE:.4f}  seyreklik {1-len(d)/HUCRE:.4f}")
print("hic etkilesimi olmayan abone", N_ABONE - len(sayi),
      " abone basina ortalama", round(len(ETKILESIM) / N_ABONE, 3),
      " en cok", max(sayi.values()))
```

```
dizey 360 abone x 24 kalem = 8640 hucre
  gosterim    3002  doluluk 0.3475  seyreklik 0.6525
  etkilesim    577  doluluk 0.0668  seyreklik 0.9332
  puan         218  doluluk 0.0252  seyreklik 0.9748
hic etkilesimi olmayan abone 80  abone basina ortalama 1.603  en cok 7
```

Üç doluluk oranı üç ayrı problemi tarif ediyor. Gösterim dizeyi 0,3475 dolu, seçim dizeyi 0,0668,
puan dizeyi 0,0252. Seyreklik seçim düzeyinde 0,9332'dir: her yüz hücrenin altmış yedisi
sunulmuş ama seçilmemiş, yirmi altısı hiç sunulmamıştır. Abone başına ortalama 1,603 etkileşim
düşer ve 360 abonenin 80'inin hiçbir etkileşimi yoktur. Bu tabloda bir aboneye ne önerileceğine
karar vermek, satırların çoğunun neredeyse tümüyle boş olduğu bir dizeyden karar çıkarmaktır.

## Herkese Aynı Kalemi Sunmak

Öneri alanının tabanı kişiselleştirmesiz bir yordamdır: eğitim kümesinde en çok seçilmiş kalemleri
sıraya dizip herkese aynı listeyi sunmak. Bu taban hiçbir aboneyi diğerinden ayırt etmez, yine de
kataloğun eğriliğinden beslenir ve şaşırtıcı biçimde yükseğe çıkar. Yanına ikinci bir taban
konur: abonenin görmediği kalemleri rastgele sıraya dizmek.

```python
# Ayirma: en az iki etkilesimi olan her abonenin SON etkilesimi ayrilmis kumeye.
EGIT, AYRI, GORULEN = {}, {}, {}
kayit = {}
for (a, j), damga in ETKILESIM.items():
    kayit.setdefault(a, []).append((damga, j))
for a, lst in kayit.items():
    lst.sort()
    if len(lst) >= 2:
        AYRI[a] = lst[-1][1]
        lst = lst[:-1]
    for damga, j in lst:
        EGIT[(a, j)] = 1
        GORULEN.setdefault(a, set()).add(j)

pay = [0] * N_KALEM
for (a, j) in EGIT:
    pay[j] += 1
sirali = sorted(range(N_KALEM), key=lambda j: -pay[j])


def isabet(oner, K):
    return sum(1 for a in AYRI if AYRI[a] in oner(a)[:K]) / len(AYRI)


def taban_populerlik(a):
    return [j for j in sirali if j not in GORULEN.get(a, set())]


rg = uretec(TOHUM + 31)


def taban_rastgele(a):
    aday = [j for j in range(N_KALEM) if j not in GORULEN.get(a, set())]
    for i in range(len(aday) - 1, 0, -1):
        t = int(rg() * (i + 1))
        aday[i], aday[t] = aday[t], aday[i]
    return aday


print("egitim", len(EGIT), "etkilesim / ayrilmis", len(AYRI), "abone")
print("egitimde en cok secilen bes kalem:")
for j in sirali[:5]:
    print(f"  {KALEM[j][0]:26s} {pay[j]:3d}")
for K in (3, 5):
    p, s = isabet(taban_populerlik, K), isabet(taban_rastgele, K)
    print(f"ilk {K}: populerlik {p:.4f}  rastgele {s:.4f}  oran {p/s:.2f}")
print("populerlik tabaninin katalog kapsamasi",
      len({j for a in AYRI for j in taban_populerlik(a)[:5]}), "/", N_KALEM)
```

```
egitim 410 etkilesim / ayrilmis 167 abone
egitimde en cok secilen bes kalem:
  odeme_hatirlatma_sms        68
  e_fatura_gecisi             64
  otomatik_odeme_talimati     47
  taksitlendirme              39
  borc_yapilandirma           27
ilk 3: populerlik 0.5509  rastgele 0.1437  oran 3.83
ilk 5: populerlik 0.6527  rastgele 0.2036  oran 3.21
populerlik tabaninin katalog kapsamasi 9 / 24
```

Kişiselleştirmesiz taban, 167 abonenin 109'unun ayrılmış kalemini ilk beşte yakalıyor: isabet
0,6527. Rastgele sıra aynı ölçüde 0,2036'da kalıyor, aradaki oran 3,21. İlk üçe inildiğinde
taban 0,5509 veriyor ve oran 3,83'e çıkıyor. Bu sayı bu kursun geri kalanı için bir eşik: bir
öneri yöntemi 0,6527'yi geçmiyorsa, hiçbir abone ayrımı yapmayan bir listeden daha iyi değildir.

Tabanın ödediği bedel dördüncü satırda: 167 abonenin ilk beş listelerinde toplam **9 ayrı kalem**
geçiyor, katalogda 24 kalem var. On beş kalem hiçbir aboneye hiç önerilmiyor. Taban yüksek isabeti
kataloğun on beş kalemini tümüyle görünmez kılarak alıyor, ve görünmeyen kalem bir sonraki dönemin
verisine de girmiyor.

## Örtük ile Açık Geri Bildirim

Örtük geri bildirim seçimin kendisidir: abone başvurdu ya da başvurmadı. Açık geri bildirim
abonenin bıraktığı puandır. İkisi aynı kataloğu ölçüyor gibi görünür, ama farklı şeyleri sayarlar.
Örtük sinyal **hacmi** ölçer ve gösterim sayısını içinde taşır; açık sinyal **seçtikten sonraki
doyumu** ölçer ve yalnız seçenlerin bir kısmından gelir.

```python
gos, sec, adet, toplam = ([0] * N_KALEM for _ in range(4))
for (a, j) in GOSTERIM:
    gos[j] += 1
for (a, j) in ETKILESIM:
    sec[j] += 1
for (a, j), p in PUAN.items():
    adet[j] += 1
    toplam[j] += p
ort = [toplam[j] / adet[j] if adet[j] else 0.0 for j in range(N_KALEM)]

s_ortuk = sorted(range(N_KALEM), key=lambda j: -sec[j])
s_acik = sorted([j for j in range(N_KALEM) if adet[j] > 0], key=lambda j: -ort[j])
print(f"{'kalem':26s} {'gosterim':>8s} {'secim':>6s} {'puan':>5s} {'ortalama':>9s}")
for j in s_ortuk[:3] + s_acik[:3]:
    print(f"{KALEM[j][0]:26s} {gos[j]:8d} {sec[j]:6d} {adet[j]:5d} {ort[j]:9.2f}")
print("ortuk ilk 5 ile acik ilk 5 kesisimi", len(set(s_ortuk[:5]) & set(s_acik[:5])))
print("hic secilmemis kalem", sum(1 for j in range(N_KALEM) if sec[j] == 0),
      " on tane puandan az kalem", sum(1 for j in range(N_KALEM) if adet[j] < 10))
print("hic gosterilmemis hucre", HUCRE - len(GOSTERIM),
      " bunlarin puan sayisi", sum(1 for k in PUAN if k not in GOSTERIM))
print("hic puan birakmamis abone", N_ABONE - len({a for (a, j) in PUAN}))
s_puan = sorted(range(N_KALEM), key=lambda j: -ort[j])
print(f"ilk 5 isabet -- ortalama puana gore {isabet(lambda a: [j for j in s_puan if j not in GORULEN.get(a, set())], 5):.4f}")
```

```
kalem                      gosterim  secim  puan  ortalama
e_fatura_gecisi                 215     93    30      3.27
odeme_hatirlatma_sms            236     89    31      3.06
otomatik_odeme_talimati         242     73    24      3.83
adres_guncelleme                158     37    14      4.79
ikinci_sayac_talebi              96      9     3      4.33
isyeri_tarifesine_gecis          91      5     4      4.25
ortuk ilk 5 ile acik ilk 5 kesisimi 1
hic secilmemis kalem 1  on tane puandan az kalem 16
hic gosterilmemis hucre 5638  bunlarin puan sayisi 0
hic puan birakmamis abone 205
ilk 5 isabet -- ortalama puana gore 0.1497
```

İki sıralamanın ilk beşinde ortak olan kalem sayısı **1**. Örtük sıralamanın başındaki üç kalem
ikişer yüzü aşan gösterim almış ve ortalama puanları 3,06 ile 3,83 arasında; açık sıralamanın
başındaki kalemlerden ikisinin puan sayısı 3 ve 4. Ortalama puana göre öneri yapmak ilk beş
isabetini 0,1497'ye düşürüyor, yani rastgele sıranın 0,2036'sının bile altına.

Sayının kaynağı üçüncü ve dördüncü satırda duruyor. Kataloğun 24 kaleminin 16'sının on taneden az
puanı var, çünkü az gösterilen kalem az seçiliyor ve az seçilen kalem az puanlanıyor. Hiç
gösterilmemiş 5.638 hücrenin puan sayısı sıfır; bu bir bulgu değil, zincirin tanımı. Abonelerin
205'i hiç puan bırakmamış. Açık geri bildirim daha zengin bir sinyal taşır, ama taşıdığı yerde
taşır: sistemin daha önce gösterdiği kalemlerde. **Gösterilmeyen kalem puanlanmaz**, ve ortalama
puanla sıralanan bir liste kataloğun görünmeyen kısmı hakkında hiçbir şey söylemez.

Toplanan veri bu dersin üçüncü sayısıdır ve iki parçadan oluşur: hiç gösterilmemiş 5.638 hücre,
ve tabanın önerdiği listelerin dışında kalan 15 kalem. Bir öneri yöntemi ölçülürken bu iki sayı
yazılmıyorsa, ölçünün hangi kataloğa ait olduğu bilinmez.

## Özet

- Kurgu dizey 360 abone ile 24 hizmet kalemini 8.640 hücrede buluşturur; gösterim doluluğu 0,3475,
  seçim 0,0668, puan 0,0252'dir. Seçim düzeyinde seyreklik 0,9332 ve 80 abonenin hiç etkileşimi
  yoktur.
- Taban çizgisi eğitimde en çok seçilen kalemleri herkese aynı sırayla sunmaktır; ilk beş isabeti
  0,6527, ilk üç isabeti 0,5509'dur. Rastgele sıra aynı ölçüde 0,2036 ve 0,1437 verir.
- Tabanın bedeli kapsamadır: 167 abonenin ilk beş listelerinde yalnız 9 ayrı kalem geçer, on beş
  kalem hiçbir aboneye önerilmez.
- Örtük ve açık geri bildirim aynı katalogda iki ayrı sıralama üretir; ilk beşlerinin kesişimi
  birdir ve ortalama puana göre öneri 0,1497 isabet verir.
- Açık sinyal seyrektir ve gösterime bağımlıdır: 24 kalemin 16'sının on taneden az puanı vardır,
  205 abone hiç puan bırakmamıştır, hiç gösterilmemiş 5.638 hücrenin puan sayısı sıfırdır.

## Sonraki Adım

Taban 0,6527 ile bir eşik koydu, ama bunu kataloğun on beş kalemini hiç göstermeyerek yaptı ve
hiç etkileşimi olmayan 80 aboneye söyleyecek ayrı bir sözü yok. İlk soru şu: aboneden hiç veri
gelmemişken bile kalemin kendisi hakkında bilinen bir şey var mı. Katalogda her kalemin alanı,
kanalı, ücretli olup olmadığı, taahhüt ve randevu gerektirip gerektirmediği yazılı. Bir sonraki
ders bu özniteliklerden abone başına bir profil çıkarır, önerileri profile benzerlikle sıralar ve
iki sayıyı ölçer: kaç abone için hiç öneri üretilemiyor, ve üretilen öneriler birbirine ne kadar
benziyor.
