十二周,离大多数科目的结课考试,还有大概一个月的时间。中间我还得准备两次数模竞赛,英语六级。最近科研方面,柳老师还给我介绍了一个中科院的老师。看了他的一篇论文后,我感觉他对ML还是有比较深入的了解。我看看,能不能多接触一些项目吧。
星期六
今天几乎花了一天在机器学习上。
上午看了一篇论文
- 这篇论文使用RF对水样进行溯源分析
- 写了个阅读笔记
下午回寝室后,就开始学RF有关内容。
- 虽然之前有一点点了解,但是还没有实战
- 写了篇Random Forest的学习笔记,记录了:
- 集成算法入门
- 随机森林基本情况,特点
- 相关基础知识
- 随机森林中的特征重要性(在下面的实战中就体现了这个特点)
- sklean 中RF的实现,主要记录了一些超参数,可以在实例化时调节以完善模型
- 在学习过程中看到一段关于调参的描述,我摘录到了学习笔记的后面。
晚上在kaggle上做了篇实战,情况如下:
机器学习分类算法实战笔记
项目来自kaggle
主要是介绍了在泰坦尼克号数据集上创建机器学习模型的整个过程。包括数据预处理和正则化处理,可视化,创建新特征,多种分类模型搭建,模型性能评估。
机器学习分类算法实战笔记
参考资料
数据集
在这个notebook中,我将介绍在著名的泰坦尼克号数据集上创建机器学习模型的整个过程,该数据集被世界各地的许多人使用。
它提供了有关泰坦尼克号上乘客命运的信息,根据经济地位(等级)、性别、年龄和生存情况进行总结。
在这个挑战中,我们被要求预测泰坦尼克号上的乘客是否会幸存下来。
实战内容
1 | # linear algebra |
Getting the Data¶
1 | test_df = pd.read_csv("test.csv") |
数据探索/分析(Data Exploration/Analysis )
训练集有 891 个样本和 11 个特征 + 目标变量(幸存)。其中 2 个特征是浮点数,5 个是整数,5 个是对象。下面我列出了这些feature的简短描述:
survival: Survival
PassengerId: Unique Id of a passenger.
pclass: Ticket class
sex: Sex
Age: Age in years
sibsp: # of siblings / spouses aboard the Titanic
parch: # of parents / children aboard the Titanic
ticket: Ticket number
fare: Passenger fare
cabin: Cabin number
embarked: Port of Embarkation
1 | train_df.info() |
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 PassengerId 891 non-null int64
1 Survived 891 non-null int64
2 Pclass 891 non-null int64
3 Name 891 non-null object
4 Sex 891 non-null object
5 Age 714 non-null float64
6 SibSp 891 non-null int64
7 Parch 891 non-null int64
8 Ticket 891 non-null object
9 Fare 891 non-null float64
10 Cabin 204 non-null object
11 Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
1 | train_df.describe() |
PassengerId | Survived | Pclass | Age | SibSp | Parch | Fare | |
---|---|---|---|---|---|---|---|
count | 891.000000 | 891.000000 | 891.000000 | 714.000000 | 891.000000 | 891.000000 | 891.000000 |
mean | 446.000000 | 0.383838 | 2.308642 | 29.699118 | 0.523008 | 0.381594 | 32.204208 |
std | 257.353842 | 0.486592 | 0.836071 | 14.526497 | 1.102743 | 0.806057 | 49.693429 |
min | 1.000000 | 0.000000 | 1.000000 | 0.420000 | 0.000000 | 0.000000 | 0.000000 |
25% | 223.500000 | 0.000000 | 2.000000 | 20.125000 | 0.000000 | 0.000000 | 7.910400 |
50% | 446.000000 | 0.000000 | 3.000000 | 28.000000 | 0.000000 | 0.000000 | 14.454200 |
75% | 668.500000 | 1.000000 | 3.000000 | 38.000000 | 1.000000 | 0.000000 | 31.000000 |
max | 891.000000 | 1.000000 | 3.000000 | 80.000000 | 8.000000 | 6.000000 | 512.329200 |
上面我们可以看到,38%的训练集在泰坦尼克号上幸存下来。我们还可以看到,乘客的年龄从0.4岁到80岁不等。最重要的是,我们已经可以检测到一些包含缺失值的特征,例如“年龄”功能。
1 | train_df.head(15) |
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
5 | 6 | 0 | 3 | Moran, Mr. James | male | NaN | 0 | 0 | 330877 | 8.4583 | NaN | Q |
6 | 7 | 0 | 1 | McCarthy, Mr. Timothy J | male | 54.0 | 0 | 0 | 17463 | 51.8625 | E46 | S |
7 | 8 | 0 | 3 | Palsson, Master. Gosta Leonard | male | 2.0 | 3 | 1 | 349909 | 21.0750 | NaN | S |
8 | 9 | 1 | 3 | Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg) | female | 27.0 | 0 | 2 | 347742 | 11.1333 | NaN | S |
9 | 10 | 1 | 2 | Nasser, Mrs. Nicholas (Adele Achem) | female | 14.0 | 1 | 0 | 237736 | 30.0708 | NaN | C |
10 | 11 | 1 | 3 | Sandstrom, Miss. Marguerite Rut | female | 4.0 | 1 | 1 | PP 9549 | 16.7000 | G6 | S |
11 | 12 | 1 | 1 | Bonnell, Miss. Elizabeth | female | 58.0 | 0 | 0 | 113783 | 26.5500 | C103 | S |
12 | 13 | 0 | 3 | Saundercock, Mr. William Henry | male | 20.0 | 0 | 0 | A/5. 2151 | 8.0500 | NaN | S |
13 | 14 | 0 | 3 | Andersson, Mr. Anders Johan | male | 39.0 | 1 | 5 | 347082 | 31.2750 | NaN | S |
14 | 15 | 0 | 3 | Vestrom, Miss. Hulda Amanda Adolfina | female | 14.0 | 0 | 0 | 350406 | 7.8542 | NaN | S |
从上表中,我们可以注意到一些事情。首先,我们需要稍后将很多特征转换为数字特征,以便机器学习算法可以处理它们。
此外,我们可以看到这些特征具有不同的取值范围,我们需要将其转换为大致相同的比例。
我们还可以发现更多包含缺失值(NaN = 不是数字)的特征,我们需要处理这些特征。
1 | total = train_df.isnull().sum().sort_values(ascending= False) # ascendings是否按指定列的数组升序排列,默认为True,即升序排列 |
Total | % | |
---|---|---|
Cabin | 687 | 77.1 |
Age | 177 | 19.9 |
Embarked | 2 | 0.2 |
PassengerId | 0 | 0.0 |
Survived | 0 | 0.0 |
Pclass | 0 | 0.0 |
Name | 0 | 0.0 |
Sex | 0 | 0.0 |
SibSp | 0 | 0.0 |
Parch | 0 | 0.0 |
Ticket | 0 | 0.0 |
Fare | 0 | 0.0 |
Embarked只有 2 个缺失值,可以轻松填充。处理“Age”功能会更加棘手,该功能有 177 个缺失值。“Cabin”功能需要进一步研究,但看起来我们可能希望将其从数据集中删除,因为其中 77% 丢失了。
1 | train_df.columns.values |
array(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype=object)
在上面,可以看到 11 个特征 + 目标变量(幸存)。
哪些功能可以提高存活率?
对我来说,除了“乘客IDPassengerId”,“Ticket”和“Name”之外的所有内容,都应该与和高存活率相关联。
数据分析之 Age and Sex:
1 | survived = 'survived' |
你可以看到,男性在18岁到30岁之间生存的概率很高,这对女性来说也有点正确,但并不完全正确。对于女性来说,14至40岁的生存机会更高。
对于男性来说,5至18岁之间的生存概率非常低,但女性并非如此。另一件需要注意的事情是,婴儿的生存概率也更高一点。
由于似乎存在某些年龄,这增加了生存几率,并且因为我希望每个特征都大致相同,所以我稍后将创建年龄组。
Embarked, Pclass and Sex:
1 | FacetGrid = sns.FacetGrid(train_df, row='Embarked', size=4.5, aspect=1.6) |
登船点Embarked似乎与生存有关,具体取决于性别。
Q端和S端的妇女生存机会更高。
如果男性在端口C,他们的生存概率很高,但如果他们在端口Q或S,生存概率很低。
Pclass似乎也与生存有关。我们将在下面生成它的另一个图。
Pclass:
1 | sns.barplot(x='Pclass', y='Survived', data=train_df) |
<AxesSubplot:xlabel='Pclass', ylabel='Survived'>
在这里,我们清楚地看到,Pclass会对一个人的生存机会做出影响,特别是如果这个人属于1类。
我们将在下面创建另一个 pclass 图。
1 | grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6) |
上面的图证实了我们对pclass 1的假设,但我们也可以发现pclass 3中的人无法生存的概率很高。
SibSp and Parch
SibSp和Parch作为一个组合特征会更有意义,它显示了一个人在泰坦尼克号上的亲属总数。我将在下面创建它,如果有人不是alon,它还有一个特征。
1 | data = [train_df, test_df] |
1 | train_df['not_alone'].value_counts() |
1 537
0 354
Name: not_alone, dtype: int64
1 | axes = sns.factorplot('relatives','Survived', |
在这里,我们可以看到,您有 1 到 3 个亲戚的话生存的概率很高,
但如果您有少于 1 个或超过 3 个,则生存概率较低(除了某些有 6 个亲戚的情况)。
数据预处理
首先,我将从火车组中删除“PassengerId”,因为它对人的生存概率没有贡献。我不会将其从测试集中删除,因为提交时需要它
1 | train_df = train_df.drop(['PassengerId'], axis=1) |
1 | train_df.head() |
Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | relatives | not_alone | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S | 1 | 0 |
1 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C | 1 | 0 |
2 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S | 0 | 1 |
3 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S | 1 | 0 |
4 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S | 0 | 1 |
缺失值处理
cabin:
提醒,我们必须处理小屋(687),登船(2)和年龄(177)。
首先我想,我们必须删除“Cabin”变量,但后来我发现了一些有趣的东西。客舱编号看起来像“C123”,字母指的是甲板。
因此,我们将提取这些并创建一个包含人员甲板的新特征。Afterwords 我们将特征转换为数值变量。缺失值将转换为零。
在下图中,您可以看到泰坦尼克号的实际甲板,范围从A到G。
1 | import re |
1 | # we can now drop the cabin feature |
年龄缺失值:
现在我们可以解决年龄特征缺失值的问题。
我将创建一个包含随机数的数组,这些随机数是根据与标准差和is_null有关的平均年龄值计算的。
1 | data = [train_df, test_df] |
1 | train_df["Age"].isnull().sum() |
0
Embarked:
由于 Embarked 特征只有 2 个缺失值,因此我们将用最常见的缺失值填充这些缺失值。
1 | train_df['Embarked'].describe() |
count 889
unique 3
top S
freq 644
Name: Embarked, dtype: object
1 | common_value = 'S' |
转化特征
1 | train_df.info() |
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Survived 891 non-null int64
1 Pclass 891 non-null int64
2 Name 891 non-null object
3 Sex 891 non-null object
4 Age 891 non-null int32
5 SibSp 891 non-null int64
6 Parch 891 non-null int64
7 Ticket 891 non-null object
8 Fare 891 non-null float64
9 Embarked 891 non-null object
10 relatives 891 non-null int64
11 not_alone 891 non-null int32
12 Deck 891 non-null int32
dtypes: float64(1), int32(3), int64(5), object(4)
memory usage: 80.2+ KB
上面,你可以看到“票价”Fare是一个浮点数,我们必须处理4个分类特征:Name, Sex, Ticket and Embarked.。让我们一个接一个地调查和转换。
Fare:
使用“astype()”函数panda将“票价”从float转换为int64
1 | data = [train_df, test_df] |
姓名:
我们将使用“名称”函数从“名称”中提取“标题”,这样我们就可以从中构建一个新特征。
1 | data = [train_df, test_df] |
1 | train_df = train_df.drop(['Name'], axis=1) |
1 | test_df |
PassengerId | Pclass | Sex | Age | SibSp | Parch | Ticket | Fare | Embarked | relatives | not_alone | Deck | Title | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 892 | 3 | male | 22 | 0 | 0 | 330911 | 7 | Q | 0 | 1 | 8 | 1 |
1 | 893 | 3 | female | 38 | 1 | 0 | 363272 | 7 | S | 1 | 0 | 8 | 3 |
2 | 894 | 2 | male | 26 | 0 | 0 | 240276 | 9 | Q | 0 | 1 | 8 | 1 |
3 | 895 | 3 | male | 35 | 0 | 0 | 315154 | 8 | S | 0 | 1 | 8 | 1 |
4 | 896 | 3 | female | 35 | 1 | 1 | 3101298 | 12 | S | 2 | 0 | 8 | 3 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
413 | 1305 | 3 | male | 19 | 0 | 0 | A.5. 3236 | 8 | S | 0 | 1 | 8 | 1 |
414 | 1306 | 1 | female | 44 | 0 | 0 | PC 17758 | 108 | C | 0 | 1 | 3 | 5 |
415 | 1307 | 3 | male | 42 | 0 | 0 | SOTON/O.Q. 3101262 | 7 | S | 0 | 1 | 8 | 1 |
416 | 1308 | 3 | male | 34 | 0 | 0 | 359309 | 8 | S | 0 | 1 | 8 | 1 |
417 | 1309 | 3 | male | 18 | 1 | 1 | 2668 | 22 | C | 2 | 0 | 8 | 4 |
418 rows × 13 columns
性别sex:
将“性别”特征转换为数字。
1 | genders = {"male": 0, "female": 1} |
Ticket:
1 | train_df['Ticket'].describe() |
count 891
unique 681
top 347082
freq 7
Name: Ticket, dtype: object
由于Ticket属性具有681个唯一的Ticket,因此将它们转换为有用的类别有点棘手。所以我们将从数据集中删除它。
1 | train_df = train_df.drop(['Ticket'], axis=1) |
Embarked:
将“登船点”功能转换为数字。
1 | ports = {"S": 0, "C": 1, "Q": 2} |
Creating Categories:创建类别:
We will now create categories within the following features:
现在,我们将在以下功能中创建类别:
年龄:¶
现在我们需要转换“年龄”特征。首先我们将它从float转换为integer。然后,我们将创建新的“AgeGroup”变量,将每个年龄分类到一个组中。请注意,重要的是要关注您如何组成这些组,因为例如您不希望80%的数据属于组1。
1 | data = [train_df, test_df] |
1 | # let's see how it's distributed |
6 162
4 160
5 146
3 136
2 123
1 96
0 68
Name: Age, dtype: int64
票价:
对于“票价”特征,我们需要执行与“年龄”特征相同的操作。但这并不容易,因为如果我们将票价值的范围划分为几个同样大的类别,80%的票价值将属于第一类。幸运的是,我们可以使用sklearn“qcut()”函数,我们可以用它来查看如何形成类别。
1 | train_df.head(10) |
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | relatives | not_alone | Deck | Title | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 2 | 1 | 0 | 7 | 0 | 1 | 0 | 8 | 1 |
1 | 1 | 1 | 1 | 5 | 1 | 0 | 71 | 1 | 1 | 0 | 3 | 3 |
2 | 1 | 3 | 1 | 3 | 0 | 0 | 7 | 0 | 0 | 1 | 8 | 2 |
3 | 1 | 1 | 1 | 5 | 1 | 0 | 53 | 0 | 1 | 0 | 3 | 3 |
4 | 0 | 3 | 0 | 5 | 0 | 0 | 8 | 0 | 0 | 1 | 8 | 1 |
5 | 0 | 3 | 0 | 6 | 0 | 0 | 8 | 2 | 0 | 1 | 8 | 1 |
6 | 0 | 1 | 0 | 6 | 0 | 0 | 51 | 0 | 0 | 1 | 5 | 1 |
7 | 0 | 3 | 0 | 0 | 3 | 1 | 21 | 0 | 4 | 0 | 8 | 4 |
8 | 1 | 3 | 1 | 3 | 0 | 2 | 11 | 0 | 2 | 0 | 8 | 3 |
9 | 1 | 2 | 1 | 1 | 1 | 0 | 30 | 1 | 1 | 0 | 8 | 3 |
1 | data = [train_df, test_df] |
1 | train_df['Fare'].describe() |
count 891.000000
mean 1.523008
std 1.250743
min 0.000000
25% 0.000000
50% 1.000000
75% 2.000000
max 5.000000
Name: Fare, dtype: float64
Creating new Features
我将向数据集添加两个新特性,这些特性是我从其他特性中计算出来的。
Age times Class
1 | data = [train_df, test_df] |
Fare per Person
1 | for dataset in data: |
1 | # Let's take a last look at the training set, before we start training the models. |
Survived | Pclass | Sex | Age | SibSp | Parch | Fare | Embarked | relatives | not_alone | Deck | Title | Age_Class | Fare_Per_Person | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 0 | 2 | 1 | 0 | 0 | 0 | 1 | 0 | 8 | 1 | 6 | 0 |
1 | 1 | 1 | 1 | 5 | 1 | 0 | 3 | 1 | 1 | 0 | 3 | 3 | 5 | 1 |
2 | 1 | 3 | 1 | 3 | 0 | 0 | 0 | 0 | 0 | 1 | 8 | 2 | 9 | 0 |
3 | 1 | 1 | 1 | 5 | 1 | 0 | 3 | 0 | 1 | 0 | 3 | 3 | 5 | 1 |
4 | 0 | 3 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 1 | 8 | 1 | 15 | 1 |
5 | 0 | 3 | 0 | 6 | 0 | 0 | 1 | 2 | 0 | 1 | 8 | 1 | 18 | 1 |
6 | 0 | 1 | 0 | 6 | 0 | 0 | 3 | 0 | 0 | 1 | 5 | 1 | 6 | 3 |
7 | 0 | 3 | 0 | 0 | 3 | 1 | 2 | 0 | 4 | 0 | 8 | 4 | 0 | 0 |
8 | 1 | 3 | 1 | 3 | 0 | 2 | 1 | 0 | 2 | 0 | 8 | 3 | 9 | 0 |
9 | 1 | 2 | 1 | 1 | 1 | 0 | 2 | 1 | 1 | 0 | 8 | 3 | 2 | 1 |
10 | 1 | 3 | 1 | 0 | 1 | 1 | 2 | 0 | 2 | 0 | 7 | 2 | 0 | 0 |
11 | 1 | 1 | 1 | 6 | 0 | 0 | 2 | 0 | 0 | 1 | 3 | 2 | 6 | 2 |
12 | 0 | 3 | 0 | 2 | 0 | 0 | 1 | 0 | 0 | 1 | 8 | 1 | 6 | 1 |
13 | 0 | 3 | 0 | 5 | 1 | 5 | 2 | 0 | 6 | 0 | 8 | 1 | 15 | 0 |
14 | 0 | 3 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 8 | 2 | 3 | 0 |
15 | 1 | 2 | 1 | 6 | 0 | 0 | 2 | 0 | 0 | 1 | 8 | 3 | 12 | 2 |
16 | 0 | 3 | 0 | 0 | 4 | 1 | 2 | 2 | 5 | 0 | 8 | 4 | 0 | 0 |
17 | 1 | 2 | 0 | 2 | 0 | 0 | 1 | 0 | 0 | 1 | 8 | 1 | 4 | 1 |
18 | 0 | 3 | 1 | 4 | 1 | 0 | 2 | 0 | 1 | 0 | 8 | 3 | 12 | 1 |
19 | 1 | 3 | 1 | 5 | 0 | 0 | 0 | 1 | 0 | 1 | 8 | 3 | 15 | 0 |
构建机器学习模型 Building Machine Learning Models
1 | X_train = train_df.drop("Survived", axis=1) # 训练集的特征 |
随机梯度下降(SGD)学习
sklearn.linear_model.SGDClassifier
该估计器通过随机梯度下降(SGD)学习实现正则化线性模型:每次对每个样本估计损失的梯度,并以递减的强度(即学习率)沿此路径更新模型。SGD允许通过该方法进行小批量(在线/核心外)学习。为了使用默认学习率计划获得最佳结果,数据应具有零均值和单位方差。partial_fit
1 | # 随机梯度下降(SGD)学习 |
78.79 %
Random Forest
1 | # Random Forest |
92.48 %
Logistic Regression
1 | # Logistic Regression |
81.59 %
KNN
1 | # KNN |
85.41 %
Gaussian Naive Bayes
1 | # Gaussian Naive Bayes |
77.55 %
Perceptron 感知机
1 | # Perceptron |
78.23 %
Linear SVC
1 | # Linear SVC |
81.14 %
决策树
1 | # Decision Tree |
92.48 %
Which is the best Model ?
1 | results = pd.DataFrame({ |
Model | |
---|---|
Score | |
92.48 | Random Forest |
92.48 | Decision Tree |
85.41 | KNN |
81.59 | Logistic Regression |
81.14 | Support Vector Machines |
78.79 | Stochastic Gradient Decent |
78.23 | Perceptron |
77.55 | Naive Bayes |
正如我们所看到的,随机森林分类器排在第一位。但首先,让我们检查一下,当我们使用交叉验证时,RF的性能如何。
k折交叉验证
一般情况将K折交叉验证用于模型调优,找到使得模型泛化性能最优的超参值。,找到后,在全部训练集上重新训练模型,并使用独立测试集对模型性能做出最终评价。
K折交叉验证使用了无重复抽样技术的好处:每次迭代过程中每个样本点只有一次被划入训练集或测试集的机会
K-Fold交叉验证将训练数据随机分成K个子集,称为折叠。让我们想象一下,我们将数据分成4个折叠(K=4)。我们的随机森林模型将被训练和评估4次,每次使用不同的折叠进行评估,而它将在剩余的3个折叠上进行训练。
下面的代码使用10个折叠(K=10)对我们的随机森林模型执行K折叠交叉验证。因此,它输出一个具有10个不同分数的数组。
1 | from sklearn.model_selection import cross_val_score |
1 | print("Scores:", scores) |
Scores: [0.77777778 0.84269663 0.74157303 0.84269663 0.88764045 0.85393258
0.82022472 0.78651685 0.82022472 0.86516854]
Mean: 0.823845193508115
Standard Deviation: 0.04207611389315941
这看起来比以前更现实。我们的模型的平均精度为82%,标准偏差为4%。标准差告诉我们,估计值有多精确。
这意味着在我们的情况下,我们的模型的精度可能相差正负4%。
我认为准确度仍然很好,因为随机森林是一个易于使用的模型,我们将在下一节中进一步提高它的性能。
随机森林
什么是随机森林?
随机森林是一种有监督的学习算法。就像你已经从它的名字中看到的那样,它创建了一个森林,并使它变得随机。它构建的“森林”是决策树的集合,大部分时间都是通过“装袋”方法训练的。装袋方法的总体思想是,学习模型的组合提高了整体结果。
简单地说:随机森林构建多个决策树,并将它们合并在一起,以获得更准确和稳定的预测。
随机森林的一大优点是,它可以用于分类和回归问题,这是当前机器学习系统的主要组成部分。除了少数例外,随机森林分类器具有决策树分类器的所有超参数以及装袋分类器的所有超级参数,以控制集合本身。
随机森林算法在生长树木时为模型带来了额外的随机性。它不是在拆分节点时搜索最佳特征,而是在随机特征子集中搜索最佳特征。这一过程产生了广泛的多样性,通常会产生更好的模型。所以,当您在随机林中生长树时,只考虑一个随机的特征子集来分割节点。您甚至可以为每个特性使用随机阈值,而不是搜索可能的最佳阈值(就像普通决策树一样),从而使树更加随机。
功能重要性
随机森林的另一个很好的特点是,它们可以很容易地测量每个特征的相对重要性。Sklearn通过查看使用该特征的树节点平均减少了多少杂质(在森林中的所有树上)来衡量特征的重要性。它在训练后自动计算每个特征的得分,并对结果进行缩放,使所有重要度之和等于1。我们将在下面对此进行评估:
1 | importances = pd.DataFrame({'feature':X_train.columns, |
1 | importances.head(15) |
importance | |
---|---|
feature | |
Title | 0.213 |
Sex | 0.167 |
Age_Class | 0.094 |
Deck | 0.078 |
Age | 0.076 |
Fare | 0.071 |
Pclass | 0.070 |
relatives | 0.055 |
Embarked | 0.053 |
SibSp | 0.044 |
Fare_Per_Person | 0.043 |
Parch | 0.022 |
not_alone | 0.013 |
1 | importances.plot.bar() |
<AxesSubplot:xlabel='feature'>
结论:
not_alone和Parch在我们的随机森林分类器预测过程中没有发挥重要作用。因此,我将从数据集中删除它们,
并再次训练分类器。我们也可以删除或多或少的特征,
但这需要对特征对模型的影响进行更详细的调查。但我认为只删除only Alone 和Parch.就行了。
1 | train_df = train_df.drop("not_alone", axis=1) |
Training random forest again:
1 | # Random Forest |
92.48 %
我们的随机森林模型预测效果和以前一样好。一个普遍的规则是,你拥有的功能越多,你的模型就越可能受到过度拟合的影响,反之亦然。但我认为我们的数据目前看起来不错,没有太多功能。
还有另一种评估随机森林分类器的方法,它可能比我们以前使用的指标准确得多。
我所说的是用来估计泛化精度的现成样本(OOB)。我不会在这里详细说明它是如何工作的。
请注意,包外评估OOB 与 使用与训练集大小相同的测试集一样准确。因此,使用OOB误差估计值不需要备用测试集。
1 | print("oob score:", round(random_forest.oob_score_, 4)*100, "%") |
oob score: 82.04 %
超参数调整
现在我们可以开始调整随机森林的超参数了。
可以通过调整以下超参数来调整模型
- min_samples_leaf,
- min_samples_split
- n_estimators
1 | # Random Forest |
oob score: 83.61 %
更多评价方式
现在我们有了一个合适的模型,我们可以开始以更准确的方式评估它的性能。以前我们只使用acc准确度和oob评分,这只是另一种形式的准确度。
问题是,评估分类模型比评估回归模型更复杂。我们将在下一节中讨论这一点。
Confusion Matrix 混沌矩阵
1 | from sklearn.model_selection import cross_val_predict |
array([[490, 59],
[ 93, 249]], dtype=int64)
第一行是关于未幸存的预测:493名乘客被正确分类为未幸存(称为真阴性),56名乘客被错误分类为未存活(假阴性)。
第二行是关于幸存的预测:93名乘客被错误地分类为幸存(假阳性),249名乘客被正确地分类为存活(真阳性)。
混淆矩阵为您提供了很多关于模型性能的信息,但有一种方法可以获得更多信息,比如计算分类器的精度。
Precision and Recall:¶
Precision(精确率)、Recalll(召回率)、F1-score主要用于分类(二分类、多分类)模型,比如对话系统中的意图分类。
- 精确率是正确预测的阳性类别与预测为阳性的所有样本的比率
- 召回是正确预测的阳性类别与所有实际阳性样本的比率:
- F1-score同时考虑了精确度和召回率。通过取两个指标的调和平均值来计算;考虑到这种相互竞争的权衡,拥有一个同时考虑精度和召回率的单一性能指标将非常重要。
模型评测:PRECISION、RECALL、F1-score
1 | from sklearn.metrics import precision_score, recall_score |
Precision: 0.8084415584415584
Recall: 0.7280701754385965
Our model predicts 81% of the time, a passengers survival correctly (precision). The recall tells us that it predicted the survival of 73 % of the people who actually survived.
F-Score
You can combine precision and recall into one score, which is called the F-score. The F-score is computed with the harmonic mean of precision and recall. Note that it assigns much more weight to low values. As a result of that, the classifier will only get a high F-score, if both recall and precision are high.
1 | from sklearn.metrics import f1_score |
F-Score: 0.7661538461538462
我们得到了77%的F分数。分数没有那么高,因为我们的召回率为73%。
但不幸的是,F分数并不完美,因为它支持具有相似精度和召回率的分类器。这是一个问题,因为有时需要高精度,有时需要高召回率。问题是,精度的提高有时会导致召回率的降低,反之亦然(取决于阈值)。这称为精度/召回权衡。我们将在下一节对此进行讨论。
在精度或召回率较差的情况下,F1-score也会较差。只有当准确率和召回率都有很好的表现时,F1-score才会很高。
精度召回曲线 Precision Recall Curve
1 | from sklearn.metrics import precision_recall_curve |
1 | def plot_precision_and_recall(precision, recall, threshold): |
上面你可以清楚地看到,召回率正在快速下降,准确率约为85%。因此,您可能希望在此之前选择精度/召回权衡-可能在75%左右。
现在,您可以选择一个阈值,该阈值为您当前的机器学习问题提供了最佳的精度/召回权衡。例如,如果你想要80%的精度,你可以很容易地查看图表,发现你需要一个大约0.4的阈值。然后你可以用这个阈值训练一个模型,从而获得所需的精度。
另一种方法是绘制精度和召回率:
1 | def plot_precision_vs_recall(precision, recall): |
结束submission
1 | submission = pd.DataFrame({ |
总结
总结
这个项目大大加深了我的机器学习知识,我加强了我将从课本、博客和各种其他来源学到的概念应用于不同类型问题的能力。该项目重点关注数据准备部分,因为这是数据科学家大部分时间的工作。
我从数据探索开始,在那里我对数据集有了感觉,检查了缺失的数据,并了解了哪些特征是重要的。在此过程中,我使用seaborn和matplotlib进行可视化。在数据预处理部分,我计算了缺失的值,将特征转换为数字,将值分组为类别,并创建了一些新的特征。之后,我开始训练8个不同的机器学习模型,选择其中一个(随机森林),并对其进行交叉验证。然后我解释了随机森林是如何工作的,看看它赋予不同功能的重要性,并通过优化其超参数值来调整其性能。最后,我查看了它的混淆矩阵,并计算了模型的精度、召回率和f分数,然后将我在测试集上的预测提交给Kaggle排行榜。
当然,仍有改进的空间,比如通过比较和绘制特征,识别和删除有噪声的特征,进行更广泛的特征工程。另一个可以改善kaggle排行榜整体结果的方法是对多个机器学习模型进行更广泛的超参数调整。当然,你也可以做一些集体学习。
原总结
This project deepened my machine learning knowledge significantly and I strengthened my ability to apply concepts that I learned from textbooks, blogs and various other sources, on a different type of problem. This project had a heavy focus on the data preparation part, since this is what data scientists work on most of their time.
I started with the data exploration where I got a feeling for the dataset, checked about missing data and learned which features are important. During this process I used seaborn and matplotlib to do the visualizations. During the data preprocessing part, I computed missing values, converted features into numeric ones, grouped values into categories and created a few new features. Afterwards I started training 8 different machine learning models, picked one of them (random forest) and applied cross validation on it. Then I explained how random forest works, took a look at the importance it assigns to the different features and tuned it’s performace through optimizing it’s hyperparameter values. Lastly I took a look at it’s confusion matrix and computed the models precision, recall and f-score, before submitting my predictions on the test-set to the Kaggle leaderboard.
Of course there is still room for improvement, like doing a more extensive feature engineering, by comparing and plotting the features against each other and identifying and removing the noisy features. Another thing that can improve the overall result on the kaggle leaderboard would be a more extensive hyperparameter tuning on several machine learning models. Of course you could also do some ensemble learning.