এই পর্বে যা শিখবেন
- Explicit আর implicit feedback, sparsity আর long tail; কেন খালি ঘর ০ নয়
- Popularity baseline, user-based আর item-based CF, cosine similarity আর অনুমিত রেটিং হাতে বের করা
- Bias-সহ matrix factorization, ALS-এর একটি ধাপ হাতে, আর শূন্য-ভরা SVD কেন ভুল
- Implicit feedback-এর weighted MF, content-based আর hybrid পদ্ধতি, cold start
- সময় মেনে leave-last-out, precision@k, recall@k, MAP, NDCG নিজে লেখা; coverage, popularity bias আর feedback loop
AI-এর ভিত্তি কোর্সের পর্ব ৩৩-এ cosine similarity, পর্ব ৫৬-এ সময় মেনে ডেটা ভাগ, আর পর্ব ৬০-এ precision ও recall দেখেছেন। এই কোর্সের পর্ব ০৯ দেখিয়েছে, রেটিং matrix-এর low-rank রূপ কীভাবে “রুচির অক্ষ” বের করে, আর সেখানেই প্রশ্ন রেখে এসেছিল: বেশিরভাগ ঘর খালি হলে কী হবে? এই পর্ব সেই প্রশ্নের উত্তর, শুরু থেকে মূল্যায়ন পর্যন্ত। চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, pandas আর matplotlib); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে।
১ · ক্রেতা × পণ্যের matrix — explicit, implicit আর ফাঁকা ঘর
পাড়ার বইয়ের দোকানদার জানেন, কোন ক্রেতা হুমায়ূন আহমেদ পড়েন আর কে জাফর ইকবালের বিজ্ঞান কল্পকাহিনি। নতুন বই এলে তিনি মনে মনে ভাবেন, “যাঁরা আগে এই ধরনের বই নিয়েছেন, এটিও তাঁদের ভালো লাগবে।” একটি recommender system এই কাজটিই করে লাখো ক্রেতা আর হাজারো পণ্যের জন্য, যেখানে কোনো দোকানদার সবাইকে মনে রাখতে পারেন না।
ডেটার রূপ একটি matrix R: সারি ক্রেতা (u), কলাম পণ্য (i), ঘরে rui. ঘরের মান দুই ধরনের হতে পারে। Explicit feedback মানে ক্রেতা নিজে মত দিয়েছেন, যেমন ১ থেকে ৫ তারা। Implicit feedback মানে আচরণ থেকে আন্দাজ: কিনেছেন, কার্টে রেখেছেন, পাতা খুলেছেন, কতক্ষণ দেখেছেন। Implicit ডেটা অনেক বেশি পরিমাণে আসে, কিন্তু তাতে “অপছন্দ” লেখা থাকে না: না-কেনার কারণ অপছন্দ হতে পারে, আবার পণ্যটি চোখে না পড়াও হতে পারে।
দ্বিতীয় বৈশিষ্ট্য sparsity: একজন ক্রেতা ক্যাটালগের সামান্য অংশ ছোঁন, তাই বেশিরভাগ ঘর খালি। তৃতীয় বৈশিষ্ট্য long tail: অল্প কয়েকটি পণ্য প্রায় সবাই কেনেন, আর বাকি অসংখ্য পণ্য অল্প কয়েকজন। তিনটি মিলে বিপদ তৈরি করে। খালি ঘরকে ০ ধরলে মডেল শেখে “না দেখা = সবচেয়ে খারাপ”। আর জনপ্রিয় পণ্যের তথ্য বেশি বলে মডেল সহজেই শুধু জনপ্রিয় পণ্যই সুপারিশ করতে শেখে।
এই পর্বের ডেটা কাল্পনিক, seed দিয়ে বানানো। বাজারঘরের ৩০০ ক্রেতা, ১২০টি পণ্য, চারটি বিভাগ (grocery, gadget, beauty, book). প্রত্যেক ক্রেতার ভেতরে চার বিভাগের প্রতি একটি গোপন রুচি আছে, প্রতিটি পণ্যের একটি জনপ্রিয়তা আর মান আছে, আর কিছু ক্রেতা স্বভাবে উদার (বেশি তারা দেন)। ক্রেতারা রুচি আর জনপ্রিয়তা অনুযায়ী কেনেন, ১৮০ দিনের মধ্যে কোনো এক দিনে, তারপর রেটিং দেন। Algorithm শুধু (user, item, rating, day) দেখে, গোপন রুচি দেখে না। split_last প্রত্যেক ক্রেতার সময়ে শেষ কেনাটি আলাদা করে রাখে, যা দিয়ে অংশ ৬-এ মূল্যায়ন হবে।
# bazar_rec.py — বাজারঘরের কাল্পনিক ক্রেতা × পণ্য রেটিং, সময়সহ (seed দিয়ে বানানো)
import numpy as np
import pandas as pd
CATS = np.array(["grocery", "gadget", "beauty", "book"])
def make_ratings(n_users=300, n_items=120, seed=53):
rng = np.random.default_rng(seed)
cat = np.arange(n_items) % 4 # প্রতিটি পণ্যের বিভাগ
V = 1.6 * np.eye(4)[cat] + rng.normal(0, 0.3, (n_items, 4))
U = 3 * rng.dirichlet(np.full(4, 0.3), n_users) # ক্রেতার গোপন রুচি
pop = rng.gumbel(0, 0.9, n_items) # কিছু পণ্য সবার চেনা
qual = rng.normal(0, 0.5, n_items) # পণ্যের মান
gen = rng.normal(0, 0.4, n_users) # কেউ উদার, কেউ কড়া
rows = []
for u in range(n_users):
w = np.exp(U[u] @ V.T + pop); w /= w.sum()
n = 4 + rng.poisson(8)
items = rng.choice(n_items, n, replace=False, p=w)
days = np.sort(rng.choice(180, n, replace=False))
s = 1.4 + 0.8 * U[u] @ V[items].T + qual[items] + gen[u]
r = np.clip(np.round(s + rng.normal(0, 0.5, n)), 1, 5)
rows += [(u, i, int(x), int(d)) for i, x, d in zip(items, r, days)]
df = pd.DataFrame(rows, columns=["user", "item", "rating", "day"])
return df, cat
def split_last(df):
"""প্রত্যেক ক্রেতার সময়ে শেষ কেনাটি test-এ, বাকি সব train-এ।"""
last = df.groupby("user")["day"].transform("max") == df["day"]
return df[~last].reset_index(drop=True), df[last].reset_index(drop=True)একটি সৎ সতর্কতা: এই ডেটায় ক্রেতা যা পছন্দ করেন তা-ই বেশি কেনেন, তাই রেটিং শুধু কেনা পণ্যের, আর বেশিরভাগই ভালো (৫ তারা ১,৩৩৮টি, ১ তারা মাত্র ২৪৮টি)। বাস্তব দোকানেও তা-ই হয়: যে পণ্য অপছন্দ, তা মানুষ সাধারণত কেনেনই না, তাই রেটিংও দেন না। একে বলে missing not at random, আর এটি পরের সব হিসাবে প্রভাব ফেলে।
২ · Popularity baseline আর neighbourhood পদ্ধতি — cosine দিয়ে item-based CF
প্রথম মাপকাঠি সবচেয়ে সরল: সবাইকে সবচেয়ে বেশি বিক্রি হওয়া পণ্য দেখানো (যা ক্রেতা আগে কেনেননি)। এটি ব্যক্তিগত নয়, তবু অবাক করার মতো শক্ত, কারণ জনপ্রিয় পণ্য অনেকেই কিনবেন। পর্ব ০২-এর নিয়ম মনে রাখুন: যে মডেল এই baseline-কে হারাতে পারে না, তা জটিলতার যোগ্য নয়।
Collaborative filtering (CF) বলে: পণ্যের বিবরণ না জেনেও, অনেক ক্রেতার আচরণের মিল থেকে একজনের পছন্দ আন্দাজ করা যায়। দুটি রূপ। User-based CF: আপনার মতো রুচির ক্রেতা খুঁজে তাঁরা যা পছন্দ করেছেন তা দেখানো। Item-based CF: আপনি যে পণ্যগুলো পছন্দ করেছেন, সেগুলোর মতো পণ্য দেখানো। দুই পণ্য “মতো” যদি একই ক্রেতারা দুটিকে একইভাবে রেট করেন। মিলের মাপ সাধারণত cosine similarity: পণ্য i আর j-এর কলাম-vector (প্রতিটি ক্রেতার রেটিং, খালি হলে ০) ধরে
sim(i, j) = (ri · rj) / (‖ri‖ ‖rj‖)
এখানে ri · rj হলো dot product (একই ক্রেতার দুই রেটিং গুণ করে যোগ), আর ‖ri‖ vector-এর দৈর্ঘ্য। তারপর ক্রেতা u-এর পণ্য i-এর অনুমিত রেটিং হলো তাঁর রেট করা পণ্যগুলোর ভারযুক্ত গড়, ভার = মিল:
r̂ui = ∑j ∈ N(i; u) sim(i, j) · ruj / ∑j ∈ N(i; u) |sim(i, j)|
N(i; u) হলো u-এর রেট করা পণ্যগুলোর মধ্যে i-এর সবচেয়ে কাছের কয়েকটি। নিচের stepper পাঁচজন ক্রেতা আর চারটি পণ্য নিয়ে পুরো হিসাবটি করে।
- ডেটা। রিনা, সুমন আর নীলা মুদি পণ্য (তেল, ডাল) ভালোবাসেন; তানিয়া আর করিম গ্যাজেট। নীলা ডাল এখনো রেট করেননি। প্রশ্ন: তিনি কত দেবেন? খালি ঘরগুলো হিসাবের সুবিধার জন্য ০.
- ডাল আর তেলের মিল। দুই কলামের dot product ৪০, দৈর্ঘ্য √45 আর √67. Cosine ০.৭২৮: একই ক্রেতারা দুটিকে উঁচু নম্বর দিয়েছেন, তাই মিল বেশি।
- ডাল আর দুই গ্যাজেটের মিল। ইয়ারফোনের সঙ্গে ০.২৬৪, পাওয়ার ব্যাংকের সঙ্গে ০.৩৪১. মিল শূন্য নয়, কারণ করিম ডালকেও ২ দিয়েছেন; ০ ধরা খালি ঘর কোনো তথ্য দেয় না, কিন্তু যেকোনো ধনাত্মক রেটিং মিল বাড়ায়।
- ভারযুক্ত গড়। নীলার তিন রেটিং (৫, ২, ১) মিল দিয়ে ভার দিয়ে গড়: ৪.৫১১ ÷ ১.৩৩৩ = ৩.৩৮. তেলের ৫ সবচেয়ে বেশি ওজন পায়, তবু দুই গ্যাজেটের কম রেটিং অনুমান টেনে নামায়।
- প্রতিবেশী কতজন? শুধু সবচেয়ে কাছের প্রতিবেশী (তেল) নিলে অনুমান ৫.০. অল্প মিলের প্রতিবেশী গোলমাল আনে, তাই বাস্তবে শুধু শীর্ষ k-টি বা একটি ন্যূনতম মিলের ওপরের প্রতিবেশী নেওয়া হয়। আরেকটি উন্নতি adjusted cosine: আগে প্রত্যেক ক্রেতার নিজের গড় বাদ দেওয়া, যাতে “উদার” আর “কড়া” ক্রেতার পার্থক্য মিলের হিসাবে না ঢোকে।
বড় ডেটায় item-based CF সাধারণত user-based-এর চেয়ে সুবিধাজনক। পণ্যের সংখ্যা প্রায়ই ক্রেতার চেয়ে কম, আর দুই পণ্যের মিল ধীরে বদলায়, তাই item × item মিলের সারণি আগে থেকে হিসাব করে রাখা যায়। নিচের ব্লক implicit রূপে (কিনেছেন = ১, না = ০) item-based CF লেখে: প্রতিটি পণ্যের score হলো ক্রেতার কেনা পণ্যগুলোর সঙ্গে তার মিলের যোগফল, যা গোটা matrix-এ এক গুণে হয়: M @ S. এই ব্লক আর পরের দুটি একই namespace ভাগ করে (data-shared).
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from bazar_rec import make_ratings, split_last, CATS
df, cat = make_ratings() # user, item, rating (1–5), day
nU, nI = 300, 120
print(df.head(3).to_string(index=False))
print(f"ratings: {len(df)} | cells: {nU * nI} | sparsity: {1 - len(df) / (nU * nI):.1%}")
tr, te = split_last(df) # প্রত্যেকের শেষ কেনা লুকানো (অংশ ৬)
R = np.zeros((nU, nI)); M = np.zeros((nU, nI))
R[tr.user, tr["item"]] = tr.rating; M[tr.user, tr["item"]] = 1
cnt = M.sum(0) # প্রতিটি পণ্য কতজন কিনেছেন
top6 = np.argsort(-cnt, kind="stable")[:6] # সবচেয়ে জনপ্রিয় ৫% (৬টি) পণ্য
print(f"top 5% items get {cnt[top6].sum() / cnt.sum():.0%} of purchases")
def item_cf(M): # item-based CF, cosine
n = np.linalg.norm(M, axis=0) + 1e-9
S = (M.T @ M) / np.outer(n, n)
np.fill_diagonal(S, 0)
return M @ S # score = কেনা পণ্যগুলোর সঙ্গে মিলের যোগ
CF = item_cf(M)
u = 11
seen = np.flatnonzero(M[u])
print("user 11 bought:", pd.Series(CATS[cat[seen]]).value_counts().to_dict())
for name, s in [("popularity", cnt.copy()), ("item-CF", CF[u].copy())]:
s[seen] = -np.inf # আগে কেনা পণ্য বাদ
rec = np.argsort(-s, kind="stable")[:5]
print(f"{name:10s} → {rec} {CATS[cat[rec]].tolist()}")
fig, ax = plt.subplots(figsize=(6, 2.6), layout="constrained")
ax.bar(range(nI), np.sort(cnt)[::-1], color="C0")
ax.set(xlabel="item rank", ylabel="buyers (train)", title="Long tail of item popularity")
plt.show()আসল আউটপুট (ব্রাউজারের Python):
user item rating day
0 56 3 18
0 20 4 50
0 116 4 52
ratings: 3529 | cells: 36000 | sparsity: 90.2%
top 5% items get 23% of purchases
user 11 bought: {'book': 9}
popularity → [ 49 113 66 14 56] ['gadget', 'gadget', 'beauty', 'beauty', 'grocery']
item-CF → [51 31 47 19 27] ['book', 'book', 'book', 'book', 'book']
৩৬,০০০ ঘরের মধ্যে রেটিং ৩,৫২৯টি, অর্থাৎ ৯০.২% খালি। বাস্তব দোকানে এই হার সাধারণত আরও অনেক বেশি (৯৯%-এর ওপরে হওয়া স্বাভাবিক), কারণ ক্যাটালগ হাজার হাজার পণ্যের। মাত্র ৬টি পণ্য (৫%) সব কেনার ২৩% পায়; ছবির বারগুলো বাঁ দিকে উঁচু আর ডানে লম্বা নিচু লেজ, এটাই long tail. ক্রেতা ১১ শুধু বই কিনেছেন। Popularity তাঁকে দুটি gadget, দুটি beauty আর একটি grocery দেখায়, কারণ সে কাউকে আলাদা করে দেখে না। Item-CF পাঁচটিই বই দেখায়, কোনো বিভাগের নাম না জেনেই: একই ক্রেতারা বইগুলো একসঙ্গে কেনেন, মিল সেখান থেকেই আসে।
৩ · Matrix factorization — রুচির গোপন অক্ষ, শুধু জানা ঘরে শেখা
Neighbourhood পদ্ধতি সরল আর ব্যাখ্যা করা সহজ (“আপনি এটি কিনেছেন, তাই এটি”), কিন্তু দুটি দুর্বলতা আছে: খুব বিরল পণ্যের মিল নির্ভরযোগ্য নয়, আর মিলগুলো জোড়ায় জোড়ায় হিসাব হয়, কোনো সার্বিক কাঠামো শেখে না। Matrix factorization (MF) ভিন্নভাবে ভাবে: প্রত্যেক ক্রেতা u-কে k-মাত্রার একটি vector pu আর প্রতিটি পণ্য i-কে একটি qi দেওয়া হয়, যাতে তাদের dot product রেটিং ব্যাখ্যা করে। এই k-টি মাত্রা হলো latent factor: কেউ নাম দেয়নি, ডেটা থেকে বেরিয়ে আসে (পর্ব ০৯-এর “মুদি বনাম গ্যাজেট” অক্ষের মতো)।
ফাঁকা রেটিং matrix ≈ ক্রেতার factor × পণ্যের factor
R (users × items, mostly empty) ≈ P (users × k) · Qᵀ (k × items), fitted on known cells only
বাস্তব রেটিংয়ের বড় অংশ কোনো “রুচি” নয়, সাধারণ প্রবণতা: কেউ সব কিছুতে ৫ দেন, কিছু পণ্য সবার ভালো লাগে। তাই অনুমানে চারটি অংশ রাখা হয় (Koren, Bell আর Volinsky ২০০৯ সালে এই রূপটি জনপ্রিয় করেন):
r̂ui = μ + bu + bi + puᵀqi
μ সব রেটিংয়ের গড়, bu ক্রেতার নিজস্ব ঝোঁক (উদার হলে ধনাত্মক), bi পণ্যের নিজস্ব মান, আর puᵀqi রুচি আর পণ্যের মিলের অংশ। শেখা হয় শুধু জানা ঘরের সেট K-এর ওপর, regularization-সহ:
min ∑(u,i) ∈ K (rui − r̂ui)² + λ (‖pu‖² + ‖qi‖² + bu² + bi²)
λ ছাড়া কী হয়? যে ক্রেতার রেটিং মাত্র দুটি, তাঁর জন্য k = ৪ মাত্রার vector যেকোনো দিকে যেতে পারে, দুটি ঘর হুবহু মিলিয়ে বাকি সব অনুমান উল্টাপাল্টা করে। L2 শাস্তি (পর্ব ১৭-এ দেখেছেন, এটি ০-কেন্দ্রিক Gaussian prior-এর MAP) কম তথ্যের ক্রেতাকে গড়ের দিকে টেনে রাখে। আর তথ্য বাড়লে শাস্তির ভূমিকা নিজে থেকেই কমে।
শূন্য-ভরা SVD কেন ভুল: পর্ব ০৯-এর Eckart–Young বলে, সব ঘর জানা থাকলে truncated SVD সবচেয়ে ভালো low-rank রূপ। কিন্তু খালি ঘরে ০ বসালে SVD সেই ০-গুলোকেও “ডেটা” ধরে মেলাতে চায়। ৯০% ঘর ০, তাই তার অনুমান ০-এর দিকে নামে, আর রেটিং ১–৫ স্কেলে অর্থহীন হয়। খালি ঘর গড় দিয়ে ভরলে কিছুটা ভালো, কিন্তু তখনো মডেল হাজারো বানানো “গড় রেটিং” শেখে। ঠিক পথ: loss-এ খালি ঘর রাখাই নয়।
এই loss কমানোর দুটি প্রধান পথ। SGD (পর্ব ১৩): একটি জানা রেটিং নিয়ে ভুল e = r − r̂ বের করে pu ← pu + η(e·qi − λpu), qi-তেও একইভাবে; Netflix Prize-এর সময় Simon Funk-এর ব্লগ থেকে এই পথ ছড়ায় (পর্ব ০৯-এ গল্পটি আছে)। ALS (alternating least squares): Q স্থির রাখলে loss pu-এর সাপেক্ষে একটি সাধারণ ridge regression, যার সমাধান এক লাইনে; তারপর P স্থির রেখে Q, পালাক্রমে। প্রতিটি ধাপে loss কমে বা একই থাকে, learning rate বাছতে হয় না, আর প্রতিটি ক্রেতার সমাধান আলাদা বলে অনেক মেশিনে ভাগ করা সহজ।
৪ · ALS ধাপে ধাপে — একটি ক্রেতার vector হালনাগাদ
Q স্থির থাকলে ক্রেতা u-এর জন্য সমস্যাটি: Qu (তিনি যে পণ্যগুলো রেট করেছেন, শুধু সেগুলোর q-এর সারি) আর তাঁর রেটিং ru দিয়ে min ‖ru − Qup‖² + λ‖p‖². Gradient শূন্য ধরে পাওয়া যায়
pu = (QuᵀQu + λI)−1 Quᵀ ru
I হলো identity matrix. এটি হুবহু ridge regression-এর সূত্র, যেখানে “feature” হলো পণ্যের factor আর “লক্ষ্য” ক্রেতার রেটিং। Bias রাখতে চাইলে q-এর শেষে একটি ১ জুড়ে দেওয়া হয়, তখন p-এর শেষ উপাদানটিই bu. নিচের stepper k = ২ নিয়ে একটি হালনাগাদ হাতে করে।
- শুরু। পণ্যের factor Q এখন স্থির (আগের ধাপে শেখা)। প্রথম মাত্রা মোটামুটি “মুদি”, দ্বিতীয় “গ্যাজেট”। শাহানা তিনটি পণ্য রেট করেছেন: তেলে ৫, চালে ৪, হেডফোনে ১. খোঁজা হচ্ছে তাঁর দুই সংখ্যার রুচি p.
- QᵀQ. প্রতিটি রেট করা পণ্যের q-এর বাইরের গুণফল qqᵀ যোগ করলে QᵀQ = [[1.64, 0.16], [0.16, 1.04]]. শুধু শাহানার তিনটি পণ্য গোনা হয়েছে, বাকি ১১৭টি নয়।
- λI যোগ। কর্ণে λ = ১ যোগ হয়ে A = [[2.64, 0.16], [0.16, 2.04]], determinant ৫.৩৬. এই যোগের কারণে A সবসময় উল্টানো যায়, এমনকি কেউ একটিমাত্র পণ্য রেট করলেও।
- Qᵀr. প্রতিটি পণ্যের q-কে তার রেটিং দিয়ে গুণ করে যোগ: (৮.২, ১.৮). মুদির দিকে বড়, কারণ উঁচু রেটিং মুদিতে।
- সমাধান। 2 × 2 inverse দিয়ে p = (৩.০৬৭, ০.৬৪২). বাস্তব কোডে inverse নয়,
np.linalg.solveব্যবহার করুন। শাহানার রুচি মুদিতে অনেক বেশি, গ্যাজেটে কম, যেমনটা রেটিং বলে। - Shrinkage দেখা। অনুমান (৩.০৭, ২.৫৮, ০.৬৪) আসলের চেয়ে ছোট: λ সব কিছু ০-এর দিকে টানে। λ = ০ হলে p = (৪.৯০, ০.৯৮), প্রায় নিখুঁত মিল, কিন্তু অল্প তথ্যে বিশ্বাসযোগ্য নয়। এ কারণেই বাস্তবে আগে μ আর bias বাদ দিয়ে বাকিটুকুতে factor শেখানো হয়: তখন shrinkage অনুমানকে ০-এর দিকে নয়, গড়ের দিকে টানে।
নিচের ব্লক পুরো ALS bias-সহ লেখে। solve_side একবারে সব ক্রেতার (বা সব পণ্যের) ছোট ridge সমস্যা সমাধান করে: np.einsum প্রতিটি সারির জন্য আলাদা A matrix বানায় (mask M দিয়ে শুধু জানা ঘর), আর np.linalg.solve সবগুলো একসঙ্গে সমাধান করে। k = ৪ আর λ = ৫ আগে থেকে ঠিক করা, এই লুকানো ডেটায় খুঁজে বের করা নয়। তুলনার জন্য তিনটি মাপকাঠি: শুধু গড়, গড় + bias, আর শূন্য-ভরা SVD.
import numpy as np
import matplotlib.pyplot as plt
k, lam, mu = 4, 5.0, tr.rating.mean() # factor ৪টি, L2 শক্তি λ = 5
rmse = lambda e: np.sqrt(np.mean(e ** 2))
def solve_side(F, Mx, T): # প্রতিটি সারির জন্য ridge: (FᵀF + λI)⁻¹Fᵀt
X = np.hstack([F, np.ones((len(F), 1))]) # শেষ কলাম = নিজের bias
A = np.einsum("ui,ik,il->ukl", Mx, X, X) + lam * np.eye(k + 1)
W = np.linalg.solve(A, ((T * Mx) @ X)[..., None])[..., 0]
return W[:, :k], W[:, k]
rng = np.random.default_rng(0)
P, Q = rng.normal(0, 0.1, (nU, k)), rng.normal(0, 0.1, (nI, k))
bu, bi, hist = np.zeros(nU), np.zeros(nI), []
for ep in range(15):
P, bu = solve_side(Q, M, R - mu - bi) # Q স্থির, ক্রেতার দিক
Q, bi = solve_side(P, M.T, (R - mu - bu[:, None]).T) # P স্থির, পণ্যের দিক
pred = mu + bu[:, None] + bi + P @ Q.T
hist.append((rmse((pred - R)[M > 0]), rmse(pred[te.user, te["item"]] - te.rating)))
if ep in (0, 1, 4, 14): print(f"epoch {ep + 1:2d} train {hist[-1][0]:.3f} val {hist[-1][1]:.3f}")
bu0 = ((R - mu) * M).sum(1) / (M.sum(1) + lam) # শুধু bias, factor নেই
bi0 = ((R - mu - bu0[:, None]) * M).sum(0) / (M.sum(0) + lam)
print("val, global mean only :", round(rmse(mu - te.rating), 3))
print("val, mean + biases :", round(rmse(mu + bu0[te.user] + bi0[te["item"]] - te.rating), 3))
U, s, Vt = np.linalg.svd(R, full_matrices=False) # ভুল পথ: খালি ঘর = 0
Rk = (U[:, :k] * s[:k]) @ Vt[:k]
print("val, SVD of zero-filled:", round(rmse(Rk[te.user, te["item"]] - te.rating), 3),
"| mean prediction", round(Rk[te.user, te["item"]].mean(), 2))
fig, ax = plt.subplots(figsize=(6, 2.8), layout="constrained")
ax.plot(range(1, 16), [h[0] for h in hist], "o-", label="train RMSE")
ax.plot(range(1, 16), [h[1] for h in hist], "s-", label="validation RMSE")
ax.set(xlabel="ALS epoch", ylabel="RMSE", title="Matrix factorization with biases (ALS)")
ax.legend(); plt.show()আসল আউটপুট (ব্রাউজারের Python):
epoch 1 train 0.850 val 0.991
epoch 2 train 0.687 val 0.947
epoch 5 train 0.615 val 0.908
epoch 15 train 0.594 val 0.884
val, global mean only : 1.284
val, mean + biases : 0.993
val, SVD of zero-filled: 2.822 | mean prediction 1.09
শুধু গড় (μ) বললে লুকানো রেটিংয়ে RMSE ১.২৮৪. Bias যোগ করলে ০.৯৯৩: ভুলের বড় অংশ আসলে “কে উদার, কোন পণ্য ভালো”। Latent factor যোগ করে ALS নামায় ০.৮৮৪-এ; উন্নতির বেশিরভাগ প্রথম পাঁচ epoch-এ, তারপর বক্ররেখা প্রায় সমতল। Train RMSE (০.৫৯৪) validation-এর চেয়ে বেশ কম, যা স্বাভাবিক; λ বাড়ালে ফাঁক কমে। শূন্য-ভরা SVD-এর RMSE ২.৮২২, শুধু গড়ের চেয়েও দ্বিগুণের বেশি খারাপ, কারণ লুকানো ঘরে তার গড় অনুমান ১.০৯: ০-এ ভরা ৯০% ঘর পুরো low-rank রূপকে নিচে টেনেছে। একটি সতর্কতা: এখানে validation আর পরের অংশের test একই লুকানো শেষ কেনা; k আর λ এতে খুঁজে বের করা হয়নি বলে ফল আশাবাদী নয়, কিন্তু বাস্তব প্রকল্পে tuning-এর জন্য আলাদা একটি সময়-খণ্ড রাখুন।
৫ · Implicit feedback — weighted matrix factorization
বাজারঘরের মতো দোকানে রেটিং দেন অল্প কয়েকজন, কিন্তু কেনা, কার্টে রাখা আর পাতা দেখার হিসাব থাকে সবার। এই implicit ডেটায় খালি ঘরকে বাদ দেওয়া যায় না, কারণ তখন শুধু ১ থাকে আর মডেল সবকিছুতে ১ অনুমান করে ভুল শূন্য করে ফেলে। আবার খালি ঘরকে সমান গুরুত্বের “০” ধরাও ভুল, অংশ ৩-এর সমস্যা ফিরে আসে।
Hu, Koren আর Volinsky (২০০৮, ICDM) একটি মাঝামাঝি পথ দেন, যার নাম এখন weighted matrix factorization (WMF) বা implicit ALS. প্রতিটি ঘরে দুটি সংখ্যা: পছন্দ xui (কিনেছেন হলে ১, না হলে ০) আর আস্থা cui = ১ + α·(কতবার কিনেছেন বা দেখেছেন)। Loss সব ঘরের ওপর, কিন্তু ভারযুক্ত:
min ∑u,i cui (xui − puᵀqi)² + λ(∑‖pu‖² + ∑‖qi‖²)
না-কেনা ঘর “০” হিসেবে থাকে, কিন্তু ভার মাত্র ১; কেনা ঘরের ভার ১ + α, তাই অনেক বেশি। অর্থাৎ মডেল বলে, “কিনেছেন, এ বিষয়ে আমি নিশ্চিত; না-কেনা থেকে সামান্য সন্দেহ”। সব ঘর loss-এ থাকায় SGD ধীর হয়ে যায়, কিন্তু ALS-এ একটি কৌশল আছে: Qᵀ Cu Q = QᵀQ + Qᵀ(Cu − I)Q, আর প্রথম অংশ সব ক্রেতার জন্য একই, দ্বিতীয় অংশে শুধু কেনা পণ্য লাগে। তাই বড় ডেটাতেও হিসাব চলে। আমাদের ছোট ডেটায় পরের ব্লক সরাসরি পুরো C দিয়েই হিসাব করে।
বিকল্প পথ, ranking থেকে শেখা: Rendle ও সহকর্মীদের BPR (২০০৯) কোনো ঘরের মান মেলাতে চায় না; চায় কেনা পণ্যের score না-কেনা একটি এলোমেলো পণ্যের চেয়ে বেশি হোক, অর্থাৎ সরাসরি ক্রম শেখে। এই “negative sampling”-এর ধারণা পরে embedding-ভিত্তিক বড় recommender-এও ব্যাপকভাবে ব্যবহৃত হয়।
৬ · মূল্যায়ন — সময় মেনে leave-last-out, precision@k, recall@k আর NDCG
Recommender-এর আসল কাজ একটি তালিকা দেখানো, যেমন অ্যাপের প্রথম পাতায় ১০টি পণ্য। তাই প্রশ্ন “রেটিং কত কাছে অনুমান হলো” নয়, “ক্রেতা পরে যা কিনলেন, তা তালিকার ওপরের দিকে ছিল কি না”। মূল্যায়নের নকশায় দুটি সিদ্ধান্ত।
কী লুকাবেন: এলোমেলো রেটিং লুকালে মডেল অনেক সময় “ভবিষ্যৎ” দেখে শেখে (মে মাসের কেনা দিয়ে মার্চের কেনা অনুমান), যা বাস্তবে কখনো হয় না; এটি data leakage-এর একটি রূপ। Leave-last-out প্রত্যেক ক্রেতার সময়ে শেষ ঘটনাটি লুকায়, আর বাকি সব দিয়ে শেখায়। আরও কড়া রূপ একটি তারিখ ধরে ভাগ করা (global time-based split): তারিখের আগের সব কিছু train, পরের সব test, যাতে কোনো ক্রেতার ক্ষেত্রেও অন্যের ভবিষ্যৎ ঢোকে না।
Leave-last-out: প্রত্যেক ক্রেতার শেষ কেনা লুকানো
Each user's most recent interaction is held out; earlier ones train the model
কী মাপবেন: ক্রেতা u-এর শীর্ষ k সুপারিশের তালিকায় প্রাসঙ্গিক (পরে সত্যিই কেনা) পণ্যের সংখ্যা h হলে
- Precision@k = h / k: তালিকার কত ভাগ কাজে লাগল।
- Recall@k = h / (মোট প্রাসঙ্গিক): প্রাসঙ্গিকের কত ভাগ তালিকায় ধরা পড়ল।
- MAP: প্রতিটি hit-এর অবস্থানে precision নিয়ে গড় (average precision), তারপর সব ক্রেতার গড়। প্রাসঙ্গিক একটি হলে এটি ১/অবস্থান, যাকে MRR বলে।
- NDCG@k: অবস্থান অনুযায়ী ছাড়: i-তম স্থানের প্রাসঙ্গিকতা reli-কে log2(i + 1) দিয়ে ভাগ করে যোগ (DCG), তারপর সবচেয়ে ভালো সম্ভাব্য ক্রমের DCG (IDCG) দিয়ে ভাগ, যাতে মান ০ থেকে ১.
- প্রাসঙ্গিকতা। ক্রেতা পরে তেল আর আটা কিনে ৫ তারা দিয়েছেন (rel ২), চাল কিনে ৪ তারা (rel ১)। মডেলের শীর্ষ তিনে আছে ডাল, তেল, চাল: দুটি hit, কিন্তু প্রথম স্থানে ভুল পণ্য।
- DCG. প্রতিটি স্থানের rel-কে log2(i + 1) দিয়ে ভাগ: প্রথম স্থানে ১ দিয়ে, দ্বিতীয়তে ১.৫৮৫, তৃতীয়তে ২. নিচের দিকে একই প্রাসঙ্গিকতার দাম কম। যোগফল ১.৭৬২.
- IDCG. সবচেয়ে ভালো সম্ভাব্য তালিকা: সব প্রাসঙ্গিক পণ্য বড় rel আগে রেখে, তালিকায় না থাকা আটাসহ। এর DCG ৩.৭৬২. এই সংখ্যা দিয়ে ভাগ করলে ক্রেতাভেদে তুলনা চলে।
- NDCG আর বাকি দুটি। NDCG@3 = ০.৪৬৮. Precision@3 = ২/৩, recall@3-ও ২/৩, কারণ তিনটি প্রাসঙ্গিকের দুটি ধরা পড়েছে।
- ক্রমের দাম। একই তিনটি পণ্য, শুধু তেল প্রথমে আর ডাল শেষে: NDCG বেড়ে ০.৬৯৯, কিন্তু precision আর recall একটুও বদলায়নি। NDCG-ই একমাত্র যে “কোনটি আগে দেখালেন” তার দাম দেয়।
কয়েকটি বিষয় খেয়াল রাখুন। NDCG-এর আরেকটি প্রচলিত রূপে লাভ হিসেবে rel-এর বদলে 2rel − 1 নেওয়া হয় (উঁচু প্রাসঙ্গিকতা আরও বেশি পুরস্কার পায়); এই উদাহরণে তাতে NDCG ০.৪৪৪. কোন রূপ ব্যবহার করলেন তা লিখে রাখুন, নইলে দুই পেপারের সংখ্যা তুলনীয় নয়। আর আগে কেনা পণ্য তালিকা থেকে বাদ দিন, নইলে মডেল “আপনি যা কিনেছেন তা-ই আবার” দেখিয়ে সহজ নম্বর পায় (যদিও চাল-ডালের মতো বারবার কেনা পণ্যে আবার দেখানো ঠিকই কাজের, সেটি আলাদা প্রশ্ন)।
৭ · চার পদ্ধতির তুলনা — নির্ভুলতা, coverage আর popularity bias
এখন চারটি পদ্ধতি একই লুকানো শেষ কেনার ওপর: popularity, item-CF (ব্লক ১), রেটিংয়ে শেখা bias-সহ MF (ব্লক ২-এর pred), আর কেনা-না-কেনা থেকে শেখা WMF. প্রত্যেক ক্রেতার প্রাসঙ্গিক পণ্য একটি (শেষ কেনা), তাই IDCG = ১, NDCG = ১/log2(অবস্থান + ১), আর precision@10 সবসময় recall@10-এর দশ ভাগের এক ভাগ। নির্ভুলতার পাশে দুটি স্বাস্থ্য-মাপ: catalog coverage (সব ক্রেতার তালিকা মিলিয়ে কত ভাগ পণ্য অন্তত একবার এসেছে) আর সুপারিশের কত ভাগ শীর্ষ ৫% জনপ্রিয় পণ্যে গেছে, যা popularity bias-এর একটি সরল মাপ।
import numpy as np
def top_n(S, n=10): # আগে কেনা পণ্য বাদ দিয়ে শীর্ষ n
S = np.where(M > 0, -np.inf, S)
return np.argsort(-S, axis=1, kind="stable")[:, :n]
def rank_metrics(rec, te): # প্রতি ক্রেতার প্রাসঙ্গিক পণ্য ১টি
hit = rec[te.user] == te["item"].to_numpy()[:, None]
pos = hit.argmax(1) # কত নম্বরে এল (0 থেকে)
ndcg = np.where(hit.any(1), 1 / np.log2(pos + 2), 0.0) # IDCG = 1
return hit.sum(1).mean() / 10, hit.any(1).mean(), ndcg.mean()
def wmf(k=4, lam=1.0, alpha=10.0, epochs=10): # implicit ALS (Hu, Koren, Volinsky)
C, g = 1 + alpha * M, np.random.default_rng(0)
Pi, Qi = g.normal(0, 0.1, (nU, k)), g.normal(0, 0.1, (nI, k))
for _ in range(epochs):
A = np.einsum("ui,ik,il->ukl", C, Qi, Qi) + lam * np.eye(k)
Pi = np.linalg.solve(A, ((C * M) @ Qi)[..., None])[..., 0]
A = np.einsum("ui,uk,ul->ikl", C, Pi, Pi) + lam * np.eye(k)
Qi = np.linalg.solve(A, ((C * M).T @ Pi)[..., None])[..., 0]
return Pi @ Qi.T
models = {"popularity": np.tile(cnt, (nU, 1)), "item-CF": CF,
"MF (ratings)": pred, "WMF (implicit)": wmf()}
print(f"{'model':15s} P@10 R@10 NDCG@10 coverage top5%")
for name, S in models.items():
rec = top_n(S)
p, r, nd = rank_metrics(rec, te)
cov = len(np.unique(rec)) / nI # কত ভাগ পণ্য অন্তত একবার সুপারিশে এল
share = np.isin(rec, top6).mean() # সুপারিশের কত ভাগ শীর্ষ ৫% পণ্যে
print(f"{name:15s} {p:.3f} {r:.3f} {nd:.3f} {cov:.2f} {share:.2f}")আসল আউটপুট (ব্রাউজারের Python):
model P@10 R@10 NDCG@10 coverage top5%
popularity 0.024 0.240 0.121 0.16 0.35
item-CF 0.057 0.567 0.329 0.77 0.17
MF (ratings) 0.025 0.250 0.135 0.44 0.04
WMF (implicit) 0.056 0.560 0.304 0.78 0.12
Popularity ২৪% ক্রেতার শেষ কেনা শীর্ষ ১০-এ ধরে, কিন্তু মাত্র ১৬% পণ্য কখনো দেখায়, আর তার তালিকার ৩৫% শীর্ষ ৬টি পণ্য। Item-CF recall@10 দ্বিগুণের বেশি (০.৫৬৭), NDCG প্রায় তিন গুণ, আর ৭৭% পণ্য কারও না কারও তালিকায় আসে। সবচেয়ে শিক্ষণীয় সারি তৃতীয়টি: যে MF লুকানো রেটিং সবচেয়ে ভালো অনুমান করেছিল (RMSE ০.৮৮৪), তালিকায় সে প্রায় popularity-র সমান (NDCG ০.১৩৫)। কারণ সে শিখেছে “কিনলে কত তারা দেবেন”, “কিনবেন কি না” নয়; তার শীর্ষে ওঠে এমন বিরল কিন্তু উঁচু-মানের পণ্য, যা অনেকে কেনেনই না; শীর্ষ ৫% পণ্যে তার সুপারিশ মাত্র ৪ শতাংশ। কেনা-না-কেনা থেকে শেখা WMF সেই একই factor কাঠামো দিয়ে item-CF-এর কাছাকাছি (০.৩০৪)। Cremonesi, Koren আর Turrin (২০১০)-এর একটি পরিচিত পরীক্ষাতেও RMSE-তে ভালো পদ্ধতি top-N তালিকায় ভালো হবে, এমন নিশ্চয়তা পাওয়া যায়নি। এই ফল একটি seed-এর, একটি কাল্পনিক ডেটার; লেখার সময় আরও তিনটি seed-এ চালিয়ে মূল ছবি একই পেয়েছি: item-CF আর WMF শীর্ষে কাছাকাছি (কখনো একটি এগিয়ে, কখনো অন্যটি), আর রেটিং-MF popularity-র কাছাকাছি নিচে। কোনটি বাস্তবে জিতবে, তা আপনার ডেটাই বলবে।
Offline মাপের সীমাও মনে রাখুন। লুকানো শেষ কেনা ঘটেছিল পুরোনো ব্যবস্থার অধীনে, যা হয়তো জনপ্রিয় পণ্যই বেশি দেখিয়েছিল; তাই offline মাপ জনপ্রিয় পণ্যের দিকে ঝুঁকে থাকে। নতুন মডেল যদি একটি অদেখা কিন্তু দারুণ পণ্য দেখায়, offline হিসাবে সেটি “ভুল”, কারণ ক্রেতা তা কেনার সুযোগই পাননি। চূড়ান্ত বিচার তাই অনলাইন A/B test (পর্ব ৬২ আর ৬৩-এ আসবে), আর offline মাপ প্রার্থী বাছাইয়ের ছাঁকনি।
Feedback loop আর ন্যায্যতা: মডেল যা দেখায়, মানুষ তা-ই বেশি কেনে; সেই কেনা পরের মাসের training ডেটা হয়; ফলে জনপ্রিয় আরও জনপ্রিয় হয়, আর নতুন বিক্রেতার পণ্য দেখা পায় না। Chaney, Stewart আর Engelhardt (২০১৮)-এর simulation দেখায় যে এমন চক্র সুপারিশকে একঘেয়ে করতে পারে। প্রতিকার হিসেবে কিছু অংশ অনুসন্ধান (নতুন পণ্য মাঝে মাঝে দেখানো), বৈচিত্র্য বা coverage-এর শর্তসহ পুনর্বিন্যাস, আর ক্রেতার আগ্রহের অনুপাত মেনে তালিকা সাজানো (Steck ২০১৮-এর “calibrated recommendations”) ব্যবহার হয়। ক্রেতা আর বিক্রেতা দুই পক্ষের ন্যায্যতার প্রশ্ন পর্ব ৬০-এ আরও আসবে; AI-এর ভিত্তি কোর্সের পর্ব ০৬-এ এই চক্রের প্রথম পরিচয় আছে।
৮ · Cold start, content-based আর hybrid
নতুন ক্রেতা সাইন-আপ করলেন, কিছু কেনেননি। CF তাঁর জন্য কিছুই জানে না: item-CF-এ যোগ করার মতো কেনা পণ্য নেই, MF-এ pu শেখার মতো রেটিং নেই। নতুন পণ্যেরও একই দশা। এটাই cold start. তিনটি প্রচলিত উত্তর।
১. Popularity
কিছুই জানা না থাকলে সবচেয়ে নিরাপদ। চাইলে এলাকা বা সময় অনুযায়ী (ঈদের আগে, শীতে) আলাদা জনপ্রিয়তা।
২. Content-based
Content-based filtering: পণ্যের নিজের বৈশিষ্ট্য (বিভাগ, দাম, বিবরণের লেখা) দিয়ে মিল। সাইন-আপে আগ্রহের বিভাগ জিজ্ঞেস করা বা প্রথম কেনা থেকেই কাজ শুরু।
৩. Hybrid
Hybrid recommender: content আর CF-এর score মেশানো; তথ্য বাড়লে CF-এর ভার বাড়ানো। নতুন পণ্যের qi তার বিবরণ থেকে আন্দাজ করাও এর একটি রূপ।
নিচের ব্লক একটি পরীক্ষা সাজায়: ক্রেতা ০–২৩৯ “পুরোনো”, তাঁদের কেনা থেকে জনপ্রিয়তা আর item-item মিল; ক্রেতা ২৪০–২৯৯ “নতুন”, যাঁদের শুধু প্রথম কেনাটি জানা। প্রশ্ন: সেই এক তথ্য থেকে তাঁদের পরের কেনাগুলোর কত ভাগ শীর্ষ ১০-এ ধরা যায় (recall@10)? Content হিসেবে শুধু পণ্যের বিভাগ ব্যবহার হয়েছে।
import numpy as np
import pandas as pd
from bazar_rec import make_ratings
df, cat = make_ratings()
old, new = df[df.user < 240], df[df.user >= 240] # ৬০ জন নতুন ক্রেতা
M = np.zeros((240, 120)); M[old.user, old["item"]] = 1
pop = M.sum(0) # পুরোনোদের থেকে জনপ্রিয়তা
nrm = np.linalg.norm(M, axis=0) + 1e-9
S = (M.T @ M) / np.outer(nrm, nrm); np.fill_diagonal(S, 0) # item-item cosine
onehot = np.eye(4)[cat] # content: পণ্যের বিভাগ
res = {"popularity": [], "content (category)": [], "item-CF (1 item)": [], "hybrid": []}
for u, g in new.sort_values("day").groupby("user"):
first, rest = g["item"].iloc[0], set(g["item"].iloc[1:])
scores = {"popularity": pop.copy(),
"content (category)": onehot @ onehot[first] * 1000 + pop,
"item-CF (1 item)": S[first] + 1e-6 * pop,
"hybrid": S[first] / S[first].max() + onehot @ onehot[first] + pop / pop.max()}
for name, s in scores.items():
s[first] = -np.inf
top = np.argsort(-s, kind="stable")[:10]
res[name].append(len(rest & set(top)) / len(rest)) # recall@10
for name, r in res.items():
print(f"{name:20s} recall@10 = {np.mean(r):.3f}")আসল আউটপুট (ব্রাউজারের Python):
popularity recall@10 = 0.288
content (category) recall@10 = 0.416
item-CF (1 item) recall@10 = 0.364
hybrid recall@10 = 0.417
কোনো তথ্য ছাড়া popularity পরের কেনার ২৮.৮% ধরে। শুধু “প্রথম কেনাটি কোন বিভাগের” জানলেই content পদ্ধতি (সেই বিভাগের জনপ্রিয় পণ্য) ৪১.৬%-এ ওঠে। একটিমাত্র পণ্য থেকে item-CF ৩৬.৪%: ভালো, তবে একটি পণ্যের মিলের সারি গোলমেলে। Hybrid (মিল, বিভাগ আর জনপ্রিয়তা সমান ভারে) এখানে content-এর প্রায় সমান, ০.৪১৭. এই কাল্পনিক ডেটায় রুচি বিভাগ ধরেই তৈরি, তাই বিভাগ এত কাজের; বাস্তবে ভালো content feature (বিবরণের লেখা, দাম, ব্র্যান্ড) খুঁজে বের করাই মূল কাজ। ক্রেতার কেনা বাড়লে CF-এর অংশ বাড়ানো স্বাভাবিক পথ।
৯ · বাস্তবের recommender — দুই ধাপ, embedding আর একটু ইতিহাস
লাখো পণ্যের দোকানে প্রতি ক্রেতার জন্য সব পণ্যের score হিসাব করা ধীর। তাই বাস্তব ব্যবস্থা সাধারণত দুই ধাপে চলে। Retrieval (candidate generation): দ্রুত কয়েকশো সম্ভাব্য পণ্য বাছাই, যেমন item-CF-এর সারণি থেকে, বা ক্রেতা আর পণ্যের vector-এর dot product দিয়ে approximate nearest neighbour খোঁজ, যার মূল ধারণা AI-এর ভিত্তি কোর্সের পর্ব ৬৯-এ দেখেছেন। Ranking: সেই কয়েকশোকে একটি সমৃদ্ধ মডেল দিয়ে সাজানো, যেখানে সময়, দাম, ডেলিভারির এলাকা, স্টকের মতো feature ঢোকে; প্রায়ই gradient boosting (পর্ব ৩৫)। শেষে ব্যবসার নিয়ম: স্টক নেই এমন পণ্য বাদ, একই বিভাগের সব নয়।
MF-এর pu আর qi আসলে embedding. পরবর্তী ধাপে দুটি neural network দিয়ে ক্রেতা আর পণ্যের feature থেকে embedding বানানো হয়, যার নাম two-tower model; নতুন পণ্যের বিবরণ থেকেও তার vector আসে বলে cold start কমে। Covington ও সহকর্মীদের (২০১৬) YouTube-এর প্রবন্ধ এই দুই ধাপের নকশার একটি বহুল উদ্ধৃত উদাহরণ। এগুলো ভবিষ্যতের deep learning কোর্সের বিষয়; এখানে মূল কথা: ভিত্তি একই dot product.
ইতিহাসের সংক্ষিপ্ত রূপরেখা, প্রচলিত বিবরণ অনুযায়ী: “collaborative filtering” শব্দটি Xerox PARC-এর Tapestry ব্যবস্থার প্রবন্ধে (Goldberg ও সহকর্মীরা, ১৯৯২) আসে; GroupLens (Resnick ও সহকর্মীরা, ১৯৯৪) সংবাদ-গোষ্ঠীর লেখায় user-based CF চালায়; Sarwar ও সহকর্মীরা (২০০১) item-based CF-এর পদ্ধতিগত তুলনা প্রকাশ করেন, আর Linden, Smith ও York (২০০৩) একটি বড় অনলাইন দোকানের item-to-item ব্যবস্থার বিবরণ দেন। ২০০৬–২০০৯-এর Netflix Prize matrix factorization-কে কেন্দ্রে আনে (পর্ব ০৯-এ বিস্তারিত)।
আরও গভীরে: Implicit ডেটায় না-দেখা পণ্যকে নিশ্চিত “০” ধরা আসলে একটি অনুমান; Marlin আর Zemel (২০০৯) দেখান রেটিং এলোমেলোভাবে হারায় না, আর Schnabel ও সহকর্মীরা (২০১৬) সুপারিশকে “treatment” ধরে inverse propensity weighting দিয়ে এই পক্ষপাত সংশোধনের প্রস্তাব দেন। মূল্যায়নেও সাবধানতা: Krichene আর Rendle (২০২০) দেখান, সব পণ্যের বদলে অল্প কয়েকটি এলোমেলো নেতিবাচক পণ্যের মধ্যে র্যাঙ্ক মাপলে (sampled metric) পদ্ধতির ক্রম পর্যন্ত উল্টে যেতে পারে। তাই এই পর্বের মতো পুরো ক্যাটালগের ওপর মাপাই নিরাপদ।
পরীক্ষা ও ইন্টারভিউতে: এই ধরনের প্রশ্ন প্রায়ই আসে।
১. দুটি rating vector-এর cosine আর একটি অনুমিত রেটিং? → dot ÷ (দৈর্ঘ্য × দৈর্ঘ্য); তারপর মিল-ভারযুক্ত গড়, ভার |sim| দিয়ে ভাগ।
২. শূন্য-ভরা SVD কেন নয়? → খালি = অজানা, ০ নয়; loss শুধু জানা ঘরে, regularization-সহ, ALS বা SGD দিয়ে শেখা।
৩. একটি ALS ধাপ? → pu = (QᵀQ + λI)−1Qᵀru, শুধু u-এর রেট করা পণ্য দিয়ে; এটি ridge regression.
৪. Precision@k, recall@k, NDCG@k? → h/k, h/মোট প্রাসঙ্গিক, আর অবস্থান-ছাড়ের DCG ÷ IDCG; প্রাসঙ্গিক একটি হলে NDCG = ১/log2(অবস্থান + ১)।
৫. Cold start? → নতুন ক্রেতায় popularity বা সাইন-আপের আগ্রহ, নতুন পণ্যে content feature, পরে hybrid.
১০ · AI-এর যুগে — recommender-এর কোড আর মূল্যায়ন যাচাই
AI assistant কয়েক সেকেন্ডে একটি recommender লিখে দেয়, আর সংখ্যাগুলো প্রায়ই দারুণ দেখায়। সমস্যা সাধারণত লুকিয়ে থাকে মূল্যায়নে।
| AI প্রায়ই যা করে | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
খালি ঘর ০ দিয়ে ভরে TruncatedSVD চালায় | অদেখা = অপছন্দ ধরে; অনুমান ০-এর দিকে নামে | Loss কোন ঘরে, তা দেখুন; শূন্য-ভরা SVD-কে baseline হিসেবে পাশে রাখুন |
| রেটিং এলোমেলো ভাগ করে, শুধু RMSE দেয় | ভবিষ্যৎ দিয়ে অতীত শেখা; RMSE তালিকার মান বলে না | সময় মেনে ভাগ; precision@k, recall@k, NDCG@k, coverage |
| আগে কেনা পণ্য তালিকায় রেখে hit গোনে | মুখস্থ জিনিসে সহজ নম্বর | Train-এর পণ্য বাদ দেওয়ার লাইনটি খুঁজুন |
| Popularity baseline ছাড়াই “উন্নতি” দাবি করে, বা অল্প নেতিবাচক নমুনায় মাপে | শক্ত baseline-ই প্রায়ই জেতে; sampled metric বিভ্রান্ত করে | পুরো ক্যাটালগে মাপুন, কয়েকটি seed বা সময়-খণ্ডে |
Library-র parameter বানায় বা পুরোনো নাম দেয় (যেমন implicit, surprise-এর API) | Version-ভেদে নাম আর ডিফল্ট বদলায় | নিজের version-এর অফিসিয়াল docs আর ছোট উদাহরণে চালিয়ে দেখুন |
কোডের সঙ্গে মূল্যায়নের নকশা আর সীমাবদ্ধতাও চেয়ে নিন:
AI-এর দেওয়া recommender-এর কোড বা ফল হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১রেটিংয়ের RMSE কমানো আর ভালো সুপারিশের তালিকা দেওয়া কেন একই কাজ নয়?
Netflix Prize (২০০৬–২০০৯) গবেষণার একটি প্রজন্মকে RMSE-র দিকে টেনেছিল: লক্ষ্য ছিল লুকানো রেটিং ১০% কম ভুলে অনুমান করা। কিন্তু একটি দোকান বা ভিডিও সাইট আসলে যে প্রশ্নের উত্তর চায় তা হলো, “এই মানুষটিকে এখন কোন দশটি জিনিস দেখাব?” দুটি প্রশ্নের মধ্যে অন্তত তিনটি ফাঁক আছে।
প্রথম, RMSE মাপা হয় শুধু সেই ঘরে যেখানে রেটিং আছে, অর্থাৎ যা মানুষ আগেই বেছে নিয়েছে। সুপারিশের তালিকা বাছাই হয় সব পণ্য থেকে, যার বেশিরভাগ মানুষ কখনো ছুঁয়েও দেখবেন না। দ্বিতীয়, RMSE সব ঘরকে সমান গুরুত্ব দেয়: একটি ২-তারার পণ্যকে ১.৫ অনুমান করার ভুল আর একটি ৫-তারার পণ্যকে ৪.৫ অনুমান করার ভুল একই। তালিকার জন্য শুধু ওপরের দিকের ক্রম জরুরি। তৃতীয়, “কত তারা দেবেন” আর “কিনবেন কি না” আলাদা প্রশ্ন; এই পর্বের পরীক্ষায় রেটিং-MF শীর্ষে তুলেছে বিরল কিন্তু ভালো পণ্য, যা কেউ কেনেননি।
Cremonesi, Koren আর Turrin (২০১০, RecSys) top-N মূল্যায়নে দেখান, RMSE-তে ভালো কিছু পদ্ধতি তালিকার নির্ভুলতায় তুলনামূলক খারাপ করতে পারে, আর কিছু সরল, অ-রেটিং-ভিত্তিক পদ্ধতি ভালো করতে পারে। সেই থেকে গবেষণার বড় অংশ ranking মাপ আর implicit ডেটার দিকে সরে গেছে। Netflix নিজেও পরে তাদের ব্লগে লেখে যে ব্যবসা streaming-এ সরার পর রেটিংয়ের চেয়ে দেখার আচরণ বেশি গুরুত্ব পায়।
এর মানে RMSE অর্থহীন নয়: রেটিং দেখানো (“আপনি সম্ভবত ৪.২ দেবেন”) বা পণ্যের মান বোঝার কাজে তা দরকারি। কিন্তু মাপকাঠি বেছে নিন কাজ থেকে, অভ্যাস থেকে নয় (পর্ব ০২-এর মূল কথা)।
মূল উপলব্ধি: আপনি যা মাপেন, মডেল তাতেই ভালো হয়। তালিকা দেখানো যদি কাজ হয়, তবে তালিকার মাপ (NDCG, recall@k) দিয়েই বাছাই করুন।
প্র ০২প্রথম দিকের recommender ছিল user-based; বড় দোকানগুলো item-based-এ গেল কেন?
১৯৯০-এর দশকের শুরুতে Xerox PARC-এর Tapestry (১৯৯২) মানুষকে অন্যদের মন্তব্য দেখে ইমেইল ছাঁকতে দিত, আর সেখান থেকেই “collaborative filtering” নামটি আসে বলে ধরা হয়। ১৯৯৪ সালে GroupLens প্রকল্প (Resnick ও সহকর্মীরা) স্বয়ংক্রিয় user-based CF চালায়: আপনার রেটিংয়ের সঙ্গে অন্যদের রেটিংয়ের correlation মেপে কাছের মানুষদের মত থেকে অনুমান। ধারণাটি স্বাভাবিক, “আমার মতো রুচির মানুষ যা পছন্দ করেছেন”।
কিন্তু ক্রেতা বাড়লে সমস্যা দেখা দেয়। প্রতিটি অনুরোধে লাখো ক্রেতার মধ্যে প্রতিবেশী খুঁজতে হয়। ক্রেতার রুচি দ্রুত বদলায়, তাই আগে থেকে হিসাব রাখা কঠিন। আর বেশিরভাগ ক্রেতা অল্প কয়েকটি পণ্য রেট করেন, তাই দুই ক্রেতার মধ্যে মিলে যাওয়া পণ্য প্রায়ই খুব কম, মিলের মাপ অনির্ভরযোগ্য।
Sarwar, Karypis, Konstan আর Riedl (২০০১) পদ্ধতিগতভাবে দেখান, পণ্যের মধ্যে মিল আগে থেকে হিসাব রাখলে অনুমান দ্রুত হয় আর মানও তুলনীয় বা ভালো থাকে। দুই পণ্যের মিল অনেক ক্রেতার আচরণ থেকে আসে, তাই বেশি স্থির। ২০০৩ সালে Linden, Smith আর York একটি বড় অনলাইন দোকানের item-to-item ব্যবস্থার বিবরণ প্রকাশ করেন: মিলের সারণি offline-এ বানানো, আর অনুরোধের সময় শুধু ক্রেতার কেনা পণ্যগুলোর সারি দেখা। “যাঁরা এটি কিনেছেন, তাঁরা এটিও কিনেছেন” ধরনের ব্যাখ্যাও এতে স্বাভাবিকভাবে আসে, যা ক্রেতার আস্থা বাড়ায়।
তবে item-based সব সময় শ্রেষ্ঠ নয়: পণ্যের সংখ্যা ক্রেতার চেয়ে অনেক বেশি হলে (সংবাদ, ছোট ভিডিও) বা পণ্য খুব দ্রুত বদলালে হিসাব উল্টে যায়, তখন content বা embedding-ভিত্তিক পদ্ধতি বেশি মানায়।
মূল উপলব্ধি: Algorithm বাছাই প্রায়ই ঠিক হয় গাণিতিক সৌন্দর্যে নয়, কোন জিনিসটি স্থির আর কোনটি আগে থেকে হিসাব করে রাখা যায়, সেই প্রকৌশলের প্রশ্নে।
প্র ০৩Popularity bias কি সবসময় খারাপ? আর feedback loop কীভাবে তাকে বাড়িয়ে দেয়?
জনপ্রিয় পণ্য দেখানোর পক্ষে যুক্তি আছে: অনেক মানুষ সেগুলো সত্যিই চান, আর নতুন ক্রেতার জন্য এটি সবচেয়ে নিরাপদ অনুমান। এই পর্বের পরীক্ষায় popularity কোনো তথ্য ছাড়াই ২৪% ক্রেতার পরের কেনা শীর্ষ ১০-এ ধরেছে। সমস্যা তখন, যখন মডেল প্রয়োজনের চেয়ে বেশি জনপ্রিয়ের দিকে ঝোঁকে: সবাই একই তালিকা পান, আর long tail-এর পণ্য কারও চোখে পড়ে না।
এর কিছুটা আসে ডেটা থেকে: জনপ্রিয় পণ্যের তথ্য বেশি, তাই তাদের অনুমান আত্মবিশ্বাসী আর score বড়। কিছুটা আসে মূল্যায়ন থেকে: test-এর কেনাও জনপ্রিয় পণ্যে বেশি, তাই offline মাপ জনপ্রিয়-ঘেঁষা মডেলকে পুরস্কার দেয়। আর বড় অংশ আসে চক্র থেকে। মডেল যা দেখায়, মানুষ তা-ই বেশি দেখে ও কেনে; সেই কেনা পরের training ডেটা; ফলে পরের মডেল আরও বেশি একই জিনিস দেখায়। Chaney, Stewart আর Engelhardt (২০১৮, RecSys)-এর simulation দেখায়, এমন “algorithmic confounding” ব্যবহারকারীদের আচরণ একঘেয়ে করতে পারে আর সবার জন্য উপযোগিতা কমাতে পারে।
“Filter bubble” নিয়ে, অর্থাৎ recommender মানুষের রুচি কতটা সংকুচিত করে, বাস্তব গবেষণার ফল মিশ্র; প্ল্যাটফর্ম আর মাপের ধরনভেদে ভিন্ন উত্তর এসেছে। তাই একে নিশ্চিত সত্য না ধরে নিজের ডেটায় মাপা উচিত। বিক্রেতার দিক থেকেও ন্যায্যতার প্রশ্ন আছে: বাজারঘরে নতুন একজন উদ্যোক্তার ভালো পণ্য যদি কখনো দেখানোই না হয়, তিনি তথ্যও পাবেন না, বিক্রিও না।
ব্যবহারিক প্রতিকার: coverage আর জনপ্রিয়ের ভাগ নিয়মিত রিপোর্টে রাখা; তালিকার একটি ছোট অংশ অনুসন্ধানে দেওয়া; বৈচিত্র্য বা আগ্রহের অনুপাত মেনে পুনর্বিন্যাস; আর logging-এ কোন পণ্য দেখানো হয়েছিল তাও রাখা, যাতে পরে পক্ষপাত সংশোধন করা যায়।
মূল উপলব্ধি: Recommender শুধু পছন্দ অনুমান করে না, পছন্দ তৈরিতেও অংশ নেয়। তাই নির্ভুলতার পাশে বৈচিত্র্য আর coverage-ও মাপা দায়িত্বের অংশ।
প্র ০৪অনেক নতুন recommender পদ্ধতি পরে কেন সরল baseline-এর চেয়ে ভালো প্রমাণিত হয়নি?
Ferrari Dacrema, Cremonesi আর Jannach (২০১৯, RecSys) top-N সুপারিশের কয়েকটি সাম্প্রতিক neural পদ্ধতি পুনরুৎপাদনের চেষ্টা করেন। তাঁদের প্রতিবেদন অনুযায়ী অনেকগুলোর ফল প্রকাশিত কোড আর ডেটা দিয়েও পুনরুৎপাদন করা যায়নি, আর যেগুলো গেছে, তার বেশিরভাগ ভালোভাবে tune করা সরল পদ্ধতি (item-based kNN, popularity-সংশোধিত পদ্ধতি বা সরল linear মডেল) দ্বারা হেরে গেছে বা সমান হয়েছে। এই প্রবন্ধ ব্যাপক আলোচনা তৈরি করে।
কারণগুলো চেনা। Baseline-গুলো প্রায়ই ডিফল্ট parameter-এ চালানো হয়েছিল, আর নতুন পদ্ধতি অনেক tuning পেয়েছিল। ডেটা ভাগের পদ্ধতি এক পেপার থেকে আরেক পেপারে আলাদা (এলোমেলো না সময় মেনে, আগে কেনা পণ্য বাদ কি না)। কিছু কাজ সব পণ্যের বদলে অল্প কয়েকটি এলোমেলো নেতিবাচক নমুনার মধ্যে র্যাঙ্ক মেপেছিল; Krichene আর Rendle (২০২০) দেখান এমন sampled metric পদ্ধতির আসল ক্রম উল্টে দিতে পারে। আর অনেক সময় test সেটের ওপরই epoch সংখ্যা বা λ বাছাই করা হয়েছিল।
এই অভিজ্ঞতা পর্ব ০৪-এর নিয়মগুলোকে জোরালো করে: seed আর config লিখে রাখা, baseline-কেও সমান যত্নে tune করা, একাধিক seed বা সময়-খণ্ডে ফল দেখানো, আর তুলনার পরিসংখ্যান (পর্ব ২২) ব্যবহার করা। এই পর্বেও একই কারণে popularity আর item-CF পাশে রাখা হয়েছে, আর পুরো ক্যাটালগে মাপা হয়েছে।
এর মানে neural পদ্ধতি অকেজো নয়: অনেক feature, লেখা বা ছবি, আর বিশাল ডেটায় তাদের সুবিধা বাস্তব হতে পারে। কিন্তু দাবিটি প্রমাণ করতে হয় সৎ তুলনায়। বাজারঘরের মতো ছোট দলের জন্য ব্যবহারিক পথ হলো আগে popularity আর item-CF ঠিকমতো দাঁড় করানো, তারপর প্রতিটি নতুন জটিলতা একই ভাগে, একই মাপে যাচাই করে তবেই যোগ করা।
মূল উপলব্ধি: একটি নতুন পদ্ধতির উন্নতি ততটুকুই বিশ্বাসযোগ্য, যতটা শক্ত তার baseline আর যতটা সৎ তার মূল্যায়নের নকশা।
প্র ০৫Implicit ডেটায় “না-কেনা” আসলে কী বলে, আর weighted MF এই অনিশ্চয়তা কীভাবে সামলায়?
একজন ক্রেতা একটি পণ্য কেনেননি। এর পেছনে অন্তত চারটি কারণ হতে পারে: তিনি পণ্যটি দেখেনইনি; দেখেছেন কিন্তু পছন্দ হয়নি; পছন্দ হয়েছে কিন্তু দাম বেশি মনে হয়েছে; অথবা অন্য দোকান থেকে কিনেছেন। ডেটায় চারটিই একই “০”। Explicit রেটিংয়ে অন্তত অপছন্দের একটি সংকেত থাকে (১ তারা), implicit ডেটায় সেটুকুও নেই।
Hu, Koren আর Volinsky (২০০৮, ICDM) একটি টিভি-দেখার ডেটা নিয়ে এই সমস্যার একটি পরিচ্ছন্ন কাঠামো দেন। তাঁরা পছন্দ (০ বা ১) আর আস্থা (কতটা নিশ্চিত) আলাদা করেন: কেউ একটি অনুষ্ঠান অনেকক্ষণ দেখলে আস্থা বেশি, একবার এক ঝলক দেখলে কম, আর না দেখলে সবচেয়ে কম কিন্তু শূন্য নয়। তারপর সব ঘরের ওপর আস্থা-ভারযুক্ত বর্গ-ভুল কমানো হয়। একই প্রবন্ধে তাঁরা দেখান, ALS-এ QᵀQ সব ক্রেতার জন্য একবার হিসাব করে রাখলে প্রতিটি ক্রেতার খরচ শুধু তাঁর নিজের ঘটনার সংখ্যার ওপর নির্ভর করে, তাই কোটি কোটি ঘর থাকলেও পদ্ধতিটি চলে।
এই কাঠামোর সীমাও আছে। আস্থার সূত্র (১ + α·গণনা, বা log রূপ) একটি পছন্দ, প্রমাণিত সত্য নয়; α tune করতে হয়। কোনো পণ্য দেখানোই হয়নি বলে না-কেনা, আর দেখে না-কেনা আলাদা, কিন্তু সাধারণ WMF দুটিকে একই ভার দেয়। যদি logging-এ “কী দেখানো হয়েছিল” (impression) থাকে, তবে দেখানো কিন্তু না-কেনা পণ্যকে আরও শক্ত নেতিবাচক ধরা যায়। আর BPR-এর মতো ranking loss একই প্রশ্নে অন্য দিক থেকে আসে: “কেনা পণ্য না-কেনার চেয়ে ওপরে থাকুক”।
এই পর্বের পরীক্ষায় রেটিং-MF আর WMF-এর factor কাঠামো একই; মূল পার্থক্য কোন ঘরে কী ভারে ভুল মাপা হলো। তাতেই NDCG ০.১৩৫ থেকে ০.৩০৪.
মূল উপলব্ধি: অনুপস্থিত তথ্যকে কী অর্থ দেবেন, সেটি একটি মডেলিং সিদ্ধান্ত। সিদ্ধান্তটি স্পষ্ট করে লিখুন, আর সম্ভব হলে কী দেখানো হয়েছিল তাও রেকর্ড করুন।
অনুশীলন
হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। কোড চালানো ঐচ্ছিক।
-
Ranking মাপ হাতে: একজন ক্রেতার প্রাসঙ্গিক পণ্য দুটি (binary rel). মডেলের শীর্ষ ৫-এ সেগুলো এসেছে ২ আর ৫ নম্বর স্থানে। (ক) Precision@5 আর recall@5 কত? (খ) NDCG@5 কত? (গ) Average precision@5 কত? (log23 ≈ ১.৫৮৫, log26 ≈ ২.৫৮৫)
(ক) Precision@5 = ২/৫ = ০.৪; recall@5 = ২/২ = ১.০. (খ) DCG = 1/log23 + 1/log26 = ০.৬৩১ + ০.৩৮৭ = ১.০১৮. আদর্শ ক্রমে দুটি প্রথম আর দ্বিতীয় স্থানে: IDCG = 1/1 + 1/1.585 = ১.৬৩১. NDCG@5 = ১.০১৮/১.৬৩১ ≈ ০.৬২৪. (গ) প্রথম hit ২ নম্বরে, তখন precision ১/২; দ্বিতীয় hit ৫ নম্বরে, তখন precision ২/৫. AP = (০.৫ + ০.৪)/২ = ০.৪৫. লক্ষ করুন, recall পূর্ণ হলেও NDCG আর AP বলে দেয় যে প্রাসঙ্গিক পণ্যগুলো একেবারে ওপরে ছিল না।
-
সমালোচনা ও নকশা: বাজারঘরের একজন সহকর্মীর রিপোর্ট: “সব রেটিং এলোমেলো ৮০/২০ ভাগ করে SVD-ভিত্তিক মডেল বানিয়েছি, test RMSE ০.৭৯. এখন প্রত্যেক ক্রেতাকে সবচেয়ে বেশি অনুমিত রেটিংয়ের ১০টি পণ্য দেখাব। Item-CF-এর RMSE বেশি ছিল, তাই বাদ।” কী কী সমস্যা, আর আপনি কীভাবে সাজাবেন?
সমস্যা: (১) এলোমেলো ভাগে ভবিষ্যতের রেটিং দিয়ে অতীত শেখা হয়, ফল আশাবাদী। (২) কাজ তালিকা দেখানো, কিন্তু বাছাই হয়েছে RMSE দিয়ে; অংশ ৭-এ দেখা গেছে RMSE-তে সেরা মডেল তালিকায় প্রায় popularity-র সমান হতে পারে। (৩) Item-CF বাদ দেওয়ার কারণও RMSE, অথচ তালিকার মাপে সে-ই হয়তো সেরা। (৪) “SVD-ভিত্তিক” মানে কী, খালি ঘর ০ দিয়ে ভরা কি না, স্পষ্ট নয়। (৫) Popularity baseline, coverage আর আগে কেনা পণ্য বাদ দেওয়ার কোনো উল্লেখ নেই। (৬) একটি ভাগ, একটি সংখ্যা: অনিশ্চয়তা অজানা।
নতুন নকশা: একটি তারিখ ধরে ভাগ (বা leave-last-out); train-এ শুধু তার আগের ঘটনা। Popularity, item-CF, bias-সহ MF আর implicit WMF একই ভাগে; tuning আলাদা একটি সময়-খণ্ডে। মাপ: precision@10, recall@10, NDCG@10, coverage আর জনপ্রিয়ের ভাগ, কয়েকটি সময়-খণ্ডে। নতুন ক্রেতার জন্য আলাদা cold-start নিয়ম। শেষ সিদ্ধান্ত একটি ছোট অনলাইন A/B test-এ।
-
আউটপুট অনুমান করুন: তিনজন ক্রেতা, চারটি পণ্য (১ = কিনেছেন)। চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
import numpy as np M = np.array([[1, 1, 0, 0], [1, 1, 1, 0], [0, 0, 1, 1]], float) n = np.linalg.norm(M, axis=0) S = (M.T @ M) / np.outer(n, n) np.fill_diagonal(S, 0) print(S.round(2)) score = M[0] @ S score[M[0] > 0] = -np.inf print(score.round(2), np.argmax(score))[[0. 1. 0.5 0. ] [1. 0. 0.5 0. ] [0.5 0.5 0. 0.71] [0. 0. 0.71 0. ]] [-inf -inf 1. 0.] 2পণ্য ০ আর ১-এর কলাম হুবহু এক (১, ১, ০), তাই cosine ১. পণ্য ২-এর কলাম (০, ১, ১): পণ্য ০-এর সঙ্গে dot ১, দৈর্ঘ্য √2 × √2 = ২, তাই ০.৫. পণ্য ২ আর ৩: dot ১, দৈর্ঘ্য √2 × ১, তাই ১/√2 ≈ ০.৭১. ক্রেতা ০ কিনেছেন ০ আর ১; পণ্য ২-এর score = ০.৫ + ০.৫ = ১, পণ্য ৩-এর ০ + ০ = ০. আগে কেনা দুটি −inf, তাই সুপারিশ পণ্য ২. লেখার সময়ের NumPy-তে ডেস্কটপে চালিয়ে মেলানো।
-
ALS আর coverage-এর numerical: (ক) k = ১. একজন ক্রেতা দুটি পণ্য রেট করেছেন, পণ্যের factor q = ২ আর ১, রেটিং (bias বাদে) ৪ আর ৩, λ = ১. ALS তাঁর p কত দেবে? q = ১.৫ এমন একটি নতুন পণ্যে অনুমান কত? (খ) ২০০টি পণ্যের দোকানে ৫০ জন ক্রেতাকে ১০টি করে সুপারিশ দেওয়া হলো; সব তালিকা মিলিয়ে ভিন্ন পণ্য ৩০টি, আর ৫০০টি সুপারিশের ৩০০টি শীর্ষ ১০টি জনপ্রিয় পণ্যে। Coverage আর জনপ্রিয়ের ভাগ কত?
(ক) k = ১-এ সূত্র স্কেলার হয়ে যায়: p = ∑q·r / (∑q² + λ) = (2·4 + 1·3)/(4 + 1 + 1) = ১১/৬ ≈ ১.৮৩৩. নতুন পণ্যে অনুমান ১.৫ × ১.৮৩৩ ≈ ২.৭৫ (bias যোগ করার আগে)। λ = ০ হলে p = ১১/৫ = ২.২; শাস্তি p-কে ছোট করেছে। (খ) Coverage = ৩০/২০০ = ১৫%; জনপ্রিয়ের ভাগ ৩০০/৫০০ = ৬০%. মাত্র ১০টি পণ্য (৫%) সুপারিশের ৬০% নিচ্ছে, অর্থাৎ শক্ত popularity bias; এই দুই সংখ্যা নির্ভুলতার মাপের পাশে নিয়মিত রিপোর্ট করার মতো।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি recommender বানিয়ে সৎভাবে মাপার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ঘটনাগুলো সময় অনুযায়ী train আর test-এ ভাগ করা
- শুধু train থেকে popularity baseline বানানো
- শুধু train-এ item-CF বা matrix factorization শেখানো
- প্রত্যেক ক্রেতার জন্য আগে কেনা পণ্য বাদ দিয়ে শীর্ষ k তালিকা বানানো
- Test-এ precision@k, recall@k, NDCG@k আর coverage মেপে baseline-এর সঙ্গে তুলনা করা
দুটি পণ্যের rating vector (তিনজন ক্রেতার রেটিং, খালি হলে ০) a = (1, 0, 1) আর b = (1, 1, 0). তাদের cosine similarity কত?
- ১
- ০.৫
- প্রায় ০.৭১
- ০
৯০% ঘর খালি এমন একটি রেটিং matrix-এ খালি ঘরগুলো ০ দিয়ে ভরে truncated SVD চালালে প্রধান সমস্যা কী?
- আয়তাকার matrix-এর SVD হয় না
- শুধু হিসাব ধীর হয়, ফল ঠিক থাকে
- খালি ঘরকে ০ রেটিং হিসেবে শিখে অনুমান ০-এর দিকে টেনে নামায়
- SVD ঋণাত্মক factor দেয় বলে রেটিং বোঝা যায় না
একজন ক্রেতার একটিমাত্র প্রাসঙ্গিক পণ্য মডেলের তালিকায় ৩ নম্বর স্থানে এসেছে। তাঁর NDCG@10 কত (binary relevance)?
- ০.৫
- প্রায় ০.৩৩
- ০.১
- ১
Recommender মূল্যায়নে এলোমেলো ভাগের বদলে প্রত্যেক ক্রেতার সময়ে শেষ ঘটনা লুকানোর (leave-last-out) প্রধান কারণ কোনটি?
- এতে test সেট বড় হয়
- এতে RMSE সবসময় কমে
- এতে cold start-এর সমস্যা থাকে না
- এতে মডেল ভবিষ্যতের ঘটনা দেখে অতীত অনুমান করার সুযোগ পায় না
Implicit feedback-এর weighted matrix factorization (Hu, Koren আর Volinsky, ২০০৮)-এ না-কেনা ঘরগুলো কীভাবে ব্যবহার হয়?
- পুরোপুরি বাদ দেওয়া হয়, শুধু কেনা ঘরে loss
- পছন্দ ০ হিসেবে থাকে, কিন্তু কেনা ঘরের তুলনায় অনেক কম ভারে
- নিশ্চিত অপছন্দ হিসেবে কেনা ঘরের সমান ভারে
- সব রেটিংয়ের গড় দিয়ে ভরা হয়
বাজারঘরে আজ একটি নতুন পণ্য যোগ হলো, এখনো কেউ কেনেননি। কোন পদ্ধতি একে কারও তালিকায় তোলার সবচেয়ে ভালো সুযোগ দেয়?
- Cosine দিয়ে item-based CF
- রেটিংয়ে শেখা matrix factorization
- পণ্যের বিভাগ আর বিবরণ ব্যবহার করে content-based বা hybrid পদ্ধতি
- শুধু popularity
ALS-এ পণ্যের factor Q স্থির রেখে একজন ক্রেতার vector pu হালনাগাদের সূত্র কোনটি (λ = regularization)?
- pu = (QuᵀQu + λI)−1 Quᵀ ru
- pu = (QuᵀQu)−1 Quᵀ ru, λ ছাড়া
- pu ← pu + η(e·qi − λpu)
- pu = Qᵀru, সব ১২০টি পণ্যের Q দিয়ে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- খালি ঘর = অজানা, ০ নয়। Explicit ডেটায় loss শুধু জানা ঘরে; implicit ডেটায় সব ঘরে, কিন্তু আস্থার ভার দিয়ে, যাকে WMF বলে।
- Baseline আগে: popularity শক্ত; item-based CF (cosine, মিল-ভারযুক্ত গড়) সরল, দ্রুত আর ব্যাখ্যাযোগ্য।
- r̂ = μ + bu + bi + puᵀqi, L2-সহ; ALS-এর প্রতিটি ধাপ ridge: pu = (QᵀQ + λI)−1Qᵀru.
- সময় মেনে মাপুন: leave-last-out বা তারিখ ধরে ভাগ; precision@k = h/k, recall@k = h/প্রাসঙ্গিক, NDCG = DCG/IDCG; সঙ্গে coverage আর জনপ্রিয়ের ভাগ। RMSE-তে সেরা মানেই তালিকায় সেরা নয়।
- Cold start: popularity, content feature, hybrid; আর feedback loop মাথায় রেখে বৈচিত্র্য ও অনুসন্ধান।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৪ · কম label-এ শেখা — semi-supervised আর active learningপরবর্তী পর্বSelf-training, label spreading আর active learning দিয়ে কম label-এ শেখা।
- মেশিন লার্নিং · কোর্স হোম৭৭টি পর্ব১৪টি মডিউলের পুরো রোডম্যাপ; পর্ব ৫৪ এখান থেকে খুলবেন।
- পর্ব ০৯ · SVD গভীরেএই কোর্সLow-rank approximation, Eckart–Young আর recommender-এর গাণিতিক ভিত্তি।
- পর্ব ১৭ · Regularization-এর তত্ত্বএই কোর্সL2 শাস্তি, MAP আর shrinkage; ALS-এর প্রতিটি ধাপের ridge.
- AI-এর ভিত্তি · পর্ব ৩৩ · Vector আর similarityআগের ভিত্তিDot product, cosine আর embedding-এর প্রথম পরিচয়।
- AI-এর ভিত্তি · পর্ব ৬৯ · Embedding আর vector searchআগের ভিত্তিNearest neighbour খোঁজ, যা বড় recommender-এর retrieval ধাপে লাগে।
- System Designপাশের ট্র্যাকCache, precompute আর বড় মাপের পরিষেবা, যা বাস্তব recommender চালাতে লাগে।