0 주입 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
"""0 주입(Zero-Injection) 검증.
문서: 46.0 주입 (예시, 정의, 카드)
주장:
1. 평점 행렬에서 사용자 1과 2가 함께 평가한 아이템이 없으면 사용자 기반 협업 필터링의 유사도를 계산할 수 없다.
"관심 없는 아이템"에 0을 넣으면 함께 채워진 칸이 생겨 유사도를 계산할 수 있다.
2. 사용 전 선호도 행렬 P(평가했으면 1, 아니면 추정값)에서 하위 θ% 빈칸만 0으로 넣는다.
θ를 키우면 0이 많아져 행렬이 더 빽빽해진다.
3. 0을 넣은 아이템은 추천 후보에서도 뺀다.
4. 문서 예시의 추정 사용 전 선호도 0.1, 0.9, 0.8, 0.2, 0.8, 0.4, 0.1, 0.8, 0.3에서
θ = 20%이면 가장 낮은 0.1 두 칸만 0이 된다(9칸 × 20% = 1.8 -> 아래에서 2칸).
5. 카드 C2: 빈칸 100개, θ = 80% -> 0을 넣는 칸 80개, 남는 빈칸 20개.
"""
import math
def pcc(x, y):
S = [i for i in range(len(x)) if x[i] is not None and y[i] is not None]
if len(S) < 2:
return None
mx = sum(x[i] for i in S) / len(S); my = sum(y[i] for i in S) / len(S)
num = sum((x[i] - mx) * (y[i] - my) for i in S)
den = math.sqrt(sum((x[i] - mx) ** 2 for i in S) * sum((y[i] - my) ** 2 for i in S))
return num / den if den else None
def main():
N = None
u1 = [5, 4, N, N, N]
u2 = [N, N, 4, 5, N]
assert pcc(u1, u2) is None
# 사용 전 선호도 추정: 각 사용자가 관심 없을 아이템(낮은 추정값)에 0 주입
pre1 = [1, 1, 0.2, 0.9, 0.1]; pre2 = [0.1, 0.8, 1, 1, 0.2]
def inject(r, pre, theta):
blanks = sorted((pre[i], i) for i in range(len(r)) if r[i] is None)
z = r[:]
for _, i in blanks[:int(len(blanks) * theta + 0.999)]:
z[i] = 0
return z
z1 = inject(u1, pre1, 0.67); z2 = inject(u2, pre2, 0.67)
s = pcc(z1, z2)
assert s is not None
print(f"[OK] 두 사용자 예: 0 주입 전 유사도 계산 불가, 후 {z1} / {z2} -> 피어슨 {s:.3f}")
est = [0.1, 0.9, 0.8, 0.2, 0.8, 0.4, 0.1, 0.8, 0.3] # 문서 예시의 추정 행렬에서 1이 아닌 칸
order = sorted(range(9), key=lambda i: est[i])
k20 = math.ceil(9 * 0.2)
assert k20 == 2 and sorted(est[i] for i in order[:k20]) == [0.1, 0.1]
print("[OK] θ = 20% 예: 추정 선호도 9칸 중 θ = 20%면 가장 낮은 0.1 두 칸에 0 주입")
assert 100 * 0.8 == 80
print("[OK] 카드 C2: 80칸")
print("ALL CHECKS PASSED")
if __name__ == "__main__":
main()