先创建一个可视化文件
import warnings
warnings.filterwarnings("ignore", category = UserWarning, module = "matplotlib")
from IPython import get_ipython
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as pl
import matplotlib.patches as mpatches
import numpy as np
import pandas as pd
from time import time
from sklearn.metrics import f1_score, accuracy_score
def distribution(data, transformed = False):
fig = pl.figure(figsize = (11,5));
for i, feature in enumerate(['capital-gain','capital-loss']):
ax = fig.add_subplot(1, 2, i+1)
ax.hist(data[feature], bins = 25, color = '#00A0A0')
ax.set_title("'%s' Feature Distribution"%(feature), fontsize = 14)
ax.set_xlabel("Value")
ax.set_ylabel("Number of Records")
ax.set_ylim((0, 2000))
ax.set_yticks([0, 500, 1000, 1500, 2000])
ax.set_yticklabels([0, 500, 1000, 1500, ">2000"])
if transformed:
fig.suptitle("Log-transformed Distributions of Continuous Census Data Features", \
fontsize = 16, y = 1.03)
else:
fig.suptitle("Skewed Distributions of Continuous Census Data Features", \
fontsize = 16, y = 1.03)
fig.tight_layout()
fig.show()
def evaluate(results, accuracy, f1):
fig, ax = pl.subplots(2, 3, figsize = (11,7))
bar_width = 0.3
colors = ['#A00000','#00A0A0','#00A000']
for k, learner in enumerate(results.keys()):
for j, metric in enumerate(['train_time', 'acc_train', 'f_train', 'pred_time', 'acc_val', 'f_val']):
for i in np.arange(3):
ax[j//3, j%3].bar(i+k*bar_width, results[learner][i][metric], width = bar_width, color = colors[k])
ax[j//3, j%3].set_xticks([0.45, 1.45, 2.45])
ax[j//3, j%3].set_xticklabels(["1%", "10%", "100%"])
ax[j//3, j%3].set_xlabel("Training Set Size")
ax[j//3, j%3].set_xlim((-0.1, 3.0))
ax[0, 0].set_ylabel("Time (in seconds)")
ax[0, 1].set_ylabel("Accuracy Score")
ax[0, 2].set_ylabel("F-score")
ax[1, 0].set_ylabel("Time (in seconds)")
ax[1, 1].set_ylabel("Accuracy Score")
ax[1, 2].set_ylabel("F-score")
ax[0, 0].set_title("Model Training")
ax[0, 1].set_title("Accuracy Score on Training Subset")
ax[0, 2].set_title("F-score on Training Subset")
ax[1, 0].set_title("Model Predicting")
ax[1, 1].set_title("Accuracy Score on Testing Set")
ax[1, 2].set_title("F-score on Testing Set")
ax[0, 1].axhline(y = accuracy, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')
ax[1, 1].axhline(y = accuracy, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')
ax[0, 2].axhline(y = f1, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')
ax[1, 2].axhline(y = f1, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')
ax[0, 1].set_ylim((0, 1))
ax[0, 2].set_ylim((0, 1))
ax[1, 1].set_ylim((0, 1))
ax[1, 2].set_ylim((0, 1))
patches = []
for i, learner in enumerate(results.keys()):
patches.append(mpatches.Patch(color = colors[i], label = learner))
pl.legend(handles = patches, bbox_to_anchor = (-.80, 2.53), \
loc = 'upper center', borderaxespad = 0., ncol = 3, fontsize = 'x-large')
pl.suptitle("Performance Metrics for Three Supervised Learning Models", fontsize = 16, y = 1.10)
pl.subplots_adjust(top=0.85, bottom=0., left=0.10, right=0.95, hspace=0.3,wspace=0.35)
pl.show()
def feature_plot(importances, X_train, y_train):
indices = np.argsort(importances)[::-1]
columns = X_train.columns.values[indices[:5]]
values = importances[indices][:5]
fig = pl.figure(figsize = (9,5))
pl.title("Normalized Weights for First Five Most Predictive Features", fontsize = 16)
rects = pl.bar(np.arange(5), values, width = 0.6, align="center", color = '#00A000', \
label = "Feature Weight")
axes = pl.gca()
axes.set_ylim([0, np.max(values) * 1.1])
delta = np.max(values) * 0.02
for rect in rects:
height = rect.get_height()
pl.text(rect.get_x() + rect.get_width()/2.,
height + delta,
'%.2f' % height,
ha='center',
va='bottom')
rotation = 0
for i in columns:
if len(i) > 20:
rotation = 10
break
pl.xticks(np.arange(5), columns, rotation = rotation)
pl.xlim((-0.5, 4.5))
pl.ylabel("Weight", fontsize = 12)
pl.xlabel("Feature", fontsize = 12)
pl.legend(loc = 'upper center')
pl.tight_layout()
pl.show()
进入正题,开始进行!!!
背景说明
在这个项目中,将使用1994年美国人口普查收集的数据,选用几个监督学习算法以准确地建模被调查者的收入。然后将根据初步结果从中选择出最佳的候选算法,并进一步优化该算法以最好地建模这些数据。目标是建立一个能够准确地预测被调查者年收入是否超过50000美元的模型。这种类型的任务会出现在那些依赖于捐款而存在的非营利性组织。了解人群的收入情况可以帮助一个非营利性的机构更好地了解他们要多大的捐赠,或是否他们应该接触这些人。虽然很难直接从公开的资源中推断出一个人的一般收入阶层,但是我们可以(也正是我们将要做的)从其他的一些公开的可获得的资源中获得一些特征从而推断出该值。
这个项目的数据集来自UCI机器学习知识库。这个数据集是由Ron Kohavi和Barry Becker在发表文章"Scaling Up the Accuracy of Naive-Bayes Classifiers: A Decision-Tree Hybrid"之后捐赠的,你可以在Ron Kohavi提供的在线版本中找到这个文章。我们在这里探索的数据集相比于原有的数据集有一些小小的改变,比如说移除了特征’fnlwgt’ 以及一些遗失的或者是格式不正确的记录。
探索数据
# 为这个项目导入需要的库
import numpy as np
import pandas as pd
from time import time
from IPython.display import display # 允许为DataFrame使用display()
# 导入附加的可视化代码visuals.py
import visuals as vs
# 为notebook提供更加漂亮的可视化
%matplotlib inline
# 导入人口普查数据
data = pd.read_csv("census.csv")
# 成功 - 显示第一条记录
display(data.head(n=1))
# TODO:总的记录数
n_records = data.shape[0]
# TODO:被调查者的收入大于$50,000的人数
n_greater_50k = data[data.income == '>50K'].shape[0]
# TODO:被调查者的收入最多为$50,000的人数
n_at_most_50k = data[data.income == '<=50K'].shape[0]
# 被调查者收入大于$50,000所占的比例
greater_percent = n_greater_50k / n_records * 100
# 打印结果
print ("Total number of records: {}".format(n_records))
print ("Individuals making more than $50,000: {}".format(n_greater_50k))
print ("Individuals making at most $50,000: {}".format(n_at_most_50k))
print ("Percentage of individuals making more than $50,000: {:.2f}%".format(greater_percent))
准备数据
获取特征和标签
# 将数据切分成特征和对应的标签
income_raw = data['income']
features_raw = data.drop('income', axis = 1)
转换倾斜的连续特征
一个数据集有时可能包含至少一个靠近某个数字的特征,但有时也会有一些相对来说存在极大值或者极小值的不平凡分布的的特征。算法对这种分布的数据会十分敏感,并且如果这种数据没有能够很好地规一化处理会使得算法表现不佳。在人口普查数据集的两个特征符合这个描述:‘capital-gain’和’capital-loss’。
# 可视化 'capital-gain'和'capital-loss' 两个特征
vs.distribution(features_raw)
对于高度倾斜分布的特征如’capital-gain’和’capital-loss’,常见的做法是对数据施加一个对数转换,将数据转换成对数,这样非常大和非常小的值不会对学习算法产生负面的影响。并且使用对数变换显著降低了由于异常值所造成的数据范围异常。但是在应用这个变换时必须小心:因为0的对数是没有定义的,所以我们必须先将数据处理成一个比0稍微大一点的数以成功完成对数转换。
# 对于倾斜的数据使用Log转换
skewed = ['capital-gain', 'capital-loss']
features_raw[skewed] = data[skewed].apply(lambda x: np.log(x + 1))
# 可视化对数转换后 'capital-gain'和'capital-loss' 两个特征
vs.distribution(features_raw, transformed = True)
规一化数字特征
除了对于高度倾斜的特征施加转换,对数值特征施加一些形式的缩放通常会是一个好的习惯。在数据上面施加一个缩放并不会改变数据分布的形式(比如上面说的’capital-gain’ or ‘capital-loss’);但是,规一化保证了每一个特征在使用监督学习器的时候能够被平等的对待。注意一旦使用了缩放,观察数据的原始形式不再具有它本来的意义了,就像下面的例子展示的。
from sklearn.preprocessing import MinMaxScaler
# 初始化一个 scaler,并将它施加到特征上
scaler = MinMaxScaler()
numerical = ['age', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']
features_raw[numerical] = scaler.fit_transform(data[numerical])
# 显示一个经过缩放的样例记录
display(features_raw.head(n = 1))
数据预处理
# 使用pandas.get_dummies()对'features_raw'数据进行独热编码
features = pd.get_dummies(features_raw)
# 将'income_raw'编码成数字值
income = income_raw.replace(['>50K', '<=50K'],[1, 0])
# 打印经过独热编码之后的特征数量
encoded = list(features.columns)
print ("{} total features after one-hot encoding.".format(len(encoded)))
混洗和切分数据
现在所有的 类别变量 已被转换成数值特征,而且所有的数值特征已被规一化。和我们一般情况下做的一样,我们现在将数据(包括特征和它们的标签)切分成训练和测试集。其中80%的数据将用于训练和20%的数据用于测试。然后再进一步把训练数据分为训练集和验证集,用来选择和优化模型。
# 导入 train_test_split
from sklearn.model_selection import train_test_split
# 将'features'和'income'数据切分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(features, income, test_size = 0.2, random_state = 0,
stratify = income)
# 将'X_train'和'y_train'进一步切分为训练集和验证集
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=0,
stratify = y_train)
# 显示切分的结果
print ("Training set has {} samples.".format(X_train.shape[0]))
print ("Validation set has {} samples.".format(X_val.shape[0]))
print ("Testing set has {} samples.".format(X_test.shape[0]))
评价模型性能
天真的预测器的性能
通过查看收入超过和不超过 50,000的人数,我们能发现多数被调查者年收入没有超过 50,000。如果我们简单地预测说“这个人的收入没有超过 $50,000”,我们就可以得到一个 准确率超过 50% 的预测。这样我们甚至不用看数据就能做到一个准确率超过 50%。这样一个预测被称作是天真的。通常对数据使用一个天真的预测器是十分重要的,这样能够帮助建立一个模型表现是否好的基准。 使用下面的代码单元计算天真的预测器的相关性能。
#不能使用scikit-learn,你需要根据公式自己实现相关计算。
#计算准确率
accuracy = len([x for x in y_val if x == 1]) / len(y_val)
#计算查准率 Precision
precision = len([x for x in y_val if x == 1]) / len(y_val)
#计算查全率 Recall
recall = len([x for x in y_val if x == 1]) / len([x for x in y_val if x == 1])
#使用上面的公式,设置beta=0.5,计算F-score
fscore = (1 + pow(0.5, 2)) * precision * recall / ((pow(0.5, 2) * precision +recall))
# 打印结果
print ("Naive Predictor on validation data: \n \
Accuracy score: {:.4f} \n \
Precision: {:.4f} \n \
Recall: {:.4f} \n \
F-score: {:.4f}".format(accuracy, precision, recall, fscore))
监督学习模型
模型1
模型名称
回答:决策树 (DecisionTree)
描述一个该模型在真实世界的一个应用场景。(你需要为此做点研究,并给出你的引用出处)
回答:应用在互联网店面个性化广告中:https://www.tandfonline.com/doi/abs/10.1080/10864415.2001.11044215
这个模型的优势是什么?他什么情况下表现最好?
回答:1.计算简单,易于理解,可解释性强;2.希望更好地理解手上的数据的时候往往可以使用决策树;3.比较适合处理有缺失属性的样本;4.能够处理不相关的特征;5.在相对短的时间内能够对大型数据源做出可行且效果良好的结果。
这个模型的缺点是什么?什么条件下它表现很差?
回答:1.容易发生过拟合(随机森林可以很大程度上减少过拟合);2.忽略了数据之间的相关性;3.对于那些各类别样本数量不一致的数据,在决策树当中,信息增益的结果偏向于那些具有更多数值的特征(只要是使用了信息增益,都有这个缺点,如RF)。
根据我们当前数据集的特点,为什么这个模型适合这个问题。
回答:1.易于编码;2.这个问题属于非线性问题,决策树正好适合解决非线性问题。
模型2
模型名称
回答:支撑向量机 (SVM)
描述一个该模型在真实世界的一个应用场景。(你需要为此做点研究,并给出你的引用出处)
回答:人脸监测:https://data-flair.training/blogs/applications-of-svm/?utm_campaign=Submission&utm_medium=Community&utm_source=GrowthHackers.com
这个模型的优势是什么?他什么情况下表现最好?
回答:1.可以解决高维问题,即大型特征空间;2.能够处理非线性特征的相互作用;3.无需依赖整个数据;4.可以提高泛化能力。
这个模型的缺点是什么?什么条件下它表现很差?
回答:1.当观测样本很多时,效率并不是很高;2.对非线性问题没有通用解决方案,有时候很难找到一个合适的核函数;3.对缺失数据敏感。
根据我们当前数据集的特点,为什么这个模型适合这个问题。
回答:因为数据量不是很多,所以使用SVM可以求解出比较精确的答案。
模型3
模型名称
回答:Logistic回归(LogisticRegression)
描述一个该模型在真实世界的一个应用场景。(你需要为此做点研究,并给出你的引用出处)
回答:是否会办金融产品的预测:https://towardsdatascience.com/building-a-logistic-regression-in-python-step-by-step-becd4d56c9c8
这个模型的优势是什么?他什么情况下表现最好?
回答:1.实现简单,广泛的应用于工业问题上;2.分类时计算量非常小,速度很快,存储资源低;3.便利的观测样本概率分数;4.对逻辑回归而言,多重共线性并不是问题,它可以结合L2正则化来解决该问题。
这个模型的缺点是什么?什么条件下它表现很差?
回答:1.当特征空间很大时,逻辑回归的性能不是很好;2.容易欠拟合,一般准确度不太高;3.不能很好地处理大量多类特征或变量;4.只能处理两分类问题(在此基础上衍生出来的softmax可以用于多分类),且必须线性可分;5.对于非线性特征,需要进行转换。
根据我们当前数据集的特点,为什么这个模型适合这个问题。
回答:选用逻辑回归是作为基线,和后面几个比较使用。
创建一个训练和预测的流水线
# 从sklearn中导入两个评价指标 - fbeta_score和accuracy_score
from sklearn.metrics import fbeta_score, accuracy_score
def train_predict(learner, sample_size, X_train, y_train, X_val, y_val):
'''
inputs:
- learner: the learning algorithm to be trained and predicted on
- sample_size: the size of samples (number) to be drawn from training set
- X_train: features training set
- y_train: income training set
- X_val: features validation set
- y_val: income validation set
'''
results = {}
# 使用sample_size大小的训练数据来拟合学习器
# Fit the learner to the training data using slicing with 'sample_size'
start = time() # 获得程序开始时间
learner = learner.fit(X_train[:sample_size], y_train[:sample_size])
end = time() # 获得程序结束时间
# 计算训练时间
results['train_time'] = end - start
# 得到在验证集上的预测值
# 然后得到对前300个训练数据的预测结果
start = time() # 获得程序开始时间
predictions_val = learner.predict(X_val)
predictions_train = learner.predict(X_train[:300])
end = time() # 获得程序结束时间
# 计算预测用时
results['pred_time'] = end - start
# 计算在最前面的300个训练数据的准确率
results['acc_train'] = accuracy_score(y_train[:300], predictions_train)
# 计算在验证上的准确率
results['acc_val'] = fbeta_score(y_val, predictions_val, beta = 0.5)
# 计算在最前面300个训练数据上的F-score
results['f_train'] = fbeta_score(y_train[:300], predictions_train, beta=0.5)
# 计算验证集上的F-score
results['f_val'] = fbeta_score(y_val, predictions_val, beta=0.5)
# 成功
print ("{} trained on {} samples.".format(learner.__class__.__name__, sample_size))
# 返回结果
return results
初始模型的评估
# 从sklearn中导入三个监督学习模型
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
# 初始化三个模型
clf_A = DecisionTreeClassifier(random_state=42)
clf_B = SVC(random_state=42)
clf_C = LogisticRegression(random_state=42)
# 计算1%, 10%, 100%的训练数据分别对应多少点
samples_1 = int(X_train.shape[0] * 0.01)
samples_10 = int(X_train.shape[0] * 0.1)
samples_100 = int(X_train.shape[0] * 1)
# 收集学习器的结果
results = {}
for clf in [clf_A, clf_B, clf_C]:
clf_name = clf.__class__.__name__
results[clf_name] = {}
for i, samples in enumerate([samples_1, samples_10, samples_100]):
results[clf_name][i] = train_predict(clf, samples, X_train, y_train, X_val, y_val)
# 对选择的三个模型得到的评价结果进行可视化
vs.evaluate(results, accuracy, fscore)
通过上面的运算结果表明,LogisticsRegression算法的比较适合于判断被调查者的年收入。
在训练集上,虽然决策树的表现比较好,是因为训练时用到了测试所使用的数据;
1、在测试集上,在训练10%和100%的数据时,决策树的表现不如SVC和LogisticsRegression,但训练1%的数据时,SVC的表现最差;
2、训练和预测时间上,特别是在测试集上,SVC使用的时间最长,其他两种模型的时间差不多。因为SVC是分类器,在处理大量数据上花费的时间比较多。
3、LogisticsRegression在测试集上效果最好,花费的时间也最少,所以选LogisticsRegression。
模型调优
# 导入'GridSearchCV', 'make_scorer'和其他一些需要的库
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer
from sklearn.metrics import f1_score
#初始化分类器
clf = LogisticRegression(random_state=42)
#创建你希望调节的参数列表
parameters = {'solver':['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'], 'max_iter':[10, 100, 1000]}
#创建一个fbeta_score打分对象
scorer = make_scorer(f1_score)
#在分类器上使用网格搜索,使用'scorer'作为评价函数
grid_obj = GridSearchCV(clf, parameters, scoring=scorer)
#用训练数据拟合网格搜索对象并找到最佳参数
grid_fit = grid_obj.fit(X_train, y_train)
# 得到estimator
best_clf = grid_obj.best_estimator_
# 使用没有调优的模型做预测
predictions = (clf.fit(X_train, y_train)).predict(X_val)
best_predictions = best_clf.predict(X_val)
# 汇报调优后的模型
print ("best_clf\n------")
print (best_clf)
# 汇报调参前和调参后的分数
print ("\nUnoptimized model\n------")
print ("Accuracy score on validation data: {:.4f}".format(accuracy_score(y_val, predictions)))
print ("F-score on validation data: {:.4f}".format(fbeta_score(y_val, predictions, beta = 0.5)))
print ("\nOptimized Model\n------")
print ("Final accuracy score on the validation data: {:.4f}".format(accuracy_score(y_val, best_predictions)))
print ("Final F-score on the validation data: {:.4f}".format(fbeta_score(y_val, best_predictions, beta = 0.5)))
最终模型评估
观察特征相关性
features_raw.head(1)
特征1: age 年龄可能对财富的累积和赚钱能力有影响。
特征2: workclass 工作类型会影响稳定性,进行可能对人的安全感有影响,从而影响是否接受产品的意愿。
特征3: education_level 教育程度可能会影响人对金钱和人生规划的态度。
特征4: matital-status 是否结婚会影响人的责任感,进而让人更加注重未来和影响金钱观。
特征5: capital-gain 收入可能会和是否接受金融产品相关。
提取特征重要性
#导入一个有'feature_importances_'的监督学习模型
from sklearn.ensemble import AdaBoostClassifier
#在训练集上训练一个监督学习模型
model = AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_depth=2), n_estimators=50)
start = time()
model.fit(X_train, y_train)
end = time()
model.predict(X_test)
full_time = end - start
#提取特征重要性
importances = model.feature_importances_
#绘图
vs.feature_plot(importances, X_train, y_train)
df_importance = pd.DataFrame(importances)
df_importance.sort_index(by=0, axis=0, ascending=False)[:5].sum()
1、这五个特征的权重加起来超过了0.5,值为0.589。
2、capital-gain、capital-loss、marital-status、age预测正确。
3、capital-gain、capital-loss直接和income相关。
4、marital-status 婚姻状况和家庭支出相关。
5、age和收入水平相关。
6、hours-per-week没有考虑到,有些行业的工资低但工作时间也比较长,例如富士康的流水线工人,会影响人的幸福感,从而影响金钱观。
特征选择
# 导入克隆模型的功能
from sklearn.base import clone
# 减小特征空间
X_train_reduced = X_train[X_train.columns.values[(np.argsort(importances)[::-1])[:5]]]
X_val_reduced = X_val[X_val.columns.values[(np.argsort(importances)[::-1])[:5]]]
# 在前面的网格搜索的基础上训练一个“最好的”模型
clf_on_reduced = (clone(best_clf)).fit(X_train_reduced, y_train)
# 做一个新的预测
reduced_predictions = clf_on_reduced.predict(X_val_reduced)
# 对于每一个版本的数据汇报最终模型的分数
print ("Final Model trained on full data\n------")
print ("Accuracy on validation data: {:.4f}".format(accuracy_score(y_val, best_predictions)))
print ("F-score on validation data: {:.4f}".format(fbeta_score(y_val, best_predictions, beta = 0.5)))
print ("\nFinal Model trained on reduced data\n------")
print ("Accuracy on validation data: {:.4f}".format(accuracy_score(y_val, reduced_predictions)))
print ("F-score on validation data: {:.4f}".format(fbeta_score(y_val, reduced_predictions, beta = 0.5)))
在测试集上测试模型
print('clf\n------')
print(clf)
print('\nbest clf\n------')
print(best_clf)
test_predictions = clf.predict(X_test)
best_test_predictions = best_clf.predict(X_test)
print('\nUnoptimized model\n------')
print('Accuracy score on validation data: {:.4f}'.format(accuracy_score(y_val, predictions)))
print('F-score on validation data: {:.4f}'.format(fbeta_score(y_val, predictions, beta=0.5)))
print('\nOptimized model\n------')
print('Final accuracy score on validation data: {:.4f}'.format(accuracy_score(y_val, best_predictions)))
print('Final F-score on validation data: {:.4f}'.format(fbeta_score(y_val, best_predictions, beta=0.5)))
print('\nUnoptimized Model on Testing\n------')
print('Final accuracy score on validation data: {:.4f}'.format(accuracy_score(y_test, test_predictions)))
print('Final F-score on validation data: {:.4f}'.format(fbeta_score(y_test, test_predictions, beta=0.5)))
print('\nOptimized Model on Testing\n------')
print('Final accuracy score on validation data: {:.4f}'.format(accuracy_score(y_test, test_predictions)))
print('Final F-score on validation data: {:.4f}'.format(fbeta_score(y_test, best_test_predictions, beta=0.5)))