您當前的位置:首頁 > 收藏

最大熵模型-詞性標註的示例

作者:由 baiziyu 發表于 收藏時間:2019-11-28

有關於最大熵模型今天再放3個詞性標註示例。一個是公開實現的示例,這個示例之前忘記是從哪兒找到的了,最近搜尋各種最大熵演示,找到了出處,應該是煉數成金髮布出來的。其他的全是一個版本,所以就是要錯一起全錯。有關於公開示例程式碼的問題已經在前邊的文章baiziyu:最大熵模型應用於詞義消岐任務也說當前公開的演示程式碼的問題 中說明了,這裡不再贅述。有關於最大熵模型庫可以看這篇文章baiziyu:最大熵模型可用工具小結 ,其中HanLP作者實現了IIS和GIS,那篇文章裡沒有列IIS的連結。另外張樂的那個實在是用不下去,用起來特別不順手,又沒有文件,Python呼叫的部分還只能用Python2。沒有閒工夫跟程式碼較勁。所以還是推薦大家用NLTK吧。如果牽扯到實際應用,似乎Stanford的CoreNLP也有最大熵模型API。今天這篇文章之所以再次放上這個程式碼是因為我添加了註釋。訓練語料格式如下:

從/p 黃土/n ,/w 從/p 歷史/n ,/w

從/p 生活/vn 的/u 縫隙/n 間/f

生/v 出/v 的/u 不起眼/a 的/u 蒲公英/n

語料需要分詞並帶有詞性標註。

示例1 用Python實現最大熵模型IIS演算法,並應用於詞性標註任務。

出處應該是煉數成金。

# encoding: utf-8

“”“

最大熵詞形標註示例

”“”

from

collections

import

defaultdict

import

math

import

codecs

class

MaxEnt

object

):

def

__init__

self

):

# 儲存特徵函式fi(x,y)關於經驗分佈p(x,y)的期望值 Ep_(fi)

self

feats

=

defaultdict

int

# 文字經過特徵抽取後建成的訓練集每一項用一個事件表示(詞性標記,特徵1,特徵2,特徵3),

self

trainset

=

[]

# 詞性標記集,表示Y可取的值的集合,比如名詞、動詞、形容詞。。。

self

labels

=

set

()

def

generate_events

self

text

train_flag

=

False

):

“”“

text: 以空格分隔的文字

train_flag: train_flag=True時,要求text必須標註好詞語詞性

功能:對輸入的一句話,抽取出事件列表

”“”

# 定義事件序列,注意輸入的1行文字可以抽取出多個事件,事件序列稱為資料集或許更合適

event_li

=

[]

# 分詞,要求輸入的字串中詞語之間以空白分隔,對於訓練集在詞語後還要加詞性標記,格式為word/pos

word_li

=

text

split

()

# 分離詞語和詞性

if

train_flag

# 對於含有詞性標記的詞語序列,分離詞語和詞性:[(word1,pos1),(word2,pos2),。。。]

word_li

=

tuple

w

split

u

‘/’

))

for

w

in

word_li

if

len

w

split

u

‘/’

))

==

2

else

# 對於沒有詞性標記的詞語序列,新增詞性“x_pos”:[(word1,‘x_pos’),(word2,‘x_pos’),。。。]

word_li

=

[(

w

u

‘x_pos’

for

w

in

word_li

# 為詞語序列新增頭元素和尾元素,便於後續抽取事件

word_li

=

[(

u

‘pre1’

u

‘pre1_pos’

)]

+

word_li

+

[(

u

‘pro1’

u

‘pro1_pos’

)]

# 遍歷中心詞抽取1個事件,每個事件由1個詞性標記和多個特徵項構成

for

i

in

range

1

len

word_li

-

1

):

# 特徵函式1 中心詞

fea_1

=

word_li

i

][

0

# 特徵函式2 前一個詞

fea_2

=

word_li

i

-

1

][

0

# 特徵函式3 後一個詞

fea_3

=

word_li

i

+

1

][

0

# 詞性y

y

=

word_li

i

][

1

# 構建1個事件,注意1個事件由3個特徵項構成,同一事件中的3個特徵項共享1個輸出標記y

# 因此1個事件對應3個特徵函式或者叫做聯合特徵:f1(fea_1,y),f2(fea_2,y),f3(fea_3,y)

# 注意這裡並沒有區分3個特徵的先後順序

fields

=

y

fea_3

fea_2

fea_1

# 將事件新增到事件序列

event_li

append

fields

# 返回事件序列

return

event_li

def

load_data

self

file

):

“”“

file: 訓練檔案路徑和檔名

功能:讀取文字資料構建訓練集

”“”

with

codecs

open

‘。。/data/199801。txt’

‘rb’

‘utf8’

‘ignore’

as

infile

for

line_ser

line

in

enumerate

infile

):

if

line_ser

>=

100

break

line

=

line

strip

()

if

line

# 抽取事件序列

events_li

=

self

generate_events

line

train_flag

=

True

# 遍歷每個事件,對每個事件構建3個特徵函式或者叫做聯合特徵fi(x,y)

for

event

in

events_li

# 第1列是輸出詞性標記y

label

=

event

0

# 更新詞性標記

self

labels

add

label

# 對聯合特徵(詞性標記,特徵)計數,以便後續計算P(X,Y)的經驗分佈

# 注意一個聯合特徵為(y,x)的組合,x表示一個特徵值,並且丟失掉了x的位置資訊

for

f

in

set

event

1

:]):

self

feats

[(

label

f

)]

+=

1

# 將事件新增到訓練集

self

trainset

append

event

def

_initparams

self

):

“”“

功能:根據load_data方法載入進來的事件集(訓練集)計算Ep_(fi),並且給聯合特徵(y,x)編號

”“”

# 訓練集中抽取的事件總數

self

N

=

len

self

trainset

# 1個事件對應的特徵數量,取值為遍歷訓練集中每個事件抽取出的最大特徵數量

self

M

=

max

([

len

record

-

1

for

record

in

self

trainset

])

# 計算每一種特徵函式fi(x,y)關於經驗分佈P_(X,Y)的期望值,指的是《統計學習方法》P82頁最下邊的式子

self

ep_

=

0。0

*

len

self

feats

# 計算Ep_(fi),並且用self。feats儲存聯合特徵的索引

for

i

union_feature

in

enumerate

self

feats

):

# 第i個特徵函式的經驗值=聯合特徵(y,x)出現次數/訓練集中事件總數。

self

ep_

i

=

float

self

feats

union_feature

])

/

float

self

N

# 記錄特徵函式索引,經過這一步後self。feats中記錄的是特徵函式的索引值,以便後續預測時讀取特徵函式對應的權值

self

feats

union_feature

=

i

# 權重序列初始化為全0,每一個特徵函式都對應一個權重值,就是所求的係數

self

w

=

0。0

*

len

self

feats

self

lastw

=

self

w

def

Ep

self

):

“”“

計算Ep(fi) = sum (x,y) [p_(x)*p(y|X)fi(x,y)] 即《統計學習方法》P83頁最上邊的式子

此過程涉及呼叫模型預測方法self。calprob,預測輸出p(y|X)

經驗分佈p_(x)剛好等於 1/訓練集樣本數,實際它的公式應該為p_(x)=v(X=x)/N

”“”

# 期望值的個數等於特徵函式的個數

ep

=

0。0

*

len

self

feats

# 遍歷每個事件

for

record

in

self

trainset

# 分離出特徵集

features

=

record

1

:]

# 計算特徵集的預測輸出P(y|X)

prob_li

=

self

calprob

features

# 遍歷每一個特徵值

for

x

in

features

# 遍歷每個標記和其對應的機率值

for

prob

y

in

prob_li

# 由標記和1個特徵構建出1個特徵函式fi(y,x)

if

y

x

in

self

feats

# 讀取出特徵函式的索引值

idx

=

self

feats

[(

y

x

)]

# p_(x)的經驗分佈計算 p(x)=1/N

ep

idx

+=

prob

*

1。0

/

self

N

return

ep

def

_convergence

self

lastw

w

):

“”“

lastw:上一次迭代的係數向量

w: 本次迭代的係數向量

返回:True表示已經收斂;False表示未收斂

”“”

for

w1

w2

in

zip

lastw

w

):

if

abs

w1

-

w2

>=

0。01

return

False

return

True

# 訓練過程

def

train

self

max_iter

=

1000

):

“”“

最大熵分詞模型訓練過程

:param max_iter: 模型訓練的最大迭代次數

:return:無

”“”

# 初始化模型引數

self

_initparams

()

# 開始迭代

for

i

in

range

max_iter

):

print

‘iter

%d

。。。’

%

i

+

1

))

# 計算特徵函式關於模型P(Y|X)和經驗分佈P(X)的期望值序列,每個特徵函式對應一個值

ep

=

self

Ep

()

# 儲存當前的權值向量

self

lastw

=

self

w

[:]

# 計算delta並更新權值向量

for

i

w

in

enumerate

self

w

):

# 計算delta

delta

=

1。0

/

self

M

*

math

log

self

ep_

i

/

ep

i

])

# 更新權值

self

w

i

+=

delta

# 檢查是否收斂

if

self

_convergence

self

lastw

self

w

):

break

def

probwgt

self

features

label

):

“”“

features: 特徵集

label: 候選標記

返回: P(y|X)的機率值

功能:給定(X,y)計算條件機率P(y|X)

”“”

wgt

=

0。0

# 遍歷每個特徵f

for

f

in

features

# 如果訓練集中見過特徵函式(y,f)則讀出權重

if

label

f

in

self

feats

# 累加權重

wgt

+=

self

w

self

feats

[(

label

f

)]]

# 取exp(權重和)作為預測值,這裡沒有進行歸一化

return

math

exp

wgt

# 最大熵模型的預測函式P(y|x),輸入為1個事件中的特徵x,輸出為標記集中每種標記對應的歸一化的機率P(y1|x),P(y2|x),。。。

# 返回[(p1, y1),(p2,y2),。。。]

def

calprob

self

features

):

“”“

features: 特徵集

返回:預測為標記集self。labels中每種標記的機率

功能:給定特徵集X,輸出預測為標記集中每種標記的機率P(y|X)

”“”

# 計算標記集中每種標記的條件機率

wgts

=

[(

self

probwgt

features

l

),

l

for

l

in

self

labels

# 使每個機率值相加後等於1

Z

=

sum

([

w

for

w

l

in

wgts

])

prob

=

[(

w

/

Z

l

for

w

l

in

wgts

return

prob

def

predict

self

text

):

“”“

text: 輸入的經過分詞但未經詞性標註的文字

返回:詞性標註後的文字

功能:對經過分詞的文字進行詞性標註

”“”

word_li

=

[]

events_li

=

self

generate_events

text

for

features

in

events_li

prob

=

self

calprob

features

prob

sort

reverse

=

True

word_li

append

u

%s

/

%s

%

features

1

],

prob

0

]))

return

word_li

if

__name__

==

“__main__”

maxent

=

MaxEnt

()

maxent

load_data

“。。/data/199801。txt”

maxent

train

50

word_li

=

maxent

predict

“中國 政府 將 繼續 堅持 奉行 獨立自主 的 和平 外交 政策 。”

print

word_li

示例2 NLTK最大熵模型庫實現詞性標註

# coding:utf-8

“”“

NLTK的最大熵模型實現詞性標註

”“”

import nltk

import codecs

# 事件生成器,1個分詞後的句子可以產生多個事件

def generate_events(word_li):

events = []

# 為詞語序列新增頭元素和尾元素,便於後續抽取事件

word_li = [(u‘pre1’, u‘pre1_pos’)] + word_li + [(u‘pro1’, u‘pro1_pos’)]

# 每個中心詞抽取1個event,每個event由1個詞性標記和多個特徵項構成

for i in range(1, len(word_li) - 1):

# 定義特徵詞典

features_dict = dict()

# 特徵函式a 中心詞

features_dict[‘fea_1’] = word_li[i][0]

# 特徵函式b 前一個詞

features_dict[‘fea_2’] = fea_2 = word_li[i - 1][0]

# 特徵函式d 下一個詞

features_dict[‘fea_4’] = word_li[i + 1][0]

# 標記

label = word_li[i][1]

# 新增一個事件

events。append((features_dict, label))

return events

# 載入資料,生成事件集,返回

def load_data(file_name):

data_set = []

with codecs。open(file_name, ‘rb’, ‘utf8’, ‘ignore’) as infile:

for line_ser, line in enumerate(infile):

if line_ser >= 100:

break

line = line。strip()

if line:

word_li = line。split()

word_li = [tuple(w。split(u‘/’)) for w in word_li if len(w。split(u‘/’)) == 2]

# 生成事件並更新到data_set

data_set。extend(generate_events(word_li))

print(“抽取出 %d 個事件” % len(data_set))

return data_set

# 標註詞性

def pos_tag(classifier, line):

new_word_li = []

word_li = line。split()

word_li = [(w, u‘x_pos’) for w in word_li]

events_li = generate_events(word_li)

for i, (features, label) in enumerate(events_li):

predict_pos = classifier。classify(features)

new_word_li。append(word_li[i][0]+“/”+predict_pos)

return new_word_li

if __name__ == “__main__”:

# 抽取特徵構建訓練和測試集

data_set = load_data(‘data/199801。txt’)

train_size = int(len(data_set)*0。8)

test_size = int(len(data_set)*0。2)

train_set = data_set[:train_size]

test_set = data_set[-test_size:]

print(“訓練集事件數=”, len(train_set))

print(“測試集事件數=”, len(test_set))

# IIS學習演算法的最大熵模型

classifier_iis = nltk。classify。maxent。MaxentClassifier。train(train_set, trace=2, algorithm=‘iis’, max_iter=10)

print(“IIS模型準確率= ”, nltk。classify。accuracy(classifier_iis, test_set))

print(“IIS模型測試:”)

print(pos_tag(classifier_iis, “中國 政府 將 繼續 堅持 奉行 獨立自主 的 和平 外交 政策 。”))

# GIS學習演算法的最大熵模型

classifier_gis = nltk。classify。maxent。MaxentClassifier。train(train_set, trace=2, algorithm=‘gis’, max_iter=10)

print(“GIS模型準確率= ”, nltk。classify。accuracy(classifier_gis, test_set))

print(“GIS模型測試:”)

print(pos_tag(classifier_gis, “中國 政府 將 繼續 堅持 奉行 獨立自主 的 和平 外交 政策 。”))

目前發現的最大熵模型的小缺陷是測試例項抽取出的所有聯合特徵如果在訓練集中都沒有的話,那麼模型一點兒辦法都沒有了,沒法預測了。NLTK的最大熵庫是目前看到的最適合實驗的庫,裡邊還有一些功能,比如抽取出最重要的聯合特徵等等,這些對於除錯分析模型都是非常重要的。所以,我很期待用最大熵做出的“的,地,得”糾正系統。

另外,有關於多人會話文字上的問答匹配的方法也該做總結了,這個實驗性的內容可以收尾了。主要使用了3種方法,分別是被神話的預訓練模型Bert,基於它判別兩個句子是否構成上下文關係;第2個方法是基於公共詞比例判別;第3種基於共現詞計算相關度判別。第1種效果不敢恭維,與所有預訓練模型一樣,絕沒有通用領域的模型。第2種跟第3種,效果差不了太多,準確性能達到80%,但是測試集太小了,這個精度是要打問號的。本來想把基於最大熵的問答匹配也做一下,但是看來時間不允許了,今天又讓加文字分類類目呢,總算是提起資料了,有資料也是藏著掖著,導資料的以為我做一套資料,他找個模型就能用了,做夢去吧,我還是那句話,如果自然語言處理的任務這麼簡單,就不需要單設一個自然語言處理的崗位。另外,今天跟已經找到工作的一個學生聊天,看來現在還是有一部分公司沒有去用神經網路,所以如果關注專欄的小夥伴有在校生,可得注意別對機器學習方法一竅不通,特別是隱馬、最大熵、條件隨機場。對於文字處理的基本語言素養,那是必須的,沒有也做不出東西來。另外,可以詳細閱讀NLTK的書,雖然是歐洲人寫的書,處理的是英文,但是能用作Python實踐的教材(指的是上課的正式教材),恐怕也就這本不錯了,特別是每章後的題目,有些題目算作是畢業設計我看也不為過。我記得之前的文章裡應該是寫過,看到過離職的留下來的演算法,那裡邊很多實現都能在NLTK裡找到影子。當然,關於文字校對的內容我還是會繼續的,請大家期待。

標簽: self  li  word  詞性  特徵函式