记一次竞赛题目有感——根据姓名预测男女性别

比赛连接 sofasofa.io

根据姓名预测男女性别

今天尝试着做了一道机器学习题——根据姓名预测男女性别。这道题目其实算法就是二分类的题目,这类算法很多,比如knn近邻算法,决策树,svm向量机等等;传统的机器学习算法可以达到百分之七十到百分之八十五之间,我想能不能用深度学习来做一下看看能达到多高的accuracy率。

我们每天会接触不同的人,有热情的面孔,也有冰冷的名片。作为一个说着中国话的中国人,我们有着常年累月的积累和对文字的理解,通过名字判断(猜测)性别,并非难事。可是,对于一个冰冷的机器,它能够根据名字判断性别吗?这个练习赛就是根据中文名字(姓已经省略),判断性别

竞赛题目

竞赛题目数据截图

数据截图

其实这道题就是一个二分类,跟之前做的识别方圆的题目是一个类型的,只不过从图片转为了文本二分类而已。那么,现在有一个问题,这里给出的是中文名字数据,这样的数据是没办法直接进入模型的,因此还需要进行数据处理;如何将中文汉字转为深度学习模型可以处理的数据就是这道题的一个难点。在这里,我采用的将中文汉字转为Unicode编码后在转为二进制的文本向量的方式来处理。利用python的ord()函数和bin()函数将汉字转为二进制的编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
FILL_NAME = [[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1]]


def str2bin(user_name):
"""

:param user_name:
:return:
"""
return [map(int, bin(ord(i))[2:]) for i in user_name[0]]


def listExtend(List):
"""

:param List:
:return:
"""
a = []
if len(List) == 3:
List.extend(FILL_NAME)
[a.extend(i) for i in List]
else:
[a.extend(i) for i in List]
return a


def load_data_dense(fileName_train=FILE_TRAIN_PATH, fileName_test=FILE_TEST_PATH):
"""

:param fileName_train:
:param fileName_test:
:return:
"""
train, test = np.array(pd.read_csv(fileName_train).values), np.array(pd.read_csv(fileName_test).values)
train_name, test_name, train_sex, test_sex = train_test_split(train[:, 1:2], train[:, 2], test_size=0.2)
train_name, test_name = map(str2bin, train_name), map(str2bin, test_name)

def name2vec(names):
return np.array(map(listExtend, names))

train_name = name2vec(train_name);
test_name = name2vec(test_name)
print train_name, '\n', np.shape(train_name)
return train_name, train_sex, test_name, test_sex

转换结果

word2vec

既然我们已经将汉字转为了向量之后,那下一步就是构建一个深度学习模型用于二分类了。关于如何构建二分类的深度学习模型,在判断奇偶数的篇章中已经给出了。

简单的全连接深度学习模型

模型代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def build_model_dense(train_x):
"""

:param train_x:
:return:
"""
inputs = Input(shape=(train_x.shape[1],))
x_dense_1 = Dense(units=64, activation='relu', use_bias=True)(inputs)
x_dropout_1 = Dropout(rate=0.1)(x_dense_1)
x_dense_2 = Dense(units=256, activation='relu', use_bias=True)(x_dropout_1)
x_dropout_2 = Dropout(rate=0.1)(x_dense_2)
x_dense_3 = Dense(units=128, activation='relu', use_bias=True)(x_dropout_2)
predictions = Dense(units=1, activation='tanh')(x_dense_3)
model = Model(inputs=inputs, outputs=predictions)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
return model


def train_model(batch_size=32, verbose=2, validation_split=0.1, model=None, type=1):
"""

:param batch_size:
:param verbose:
:param validation_split:
:param model:
:param type:
:return:
"""
if model is None:
raise Exception("model is None")
train_name, train_sex, test_name, test_sex = ld.load_data(type)
history = model.fit(train_name, train_sex,
batch_size=batch_size,
validation_split=validation_split,
epochs=50,
verbose=verbose)
plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend()
plt.show()
score = model.evaluate(test_name, test_sex,
batch_size=batch_size)
print(score)

那么这个模型的结果如何呢?跑一跑就知道了

模型训练/测试的损失函数值变化情况

loss_data

模型二分类结果

binary_classify

可以看出,模型的二分类准确率为百分之78~79,将近百分之80,因此这个模型的accuracy还是可以接受的。但是,跟传统的机器学习相比,百分之78~79的正确率有点不尽人意。那么,如何提高深度学习的accuracy呢?

多输入单输出的深度学习模型(accuracy为百分之81.xx)

观察训练数据可以看出,名字要么是由两个字组成的,要么就是单个字;而两个字组成的名字,而每个字对应男女的可能性都有所不同,我们可以认为两个字共同影响最终性别的取向,因此可以将名字拆分,分别进行学习{字->性别}的映射,然后将映射组合起来,再进行两个字对性别综合影响的学习。

模型图

多输入单输出的深度学习模型

每一个字进入一个神经网络进行学习字与性别的关系,分别为a_name和b_name,通过神经网络学习到了a_name与性别的特征向量encode_1、b_name与性别的特征向量encode_2;然后通过keras.layers.concatenate将学习到的特征向量组合成一个新的特征向量,再进行一次神经网络的学习。

模型代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def build_model():
a_name = Input(shape=(24,))
a_dense_1 = Dense(units=48, activation='relu')(a_name)
a_dropout = Dropout(rate=0.2)(a_dense_1)
a_dense_2 = Dense(units=96, activation='relu')(a_dropout)
encode_1 = Dense(units=24, activation='relu')(a_dense_2)

b_name = Input(shape=(24,))
b_dense_1 = Dense(units=48, activation='relu')(b_name)
b_dropout = Dropout(rate=0.2)(b_dense_1)
b_dense_2 = Dense(units=96, activation='relu')(b_dropout)
encode_2 = Dense(units=24, activation='relu')(b_dense_2)

inputs = concatenate([encode_1, encode_2], axis=-1)
x_dense_1 = Dense(units=96, activation='relu', use_bias=True)(inputs)
x_dropout_1 = Dropout(rate=0.1)(x_dense_1)
x_dense_2 = Dense(units=192, activation='relu', use_bias=True)(x_dropout_1)
x_dropout_2 = Dropout(rate=0.1)(x_dense_2)
x_dense_3 = Dense(units=96, activation='relu', use_bias=True)(x_dropout_1)
predictions = Dense(units=1, activation='sigmoid', name='out_put')(x_dense_3)

model = Model(inputs=[a_name, b_name], outputs=predictions)
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
return model


def train_model(batch_size=64, verbose=2, validation_split=0.1, type='dense'):
"""

:param batch_size:
:param verbose:
:param validation_split:
:param type:
:return:
"""
train_name, train_sex, test_name, test_sex = ld.load_data(type=type)
model = build_model()
history = model.fit([train_name[:, 0: 24], train_name[:, 24:]], train_sex,
batch_size=batch_size,
validation_split=validation_split,
epochs=50,
verbose=verbose)

模型训练/测试的损失函数值的变化情况

模型训练/测试的损失函数值的变化情况

模型对测试集的预测情况

模型对测试集的预测情况

模型展示与训练(视频)

bilibili