项目背景
安然曾是 2000 年美国最大的公司之一。辉煌时期,市值高达700亿美元。2002 年,由于其存在大量的企业欺诈行为,这个昔日的大集团以极快的速度土崩瓦解。 在随后联邦进行的调查过程中,大量有代表性的保密信息进入了公众的视线,包括成千上万涉及高管的邮件和详细的财务数据。 你将在此项目中扮演侦探,运用你的新技能,根据安然丑闻中公开的财务和邮件数据来构建相关人士识别符。利用机器学习算法进行数据分析,从邮件和财务数据中找出犯罪嫌疑人。
数据集初步探索
加载数据集
首先我们加载数据集
with open("final_project_dataset.pkl", "r") as data_file:
data_dict = pickle.load(data_file)
电子邮件和财务 (E+F) 数据字典被存储在 pickle 文件中,该文件可直接存储和加载 python 对象,非常方便。
数据集初步分析
分析发现我们读取的数据数据实际上是以字典形式进行。随机取一个字典,其存储结构如下所示:
{METTS MARK:{'salary': 365788, 'to_messages': 807, 'deferral_payments': 'NaN', 'total_payments': 1061827, 'exercised_stock_options': 'NaN', 'bonus': 600000, 'restricted_stock': 585062, 'shared_receipt_with_poi': 702, 'restricted_stock_deferred': 'NaN', 'total_stock_value': 585062, 'expenses': 94299, 'loan_advances': 'NaN', 'from_messages': 29, 'other': 1740, 'from_this_person_to_poi': 1, 'poi': False, 'director_fees': 'NaN', 'deferred_income': 'NaN', 'long_term_incentive': 'NaN', 'email_address': 'mark.metts@enron.com', 'from_poi_to_this_person': 38}}
在预处理此项目时,我们已将安然邮件和财务数据与字典结合在一起,字典中的每对键值对应一个人。 字典键是人名,值是另一个字典(包含此人的所有特征名和对应的值)。 数据中的特征分为三大类,即财务特征、邮件特征和 POI 标签。
财务特征 : [‘salary’, ‘deferral_payments’, ‘total_payments’, ‘loan_advances’, ‘bonus’, ‘restricted_stock_deferred’, ‘deferred_income’, ‘total_stock_value’, ‘expenses’, ‘exercised_stock_options’, ‘other’, ‘long_term_incentive’, ‘restricted_stock’, ‘director_fees’] (单位均是美元)
邮件特征 : [‘to_messages’, ‘email_address’, ‘from_poi_to_this_person’, ‘from_messages’, ‘from_this_person_to_poi’, ‘shared_receipt_with_poi’] (单位通常是电子邮件的数量,明显的例外是 ‘email_address’,这是一个字符串)
POI 标签 : [‘poi’] (boolean,整数)
数据特征
分析这个数据字典,我们发现每个人一共有20个特征可以用于分析,其中的poi不是特征,而是label/target
数据点总数
分析整个数据字典,我们发现一共有146个数据点(人)。
POI统计
POI( Person of interest )是嫌疑犯的意思,数据集有一个标签(label)就是嫌疑犯,所以我们只需要统计
data_dict[preson name][‘poi’] == 1
的数量就可以了。
统计发现有18个。
缺失的特征
数据集并不是每个特征后都有明确的值,有很多信息的特征是缺失的。对于salary特征,很多人的信息就是NaN。146个数据点(人)中,只有95个人有salary的具体信息。有111个人有邮箱地址,其他人的邮箱地址信息为NaN。
异常值调查和处理
我们在分析财务数据salary和bounds之间的关系时发现了一个极为异常额异常值,如下图所示
明显在右上角有一个极为异常的点,奖金和薪水远远高于其他人。我们通过代码寻找一下奖金和薪水都极高的人,看是否还有其他的异常值。
data_dict = sorted(data_dict.items(), key = lambda x : x[1]["salary"] ,reverse=True)
for x in data_dict :
if x[1]['salary'] > 1000000 and x[1]['bonus'] > 5000000 :
print x[0], x[1]['salary'], x[1]['bonus']
审查发现一共有三个异常值。第一个为TOTAL,很明显不是一个人名,而且薪水和奖金都极度异常,我们将他作为真正的异常值删除掉。
data_dict = dict(data_dict)
data_dict.pop('TOTAL', '52')
剩下两个分别是SKILLING JEFFREY K和LAY KENNETH L。他们分别是安然公司的CEO和董事长,他们是整个安然欺诈事件中最大的嫌疑犯。他们能有这么高的薪水和奖金也就不足为奇了。他们不是真的异常值,因此不对他们进行处理。
优化特征选择
创建新的特征
我们有特征to_messages和from_poi_to_this_person这两个特征,因此我想我们可以建立一个新的特征命名为 to_poi_ratio,其值为from_poi_to_this_person和to_messages的比值,比值越大也就意味着这个人收到的邮件中来自嫌疑人的邮件越多,往往也就意味着这个人和嫌疑人的关系越密切,很有可能这个人也是一个嫌疑人。具体创立代码如下:
def poi_email_ratio(from_poi_to_this_person, to_messages):
if from_poi_to_this_person or to_messages == 'NaN':
to_poi_ratio = 0
else:
to_poi_ratio = float(from_poi_to_this_person)/to_messages
return to_poi_ratio
# create new key and value
for key in my_dataset:
my_dataset[key]['to_poi_ratio'] = poi_email_ratio(my_dataset[key]['from_poi_to_this_person'], my_dataset[key]['to_messages'])
测试新特征是否会对分类算法的结果产生影响的代码如下:
### 添加新特征之后的数据集
data = featureFormat(my_dataset, features_list, sort_keys = True)
labels, features = targetFeatureSplit(data)
### 未添加新特征的数据集
data = featureFormat(data_dict, features_list, sort_keys = True)
labels, features = targetFeatureSplit(data)
我们分别运行这两段代码,比较结果就可以知道新特征是否会对分类算法产生影响了。运行结果分别如下:
### new feature
The naive_bayes's recall is: 0.871794871795
The naive_bayes's precision is : 0.871794871795
The Decession_tree's recall is: 0.897435897436
The Decession_tree's precision is : 0.897435897436
### orignal feature
The naive_bayes's recall is: 0.871794871795
The naive_bayes's precision is : 0.871794871795
The Decession_tree's recall is: 0.846153846154
The Decession_tree's precision is : 0.846153846154
通过对比发现添加新特征对于朴素贝叶斯的结果完全没有任何影响,而对于决策树算法有一定影响。添加新特征之后决策树算法的准确率提高了。
选择最佳特征
选择和调整算法
选择算法
我们这里邮件信息的学习过程实际上是一个监督学习的过程,我们这里分别使用朴素贝叶斯和决策树来对模型进行训练和评估。因为这个数据集很不平衡(imbalance), 也就说明accuracy并不是很好的评估指标,因此我们选择precision和recall来作为模型的评估指标。
朴素贝叶斯的机器学习模型建立如下:
from sklearn.naive_bayes import GaussianNB
clf = GaussianNB()
clf.fit(features_train, labels_train)
y_pred = clf.predict(features_test)
recall = recall_score(labels_test, y_pred, average='micro')
precision = precision_score(labels_test, y_pred, average='micro')
print "The naive_bayes's recall is: %s " % recall
print "The naive_bayes's precision is : %s" % precision
该模型预测的准确率为0.85
决策树的机器学习模型建立如下:
from sklearn import tree
from sklearn.model_selection import GridSearchCV
trees = tree.DecisionTreeClassifier()
parameters = {'min_samples_split' : range(5,80,5), 'splitter' : ('best', 'random')}
clf = GridSearchCV(trees, parameters)
clf.fit(features_train, labels_train)
y_pred = clf.predict(features_test)
recall = recall_score(labels_test, y_pred, average='micro')
precision = precision_score(labels_test, y_pred, average='micro')
print "The Decession_tree's recall is: %s " % recall
print "The Decession_tree's precision is : %s" % precision
决策树训练模型涉及到较多的参数,要想得到更好的训练效果,对于参数的调整是绝对必要的。
调整算法
使用决策树有一个缺点就是容易过拟合,因此我们发应当尽可能的合理调整参数以达到最好的训练效果。
决策树有一个参数为min_samples_split,用于设置最小分割数。另外我们还可以调整splitter参数,该参数可以设置分割方法,有两种:一种是’best’策略,用于选择最好的分割,另一种是’random’策略,用于选择最好的随机分割。通常情况下当样本量比较小的时候我们采取’best’策略进行分割,而当样本量比较大的时候,我们采取‘random’的效果会更好。
使用 GridSearchCV 进行参数调整
GridSearchCV 用于系统地遍历多种参数组合,通过交叉验证确定最佳效果参数。它的好处是,只需增加几行代码,就能遍历多种组合。当然与此对应的是机器学习过程所消耗的时间会相对较多。下面我们用GridSearchCV对决策树参数进行调整:
from sklearn import tree
from sklearn.model_selection import GridSearchCV
trees = tree.DecisionTreeClassifier()
parameters = {'min_samples_split' : range(5,80,5), 'splitter' : ('best', 'random')}
clf = GridSearchCV(trees, parameters)
clf.fit(features_train, labels_train)
print clf.score(features_test, labels_test)
经测试发现准确率为0.91,高于朴素贝叶斯的0.87。这里我们使用决策树效果更好。
验证和评估
我们分别使用精确度和召回率这两个指标来评估模型的好坏。精确度概括的说可以是:猜对真的/(猜对真的+误以为真的)。召回率概括的说可以是猜对真的/(猜对真的+误以为假的)。我们分别对两个算法模型进行评估
验证及其重要性
验证是用于评估模型好坏的一个重要方法,我们通常将数据集分为训练集和测试集就是为了验证的方便。前者用以建立模型(model),后者则用来评估该模型对未知样本进行预测时的泛化能力。我们需要在测试集上进行验证,来确定训练集是否“过拟合”或者“欠拟合”。不同的数据集和训练集的划分方法,也会对验证的效果产生一定影响。
训练集和数据集的拆分
我使用如下方法拆分训练集和数据集的
from sklearn.model_selection import train_test_split
features_train, features_test, labels_train, labels_test = train_test_split(
features, labels, test_size=0.3, random_state=42)
数据集的70%作为训练集,30%作为测试集
参考资料
我在此确认,所提交的项目为我的工作成果,其中引用的信息出自网站、书籍、论坛、博客文章和 GitHub 代码库等。下面列出我在完成项目过程中所参考的资料:
Recall和Precision的理解 http://blog.csdn.net/Relocy/article/details/51453950
Precision-Recall metric: http://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html
recall score :http://scikit-learn.org/stable/modules/generated/sklearn.metrics.recall_score.html