我們在網上購物的時候總是會花大量時間去搜索自己需要的信息,即使找到了一些類似的商品也可能不是特別滿意的,搭上了大把的時間不說可能還選不到合適的商品那就太悲催咯,鑒於這個情況,個性化推薦就應運而生了,如果你去過亞馬遜或卓越亞馬遜應該就會知道這個東西。
當我們登陸進去后,網站首頁會自動列出一些我們喜歡的商品,這樣我們就不用到處去找我們想買的商品了。
網站是怎么知道我們喜歡什么商品的呢? 下面是我對這個原理比較膚淺的理解,現帖出來分享一下,廢話不多說,切入正題。
基本概念:
協同過濾算法:協同過濾分析用戶興趣,在用戶群中找到指定用戶的相似(興趣)用戶,綜合這些相似用戶對某一信息的評價,形成系統對該指定用戶對此信息的喜好程度預測。
目前還存在的缺點:
· 稀疏性問題:用戶和商品的總數都非常大,蛋是用戶對商品的評分卻很少
· 性能問題: 用戶數和商品數越來越多,數據量日益增大導致處理效率降低
· 最初評價問題:如果沒人給這個商品評分,那么這個商品因為沒基數就無法被推薦
為了彌補這三個很要命的缺點,所以又提出了基於組合加權評分的方法,這樣基本可以緩解以上缺點,具體如何解決且聽慢慢道來。
為什么要用組合加權評分?他的主要作用是通過對評分矩陣行和列的平均加權評分進行綜合處理並計算得出預測評分,這樣就可以使每個用戶對每個商品都有評分值,從而緩解了稀疏性問題。
為什么要Base on item? 如果按會員和商品搞個笛卡爾積形成矩陣再進行計算會是一件很悲催的事情,假設目前會員10萬人,商品10萬件, 10w*10w 后面會有10個零。那會是多少? 天文數字吧,不現實的,放棄吧。
建立矩陣R(m,n) 他是一個m*n階矩陣,其中m行表示m個用戶,n列表示n個商品, Ri,j 表示用戶i對商品j的評分值。(至於怎么降維可以參考奇異值分解法SVD,HSPA算法,單調遞減指數函數時間權重分析法,基於時間權重還可以及時反映用戶的興趣變化)
組合加權評分由用戶平均加權評分和項目平均加權評分兩部分組成。
用戶平均加權評分的公式是:
其中i表示商品,u表示用戶,ru為此用戶平均評分與用戶各項評分相對於未評分項平均評分的平均偏差之和。
其中Q為用戶u對商品空間的評分總數,ri為此商品平均評分與用戶集合中各用戶對未評分項i評分與用戶平均評分的平均偏差之和。
組合加權評分ru,I 即是求前兩個積的平方根。
用這個公式把相應項填充后就造成了所有用戶對所有商品均有評分的結果,這樣矩陣就圓滿了。
具體實現方法:
建環境:
會員表:
create table tmp_yofee_member (id number,login_id varchar2(100),status varchar2(30));
商品表:
create table tmp_yofee_product(id number,subject varchar2(100),status varchar2(30));
插入測試數據:
--會員表
declare i number;
begin
i:=2;
while i<200 loop
insert into tmp_yofee_member (id,login_id)
values(i,'yuki'||i);
i:=i+2;
end loop;
end;
--商品表
declare i number;
begin
i:=2;
while i<1000 loop
insert into tmp_yofee_product (id,subject)
values(i,'item_'||i);
i:=i+2;
end loop;
end;
評分表(將會員表和商品表笛卡爾積):
create table TMP_YOFEE_VOTE
as
select b.id pid,b.subject,a.id mid,a.login_id
from tmp_yofee_member a ,tmp_yofee_product b;
加入評分列:
alter table tmp_yofee_vote add score number(6,0);
修改隨機評分值:
updateTMP_YOFEE_VOTE
set score = trunc(dbms_random.value(40,100))
where pid not like '%5';
commit;
下面就可以按照剛才的公式通過游標逐個計算還沒有評分項的預測評分值.
DECLARE
AVG_P NUMBER(6, 2);
AVG_M NUMBER(6, 2);
WE_M NUMBER(6, 2);
WE_P NUMBER(6, 2);
CURSOR CUR IS
SELECT PID, MID
FROM (SELECT DISTINCT PID, MID FROM TMP_YOFEE_VOTE WHERE SCORE IS NULL);
CUR_PID NUMBER;
CUR_MID NUMBER;
BEGIN
OPEN CUR;
FETCH CUR
INTO CUR_PID, CUR_MID;
WHILE CUR%FOUND LOOP
SELECT AVG(SCORE)
INTO AVG_P --商品平均
FROM TMP_YOFEE_VOTE
WHERE PID = CUR_PID
AND SCORE IS NOT NULL;
SELECT AVG(SCORE)
INTO AVG_M --用戶平均
FROM TMP_YOFEE_VOTE
WHERE MID = CUR_MID
AND SCORE IS NOT NULL;
SELECT SUM(SCORE - AVG_P) / COUNT(PID) + AVG_M
INTO WE_M --用戶加權
FROM TMP_YOFEE_VOTE
WHERE MID = CUR_MID
AND SCORE IS NOT NULL;
SELECT SUM(SCORE - AVG_M) / COUNT(PID) + AVG_P
INTO WE_P --商品加權
FROM TMP_YOFEE_VOTE
WHERE PID = CUR_PID
AND SCORE IS NOT NULL;
-- DBMS_OUTPUT.PUT_LINE(SQRT(WE_M * WE_P)); --67.15
UPDATE TMP_YOFEE_VOTE
SET SCORE = SQRT(WE_M * WE_P)
WHERE PID = CUR_PID
AND MID = CUR_MID;
FETCH CUR
INTO CUR_PID, CUR_MID;
END LOOP;
CLOSE CUR;
COMMIT;
END;
這樣我們就可以通過這種方法把矩陣里面未評分項預測並填滿以便即將進行的協同過濾.
矩陣已經填滿,然后,我們就可以進入協同過濾算法核心部分,計算商品相似性並搜尋目標商品的最近鄰居商品集合。
(*注)這里是用的sql實現的,C語言方法以后再加。
輸入:用戶-商品評分矩陣R(m,n) 最近鄰用戶數k, top-N 推薦集項的項目數N.
輸出: 目標用戶u的top-N推薦項集I
第一步:建立用戶-商品評分矩陣R(m,n).
表tmp_yofee_vote(PID,MID,Score) 可以反映之間的對應關系。
第二步:從R(m,n)中分別提取目標商品i與商品j的評分項集,設為Ii,Ij從而得到商品I,j的評分項並集Iij=IiUIj .
第三步:用剛才的公式對這評分項並集中未評分項進行填補。
第四步(關鍵點):搜尋最近鄰居項目,對於目標項目i,算法需要搜尋i的最近鄰居商品集合I={i1,i2,…ik},i I且i與I中商品ik之間的相似性sim(i,ik) (1<=k<=K) 由大到小排列。k值可直接給定或通過相似性閾值來確定,也可將這兩種方法結合,即在相似性大於閾值的商品中擇取相似性最大的前k個商品。
第五步:循環執行1~4步,得到i與其他商品的相似性,從而擇取相似性最大的前k個項目作為i的最近鄰居項目集合I={i1,i2,…,ik} sim(i,ik)由大到小排列。
第六步: 通過計算目標用戶u對任意項目i的評分,然后選擇得到top-N推薦集。設項目i的最近鄰居項目集合為I={i1,i2,…ik} 且i與I中任意項目ik(1<=k<=K)之間的相似性 sim(i,ik)由大到小排列,則目標用戶u對項目i的評分P(u,i)可以基於用戶u對I 中各商品的評分進行加權處理得到:
第七步:輸出u的top-N推薦項集I,結束。
--創建相似度表:
CREATE TABLE tmp_yofee_sim
(pid_i NUMBER(6,0),pid_j NUMBER(6,0),score NUMBER(6,4));
余弦相似性:
代碼大致思路:
通過兩個游標進行循環取商品i和商品j進行對比。
--24000sec
DECLARE
SUM_AB NUMBER;
I2 NUMBER;
J2 NUMBER;
MAX_SCORE NUMBER(6, 4);
CNT_6 NUMBER;
CURSOR CUR1 IS
SELECT DISTINCT PID FROM TMP_YOFEE_VOTE;
CURSOR CUR2 IS
SELECT DISTINCT PID FROM TMP_YOFEE_VOTE;
CUR_PID NUMBER;
CUR_PID2 NUMBER;
BEGIN
OPEN CUR1;
FETCH CUR1
INTO CUR_PID;
WHILE CUR1%FOUND LOOP
OPEN CUR2;
FETCH CUR2
INTO CUR_PID2;
--排除將兩個完全一樣的數據進行對比,第二個游標開始循環。
WHILE CUR2%FOUND AND CUR_PID <> CUR_PID2 LOOP
--一用戶同時給兩商品都有評分並將兩個的評分值相乘然后把所有這種可能的用戶情況相加。
SELECT SUM(A.SCORE * B.SCORE)
INTO SUM_AB
FROM TMP_YOFEE_VOTE A, TMP_YOFEE_VOTE B
WHERE A.MID = B.MID
AND A.PID = CUR_PID
AND B.PID = CUR_PID2;
--將商品i的所有評分算平方和再求平方根
SELECT SQRT(SUM(SCORE * SCORE))
INTO I2
FROM TMP_YOFEE_VOTE
WHERE PID = CUR_PID;
--將商品j的所有評分算平方和再求平方根
SELECT SQRT(SUM(SCORE * SCORE))
INTO J2
FROM TMP_YOFEE_VOTE
WHERE PID = CUR_PID2;
--下面這一段的意思是找出與這個商品最相似的六個商品,如果沒到六個就直接插入,如果已經有六個了如果比這個最小的大就插入新值。
SELECT DECODE(MIN(SCORE), NULL, 0, MIN(SCORE))
INTO MIN_SCORE
FROM TMP_YOFEE_SIM
WHERE PID_I = CUR_PID;
SELECT COUNT(*) INTO CNT_6 FROM TMP_YOFEE_SIM WHERE PID_I = CUR_PID;
IF CNT_6 < 6 THEN
INSERT INTO TMP_YOFEE_SIM
SELECT CUR_PID, CUR_PID2, SUM_AB / (I2 * J2) FROM DUAL;
ELSE
IF CNT_6 >= 6 AND SUM_AB / (I2 * J2) >= MIN_SCORE THEN
INSERT INTO TMP_YOFEE_SIM
SELECT CUR_PID, CUR_PID2, SUM_AB / (I2 * J2) FROM DUAL;
END IF;
END IF;
--打完收功
FETCH CUR2
INTO CUR_PID2;
END LOOP;
CLOSE CUR2;
COMMIT;
FETCH CUR1
INTO CUR_PID;
END LOOP;
CLOSE CUR1;
COMMIT;
END;
--最后查出每個商品按分值排序取最相近的六個並列出來。
SELECT PID_I, PID_J, SCORE, MM
FROM (SELECT PID_I,
PID_J,
SCORE,
RANK() OVER(PARTITION BY PID_I ORDER BY SCORE DESC) MM
FROM TMP_YOFEE_SIM)
WHERE MM <= 6
ORDER BY PID_I, MM





