spaCy/spacy/lang/ja/__init__.py

119 lines
3.6 KiB
Python
Raw Normal View History

2017-05-03 07:56:21 +03:00
# encoding: utf8
from __future__ import unicode_literals, print_function
import re
from collections import namedtuple
from .stop_words import STOP_WORDS
from .tag_map import TAG_MAP
from ...attrs import LANG
from ...language import Language
from ...tokens import Doc, Token
from ...compat import copy_reg
from ...util import DummyTokenizer
2019-02-08 16:14:49 +03:00
ShortUnitWord = namedtuple("ShortUnitWord", ["surface", "lemma", "pos"])
2019-02-08 16:14:49 +03:00
# TODO: Is this the right place for this?
Token.set_extension("mecab_tag", default=None)
def try_mecab_import():
"""Mecab is required for Japanese support, so check for it.
It it's not available blow up and explain how to fix it."""
try:
import MeCab
return MeCab
except ImportError:
raise ImportError(
"Japanese support requires MeCab: "
"https://github.com/SamuraiT/mecab-python3"
)
def resolve_pos(token):
"""If necessary, add a field to the POS tag for UD mapping.
Under Universal Dependencies, sometimes the same Unidic POS tag can
be mapped differently depending on the literal token or its context
in the sentence. This function adds information to the POS tag to
resolve ambiguous mappings.
"""
2019-02-08 16:14:49 +03:00
# TODO: This is a first take. The rules here are crude approximations.
# For many of these, full dependencies are needed to properly resolve
# PoS mappings.
if token.pos == "連体詞,*,*,*":
2019-02-07 22:54:07 +03:00
if re.match(r"[こそあど此其彼]の", token.surface):
return token.pos + ",DET"
2019-02-07 22:54:07 +03:00
if re.match(r"[こそあど此其彼]", token.surface):
return token.pos + ",PRON"
2019-02-07 22:54:07 +03:00
return token.pos + ",ADJ"
return token.pos
def detailed_tokens(tokenizer, text):
"""Format Mecab output into a nice data structure, based on Janome."""
node = tokenizer.parseToNode(text)
node = node.next # first node is beginning of sentence and empty, skip it
words = []
while node.posid != 0:
surface = node.surface
base = surface # a default value. Updated if available later.
parts = node.feature.split(",")
pos = ",".join(parts[0:4])
if len(parts) > 7:
# this information is only available for words in the tokenizer
# dictionary
base = parts[7]
words.append(ShortUnitWord(surface, base, pos))
node = node.next
return words
2017-10-14 14:11:39 +03:00
class JapaneseTokenizer(DummyTokenizer):
2017-10-14 14:11:39 +03:00
def __init__(self, cls, nlp=None):
self.vocab = nlp.vocab if nlp is not None else cls.create_vocab(nlp)
self.tokenizer = try_mecab_import().Tagger()
2018-12-18 17:02:11 +03:00
self.tokenizer.parseToNode("") # see #2901
2017-10-14 14:11:39 +03:00
def __call__(self, text):
dtokens = detailed_tokens(self.tokenizer, text)
words = [x.surface for x in dtokens]
spaces = [False] * len(words)
doc = Doc(self.vocab, words=words, spaces=spaces)
for token, dtoken in zip(doc, dtokens):
token._.mecab_tag = dtoken.pos
token.tag_ = resolve_pos(dtoken)
token.lemma_ = dtoken.lemma
return doc
2017-10-14 14:11:39 +03:00
class JapaneseDefaults(Language.Defaults):
lex_attr_getters = dict(Language.Defaults.lex_attr_getters)
2019-02-07 22:54:07 +03:00
lex_attr_getters[LANG] = lambda _text: "ja"
stop_words = STOP_WORDS
tag_map = TAG_MAP
2017-10-14 14:11:39 +03:00
@classmethod
def create_tokenizer(cls, nlp=None):
2019-02-08 16:14:49 +03:00
return JapaneseTokenizer(cls, nlp)
2017-05-03 07:56:21 +03:00
2017-05-03 07:56:21 +03:00
class Japanese(Language):
lang = "ja"
2017-10-14 14:11:39 +03:00
Defaults = JapaneseDefaults
2017-05-03 07:56:21 +03:00
def make_doc(self, text):
return self.tokenizer(text)
2017-05-03 12:07:29 +03:00
def pickle_japanese(instance):
return Japanese, tuple()
copy_reg.pickle(Japanese, pickle_japanese)
__all__ = ["Japanese"]