机器学习——过程概览

最近几天一直在学习Machine Learning的只是,自从知道Kaggle这个网站以来,成为Grand Master的计划也被提到了日程上来。

做了几个Get Started的competitions之后逐渐总结了一下机器学习的一个例程。

首先,在Data Science的时候,有下面三个点非常重要:

  1.  对数据的认识特别重要。通过研究数据本身的特点,数据与数据之间的关系,往往会给我们之后的工作带来灵感。
  2. Feature Engineering特别重要。我们有一个词叫做Magic Feature,就是引入这个Feature以后你的score会有一个很明显的上升。所以说Feature Engineer可以在一定程度上帮助我们的提升自己的ranking。
  3. Model Ensemble特别重要。大家都知道撸一个baseline model是一件特别容易的事情,所以大家都能达到baseline model的score。而在所有tricks中,最能明显提升你的ranking的,一个是Feature Engineering,还有一个就是Model Ensemble。

这个可以说是我们在kaggle比赛之前必须了解的点,下面我们就来看看我们机器学习的基本流程。

一. EDA

(1). 从整体上了解你的数据

先了解一些基本的概念:

首先,数据基本上可以分为两大类,Numerical DataCategorical Data。前者又可以分为Discrete ValueContinuous Value

对于Numerical Data,他独有的特性有:mean, std, max和min等。我们可以用pandas中的describe方法来描述。

# For numerical data
data_set.describe()

对于Discrete Data,他独有的特性有:unique, count等。我们依然可以用pandas中的describe方法来描述。

# For continuous data
data_set.describe(include=["O"])    # 注意这里是大写的O

个人认为Pandas包最为强大的一点是对于数据缺失值的处理,这个你会在之后简单的数据预处理之中碰到。为了获得到底有多少缺失值,每一个Feature的类型又是什么,我们可以用info方法来查看。

# See if there is any missing value
data_set.info()

在一些特殊的问题下我们可能还要知道相应的分布,这个实现真的非常简单,我们可以用DataFrame对象的value_counts+Plot来达到:

train_set.Survived.value_counts().plot(kind='bar')

以上这些都是非常简单的了解你数据的方法。我建议在ipython这种REPL环境中弄,当然如果是ipynb也很不错,ipynb的一个强大之处就在于他能让代码按块执行(区别于其他REPL环境的按行执行),执行后还能保存相应的结果,执行下一段代码的时候还不用重新训练(区别于Pycharm等IDE)。所以我强烈推荐。

(2). 了解数据与数据之间的关系

当你看到总体数据的一些特征以后,你恐怕对最后要选择的特征有一点assumption了,下面要做的就是讲这些assumption付诸实践。这里我们既可以使用DataFrame的方式,也可以用matplotlib或者Seaborn中的图像来展现我们的结果

这里以Kaggle中Hello World级别的competition——Titanic来举例子。从题干中我们得知妇女和小孩优先,所以我们就猜想性别和年龄会不会和最后的幸存结果有关。

像性别这种,就是典型的Categorical Data,Panads里面提供了一个非常好的API——groupby, 它创建了一个groupby对象,我们可以通过对这个groupby对象使用各种方法来查看相应类别下的统计结果

# Groupby method creates a group object
# ax_index format the data frame
train_set[["Sex", "Survived"]].groupby(["Sex"], as_index=False).mean()

对于Categorical Data,也用一种较好的可视化的方法,就是在matplotlib中设置stacked=True。

Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts()
Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts()
df=pd.DataFrame({'Survived':Survived_1, 'Unsurvived':Survived_0})
df.plot(kind='bar', stacked=True)

年龄也是一个非常典型的特征,他虽然看上去是Discrete Numerical Data,但是具体的问题往往不是取决于年龄的具体数值,而是取决于年龄的分段——比如Titanic这个问题中小孩具有逃生的优先级,那么我们就要在年龄中划分出小孩的分段,这个在特征工程中叫做等宽分箱法。(当然特征工程现在还不用急着做)

我们先把年龄视为一个Numerical Data来对待,这个好像就不能用Groupby这种放来来进行DataFrame的观察了,我们还是用histgram来查看吧。

具体的来讲的话,我希望获得Survive的年龄的分布以及Unsurvived的年龄分布,这时候我们就需要两张图了,这里我推荐使用Seaborn的FacetGrid方法,因为matplotlib中的多个子图的API比较混乱且难以理解。

g = sns.FacetGrid(train_set, col='Survived')
g.map(plt.hist, 'Age', bins=20)

如果你想同时研究多个变量,我们的FacetGrid也同样支持。注意我们可以在最前面添加sns.set()或者是引入hue属性让我们的图变得更加好看(基本上我数模的paper就是这么整的)。

# 在不同社会等级下,男性和女性在不同登陆港口下的数量对比
grid = sns.FacetGrid(train_set, col='Pclass', hue='Sex', palette='seismic', size=4)
grid.map(sns.countplot, 'Embarked', alpha=.8)
grid.add_legend() 

 

二. 数据预处理

数据预处理主要做三件事情:

  1.  处理缺失值。很显然如果不处理确实值的话我们将很难进行下面的工作。
  2.  特征工程。特征工程又可以大致上分为特征筛选和特征构造,这个我将放在最后讲。
  3.  标准化和编码。一般而言我们的Numerical Value的数值比较大,在我们的模型上不太好收敛,所以我们要对其进行标准化,将其限制在一定范围。至于我们的Categorical Value,一般我们都会选择one-hot编码,事实上,对于一些字符串类型的特征,我们也不得不对其进行编码。

(1).处理缺失值

处理缺失值的方法实在是太多了,传送门。总的来说就是具体问题具体分析。之前也提到过pandas提供了不少API用来处理缺失值,比如想要用均值来填充缺失值的话,可以用下面的语句:

test_df['Fare'].fillna(test_df['Fare'].dropna().median(), inplace=True)

如果想把缺失值作为一个新的类别

# 这里如果先填充缺失值,那么所有的数据都会变成"yes",想一想是为什么
df.loc[df.Cabin.notna(), "Cabin"] = "yes"
df.loc[df.Cabin.isna(), "Cabin"] = "no"

这个我之后也会做相应的博文总结。

(2). 标准化和编码

这个在之前已经说的相当清楚了,如果是Numerical数据的话,就采用标准化算法,这个在sklearn中有很多,比如说下面我选择了preprocessing中的StandardScaler:

    df["Age"] = StandardScaler().fit_transform(df["Age"].to_frame())

如果是Categorical数据的话,就采用one-hot编码(当然你也采用其他编码方式,比如说顺序编码或者二进制编码等其他编码方法),pandas里倒是有直接用one-hot进行编码的API,叫做get_dummies,这个会在你原来的DataFrame上产生新的特征,详情见下:

features = ["Cabin", "Embarked", "Sex", "Pclass", "Title"]
one_hot_category = []
for feature in features:
    dummies = pd.get_dummies(df[feature], prefix=feature)
    one_hot_category.append(dummies)
# 拼接的具体方法是讲新的数据和原来的数据拼接,然而再扔掉旧的
df = pd.concat([df, *one_hot_category], axis=1)
df = df.drop(features, axis=1)

 

三. 建模和预测

这个看上去是最重要的部分,但是我反而不会讲,因为在sklearn里面调用实在是太简单了。至于用什么模型,其实baseline model用什么都无所谓,反正最后model ensemble的时候会集成各种各样的模型,所以具体怎么做还得看最后的model ensemble。

四. 绘制Learning Curve

Angrew NG在coursa上专门将到了我们的learning curve。他说learning curve是专门用来判断我们的模型是否收敛的。事实上如果我们sklearn给出的模型没有收敛,那么应该会给出warning。但是warning我都通过下面的语句屏蔽掉了:

import warnings
warnings.filterwarnings("ignore")

由于对于sklearn模型的训练我们都是不可见的,这也就意味着我们不能想训练神经网络那样定义一个列表,然后每一次训练的时候都讲metrics存到列表里面。还好sklearn给我们提供了一个learning_curve的包,用法如下所示:

# 盲猜这个train_size是训练次数,至于这个train_score和这个test_score难道是不同训练下的答案
train_sizes, train_scores, test_scores = learning_curve(
    estimator, X, y, cv=None, train_sizes=np.linspace(0.05, 1, 20)
)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)

# train_size是训练的样本数,fill_between还要额外接受两个参数(上限和下限)
# 注意fill_between要设置透明度, 不然就直接和learning_curve混在一起了
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,         
train_scores_mean + train_scores_std, alpha=0.1, color="b")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1, color="r")
plt.plot(train_sizes, train_scores_mean, "o-", color="b")
plt.plot(train_sizes, test_scores_mean, "o-", color="r")
plt.show()

这个图用了matplotlib中的fill_between方法,讲均值和标准差混在一起用,效果非常好。

 

三. 特征工程

特征工程可不是一个很简单的任务,当然作为Get Started的任务,我还是将他简单化处理了。

(1)Numerical转Categorical

这就是我们之前所提到的等宽分箱法。你还别说,pandas还真是强大,就连等宽分箱,他都帮我们准备好了。

train_df['AgeBand'] = pd.cut(train_df['Age'], 5)

这时候我们得到了五个等宽的范围,显然范围不太适合我们的模型进行直接的解析,所以我们将其转化为相应的Categorical特征,如下所示:

 df.loc[df["Age"] <= 16, "Age"] = 0
 df.loc[(df["Age"] > 16) & (df["Age"] <= 32), "Age"] = 1
 df.loc[(df["Age"] > 32) & (df["Age"] <= 48), "Age"] = 2
 df.loc[(df["Age"] > 48) & (df["Age"] <= 64), "Age"] = 3
 df.loc[df["Age"] > 64, "Age"] = 4

这里用到了一点numpy的技巧,就是中间的&,由于Series对象和数值比较返回的是一个boolean的array,这个array是无法用简单的逻辑和,或等操作符连接起来的。而这个&是经过overload过的,它展示了一个element-wise的product。利用这个新的array,我们就可以获得所有值为True的行了。

(2)字符串类型

Titanic有一个非常好的示例就是从字符串中提取信息。他要从姓名中提取一些称呼的前缀。关于字符串的处理我们当然是选用正则表达式,不过内置的str也有非常好的处理方法:

df['Title'] = df.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
df['Title'] = df['Title'].replace(['Lady', 'Countess', 'Capt', 'Col',
                                   'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
df['Title'] = df['Title'].replace('Mlle', 'Miss')
df['Title'] = df['Title'].replace('Ms', 'Miss')
df['Title'] = df['Title'].replace('Mme', 'Mrs')
df = df.drop("Name", axis=1)

这个只不过是特征工程的一角而已,具体的请看传送门

四. 模型集成

知乎上的讲解:传送门;kaggle上的讲解:传送门

真实的模型集成十分复杂,这个我之后也会补全。这里只是简单的使用sklearn里面的包做简单的模型集成。

还是以Titanic为例,这是一个分类问题,所以我们要选择对应的分类器进行集成,常见的分类器有:

  1. logistic regression
  2. support vector machine
  3. random forest classifier
  4. naive bayes classifier
  5. preceptron(感知机)
  6. KNN

然后我们就可以通过简单的调用来实现模型的集成了,这里选用的是普通的VotingClassifier来进行异质模型的集成

LR = LogisticRegression(solver="lbfgs", C=1.0, penalty="l2", tol=1e-6)
SVM = SVC(kernel="rbf", C=1)
RF = RandomForestClassifier(n_estimators=20, max_depth=5)
GNB = GaussianNB()
P = Perceptron()
KNN = KNeighborsClassifier(n_neighbors=3)
model = VotingClassifier([("lr", LR), ("svm", SVM), ("gnb", GNB), ("rf", RF), ("p", P), ("knn", KNN)])
model.fit(X, y)

还有一点要注意的是,不管是我们的特征工程还是模型集成,都不能确保模型得分的上升。我们还需要自己在cv上进行评估。

虽然Kaggle上有很多score都非常离谱。比如Regression问题中,MSE竟然会出现0,Classification中,Accuracy竟然会出现100%, 这些其实都是overfitting,解决方法就是找到原始的数据集,再另KNN中的k=1。虽然这种行为完全没有意义,但是我们的cv可不会骗我们。

print(cross_val_score(model, X, y))

只要我们的cv上出现了明显的提升,那我们的得分也会得到相应的提升(确信)。

其他cv检验的方法:传送门

五. 提交结果

提交结果就不用说了,首先讲我们的结果转换为csv,然后再submission->upload即可。

转化为csv的方法——还是通过pandas,一个简单的to_csv方法:

outputs = model.predict(test_set)
result = pd.DataFrame({"PassengerId": range(892, 1310), "Survived": outputs})
result.to_csv("./result.csv", index=False)
# Remind me if over
print("Done!")
发布了137 篇原创文章 · 获赞 19 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43338695/article/details/103791134