基於分布式的短文本命題實體識別之----人名識別(python實現)


目前對中文分詞精度影響最大的主要是兩方面:未登錄詞的識別歧義切分

據統計:未登錄詞中中文姓人名在文本中一般只占2%左右,但這其中高達50%以上的人名會產生切分錯誤。在所有的分詞錯誤中,與人名有關的錯誤占到了將近90%,這中國人名都是根據人的想法起的名字,有很大的隨意性,並且數量巨大,規律也不盡相同。

這里寫圖片描述


1.理論簡介

命名實體識別(Named Entities Recognition, NER)是自然語言處理(Natural LanguageProcessing, NLP)的一個基礎任務。其目的是識別語料中人名、地名、組織機構名等命名實體。由於這些命名實體數量不斷增加,通常不可能在詞典中窮盡列出,且其構成方法具有各自的一些規律性,因而,通常把對這些詞的識別從詞匯形態處理(如漢語切分)任務中獨立處理,稱為命名實體識別。命名實體識別技術是信息抽取、信息檢索、機器翻譯、問答系統等多種自然語言處理技術必不可少的組成部分。

命名實體是命名實體識別的研究主體,一般包括3大類(實體類、時間類和數字類)和7小類(人名、地名、機構名、時間、日期、貨幣和百分比)命名實體。評判一個命名實體是否被正確識別包括兩個方面:實體的邊界是否正確;實體的類型是否標注正確。主要錯誤類型包括文本正確,類型可能錯誤;反之,文本邊界錯誤,而其包含的主要實體詞和詞類標記可能正確。
命名實體識別的主要技術方法分為:基於規則和詞典的方法、基於統計的方法、二者混合的方法等

1.1基於規則和詞典的方法

基於規則的方法多采用語言學專家手工構造規則模板,選用特征包括統計信息、標點符號、關鍵字、指示詞和方向詞、位置詞(如尾字)、中心詞等方法,以模式和字符串相匹配為主要手段,這類系統大多依賴於知識庫和詞典的建立。基於規則和詞典的方法是命名實體識別中最早使用的方法,一般而言,當提取的規則能比較精確地反映語言現象時,基於規則的方法性能要優於基於統計的方法。但是這些規則往往依賴於具體語言、領域和文本風格,編制過程耗時且難以涵蓋所有的語言現象,特別容易產生錯誤,系統可移植性不好,對於不同的系統需要語言學專家重新書寫規則。基於規則的方法的另外一個缺點是代價太大,存在系統建設周期長、移植性差而且需要建立不同領域知識庫作為輔助以提高系統識別能力等問題。

1.2基於統計的方法

基於統計機器學習的方法主要包括:隱馬爾可夫模型(HiddenMarkovMode,HMM)、最大熵(MaxmiumEntropy,ME)、支持向量機(Support VectorMachine,SVM)、條件隨機場( ConditionalRandom Fields,CRF)等。

在這4種學習方法中,最大熵模型結構緊湊,具有較好的通用性,主要缺點是訓練時間復雜性非常高,有時甚至導致訓練代價難以承受,另外由於需要明確的歸一化計算,導致開銷比較大。而條件隨機場為命名實體識別提供了一個特征靈活、全局最優的標注框架,但同時存在收斂速度慢、訓練時間長的問題。一般說來,最大熵和支持向量機在正確率上要比隱馬爾可夫模型高一些,但是隱馬爾可夫模型在訓練和識別時的速度要快一些,主要是由於在利用Viterbi算法求解命名實體類別序列的效率較高。隱馬爾可夫模型更適用於一些對實時性有要求以及像信息檢索這樣需要處理大量文本的應用,如短文本命名實體識別。

基於統計的方法對特征選取的要求較高,需要從文本中選擇對該項任務有影響的各種特征,並將這些特征加入到特征向量中。依據特定命名實體識別所面臨的主要困難和所表現出的特性,考慮選擇能有效反映該類實體特性的特征集合。主要做法是通過對訓練語料所包含的語言信息進行統計和分析,從訓練語料中挖掘出特征。有關特征可以分為具體的單詞特征、上下文特征、詞典及詞性特征、停用詞特征、核心詞特征以及語義特征等。
基於統計的方法對語料庫的依賴也比較大,而可以用來建設和評估命名實體識別系統的大規模通用語料庫又比較少。

1.3混合方法

自然語言處理並不完全是一個隨機過程,單獨使用基於統計的方法使狀態搜索空間非常龐大,必須借助規則知識提前進行過濾修剪處理。目前幾乎沒有單純使用統計模型而不使用規則知識的命名實體識別系統,在很多情況下是使用混合方法:

3.1 統計學習方法之間或內部層疊融合。
3.2 規則、詞典和機器學習方法之間的融合,其核心是融合方法技術。
在基於統計的學習方法中引入部分規則,將機器學習和人工知識結合起來。
3.3 將各類模型、算法結合起來,將前一級模型的結果作為下一級的訓練數據,並用這些訓練數據對模型進行訓練,得到下一級模型。
這種方法在具體實現過程中需要考慮怎樣高效地將兩種方法結合起來,采用什么樣的融合技術。由於命名實體識別在很大程度上依賴於分類技術,在分類方面可以采用的融合技術主要包括如Voting, XVoting,GradingVa,l Grading等。

這里寫圖片描述


2 jieba框架以及算法簡介jieba介紹

jieba分詞系統,主要實現三個模塊,

分詞
詞性標注
關鍵詞抽取

以下算法介紹,均參考jieba介紹

2.1分詞

jieba基於前綴詞典和動態規划方法實現分詞,

2.2詞性標注

jieba分詞是如何對未登錄詞進行分詞呢?

基於漢字成詞能力的HMM模型識別未登錄詞。利用HMM模型進行分詞,主要是將分詞問題視為一個序列標注(sequence labeling)問題,其中,句子為觀測序列,分詞結果為狀態序列。首先通過語料訓練出HMM相關的模型,然后利用Viterbi算法進行求解,最終得到最優的狀態序列,然后再根據狀態序列,輸出分詞結果。

e.g.ICTCLAS中的HMM人名識別

1.以“王菲”為例,粗分結果是“始##始, 王, 菲, 末##末,”,很明顯,粗分過程並不能識別正確的人名,因為“王菲”這個詞並不存在於一元語言模型詞典中。
觀測序列

觀測序列是我們能看到的顯狀態序列,這個例子里是“始##始, 王, 菲, 末##末,”。
之后通過查表,初分等以下幾個過程
隱狀態

初始概率

轉移概率

發射概率

求解HMM
通過維特比算法找出最可能的標注序列了。最終標注結果:

始##始, 王, 菲, 末##末,
100-* 1-B 4-E 101-*

模式匹配

對於BE這個標注序列,如何知道里面是否含有人名,含有的是哪種人名呢?這需要通過模式匹配來發現,模式串有:

這里寫圖片描述

這里寫圖片描述

我們的BE匹配到了BE: 姓+單名這條規則,所以是一個單名人名,最終識別出結果:

王菲


3 單機版實現

這里寫圖片描述

本文基於大數據的開源組件實現了兩個姓名提取腳本,
一個單機版,一個spark版本。 主要使用到了python3和jieba分詞庫,以及部分人工積累的停用詞庫。

利用hdfs清洗后的結構化數據,在hive中創建外表語句:

create external table name_analysis ( name string, idcard string, src string, ) PARTITIONED BY (source string) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

調用腳本

#!/bin/bash
echo "start running the Abstract Name analysis working ......"
START=$(date +%s);
date=`date -d "$1" "+%Y%m%d"`

echo "running date is $date"

##################################################
##你的數據外表
echo "-------------------start write content to txt-------------------"

hive -f hive_temp.hql -hivevar date=$date>>$date.txt
echo "-------------------content to $date.txt done-----------------------"


###############################################
###abstract name to txt file
echo "------------------start abstract name------------------------------"
python3 abstractNameToFile.py $date.txt
echo "------------------abstract name done-------------------------------"

##############################################
echo "-----------------start put file to hdfs/hive---------------------"
NAME_CONTENT=name_$date.txt

hdfs dfs -put $NAME_CONTENT /HDFS/name_analysis/content


echo "----------------put file to hdfs/hive done ----------------------------"


################################################
END=$(date +%s);
echo 'running time is: '
echo $((END - START))| awk '{print int($1/3600)":"int($1%3600/60)":"int($1%3600%60)}'

python3分詞腳本


# -*- coding: utf-8 -*-
import jieba
import jieba.posseg as pseg
import datetime
import sys


#詞性標注,nr為人名
def getFirstName(messageContent):
    words = pseg.cut(messageContent)
    for word, flag in words:
        if flag == 'nr'and len(word)>1:#單字姓名去掉
            return word

    return False

def getAllName(messageContent):
    words = pseg.cut(messageContent)
    names = []
    for word, flag in words:
        print('%s,%s' %(word,flag))
        if flag == 'nr':#人名詞性為nr
            names.append(word)
    return names

#修改停用詞集合中所有詞性為名詞,大部分為名詞
def alterWordTagToX(list):
    for x in list:
        jieba.add_word(x, tag='n')

def LoadStopWord(StopWordFileName):
    StopWord_file = open(StopWordFileName, 'r', encoding='utf-8')
    StopWordList = []

    for line in StopWord_file.readlines():
        StopWordList.append(line.strip('\n'))

    set(StopWordList)
    StopWord_file.close()
    alterWordTagToX(StopWordList)

def main():
    #加載停用詞詞典文件
    LoadStopWord('stopword.txt')

    input_file_name = sys.argv[1]
    output_file_name = 'name_'+ input_file_name
    print(input_file_name)  
    print(output_file_name)
    begin = datetime.datetime.now()
    #單機並行分詞
    jieba.enable_parallel(8)
    input_file = open(input_file_name, 'r', encoding='utf-8')
    output_file = open(output_file_name, 'w')

    for line in input_file:
        temp = line.split('\t')
        if len(temp)!=4:
            continue
        name = getFirstName(temp[1])

        if name != False:
            #print(name)姓名作為一行中的一個字段,其他為你需要的字段
            time = str(temp[3]).strip('\n') 
            output_file.write(temp[0] + ','+ name + ','+ '\n')
        else:
            continue

    end = datetime.datetime.now()
    print((end - begin).seconds)

#單元測試代碼
    names = getAllName('我老公寶貝叫王寧,尊敬的王大力,CCHHKK旗艦店,尊敬的鐵路客服人員李天,馮達輝')
    print(names)
    print(getFirstName('尊敬的鐵路客服人員李天'))
    output_file.close()
    input_file.close()


if __name__ =='__main__':
    main()


這里寫圖片描述
停用詞文件舉例

這里寫圖片描述

人名提取的結果示例


4.spark分布式版本

4.1分布式環境搭建

4.1.1 spark環境搭建

4.1.2 分布式環境下,分詞庫的安裝

每個節點jieba庫的安裝,在一個節點配置好免密登錄后可使用如下腳本進行jieba庫的批量安裝

for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; mkdir /opt/python;";done
for((i=2;i<=xxx;i++));do scp /opt/jieba-0.38.zip root@host-${i}:/opt/python;done
for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; unzip /opt/python/jieba-0.38.zip;";done
for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; mv ~/jieba-0.38 /opt/python;";done
for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; cd /opt/python/jieba-0.38;python setup.py install";done

4.2 分布式分詞要點

4.2.1 如何保障每個節點都能加載停用詞:

spark有兩個技術可以保證:

1.全局變量Broadcast spark文檔
A broadcast variable that gets reused across tasks.
A broadcast variable created with SparkContext.broadcast(). Access its value through value.

class pyspark.Broadcast(sc=None, value=None, pickle_registry=None, path=None) A broadcast variable created with SparkContext.broadcast(). Access its value through value. Examples: >>> from pyspark.context import SparkContext >>> sc = SparkContext('local', 'test') >>> b = sc.broadcast([1, 2, 3, 4, 5]) >>> b.value [1, 2, 3, 4, 5] >>> sc.parallelize([0, 0]).flatMap(lambda x: b.value).collect() [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] >>> b.unpersist() >>> large_broadcast = sc.broadcast(range(10000))

2.sc.addFile(path)添加可分發的文件 spark文檔
addFile(path, recursive=False)
Add a file to be downloaded with this Spark job on every node. The path passed can be either a local file, a file in HDFS (or other Hadoop-supported filesystems), or an HTTP, HTTPS or FTP URI.

To access the file in Spark jobs, use L{SparkFiles.get(fileName)

4.2.2 使用spark-submit 提交姓名提取腳本

在命令行調用:(后面還可以根據自己的集群添加其他選項)

    spark-submit SparkAbstractName.py

基於python2的pyspark腳本,本來想統一成python3的但是集群是生存環境不好更改,只好用系統自帶的python2了,因為jieba庫是python2,3都兼容的,這一點向作者致敬。

# -*- coding: utf-8 -*-
from pyspark import SparkConf,SparkContext
from pyspark import SparkFiles
import jieba
import jieba.posseg as pseg
import datetime
import os
import sys
reload(sys)
sys.setdefaultencoding('utf-8')


#word tagging,nr for name
def getFirstName(messageContent):
    words = pseg.cut(messageContent)
    for word, flag in words:
        if flag == 'nr'and len(word)>1:#delete single name
            return word

    return False



#alter stopName's property to N
def alterWordTagToX(list):
    for x in list:
        jieba.add_word(x, tag='n')

#load local stopName
def LoadStopWord(StopWordFileName):
    with  open(StopWordFileName, 'r') as StopWord_file:
        StopWordList = []

        for line in StopWord_file.readlines():
            StopWordList.append(line.strip('\n'))

        set(StopWordList)
        alterWordTagToX(StopWordList)
        return StopWordList

def Abstractfunc(line):
    LoadStopWord(SparkFiles.get('stopName.txt'))
    name = getFirstName(line[3])
    if name != False:#對原始數據的重新排列
        return [line[1],name,'',line[2],line[0]]
    else:
        return [line[1],'0','',line[2],line[0]]


def main(sc):

    #print(LoadStopWord(SparkFiles.get("stopName.txt")))
    input_file = sc.textFile('''file:///name_analysis/test.txt''')

    begin = datetime.datetime.now()
    length =  input_file.map(lambda s:len(s)).reduce(lambda a,b:a+b)
    print(length)
    #加載,分割的原始數據
    content_list = input_file.map(lambda x: x.split(','))
    #獲取我需要的列
    row_content = content_list.map(lambda x:(x[8],x[9],.....))
    print(row_content.map(lambda s:len(s)).reduce(lambda a,b:a+b))
    #數據清洗,分詞
    list_content = row_content.map(lambda x:(list(x))).filter(lambda x:x[1]!='0')
    result_content = list_content.map(lambda line:(Abstractfunc(line))).filter(lambda x:x[1]!='0')
    print(list_content.map(lambda s:len(s)).reduce(lambda a,b:a+b))

    #獲取樣例數據
    test  = result_content.take(10)
    for x in test:
        print (x[1])
        print(type(x))

    ''' jieba.enable_parallel(8) input_file = open(input_file_name, 'r', encoding='utf-8') output_file = open(output_file_name, 'w') '''
    end = datetime.datetime.now()
    print((end - begin).seconds)

#unit test
''' ...... '''

if __name__ =='__main__':
    conf = SparkConf().setAppName("SparkAbstractName")
    sc = SparkContext(conf = conf)
    sc.setLogLevel("WARN")
    path = os.path.join(os.getcwd(), '''stopName.txt''')
    print(os.getcwd())
    print(path)
    sc.addFile(path)
    main(sc)
    sc.stop()

未完待續。。。

參考文獻

1.http://blog.csdn.net/lalalawxt/article/details/55804384
2.http://www.cnblogs.com/yuxc/archive/2012/01/11/2319631.html
3.臧勇真. 基於統計和規則的中文人名識別研究與實現[D]. 西南交通大學, 2013.
4.jieba介紹 http://www.cnblogs.com/zhbzz2007/p/6076246.html
5.spark文檔 http://spark.apache.org/docs/latest/api/python/pyspark.html
6.文本情感分析 https://www.ibm.com/developerworks/cn/cognitive/library/cc-1606-spark-seniment-analysis/index.html
7.ICTCLAS中的HMM人名識別
http://www.hankcs.com/nlp/segment/ictclas-the-hmm-name-recognition.html
8.實戰HMM-Viterbi角色標注中國人名識別
http://www.hankcs.com/nlp/chinese-name-recognition-in-actual-hmm-viterbi-role-labeling.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM