剛看到一新聞說很多互聯網公司尤其是草根北京的都缺少有針對性的CTO,看完我感覺我要努力學好算法,爭取自己創業。
一、PearsonCorrelation
兩個變量之間的相關系數越高,從一個變量去預測另一個變量的精確度就越高,這是因為相關系數越高,就意味着這兩個變量的共變部分越多,所以從其中一個變量的變化就可越多地獲知另一個變量的變化。如果兩個變量之間的相關系數為1或-1,那么你完全可由變量X去獲知變量Y的值。
相關系數:考察兩個事物(在數據里我們稱之為變量)之間的相關程度。
如果有兩個變量:X、Y,最終計算出的相關系數的含義可以有如下理解:
(1)、當相關系數為0時,X和Y兩變量無關系。
(2)、當X的值增大(減小),Y值增大(減小),兩個變量為正相關,相關系數在0.00與1.00之間。
(3)、當X的值增大(減小),Y值減小(增大),兩個變量為負相關,相關系數在-1.00與0.00之間。
相關系數的絕對值越大,相關性越強,相關系數越接近於1或-1,相關度越強,相關系數越接近於0,相關度越弱。
通常情況下通過以下取值范圍判斷變量的相關強度:
相關系數 0.8-1.0 極強相關
0.6-0.8 強相關
0.4-0.6 中等程度相關
0.2-0.4 弱相關
0.0-0.2 極弱相關或無相關
皮爾遜相關也稱為積差相關(或積矩相關)是英國統計學家皮爾遜於20世紀提出的一種計算直線相關的方法。
假設有兩個變量X、Y,那么兩變量間的皮爾遜相關系數可通過以下公式計算:
從公式一可以看出只要兩個變量的標准差都不為0相關系數才有意義。
該系數不足:需要指出的是,相關系數有一個明顯的缺點,即它接近於1的程度與數據組數n相關,這容易給人一種假象。因為,當n較小時,相關系數的波動較大,對有些樣本相關系數的絕對值易接近於1;當n較大時,相關系數的絕對值容易偏小。特別是當n=2時,相關系數的絕對值總為1。因此在樣本容量n較小時,我們僅憑相關系數較大就判定變量x與y之間有密切的線性關系是不妥當的。
二、算法簡介
我們先做個詞法分析基於用戶說明這個算法是以用戶為主體的算法,這種以用戶為主體的算法比較強調的是社會性的屬性,也就是說這類算法更加強調把和你有相似愛好的其他的用戶的物品推薦給你,與之對應的是基於物品的推薦算法,這種更加強調把和你你喜歡的物品相似的物品推薦給你。然后就是協同過濾了,所謂協同就是大家一起幫助你啦,然后后面跟個過濾,就是大家是商量過后才把結果告訴你的,不然信息量太大了。所以,綜合起來說就是這么一個算法,那些和你有相似愛好的小伙伴們一起來商量一下,然后告訴你什么東西你會喜歡。
如何計算相似度,可采用皮爾森相關系數也可以用交集除以並集,本篇只介紹第一種。
如何找最近鄰K呢?我們知道,在找和你興趣愛好相似的小伙伴的時候,我們可能可以找到幾百個,但是有些是好基友,但有些只是普通朋友,那么一般的,我們會定一個數K,和你最相似的K個小伙伴就是你的好基友了,他們的愛好可能和你的愛好相差不大,讓他們來推薦東西給你(比如肥皂)是最好不過了。
何為和你相似呢?簡單的說就是,比如你喜歡macbook,iphone,ipad,A小伙伴喜歡macbook,iphone,note2,小米盒子,肥皂,蠟燭,B小伙伴喜歡macbook,iphone,ipad,肥皂,潤膚霜,C女神喜歡雅詩蘭黛,SK2,香奈兒,D屌絲喜歡ipad,諾基亞8250,小霸王學習機那么很明顯,B小伙伴和你更加相似,而C女神完全和你不在一個檔次上,那我們推薦的時候會把肥皂推薦給你,因為我們覺得肥皂可能最適合你。
那么,如何找出這K個基友呢?最直接的辦法就是把目標用戶和數據庫中的所有用戶進行比較,找出和目標用戶最相似的K個用戶,這就是好基友了。
這么做理論上是沒什么問題的,但是當數據量巨大的時候,計算K個基友的時間將會非常長,而且你想想就知道,數據庫中的大部分用戶其實和你是沒有什么交集的,所沒必要計算所有用戶了,只需要計算和你有交集的用戶就行了。要計算和你有交集的用戶,就要用到物品到用戶的反查表,什么是反查表呢?很簡單,還是是上面那個AB小伙伴和C女神的例子,反查表就是喜歡macbook的有你,A,B,喜歡iphone的有你,B。。。就是喜歡某些物品的用戶,有了這個表,我們就可以看出來,和你有關系的用戶就只有A和B,D了,而C女神和你沒有任何交集,所以不用去想C了。
這樣,我們有了A和B,D,然后就分別計算A和B,D與你的相似度,不管用哪個相似性公式,我們算出來都是B和你更相似(在這個例子中,一般會用Jaccard來計算,因為這些向量不是特別好余弦化),但如果此時我們的K設定為2,那么我們就得出了與你最相鄰的基友是B和A。
這就是與目標用戶最相鄰的K個用戶的計算。通過這K個用戶來推薦商品了
好了,你的好基友我們也算出來了,接下來要向你推薦商品了。但是我們可推薦的商品有小米盒子,note2,蠟燭,潤膚霜,肥皂這么四種,到底哪種才是你需要的呢?這里的算法就比較廣泛了,我們可以不排序,都一股腦推薦給你,但這明顯可能有些你不怎么感興趣,我們也可以做一些處理,假如我們算出來A和你的相似度是25%,B和你的相似度是80%,那么對於上面這些產品,我們的推薦度可以這么來算
小米盒子: 1*0.25 = 0.25
note2: 1*0.25 = 0.25
蠟燭: 1*0.25 = 0.25
潤膚霜: 1*0.8 = 0.8
肥皂: 1*0.8+1*0.25=1.05
好了,通過這個例子,你大概知道了為什么會推薦肥皂給你了吧,這就是基於用戶的協同推薦算法的描述,總結起來就是這么幾步
1.計算其他用戶和你的相似度,可以使用反差表忽略一部分用戶
2.根據相似度的高低找出K個與你最相似的鄰居
3.在這些鄰居喜歡的物品中,根據鄰居與你的遠近程度算出每一件物品的推薦度
4.根據每一件物品的推薦度高低給你推薦物品。
比如上面那個例子,首先,我們通過反查表忽略掉了C女神,然后計算出A和B,D與你的相似度,然后根據K=2找出最相似的鄰居A和B,接着根據A,B與你相似度計算出每件物品的推薦度並排序,最后根據排好序的推薦度給你推薦商品。
這個算法實現起來也比較簡單,但是在實際應用中有時候也會有問題的。
比如一些非常流行的商品可能很多人都喜歡,這種商品推薦給你就沒什么意義了,所以計算的時候需要對這種商品加一個權重或者把這種商品完全去掉也行。再有,對於一些通用的東西,比如買書的時候的工具書,如現代漢語詞典,新華字典神馬的,通用性太強了,推薦也沒什么必要了。
這些都是推薦系統的臟數據,如何去掉臟數據,這是數據預處理的時候事情了,這里就不多說了。
例:由於用戶給電影打分有好有壞[1到5分],而我們上面的例子中都是說的喜歡某件物品而沒有說不喜歡的情況,所以首先,我們要把數據處理一下,簡單的來做,我們可以認為3分以上的話代表這個用戶喜歡這個電影,否則就是不喜歡,這樣顯得有點太死板了,我們也可以這么來定義,比如用戶A對30部電影打分了,首先求出他打分的平均值,然后高於這個平均值的我們覺得用戶喜歡這個電影,否則認為他不喜歡。
主要參考文獻http://blog.csdn.net/ygrx/article/details/15501679
三、發現的問題
發現了二個知識點。
- 報空指針異常
1: Node[] recommend = new Node[preference[0].length];
2: for(int j=0; j<recommend.length; j++) {3: recommend[j] = new Node();
4: }
對recommend數組new完空間后必須用for循環一個一個初始化,否則recommend[i].itemID就會包空指針,因為recommend[i]本身就是null。
- 數組參數
數組pearsonCorrelation里可以直接用形參的preference的屬性,比如長度,實際上就是地址傳遞,當然可以了。1: public Point[] calcPearson(int i, int[][] preference) {2: Point[] pearCorrelation = new Point[preference.length-1];
四、算法實現
可能某些地方有些小錯誤,希望路過的指出來,文件粘貼在工程路徑下。
1: package util;
2:3: import java.io.BufferedReader;
4: import java.io.File;
5: import java.io.FileNotFoundException;
6: import java.io.FileReader;
7: import java.io.IOException;
8: import java.security.acl.Permission;
9: import java.util.Arrays;
10:11: public class CollaborFiltering {12:13: /**
14: * @param args15: */16: //找最相關的5個人進行推薦,沒有判斷強弱相關
17: static int NEARK = 5;18: public static void main(String[] args) {19: // TODO Auto-generated method stub
20: CollaborFiltering cf = new CollaborFiltering();
21: int[][] preference = cf.readFile("preference.data");22: //System.out.println(cf);
23: // for(int i=0; i<preference.length; i++) {
24: // for(int j=0; j<preference[0].length;j++) {
25: // System.out.print(preference[i][j]+" ");
26: // }
27: // System.out.println();
28: // }
29: //System.out.println(preference);
30: cf.go(preference);31: }32:33: public void go(int[][] preference) {34: //物品數目
35: int itemNum = preference[0].length;
36: //對於每個用戶
37: for(int i=0; i<preference.length; i++) {38: //計算每個用戶和其他用戶的相似系數存入Point pearCorrelation中
39: Point[] pearCorrelation = calcPearson(i,preference);40: Arrays.sort(pearCorrelation);41: //只取前NEARK個,對每種不存在於用戶i的物品計算推薦度
42: //如果每種都有,則不推薦
43: Node[] recommend = calcRecommendation(i,pearCorrelation,preference);44: Arrays.sort(recommend);45:46: //輸出前3中推薦物品
47: System.out.print("第"+i+"個用戶的前三個推薦物品及推薦度為:");48: for(int j=0; j<3; j++) {49: System.out.println(recommend[j].itemId+"物品,推薦度為"+recommend[j].recommendation);
50: }51: }52: }53:54: public Node[] calcRecommendation(int i,Point[] pearCorrelation,int[][] preference) {55: // TODO Auto-generated method stub
56: Node[] recommend = new Node[preference[0].length];
57: for(int j=0; j<recommend.length; j++) {58: recommend[j] = new Node();
59: }60: int t = 0;//recommend下標61: //對每種不存在於i的物品計算推薦度
62: for(int j=0; j<preference[0].length; j++) {63: if(preference[i][j]==0) {
64: recommend[t].itemId = j;65: double temp = 0.0;
66: for(int k=0; k<NEARK; k++) {67: temp += preference[pearCorrelation[k].userID][j]68: *pearCorrelation[k].correlation;69: }70: recommend[t].recommendation = temp;71: t++;72: }73: }74: return recommend;
75: }76:77: //計算i用戶和其他用戶的皮爾森系數
78: public Point[] calcPearson(int i, int[][] preference) {79: // TODO Auto-generated method stub
80: //System.out.println(preference.length);
81: //不是preference[0].length
82: Point[] pearCorrelation = new Point[preference.length-1];
83: for(int j=0; j<pearCorrelation.length; j++) {84: pearCorrelation[j] = new Point();
85: }86: int t = 0;//pearCorrelation的下標87: double sumX = 0;
88: double sumY = 0;
89: double squareX = 0;
90: double squareY = 0;
91: double sumXTimesY = 0;
92: for(int j=0; j<preference[0].length; j++) {93: sumX += preference[i][j];94: squareX += preference[i][j]*preference[i][j];95: }96: for(int k=0; k<preference.length; k++) {97: if(k!=i) {
98: for(int j=0; j<preference[0].length; j++) {99: sumY += preference[k][j];100: squareY += preference[k][j]*preference[k][j];101: sumXTimesY += preference[i][j]*preference[k][j];102: }103: // System.out.println(t);
104: // System.out.println(pearCorrelation[t]);
105: pearCorrelation[t].userID = k;106: int n = preference[0].length;
107: //公式4,不用2,怕出現大數
108: double temp = (sumXTimesY - sumX*sumY/n)
109: /(Math.sqrt((squareX - sumX*sumX/n)*(squareY - sumY*sumY/n)));110: pearCorrelation[t].correlation = temp;111: sumY = 0;112: squareY = 0;113: sumXTimesY = 0;114: t++;115: }116: }117: return pearCorrelation;
118: }119:120: public int[][] readFile(String filename) {121: File file = new File(filename);
122: FileReader fr;123: int i = 0;
124: String line = "";125: //想讓數據的列等於數據列,找到了方法,但感覺還不如直接寫呢
126: //行:br.readLine().split(",").length;
127: //列:幾次br.readLine()就幾次
128: int[][] preference = new int[20][8];129: try {
130: fr = new FileReader(file);
131: BufferedReader br = new BufferedReader(fr);
132: String[] data = new String[10];
133: while(br.ready()) {
134: data = br.readLine().split(",");
135: for(int j=0; j<data.length; j++) {136: preference[i][j] = Integer.parseInt(data[j]);137: }138: i++;139: }140: } catch (FileNotFoundException e) {
141: // TODO Auto-generated catch block
142: e.printStackTrace();143: } catch (IOException e) {
144: // TODO Auto-generated catch block
145: e.printStackTrace();146: }147: return preference;
148: }149:150: }151: class Point implements Comparable<Point>{152: double correlation;
153: int userID;
154: public Point(int correlation, int userID) {155: super();
156: this.correlation = correlation;
157: this.userID = userID;
158: }159: public Point() {
160: super();
161: this.correlation = 0.0;
162: this.userID = 0;
163: }164: @Override165: public int compareTo(Point o) {166: // TODO Auto-generated method stub
167: //向上取整
168: return (int)Math.ceil(o.correlation - this.correlation);169: }170:171: }172: class Node implements Comparable<Node> {173: int itemId;
174: double recommendation;
175: public Node(int itemId, double recommendation) {176: super();
177: this.itemId = itemId;
178: this.recommendation = recommendation;
179: }180: public Node() {
181: super();
182: this.itemId = 0;
183: this.recommendation = 0.0;
184: }185: @Override186: public int compareTo(Node o) {187: // TODO Auto-generated method stub
188: return (int)Math.ceil(o.recommendation - this.recommendation);189: }190: }191:192:
五、算法結果
1: 第0個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.02: 0物品,推薦度為0.03: 0物品,推薦度為0.04: 第1個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.05: 0物品,推薦度為0.06: 0物品,推薦度為0.07: 第2個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.08: 0物品,推薦度為0.09: 0物品,推薦度為0.010: 第3個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.011: 0物品,推薦度為0.012: 0物品,推薦度為0.013: 第4個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.014: 0物品,推薦度為0.015: 0物品,推薦度為0.016: 第5個用戶的前三個推薦物品及推薦度為:0物品,推薦度為-1.206580159897706217: 1物品,推薦度為-0.625067925235103218: 0物品,推薦度為0.019: 第6個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.020: 0物品,推薦度為0.021: 0物品,推薦度為0.022: 第7個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.023: 0物品,推薦度為0.024: 0物品,推薦度為0.025: 第8個用戶的前三個推薦物品及推薦度為:7物品,推薦度為4.90086148937954326: 2物品,推薦度為2.292647993022594527: 3物品,推薦度為0.936207688524135228: 第9個用戶的前三個推薦物品及推薦度為:6物品,推薦度為3.420827595753851629: 3物品,推薦度為2.01153530442974130: 0物品,推薦度為0.031: 第10個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.032: 0物品,推薦度為0.033: 0物品,推薦度為0.034: 第11個用戶的前三個推薦物品及推薦度為:3物品,推薦度為1.65391251718069435: 4物品,推薦度為-0.2171047342971895236: 5物品,推薦度為-0.0864025491156605937: 第12個用戶的前三個推薦物品及推薦度為:2物品,推薦度為0.633099774172628338: 3物品,推薦度為0.05647608588995267639: 0物品,推薦度為0.040: 第13個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.041: 0物品,推薦度為0.042: 0物品,推薦度為0.043: 第14個用戶的前三個推薦物品及推薦度為:1物品,推薦度為0.2181654378375201844: 0物品,推薦度為0.045: 0物品,推薦度為0.046: 第15個用戶的前三個推薦物品及推薦度為:4物品,推薦度為0.663465962268607747: 0物品,推薦度為0.048: 0物品,推薦度為0.049: 第16個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.050: 0物品,推薦度為0.051: 0物品,推薦度為0.052: 第17個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.053: 0物品,推薦度為0.054: 0物品,推薦度為0.055: 第18個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.056: 0物品,推薦度為0.057: 0物品,推薦度為0.058: 第19個用戶的前三個推薦物品及推薦度為:0物品,推薦度為0.059: 0物品,推薦度為0.060: 0物品,推薦度為0.061:
六、SlopeOne算法簡介
用戶Z對事物B的打分可能是多少呢?股票上有個說法是平均值可以掩蓋一切異常波動,所以股票上的各個技術指標收拾不同時間段的平均值的曲線圖或者柱狀圖等。同樣的,Slope One算法也認為:平均值也可以代替某兩個未知個體之間的打分差異,事物A對事物B的平均很差是:((3 - 4) + (2 - 4)) / 2 = -1.5,也就是說人們對事物B的打分一般比事物A的打分要高1.5,於是Slope one算法就猜測Z對事物B的打分是4 + 1.5 = 5.5。
有n個人對事物A和事物B打分了,R(A->B)表示這n個人對A和對B打分的平均差(A-B),有m個人對事物B和事物C打分 了,R(B->C)表示這m個人對B和對C打分的平均差(B-C),注意都是平均差而不是平方差,現在某個用戶對A的打分是ra,對C的打分是 rc,那么A對B的打分可能是:
rb = (n * (ra - R(A->B)) + m * (rc + R(B->C)))/(m+n) 。