机器学习纳米学位

非监督学习

项目 3: 创建用户分类

欢迎来到机器学习工程师纳米学位的第三个项目!在这个notebook文件中,有些模板代码已经提供给你,但你还需要实现更多的功能来完成这个项目。除非有明确要求,你无须修改任何已给出的代码。以'练习'开始的标题表示接下来的代码部分中有你必须要实现的功能。每一部分都会有详细的指导,需要实现的部分也会在注释中以'TODO'标出。请仔细阅读所有的提示!

除了实现代码外,你还必须回答一些与项目和你的实现有关的问题。每一个需要你回答的问题都会以'问题 X'为标题。请仔细阅读每个问题,并且在问题后的'回答'文字框中写出完整的答案。我们将根据你对问题的回答和撰写代码所实现的功能来对你提交的项目进行评分。

提示:Code 和 Markdown 区域可通过 Shift + Enter 快捷键运行。此外,Markdown可以通过双击进入编辑模式。

开始

在这个项目中,你将分析一个数据集的内在结构,这个数据集包含很多客户真对不同类型产品的年度采购额(用金额表示)。这个项目的任务之一是如何最好地描述一个批发商不同种类顾客之间的差异。这样做将能够使得批发商能够更好的组织他们的物流服务以满足每个客户的需求。

这个项目的数据集能够在UCI机器学习信息库中找到.因为这个项目的目的,分析将不会包括'Channel'和'Region'这两个特征——重点集中在6个记录的客户购买的产品类别上。

运行下面的的代码单元以载入整个客户数据集和一些这个项目需要的Python库。如果你的数据集载入成功,你将看到后面输出数据集的大小。


In [33]:
%%time

# 引入这个项目需要的库
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)
    print "Wholesale customers dataset has {} samples with {} features each.".format(*data.shape)
except:
    print "Dataset could not be loaded. Is the dataset missing?"


Wholesale customers dataset has 440 samples with 6 features each.
Wall time: 14 ms

分析数据

在这部分,你将开始分析数据,通过可视化和代码来理解每一个特征和其他特征的联系。你会看到关于数据集的统计描述,考虑每一个属性的相关性,然后从数据集中选择若干个样本数据点,你将在整个项目中一直跟踪研究这几个数据点。

运行下面的代码单元给出数据集的一个统计描述。注意这个数据集包含了6个重要的产品类型:'Fresh', 'Milk', 'Grocery', 'Frozen', 'Detergents_Paper''Delicatessen'。想一下这里每一个类型代表你会购买什么样的产品。


In [34]:
# 显示数据集的一个描述
display(data.describe())


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
count 440.000000 440.000000 440.000000 440.000000 440.000000 440.000000
mean 12000.297727 5796.265909 7951.277273 3071.931818 2881.493182 1524.870455
std 12647.328865 7380.377175 9503.162829 4854.673333 4767.854448 2820.105937
min 3.000000 55.000000 3.000000 25.000000 3.000000 3.000000
25% 3127.750000 1533.000000 2153.000000 742.250000 256.750000 408.250000
50% 8504.000000 3627.000000 4755.500000 1526.000000 816.500000 965.500000
75% 16933.750000 7190.250000 10655.750000 3554.250000 3922.000000 1820.250000
max 112151.000000 73498.000000 92780.000000 60869.000000 40827.000000 47943.000000

练习: 选择样本

为了对客户有一个更好的了解,并且了解代表他们的数据将会在这个分析过程中如何变换。最好是选择几个样本数据点,并且更为详细地分析它们。在下面的代码单元中,选择三个索引加入到索引列表indices中,这三个索引代表你要追踪的客户。我们建议你不断尝试,直到找到三个明显不同的客户。


In [35]:
# TODO:从数据集中选择三个你希望抽样的数据点的索引
indices = [90, 200, 265]

# 为选择的样本建立一个DataFrame
samples = pd.DataFrame(data.loc[indices], columns = data.keys()).reset_index(drop = True)
print "Chosen samples of wholesale customers dataset:"
display(samples)


Chosen samples of wholesale customers dataset:
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 11405 596 1638 3347 69 360
1 3067 13240 23127 3941 9959 731
2 5909 23527 13699 10155 830 3636

问题 1

在你看来你选择的这三个样本点分别代表什么类型的企业(客户)?对每一个你选择的样本客户,通过它在每一种产品类型上的花费与数据集的统计描述进行比较,给出你做上述判断的理由。

提示: 企业的类型包括超市、咖啡馆、零售商以及其他。注意不要使用具体企业的名字,比如说在描述一个餐饮业客户时,你不能使用麦当劳。

回答:

  1. 客户1:代表小区附近的生鲜店,小卖部(便利店)的客户。总体采购量较小,还以Fresh和Frozen为主。
    • Fresh和Frozen需求量较大,都超过了总体的75%;
    • 而其他种类的商品的消费量却较小,Milk,Grocery,Detergents_Paper,Delicatessen都不到总体的25%。说明,客户家中人数少,客户经常性买生鲜类产品。
  2. 客户2:代表超市的客户。各种商品的采购量都较大,非常适合去超市批量采购,会更加的优惠。
    • Milk,Grocery,Frozen,Detergents_Paper,Delicatessen需求量非常大,超过了总体的75%;
    • 只有Delicatessen低于50%。
  3. 客户3:代表超市的客户。类似客户2,各种商品的采购量都较大,非常适合去超市批量采购,会更加的优惠。
    • 这个客户Fresh、Milk、Grocery,Frozen,Delicatessen需求量都较大,都超过了总体的75%。
    • Detergents_Paper刚刚超过50%。

练习: 特征相关性

一个有趣的想法是,考虑这六个类别中的一个(或者多个)产品类别,是否对于理解客户的购买行为具有实际的相关性。也就是说,当用户购买了一定数量的某一类产品,我们是否能够确定他们必然会成比例地购买另一种类的产品。有一个简单的方法可以检测相关性:我们用移除了某一个特征之后的数据集来构建一个监督学习(回归)模型,然后用这个模型去预测那个被移除的特征,再对这个预测结果进行评分,来判断由余下5个特征构建的模型对移除掉的那一个特征预测能力的好坏。

在下面的代码单元中,你需要实现以下的功能:

  • 使用DataFrame.drop函数移除数据集中你选择的不需要的特征,并将移除后的结果赋值给new_data
  • 使用sklearn.model_selection.train_test_split将数据集分割成训练集和测试集。
    • 使用移除的特征作为你的目标标签。设置test_size0.25并设置一个random_state
  • 导入一个DecisionTreeRegressor(决策树回归器),设置一个random_state,然后用训练集训练它。
  • 使用回归器的score函数输出模型在测试集上的预测得分。

In [36]:
%%time

from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor

# TODO:为DataFrame创建一个副本,用'drop'函数丢弃一些指定的特征

grocery = data['Grocery']
data_without_grocery = data.drop(['Grocery'],1)

# display(data.head(1))
# display(fresh.head())
# display(new_data.head())

# TODO:使用给定的特征作为目标,将数据分割成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(data_without_grocery, grocery, test_size=0.25, random_state=20)

# display(X_train.head(1))
# display(X_test.head(1))
# display(y_train.head(1))
# display(y_test.head(1))

# # TODO:创建一个DecisionTreeRegressor(决策树回归器)并在训练集上训练它
regressor = DecisionTreeRegressor(max_depth=15, random_state=20)
regressor.fit(X_train, y_train)

# # TODO:输出在测试集上的预测得分
score = regressor.score(X_test, y_test)
print score


0.728055298278
Wall time: 6 ms

问题 2

你尝试预测哪一个特征?预测的得分是多少?这个特征对于区分用户的消费习惯来说必要吗?
提示: 决定系数(coefficient of determination), R^2,结果在0到1之间,1表示完美拟合,一个负的R^2表示模型不能够拟合数据。

回答: 预测Grocery,预测得分约为0.728,这个特征对于区分用户是没有必要的。因为这个特征可以被其他特征很好的预测,说明与其他特征的相关性好,可以被其他的特征替代,所以没有存在的必要性。

可视化特征分布

为了能够对这个数据集有一个更好的理解,我们可以对数据集中的每一个产品特征构建一个散布矩阵(scatter matrix)。如果你发现你在上面尝试预测的特征对于区分一个特定的用户来说是必须的,那么这个特征和其它的特征可能不会在下面的散射矩阵中显示任何关系。相反的,如果你认为这个特征对于识别一个特定的客户是没有作用的,那么通过散布矩阵可以看出在这个数据特征和其它特征中有关联性。运行下面的代码以创建一个散布矩阵。


In [37]:
# 对于数据中的每一对特征构造一个散布矩阵
pd.plotting.scatter_matrix(data, alpha = 0.3, figsize = (14,8), diagonal = 'kde');



In [38]:
%pdb
# 数据关联度表格呈现
import seaborn
seaborn.heatmap(data.corr(), annot=True)
# 添加这些代码以后,导致所有曲线图背景颜色显示异常(???????????)


Automatic pdb calling has been turned OFF
Out[38]:
<matplotlib.axes._subplots.AxesSubplot at 0xed69588>

问题 3

这里是否存在一些特征他们彼此之间存在一定程度相关性?如果有请列出。这个结果是验证了还是否认了你尝试预测的那个特征的相关性?这些特征的数据是怎么分布的?

提示: 这些数据是正态分布(normally distributed)的吗?大多数的数据点分布在哪?

回答:

  • Grocery与Milk也存在比较好的正相关,大部分数据分布在图形左下角斜向上的锥形区域,符合正态分布。
  • Milk与Detergents_Paper存在非常好的正相关,同上,也是大部分数据分布在图形左下角斜向上的锥形区域,符合正态分布。
  • Grocery与Detergents_Paper存在非常好的正相关,大部分数据分布在图形左下角斜向上的长条形区域,符合正态分布。 这个结果与之前的预测的结果(Grocery相关性好)相似。

数据预处理

在这个部分,你将通过在数据上做一个合适的缩放,并检测异常点(你可以选择性移除)将数据预处理成一个更好的代表客户的形式。预处理数据是保证你在分析中能够得到显著且有意义的结果的重要环节。

练习: 特征缩放

如果数据不是正态分布的,尤其是数据的平均数和中位数相差很大的时候(表示数据非常歪斜)。这时候通常用一个非线性的缩放是很合适的(英文原文) — 尤其是对于金融数据。一种实现这个缩放的方法是使用Box-Cox 变换,这个方法能够计算出能够最佳减小数据倾斜的指数变换方法。一个比较简单的并且在大多数情况下都适用的方法是使用自然对数。

在下面的代码单元中,你将需要实现以下功能:

  • 使用np.log函数在数据 data 上做一个对数缩放,然后将它的副本(不改变原始data的值)赋值给log_data
  • 使用np.log函数在样本数据 samples 上做一个对数缩放,然后将它的副本赋值给log_samples

In [39]:
%%time
from scipy import stats

display(data.head())
# TODO:使用自然对数缩放数据
# log_data = data.apply(lambda x: np.log(x))
log_data = np.log(data)
display(log_data.head())

# TODO:使用自然对数缩放样本数据
log_samples = np.log(samples)
display(log_samples.head())

# 为每一对新产生的特征制作一个散射矩阵
pd.plotting.scatter_matrix(log_data, alpha = 0.3, figsize = (14,8), diagonal = 'kde');


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 12669 9656 7561 214 2674 1338
1 7057 9810 9568 1762 3293 1776
2 6353 8808 7684 2405 3516 7844
3 13265 1196 4221 6404 507 1788
4 22615 5410 7198 3915 1777 5185
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 9.446913 9.175335 8.930759 5.365976 7.891331 7.198931
1 8.861775 9.191158 9.166179 7.474205 8.099554 7.482119
2 8.756682 9.083416 8.946896 7.785305 8.165079 8.967504
3 9.492884 7.086738 8.347827 8.764678 6.228511 7.488853
4 10.026369 8.596004 8.881558 8.272571 7.482682 8.553525
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 9.341807 6.390241 7.401231 8.115820 4.234107 5.886104
1 8.028455 9.490998 10.048756 8.279190 9.206232 6.594413
2 8.684232 10.065904 9.525078 9.225721 6.721426 8.198639
Wall time: 4.36 s

观察

在使用了一个自然对数的缩放之后,数据的各个特征会显得更加的正态分布。对于任意的你以前发现有相关关系的特征对,观察他们的相关关系是否还是存在的(并且尝试观察,他们的相关关系相比原来是变强了还是变弱了)。

运行下面的代码以观察样本数据在进行了自然对数转换之后如何改变了。


In [40]:
# 展示经过对数变换后的样本数据
display(log_samples)


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 9.341807 6.390241 7.401231 8.115820 4.234107 5.886104
1 8.028455 9.490998 10.048756 8.279190 9.206232 6.594413
2 8.684232 10.065904 9.525078 9.225721 6.721426 8.198639

练习: 异常值检测

对于任何的分析,在数据预处理的过程中检测数据中的异常值都是非常重要的一步。异常值的出现会使得把这些值考虑进去后结果出现倾斜。这里有很多关于怎样定义什么是数据集中的异常值的经验法则。这里我们将使用Tukey的定义异常值的方法:一个异常阶(outlier step)被定义成1.5倍的四分位距(interquartile range,IQR)。一个数据点如果某个特征包含在该特征的IQR之外的特征,那么该数据点被认定为异常点。

在下面的代码单元中,你需要完成下面的功能:

  • 将指定特征的25th分位点的值分配给Q1。使用np.percentile来完成这个功能。
  • 将指定特征的75th分位点的值分配给Q3。同样的,使用np.percentile来完成这个功能。
  • 将指定特征的异常阶的计算结果赋值给step.
  • 选择性地通过将索引添加到outliers列表中,以移除异常值。

注意: 如果你选择移除异常值,请保证你选择的样本点不在这些移除的点当中! 一旦你完成了这些功能,数据集将存储在good_data中。


In [41]:
import numpy as np

# 用于记录所有的异常值的索引
allOutliers = []

# 对于每一个特征,找到值异常高或者是异常低的数据点
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)
    display(log_data[~((log_data[feature] >= Q1 - step) & (log_data[feature] <= Q3 + step))])
    
    outlier = log_data[~((log_data[feature] >= Q1 - step) & (log_data[feature] <= Q3 + step))]
    allOutliers.extend(outlier.index)
    
# 找出重复的索引值
sortedAllOutliers = np.sort(allOutliers)
duplicatedOutliers = []
preElement = -1
for element in sortedAllOutliers.flat:
    if element == preElement and element not in duplicatedOutliers:
        duplicatedOutliers.append(element);
    preElement = element
    
print "sortedAllOutliers:{0}".format(sortedAllOutliers)
print "duplicatedOutliers: {0}".format(duplicatedOutliers)
    
# 可选:选择你希望移除的数据点的索引
outliers  = np.unique(sortedAllOutliers)
print "outliers: {}".format(outliers)

# 如果选择了的话,移除异常点
good_data = log_data.drop(log_data.index[outliers]).reset_index(drop = True)


Data points considered outliers for the feature 'Fresh':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
65 4.442651 9.950323 10.732651 3.583519 10.095388 7.260523
66 2.197225 7.335634 8.911530 5.164786 8.151333 3.295837
81 5.389072 9.163249 9.575192 5.645447 8.964184 5.049856
95 1.098612 7.979339 8.740657 6.086775 5.407172 6.563856
96 3.135494 7.869402 9.001839 4.976734 8.262043 5.379897
128 4.941642 9.087834 8.248791 4.955827 6.967909 1.098612
171 5.298317 10.160530 9.894245 6.478510 9.079434 8.740337
193 5.192957 8.156223 9.917982 6.865891 8.633731 6.501290
218 2.890372 8.923191 9.629380 7.158514 8.475746 8.759669
304 5.081404 8.917311 10.117510 6.424869 9.374413 7.787382
305 5.493061 9.468001 9.088399 6.683361 8.271037 5.351858
338 1.098612 5.808142 8.856661 9.655090 2.708050 6.309918
353 4.762174 8.742574 9.961898 5.429346 9.069007 7.013016
355 5.247024 6.588926 7.606885 5.501258 5.214936 4.844187
357 3.610918 7.150701 10.011086 4.919981 8.816853 4.700480
412 4.574711 8.190077 9.425452 4.584967 7.996317 4.127134
Data points considered outliers for the feature 'Milk':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
86 10.039983 11.205013 10.377047 6.894670 9.906981 6.805723
98 6.220590 4.718499 6.656727 6.796824 4.025352 4.882802
154 6.432940 4.007333 4.919981 4.317488 1.945910 2.079442
356 10.029503 4.897840 5.384495 8.057377 2.197225 6.306275
Data points considered outliers for the feature 'Grocery':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
75 9.923192 7.036148 1.098612 8.390949 1.098612 6.882437
154 6.432940 4.007333 4.919981 4.317488 1.945910 2.079442
Data points considered outliers for the feature 'Frozen':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
38 8.431853 9.663261 9.723703 3.496508 8.847360 6.070738
57 8.597297 9.203618 9.257892 3.637586 8.932213 7.156177
65 4.442651 9.950323 10.732651 3.583519 10.095388 7.260523
145 10.000569 9.034080 10.457143 3.737670 9.440738 8.396155
175 7.759187 8.967632 9.382106 3.951244 8.341887 7.436617
264 6.978214 9.177714 9.645041 4.110874 8.696176 7.142827
325 10.395650 9.728181 9.519735 11.016479 7.148346 8.632128
420 8.402007 8.569026 9.490015 3.218876 8.827321 7.239215
429 9.060331 7.467371 8.183118 3.850148 4.430817 7.824446
439 7.932721 7.437206 7.828038 4.174387 6.167516 3.951244
Data points considered outliers for the feature 'Detergents_Paper':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
75 9.923192 7.036148 1.098612 8.390949 1.098612 6.882437
161 9.428190 6.291569 5.645447 6.995766 1.098612 7.711101
Data points considered outliers for the feature 'Delicatessen':
Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
66 2.197225 7.335634 8.911530 5.164786 8.151333 3.295837
109 7.248504 9.724899 10.274568 6.511745 6.728629 1.098612
128 4.941642 9.087834 8.248791 4.955827 6.967909 1.098612
137 8.034955 8.997147 9.021840 6.493754 6.580639 3.583519
142 10.519646 8.875147 9.018332 8.004700 2.995732 1.098612
154 6.432940 4.007333 4.919981 4.317488 1.945910 2.079442
183 10.514529 10.690808 9.911952 10.505999 5.476464 10.777768
184 5.789960 6.822197 8.457443 4.304065 5.811141 2.397895
187 7.798933 8.987447 9.192075 8.743372 8.148735 1.098612
203 6.368187 6.529419 7.703459 6.150603 6.860664 2.890372
233 6.871091 8.513988 8.106515 6.842683 6.013715 1.945910
285 10.602965 6.461468 8.188689 6.948897 6.077642 2.890372
289 10.663966 5.655992 6.154858 7.235619 3.465736 3.091042
343 7.431892 8.848509 10.177932 7.283448 9.646593 3.610918
sortedAllOutliers:[ 38  57  65  65  66  66  75  75  81  86  95  96  98 109 128 128 137 142
 145 154 154 154 161 171 175 183 184 187 193 203 218 233 264 285 289 304
 305 325 338 343 353 355 356 357 412 420 429 439]
duplicatedOutliers: [65, 66, 75, 128, 154]
outliers: [ 38  57  65  66  75  81  86  95  96  98 109 128 137 142 145 154 161 171
 175 183 184 187 193 203 218 233 264 285 289 304 305 325 338 343 353 355
 356 357 412 420 429 439]

问题 4

请列出所有在多于一个特征下被看作是异常的数据点。这些点应该被从数据集中移除吗?为什么?把你认为需要移除的数据点全部加入到到outliers变量中。

回答:多于一个特征下被看作是异常的数据点为:65, 66, 75, 128, 154。 虽然这些数据点有多个特性,说明信息比较丰富,不是单一的数据异常。但是,这些数据做增加的信息所带来的积极的影响,远小于其使类聚中心偏移所带来的消极影响。所以,这些数据点应该被移除。

特征转换

在这个部分中你将使用主成分分析(PCA)来分析批发商客户数据的内在结构。由于使用PCA在一个数据集上会计算出最大化方差的维度,我们将找出哪一个特征组合能够最好的描绘客户。

练习: 主成分分析(PCA)

既然数据被缩放到一个更加正态分布的范围中并且我们也移除了需要移除的异常点,我们现在就能够在good_data上使用PCA算法以发现数据的哪一个维度能够最大化特征的方差。除了找到这些维度,PCA也将报告每一个维度的解释方差比(explained variance ratio)--这个数据有多少方差能够用这个单独的维度来解释。注意PCA的一个组成部分(维度)能够被看做这个空间中的一个新的“特征”,但是它是原来数据中的特征构成的。

在下面的代码单元中,你将要实现下面的功能:

  • 导入sklearn.decomposition.PCA并且将good_data用PCA并且使用6个维度进行拟合后的结果保存到pca中。
  • 使用pca.transformlog_samples进行转换,并将结果存储到pca_samples中。

In [42]:
%%time
from sklearn.decomposition import PCA

# TODO:通过在good data上使用PCA,将其转换成和当前特征数一样多的维度
pca = PCA(n_components=good_data.shape[1], random_state=20)
pca.fit(good_data)

# TODO:使用上面的PCA拟合将变换施加在log_samples上
pca_samples = pca.transform(log_samples)

# 生成PCA的结果图
pca_results = vs.pca_results(good_data, pca)


Wall time: 259 ms

问题 5

数据的第一个和第二个主成分 总共 表示了多少的方差? 前四个主成分呢?使用上面提供的可视化图像,讨论从用户花费的角度来看前四个主要成分的消费行为最能代表哪种类型的客户并给出你做出判断的理由。

提示: 某一特定维度上的正向增长对应正权特征的增长负权特征的减少。增长和减少的速率和每个特征的权重相关。参考资料(英文)

回答:前两个主成分表示了0.7252,即%72.52的方差。前四个主成分表示了0.9279,即%92.79的方差。

  • Dimension 1代表Milk、Grocery和Detergents三者相关性购买的客户(买其中一种的商品的同时,会购买相应数量的另外2种商品)。
  • Dimension 2代表Fresh、Frozen和Delicatessen三者相关性购买的客户(买其中一种的商品的同时,会购买相应数量的另外2种商品)。
  • Dimension 3代表Delicatessen和Fresh二者负相关性购买的客户(购买Delicatessen时,会购买更少Fresh的客户)。
  • Dimension 4代表Delicatessen和Frozen二者负相关性购买的客户(购买Delicatessen时,会购买更少Frozen的客户)。

正的权值越大,说明购买相关性越大,所以,主成分分析的结果,才会放到一起,合并到同一个维度里面来。

【CodeReview20170723】 在这边,我们使用了主成分分析法,将原来的6个特征通过数学变换,变换为了另外6个特征。对方差的计算,是为了让我们能够选择方差较大的特征以保留它们。每个新特征,实际上都是由原来的特征通过某种带权重的组合得到的,权重就是图中柱状图柱高度。考虑权重的绝对值,权重绝对值越大,说明权重对应的原特征对这个新特征带来的影响越大,反之亦反。权重若为正,则说明他们有正相关性;负值则说明它们是负相关性。A和B有正相关性可以理解为,买更多的A意味着有很大可能买更多的B;负相关性意味着买更多的A意味着有很大可能买更少的B。

观察

运行下面的代码,查看经过对数转换的样本数据在进行一个6个维度的主成分分析(PCA)之后会如何改变。观察样本数据的前四个维度的数值。考虑这和你初始对样本点的解释是否一致。


In [43]:
# 展示经过PCA转换的sample log-data
display(samples)
display(pd.DataFrame(np.round(pca_samples, 4), columns = pca_results.index.values))


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 11405 596 1638 3347 69 360
1 3067 13240 23127 3941 9959 731
2 5909 23527 13699 10155 830 3636
Dimension 1 Dimension 2 Dimension 3 Dimension 4 Dimension 5 Dimension 6
0 -3.3817 0.0260 -0.2641 -0.3833 0.0869 0.6051
1 3.0820 0.1314 0.3994 -1.4197 0.4747 0.2263
2 1.2628 1.9928 1.7162 -0.2202 1.3011 0.0066

练习:降维

当使用主成分分析的时候,一个主要的目的是减少数据的维度,这实际上降低了问题的复杂度。当然降维也是需要一定代价的:更少的维度能够表示的数据中的总方差更少。因为这个,累计解释方差比(cumulative explained variance ratio)对于我们确定这个问题需要多少维度非常重要。另外,如果大部分的方差都能够通过两个或者是三个维度进行表示的话,降维之后的数据能够被可视化。

在下面的代码单元中,你将实现下面的功能:

  • good_data用两个维度的PCA进行拟合,并将结果存储到pca中去。
  • 使用pca.transformgood_data进行转换,并将结果存储在reduced_data中。
  • 使用pca.transformlog_samples进行转换,并将结果存储在pca_samples中。

In [44]:
%%time
from sklearn.decomposition import PCA

# TODO:通过在good data上进行PCA,将其转换成两个维度
pca = PCA(n_components = 2, random_state=20)
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'])


Wall time: 3 ms

观察

运行以下代码观察当仅仅使用两个维度进行PCA转换后,这个对数样本数据将怎样变化。观察这里的结果与一个使用六个维度的PCA转换相比较时,前两维的数值是保持不变的。


In [45]:
# 展示经过两个维度的PCA转换之后的样本log-data
display(pd.DataFrame(np.round(pca_samples, 4), columns = ['Dimension 1', 'Dimension 2']))


Dimension 1 Dimension 2
0 -3.3817 0.0260
1 3.0820 0.1314
2 1.2628 1.9928

可视化一个双标图(Biplot)

双标图是一个散点图,每个数据点的位置由它所在主成分的分数确定。坐标系是主成分(这里是Dimension 1Dimension 2)。此外,双标图还展示出初始特征在主成分上的投影。一个双标图可以帮助我们理解降维后的数据,发现主成分和初始特征之间的关系。

运行下面的代码来创建一个降维后数据的双标图。


In [46]:
# Create a biplot
vs.biplot(good_data, reduced_data, pca)


Out[46]:
<matplotlib.axes._subplots.AxesSubplot at 0xe3acb70>

观察

一旦我们有了原始特征的投影(红色箭头),就能更加容易的理解散点图每个数据点的相对位置。

在这个双标图中,哪些初始特征与第一个主成分有强关联?哪些初始特征与第二个主成分相关联?你观察到的是否与之前得到的 pca_results 图相符?

回答:与第一个主成分有强关联的有:Milk,Grocery,Detergents_Paper。与第二个主成分有关联的有:Fresh,Frozen。这个与之前得到的pca_results 图相符。

聚类

在这个部分,你讲选择使用K-Means聚类算法或者是高斯混合模型聚类算法以发现数据中隐藏的客户分类。然后,你将从簇中恢复一些特定的关键数据点,通过将它们转换回原始的维度和规模,从而理解他们的含义。

问题 6

使用K-Means聚类算法的优点是什么?使用高斯混合模型聚类算法的优点是什么?基于你现在对客户数据的观察结果,你选用了这两个算法中的哪一个,为什么?

回答: K-Means类聚算法的优点是:

  1. 算法快速、简单;
  2. 对大数据集有较高的效率,并且是可伸缩性的;
  3. 时间复杂度近于线性,而且适合挖掘大规模数据集。

高斯混合模型聚类算法的优点是:

  1. 模型假设更加复杂准确,即假定服从正态分布(同时,也带来缺点是,计算复杂度增加)
  2. 运行结果是属于某个分类的概率,描述更精确严谨。

根据数据分布比较区域比较大,分类的界限并没有那么的严格和清晰,GMM这种分类结果是概率的方法,更加的精确严谨。我打算选用高斯混合模型聚类算法。

练习: 创建聚类

针对不同情况,有些问题你需要的聚类数目可能是已知的。但是在聚类数目不作为一个先验知道的情况下,我们并不能够保证某个聚类的数目对这个数据是最优的,因为我们对于数据的结构(如果存在的话)是不清楚的。但是,我们可以通过计算每一个簇中点的轮廓系数来衡量聚类的质量。数据点的轮廓系数衡量了它与分配给他的簇的相似度,这个值范围在-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并输出结果。

In [47]:
%%time

from sklearn.mixture import GaussianMixture 
from sklearn.metrics import silhouette_score
# TODO:在降维后的数据上使用你选择的聚类算法
clusterer = GaussianMixture(n_components=2, random_state=20).fit(reduced_data)

# TODO:预测每一个点的簇
preds = clusterer.predict(reduced_data)

# TODO:找到聚类中心
centers = clusterer.means_
print "centers: \n{0}".format(centers)

# TODO:预测在每一个转换后的样本点的类
sample_preds = clusterer.predict(pca_samples)

# TODO:计算选择的类别的平均轮廓系数(mean silhouette coefficient)
score = silhouette_score(reduced_data, preds)
print "silhouette_score: {0}".format(score)


centers: 
[[-1.26441765  0.16908975]
 [ 2.09897893 -0.28069508]]
silhouette_score: 0.446753526945
Wall time: 18 ms

问题 7

汇报你尝试的不同的聚类数对应的轮廓系数。在这些当中哪一个聚类的数目能够得到最佳的轮廓系数?

回答:

  • n_components=2: 0.447
  • n_components=3: 0.362
  • n_components=4: 0.304

当类聚数目为2时,能够得到最佳的轮廓系数。

聚类可视化

一旦你选好了通过上面的评价函数得到的算法的最佳聚类数目,你就能够通过使用下面的代码块可视化来得到的结果。作为实验,你可以试着调整你的聚类算法的聚类的数量来看一下不同的可视化结果。但是你提供的最终的可视化图像必须和你选择的最优聚类数目一致。


In [48]:
print pca_samples


[[-3.38171326  0.02604577]
 [ 3.08202637  0.13142075]
 [ 1.26278565  1.99276191]]

In [49]:
# 从已有的实现中展示聚类的结果
vs.cluster_results(reduced_data, preds, centers, pca_samples)


练习: 数据恢复

上面的可视化图像中提供的每一个聚类都有一个中心点。这些中心(或者叫平均点)并不是数据中真实存在的点,但是是所有预测在这个簇中的数据点的平均。对于创建客户分类的问题,一个簇的中心对应于那个分类的平均用户。因为这个数据现在进行了降维并缩放到一定的范围,我们可以通过施加一个反向的转换恢复这个点所代表的用户的花费。

在下面的代码单元中,你将实现下列的功能:

  • 使用pca.inverse_transformcenters 反向转换,并将结果存储在log_centers中。
  • 使用np.log的反函数np.exp反向转换log_centers并将结果存储到true_centers中。

In [50]:
log_centers = pca.inverse_transform(centers)
print log_centers


[[ 9.1584139   7.62492801  7.86263535  7.6976342   5.82081026  6.6799615 ]
 [ 8.55996955  8.94516399  9.34161859  6.9836291   8.39226913  7.00243444]]

In [51]:
# 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)


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
Segment 0 9494.0 2049.0 2598.0 2203.0 337.0 796.0
Segment 1 5219.0 7671.0 11403.0 1079.0 4413.0 1099.0

问题 8

考虑上面的代表性数据点在每一个产品类型的花费总数,你认为这些客户分类代表了哪类客户?为什么?需要参考在项目最开始得到的统计值来给出理由。

提示: 一个被分到'Cluster X'的客户最好被用 'Segment X'中的特征集来标识的企业类型表示。


In [52]:
import seaborn as sns
import matplotlib.pyplot as plt
# add the true centers as rows to our original data
newdata = data.append(true_centers)

# show the percentiles of the centers
ctr_pcts = 100. * newdata.rank(axis=0, pct=True).loc[['Segment 0', 'Segment 1']].round(decimals=3)
print ctr_pcts

# visualize percentiles with heatmap
sns.heatmap(ctr_pcts, annot=True, cmap='Greens', fmt='.1f', linewidth=.1, square=True, cbar=False)
plt.xticks(rotation=45, ha='center')
plt.yticks(rotation=0)
plt.title('Percentile ranks of\nsegment centers');


           Fresh  Milk  Grocery  Frozen  Detergents_Paper  Delicatessen
Segment 0   54.8  33.7     32.8    60.0              31.2          44.3
Segment 1   34.8  77.8     78.1    38.7              78.1          55.7

回答:

  • Segment 0,应该是属于零售店客户,各种类别的商品采购量都比较小,Fresh和Frozen购买量刚刚超过最大值的50%,其他类别的商品采购量都在最大值的25%~50%。
  • Segment 1,应该是属于超市客户,Milk,Grocery,Frozen,购买量都相对较大,购买量达到了最大值的75%,Delicatessen购买量达到了最大值的50%~75%,只有Fresh,Frozen购买量在25%~50%。

问题 9

对于每一个样本点 问题 8 中的哪一个分类能够最好的表示它?你之前对样本的预测和现在的结果相符吗?

运行下面的代码单元以找到每一个样本点被预测到哪一个簇中去。


In [53]:
# 显示预测结果
display(samples.head())

for i, pred in enumerate(sample_preds):
    print "Sample point", i, "predicted to be in Cluster", pred


Fresh Milk Grocery Frozen Detergents_Paper Delicatessen
0 11405 596 1638 3347 69 360
1 3067 13240 23127 3941 9959 731
2 5909 23527 13699 10155 830 3636
Sample point 0 predicted to be in Cluster 0
Sample point 1 predicted to be in Cluster 1
Sample point 2 predicted to be in Cluster 1

回答:

  • Sample point 0: 零售店(便利店)客户
  • Sample point 1: 超市客户
  • Sample point 2: 超市客户

与之前的预测基本相符。

结论

在最后一部分中,你要学习如何使用已经被分类的数据。首先,你要考虑不同组的客户客户分类,针对不同的派送策略受到的影响会有什么不同。其次,你要考虑到,每一个客户都被打上了标签(客户属于哪一个分类)可以给客户数据提供一个多一个特征。最后,你会把客户分类与一个数据中的隐藏变量做比较,看一下这个分类是否辨识了特定的关系。

问题 10

在对他们的服务或者是产品做细微的改变的时候,公司经常会使用A/B tests以确定这些改变会对客户产生积极作用还是消极作用。这个批发商希望考虑将他的派送服务从每周5天变为每周3天,但是他只会对他客户当中对此有积极反馈的客户采用。这个批发商应该如何利用客户分类来知道哪些客户对它的这个派送策略的改变有积极的反馈,如果有的话?你需要给出在这个情形下A/B 测试具体的实现方法,以及最终得出结论的依据是什么?
提示: 我们能假设这个改变对所有的客户影响都一致吗?我们怎样才能够确定它对于哪个类型的客户影响最大?

回答:

  • 首先假设,这个服务改变对于用户的影响是均匀的,即假设一个已经确定是积极的服务改变对于某单个客户的影响有可能是积极的,也有可能的消极的,但是对于总体的影响应该是恒定的。我们不能假设对于客户的影响是一致的。
  • 确认结果的评价标准是:客户投诉率变高(或变低),客户退订减少(或增多),新客户增加(或减少)为好(或坏)。
  • 过程:假设有2种分类的客户,将各种分类的客户分别随机用户分成100组,共200组。然后,从各个分类中分别抽出3组来实验。然后,收集实验结果。
  • 根据结果评价标准,确定这个服务改变对哪些分类的客户有积极的作用,然后对这些分类的客户采用新的服务模式。

问题 11

通过聚类技术,我们能够将原有的没有标记的数据集中的附加结构分析出来。因为每一个客户都有一个最佳的划分(取决于你选择使用的聚类算法),我们可以把用户分类作为数据的一个工程特征。假设批发商最近迎来十位新顾客,并且他已经为每位顾客每个产品类别年度采购额进行了预估。进行了这些估算之后,批发商该如何运用它的预估和非监督学习的结果来对这十个新的客户进行更好的预测?

提示:在下面的代码单元中,我们提供了一个已经做好聚类的数据(聚类结果为数据中的cluster属性),我们将在这个数据集上做一个小实验。尝试运行下面的代码看看我们尝试预测‘Region’的时候,如果存在聚类特征'cluster'与不存在相比对最终的得分会有什么影响?这对你有什么启发?


In [54]:
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, train_size=0.8, 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特征的得分 0.666666666667
不使用cluster特征的得分 0.64367816092

回答: 得分基本相同,但是不使用cluster特征的得分略微低一些。 在数据有Label的情况下,即可以用于监督学习的情况。可以先用非监督学习来类聚数据,然后对比非监督学习与监督学习的结果进行比较,相互印证。增加对于模型,以及分析结果的信心。 另外,将使用非监督学习的预测出来的10个新用户的label,组成新的样本数据(feature,label),来训练出新的监督学习模型。(这样做的目的何在???)

【CodeReview20170725】 根据上一次review中给出的note,这里我们已经通过历史数据得到了非监督学习的结果,那么现在我们迎来了10个新的用户,实际上我们可以将这10个samples的类别预测出来,这样我们就得到了新用户的label,所以这里说,在这个情景中,我们可以使用非监督学习的成果,即得到的label,来实现监督学习的功能:利用得到的(feature,label)来学到一个监督学习模型。

可视化内在的分布

在这个项目的开始,我们讨论了从数据集中移除'Channel''Region'特征,这样在分析过程中我们就会着重分析用户产品类别。通过重新引入Channel这个特征到数据集中,并施加和原来数据集同样的PCA变换的时候我们将能够发现数据集产生一个有趣的结构。

运行下面的代码单元以查看哪一个数据点在降维的空间中被标记为'HoReCa' (旅馆/餐馆/咖啡厅)或者'Retail'。另外,你将发现样本点在图中被圈了出来,用以显示他们的标签。


In [55]:
# 根据‘Channel‘数据显示聚类的结果
vs.channel_results(reduced_data, outliers, pca_samples)


问题 12

你选择的聚类算法和聚类点的数目,与内在的旅馆/餐馆/咖啡店和零售商的分布相比,有足够好吗?根据这个分布有没有哪个簇能够刚好划分成'零售商'或者是'旅馆/饭店/咖啡馆'?你觉得这个分类和前面你对于用户分类的定义是一致的吗?

回答: 我类聚结果对于数据点的分割是类似的,都是左右两块区域对应两个分类。但是,两个分类的标签是不一样的。尤其是,零售商这个分类,刚好与我的分类是反的。对于左右两块区域的标签分别是:超市、零售商(便利店),而这里原题给出的标签是:零售商、旅馆/餐馆/咖啡店。原题对于两个分类给出的标签的依据是什么,不是很清楚。而我的依据是,总的采购量的大小。

注意: 当你写完了所有的代码,并且回答了所有的问题。你就可以把你的 iPython Notebook 导出成 HTML 文件。你可以在菜单栏,这样导出File -> Download as -> HTML (.html)把这个 HTML 和这个 iPython notebook 一起做为你的作业提交。