Python 和 R 语言中如何根据姓名预测性别?

之前给大家分享过上市公司高管简历数据:

历年上市公司高管、董事和监事简历、持股情况、任职情况变动数据(2025年8月初爬取):https://rstata.duanshu.com/#/brief/course/b75a97992e6e4e63a50ef8c79d3a47b7

不过这个数据里面缺少高管的性别变量。这个该如何解决呢?

Github 上倒是有个项目也是为了解决这个问题:https://github.com/AlphaINF/name2gender 不过运行了下作者的代码得到的结果并不符合预期。不过幸运的是作者也提供了训练模型使用的数据,也就是附件中的 ccnc.csv 文件。

该数据文件包含了 365 万人的姓名和性别:

姓,名,姓名,性别
陈,品如,陈品如,男
陈,祥旭,陈祥旭,男
陈,晓,陈晓,男
陈,东慧,陈东慧,男
陈,镇彬,陈镇彬,男
陈,锐句,陈锐句,男
陈,晨,陈晨,男
陈,锐,陈锐,男
陈,品希,陈品希,男
陈,韦明,陈韦明,男
......

于是我们可以尝试使用这个数据来训练一个自己的模型来根据姓名预测性别。

虽然我也不是很熟悉 Python,不过这部分代码可以让豆包来写:

现有一个数据集,包含 姓,名,全名,性别 四个变量,如何训练一个模型根据中文姓名判断性别。使用 CNN 模型,给出完整的代码。

其中 CNN 模型指的是卷积神经网络模型,该模型可以用卷积层提取名字中局部字的组合特征(如 2-gram 字的性别倾向);

使用 R 语言的小伙伴不必浏览 Python 部分,直接从 R 语言部分阅读即可。

Python 部分

根据豆包的回答最终我整理了下面的代码:

首先使用 conda 搭建虚拟环境 genderpy_new:

conda create -n genderpy_new python=3.10
conda activate genderpy_new
pip install -r requirements.txt

模型训练和保存代码(附件中的 create_model.py 文件):

这部分代码并不需要运行,附件中已经有保存好的 gender_prediction_model.h5、name_tokenizer.pkl 和 max_length.pkl 文件:

import pandas as pd
import numpy as np
import re
from collections import defaultdict
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.callbacks import EarlyStopping

# ----------------------
# 1. 数据加载与预处理
# ----------------------
# 加载数据(假设CSV文件有"姓名"和"性别"列,性别值为"男"/"女")
data = pd.read_csv("dataset/ccnc.csv") # 替换为你的数据集路径
print(f"原始数据量:{len(data)}")

# 数据清洗
def clean_name(name):
"""清理姓名:保留中文字符,去除特殊符号和数字"""
name = str(name).strip()
return re.sub(r'[^\u4e00-\u9fa5]', '', name) # 仅保留中文

# 应用清洗
data["姓名"] = data["姓名"].apply(clean_name)
# 过滤空值和过短姓名(至少2个字)
data = data[data["姓名"].str.len() >= 2]
# 过滤性别异常值
data = data[data["性别"].isin(["男", "女"])]
print(f"清洗后数据量:{len(data)}")

# 性别编码(男=1,女=0)
label_encoder = LabelEncoder()
data["性别编码"] = label_encoder.fit_transform(data["性别"])
y = data["性别编码"].values # 目标变量

# ----------------------
# 2. 特征工程:姓名字符序列处理
# ----------------------
# 提取姓名字符(每个字作为一个token)
names = data["姓名"].tolist()
# 最大姓名长度(取95%分位数,避免极端值)
max_length = int(np.percentile([len(name) for name in names], 95))
print(f"最大姓名长度:{max_length}")

# 字符级Tokenizer(将每个汉字映射为唯一ID)
tokenizer = Tokenizer(char_level=True) # 按字符级处理
tokenizer.fit_on_texts(names) # 学习所有出现的汉字
vocab_size = len(tokenizer.word_index) + 1 # 词汇表大小(+1用于padding)
print(f"词汇表大小(不同汉字数量):{vocab_size}")

# 将姓名转换为整数序列(如"张三"→[2,5])
sequences = tokenizer.texts_to_sequences(names)
# 统一序列长度(短补0,长截断)
X = pad_sequences(sequences, maxlen=max_length, padding="post")

# 划分训练集和测试集(8:2)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

# ----------------------
# 3. 构建CNN模型
# ----------------------
model = Sequential([
# 嵌入层:将字符ID映射为向量(维度32)
Embedding(
input_dim=vocab_size, # 词汇表大小
output_dim=32, # 嵌入向量维度
input_length=max_length # 输入序列长度
),
# 卷积层:提取局部字符特征(如2-3个字的组合)
Conv1D(
filters=64, # 卷积核数量
kernel_size=2, # 卷积核大小(每次看2个连续字)
activation="relu" # 激活函数
),
# 全局最大池化:保留最显著的特征
GlobalMaxPooling1D(),
# 全连接层+ dropout防止过拟合
Dense(32, activation="relu"),
Dropout(0.5),
# 输出层:二分类(sigmoid输出0-1概率)
Dense(1, activation="sigmoid")
])

# 编译模型
model.compile(
loss="binary_crossentropy", # 二分类损失函数
optimizer="adam", # 优化器
metrics=["accuracy"] # 评估指标
)

model.summary() # 打印模型结构

# ----------------------
# 4. 模型训练
# ----------------------
# 早停策略:防止过拟合(验证集准确率3轮不提升则停止)
early_stopping = EarlyStopping(
monitor="val_accuracy",
patience=3,
mode="max",
restore_best_weights=True
)

# 训练模型
history = model.fit(
X_train, y_train,
epochs=20, # 最大训练轮次
batch_size=32, # 批次大小
validation_split=0.1, # 从训练集划分10%作为验证集
callbacks=[early_stopping],
verbose=1
)

# ----------------------
# 5. 模型评估
# ----------------------
print("\n测试集评估:")
loss, accuracy = model.evaluate(X_test, y_test, verbose=0)
print(f"损失:{loss:.4f},准确率:{accuracy:.4f}")

# ----------------------
# 6. 预测示例
# ----------------------
def predict_gender(name):
"""预测姓名对应的性别"""
# 预处理姓名
name_clean = clean_name(name)
if len(name_clean) == 0:
return "无效姓名"
# 转换为序列并补齐长度
seq = tokenizer.texts_to_sequences([name_clean])
seq_pad = pad_sequences(seq, maxlen=max_length, padding="post")
# 预测概率(>0.5为男,否则为女)
prob = model.predict(seq_pad)[0][0]
gender = "男" if prob > 0.5 else "女"
return f"姓名:{name},预测性别:{gender}(概率:{prob:.2f})"

# 测试几个姓名
test_names = ["张三", "李四", "王丽", "赵强", "刘芳", "陈明"]
for name in test_names:
print(predict_gender(name))

# 保存
import pickle # 用于保存Tokenizer

# 保存模型(HDF5格式)
model.save("gender_prediction_model.h5")
print("模型已保存为:gender_prediction_model.h5")

# 保存Tokenizer(使用pickle)
with open("name_tokenizer.pkl", "wb") as f:
pickle.dump(tokenizer, f)
print("Tokenizer已保存为:name_tokenizer.pkl")

# 同时保存max_length(预测时需要用到)
with open("max_length.pkl", "wb") as f:
pickle.dump(max_length, f)
print("最大姓名长度已保存为:max_length.pkl")

该模型的损失为 0.8121,准确率为 0.9798。

如果想要预测单个姓名的性别,可以使用附件中的 predict_gender.py 函数:

import re
import pickle
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 加载模型和必要组件(只需加载一次)
def load_gender_model(model_path="gender_prediction_model.h5",
tokenizer_path="name_tokenizer.pkl",
max_len_path="max_length.pkl"):
"""加载训练好的模型、tokenizer和最大长度参数"""
# 加载模型
model = load_model(model_path)
# 加载tokenizer
with open(tokenizer_path, "rb") as f:
tokenizer = pickle.load(f)
# 加载最大长度
with open(max_len_path, "rb") as f:
max_length = pickle.load(f)
return model, tokenizer, max_length

# 加载模型组件(全局变量,避免重复加载)
model, tokenizer, max_length = load_gender_model()

def predict_gender(name):
"""
预测姓名对应的性别

参数:
name: str,输入的姓名(可以包含非中文字符,会自动清洗)

返回:
str: "男" 或 "女"(若姓名无效则返回"无效姓名")
"""
# 1. 清洗姓名(仅保留中文字符)
def clean_name(name):
name = str(name).strip()
return re.sub(r'[^\u4e00-\u9fa5]', '', name) # 过滤非中文字符

name_clean = clean_name(name)

# 2. 检查清洗后的姓名是否有效
if len(name_clean) < 1: # 至少保留1个中文字符
return "无效姓名"

# 3. 将姓名转换为模型可接受的序列
# 转换为整数序列
sequence = tokenizer.texts_to_sequences([name_clean])
# 补齐/截断到训练时的最大长度
padded_sequence = pad_sequences(sequence, maxlen=max_length, padding="post")

# 4. 模型预测
prob = model.predict(padded_sequence, verbose=0)[0][0] # verbose=0关闭预测输出

# 5. 根据概率返回性别(阈值0.5)
return "男" if prob > 0.5 else "女"

# ----------------------
# 使用示例
# ----------------------
if __name__ == "__main__":
test_names = ["张三", "李四", "王丽", "赵强", "刘芳", "陈明", "欧阳雪", "司马勇"]
for name in test_names:
print(f"姓名:{name} → 预测性别:{predict_gender(name)}")

# 姓名:张三 → 预测性别:男
# 姓名:李四 → 预测性别:男
# 姓名:王丽 → 预测性别:女
# 姓名:赵强 → 预测性别:男
# 姓名:刘芳 → 预测性别:女
# 姓名:陈明 → 预测性别:男
# 姓名:欧阳雪 → 预测性别:女
# 姓名:司马勇 → 预测性别:男

看起来效果还不错。

如果想要预测多个姓名的性别,可以先把需要预测姓名放到附件中的 input_names.csv 中:

name
李浩垣
张杜超
于光浦
孙爱仙
杨莲菊
赵东祥
邢丽炫
张为梅
陈佩奇
黄懿贤
齐敏音
李圣用
姚龙兵
杨仲东
豆珂阳

然后运行下面的代码(附件中的 csv_handel.py 文件):

import re
import pickle
import pandas as pd
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences

# ----------------------
# 1. 加载模型和必要组件
# ----------------------
def load_gender_model(model_path="gender_prediction_model.h5",
tokenizer_path="name_tokenizer.pkl",
max_len_path="max_length.pkl"):
"""加载训练好的模型、tokenizer和最大长度参数"""
try:
# 加载模型
model = load_model(model_path)
# 加载tokenizer
with open(tokenizer_path, "rb") as f:
tokenizer = pickle.load(f)
# 加载最大长度
with open(max_len_path, "rb") as f:
max_length = pickle.load(f)
return model, tokenizer, max_length
except Exception as e:
print(f"加载模型失败:{e}")
raise # 抛出异常,终止程序

# ----------------------
# 2. 定义预测函数
# ----------------------
def predict_single_name(name, model, tokenizer, max_length):
"""单个姓名的性别预测"""
# 清洗姓名(仅保留中文字符)
def clean_name(name):
name = str(name).strip()
return re.sub(r'[^\u4e00-\u9fa5]', '', name) # 过滤非中文字符

name_clean = clean_name(name)

# 无效姓名处理
if len(name_clean) < 1:
return "无效姓名"

# 转换为模型输入格式
sequence = tokenizer.texts_to_sequences([name_clean])
padded_sequence = pad_sequences(sequence, maxlen=max_length, padding="post")

# 预测并返回结果
prob = model.predict(padded_sequence, verbose=0)[0][0]
return "男" if prob > 0.5 else "女"

# ----------------------
# 3. 批量处理CSV文件
# ----------------------
def process_csv(input_csv_path, output_csv_path, model, tokenizer, max_length):
"""
处理输入CSV文件,批量预测性别并生成输出文件

参数:
input_csv_path: 输入CSV文件路径(包含name字段)
output_csv_path: 输出CSV文件路径(新增gender字段)
model, tokenizer, max_length: 加载的模型组件
"""
# 读取输入CSV
try:
df = pd.read_csv(input_csv_path)
except Exception as e:
print(f"读取输入文件失败:{e}")
return

# 检查是否包含name字段
if "name" not in df.columns:
print("输入CSV文件必须包含'name'字段")
return

# 批量预测性别(应用预测函数到每一行)
print(f"开始预测,共{len(df)}条数据...")
df["gender"] = df["name"].apply(
lambda x: predict_single_name(x, model, tokenizer, max_length)
)

# 保存结果到新CSV
try:
df.to_csv(output_csv_path, index=False, encoding="utf-8-sig") # utf-8-sig确保中文正常显示
print(f"预测完成,结果已保存至:{output_csv_path}")
except Exception as e:
print(f"保存输出文件失败:{e}")

# ----------------------
# 4. 主程序入口
# ----------------------
if __name__ == "__main__":
# 配置文件路径(根据实际情况修改)
INPUT_CSV = "input_names.csv" # 输入CSV路径(包含name字段)
OUTPUT_CSV = "predicted_genders.csv" # 输出CSV路径(新增gender字段)

# 加载模型组件
model, tokenizer, max_length = load_gender_model()

# 批量处理并生成结果
process_csv(INPUT_CSV, OUTPUT_CSV, model, tokenizer, max_length)

然后就会得到预测结果了(predicted_genders.csv 文件):

name,gender
李浩垣,男
张杜超,男
于光浦,男
孙爱仙,女
杨莲菊,女
赵东祥,男
邢丽炫,女
张为梅,女
陈佩奇,男
黄懿贤,男
齐敏音,女
李圣用,男
姚龙兵,男
杨仲东,男
豆珂阳,男

R 语言部分

R 语言中可以使用 reticulate 包创建 Python 虚拟环境以及把 Python 函数封装成 R 语言的函数。

首先是创建虚拟环境:

library(tidyverse)
library(reticulate)
# 安装 python3.10
conda_create(envname = "genderpy", python_version = "3.10")
use_condaenv("genderpy")

# 1. 读取requirements.txt文件
requirements <- readLines("requirements.txt")

# 2. 清理空行和注释
requirements <- grep("^[^#]", requirements, value = TRUE) # 移除注释行
requirements <- trimws(requirements) # 去除两端空格
requirements <- requirements[requirements != ""] # 移除空行

requirements

# 3. 安装所有依赖
conda_install(packages = requirements, envname = "genderpy",
python_version = "3.10", pip = T,
pip_options = "--index-url https://pypi.tuna.tsinghua.edu.cn/simple")

封装 Python 函数:

source_python("predict_gender.py")

predict_gender("张小龙")
#> [1] "男"

预测多个姓名的性别可以这样:

read_csv("input_names.csv") -> df

df %>%
mutate(gender = map_chr(name, predict_gender)) -> df2

df2

#> # A tibble: 15 × 2
#> name gender
#> <chr> <chr>
#> 1 李浩垣 男
#> 2 张杜超 男
#> 3 于光浦 男
#> 4 孙爱仙 女
#> 5 杨莲菊 女
#> 6 赵东祥 男
#> 7 邢丽炫 女
#> 8 张为梅 女
#> 9 陈佩奇 男
#> 10 黄懿贤 男
#> 11 齐敏音 女
#> 12 李圣用 男
#> 13 姚龙兵 男
#> 14 杨仲东 男
#> 15 豆珂阳 男

这样就解决了这个问题。

点击这里跳转到 RStata 短书平台获取附件:Python 和 R 语言中如何根据姓名预测性别?

评论