题目貌似取得有些大,准确的说应该叫基于大数据的机器学习,不过就这样吧>_<
每当说到大数据分析,有些人就很讳莫如深,故意讲得很复杂,生怕别人听懂就low了。然而其实可能没那么复杂,就像陈皓说的——所谓大数据不就是因为硬盘便宜了,所以不用删日志了吗? <_<
好了,废话不多说,这次实现的目标很简单,就是通过已知数据,通过某人的姓名分析出他的性别。如你所知,中国的姓名那是千千万万,不像老外来来回回就是那几个名字,拿个数据表对照一下就可以了——毛子更懒,男的叫朱可夫,女的就叫朱可娃,自带性别标识,生怕别人以为自己是伪娘。。。
也许此刻你或许会问这有什么用啊,简单来说你想给自己的网站做一个性别统计,并分析出各自的爱好,购买习惯等,类似淘宝每年的公众那个统计报表,此时就可以用到——也许你会说可以直接统计用户信息里面啊,然而你确定有多少用户会愿意填?又有多少是认真的呢?不过有一个数据一般是真实的,那就是快递单及收货人姓名!
以下内容均在win10+python2.7+mysql5.5上验证通过。
数据分析,数据分析,没数据当然不行,所以程序未动,数据先行。本次我选择的是某2000w的那个数据——不要问我为什么会有,这不是本次的重点——之所以选择这个是有一定理由的,这个我下面会说到。
首先这次我们要做的是基于中文名的数据,所以老外很多开源的训练数据库直接over了。。。然后要有足够的基础量,也就是说作为训练数据库应该有200w左右的数据,这样可以保证数据的覆盖面足够广。其次需要有自检功能,也就是说我能通过已知的数据去修复某些受损数据,且不带来其他任何影响,这点尤其重要!因此你可以选择这个2000w或者某数字订票的。。。
不过这个数据也并不是完美的,最明显的一点就是本身其性别差就很大,男的多,女的少,而标准的训练数据应该保证所有姓名的单字值总和为0,即未计算前这个名字是男是女各是0.5,不过影响不算特别大,可以通过程序修正部分。
导数据这部分我就不说了,导完之后就是苦逼的洗数据。这是大头,也是占这次示例里面时间最多的部分。
1、筛选出是中文名的数据,并在info表添加一个is_China。
select * from info where not name regexp '^[1-9A-Za-z]';
或者直接删除这部分不用的数据
delete from info where name regexp '^[1-9A-Za-z]';
2、进行数据自检
之前说过之所以选择这个数据,一个很重要的原因就是可以自检。因为你可以通过身份证号来重新计算性别。
如果你之前已经看过这个数据库,应该会发现其中有很多的数据实际上是不对的,比如,你查看“雯”这个字,会发现几乎男女的性别是各一半,这显然是不对的——当然也有一种可能,就是现在已经基佬遍地了<_<
为此,首先第一步是将正确的身份证号筛选出来,虽然你可以通过相互对照来修复部分身份证,但还是不推荐这么做,所以只需将不符合规则的身份证给剔除掉。
delete FROM info WHERE is_China = 1 AND CtfTp = 'ID' AND (CHAR_LENGTH(CtfId) <>18 and CHAR_LENGTH(CtfId) <>15)
当有了正确的身份证之后,就可以进行自检修复了。
update info a inner join ( SELECT id, NAME, Gender, CASE (CASE CHAR_LENGTH(CtfId) WHEN 18 THEN substring(CtfId, 17, 1) ELSE substring(CtfId, 15, 1) END)%2 WHEN 0 THEN 'F' WHEN 1 THEN 'M' END sex, CtfId FROM `info` WHERE is_China = 1 AND CtfTp = 'ID') b set a.Gender = b.sex where a.id=b.id
以上SQL基本上没什么难度,就是截取和更新。执行的时候,你可以出去喝杯咖啡,或者运动一番,又或是去趟卫生间。。。反正什么打发时间做什么。
基本上上述工作做完,你的可用数据在180w左右。
3、建立字符表,将文本转化为数值
首先先建表
CREATE TABLE `name` ( `id` int(11) NOT NULL AUTO_INCREMENT, `chars` varchar(2) COLLATE utf8_unicode_ci DEFAULT NULL, `val` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IDX_NAME` (`chars`,`val`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
这里我偷懒选择了将值直接插入数据库,即每个文字都是有多行的,而不是分类统计的方法,如果你有兴趣可以试着改写一下表结构,val变成三列——男、女、总和,使得读取更加快速方便。
接着我们就要往里面灌数据了。虽然用复杂的SQL应该也能实现,不过这里我们使用python来完成这项工作。如果你对python还不是很熟悉,可以去这里学习一下
#encoding=utf-8 import sys import MySQLdb import random import time import traceback import math reload(sys) sys.setdefaultencoding('utf-8') #设置Python的默认编码为 utf-8 ######################################################################## db=MySQLdb.connect(host="localhost",user="root",passwd="",charset='utf8') cur=db.cursor(cursorclass = MySQLdb . cursors . DictCursor ) #让返回的是字典类型 cur.execute('use 2000w') cur.execute('SELECT id,Name,Gender FROM `info` WHERE is_China=1;') rets=cur.fetchall() chars=[] chars_gender=[] for ret in rets: length=len(ret['Name'])/2 char=ret['Name'][length:] for x in xrange(length,len(char)+1): c=char[x-1:x] chars.append(c); sex=0 if(ret['Gender']=='M'): sex=1 else: sex=-1 chars_gender.append(sex) for k in xrange(0,len(chars)): sql="INSERT INTO name (chars, val) VALUES ('"+chars[k]+"',"+(str)(chars_gender[k])+");" # print sql try: cur.execute(sql) db.commit() #加上这句之后才会提交执行,否则不执行 except: # 发生错误时回滚 db.rollback()
现在知道我为什么偷懒了吧,因为这样可以少些很多行,顺带可以不用动脑筋——哈哈哈哈哈哈哈哈——pia飞。。。
接着你又可以去喝杯咖啡,上趟厕所了。。。
至此,我们的数据处理部分就全部结束了。是不是很简单呢。理论上应该没什么不容易理解的,那么,OK,下面就是使用这些数据了。
模型设计。老实说,这部分一般都是由Scientist来设计完成的,不过这次我们不需要这么复杂,就用最简单的条件概率就OK了。
条件概率,如果你还记的高中或大学的知识的话应该知道,他是计算在某条件下某事发生的概率。因此我们将这次的命题转化一下就可以知道,其实质是已知我们选了某字则该字是属于女生/男生的概率。至于公式的话,自己翻书吧。
好,接下来就到了coding时间了。废话不多说,直接上代码
#encoding=utf-8 import sys import MySQLdb import random import time import traceback import math import decimal reload(sys) sys.setdefaultencoding('utf-8') #设置Python的默认编码为 utf-8 ######################################################################## name=raw_input('please input name:'); name=name.decode("gbk") chars=[] length=len(name)/2 char=name[length:] for x in xrange(length,len(char)+1): c=char[x-1:x] chars.append(c); # 简单的叠字处理修正 chars_unique=set(chars) is_overlapping=False if len(chars_unique)!=len(chars): is_overlapping=True db=MySQLdb.connect(host="localhost",user="root",passwd="",charset='utf8') cur=db.cursor(cursorclass = MySQLdb . cursors . DictCursor ) #让返回的是字典类型 cur.execute('use 2000w') # sql="SELECT Gender,COUNT(Name) total_num FROM `info` WHERE is_China=1 GROUP BY Gender" # cur.execute(sql) # rets=cur.fetchall() # F_total_num=rets[0]['total_num'] # M_total_num=rets[1]['total_num'] # total_num=M_total_num+F_total_num # 加速运行 每次都去统计其实意义不大,因为当数量足够大时,微小的变化并不会影响统计结果,因为在那之前精度已经不足了 total_num=1870562 F_total_num=562496 M_total_num=1308066 pro1_arr=[] pro2_arr=[] overlapping_word='' for x in xrange(0,len(chars)): sql="SELECT SUM(val) v FROM `name` WHERE chars='" +chars[x]+ "' GROUP BY val" cur.execute(sql) rets=cur.fetchall() if(len(rets)==1): if(rets[0]['v']>=0): m_nums=rets[0]['v'] f_nums=0; else: f_nums=rets[0]['v'] m_nums=0; else: try: f_nums=rets[0]['v'] except: f_nums=0 try: m_nums=rets[1]['v'] except: m_nums=0 if f_nums==0 and m_nums==0: # 此处为不存在的字符做异常处理,可以调用类似createSex.py的方法将不存在的数据加入那么表 print "Sorry,I don't know the name sex,maybe you can help me" sys.exit(0) char_total_num=abs(m_nums)+abs(f_nums) pro1_val=(abs(f_nums)/total_num)/(char_total_num/total_num) pro2_val=(abs(m_nums)/total_num)/(char_total_num/total_num) pro1_arr.append(pro1_val) pro2_arr.append(pro2_val) if overlapping_word==chars[x]: if pro1_val > 0.8 or pro2_val > 0.8: is_overlapping=False else: overlapping_word=chars[x] pro1=sum(pro1_arr)/len(chars); pro2=sum(pro2_arr)/len(chars); if is_overlapping: pro1+=decimal.Decimal(0.3) pro2-=decimal.Decimal(0.3)
if pro1>decimal.Decimal(1): pro1=decimal.Decimal(1); pro2=decimal.Decimal(0);
if pro1>pro2: print "The Name have "+(str)(pro1*100)+" is a woman name"; else: print "The Name have "+(str)(pro2*100)+" is a man name";
看到这里,也许有人会说,卧槽,这不是坑爹吗?就这100来行的代码就完成了?这他喵的也算大数据分析?但是,why not ?
好了,收起无谓的口水仗吧,来看看我们的程序做了什么。第一将新的名字处理成单字,然后分别再去计算每个单字的条件概率,最后将所有的概率相加然后平均。然后还为叠字做了一些小小的处理。别看虽然代码不多,但如果只看结果的话,还是可以接受的,基本上可以达到80%以上的识别率(日文名字不行,哪怕是汉字,不要问我为什么——自己往前看,当然有例外,比如西木野真姬<_<)。
是不是感觉很简单!蹭蹭几下,你就会大数据分析了!(伪
如你所见,除了陆无双判断有些失误之外其他均可以,当然这里还有一部分原因是由于之前提到过的本身数据问题造成的,还有部分是程序的原因,比如这边计算总的概率时只是简单的相加平均,这会导致某些字由于两极分化严重而导致总结果的不准确,因此应该才用加权计算来消磨掉这部分的错误。当然其他方法也是有的,我在这里只是做一个抛砖引玉的作用,如果各位有兴趣或者有更好的思路欢迎来这里,互相探♂讨♂交♂流