Skip to content

Commit b7c624c

Browse files
committed
new thai word segmentation by @korakot & thai2vec
- new thai word segmentation using maximal matching by @korakot - thai2vec by @cstorm125
1 parent c002378 commit b7c624c

File tree

3 files changed

+180
-118
lines changed

3 files changed

+180
-118
lines changed

pythainlp/tokenize/newmm.py

Lines changed: 114 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,127 @@
11
# -*- coding: utf-8 -*-
2-
from __future__ import absolute_import,unicode_literals
3-
'''
4-
โปรแกรม multi-cut
5-
ตัดคำภาษาไทยโดยใช้ Maximum Matching algorithm
6-
เดติดโค้ดต้นฉบับ คุณ Korakot Chaovavanich
7-
จาก https://www.facebook.com/groups/408004796247683/permalink/431283740586455/
8-
และ https://gist.github.com/korakot/fe26c65dc9eed467f4497f784a805716
2+
'''ตัวตัดคำภาษาไทยโดยใช้หลักการ maximal matching และ TCC
3+
พัฒนาโดยคุณ Korakot Chaovavanich
4+
Notebook : https://colab.research.google.com/notebook#fileId=1V1Z657_5eSWPo8rLfVRwA0A5E4vkg7SI
95
'''
10-
import six
11-
if six.PY2:
12-
from builtins import *
6+
from __future__ import absolute_import,unicode_literals
137
import re
14-
import copy
15-
from pythainlp.tools import file_trie
16-
from marisa_trie import Trie
178
from collections import defaultdict
18-
from pythainlp.tokenize import tcc
19-
class LatticeString(str):
20-
''' String subclass เพื่อเก็บวิธีตัดหลายๆ วิธี
21-
'''
22-
def __new__(cls, value, multi=None, in_dict=True):
23-
return str.__new__(cls, value)
9+
from heapq import heappush, heappop # for priority queue
10+
from marisa_trie import Trie
11+
from pythainlp.corpus.thaiword import get_data # ดึงข้อมูลรายการคำในภาษาไทย
2412

25-
def __init__(self, value, multi=None, in_dict=True):
26-
self.unique = True
27-
if multi:
28-
self.multi = list(multi)
29-
if len(self.multi) > 1:
30-
self.unique = False
31-
else:
32-
self.multi = [value]
33-
self.in_dict = in_dict # บอกว่าเป็นคำมีในดิกหรือเปล่า
34-
spat_eng = r'''(?x)
35-
([\d,\.]\#)+| # number
36-
([๑๒๓๔๕๖๗๘๙๐,\.]\#)+| # thai number
37-
([a-zA-Z]\#)+| # english
13+
14+
# ช่วยตัดพวกภาษาอังกฤษ เป็นต้น
15+
pat_eng = re.compile(r'''(?x)
16+
[-a-zA-Z]+| # english
17+
\d[\d,\.]*| # number
3818
[ \t]+| # space
3919
\r?\n # newline
40-
'''
41-
pat_eng = re.compile(spat_eng)
42-
43-
def multicut(text,data):
44-
''' ส่งคืน LatticeString คืนมาเป็นก้อนๆ
45-
'''
46-
words_at = defaultdict(list) # main data structure
47-
if data!="": # ถ้าหากกำหนดข้อมูลโดยใช้ dict ของตัวเอง
48-
i=0
49-
data2=copy.copy(data)
50-
while i<len(data2):
51-
data2[i]=tcc.tcc(data2[i],sep='#')
52-
if(data2[len(data2[i])-1]!="#"):
53-
data2[i]+="#"
54-
i+=1
55-
trie = Trie(data2)
56-
else:
57-
trie = file_trie(data="newmm")
58-
def serialize(p, p2): # helper function
59-
for w in words_at[p]:
60-
p_ = p + len(w)
61-
if p_== p2:
62-
yield w
63-
elif p_ < p2:
64-
for path in serialize(p_, p2):
65-
yield w+'/'+path
66-
q = {0}
67-
last_p = 0 # last position for yield
68-
while min(q) < len(text):
69-
p = min(q)
70-
q -= {p} # q.pop, but for set
20+
''')
21+
# TCC
22+
pat_tcc = """\
23+
เc็c
24+
เcctาะ
25+
เccีtยะ
26+
เccีtย(?=[เ-ไก-ฮ]|$)
27+
เccอะ
28+
เcc็c
29+
เcิc์c
30+
เcิtc
31+
เcีtยะ?
32+
เcืtอะ?
33+
เc[ิีุู]tย(?=[เ-ไก-ฮ]|$)
34+
เctา?ะ?
35+
cัtวะ
36+
c[ัื]tc[ุิะ]?
37+
c[ิุู]์
38+
c[ะ-ู]t
39+
c็
40+
ct[ะาำ]?
41+
แc็c
42+
แcc์
43+
แctะ
44+
แcc็c
45+
แccc์
46+
โctะ
47+
[เ-ไ]ct
48+
""".replace('c','[ก-ฮ]').replace('t', '[่-๋]?').split()
7149

72-
for w in trie.prefixes(text[p:]):
73-
words_at[p].append(w)
74-
q.add(p+len(w))
50+
def tcc(w):
51+
p = 0
52+
pat = re.compile("|".join(pat_tcc))
53+
while p<len(w):
54+
m = pat.match(w[p:])
55+
if m:
56+
n = m.span()[1]
57+
else:
58+
n = 1
59+
yield w[p:p+n]
60+
p += n
7561

76-
if len(q)==1:
77-
q0 = min(q)
78-
yield LatticeString(text[last_p:q0], serialize(last_p, q0))
79-
last_p = q0
62+
def tcc_pos(text):
63+
p_set = set()
64+
p = 0
65+
for w in tcc(text):
66+
p += len(w)
67+
p_set.add(p)
68+
return p_set
69+
def serialize(words_at, p, p2):
70+
# find path ทั้งหมด แบบ depth first
71+
for w in words_at[p]:
72+
p_ = p + len(w)
73+
if p_== p2:
74+
yield [w]
75+
elif p_ < p2:
76+
for path in serialize(words_at, p_, p2):
77+
yield [w]+path
78+
def onecut(text,data=['']):
79+
if(data!=['']):
80+
trie = Trie(data)
81+
else:
82+
trie = Trie(get_data())
83+
words_at = defaultdict(list) # main data structure
84+
allow_pos = tcc_pos(text) # ตำแหน่งที่ตัด ต้องตรงกับ tcc
85+
86+
q = [0] # min-heap queue
87+
last_p = 0 # last position for yield
88+
while q[0] < len(text):
89+
p = heappop(q)
8090

81-
# กรณี len(q) == 0 คือ ไม่มีใน dict
82-
elif len(q)==0:
83-
m = pat_eng.match(text[p:])
84-
if m!=None: # อังกฤษ, เลข, ว่าง
85-
i = p + m.span()[1]
86-
else: # skip น้อยที่สุด ที่เป็นไปได้
87-
for i in range(p, len(text)):
88-
ww = trie.prefixes(text[i:])
89-
m = pat_eng.match(text[i:])
90-
if ww or m:
91-
break
92-
else:
93-
i = len(text)
94-
w = text[p:i]
91+
for w in trie.prefixes(text[p:]):
92+
p_ = p + len(w)
93+
if p_ in allow_pos: # เลือกที่สอดคล้อง tcc
9594
words_at[p].append(w)
96-
yield LatticeString(w, in_dict=False)
97-
last_p = i
98-
q.add(i)
95+
if p_ not in q:
96+
heappush(q, p_)
9997

100-
def mmcut(text,data=''):
101-
res = []
102-
text=tcc.tcc(text,sep='#') # ให้นำข้อความมาผ่าน tcc
103-
if(text[len(text)-1]!='#'): # ถ้าตัวสุดท้ายของสตริงไม่เป็น #
104-
text+='#' # ให้เพิ่ม # เข้าไป
105-
for w in multicut(text,data=data):
106-
mm = min(w.multi, key=lambda x: x.count('/'))
107-
res.extend(mm.split('/'))
108-
return [x.replace('#','') for x in res if x!='#'] # เอา # ออก
109-
def combine(ww):
110-
if ww == []:
111-
yield ""
112-
else:
113-
w = ww[0]
114-
for tail in combine(ww[1:]):
115-
if w.unique:
116-
yield w+"|"+tail
117-
else:
118-
for m in w.multi:
119-
yield m.replace("/","|")+"|"+tail
98+
# กรณี length 1 คือ ไม่กำกวมแล้ว ส่งผลลัพธ์ก่อนนี้คืนได้
99+
if len(q)==1:
100+
paths = serialize(words_at, last_p, q[0])
101+
for w in min(paths, key=len):
102+
yield w
103+
last_p = q[0]
120104

121-
def listcut(text,data=''):
122-
'''
123-
ใช้ในการหา list ที่สามารถตัดคำได้ทั้งหมด
124-
'''
125-
ww = list(multicut(text,data))
126-
return list(combine(ww))
127-
if __name__ == "__main__":
128-
text='ผมรักคุณนะครับโอเคบ่พวกเราเป็นคนไทยรักภาษาไทยภาษาบ้านเกิด'
129-
mmcut(text)
130-
#print(listcut(text))
105+
# กรณี length 0 คือ ไม่มีใน dict
106+
if len(q)==0:
107+
m = pat_eng.match(text[p:])
108+
if m: # อังกฤษ, เลข, ว่าง
109+
i = p + m.end()
110+
else: # skip น้อยที่สุด ที่เป็นไปได้
111+
for i in range(p+1, len(text)):
112+
if i in allow_pos: # ใช้ tcc ด้วย
113+
ww = trie.prefixes(text[i:])
114+
m = pat_eng.match(text[i:])
115+
if ww or m:
116+
break
117+
else:
118+
i = len(text)
119+
w = text[p:i]
120+
words_at[p].append(w)
121+
yield w
122+
last_p = i
123+
heappush(q, i)
124+
125+
# ช่วยให้ไม่ต้องพิมพ์ยาวๆ
126+
def mmcut(text,data=['']):
127+
return list(onecut(text,data=data))

pythainlp/tools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def file_trie(data):
3333
from pythainlp.corpus.thaiword import get_data # ข้อมูลเก่า
3434
data=get_data()
3535
else:
36-
from pythainlp.corpus.newthaiword import get_data # ข้อมูลเก่า
36+
from pythainlp.corpus.newthaiword import get_data # ข้อมูลใหม่
3737
data=get_data()
3838
with open(path,'wb') as dill_file:
3939
dill.dump(marisa_trie.Trie(data),dill_file)

pythainlp/word_vector/__init__.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
Code by https://github.com/cstorm125/thai2vec/blob/master/notebooks/examples.ipynb
4+
'''
5+
from __future__ import absolute_import,unicode_literals
6+
import six
7+
import sys
8+
if six.PY2:
9+
print("Thai sentiment in pythainlp. Not support python 2.7")
10+
sys.exit(0)
11+
try:
12+
from gensim.models import KeyedVectors
13+
import numpy as np
14+
except ImportError:
15+
import pip
16+
pip.main(['install','gensim','numpy'])
17+
try:
18+
from gensim.models import KeyedVectors
19+
import numpy as np
20+
except ImportError:
21+
print("Error ! using 'pip install gensim numpy'")
22+
sys.exit(0)
23+
from pythainlp.tokenize import word_tokenize
24+
import os
25+
26+
def download():
27+
path = os.path.join(os.path.expanduser("~"), 'pythainlp-data')
28+
if not os.path.exists(path):
29+
os.makedirs(path)
30+
path = os.path.join(path, 'thai2vec.vec')
31+
if not os.path.exists(path):
32+
print("Download models...")
33+
from urllib import request
34+
request.urlretrieve("https://github.com/cstorm125/thai2vec/raw/master/data/thaiwiki/models/thai2vec.vec",path)
35+
print("OK.")
36+
return path
37+
def get_model():
38+
return KeyedVectors.load_word2vec_format(download(),binary=False)
39+
def most_similar_cosmul(positive,negative):
40+
'''
41+
การใช้งาน
42+
input list
43+
'''
44+
return get_model().most_similar_cosmul(positive=positive, negative=negative)
45+
def doesnt_match(listdata):
46+
return get_model().doesnt_match(listdata)
47+
def similarity(word1,word2):
48+
return get_model().similarity(word1,word2)
49+
def sentence_vectorizer(ss,dim=300,use_mean=False):
50+
s = word_tokenize(ss)
51+
vec = np.zeros((1,dim))
52+
for word in s:
53+
if word in get_model().wv.index2word:
54+
vec+= get_model().wv.word_vec(word)
55+
else: pass
56+
if use_mean: vec /= len(s)
57+
return(vec)
58+
def about():
59+
return '''
60+
thai2vec
61+
Language Modeling, Word2Vec and Text Classification in Thai Language. Created as part of pyThaiNLP.
62+
63+
Development : Charin Polpanumas
64+
GitHub : https://github.com/cstorm125/thai2vec
65+
'''

0 commit comments

Comments
 (0)