python构建地址元素词库+双向最大匹配分词

本文示例程序的功能主要是将全量不规范地址文本进行解析,提取地址要素词构建自定义地址元素词库,该词库分别维护了停用词、地址要素级别关键字、地址要素词、同义词等;自定义地址元素词库完成初始化之后,首先基于该词库对每条地址文本进行停用词剔除、同义词替换等预处理,然后应用双向最大匹配法对地址文本进行分词,提取地址要素词,最后是对分词结果的应用,例如词云、文本比对等。需要关注应用过程中遇到的问题,持续优化代码的同时动态维护完善自定义地址元素词库。

这个项目是之前一位算法同事交接给我的,不过我基本算是重写了,展开来写的暂不考虑性能等方面的问题,对小白稍微友好一点~仅做个人积累记录使用(mac OS系统,其他文章如未特意说明,则均默认windows环境),如有侵权或不合规请及时联系处理~ 


目录

一、自定义地址元素词库构建

1、停用词库

2、地址要素词库、地址要素级别关键字

2.1 文本预处理

2.2 提取地址要素词、识别地址要素级别关键字

3、同义词库

二、双向最大匹配分词法

1、文本预处理

2、前向最大匹配逻辑

3、双向最大匹配分词


一、自定义地址元素词库构建

本文样本数据为来自两个系统的停电记录数据,其中“停电范围”字段内容为非结构化地址文本,因数据涉敏、只摘取一条“停电范围”文本作为示例,如下所示:

“【影响街道村组】湖北省恩施州【巴东县绿葱坡镇】锦衣村、白果庄村、枣子坪村、青大山村、龙池槽村、抗家岭村、岩屋坪村、祁家坪村、冯家湾村、范家坪村、绿葱坡居委会七至九组。绿葱坡镇范家坪村、恩施州巴东县青树沟煤矿有限责任公司 
【高危重要客户】无
【影响客户】巴东县绿葱坡镇锦衣村、白果庄村、枣子坪村、青大山村、龙池槽村、抗家岭村、岩屋坪村、祁家坪村、冯家湾村、范家坪村、绿葱坡居委会七至九组。绿葱坡镇范家坪村、恩施州巴东县青树沟煤矿有限责任公司”

1、停用词库

观察发现文本中存在跟地址无关的词组,如:“【影响街道村组】、【高危重要客户】无、【影响客户】”等,规律较一致,基本都是被【】、[]等括号标记,可通过正则表达式提取这类词组作为停用词初始化到停用词库中。同时需要注意文本中也存在括号内标记了有效地址文本,如【巴东县绿葱坡镇】,是需要结合人工干预来构建并动态维护停用词库。

import pandas as pd
import re
import string
import sys


#获取样本数据
info_cms = pd.read_excel('filename1.xlsx', converters={u'信息编号':str})
info_pms = pd.read_excel('filename2.xlsx', converters={u'停电编号':str})


#--开始提取停用词的分割线---------------------------------------------------

info_cms['停电地址']=info_cms['停电范围']
info_pms['停电地址']=info_pms['停电范围']

#括号符号规范化处理
info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: x.replace('[','【').replace(']','】'))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: x.replace('[','【').replace(']','】'))

#提取【】内的文本
t1=info_cms['停电地址'].apply(lambda x : re.findall('【(.*?)】', x))
t2=info_pms['停电地址'].apply(lambda x : re.findall('【(.*?)】', x))

#维护停用词库
d1={}
for i in range(len(t1)):
    for j in t1[i]:
        if not j in d1:
            d1[j]=1
        else:
            d1[j]=d1[j]+1

for i in range(len(t2)):
    for j in t2[i]:
        if not j in d1:
            d1[j]=1
        else:
            d1[j]=d1[j]+1
       

#导出词集,结合人工干预、维护停用词库
df1=pd.DataFrame.from_dict(d1,orient='index',columns=['计数'])
df1 = df1.reset_index().rename(columns = {'index':'停用词筛查'})
df1.to_excel('filepath/停用词筛查.xlsx',index=False)

2、地址要素词库、地址要素级别关键字

将停电范围文本剔除停用词后,如下所示:

“【】湖北省恩施州【巴东县绿葱坡镇】锦衣村、白果庄村、枣子坪村、青大山村、龙池槽村、抗家岭村、岩屋坪村、祁家坪村、冯家湾村、范家坪村、绿葱坡居委会七至九组。绿葱坡镇范家坪村、恩施州巴东县青树沟煤矿有限责任公司 
【】
【】巴东县绿葱坡镇锦衣村、白果庄村、枣子坪村、青大山村、龙池槽村、抗家岭村、岩屋坪村、祁家坪村、冯家湾村、范家坪村、绿葱坡居委会七至九组。绿葱坡镇范家坪村、恩施州巴东县青树沟煤矿有限责任公司”

2.1 文本预处理

第一步:在对文本进行解析、提取地址要素词之前需要进行一系列的文本预处理工作,如文本中各式各样的特殊符号,有些可作为切分符(暂不处理)、有些不作为切分符。首先将不作为切分符的 “ ()(){}><|”等符号替换为空,将“#”替换为汉字“号”等,为后续其他预处理步骤做准备。

#导入停用词库,剔除停用词
droplist = pd.read_excel('filepath/词库.xlsx', sheet_name='停用词')

for i in range(len(droplist)):
    info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: x.replace(droplist['停用词维护'][i],''))
    info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: x.replace(droplist['停用词维护'][i],''))


#--切分前预处理------------------------------------------
#剔除部分特殊符号
info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: re.sub(r'[()(){}><|]','',x))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: re.sub(r'[()(){}><|]','',x))

#将符号‘#’转换为汉字“号”
info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: x.replace('#','号').replace('#','号').replace('—','-'))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: x.replace('#','号').replace('#','号').replace('—','-'))

第二步:处理通过“-”连接的数字文本信息,如“2020-3-17”等与地址无关的日期文本直接剔除,“8栋1-3单元”替换为“8栋1至3单元”,避免后续通过“-”切词时将其切分为“8栋1”、“3单元”等无法识别的地址要素,或直接剔除“-”时被误转换为“8栋13单元”。此处我们将这几种情况替换完成后再将其他地方的“-”剔除处理。

#用‘-’表示的楼栋单元形式转换
def lddyzh(s):
    text1 = iter(['栋', '单元'])
    t1=re.findall(r'\d{4}-\d+-\d+',s) #剔除年月日
    for i in t1:
        s=s.replace(i,'')
    s1=re.findall(r'\d+-\d+-\d+',s)
    for j in s1:
        try:
            s2=re.sub(r'-',string=j,repl=lambda y: next(text1))
            s=s.replace(j,s2)
        except StopIteration:
            pass
    s3=re.findall(r'\d+栋\d+-\d+',s)
    for k in s3:
        s4=re.sub(r'-','单元',k)
        s=s.replace(k,s4)
    s5=re.findall(r'\d+-\d+',s)
    for h in s5:
        s6=re.sub(r'-','至',h)
        s=s.replace(h,s6)
    s7=re.findall(r'\d+号-\d+号',s)
    for z in s7:
        s8=re.sub(r'-','至',z)
        s=s.replace(z,s8)
    return s

info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: lddyzh(x))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: lddyzh(x))

info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: x.replace('-',','))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: x.replace('-',','))

第三步:将文本里的中文大写数字转换为阿拉伯小写数字,方便后续统一处理村组或楼栋单元号等地址缩略问题。注意这里只处理“组/队/单元/栋/号楼/台区”等字符前面的大写数字,如“三楼八单元五组六队七号楼”,其他如“十里铺”、“百步亭”等不做替换处理。

#中文大写数字转换为阿拉伯小写数字,方便后续统一补齐村组或楼栋单元号等地址缩略
common_used_numerals_tmp ={'零':0, '一':1, '二':2, '两':2, '三':3, '四':4, '五':5, 
                           '六':6, '七':7, '八':8, '九':9, '十':10, '百':100, 
                           '千':1000, '万':10000, '亿':100000000}

def chinese2digits(s1):  
    l1=re.findall(r'([零一二三四五六七八九十百千万亿]+)[组队单元栋号楼台区]',s1)
    for j in range(len(l1)):
        uchars_chinese=l1[j].encode('cp936').decode('cp936')
        total = 0
        r = 1              
        for i in range(len(uchars_chinese) - 1, -1, -1):
            val = common_used_numerals_tmp.get(uchars_chinese[i])
            if val >= 10 and i == 0:  
                if val > r:
                    r = val
                    total = total + val
                else:
                    r = r * val
            elif val >= 10:
                if val > r:
                    r = val
                else:
                    r = r * val
            else:
                total = total + r * val                
        s1=s1.replace(l1[j],str(total))
    return s1

info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: chinese2digits(x))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: chinese2digits(x))    

第四步:将文本里的村组、队、台区等地址缩略词进行补全处理,如“锦衣村1、2、3、4组,锦衣村7队、8队、9队”等,处理为“锦衣村1组、锦衣村2组、锦衣村3组”等,避免后续通过“、”切词时切分为“锦衣村1”、“4组”等无法识别的地址要素。

#村组台区地址缩略词补全
def cztqdzslbq(s1):   
    l1=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[组][、.,, ]{1,}[0-9]+组',s1)
    l2=[x.replace('组','') for x in l1]
    for i in range(len(l2)):        
        l2[i]=re.split(r'[、.,,  ]+',l2[i])   
        s2=l2[i][0]+'组'
        for j in range(1,len(l2[i])):            
            l2[i][j]=re.findall(r'([\u4E00-\u9FA50-9]+[\u4E00-\u9FA5])[0-9]+',l2[i][0])[0]+l2[i][j]+'组'
            s2=s2+','+l2[i][j]           
        s1=s1.replace(l1[i],s2)
      
    l3=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[队][、.,, ]{1,}[0-9]+队',s1)
    l4=[y.replace('队','') for y in l3]
    for k in range(len(l4)):        
        l4[k]=re.split(r'[、.,,  ]+',l4[k])   
        s3=l4[k][0]+'队'
        for r in range(1,len(l4[k])):            
            l4[k][r]=re.findall(r'([\u4E00-\u9FA50-9]+[\u4E00-\u9FA5])[0-9]+',l4[k][0])[0]+l4[k][r]+'队'
            s3=s3+','+l4[k][r]           
        s1=s1.replace(l3[k],s3)
    return s1

    l5=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[台区][、.,, ]{1,}[0-9]+台区',s1)
    l6=[z.replace('台区','') for z in l5]
    for p in range(len(l6)):        
        l6[p]=re.split(r'[、.,,  ]+',l6[p])   
        s4=l6[p][0]+'台区'
        for q in range(1,len(l6[p])):            
            l6[p][q]=re.findall(r'([\u4E00-\u9FA50-9]+[\u4E00-\u9FA5])[0-9]+',l6[p][0])[0]+l6[p][q]+'台区'
            s4=s4+','+l6[p][q]           
        s1=s1.replace(l5[p],s4)
    return s1


info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: cztqdzslbq(x))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: cztqdzslbq(x))

第五步:将文本里的楼栋、单元等地址缩略词进行补全处理,如“碧桂园三期8栋1、2、3、4单元,9号楼1单元、2单元”等,处理为“碧桂园三期8栋1单元、碧桂园三期8栋2单元…”等,避免后续通过“、”切词时切分为“碧桂园三期8栋1”、“4单元”等无法识别的地址要素。

#楼栋单元地址缩略词补全
def lddzslbq(s1):   
    l1=re.findall(r'[0-9]+栋[0-9单元、.,,  ]+单元',s1)
    l2=[x.replace('单元','') for x in l1]
    for i in range(len(l2)):        
        l2[i]=re.split(r'[、.,,  ]+',l2[i])   
        s2=l2[i][0]+'单元'
        for j in range(1,len(l2[i])):            
            l2[i][j]=re.findall(r'[0-9]+栋',l2[i][0])[0]+l2[i][j]+'单元'
            s2=s2+','+l2[i][j]           
        s1=s1.replace(l1[i],s2)   
        
    l3=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[栋][0-9单元、.,, ]+[0-9]+栋',s1)
    l4=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[栋][0-9单元、.,, ]+[0-9]+栋',s1)
    for k in range(len(l4)):        
        l4[k]=re.split(r'[、.,,  ]+',l4[k])   
        s3=l4[k][0]
        for r in range(1,len(l4[k])):            
            l4[k][r]=re.findall(r'([\u4E00-\u9FA50-9]+[\u4E00-\u9FA5])[0-9]+栋',l4[k][0])[0]+l4[k][r]
            s3=s3+','+l4[k][r]           
        s1=s1.replace(l3[k],s3)
        
    l5=re.findall(r'[0-9]+号楼[0-9单元、.,,  ]+单元',s1)
    l6=[y.replace('单元','') for y in l5]  
    for p in range(len(l6)):        
        l6[p]=re.split(r'[、.,,  ]+',l6[p])   
        s4=l6[p][0]+'单元'
        for q in range(1,len(l6[p])):            
            l6[p][q]=re.findall(r'[0-9]+号楼',l6[p][0])[0]+l6[p][q]
            s4=s4+','+l6[p][q]           
        s1=s1.replace(l5[p],s4)
                   
    l7=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[号楼][0-9单元、.,, ]+[0-9]+号楼',s1)
    l8=re.findall(r'[\u4E00-\u9FA50-9]+[\u4E00-\u9FA5][0-9]+[号楼][0-9单元、.,, ]+[0-9]+号楼',s1)
    for s in range(len(l8)):        
        l8[s]=re.split(r'[、.,,  ]+',l8[s])   
        s5=l8[s][0]
        for t in range(1,len(l8[s])):            
            l8[s][t]=re.findall(r'([\u4E00-\u9FA50-9]+[\u4E00-\u9FA5])[0-9]+[号楼]',l8[s][0])[0]+l8[s][t]
            s5=s5+','+l8[s][t]           
        s1=s1.replace(l7[s],s5)  
    return s1
        
        
info_cms['停电地址']=info_cms['停电地址'].apply(lambda x: lddzslbq(x))
info_pms['停电地址']=info_pms['停电地址'].apply(lambda x: lddzslbq(x))

2.2 提取地址要素词、识别地址要素级别关键字

第一步:初步识别“省、市、区、县、镇、街、乡、村、组、队”等行政地址要素级别关键字,在每个地址要素级别关键字后添加切分符“,”,如将“省”替换为“省,”,方便后续切词。然后将样本数据地址文本中收集的特殊符号定义为切分符,如“,+",”.》?·、’〕“*:…:;\;【】\s。.//”等,将地址文本进行切分后创建字典保存分词结果及其词频。

#--开始切分,提取地址要素词-------------------------------------

#将省、市、县、镇等地址要素级别关键字添加切分符‘,’
dzys_jb_gjz = pd.read_excel('filepath/词库.xlsx', sheet_name='地址要素等级关键字')

for i in range(dzys_jb_gjz.shape[0]):
    info_cms['地址元素']=info_cms['停电地址'].apply(lambda x: x.replace(dzys_jb_gjz['关键字'][i],dzys_jb_gjz['关键字分隔符'][i]))
    info_pms['地址元素']=info_pms['停电地址'].apply(lambda x: x.replace(dzys_jb_gjz['关键字'][i],dzys_jb_gjz['关键字分隔符'][i]))

#切分符收录、切分提取地址元素
delimiters =r'[,+",”.》?·、’〕“*:…:;\;【】\s。.//]'
info_cms['地址元素']=info_cms['停电地址'].apply(lambda x: re.split(delimiters,x))
info_pms['地址元素']=info_pms['停电地址'].apply(lambda x: re.split(delimiters,x))

#维护地址元素词库
d1={}
for i in range(len(info_cms['地址元素'])):
    for j in info_cms['地址元素'][i]:
        if not j in d1:
            d1[j]=1
        else:
            d1[j]=d1[j]+1

for i in range(len(info_pms['地址元素'])):
    for j in info_pms['地址元素'][i]:
        if not j in d1:
            d1[j]=1
        else:
            d1[j]=d1[j]+1

第二步:创建表格保存地址文本解析切词结果。

writer1 = pd.ExcelWriter(r'filepath/切词及地址要素级别关键字.xlsx', engine='xlsxwriter')            
df1=pd.DataFrame.from_dict(d1,orient='index',columns=['计数'])
df1 = df1.reset_index().rename(columns = {'index':'地址元素'})
df1 = df1.drop(df1[(df1['地址元素'] == '')].index)
df1.sort_values('计数',ascending=False,inplace=True)
df1['词长']=df1['地址元素'].apply(lambda x: len(x))
df1['末字']=df1['地址元素'].apply(lambda x: x[-1])
df_gjz=df1['末字'].value_counts()
df_gjz=df_gjz.reset_index().rename(columns={'index':'关键字','末字':'计数'}) 
df1.to_excel(writer1,sheet_name='地址要素词',index=False)
df_gjz.to_excel(writer1,sheet_name='地址要素级别关键字',index=False)
writer1.save()

表格有两个sheet页,其中“地址要素词”sheet页如左所示,“地址要素级别关键字”sheet页如右所示,发现前述初步识别的地址要素级别关键字不够全面,需要持续补充完善、再次执行文本解析代码,再结合必要的人工干预将文本解析分词结果整理为地址要素词库。

                                              

第三步:同时创建表格按照地址要素级别关键字保存地址文本解析切词结果。这里需要格外注意的是原文本中存在如“冯家湾村、范家坪村”等一个地址要素词中含多个地址要素级别关键字“湾、坪、村”,在切词的时候容易粗暴的处理为[“冯家湾”、“村”、“范家坪”、“村”],导致地址要素词提取有误。

我还没有想到更好的处理方法,针对关键字“村”,目前就是根据全部地址要素级别关键词先提取一遍地址要素词,得到“冯家湾”、“范家坪”等添加到地址要素词库,然后剔除可能出现在“村”前面的其他低级别地址要素关键字“湾、坝、坪”等再提取一遍地址要素词,得到“冯家湾村”、“范家坪村”等也添加到地址要素词库。

writer2 = pd.ExcelWriter(r'filepath/地址元素级别.xlsx', engine='xlsxwriter')
df3=df1.copy()

for i in range(dzys_jb_gjz.shape[0]):   
    d2={}
    for k,v in d1.items():
        if len(k)>0 and k[-1]==dzys_jb_gjz['关键字'][i]:
            d2[k]=v
        else:
            pass    
    df2=pd.DataFrame.from_dict(d2,orient='index',columns=['计数'])
    df2 = df2.reset_index().rename(columns = {'index':'地址元素'})
    df2.sort_values('计数',ascending=False,inplace=True)
    df3=df3[~(df3['地址元素'].isin(df2['地址元素']))]  
    df3.sort_values('计数',ascending=False,inplace=True)
    df2.to_excel(writer2,sheet_name=dzys_jb_gjz['关键字'][i],index=False)

df3.to_excel(writer2,sheet_name='其他',index=False)
writer2.save()

3、同义词库

同义词库主要针对错别字等问题,如“红花村”、“红华村”等,经确认确属同一个地名的,可以替换处理。这里暂时没发现就没做,等遇到了再按需添加。

以上,基本上就已经初步构建了一个自定义地址元素词库,保存为一个表格,4个sheet页分别为“停用词库”、“地址元素库”、“地址要素等级关键字”、“同义词库”。注意只是初步创建完成,我自己也还没有穷尽所有问题,但典型问题基本都挑出来了。这个词库是需要动态持续维护的,发现了新的停用词、识别到新的同义词组、产生了新的地址文本等都需要及时更新词库。

另外,地址要素词库最好能按照行政区划将地址库中的各级地址要素关联汇编为不同的词典,方便快速检索与调用相应区域的全量地址要素,做词云的时候也可以实现下钻。我这里的停电地址文本不太规范不像快递邮寄地址,大部分都不是按照地址要素级别从高到低严格填写的,所以暂时没法分级管理。

二、双向最大匹配分词法

基于自定义词库的双向最大匹配分词法,其实就是关键词匹配法,从原文本段中按规则截取字符串(长词或短词)后在词库中查找是否存在该词。

1、文本预处理

注意这里不是直接对原文本“停电范围”字段使用双向最大匹配分词法,而是对经过一系列预处理后形成的“停电地址”字段使用双向最大匹配分词法。

#--基于自定义词库的双向最大匹配分词--------------------------
uni_cms=info_cms[['信息编号','变电站名称','停电范围','停电地址']]
uni_pms=info_pms[['停电编号','变电站名称','停电范围','停电地址']]

uni_cms['关联标识']=uni_cms['信息编号']+uni_cms['变电站名称']
uni_pms['关联标识']=uni_pms['停电编号']+uni_pms['变电站名称']

uni_cms = uni_cms.drop(['信息编号', '变电站名称'], axis=1)
uni_pms = uni_pms.drop(['停电编号', '变电站名称'], axis=1)

uni_cms=uni_cms.rename(columns={'停电地址':'停电地址_cms','停电范围':'停电范围_cms'}) 
uni_pms=uni_pms.rename(columns={'停电地址':'停电地址_pms','停电范围':'停电范围_pms'}) 

uni_tdxx=pd.merge(uni_cms.drop_duplicates(['关联标识']),uni_pms.drop_duplicates(['关联标识']),how='inner')

#去除字符串空格及乱七八糟符号
uni_tdxx['停电地址_cms'] = uni_tdxx['停电地址_cms'].apply(lambda x:re.sub(r'[,+",”.》?·、’〕“*:…:;\;【】\s。.//]','',x))
uni_tdxx['停电地址_pms'] = uni_tdxx['停电地址_pms'].apply(lambda x:re.sub(r'[,+",”.》?·、’〕“*:…:;\;【】\s。.//]','',x))

本例包含了来自两个系统的停电信息,创建关联字段拼接两张表保留关键字段后,针对两列“停电地址”文本,分别剔除文本中所有特殊符号、保留洁净地址文字部分。

“湖北省恩施州巴东县绿葱坡镇锦衣村白果庄村枣子坪村青大山村龙池槽村抗家岭村岩屋坪村祁家坪村冯家湾村范家坪村绿葱坡居委会七至九组绿葱坡镇范家坪村恩施州巴东县青树沟煤矿有限责任公司巴东县绿葱坡镇锦衣村白果庄村枣子坪村青大山村龙池槽村抗家岭村岩屋坪村祁家坪村冯家湾村范家坪村绿葱坡居委会七至九组绿葱坡镇范家坪村恩施州巴东县青树沟煤矿有限责任公司”

2、前向最大匹配逻辑

前向最大匹配法(FMM)首先定义MAX_LENGTH的大小,一般为词典中最长的单词长度,再将待分词字串从前往后(BMM为从后往前)扫描切分出MAX_LENGTH长度的子串,然后在词典中进行匹配,尽可能的选择与词典中最长单词匹配的词作为目标分词(“冯家湾村”优先匹配为“冯家湾村”而不是“冯家湾”、“村”),当词典中找不到匹配词时将子串最右边(BMM为最左边)一个字去掉,再进行下一次匹配,重复“匹配-切分”的步骤,直至将待切分字串完全切分。FMM工作流程如图所示,其中A为待切分字串,L为字数,M表示分词词典中最长词字数,F为分词结果集合。

后向最大匹配法(BMM)不赘述,双向最大匹配法(BM)字面意思就是两边都来一遍,然后比对FMM和BMM的分词结果词数,当词数不相同时,取分词数较少的结果。当分词数相同时,则进一步比对FMM和BMM的分词结果,若分词结果完全一致则返回任一个;若分词结果不完全一致则返回单字较少的结果。

3、双向最大匹配分词

python目前并没有现成的包可以直接调用实现双向最大匹配分词法,基本都是自己照着上面的规则逻辑来写方法,网上已经有很多了,可以直接引用或改进。


#--双向最大分词法---------------------------     
#加载自定义地址元素词库        
df_dzysk=pd.read_excel('filepath/词库.xlsx', sheet_name='地址元素库')
user_dict=df_dzysk['地址要素词'].tolist()
max_length=max(df_dzysk['地址要素词'].apply(lambda x:len(x)))

#正向最大匹配
def FMM(user_dict, sentence):
    result = []
    start = 0
    while start != len(sentence):
        index = start + max_length
        if index > len(sentence):
            index = len(sentence)
        for i in range(index, start, -1):
            if (sentence[start:i] in user_dict) or (len(sentence[start:i]) == 1):
                result.append(sentence[start:i])
                break   
        start = i
    return result


#反向最大匹配
def BMM(user_dict, sentence):
    result = []
    start = len(sentence)
    while start != 0:
        index = start - max_length
        if index < 0:
            index = 0
        for i in range(index, start):
            if (sentence[i:start] in user_dict) or (len(sentence[i:start]) == 1):
                result.append(sentence[i:start])
                break
        start = i
    return result

#双向最大匹配
def bi_maximum_match(user_dict, sentence):
    FMM_ = FMM(user_dict, sentence)
    BMM_ = BMM(user_dict, sentence)
    if (len(FMM_)) != (len(BMM_)):
        if (len(FMM_)) <= (len(BMM_)):
            return FMM_
        else:
            return BMM_
    else:
        FMM_single = 0
        BMM_single = 0
        for i in range(len(FMM_)):
            if len(FMM_[i]) == 1:
                FMM_single += 1
        for j in range(len(BMM_)):
            if len(FMM_[i]) == 1:
                BMM_single += 1 
        if FMM_single > BMM_single:
            return BMM_
        else:
            return FMM_


uni_tdxx['地址切词_cms']=uni_tdxx['停电地址_cms'].apply(lambda x: bi_maximum_match(user_dict, x))
uni_tdxx['地址切词_pms']=uni_tdxx['停电地址_pms'].apply(lambda x: bi_maximum_match(user_dict, x))

分词结果:

['湖北省',
 '恩施州',
 '巴东县', 
 '绿葱坡镇',
 '锦衣村',
 '白果庄村',
 '枣子坪村',
 '青大山村',
 '龙池槽村',
 '抗家岭村',
 '岩屋坪村',
 '祁家坪村',
 '冯家湾村',
 '范家坪村',
 '绿葱坡居委会七至九组',
 '绿葱坡镇', 
 '范家坪村',
 '恩施州',
 '巴东县',
 '青树沟煤矿有限责任公司',
 '巴东县', 
 '绿葱坡镇',
 '锦衣村',
 '白果庄村',
 '枣子坪村',
 '青大山村',
 '龙池槽村',
 '抗家岭村',
 '岩屋坪村',
 '祁家坪村',
 '冯家湾村',
 '范家坪村',
 '绿葱坡居委会七至九组',
 '绿葱坡镇',
 '范家坪村',
 '恩施州',
 '巴东县',
 '青树沟煤矿有限责任公司']

将表格中两个系统的“停电范围”字段经过一系列文本处理后形成“停电地址”字段、采用双向最大匹配分词法处理后形成“地址切词”字段,统计词频可做词云,也可以对比两个系统中维护的同一件停电事件其停电信息是否存在不一致问题,这里就不继续发挥了。

总之,自定义地址要素词库维护的越完善,分词效果越好~

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值