Spark與機器學習----資料的獲取、處理與準備
1. 資料獲取
常用公開資料集:
- UCL機器學習知識庫: 包括近300個不同大小和型別的資料集,可用於分類、迴歸、聚類和推薦系統任務。資料集列表位於: http://archive.ics.uci.edu/ml/。
- Amazon AWS公開資料集: 包含的通常是大型資料集,可通過Amazon S3訪問。相關資訊可參見: http://aws.amazon.com/publicdatasets/。
- Kaggle: 這裡集合了Kaggle舉行的各種機器學習競賽所用的資料集。下載:http://www.kaggle.com/competitions。
- KDnuggets: 這裡包含一個詳細的公開資料集列表,其中一些上面提到過的。該列表位於: http://www.kdnuggets.com/datasets/index.html。
2. 探索與視覺化資料
載入資料,通過IPython notebook的視覺化程式設計,來分析資料結構,統計資料分佈,瞭解資料的缺失性,為資料的整理和清洗做前期準備工作。可通過Matplotlib對資料生成統計圖表,瞭解資料的分佈情況。
數值型資料通過統計:數量、最大值、最小值、平均值、中值、方差等數學指標瞭解其特徵
類別信資料通過統計:數量、類別分佈等資訊瞭解其特徵。資料探索可通過Spark中對RDD操作API函式實現。
3. 資料的處理與轉換
為讓原始資料可用於機器學習演算法,需要先對其進行清理,並可能需要將其進行各種轉換,之後才能從轉換後的資料裡提取有用的特徵。資料的轉換和特徵提取聯絡緊密。某些情況下,一些轉換本身便是特徵提取的過程。一般來說,現實中的資料會存在資訊不規整、資料點缺失和異常值問題。理想情況下,我們會修復非規整資料。大致的處理方法如下:
- 過濾掉或刪除非規整或有值缺失的資料: 這通常是必須的,但的確會損失這些資料裡那些好的資訊。
- 填充非規整或缺失的資料: 可以根據其他的資料來填充非規整或缺失的資料。方法包括用零值、全域性期望或中值來填充,或是根據相鄰或類似的資料點來做插值(通常針對時序資料)等。選擇正確的方式並不容易,它會因資料、應用場景和個人經驗而不同。
- 對異常值做魯棒處理: 異常值的主要問題在於即使它們是極值也不一定就是錯的。到底是對是錯通常很難分辨。異常值可被移除或是填充,但的確存在某些統計技術(如魯棒迴歸)可用於處理異常值或是極值。
- 對可能的異常值進行轉換: 另一種處理異常值或極值的方法是進行轉換。對那些可能存在異常值或值域覆蓋過大的特徵,利用如對數或高斯核對其轉換。這類轉換有助於降低變數存在的值跳躍的影響,並將非線性關係變為線性的。
4. 資料中提取有用特徵
特徵(feature) 指那些用於模型訓練的變數。特徵可以概括地分為如下幾種:
- 數值特徵( numerical feature): 這些特徵通常為實數或整數,比如年齡。
- 類別特徵( categorical feature): 它們的取值只能是可能狀態集合中的某一種。我們資料集中的使用者性別、職業或電影類別便是這類。
- 文字特徵( text feature): 它們派生自資料中的文字內容,比如名稱、描述或是評論。
- 其他特徵: 大部分其他特徵都最終表示為數值。比如影象、視訊和音訊可被表示為數值資料的集合。地理位置則可由經緯度或地理雜湊(geohash)表示。
4.1. 數值特徵
原始的數值和一個數值特徵之間的區別是什麼?實際上,任何數值資料都能作為輸入變數。但是,機器學習模型中所學習的是各個特徵所對應的向量的權值。這些權值在特徵值到輸出或是目標變數(指在監督學習模型中)的對映過程中扮演重要角色。由此我們會想使用那些合理的特徵,讓模型能從這些特徵學到特徵值和目標變數之間的關係。比如年齡就是一個合理的特徵。年齡的增加和某項支出之間可能就存在直接關係。
當數值特徵仍處於原始形式時,其可用性相對較低,但可以轉化為更有用的表示形式。位置資訊便是如此。若使用原始位置資訊(比如用經緯度表示的),我們的模型可能學習不到該資訊和某個輸出之間的有用關係,這就使得該資訊的可用性不高。然而若對位置進行聚合或挑選後(比如聚焦為一個城市或國家),便容易和特定輸出之間存在某種關聯了。
4.2. 類別特徵
當類別特徵仍為原始形式時,其取值來自所有可能取值所構成的集合而不是一個數字,故不能作為輸入。
名義(nominal)變數,其各個可能取值之間沒有順序關係(比如職業)
有序(ordinal)變數,其值存在順序關係(比如評級)
將類別特徵表示為數字形式,常可藉助k之1(1-of-k)方法進行。將名義變量表示為可用於機器學習任務的形式,會需要藉助如k之1編碼這樣的方法。有序變數的原始值可能就能直接使用,但也常會經過和名義變數一樣的編碼處理。
假設變數可取的值有k個。如果對這些值用1到k編序,則可以用長度為k的二元向量來表示一個變數的取值。在這個向量裡,該取值對應的序號所在的元素為1,其他元素都為0。
4.3. 派生特徵
從現有的一個或多個變數派生出新的特徵常常是有幫助的。理想情況下,派生出的特徵能比原始屬性帶來更多資訊。從原始資料派生特徵的例子包括計算平均值、中位值、方差、和、差、最大值或最小值以及計數。數值特徵到類別特徵的轉換也很常見,比如劃分為區間特徵。進行這類轉換的變數常見的有年齡、地理位置和時間。
def assign_tod(hr):
times_of_day = {
'morning' : range(7, 12),
'lunch' : range(12, 14),
'afternoon' : range(14, 18),
'evening' : range(18, 23),
'night' : range(23, 7)
}
for k, v in times_of_day.iteritems():
if hr in v:
return k
timestamps = rating_data.map(lambda fields: int(fields[3]))
hour_of_day = timestamps.map(lambda ts: extract_datetime(ts).hour)
time_of_day = hour_of_day.map(lambda hr: assign_tod(hr))
time_of_day.take(5)
4.4. 文字特徵
從某種意義上說,文字特徵也是一種類別特徵或派生特徵。文字的處理方式有很多種。自然語言處理便是專注於文字內容的處理、表示和建模的一個領域。
詞袋法將一段文字視為由其中的文字或數字組成的集合,其處理過程如下:
- 分詞( tokenization): 首先會應用某些分詞方法來將文字分隔為一個由詞(一般如單詞、數字等)組成的集合。
- 刪除停用詞( stop words removal): 之後,它通常會刪除常見的單詞,比如the、 and和but(這些詞被稱作停用詞)。
- 提取詞幹( stemming): 下一步則是詞幹的提取。這是指將各個詞簡化為其基本的形式或者幹詞。常見的例子如複數變為單數(比如dogs變為dog等)
- 向量化( vectorization): 最後一步就是用向量來表示處理好的詞。二元向量可能是最為簡單的表示方式。它用1和0來分別表示是否存在某個詞。從根本上說,這與之前提到的k之1編碼相同。
# 下面用簡單空白分詞法將標題分詞為詞
title_terms = movie_titles.map(lambda t: t.split(" "))
# 下面取回所有可能的詞,以便構建一個詞到序號的對映字典
all_terms = title_terms.flatMap(lambda x: x).distinct().collect()
idx = 0
all_terms_dict = {}
for term in all_terms:
all_terms_dict[term] = idx
idx +=1
#以下程式碼達到上面相同效果
all_terms_dict2 = title_terms.flatMap(lambda x: x).distinct().zipWithIndex().collectAsMap()
# 該函式輸入一個詞列表,
# 並用k之1編碼類似的方式將其編碼為一個scipy稀疏向量
def create_vector(terms, term_dict):
from scipy import sparse as sp
num_terms = len(term_dict)
x = sp.csc_matrix((1, num_terms))
for t in terms:
if t in term_dict:
idx = term_dict[t]
x[0, idx] = 1
return x
#程式碼中用Spark的broadcast函式來建立了一個包含詞字典的廣播變數。
#現實場景中該字典可能會極大,故適合使用廣播變數。
all_terms_bcast = sc.broadcast(all_terms_dict)
term_vectors = title_terms.map(lambda terms: create_vector(terms, all_terms_bcast.value))
4.5. 正則化特徵
在將特徵提取為向量形式後,一種常見的預處理方式是將數值資料正則化(normalization)。其背後的思想是將各個數值特徵進行轉換,以將它們的值域規範到一個標準區間內。正則化的方法有如下幾種:
- 正則化特徵:這實際上是對資料集中的單個特徵進行轉換。比如減去平均值(特徵對齊)或是進行標準的正則轉換(以使得該特徵的平均值和標準差分別為0和1)
- 正則化特徵向量:這通常是對資料中的某一行的所有特徵進行轉換,以讓轉換後的特徵向量的長度標準化。也就是縮放向量中的各個特徵以使得向量的範數為1(常指一階或二階範數)。
#向量正則化可通過numpy的norm函式來實現
np.random.seed(42)
x = np.random.randn(10)
norm_x_2 = np.linalg.norm(x)
normalized_x = x / norm_x_2
#Spark在其MLlib機器學習庫中內建了一些函式用於特徵的縮放和標準化。
#它們包括供標準正態變換的StandardScaler,
#以及提供與上述相同的特徵向量正則化的Normalizer。
from pyspark.mllib.feature import Normalizer
normalizer = Normalizer()
vector = sc.parallelize([x])
normalized_x_mllib = normalizer.transform(vector).first().toArray()
4.6. 用軟體包提取特徵
我們可以通過開發的軟體包,藉助其中完善的工具箱來實現特徵的處理和提取,以及向量表示。特徵提取可藉助的軟體包有scikit-learn、gensim、 scikit-image、 matplotlib、 Python的NLTK、 Java編寫的OpenNLP以及用Scala編寫的Breeze和Chalk。