NGender:根据中文姓名猜测其性别

1,057 阅读3分钟

项目地址:github.com/observerss/…

NGender:

根据中文姓名猜测其性别

  • 不到20行纯Python代码(核心部分)
  • 无任何依赖库
  • 兼容python3, python2, pypy
  • 82%的准确率
  • 可用于猜测性别
  • 也可用于判断名字的男性化/女性化程度

使用

  • 安装
    • pip install ngender
  • 然后在命令行中
 $ cli 赵本山 宋丹丹
 name: 赵本山 => gender: male, probability: 0.9836229687547046
 name: 宋丹丹 => gender: female, probability: 0.9759486128949907

当然也可以在Python程序中用

 >>> import ngender
 >>> ngender.guess('赵本山')
 ('male', 0.9836229687547046)
 
 >>> ngender.guess('宋丹丹')
 ('female', 0.9759486128949907)
 
 >>> %timeit guess('宋丹丹')
 100000 loops, best of 3: 4.01 µs per loop

原理(重要)

数学(朴素贝叶斯)

贝叶斯公式: P(Y|X) = P(X|Y) * P(Y) / P(X)

当X条件独立时, P(X|Y) = P(X1|Y) * P(X2|Y) * ...

应用到猜名字上

 P(gender=男|name=本山) 
 = P(name=本山|gender=男) * P(gender=男) / P(name=本山)
 = P(name has 本|gender=男) * P(name has 山|gender=男) * P(gender=男) / P(name=本山)

计算

  1. 文件charfreq.csv是怎么来的?

    曾经有个东西叫开房记录.avi(雾),里面有名字和性别, 2000w条, 统计一下得出

  2. 怎么算 P(name has 本|gender=男)?

    “本”在男性名字中出现的次数 / 男性字出现的总次数

  3. 怎么算 P(gender=男)?

    男性名出现的次数 / 总次数

  4. 怎么算 P(name=本山)?

    不用算(不用算的根本原因是:女性和男性都要计算,但是只是分母,对于比较没有影响,所以即使去掉也没事,并不影响概率的比较)

项目解析:

在源码结构如下 image.png

  • charfreq.csv

image.png

  • cli.py

    • 可以作为运行的客户端
     import argparse
     
     from .ngender import guess
     
     def main():
         # 参数解读
         parser = argparse.ArgumentParser(
             description='Guess gender for Chinese names')
     
         parser.add_argument('names', nargs='+',
                             help='chinese names to guess')
         args = parser.parse_args()
         
         # 多个名字循环猜性别,然后打印猜测信息
         for name in args.names:
             gender, prob = guess(name)
             print('name: {} => gender: {}, probability: {}'
                   ''.format(name, gender, prob))
     
     
     if __name__ == '__main__':
         main()
    
  • ngender.py

    • 核心代码,给出名字预测性别
     #!/usr/bin/env python
     # -*- coding: utf-8 -*-
     import os
     
     __all__ = ['guess']
     
     # 对于python2的unicode的兼容
     def py2compat(name):
         try:
             name = name.decode('utf-8')
         except:
             pass
         return name
     
     
     class Guesser(object):
         def __init__(self):
             # 加载模型,获取字频数据
             self._load_model()
         
         def _load_model(self):
             self.male_total = 0
             self.female_total = 0
             # 在这统计
             self.freq = {}
     
             with open(os.path.join(os.path.dirname(__file__),
                                    'charfreq.csv'),
                       'rb') as f:
                 # skip first line,第一行是title
                 next(f)
                 for line in f:
                     line = line.decode('utf-8')
                     char, male, female = line.split(',')
                     char = py2compat(char)
                     self.male_total += int(male)
                     self.female_total += int(female)
                     self.freq[char] = (int(female), int(male))
             # 计算总数
             self.total = self.male_total + self.female_total
             
             # 计算每个字符出现在男中的概率和出现在女中的概率
             for char in self.freq:
                 female, male = self.freq[char]
                 self.freq[char] = (1. * female / self.female_total,
                                    1. * male / self.male_total)
    
         def guess(self, name):
             name = py2compat(name)
             # 去除姓
             firstname = name[1:]
             # 在这个地方可以学习到检测中文的方法
             for char in firstname:
                 assert u'\u4e00' <= char <= u'\u9fa0', u'姓名必须为中文'
             
             # 分别计算是男概率和是女的概率
             pf = self.prob_for_gender(firstname, 0)
             pm = self.prob_for_gender(firstname, 1)
     
             # 男概率大,就预测是男;女概率大,就预测是女
             if pm > pf:
                 return ('male', 1. * pm / (pm + pf))
             elif pm < pf:
                 return ('female', 1. * pf / (pm + pf))
             else:
                 return ('unknown', 0)
     
         def prob_for_gender(self, firstname, gender=0):
             # 三目运算 计算是男性占比和女性占比
             p = 1. * self.female_total / self.total \
                 if gender == 0 \
                 else 1. * self.male_total / self.total
             
             # 朴素贝叶斯的核心:概率条件无关,直接相乘,得出名字是男性的概率或是女性的概率
             for char in firstname:
                 p *= self.freq.get(char, (0, 0))[gender]
     
             return p
     
     
     guesser = Guesser()
     
     
     def guess(name):
         return guesser.guess(name)
    

    特别收获(合法中文的检测)

    # 在这个地方可以学习到检测中文的方法          
    for char in firstname:              
        assert u'\u4e00' <= char <= u'\u9fa0', u'姓名必须为中文'