使用Tensorflow做Kaggle入门题之泰坦尼克

本分享推荐使用PC阅读并自己跟随操作。全部完成大约需要一小时左右。因为代码较长文章全部贴出无意义,参考代码地址(https://gist.github.com/moshoujingli/384b8b4e624d4a846fc26c8e5076a9ca),网络定义写在函数defGraph中,特征定义写在getFeatureArr中。

前期准备

目标

  • 实践Tensorflow的API
    • 增加网络层
    • 定义交叉熵loss和l2 loss
    • 查看特定运算结果
    • 训练和预测
  • 实践机器学习的方法论
    • 过拟合
    • 欠拟合
    • 混淆矩阵

本文结构

  • 题目
    • 理解数据
    • 组织数据
  • 构建网络
  • 优化
  • 总结

题目

Titanic: Machine Learning from Disaster

Overview里面简要介绍了任务目的:根据给出的900名乘客的个人信息和幸存与否的数据,预测400个乘客幸存与否。

点击Data可以看到题目给出了三个文件。

train.csv文件

从第一列开始

  1. survival 幸存与否 0 = No, 1 = Yes
  2. pclass 代表社会经济状况,数字越小状况越好 1 = upper, 2 = mid, 3 = lower
  3. Name 全名
  4. sex 性别
  5. Age 年龄(如果是估计值将用以xx.5表示,如果这个数字很小表示不满一岁)
  6. sibsp 在船上的旁系亲属和配偶的数量
  7. parch 在船上的父母子女的数量
  8. ticket 票号
  9. fare 船费
  10. cabin 舱号
  11. embarked 登船港口 C = Cherbourg, Q = Queenstown, S = Southampton

test.csv文件中除了幸存与否的信息删除,后面每列前移,格式和train.csv文件一致。

gender_submission.csv给出了你生成的答案的格式,即一个两列的csv文件,首行分别为该文件的首行。之后每一行对应test.csv中的每一行数据中的id,以及你判定的生存与否。

理解数据

One of the reasons that the shipwreck led to such loss of life was that there were not enough lifeboats for the passengers and crew. Although there was some element of luck involved in surviving the sinking, some groups of people were more likely to survive than others, such as women, children, and the upper-class.

题目已经硬点了,性别,年龄,阶级(经济状况)是关键特征。

我们看一下在这三个特征和生存与否关系如何:

性别:

《使用Tensorflow做Kaggle入门题之泰坦尼克》
《使用Tensorflow做Kaggle入门题之泰坦尼克》

survival 幸存与否 0 = No, 1 = Yes

上面这个图分别代表训练数据中男性和女性,幸存和未幸存的四种组合分别占总体的比。

所以即使直接将所有女性判定为1,所有男性判定为0,仍然只会错掉左上和右下,共21.32%,所以我们的目标就是利用剩余的信息超越这个值。

然后对年龄以五年为一个区间,可以看到年龄小于10岁的比平均生存率高很多:

《使用Tensorflow做Kaggle入门题之泰坦尼克》
《使用Tensorflow做Kaggle入门题之泰坦尼克》 但是10岁以内的儿童,男:女 = 32:30,,女性占比远高于总体水平,因此性别可能存在影响。

而且同时无论是训练数据还是测试数据,年龄有缺失。如何尽可能合理的估计和补上这个年龄是个关键的问题。并且一个非常有意思的现象是,年龄缺失的人生还率更高一点。

阶级和生还率

《使用Tensorflow做Kaggle入门题之泰坦尼克》
《使用Tensorflow做Kaggle入门题之泰坦尼克》 上流阶级生还率高很多。

组织数据

因为tensorflow的输入需要是一个NumPy的Array,因此我们把每一行输入转换为数字,同时补全年龄信息。

此处有两个相对比较重要的点

  1. 非数值特征表达为数值特征
  2. 特征值的处理

关于非数值特征表达为数值特征,这一点上,一般我的做法是,如果该特征的各个值之间有明确的大小关系且逻辑上可以度量接近程度,那么该非数值特征直接在特征向量中占一个位置,特征值间的数值关系应该能反应其逻辑上的接近程度。

而如果某一项特征,其值之间没有逻辑上的接近程度,只是通过数值不同来表现该值在各个样本间该特征不同,可以考虑直接在特征向量中分给K个位置(K表示该特征的可能取值个数),然后把对应值的位置置1,其他置0

以本题为例:比如舱号首位字符,称谓这两个特征就属于第二种,因此实际上我开了8个位置给cabin首字符,然后把对应位置置1.称谓同理

    cabins = record.cabin.split(' ')
    if len(cabins)>0:
        firstChar = [cabin[0] for cabin in cabins if len(cabin)>0]
        for f in firstChar:
            cabinLead[tagListMap['cabinLead'].index(f)] = 1

其余列因都是简单数值类型,因此直接填充即可。

构建网络

我们首先用一个简单的二层网络,本质上就是个二维的logits

def defGraph(featureVecLen):
    X = tf.placeholder("float", [None, featureVecLen])
    Y = tf.placeholder("float", [None, 2])


    w_o = init_weights([featureVecLen,2], "w_o")
    b_o = init_weights([2], "b_o")

    py_x = tf.nn.softmax(tf.matmul(ho, w_o) + b_o)
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels =Y))
    train_op = tf.train.AdamOptimizer(1e-4).minimize(cost)
    predict_op = tf.argmax(py_x, 1)

    return predict_op, train_op, X, Y 

特征向量也只年龄,阶级,性别

用倒数200个样本做测试集,其他的每次随机抽取100个做训练,迭代2000次。

在测试集上的结果(Accuracy):0.69

在训练集上的结果(Accuracy):0.672566371681

感觉明显的欠拟合

优化

改进网络,增加一个隐层:

def defGraph(featureVecLen):
    X = tf.placeholder("float", [None, featureVecLen])
    Y = tf.placeholder("float", [None, 2])

    w_1 = init_weights([featureVecLen,64], "w_1")
    b_1 = init_weights([64], "b_1")

    h1 = tf.nn.relu(tf.matmul(X, w_1) + b_1)

    w_o = init_weights([64,2], "w_o")
    b_o = init_weights([2], "b_o")

    py_x = tf.nn.softmax(tf.matmul(h1, w_o) + b_o)
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels =Y))
    train_op = tf.train.AdamOptimizer(1e-4).minimize(cost)
    predict_op = tf.argmax(py_x, 1)

    return predict_op, train_op, X, Y 

在测试集上的结果(Accuracy):0.8

在训练集上的结果(Accuracy):0.793931731985

看起来好了很多,但是还不够好,只是刚刚达到了只靠性别判断的程度。

再加一个隐层:

在测试集上的结果(Accuracy):0.84

在训练集上的结果(Accuracy):0.793931731985

再加一个:

在测试集上的结果(Accuracy):0.82

在训练集上的结果(Accuracy):0.815423514539

感觉上靠现在这三个数据已经停滞不前了,而且绝对出现了过拟合

下面我们喂进更多的数据,把船费,随性人员都加入进来,并且把网络缩小回三层。

在测试集上的结果(Accuracy):0.86

在训练集上的结果(Accuracy):0.839443742099

额外数据倒是有一些效果。我们先用现在这个模型提交一次吧!

得分 0.75120

讲道理还不如直接年龄判断,而且看起来有过拟合了。

加入l2 loss,并且继续把其他数据添加进来。直觉上中间称谓(Mr Miss)一定程度上可以帮助我们修复年龄的缺失,最后票号,舱号等等可能也存在对易遇难区域的判断。

def defGraph(featureVecLen):
    X = tf.placeholder("float", [None, featureVecLen])
    Y = tf.placeholder("float", [None, 2])

    w_1 = init_weights([featureVecLen,256], "w_1")
    b_1 = init_weights([256], "b_1")

    h1 = tf.nn.relu(tf.matmul(X, w_1) + b_1)

    w_o = init_weights([256,2], "w_o")
    b_o = init_weights([2], "b_o")

    py_x = tf.nn.softmax(tf.matmul(h1, w_o) + b_o)
    vars   = tf.trainable_variables() 
    lossL2 = tf.add_n([ tf.nn.l2_loss(v) for v in vars
                    if 'b_' not in v.name ]) * 0.001
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels =Y)+lossL2)
    train_op = tf.train.AdamOptimizer(1e-4).minimize(cost)
    predict_op = tf.argmax(py_x, 1)

    return predict_op, train_op, X, Y , tf.reduce_mean(lossL2) , cost

提交一个0.775,倒是不错

效果比之前好了,但是可能还是存在过拟合。

现在没有比较直观的角度来进行提升了,因此我们输出一下在测试集上的混淆矩阵,以及输出现在的预测loss和l2 loss.

l2 loss:0.0138169

loss:0.51677

可见l2 loss占总体loss的比是很低的,应该适度提高一下。

《使用Tensorflow做Kaggle入门题之泰坦尼克》
《使用Tensorflow做Kaggle入门题之泰坦尼克》

混淆矩阵现在看不出啥来,错的挺平均的。bad case打出来也挺意外,不少被判活的年轻女性,但是实际是死了,还有一些下层中年大叔被判死但是实际上活下来了。试试提高一下l2 loss系数来一次,竟然提高了 0.78947。

把网络加到五层,提高l2 loss系数,提高训练次数

def defGraph(featureVecLen):
    X = tf.placeholder("float", [None, featureVecLen])
    Y = tf.placeholder("float", [None, 2])

    w_1 = init_weights([featureVecLen,512], "w_1")
    b_1 = init_weights([512], "b_1")

    w_2 = init_weights([512,256], "w_2")
    b_2 = init_weights([256], "b_2")  

    w_3 = init_weights([256,128], "w_3")
    b_3 = init_weights([128], "b_3")  

    w_4 = init_weights([128,16], "w_4")
    b_4 = init_weights([16], "b_4")  

    h1 = tf.nn.relu(tf.matmul(X, w_1) + b_1)
    h2 = tf.nn.relu(tf.matmul(h1, w_2) + b_2)
    h3 = tf.nn.relu(tf.matmul(h2, w_3) + b_3)
    ho = tf.nn.relu(tf.matmul(h3, w_4) + b_4)

    w_o = init_weights([16,2], "w_o")
    b_o = init_weights([2], "b_o")

    py_x = tf.nn.softmax(tf.matmul(ho, w_o) + b_o)
    vars   = tf.trainable_variables() 
    lossL2 = tf.add_n([ tf.nn.l2_loss(v) for v in vars
                    if 'b_' not in v.name ]) * 0.005
    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels =Y)+lossL2)
    train_op = tf.train.AdamOptimizer(2e-5).minimize(cost)
    predict_op = tf.argmax(py_x, 1)

    return predict_op, train_op, X, Y , tf.reduce_mean(lossL2) , cost

再来一次 0.77 惨不忍睹。。。

总结

对于少样本数据,测试集比训练集低的,要注意过拟合的问题。对于在训练集上都不过80%的,要考虑没训练好(以及欠拟合)的问题。

对于试着入坑DL的各位,调参愉快。不过说实话对于训练样本有限的题,传统ML方法效果更好。我看有用决策树的轻松拿到0.78469,比这个的最好成绩多错一个。

有更好结果的大神360度转体跪求务必留言教我!

本系列下期预告

Digit Recognizer | Kaggle

    原文作者:毕骁鹏
    原文地址: https://zhuanlan.zhihu.com/p/26139923
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞