導讀:隨着大數據時代浪潮的到來數據科學家這一新興職業也越來越受到人們的關注。本文作者Alexandru Nedelcu就將數學挖掘算法與大數據有機的結合起來,並無縫的應用在面臨大數據浪潮的網站之中。
數據科學家需要具備專業領域知識並研究相應的算法以分析對應的問題,而數據挖掘是其必須掌握的重要技術。以幫助創建推動業務發展的相應大數據產品和大數據解決方案。EMC最近的一項調查也證實了這點。調查結果顯示83%的人認爲大數據浪潮所催生的新技術增加了數據科學家的需求。本文將爲您展示如何基於一個簡單的公式查找相關的項目。請注意,此項技術適用於所有的網站(如亞馬遜),以個性化用戶體驗、提高轉換效率。
查找相關項問題
要想爲一個特定的項目查找相關項,就必須首先爲這兩個項目定義相關之處。而這些也正是你要解決的問題:
- 在博客上,你可能想以標籤的形式分享文章,或者對比查看同一個人閱讀過的文章
- 亞馬遜站點被稱爲“購買此商品的客戶還購買了”的部分
- 一個類似於IMDB(Internet Movie Database)的服務,可以根據用戶的評級,給出觀影指南建議
不論是標籤、購買的商品還是觀看的電影,我們都要對其進行分門別類。這裏我們將採用標籤的形式,因爲它很簡單,而且其公式也適用於更復雜的情形。
以幾何關係重定義問題
現在以我的博客爲例,來列舉一些標籤:
- ["API", "Algorithms", "Amazon", "Android", "Books", "Browser"]
好,我們來看看在歐式空間幾何學中如何表示這些標籤。
我們要排序或比較的每個項目在空間中以點表示,座標值(代表一個標籤)爲1(標記)或者0(未標記)。
因此,如果我們已經獲取了一篇標籤爲“API”和“Browser”的文章,那麼其關聯點是:
- [ 1, 0, 0, 0, 0, 1 ]
現在這些座標可以表示其它含義。例如,他們可以代表用戶。如果在你的系統中有6個用戶,其中2個用戶對一篇文章分別評了3星和5星,那麼你就可以針對此文章查看相關聯的點(請注意順序):
- [ 0, 3, 0, 0, 5, 0 ]
現在我們可以計算出相關矢量之間的夾角,以及這些點之間的距離。下面是它們在二維空間中的圖像:
歐式幾何空間距離
計算歐式幾何空間兩點之間距離的數學公式非常簡單。考慮相關兩點A、B之間的距離:
兩點之間的距離越近,它們的相關性越大。下面是Ruby代碼:
- # Returns the Euclidean distance between 2 points
- #
- # Params:
- # - a, b: list of coordinates (float or integer)
- #
- def euclidean_distance(a, b)
- sq = a.zip(b).map{|a,b| (a - b) ** 2}
- Math.sqrt(sq.inject(0) {|s,c| s + c})
- end
- # Returns the associated point of our tags_set, relative to our
- # tags_space.
- #
- # Params:
- # - http://www.89900.com
- # - tags_set: list of tags
- # - tags_space: _ordered_ list of tags
- def tags_to_point(tags_set, tags_space)
- tags_space.map{|c| tags_set.member?(c) ? 1 : 0}
- end
- # Returns other_items sorted by similarity to this_item
- # (most relevant are first in the returned list)
- #
- # Params:
- # - items: list of hashes that have [:tags]
- # - by_these_tags: list of tags to compare with
- def sort_by_similarity(items, by_these_tags)
- tags_space = by_these_tags + items.map{|x| x[:tags]}
- tags_space.flatten!.sort!.uniq!
- this_point = tags_to_point(by_these_tags, tags_space)
- other_points = items.map{|i|
- [i, tags_to_point(i[:tags], tags_space)]
- }
- similarities = other_points.map{|item, that_point|
- [item, euclidean_distance(this_point, that_point)]
- }
- sorted = similarities.sort {|a,b| a[1] <=> b[1]}
- return sorted.map{|point,s| point}
- End
這是一些示例代碼,你可以直接複製運行:
- # SAMPLE DATA
- all_articles = [
- {
- :article => "Data Mining: Finding Similar Items",
- :tags => ["Algorithms", "Programming", "Mining",
- "Python", "Ruby"]
- },
- {
- :article => "Blogging Platform for Hackers",
- :tags => ["Publishing", "Server", "Cloud", "Heroku",
- "Jekyll", "GAE"]
- },
- {
- :article => "UX Tip: Don't Hurt Me On Sign-Up",
- :tags => ["Web", "Design", "UX"]
- },
- {
- :article => "Crawling the Android Marketplace",
- :tags => ["Python", "Android", "Mining",
- "Web", "API"]
- }
- ]
- # SORTING these articles by similarity with an article
- # tagged with Publishing + Web + API
- #
- #
- # The list is returned in this order:
- #
- # 1. article: Crawling the Android Marketplace
- # similarity: 2.0
- #
- # 2. article: "UX Tip: Don't Hurt Me On Sign-Up"
- # similarity: 2.0
- #
- # 3. article: Blogging Platform for Hackers
- # similarity: http://www.gw005.com 2.645751
- #
- # 4. article: "Data Mining: Finding Similar Items"
- # similarity: 2.828427
- #
- sorted = sort_by_similarity(
- all_articles, ['Publishing', 'Web', 'API'])
- require 'yaml'
- puts YAML.dump(sorted)
你是否留意到我們之前選擇的數據存在一個缺陷?前兩篇文章對於標籤“["Publishing", "Web", "API"]”有着相同的歐氏幾何空間距離。
爲了更加形象化,我們來看看計算第一篇文章所用到的點:
- [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
- [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1]
只有四個座標值不同,我們再來看看第二篇文章所用到的點:
- [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
- [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
與第一篇文章相同,也只有4個座標值不同。歐氏空間距離的度量取決於點之間的差異。這也許不太好,因爲相對平均值而言,有更多或更少標籤的文章會處於不利地位。
餘弦相似度
這種方法與之前的方法類似,但更關注相似性。下面是公式:
下面是Ruby代碼:
- def dot_product(a, b)
- products = a.zip(b).map{|a, b| a * b}
- products.inject(0) {|s,p| s + p}
- end
- def magnitude(point)
- squares = point.map{|x| x ** 2}
- Math.sqrt(squares.inject(0) {|s, c| s + c})
- end
- # Returns the cosine of the angle between the vectors
- #associated with 2 points
- #
- # Params:
- # - a, b: list of coordinates (float or integer)
- #
- def cosine_similarity(a, b)
- dot_product(a, b) / (magnitude(a) * magnitude(b))
- end
對於以上示例,我們對文章進行分類得到:
- - article: Crawling the Android Marketplace
- similarity: 0.5163977794943222
- - article: "UX Tip: Don't Hurt Me On Sign-Up"
- similarity: 0.33333333333333337
- - article: Blogging Platform for Hackers
- similarity: 0.23570226039551587
- - article: "Data Mining: Finding Similar Items"
- similarity: 0.0
這種方法有了很大改善,我們的代碼可以很好地運行,但它依然存在問題。
示例中的問題:Tf-ldf權重
我們的數據很簡單,可以輕鬆地計算並作爲衡量的依據。如果不採用餘弦相似度,很可能會出現相同的結果。
Tf-ldf權重是一種解決方案。Tf-ldf是一個靜態統計量,用於權衡文本集合中的一個詞在一個文檔中的重要性。
根據Tf-ldff,我們可以爲座標值賦予獨特的值,而並非侷限於0和1.
對於我們剛纔示例中的簡單數據集,也許更簡單的度量方法更適合,比如Jaccard index也許會更好。
皮爾遜相關係數(Pearson Correlation Coefficient)
使用皮爾遜相關係數(Pearson Correlation Coefficient)尋找兩個項目之間的相似性略顯複雜,也並不是非常適用於我們的數據集合。
例如,我們在IMDB中有2個用戶。其中一個用戶名爲John,對五部電影做了評級:[1,2,3,4,5]。另一個用戶名爲Mary,對這五部電影也給出了評級:[4, 5, 6, 7, 8]。這兩個用戶非常相似,他們之間有一個完美的線性關係,Mary的評級都是在John的基礎上加3。
計算公式如下:
代碼如下:
- def pearson_score(a, b)
- n = a.length
- return 0 unless n > 0
- # summing the preferences
- sum1 = a.inject(0) {|sum, c| sum + c}
- sum2 = b.inject(0) {|sum, c| sum + c}
- # summing up the squares
- sum1_sq = a.inject(0) {|sum, c| sum + c ** 2}
- sum2_sq = b.inject(0) {|sum, c| sum + c ** 2}
- # summing up the product
- prod_sum = a.zip(b).inject(0) {|sum, ab| sum + ab[0] * ab[1]}
- # calculating the Pearson score
- num = prod_sum - (sum1 *sum2 / n)
- den = Math.sqrt((sum1_sq - (sum1 ** 2) / n) * (sum2_sq - (sum2 ** 2) / n))
- return 0 if den == 0
- return num / den
- end
- puts pearson_score([1,2,3,4,5], [4,5,6,7,8])
- # => 1.0
- puts pearson_score([1,2,3,4,5], [4,5,0,7,8])
- # => 0.5063696835418333
- puts pearson_score([1,2,3,4,5], [4,5,0,7,7])
- # => 0.4338609156373132
- puts pearson_score([1,2,3,4,5], [8,7,6,5,4])
- # => -1
曼哈頓距離算法
沒有放之四海而皆準的真理,我們所使用的公式取決於要處理的數據。下面我們簡要介紹一下曼哈頓距離算法。
曼哈頓距離算法計算兩點之間的網格距離,維基百科中的圖形完美詮釋了它與歐氏幾何距離的不同:
紅線、黃線和藍線是具有相同長度的曼哈頓距離,綠線代表歐氏幾何空間距離。(張志平/編譯)