歡迎來到機器學習工程師納米學位的第三個項目!在這個notebook文件中,有些模板代碼已經提供給你,但你還需要實現更多的功能來完成這個項目。除非有明確要求,你無須修改任何已給出的代碼。以'練習'開始的標題表示接下來的代碼部分中有你必須要實現的功能。每一部分都會有詳細的指導,需要實現的部分也會在註釋中以'TODO'標出。請仔細閱讀所有的提示!
除了實現代碼外,你還必須回答一些與項目和你的實現有關的問題。每一個需要你回答的問題都會以'問題 X'爲標題。請仔細閱讀每個問題,並且在問題後的'回答'文字框中寫出完整的答案。我們將根據你對問題的回答和撰寫代碼所實現的功能來對你提交的項目進行評分。
提示:Code 和 Markdown 區域可通過 Shift + Enter 快捷鍵運行。此外,Markdown可以通過雙擊進入編輯模式。
開始
在這個項目中,你將分析一個數據集的內在結構,這個數據集包含很多客戶真對不同類型產品的年度採購額(用金額表示)。這個項目的任務之一是如何最好地描述一個批發商不同種類顧客之間的差異。這樣做將能夠使得批發商能夠更好的組織他們的物流服務以滿足每個客戶的需求。
這個項目的數據集能夠在UCI機器學習信息庫中找到.因爲這個項目的目的,分析將不會包括'Channel'和'Region'這兩個特徵——重點集中在6個記錄的客戶購買的產品類別上。
運行下面的的代碼單元以載入整個客戶數據集和一些這個項目需要的Python庫。如果你的數據集載入成功,你將看到後面輸出數據集的大小。
# 檢查你的Python版本
from sys import version_info
if version_info.major != 2 and version_info.minor != 7:
raise Exception('請使用Python 2.7來完成此項目')
# 引入這個項目需要的庫
import numpy as np
import pandas as pd
import visuals as vs
from IPython.display import display # 使得我們可以對DataFrame使用display()函數
# 設置以內聯的形式顯示matplotlib繪製的圖片(在notebook中顯示更美觀)
%matplotlib inline
# 載入整個客戶數據集
try:
data = pd.read_csv("customers.csv")
data.drop(['Region', 'Channel'], axis = 1, inplace = True)
#這裏的inplace參數代表
print "Wholesale customers dataset has {} samples with {} features each.".format(*data.shape)
except:
print "Dataset could not be loaded. Is the dataset missing?"
# 顯示數據集的一個描述
display(data.describe())
# TODO:從數據集中選擇三個你希望抽樣的數據點的索引
indices = [1,14,168]
# 爲選擇的樣本建立一個DataFrame
samples = pd.DataFrame(data.loc[indices], columns = data.keys()).reset_index(drop = True)
print "Chosen samples of wholesale customers dataset:"
display(samples)
回答:第一個樣本大概率是咖啡館,因爲milk和Grocery的購買額高於均值,但明顯不偏好fresh,這與我們的常識一致; 第二個樣本大概率是餐廳,因爲生鮮和食雜的額度大大高於均值; 第三個樣本大概率是生鮮和凍品類的零售客戶,因爲各項購買額均小於均值,但生鮮和凍品有所偏好。
練習: 特徵相關性
一個有趣的想法是,考慮這六個類別中的一個(或者多個)產品類別,是否對於理解客戶的購買行爲具有實際的相關性。也就是說,當用戶購買了一定數量的某一類產品,我們是否能夠確定他們必然會成比例地購買另一種類的產品。有一個簡單的方法可以檢測相關性:我們用移除了某一個特徵之後的數據集來構建一個監督學習(迴歸)模型,然後用這個模型去預測那個被移除的特徵,再對這個預測結果進行評分,看看預測結果如何。
在下面的代碼單元中,你需要實現以下的功能:
- 使用
DataFrame.drop
函數移除數據集中你選擇的不需要的特徵,並將移除後的結果賦值給new_data
。 - 使用
sklearn.model_selection.train_test_split
將數據集分割成訓練集和測試集。- 使用移除的特徵作爲你的目標標籤。設置
test_size
爲0.25
並設置一個random_state
。
- 使用移除的特徵作爲你的目標標籤。設置
- 導入一個DecisionTreeRegressor(決策樹迴歸器),設置一個
random_state
,然後用訓練集訓練它。 - 使用迴歸器的
score
函數輸出模型在測試集上的預測得分。
# TODO:爲DataFrame創建一個副本,用'drop'函數丟棄一個特徵
print data.keys()
y_data = data['Detergents_Paper']
new_data = data.drop('Detergents_Paper', axis = 1)
display (new_data.describe())
# TODO:使用給定的特徵作爲目標,將數據分割成訓練集和測試集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(new_data, y_data, test_size=0.25, random_state=40)
# TODO:創建一個DecisionTreeRegressor(決策樹迴歸器)並在訓練集上訓練它
from sklearn.tree import DecisionTreeRegressor
regressor = DecisionTreeRegressor(random_state=0)
regressor.fit(X_train,y_train)
# TODO:輸出在測試集上的預測得分
score = regressor.score(X_test, y_test)
print score
回答:0.788.當選擇'Detergents_Paper'特徵作爲標籤時,得到這個分數。曾嘗試過Fresh,但決定係數爲負值。明顯Detergents_Paper'這個特徵對於區分用戶的消費習慣來說不是必要的,該特徵與其它特徵的有一定相關性,決定係數較高,該變量可以被其他變量來預測,說明這個變量可以通過其他變量通過一些變化得到。
# 對於數據中的每一對特徵構造一個散佈矩陣
pd.plotting.scatter_matrix(data, alpha = 0.3, figsize = (14,8), diagonal = 'kde');
import seaborn as sns
sns.heatmap(data.corr(), annot=True)
回答:我認爲存在一定特徵相關性。如'Detergents_Paper'和milk,'Detergents_Paper'和Grocery,Grocery和Milk具有明顯相關性。 不是正態分佈的,在上圖矩陣的對角線上,可以呈現出正偏態分佈特徵。
練習: 特徵縮放
如果數據不是正態分佈的,尤其是數據的平均數和中位數相差很大的時候(表示數據非常歪斜)。這時候通常用一個非線性的縮放是很合適的,(英文原文) — 尤其是對於金融數據。一種實現這個縮放的方法是使用Box-Cox 變換,這個方法能夠計算出能夠最佳減小數據傾斜的指數變換方法。一個比較簡單的並且在大多數情況下都適用的方法是使用自然對數。
在下面的代碼單元中,你將需要實現以下功能:
- 使用
np.log
函數在數據data
上做一個對數縮放,然後將它的副本(不改變原始data的值)賦值給log_data
。 - 使用
np.log
函數在樣本數據samples
上做一個對數縮放,然後將它的副本賦值給log_samples
。
# TODO:使用自然對數縮放數據
log_data = np.log(data)
# TODO:使用自然對數縮放樣本數據
log_samples = np.log(samples)
# 爲每一對新產生的特徵製作一個散射矩陣
pd.plotting.scatter_matrix(log_data, alpha = 0.3, figsize = (14,8), diagonal = 'kde');
# 展示經過對數變換後的樣本數據
display(log_samples)
練習: 異常值檢測
對於任何的分析,在數據預處理的過程中檢測數據中的異常值都是非常重要的一步。異常值的出現會使得把這些值考慮進去後結果出現傾斜。這裏有很多關於怎樣定義什麼是數據集中的異常值的經驗法則。這裏我們將使用Tukey的定義異常值的方法:一個異常階(outlier step)被定義成1.5倍的四分位距(interquartile range,IQR)。一個數據點如果某個特徵包含在該特徵的IQR之外的特徵,那麼該數據點被認定爲異常點。
在下面的代碼單元中,你需要完成下面的功能:
- 將指定特徵的25th分位點的值分配給
Q1
。使用np.percentile
來完成這個功能。 - 將指定特徵的75th分位點的值分配給
Q3
。同樣的,使用np.percentile
來完成這個功能。 - 將指定特徵的異常階的計算結果賦值給
step
. - 選擇性地通過將索引添加到
outliers
列表中,以移除異常值。
注意: 如果你選擇移除異常值,請保證你選擇的樣本點不在這些移除的點當中! 一旦你完成了這些功能,數據集將存儲在good_data
中。
# 對於每一個特徵,找到值異常高或者是異常低的數據點
bad_data_index = []
for feature in log_data.keys():
# TODO:計算給定特徵的Q1(數據的25th分位點)
Q1 = np.percentile(log_data[feature],25)
# TODO:計算給定特徵的Q3(數據的75th分位點)
Q3 = np.percentile(log_data[feature],75)
# TODO:使用四分位範圍計算異常階(1.5倍的四分位距)
step = 1.5*(Q3-Q1)
# 顯示異常點
print "Data points considered outliers for the feature '{}':".format(feature)
bad_data =log_data[~((log_data[feature] >= Q1 - step) & (log_data[feature] <= Q3 + step))]
display(bad_data)
bad_data_index.extend(list(bad_data.index))
a = list(set(bad_data_index))
b = list(np.sort(a))
for i in b:
bad_data_index.remove(i)
print list(bad_data_index)
# 可選:選擇你希望移除的數據點的索引
outliers = [65,66,75,128,154]
bad_samples = pd.DataFrame(data.loc[outliers], columns = data.keys())
display(bad_samples)
# 如果選擇了的話,移除異常點
good_data = log_data.drop(log_data.index[outliers]).reset_index(drop = True)
回答:65,66,75,128,154.這些點在多個特徵中都顯示爲異常數據,如果僅從單一特徵判斷異常數據的話,那異常數據量達到了40餘個,相對於總數量440的樣本來說,佔比過大,一旦移除之後,容易造成信息損失,因此判斷2個以上特徵同時異常才判斷爲異常數據是合理的,我認爲應當被移除。
練習: 主成分分析(PCA)
既然數據被縮放到一個更加正態分佈的範圍中並且我們也移除了需要移除的異常點,我們現在就能夠在good_data
上使用PCA算法以發現數據的哪一個維度能夠最大化特徵的方差。除了找到這些維度,PCA也將報告每一個維度的解釋方差比(explained variance ratio)--這個數據有多少方差能夠用這個單獨的維度來解釋。注意PCA的一個組成部分(維度)能夠被看做這個空間中的一個新的“特徵”,但是它是原來數據中的特徵構成的。
在下面的代碼單元中,你將要實現下面的功能:
- 導入
sklearn.decomposition.PCA
並且將good_data
用PCA並且使用6個維度進行擬合後的結果保存到pca
中。 - 使用
pca.transform
將log_samples
進行轉換,並將結果存儲到pca_samples
中。
# TODO:通過在good_data上使用PCA,將其轉換成和當前特徵數一樣多的維度
from sklearn.decomposition import PCA
pca = PCA(n_components=6)
pca.fit(good_data)
# TODO:使用上面的PCA擬合將變換施加在log_samples上
pca_samples = pca.transform(log_samples)
# 生成PCA的結果圖
pca_results = vs.pca_results(good_data, pca)
問題 5
數據的第一個和第二個主成分 總共 表示了多少的方差? 前四個主成分呢?使用上面提供的可視化圖像,討論從用戶花費的角度來看前四個主要成分的消費行爲最能代表哪種類型的客戶並給出你做出判斷的理由。
提示: 某一特定維度上的正向增長對應正權特徵的增長和負權特徵的減少。增長和減少的速率和每個特徵的權重相關。參考資料(英文)。
回答: 前兩個主成分0.72;前4個主成分表示了0.93的方差。其中後三個主成分有一定的相關性。通過第一主成分,第二三四主成分能明顯區別兩類用戶。 第一個最大可能是咖啡店,因爲該類客戶特徵權重最大的是負權特徵是洗滌用品、其次是食雜,說明該類客戶同時購買洗滌、食雜和牛奶的可能性較高; 第二個最大可能是食品零售店,以生鮮、凍品、熟食的權重絕對值較大,說明該類客戶同時購買此類產品的偏好較高; 第三個主成分在第二個的基礎上,最大的兩個權重,熟食正相關,生鮮負相關,說明此類客戶傾向於購買熟食和凍品,但同時不會購買生鮮的可能性較高。 第四個可能是凍品零售商,因爲凍品的正相關較高,同時不會購買熟食的可能性較高。
# 展示經過PCA轉換的sample log-data
display(pd.DataFrame(np.round(pca_samples, 4), columns = pca_results.index.values))
練習:降維
當使用主成分分析的時候,一個主要的目的是減少數據的維度,這實際上降低了問題的複雜度。當然降維也是需要一定代價的:更少的維度能夠表示的數據中的總方差更少。因爲這個,累計解釋方差比(cumulative explained variance ratio)對於我們確定這個問題需要多少維度非常重要。另外,如果大部分的方差都能夠通過兩個或者是三個維度進行表示的話,降維之後的數據能夠被可視化。
在下面的代碼單元中,你將實現下面的功能:
- 將
good_data
用兩個維度的PCA進行擬合,並將結果存儲到pca
中去。 - 使用
pca.transform
將good_data
進行轉換,並將結果存儲在reduced_data
中。 - 使用
pca.transform
將log_samples
進行轉換,並將結果存儲在pca_samples
中。
# TODO:通過在good data上進行PCA,將其轉換成兩個維度
pca = PCA(n_components=2)
pca.fit(good_data)
# TODO:使用上面訓練的PCA將good data進行轉換
reduced_data = pca.transform(good_data)
# TODO:使用上面訓練的PCA將log_samples進行轉換
pca_samples = pca.transform(log_samples)
# 爲降維後的數據創建一個DataFrame
reduced_data = pd.DataFrame(reduced_data, columns = ['Dimension 1', 'Dimension 2'])
# 展示經過兩個維度的PCA轉換之後的樣本log-data
display(pd.DataFrame(np.round(pca_samples, 4), columns = ['Dimension 1', 'Dimension 2']))
# Create a biplot
vs.biplot(good_data, reduced_data, pca)
回答:K-Means優點是:計算速度快、時間短,易解釋,聚類效果還不錯;但缺點主要是需要提前確定K值,對異常值極度敏感。 高斯混合模型聚類算法的優點是聚類輸出的信息量更大,理論上可以擬合任何連續的概率密度函數。 我會選高斯混合模型,因爲現有客戶數據在做了Box-Cox 變換後,在各個維度都呈現了較爲明顯的正態分佈特徵。
練習: 創建聚類
針對不同情況,有些問題你需要的聚類數目可能是已知的。但是在聚類數目不作爲一個先驗知道的情況下,我們並不能夠保證某個聚類的數目對這個數據是最優的,因爲我們對於數據的結構(如果存在的話)是不清楚的。但是,我們可以通過計算每一個簇中點的輪廓係數來衡量聚類的質量。數據點的輪廓係數衡量了它與分配給他的簇的相似度,這個值範圍在-1(不相似)到1(相似)。平均輪廓係數爲我們提供了一種簡單地度量聚類質量的方法。
在接下來的代碼單元中,你將實現下列功能:
- 在
reduced_data
上使用一個聚類算法,並將結果賦值到clusterer
,需要設置random_state
使得結果可以復現。 - 使用
clusterer.predict
預測reduced_data
中的每一個點的簇,並將結果賦值到preds
。 - 使用算法的某個屬性值找到聚類中心,並將它們賦值到
centers
。 - 預測
pca_samples
中的每一個樣本點的類別並將結果賦值到sample_preds
。 - 導入sklearn.metrics.silhouette_score包並計算
reduced_data
相對於preds
的輪廓係數。- 將輪廓係數賦值給
score
並輸出結果。
- 將輪廓係數賦值給
# TODO:在降維後的數據上使用你選擇的聚類算法
from sklearn.mixture import GaussianMixture
clusterer = GaussianMixture(n_components=2,random_state = 40)
clusterer.fit(reduced_data)
# TODO:預測每一個點的簇
preds = clusterer.predict(reduced_data)
# TODO:找到聚類中心
centers = clusterer.means_
# TODO:預測在每一個轉換後的樣本點的類
sample_preds = clusterer.predict(pca_samples)
from sklearn.metrics import silhouette_score
# TODO:計算選擇的類別的平均輪廓係數(mean silhouette coefficient)
score = silhouette_score(reduced_data,preds)
print score
回答:聚類數目爲2時,輪廓係數達到了0.42最高。當聚類數目爲3時,輪廓係數達到了0.32.當聚類數目爲6時,輪廓係數爲0.31.
# 從已有的實現中展示聚類的結果
vs.cluster_results(reduced_data, preds, centers, pca_samples)
# TODO:反向轉換中心點
log_centers = pca.inverse_transform(centers)
# TODO:對中心點做指數轉換
true_centers = np.exp(log_centers)
# 顯示真實的中心點
segments = ['Segment {}'.format(i) for i in range(0,len(centers))]
true_centers = pd.DataFrame(np.round(true_centers), columns = data.keys())
true_centers.index = segments
display(true_centers)
回答:我認爲0代表餐廳,1代表超市。因爲第一個數據點的Grocery和Detergents——paper開支較高,高於總體中值;而第二個數據點的各項開支基本符合一個超市類零售商的消費特徵。
# 顯示預測結果
for i, pred in enumerate(sample_preds):
print "Sample point", i, "predicted to be in Cluster", pred
回答:cluster0,也就是餐廳。結果不太相符。因爲之前的預測更爲細緻,目前的算法只輸出2類。
問題 10
在對他們的服務或者是產品做細微的改變的時候,公司經常會使用A/B tests以確定這些改變會對客戶產生積極作用還是消極作用。這個批發商希望考慮將他的派送服務從每週5天變爲每週3天,但是他只會對他客戶當中對此有積極反饋的客戶採用。這個批發商應該如何利用客戶分類來知道哪些客戶對它的這個派送策略的改變有積極的反饋,如果有的話?你需要給出在這個情形下A/B 測試具體的實現方法,以及最終得出結論的依據是什麼?
提示: 我們能假設這個改變對所有的客戶影響都一致嗎?我們怎樣才能夠確定它對於哪個類型的客戶影響最大?
回答:對兩組客戶分別分爲A1;A2,B1,B2組;對A1參考組,A2實驗組組分別應用每週5天和每週3天的配送服務,分別記錄採用不同配送服務時的周均銷售額對比,如果採用A2實驗組採用新配送服務的銷售額較高,說明該類服務對該類客戶組有積極作用,則對所在A1A2組採用新配送服務,否則採用原配送服務;同理可對B1,B2組採取同樣方法。如果新配送服務對兩組都有積極反饋,則看實驗組和參考組的銷售額上升程度,上升較多的那一組客戶爲影響最大的客戶。
問題 11
通過聚類技術,我們能夠將原有的沒有標記的數據集中的附加結構分析出來。因爲每一個客戶都有一個最佳的劃分(取決於你選擇使用的聚類算法),我們可以把用戶分類作爲數據的一個工程特徵。假設批發商最近迎來十位新顧客,並且他已經爲每位顧客每個產品類別年度採購額進行了預估。進行了這些估算之後,批發商該如何運用它的預估和非監督學習的結果來對這十個新的客戶進行更好的預測?
提示:在下面的代碼單元中,我們提供了一個已經做好聚類的數據(聚類結果爲數據中的cluster屬性),我們將在這個數據集上做一個小實驗。嘗試運行下面的代碼看看我們嘗試預測‘Region’的時候,如果存在聚類特徵'cluster'與不存在相比對最終的得分會有什麼影響?這對你有什麼啓發?
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# 讀取包含聚類結果的數據
cluster_data = pd.read_csv("cluster.csv")
y = cluster_data['Region']
X = cluster_data.drop(['Region'], axis = 1)
# 劃分訓練集測試集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=24)
clf = RandomForestClassifier(random_state=24)
clf.fit(X_train, y_train)
print "使用cluster特徵的得分", clf.score(X_test, y_test)
# 移除cluster特徵
X_train = X_train.copy()
X_train.drop(['cluster'], axis=1, inplace=True)
X_test = X_test.copy()
X_test.drop(['cluster'], axis=1, inplace=True)
clf.fit(X_train, y_train)
print "不使用cluster特徵的得分", clf.score(X_test, y_test)
回答:使用cluster特徵得分更高;使用聚類分析得到的結果,加入原有的監督學習的輸入特徵中,給監督學習增加了特徵信息,增加了特徵維度,有利於結果精度提高。
# 根據‘Channel‘數據顯示聚類的結果
vs.channel_results(reduced_data, outliers, pca_samples)
回答:基本一致,我認爲沒有哪個簇能夠剛好劃分這兩類,因爲彼此的間隔並不清晰。這個分類和前面的定義基本一致。