此为中文翻译版
1:竞赛
我们将学习如何为Kaggle竞赛生成一个提交答案(submisson
)。Kaggle是一个你通过完成算法和全世界机器学习从业者进行竞赛的网站。如果你的算法精度是给出数据集中最高的,你将赢得比赛。Kaggle也是一个实践你机器学习技能的非常有趣的方式。
Kaggle网站有几种不同类型的比赛。其中的预测一个就是预测在泰坦尼克号沉没的时候哪个乘客会成为幸存者。 在这个任务和下一个任务我们将学习如何提交我们的答案。
我们的数据是csv
格式。你可以在这里下载数据开始比赛。
每一行重现了一个在泰坦尼克的乘客以及和他们有关的一些信息。让我们来看一下各列:
- PassengerId:一个用以标记每个乘客的数字id
- Survived:标记乘客是否幸存——幸存(1)、死亡(0)。我们将预测这一列。
- Pclass:标记乘客所属船层——第一层(1),第二层(2),第三层(3)。
- Name:乘客名字。
- Sex:乘客性别——男male、女female
- Age:乘客年龄。部分。
- SibSp:船上兄弟姐妹和配偶的数量。
- Parch:船上父母和孩子的数量。
- Ticket:乘客的船票号码。
- Fare:乘客为船票付了多少钱。
- Cabin:乘客住在哪个船舱。
- Embarked:乘客从哪个地方登上泰坦尼克号。
一个好的开始就是有条理的思考各个列和我们的预测之间的逻辑关系。
我们都知道妇女和儿童更可能被救。因此,年龄
和性别
很可能更好的帮助我们预测。认为乘客的船层可能会影响结果也是符合逻辑的,因为第一层的船舱更靠近船的甲板。Fare
票价和乘客所住船层相关,而且可能是高度相关的,但是也可能会增加一些额外的信息。SibSp、Parch
兄弟姐妹、配偶、父母/孩子的数量很可能关系到是否被某一个或很多个人救,会有很多人去帮助你或者有很多人想到你尝试去救你。
像Embarked
登船(也许有一些信息和怎么靠近船的顶部的人的船舱有关),Ticket
票号和Name
名字。
这一步通常是习得相关的领域知识[要对业务比较深入],这对于绝大多数机器学习任务来说非常非常非常的重要。
我们非常细致的处理特征以便我们能从我们现有的数据中获得最有用的信息来帮助我们进行预测。
2:观察数据
我们将使用Python3,pandas库和scikit-learn库来分析我们的数据并且生成一个提交答案submisson
。我们将使用代码框进行交互式编程,就和你看到的下面那个一样。如果你对Python还不熟悉,你可能想看一看我们的课程。
一个好的第二步就是仔细观察数据中的高级描述。在这种情况下,我们使用pandas的.describe()
方法来查看每一列数值的特性的不同之处。
# We can use the pandas library in python to read in the csv file.
# This creates a pandas dataframe and assigns it to the titanic variable.
titanic = pandas.read_csv("titanic_train.csv")
# Print the first 5 rows of the dataframe.
print(titanic.describe())
3:缺失数据
在上一节你使用.describe()
方法查看titanic
数据框的时候,你可能注意到年龄列只有714个计数,而其他列都有891个计数。这表明在年龄列有一些缺失值——空值(null,NA,非数字)。
这就意味着数据并不是完全的干净,我们必须自己清洗数据。我们不希望不得不删除有缺失数据的行,因为更多的数据能帮助我们训练出一个更好的算法。当然,我们也不想删除整列,因为age年龄很可能对我们的分析非常重要。
有很多种策略来处理缺失数据,但是一种简单的方法就是将那一列的缺失值用中位数填充。
我们可以像字典一样通过数据框索引选取一个单列。这样会给我们一个pandas Series(序列):
titanic['Age']
我们可以使用.fillna
方法替换序列中的任何缺失值。.fillna
可以传入一个参数用参数的值来替换缺失值。
在我们的例子中,我们打算用列的中位数来填充:
titanic['Age'].fillna(titanic['Age'].median())
之后我们必须将替换后的结果赋值回原来的列:
titanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())
4:非数值列
前两节我们使用过.describe()
,你也许也主要到并不是所有的列都显示出来了。只有数值列显示了。我们的列中有几个是非数值的,当你开始做预测的时候这将是一个问题——我们不能将非数值列传入机器学习算法中并且期望机器学习算法能理解他们。
我们在训练我们的算法时要么必须排除这些非数值列(Name
, Sex
, Cabin
, Embarked
, Ticket
),要么就要找到一种方法将他们转换成数值列。
我们将忽略Ticket
, Cabin
,Name
这三行。因为我们不能从他们中提取出太多有用的信息。Cabin
列大多数值都是缺失的(891行中只有204行数值),而且起初看起来好像也不是特别有用的列。如果没有一些关于船票号码意义的专业领域知识和哪些名字的特点像大户或者有钱家庭,那么Ticket
和Name
列也不太可能会告诉我们太多的信息。
5:转换性别Sex
列
性别列是非数值的,但是我们想把它保留——他可能非常有用。我们将每一个性别用数值码替换将其转换成一个数值列。之后机器学习算法能够使用这些类别去做预测。
为了完成这个目的,我们首先要找出列中所有的唯一性别(我们知道有male男和female女,但是有没有可能谁在数据集用的是其他缺失值代码呢?)。我们将0赋值给male,将1赋值给female。
我们可以选择Sex
列的所有值为male的行并且用0替换male:
titanic.loc[titanic['Sex']=='male','Sex'] = 0
titanic.loc[titanic['Sex']=='female','Sex'] = 1
# 有一个问题就是我自己测试的时候,describe()方法还是不显示性别。
6:转换登船Embarked
列
我们现在可以用和转换Sex
列相同的方法将Embarked
列转换成数值码。Embarked
列的唯一值是S,C,Q
和缺失值nan
。每个字母是一个登船港口名字的缩写。
第一步是替换列中的缺失值。大部分共同的登船港口都是S
,所以我们假设每个人都是从那儿上船的。将Embarked
列的缺失值全都替换成S
。
我们分别将0
赋值给S
,将1
赋值给C
,将2
赋值给Q
,替换每个值相应的代码:
# Find all the unique values for "Embarked".
print(titanic["Embarked"].unique())
titanic['Embarked'] = titanic['Embarked'].fillna('S')
titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2
7:交给机器学习
现在我们的数据已经清洗得干净了一点了,我们准备开始探索一些机器学习。我们开始的一些行数据看起来像这样:
Age | Sex | Survived |
---|---|---|
10 | 0 | 0 |
5 | 1 | 1 |
30 | 0 | 0 |
如果我们想利用Age
列的数据来预测是否有人幸存或者没有幸存,我们可以使用一种叫做线性回归的方法。线性回归的等式如下:(y=mx+b),y
的值是我们设法预测的,m
是一个叫做斜率的系数,x
是一列的值,b
是一个叫做截距的常数。
我们可以通过赋值-2
给m
,赋值20
给b
做一个预测。我们得到这样的结果:
Age | Sex | Survived | Predictions |
---|---|---|---|
10 | 0 | 0 | -2*10+20=0 |
5 | 1 | 1 | -2*5+20=10 |
30 | 0 | 0 | -2*30+20=-40 |
如果我们将得到的预测结果中任何大于0的值转换成1,任何小于等于0的值换成0,我们将得到这样的结果:
Age | Sex | Survived | Predictions |
---|---|---|---|
10 | 0 | 0 | 0 |
5 | 1 | 1 | 1 |
30 | 0 | 0 | 0 |
这个简单的模型预测幸存者相当好。线性模型可以是一个非常强大的算法,但是也有一些缺点:
- 如果一个列和一个结果不是线性相关,这就不会有好的效果。例如:如果除了80岁以上的老年女性也不幸存,那么线性回归将不会有好效果。
- 它不能给你幸存率,只有具体的数值来表明某人是否幸存。
我们之后会讨论如何处理这两个问题。现在,我们将学习如何自动的计算线性回归的回归系数,如何用多列数据来预测一个结果。
8:交叉验证
我们现在使用线性回归在我们的训练数据集上做预测。
我们想用除我们做预测之外的不同的数据来训练算法。如果我们想避免过拟合这是非常关键的。过拟合是当一个模型拟合它本身过于复杂,不显著。每个数据都有其独有的特点且不存在于全集。例如:如果我要求你利用一辆车的马力和其他特征,并且给你一个随机的非常高的最高速度数据集,来预测一辆车的最高速度,你将会建立一个速度过高的模型。解决的办法就是用你没有用来训练模型的数据去测试性能。
每一个机器学习算法都可以过拟合,即使有的(像线性回归)非常不容易。如果你测试(评估)你的算法和你训练算法是用的同一个数据集,那么你很可能不知道它的性能好到底是算法很好还是因为过拟合它自己的噪声。
幸运的是,交叉验证是一个很简单的避免过拟合的方法。交叉验证,你将你的数据分成一些数字部分(或子类)。我们使用3
作为一个例子。然后你这样做:
- 合并第一二部分,训练一个模型,在第三部分上做预测。
- 合并第一三部分,训练一个模型,在第二部分上做预测。
- 合并第二三部分,训练一个模型,在第一部分上做预测。
这种方式,评价我们生成的预测的精度的整个数据集和曾经训练我们的模型没有相同的数据。
9: 预测
我们可以使用极好的scikit-learn
库来做预测。我们将使用skelearn的一个助手来将数据分成交叉验证的子类,然后用每一个子类分别来训练算法做预测。最后,我们将得到一个预测列表,每一个列表项包含了相关子类的预测数据。
# Import the linear regression class
from sklearn.linear_model import LinearRegression
# Sklearn also has a helper that makes it easy to do cross validation
from sklearn.cross_validation import KFold
# The columns we'll use to predict the target
predictors = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "Embarked"]
# Initialize our algorithm class
alg = LinearRegression()
# Generate cross validation folds for the titanic dataset. It return the row indices corresponding to train and test.
# We set random_state to ensure we get the same splits every time we run this.
kf = KFold(titanic.shape[0], n_folds=3, random_state=1)
predictions = []
for train, test in kf:
# The predictors we're using the train the algorithm. Note how we only take the rows in the train folds.
train_predictors = (titanic[predictors].iloc[train,:])
# The target we're using to train the algorithm.
train_target = titanic["Survived"].iloc[train]
# Training the algorithm using the predictors and target.
alg.fit(train_predictors, train_target)
# We can now make predictions on the test fold
test_predictions = alg.predict(titanic[predictors].iloc[test,:])
predictions.append(test_predictions)
10:测试(评价)误差
现在我们有了预测结果,我们可以测试我们的误差了。
第一步我们需要先定义误差的度量标准,所以我们先算出我们模型的精度。从Kaggle竞赛的描述,误差的度量标准是正确预测的百分比。我们将使用这个相同的度量标准来测试我们本地模型的性能。
这个度量标准将基本上是predictions
中找到的值和他们在titanic['Survived']
的副本中准确对应的值的数量然后再除以乘客的总数。
在我们这么做之前,我们需要先将三个预测数据集合并到一个列中。因为每一个预测数据集是一个numpy(python科学计算库[注:真正的科学计算库应该是scipy,而numpy主要是矩阵数组等数据处理运算])数组,我们可以使用一个numpy方法将他们连接到一个列里。
算出predictions
预测值中和titanic["Survived"]
副本中准确相同的值的比例。这个计算结过应该是一个浮点数(小数)并将它赋值给变量accuracy
。
import numpy as np
# The predictions are in three separate numpy arrays. Concatenate them into one.
# We concatenate them on axis 0, as they only have one axis.
predictions = np.concatenate(predictions, axis=0)
# Map predictions to outcomes (only possible outcomes are 1 and 0)
predictions[predictions > .5] = 1
predictions[predictions <=.5] = 0
accuracy = sum(predictions[predictions == titanic['Survived']])/(titanic['Survived'].count())
11:逻辑回归
我们有了我们的第一个预测结果!可是结果并不是很好,只有78.3%的正确率。在视频中,我们曾提到一种方式使线性回归的输出值介于0
和1
之间。这种方法叫做逻辑回归。
一个好的方法就是将逻辑回归当成是线性回归的逻辑输出,所以他的值就是0
和1
。用逻辑函数 logit function来完成。输入任何值到逻辑函数都将通过“压缩”极值匹配成0
和1
。这对我们来说非常完美,因为我们只关心两种输出结果。
Sklearn
有一个逻辑回归的类我们可以使用。通过使用一个Sklearn
助手函数可以使我们所有的交叉验证和测试变得更简单。
12:处理测试集
我们的正确度已经可以了,但是还不是非常好。我们任然可以尝试一些方法来使它变得更好,在下一个任务将会讨论。
但是我们需要提交一个答案到比赛中。我们需要在测试数据上采取一个和训练数据上一样的校准的步骤来完成这个事情。如果我们没有校准操作,我们就不能够利用它做出有效的预测。
这些操作要在我们对列所有的改变之前进行。
处理titanic_test
和处理titanic
用同样的方法。
这涉及到:
- 使用训练数据集中的年龄中位数填充“Age”列的的所有缺失值。训练数据集的年龄已经使用完全相同的值替换了缺失年龄(因为值已经不同了,所以这不能作为测试集的中位数)。你应该用
titanic['Age'].median()
去找到中位数。 - 将
Sex
列中的所有male
换成0
,所有female
换成1
。 - 用
S
填充Embarked
列中的所有缺失值。 - 将
Embarked
列中的S
换成0
,C
换成1
,Q
换成2
。
我们也需要将Fare
列的缺失值替换掉。用.fillna
将测试集中的缺失值用中位数替换掉。训练集中的Fare
列没有缺失值了,但是测试集有时会不同。
titanic_test = pandas.read_csv("titanic_test.csv")
titanic_test['Age'] = titanic_test['Age'].fillna(titanic_test['Age'].median())
titanic_test['Fare'] = titanic_test['Fare'].fillna(titanic_test['Fare'].median())
titanic_test.loc[titanic_test['Sex'] == 'male','Sex'] = 0
titanic_test.loc[titanic_test['Sex'] == 'female','Sex'] = 1
titanic_test['Embarked'] = titanic_test['Embarked'].fillna('S')
titanic_test.loc[titanic_test['Embarked'] == 'S', 'Embarked'] = 0
titanic_test.loc[titanic_test['Embarked'] == 'C', 'Embarked'] = 1
titanic_test.loc[titanic_test['Embarked'] == 'Q', 'Embarked'] = 2
13:生成一个提交文件
现在我们有了我们需要生成一个比赛提交答案的所有东西了。
首先,我们必须先在训练数据上训练一个算法。然后我们在测试数据集上做一个预测。最后,我们生成一个包含预测和乘客id的csv文件。
一旦你完成了下面步骤的代码,你就能用submission.to_csv('kaggle.csv, index=False')
输出一个csv结果。这将会给你的第一个条件所有需要的东西——虽然这没有给你很好的准确率。(~0.75)
# Initialize the algorithm class
alg = LogisticRegression(random_state=1)
# Train the algorithm using all the training data
alg.fit(titanic[predictors], titanic["Survived"])
# Make predictions using the test set.
predictions = alg.predict(titanic_test[predictors])
# Create a new dataframe with only the columns Kaggle wants from the dataset.
submission = pandas.DataFrame({
"PassengerId": titanic_test["PassengerId"],
"Survived": predictions
})
14:下一步
我们只是生成了一个调教文件,但是准确率不是很好(0.75)。由于我们的预测在不同的数据集上导致在测试集上的分数低于我们在交叉验证上的分数。在下一个任务中我们将学习如何生成更好的特征和使用更好的模型来提高我们的分数。
恭喜你完成了这个任务!