yijigao / finding_donors_master

Machine Learning, 监督学习, 二分类, AdaBoost

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

监督学习项目: 为CharityML寻找捐献者

项目背景

在这个项目中,你将使用1994年美国人口普查收集的数据,选用几个监督学习算法以准确地建模被调查者的收入。然后,你将根据初步结果从中选择出最佳的候选算法,并进一步优化该算法以最好地建模这些数据。你的目标是建立一个能够准确地预测被调查者年收入是否超过50000美元的模型。这种类型的任务会出现在那些依赖于捐款而存在的非营利性组织。了解人群的收入情况可以帮助一个非营利性的机构更好地了解他们要多大的捐赠,或是否他们应该接触这些人。虽然我们很难直接从公开的资源中推断出一个人的一般收入阶层,但是我们可以(也正是我们将要做的)从其他的一些公开的可获得的资源中获得一些特征从而推断出该值。

数据

修改的人口普查数据集含有将近32,000个数据点,每一个数据点含有13个特征。这个数据集是Ron Kohavi的论文*"Scaling Up the Accuracy of Naive-Bayes Classifiers: a Decision-Tree Hybrid",*中数据集的一个修改版本。你能够在这里找到论文,在UCI的网站找到原始数据集。

特征

  • age: 一个整数,表示被调查者的年龄。
  • workclass: 一个类别变量表示被调查者的通常劳动类型,允许的值有 {Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked}
  • education_level: 一个类别变量表示教育程度,允许的值有 {Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters, 1st-4th, 10th, Doctorate, 5th-6th, Preschool}
  • education-num: 一个整数表示在学校学习了多少年
  • marital-status: 一个类别变量,允许的值有 {Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse}
  • occupation: 一个类别变量表示一般的职业领域,允许的值有 {Tech-support, Craft-repair, Other-service, Sales, Exec-managerial, Prof-specialty, Handlers-cleaners, Machine-op-inspct, Adm-clerical, Farming-fishing, Transport-moving, Priv-house-serv, Protective-serv, Armed-Forces}
  • relationship: 一个类别变量表示家庭情况,允许的值有 {Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried}
  • race: 一个类别变量表示人种,允许的值有 {White, Asian-Pac-Islander, Amer-Indian-Eskimo, Other, Black}
  • sex: 一个类别变量表示性别,允许的值有 {Female, Male}
  • capital-gain: 连续值。
  • capital-loss: 连续值。
  • hours-per-week: 连续值。
  • native-country: 一个类别变量表示原始的国家,允许的值有 {United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, Greece, South, China, Cuba, Iran, Honduras, Philippines, Italy, Poland, Jamaica, Vietnam, Mexico, Portugal, Ireland, France, Dominican-Republic, Laos, Ecuador, Taiwan, Haiti, Columbia, Hungary, Guatemala, Nicaragua, Scotland, Thailand, Yugoslavia, El-Salvador, Trinadad&Tobago, Peru, Hong, Holand-Netherlands}

目标变量

  • income: 一个类别变量,表示收入属于那个类别,允许的值有 {<=50K, >50K}

探索数据

运行下面的代码单元以载入需要的Python库并导入人口普查数据。注意数据集的最后一列'income'将是我们需要预测的列(表示被调查者的年收入会大于或者是最多50,000美元),人口普查数据中的每一列都将是关于被调查者的特征。

# 为这个项目导入需要的库
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib
from time import time
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import warnings

warnings.filterwarnings("ignore")
# from IPython.display import display # 允许为DataFrame使用display()

# 导入附加的可视化代码visuals.py
import visuals as vs

# 为notebook提供更加漂亮的可视化
%matplotlib inline
matplotlib.rcParams["figure.figsize"] = (14, 10)
# 导入人口普查数据
data = pd.read_csv("census.csv")

data.head(2)
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
age workclass education_level education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country income
0 39 State-gov Bachelors 13.0 Never-married Adm-clerical Not-in-family White Male 2174.0 0.0 40.0 United-States <=50K
1 50 Self-emp-not-inc Bachelors 13.0 Married-civ-spouse Exec-managerial Husband White Male 0.0 0.0 13.0 United-States <=50K
data.describe()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
age education-num capital-gain capital-loss hours-per-week
count 45222.000000 45222.000000 45222.000000 45222.000000 45222.000000
mean 38.547941 10.118460 1101.430344 88.595418 40.938017
std 13.217870 2.552881 7506.430084 404.956092 12.007508
min 17.000000 1.000000 0.000000 0.000000 1.000000
25% 28.000000 9.000000 0.000000 0.000000 40.000000
50% 37.000000 10.000000 0.000000 0.000000 40.000000
75% 47.000000 13.000000 0.000000 0.000000 45.000000
max 90.000000 16.000000 99999.000000 4356.000000 99.000000

首先我们对数据集进行一个粗略的探索,我们将看看每一个类别里会有多少被调查者?并且告诉我们这些里面多大比例是年收入大于50,000美元的。

# TODO:总的记录数
n_records = len(data)

# TODO:被调查者的收入大于$50,000的人数
n_greater_50k = len(data[data["income"] == ">50K"])

# TODO:被调查者的收入最多为$50,000的人数
n_at_most_50k = len(data[data["income"] == "<=50K"])

# TODO:被调查者收入大于$50,000所占的比例
greater_percent = n_greater_50k*100 / n_records

# 打印结果
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)) 
Total number of records: 45222
Individuals making more than $50,000: 11208
Individuals making at most $50,000: 34014
Percentage of individuals making more than $50,000: 24.78%

准备数据

在数据能够被作为输入提供给机器学习算法之前,它经常需要被清洗,格式化,和重新组织 - 这通常被叫做预处理。幸运的是,对于这个数据集,没有我们必须处理的无效或丢失的条目,然而,由于某一些特征存在的特性我们必须进行一定的调整。这个预处理都可以极大地帮助我们提升几乎所有的学习算法的结果和预测能力。

获得特征和标签

income 列是我们需要的标签,记录一个人的年收入是否高于50K。 因此我们应该把他从数据中剥离出来,单独存放。

# 将数据切分成特征和对应的标签
income_raw = data['income']
features_raw = data.drop('income', axis = 1)

转换倾斜的连续特征

一个数据集有时可能包含至少一个靠近某个数字的特征,但有时也会有一些相对来说存在极大值或者极小值的不平凡分布的的特征。算法对这种分布的数据会十分敏感,并且如果这种数据没有能够很好地规一化处理会使得算法表现不佳。在人口普查数据集的两个特征符合这个描述:'capital-gain''capital-loss'

# 可视化 'capital-gain'和'capital-loss' 两个特征
# plt.figure(figsize=(14, 10))
fig, ax = plt.subplots(1, 2, sharey=True)
sns.distplot(features_raw["capital-gain"],kde=False,ax=ax[0])
sns.distplot(features_raw["capital-loss"],kde=False,ax=ax[1])
plt.ylim(0, 2000)
(0, 2000)

png

对于高度倾斜分布的特征如'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' 两个特征
fig, ax = plt.subplots(1, 2, sharey=True)
plt.ylim(0, 2000)
sns.distplot(features_raw["capital-gain"],kde=False, ax=ax[0])
sns.distplot(features_raw["capital-loss"],kde=False, ax=ax[1])
<matplotlib.axes._subplots.AxesSubplot at 0x189e8f60>

png

规一化数字特征

除了对于高度倾斜的特征施加转换,对数值特征施加一些形式的缩放通常会是一个好的习惯。在数据上面施加一个缩放并不会改变数据分布的形式(比如上面说的'capital-gain' or 'capital-loss');但是,规一化保证了每一个特征在使用监督学习器的时候能够被平等的对待。注意一旦使用了缩放,观察数据的原始形式不再具有它本来的意义了,就像下面的例子展示的。

运行下面的代码单元来规一化每一个数字特征。我们将使用sklearn.preprocessing.MinMaxScaler来完成这个任务。

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])

# 显示一个经过缩放的样例记录
features_raw.head(3)
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
age workclass education_level education-num marital-status occupation relationship race sex capital-gain capital-loss hours-per-week native-country
0 0.301370 State-gov Bachelors 0.800000 Never-married Adm-clerical Not-in-family White Male 0.02174 0.0 0.397959 United-States
1 0.452055 Self-emp-not-inc Bachelors 0.800000 Married-civ-spouse Exec-managerial Husband White Male 0.00000 0.0 0.122449 United-States
2 0.287671 Private HS-grad 0.533333 Divorced Handlers-cleaners Not-in-family White Male 0.00000 0.0 0.397959 United-States

数据预处理

从上面的数据探索中的表中,我们可以看到有几个属性的每一条记录都是非数字的。通常情况下,学习算法期望输入是数字的,这要求非数字的特征(称为类别变量)被转换。转换类别变量的一种流行的方法是使用独热编码方案。独热编码为每一个非数字特征的每一个可能的类别创建一个_“虚拟”_变量。例如,假设someFeature有三个可能的取值AB或者C,。我们将把这个特征编码成someFeature_A, someFeature_BsomeFeature_C.

| 特征X | | 特征X_A | 特征X_B | 特征X_C | | :-: | | :-: | :-: | :-: | | B | | 0 | 1 | 0 | | C | ----> 独热编码 ----> | 0 | 0 | 1 | | A | | 1 | 0 | 0 |

此外,对于非数字的特征,我们需要将非数字的标签'income'转换成数值以保证学习算法能够正常工作。因为这个标签只有两种可能的类别("<=50K"和">50K"),我们不必要使用独热编码,可以直接将他们编码分别成两个类01

# TODO:使用pandas.get_dummies()对'features_raw'数据进行独热编码
features = pd.get_dummies(features_raw)

# TODO:将'income_raw'编码成数字值
income = income_raw.apply(lambda a: 0 if a=="<=50K" else 1)

# 打印经过独热编码之后的特征数量
encoded = list(features.columns)
print("{} total features after one-hot encoding.".format(len(encoded)))

# 移除下面一行的注释以观察编码的特征名字
#print encoded
103 total features after one-hot encoding.

混洗和切分数据

现在所有的 类别变量 已被转换成数值特征,而且所有的数值特征已被规一化。和我们一般情况下做的一样,我们现在将数据(包括特征和它们的标签)切分成训练和测试集。其中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])) 
Training set has 28941 samples.
Validation set has 7236 samples.
Testing set has 9045 samples.

评价模型性能

在这一部分中,我们将尝试四种不同的算法,并确定哪一个能够最好地建模数据。四种算法包含一个天真的预测器 和三个你选择的监督学习器。

评价方法和朴素的预测器

CharityML通过他们的研究人员知道被调查者的年收入大于$50,000最有可能向他们捐款。因为这个原因CharityML对于准确预测谁能够获得$50,000以上收入尤其有兴趣。这样看起来使用准确率作为评价模型的标准是合适的。另外,把没有收入大于$50,000的人识别成年收入大于$50,000对于CharityML来说是有害的,因为他想要找到的是有意愿捐款的用户。这样,我们期望的模型具有准确预测那些能够年收入大于$50,000的能力比模型去查全这些被调查者更重要。我们能够使用F-beta score作为评价指标,这样能够同时考虑查准率和查全率:

$$ F_{\beta} = (1 + \beta^2) \cdot \frac{precision \cdot recall}{\left( \beta^2 \cdot precision \right) + recall} $$

尤其是,当 $\beta = 0.5$ 的时候更多的强调查准率,这叫做F$_{0.5}$ score (或者为了简单叫做F-score)。

天真的预测器的性能

通过查看收入超过和不超过 $50,000 的人数,我们能发现多数被调查者年收入没有超过 $50,000。如果我们简单地预测说*“这个人的收入没有超过 $50,000”,我们就可以得到一个 准确率超过 50% 的预测。这样我们甚至不用看数据就能做到一个准确率超过 50%。这样一个预测被称作是天真的。通常对数据使用一个天真的预测器*是十分重要的,这样能够帮助建立一个模型表现是否好的基准。 使用下面的代码单元计算天真的预测器的相关性能。将你的计算结果赋值给'accuracy', ‘precision’, ‘recall’'fscore',这些值会在后面被使用,请注意这里不能使用scikit-learn,你需要根据公式自己实现相关计算。 如果我们选择一个无论什么情况都预测被调查者年收入大于 $50,000 的模型,那么这个模型在验证集上的准确率,查准率,查全率和 F-score是多少?

naive_pred = np.ones(y_val.shape)
#不能使用scikit-learn,你需要根据公式自己实现相关计算。
true_negatives,false_negatives,false_positives,true_positives = 0, 0, 0, 0

for prediction, truth in zip(naive_pred, y_val):
    if prediction == 0 and truth == 0:
        true_negatives += 1
    elif prediction == 0 and truth == 1:
        false_negatives += 1
    elif prediction == 1 and truth == 0:
        false_positives += 1
    elif prediction == 1 and truth == 1:
        true_positives += 1

        
#TODO: 计算准确率
total_predictions = true_negatives + false_negatives + false_positives + true_positives

accuracy = 1.0 * (true_positives + true_negatives) / (total_predictions)

# TODO: 计算查准率 Precision
precision = 1.0 * true_positives / (true_positives + false_positives)

# TODO: 计算查全率 Recall
recall = 1.0 * true_positives / (true_positives + false_negatives)

# TODO: 使用上面的公式,设置beta=0.5,计算F-score
fscore = (1 + 0.5**2)* precision * recall /(((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))
Naive Predictor on validation data: 
     Accuracy score: 0.2478 
     Precision: 0.2478 
     Recall: 1.0000 
     F-score: 0.2917

监督学习模型

模型选择

你能够在 scikit-learn 中选择以下监督学习模型

  • 高斯朴素贝叶斯 (GaussianNB)
  • 决策树 (DecisionTree)
  • 集成方法 (Bagging, AdaBoost, Random Forest, Gradient Boosting)
  • K近邻 (K Nearest Neighbors)
  • 随机梯度下降分类器 (SGDC)
  • 支撑向量机 (SVM)
  • Logistic回归(LogisticRegression)

模型1

模型名称: 决策树

描述一个该模型在真实世界的一个应用场景。

回答:银行行销领域的预测。在电话销售前先根据客户的特征进行预测,以此提高电销的成功率。

这个模型的优势是什么?他什么情况下表现最好?

优点:易于实现和理解;能够同时处理数据型和常规型属性;对缺失值不敏感;可以处理不相关特征数据;只需一次构建,反复使用。
当实例由键值对表示,目标函数具有离散的输出值的时候,表现最好。

这个模型的缺点是什么?什么条件下它表现很差?

缺点:容易出现过拟合;对于连续的字段比较难预测;对于类比不一致的样本,决策树的信息增益倾向于那些数据值较多的特征。当分类的类别过于复杂,数据的属性之间具有强关联时,表现很差。

根据我们当前数据集的特点,为什么这个模型适合这个问题。

该问题是非线性问题,决策树能够很好的解决非线性问题;不需要太多的训练数据,不需要对数据过多的处理。

模型2

模型名称: 集成方法 AdaBoost

描述一个该模型在真实世界的一个应用场景。(你需要为此做点研究,并给出你的引用出处)

乳腺癌微钙化的自动检测

这个模型的优势是什么?他什么情况下表现最好?

优势:可以将不同的分类算法作为弱分类器,并很好地利用弱分类器进行级联,具有很高的精度。

这个模型的缺点是什么?什么条件下它表现很差?

缺点:AdaBoost算法的迭代次数不好设定,需要使用交叉验证的方式来确定;数据集的不平衡分布导致分类器的分类精度下降;训练比较耗费时间;对异常值比较敏感。
当数据集分布非常不均匀,数据集中含有较多的异常值的时候,表现很差。

根据我们当前数据集的特点,为什么这个模型适合这个问题。

我们的数据集中的异常值较少,并且可以归属于二分类问题,因此适合。

模型3

模型名称: 支持向量机

描述一个该模型在真实世界的一个应用场景。(你需要为此做点研究,并给出你的引用出处)

模式识别相关领域,常见疾病中的应用

这个模型的优势是什么?他什么情况下表现最好?

优点:可以有效地处理高维空间的数据;可以有效地处理变量个数大于样本个数的数据;可以提高泛化能力等。
当数据纬度较高,样本数量较少时,表现良好。

这个模型的缺点是什么?什么条件下它表现很差?

缺点:无法处理大规模的数据集,训练时间长; 对缺失数据非常敏感。
当数据集过大,数据集中缺失较多的数据,对算法效率要求高的时候,表现很差。

根据我们当前数据集的特点,为什么这个模型适合这个问题。

我们当前数据集较少,经过独热编码后,维度较高,并且是二分类问题,而且SVM可以指定不同的核函数,可以提高分类器的准确率,因此适合。

创建一个训练和预测的流水线

为了正确评估你选择的每一个模型的性能,创建一个能够帮助你快速有效地使用不同大小的训练集并在验证集上做预测的训练和验证的流水线是十分重要的。 你在这里实现的功能将会在接下来的部分中被用到。在下面的代码单元中,你将实现以下功能:

  • sklearn.metrics中导入fbeta_scoreaccuracy_score
  • 用训练集拟合学习器,并记录训练时间。
  • 对训练集的前300个数据点和验证集进行预测并记录预测时间。
  • 计算预测训练集的前300个数据点的准确率和F-score。
  • 计算预测验证集的准确率和F-score。
# TODO:从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 = {}
    
    # TODO:使用sample_size大小的训练数据来拟合学习器
    # TODO: 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() # 获得程序结束时间
    
    # TODO:计算训练时间
    results['train_time'] = end - start
    
    # TODO: 得到在验证集上的预测值
    #       然后得到对前300个训练数据的预测结果
    start = time() # 获得程序开始时间
    predictions_val = learner.predict(X_val)
    predictions_train = learner.predict(X_train[:300])
    end = time() # 获得程序结束时间
    
    # TODO:计算预测用时
    results['pred_time'] = end - start
            
    # TODO:计算在最前面的300个训练数据的准确率
    results['acc_train'] = accuracy_score(y_train[:300], predictions_train)
        
    # TODO:计算在验证上的准确率
    results['acc_val'] = accuracy_score(y_val, predictions_val)
    
    # TODO:计算在最前面300个训练数据上的F-score
    results['f_train'] = fbeta_score(y_train[:300],predictions_train,beta=0.5)
        
    # TODO:计算验证集上的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

初始模型的评估

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

  • 导入你在前面讨论的三个监督学习模型。
  • 初始化三个模型并存储在'clf_A''clf_B''clf_C'中。
    • 使用模型的默认参数值,在接下来的部分中你将需要对某一个模型的参数进行调整。
    • 设置random_state (如果有这个参数)。
  • 计算1%, 10%, 100%的训练数据分别对应多少个数据点,并将这些值存储在'samples_1', 'samples_10', 'samples_100'

**注意:**取决于你选择的算法,下面实现的代码可能需要一些时间来运行!

# TODO:从sklearn中导入三个监督学习模型
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import SVC

# TODO:初始化三个模型
clf_A = DecisionTreeClassifier(random_state=42)
clf_B = AdaBoostClassifier(random_state=42)
clf_C = SVC(random_state=42)

# TODO:计算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.0)

# 收集学习器的结果
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)
DecisionTreeClassifier trained on 289 samples.
DecisionTreeClassifier trained on 2894 samples.
DecisionTreeClassifier trained on 28941 samples.
AdaBoostClassifier trained on 289 samples.
AdaBoostClassifier trained on 2894 samples.
AdaBoostClassifier trained on 28941 samples.
SVC trained on 289 samples.
SVC trained on 2894 samples.
SVC trained on 28941 samples.
# 对选择的三个模型得到的评价结果进行可视化
g, ax = plt.subplots(2, 3, figsize=(11, 7))
# constants
bar_width = 0.3
colors = ['#A00000','#00A0A0','#00A000']

for k, learner in enumerate(results):
    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)) 

# Add unique y-labels
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")
# Add titles
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 Validation Set")
ax[1, 2].set_title("F-score on Validation Set")

# Add horizontal lines for naive predictors
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 = fscore, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')
ax[1, 2].axhline(y = fscore, xmin = -0.1, xmax = 3.0, linewidth = 1, color = 'k', linestyle = 'dashed')

# Set y-limits for score panels
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))

# Create patches for the legend
patches = []
for i, learner in enumerate(results.keys()):
    patches.append(mpatches.Patch(color = colors[i], label = learner))
plt.legend(handles = patches, bbox_to_anchor = (-.80, 2.53), \
           loc = 'upper center', borderaxespad = 0., ncol = 3, fontsize = 'x-large')

# Aesthetics
plt.suptitle("Performance Metrics for Three Supervised Learning Models", fontsize = 16, y = 1.10)
plt.tight_layout()

png


提高效果

在这最后一节中,您将从三个有监督的学习模型中选择 最好的 模型来使用学生数据。你将在整个训练集(X_trainy_train)上使用网格搜索优化至少调节一个参数以获得一个比没有调节之前更好的 F-score。

选择最佳的模型

根据以上结果,AdaBoost比较合适。

  1. 根据模型预测/训练时间看,AdaBoost 和 决策树相差不大,但是SVM花费时间过长,说明SVM算法效率差
  2. 根据准确率和F-score评价指标看,训练集中决策树表现最好,但是在验证集中表现不如其它两个算法,说明出现了过拟合。
  3. 根据数据集特点,这是个二分类问题,综上表现,AdaBoost因该是理想的模型

用通俗的话解释模型

用一到两段话,向 CharityML 用外行也听得懂的话来解释最终模型是如何工作的。你需要解释所选模型的主要特点。例如,这个模型是怎样被训练的,它又是如何做出预测的。避免使用高级的数学或技术术语,不要使用公式或特定的算法名词。

AdaBoost 是自适应算法,其主要特点是通过改变训练样本的权重,学习多个弱分类器,并将这些分类器进行线性拟合,提高分类器性能。首先我们假设数据集具有相同的权值分布,即对N个样本赋予初始的权值1/N, 然后使用当前加权的训练集来学习基本分类器。在每次学习基本分类器的过程中,都会计算基本分类器的误差率,并且通过误差率的大小,来更新训练集的权值分布,即被基本分类器错分类样本的权值增大,正确分类的样本的权值减少。这样为下一轮学习新的基本分类器提供不同权值的训练集。在每次学习分类器的时候,都会计算分类器的权重,即分类器误差率越小的分类器在最终分类器中的作用越大。由此经过K轮学习,得到不同权重的K个基本分类学习器,并将这些分类器进行线性拟合,得到最终分类器。这就是AdaBoost算法的工作流程。

模型调优

调节选择的模型的参数。使用网格搜索(GridSearchCV)来至少调整模型的重要参数(至少调整一个),这个参数至少需尝试3个不同的值。你要使用整个训练集来完成这个过程。在接下来的代码单元中,你需要实现以下功能:

  • 导入sklearn.model_selection.GridSearchCVsklearn.metrics.make_scorer.
  • 初始化你选择的分类器,并将其存储在clf中。
  • 设置random_state (如果有这个参数)。
  • 创建一个对于这个模型你希望调整参数的字典。
  • 例如: parameters = {'parameter' : [list of values]}。
  • 注意: 如果你的学习器有 max_features 参数,请不要调节它!
  • 使用make_scorer来创建一个fbeta_score评分对象(设置$\beta = 0.5$)。
  • 在分类器clf上用'scorer'作为评价函数运行网格搜索,并将结果存储在grid_obj中。
  • 用训练集(X_train, y_train)训练grid search object,并将结果存储在grid_fit中。
# TODO:导入'GridSearchCV', 'make_scorer'和其他一些需要的库
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer

# TODO:初始化分类器
clf = AdaBoostClassifier(random_state=42)

# TODO:创建你希望调节的参数列表
parameters = {
    "n_estimators":(10, 300)
}

# TODO:创建一个fbeta_score打分对象
scorer = make_scorer(fbeta_score, beta=0.5)

# TODO:在分类器上使用网格搜索,使用'scorer'作为评价函数
grid_obj = GridSearchCV(clf, param_grid=parameters, scoring=scorer)

# TODO:用训练数据拟合网格搜索对象并找到最佳参数
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("Unoptimized 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))) 
Unoptimized model
------
Accuracy score on validation data: 0.8648
F-score on validation data: 0.7443

Optimized Model
------
Final accuracy score on the validation data: 0.8722
Final F-score on the validation data: 0.7559

最终模型评估

你的最优模型在测试数据上的准确率和 F-score 是多少?这些分数比没有优化的模型好还是差?你优化的结果相比于你在问题 1中得到的天真预测器怎么样?

结果:

评价指标 天真预测器 未优化的模型 优化的模型
准确率 0.2478 0.8648 0.8722
F-score 0.2917 0.7443 0.7559

使用AdaBoost算法进行网格搜索对参数n_estimators进行调整,得到的分数略高于未优化的模型。但远高于天真的预测器


特征的重要性

在数据上(比如我们这里使用的人口普查的数据)使用监督学习算法的一个重要的任务是决定哪些特征能够提供最强的预测能力。专注于少量的有效特征和标签之间的关系,我们能够更加简单地理解这些现象,这在很多情况下都是十分有用的。在这个项目的情境下这表示我们希望选择一小部分特征,这些特征能够在预测被调查者是否年收入大于$50,000这个问题上有很强的预测能力。

选择一个有 'feature_importance_' 属性的scikit学习分类器(例如 AdaBoost,随机森林)。'feature_importance_' 属性是对特征的重要性排序的函数。在下一个代码单元中用这个分类器拟合训练集数据并使用这个属性来决定人口普查数据中最重要的5个特征。

观察特征相关性

探索数据的时候,它显示在这个人口普查数据集中每一条记录我们有十三个可用的特征。
在这十三个记录中,你认为哪五个特征对于预测是最重要的,选择每个特征的理由是什么?你会怎样对他们排序?

  • education_level: 教育程度越高的人,收入普遍越高。
  • hours-per-week: 工作时间越长,获得更多收入的可能越大。
  • captial-gain: 资金的获得多少是决定收入的重要部分。
  • occupation: 所处行业不同,收入差异性也可能很大。
  • age: 年纪越大,经验越丰富,获得更多收入的可能越大。

提取特征重要性

选择一个scikit-learn中有feature_importance_属性的监督学习分类器,这个属性是一个在做预测的时候根据所选择的算法来对特征重要性进行排序的功能。

# TODO:导入一个有'feature_importances_'的监督学习模型
from sklearn.ensemble import RandomForestClassifier

# TODO:在训练集上训练一个监督学习模型
model = RandomForestClassifier()
model.fit(X_train, y_train)

# TODO: 提取特征重要性
importances = model.feature_importances_

# 绘图
vs.feature_plot(importances, X_train, y_train)

png

特征选择

如果我们只是用可用特征的一个子集的话模型表现会怎么样?通过使用更少的特征来训练,在评价指标的角度来看我们的期望是训练和预测的时间会更少。从上面的可视化来看,我们可以看到前五个最重要的特征贡献了数据中所有特征中超过一半的重要性。这提示我们可以尝试去减小特征空间,简化模型需要学习的信息。下面代码单元将使用你前面发现的优化模型,并只使用五个最重要的特征在相同的训练集上训练模型。

# 导入克隆模型的功能
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))) 
Final Model trained on full data
------
Accuracy on validation data: 0.8722
F-score on validation data: 0.7559

Final Model trained on reduced data
------
Accuracy on validation data: 0.8577
F-score on validation data: 0.7354

特征选择的影响

最终模型在只使用5个特征的数据上和使用所有特征数据上的F-score和准确率相比略有下降。 考虑训练时间,我会考虑使用部分重要特征数据作为训练集

在测试集上测试模型

#TODO test your model on testing data and report accuracy and F score
final_prediction = best_clf.predict(X_test)
print("accuracy_score: {:4f}".format(accuracy_score(y_test, final_prediction)))
print("accuracy_score: {:4f}".format(fbeta_score(y_test, final_prediction, beta=0.5)))
accuracy_score: 0.867109
accuracy_score: 0.750157

About

Machine Learning, 监督学习, 二分类, AdaBoost


Languages

Language:Jupyter Notebook 96.6%Language:Python 3.4%