泰坦尼克号是英国白星航运公司下的一艘奥林匹克级邮轮,号称永不沉没,然而在1912年4月14日却与冰山相撞,沉没水中。2224 名船员及乘客中,有 1517 人丧生,是一次伤亡惨重的海难。

而泰坦尼克号数据分析比赛是 Kaggle 上的一个比赛,通过训练集和测试机来预测乘客的生还情况。

环境

  • Python 3.6
  • WSL ubuntu 18.04
  • VS Code
  • Tensorflow 1 / Tensorflow 2

读取数据

首要目的是将数据读取到内存中,pandas 库可以方便的对数据进行读取。

import pandas as pd
# /mnt/e/python-learn/data/titanic/train.csv
titanic_test_data = pd.read_csv(r'/mnt/e/python-learn/data/titanic/train.csv')

数据清洗

数据清洗在数据分析中占有很重要的一部分。通过下面代码我们可以知道读入数据的数据结构。

titanic_test_data.info()

当执行该代码的时候,会打印出下面的结构。

<class ‘pandas.core.frame.DataFrame’>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB

通过观察可以发现,部分数据是丢失的,比如说 AgeCabinEmbarked

这里可以看出所有的属性字段,但是有一些字段对于我们的研究是无关的。

PassgengerId 该属性对于乘客是否被救是无关的。

Name,姓名可以说对我们的研究是不太相关的,但是也不能说完全不相关,比如说我们可以从姓名中找到该乘客的家族等。这里我们可以选择把他剔除掉。

Ticket 该字段每个值都是不同的,不会对预测结果产生影响。

Cabin 值缺失太多,我们无法直接去使用

所以我们先把这些无用值进行剔除。

titanic_test_data = titanic_test_data[['Survived', 'Pclass',
                                       'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']]

下面就要对这些缺失值进行补全。对缺失值进行补全的方法有很多:

  1. 忽略元组

  2. 人工填写缺失值

  3. 使用一个全局常量填充缺失值

  4. 使用属性的中心度量填充缺失值

  5. 使用与给定元组属同一类的所有样本的属性均值或者中位数

  6. 使用最有可能的值填充缺失值

这里我采用了使用第四点,使用属性的中心度量填充缺失值。

使用平均值对数据进行填充。

# Age
age_median = titanic_train_data['Age'].mean()
titanic_train_data['Age'] = titanic_train_data['Age'].fillna(age_median)

数据归一化

可以看出来,有些属性的值是字符串类型,这些类型对于计算机来说是操作不利的,要将字符串类型改为数值类型。

Sex 这一栏中,给定的值为 male 和 female 两个值,我们可以将它们转化为 0 与 1。

titanic_train_data['Sex'] = [ 1 if x == 'male' else 0 for x in titanic_train_data.Sex]

还有 embarked 也可以进行调整,将字符 C,S,Q 进行转换。

titanic_train_data['Embarked'][titanic_train_data['Embarked'] == 'S'] = 1
titanic_train_data['Embarked'][titanic_train_data['Embarked'] == 'C'] = 2
titanic_train_data['Embarked'][titanic_train_data['Embarked'] == 'Q'] = 3

最后将 label 数据分离出来。

y_data = titanic_train_data
titanic_train_data = titanic_train_data[['Pclass','Sex', 'Age', 'SibSp','Parch', 'Fare', 'Embarked']]
y_label = y_data[['Survived']]

可以用上述方法处理测试集数据。

对 Embarked 进行补全,我们采取出现频率最高的进行补全。

embarked_value = titanic_train_data['Embarked'].value_counts().index[0]
titanic_train_data['Embarked'] = titanic_train_data['Embarked'].fillna(
    embarked_value)

训练模型

创建 Tensor

创建 Tensor。采用线性回归,$ y=\omega x^{\tau } + b $ 。

在数据处理的时候,只留下了7列数据来供我们使用,而最后的结果值只有一个。所以我们对数据进行定义的时候要明白我们的定义的形状。

y = tf.placeholder(name='y', shape=[None, 1], dtype=tf.float32)
x = tf.placeholder(name='x', shape=[None, 7], dtype=tf.float32)
w = tf.Variable(tf.random_uniform([1, 7]), name='w', dtype=tf.float32)
b = tf.Variable(tf.random_uniform([1]), name='b')

Tensor 定义完成后,需要进行对式子进行表达。

z = tf.matmul(w, tf.transpose(x)) + b

损失函数

损失函数是对模型评估的重要方法,这里采用交叉熵进行评估。

loss = tf.nn.sigmoid_cross_entropy_with_logits(
    labels=y_label_data_array_shape , logits=z)
loss = tf.reduce_mean(loss)

优化器

通过优化器来对函数进行求解。这里采用 AdagradOptimizer 算法进行计算,学习率初值为 0.5 。

optimizer = tf.train.AdagradOptimizer(0.5).minimize(loss=loss)

进行训练

我选择了 epoch 为 10000,进行 10000 轮的迭代。同时要对 y_label 进行一下转换,保证形状的正确性。

epoch = 10000
y_label_data_array_shape = tf.reshape(y_label, [1, 891])
y_label_data_array_shape = tf.to_float(y_label_data_array_shape)

每次都将所有的值进行投喂,进行结果训练,并没100次将值进行打印输出,查看当前计算的损失值。

最后将输出的值进行划分为 0 和 1 .

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    for i in range(epoch):
        feed = {x: titanic_train_data, y: y_label}
        sess.run(optimizer, feed_dict=feed)

        if i % 100 == 0:
            loss_value = sess.run(
                [loss], feed_dict=feed)
            print(i//100 + 1,   ' loss =>', loss_value)

    pred = sess.run(tf.nn.sigmoid(z) , feed_dict={x: titanic_test_data})
    
    sur = [1 if x > 0.5 else 0 for x in  pred.flatten()]
    submission = pd.DataFrame({
        'PassengerId': original_test_data['PassengerId'],
        'Survived':  sur
    })

    submission.to_csv('titanic-submission.csv', index=False)

这里通过测试集的正确率是 0.78 。

算法还在不断完善。

后记

在国庆期间, Google 发布了 Tensorflow 2。相较于 tf1, tf2 做了很多更改,更加的推荐使用 keras 来进行模型的搭建,也更改了很多 API,比如说不再需要 PlaceHolder,运行不需要再进行 Session 管理,这些更改都极大的我们使用和上手的难度,使 tf 更加易用。

下面贴出 tf2 的兼容代码

tf.compat.v1.disable_eager_execution()
y = tf.compat.v1.placeholder(name='y', shape=[None,1], dtype=tf.float32)
x = tf.compat.v1.placeholder(name='x', shape=[None, 7], dtype=tf.float32)
w = tf.Variable(tf.random.normal([1, 7]), name='w', dtype=tf.float32)
b = tf.Variable(tf.random.normal([1]), name='b')
y_label_tensor = tf.convert_to_tensor(y_label,dtype=tf.float32,dtype_hint=True)
y_label_data_array_shape = tf.reshape(y_label_tensor, [1, 891])

y_label_data_array_shape = tf.cast(y_label_data_array_shape,dtype=tf.float32)
#  z 为预测值
z = tf.matmul(w, tf.transpose(x)) + b

# sigmoid 
# 交叉熵
loss = tf.nn.sigmoid_cross_entropy_with_logits(
    labels=y_label_data_array_shape , logits=z)
loss = tf.reduce_mean(loss)

optimizer = tf.compat.v1.train.AdagradOptimizer(0.1).minimize(loss=loss)

epoch = 10000
loss_value_in_plt = []

with tf.compat.v1.Session() as sess:
    sess.run(tf.compat.v1.global_variables_initializer())
    feed = {x: titanic_train_data, y: y_label}
    for i in range(epoch):
   
        sess.run(optimizer, feed_dict=feed)

        if i % 100 == 0:
            loss_value = sess.run(
                [loss], feed_dict=feed)
            loss_value_in_plt.append(loss_value)

            print(i//100 + 1,   ' loss =>', loss_value)

    pred = sess.run(tf.nn.sigmoid(z) , feed_dict={x: titanic_test_data})
    
    sur = [1 if x > 0.5 else 0 for x in  pred.flatten()]
    submission = pd.DataFrame({
        'PassengerId': original_test_data['PassengerId'],
        'Survived':  sur
    })

    submission.to_csv('titanic-submission.csv', index=False)

print("训练完毕")

然而,tf2 更加推荐使用 keras。下面是采用 Keras 进行的训练代码。

x = titanic_train_data
y = y_label
model = tf.keras.Sequential()

model.add(tf.keras.layers.Dense(1,input_shape=(7,),activation='sigmoid'))

model.compile(optimizer='adam',loss='binary_crossentropy',metrics=['acc'])

history = model.fit(x,y,epochs=epoch,batch_size=50,verbose=0)

l = model.predict(titanic_test_data)

survived = [1 if x > 0.5 else 0 for x in l]

submission = pd.DataFrame({
    'PassengerId': original_test_data['PassengerId'],
    'Survived':  survived
})

submission.to_csv('titanic-submission.csv', index=False)