Pythonで、ユーザーが入力に使用するトークナイザーを指定できるカスタムトークナイザーモジュールを設計したいと思います。たとえば、次の入力について考えてみます。
Q:これを達成するための良い方法は何ですか? A:よくわかりません。 Pythonを使うと思います。
NLTKの文のトークン化 、sent_tokenize()
をオプションとして提供できるようにしたいのです。これは、多くの状況でうまく機能し、車輪の再発明をしたくないからです。これに加えて、よりきめ細かいトークン化ビルダー(ルールエンジンのラインに沿ったもの)も提供したいと思います。説明させてください:
いくつかのトークナイザーを提供するとします。
SENTENCE # Tokenizes the given input by using sent_tokenize()
Word # Tokenizes the given input by using Word_tokenize()
QA # Tokenizes using a custom regular expression. E.g., Q: (.*?) A: (.*?)
次のようなルールをサポートしたいと思います。
したがって、期待される出力は次のとおりです。
1。 QA->文
[
('QUESTION',
('SENTENCE', 'What is a good way to achieve this?'),
),
('ANSWER',
('SENTENCE', 'I am not so sure', 'I think I will use Python')
)
]
2。 QA
[
('QUESTION', 'What is a good way to achieve this?'),
('ANSWER', 'I am not so sure. I think I will use Python')
]
これを効率的に実現するための優れた設計は何ですか?
Pythonではトークン化が簡単なので、モジュールが何を提供する予定なのか疑問に思います。つまり、ソフトウェアを開始するとき、優れた設計は、最初にデータ構造を検討するのではなく、使用シナリオを検討することから生まれます。
期待される出力の例は少し紛らわしいです。トークナイザーが左側に名前を返し、右側にトークンのリストを返すようにしたいと思います。同様の結果を達成するために少し遊んだのですが、扱いやすくするためにリストを使用しました。
import re
# some tokenizers
def tokzr_Word(txt): return ('Word', re.findall(r'(?ms)\W*(\w+)', txt)) # split words
def tokzr_SENT(txt): return ('SENTENCE', re.findall(r'(?ms)\s*(.*?(?:\.|\?|!))', txt)) # split sentences
def tokzr_QA(txt):
l_qa = []
for m in re.finditer(r'(?ms)^[\s#\-\*]*(?:Q|Question)\s*:\s*(?P<QUESTION>\S.*?\?)[\s#\-\*]+(?:A|Answer)\s*:\s*(?P<ANSWER>\S.*?)$', txt): # split (Q, A) sequences
for k in ['QUESTION', 'ANSWER']:
l_qa.append(m.groupdict()[k])
return ('QA', l_qa)
def tokzr_QA_non_canonical(txt): # Note: not supported by tokenize_recursively() as not canonical.
l_qa = []
for m in re.finditer(r'(?ms)^[\s#\-\*]*(?:Q|Question)\s*:\s*(?P<QUESTION>\S.*?\?)[\s#\-\*]+(?:A|Answer)\s*:\s*(?P<ANSWER>\S.*?)$', txt): # split (Q, A) sequences
for k in ['QUESTION', 'ANSWER']:
l_qa.append((k, m.groupdict()[k]))
return l_qa
dict_tokzr = { # control string: tokenizer function
'Word' : tokzr_Word,
'SENTENCE': tokzr_SENT,
'QA' : tokzr_QA,
}
# the core function
def tokenize_recursively(l_tokzr, work_on, lev=0):
if isinstance(work_on, basestring):
ctrl, work_on = dict_tokzr[l_tokzr[0]](work_on) # tokenize
else:
ctrl, work_on = work_on[0], work_on[1:] # get right part
ret = [ctrl]
if len(l_tokzr) == 1:
ret.append(work_on) # add right part
else:
for wo in work_on: # dive into tree
t = tokenize_recursively(l_tokzr[1:], wo, lev + 1)
ret.append(t)
return ret
# just for printing
def nestedListLines(aList, ind=' ', d=0):
""" Returns multi-line string representation of \param aList. Use \param ind to indent per level. """
sRet = '\n' + d * ind + '['
nested = 0
for i, e in enumerate(aList):
if i:
sRet += ', '
if type(e) == type(aList):
sRet += nestedListLines(e, ind, d + 1)
nested = 1
else:
sRet += '\n' + (d + 1) * ind + repr(e) if nested else repr(e)
sRet += '\n' + d * ind + ']' if nested else ']'
return sRet
# main()
inp1 = """
* Question: I want try something. Should I?
* Answer : I'd assume so. Give it a try.
"""
inp2 = inp1 + 'Q: What is a good way to achieve this? A: I am not so sure. I think I will use Python.'
print repr(tokzr_Word(inp1))
print repr(tokzr_SENT(inp1))
print repr(tokzr_QA(inp1))
print repr(tokzr_QA_non_canonical(inp1)) # Really this way?
print
for ctrl, inp in [ # example control sequences
('SENTENCE-Word', inp1),
('QA-SENTENCE', inp2)
]:
res = tokenize_recursively(ctrl.split('-'), inp)
print nestedListLines(res)
ところで。 Python/Lib/tokenize.py(Pythonコード自体))は、物事の処理方法を一見の価値があるかもしれません。
私が質問を正しく理解しているなら、あなたは車輪の再発明をすべきだと思います。必要なさまざまなタイプのトークン化用にステートマシンを実装し、トークンを保存するためにpython辞書を使用します。
http://en.wikipedia.org/wiki/Finite-state_machine
スペースを含む文を取得して単語を出力するステートマシンの例。もちろん、この特定の例をより簡単な方法で実行できます。しかし、一般にステートマシンを使用すると、線形時間パフォーマンスが得られ、簡単にコスト計算できます。
while 1:
if state == "start":
if i == len(text):
state = "end"
Elif text[i] == " ":
state = "new Word"
i = i - 1
else:
Word.append(text[i])
Elif state == "new Word":
print(''.join(Word))
del Word[:]
state = "start"
Elif state == "end":
print(''.join(Word))
break
i = i + 1
http://docs.python.org/2/library/collections.html#collections.Counter
次に、たとえば、このpythonデータ構造を使用してトークンを保存できます。これは、ニーズに完全に適していると思います。
これが助けになったといいのですが。