なんの記事?
Wikiaを用いて、固有表現抽出とエンティティ・リンキング用のデータセットを作製するツールを公開しました。
ツールで作製されるデータセットの中身
固有表現に対して、それに紐づくエンティティがセットでアノテーションされている形になります。下は、VTuberのwikiaを用いて作製されたデータセットの一部です。
{
"doc_title": "Melissa Kinrenka",
"annotation": [
{
"0": {
"document_title": "Melissa Kinrenka",
"anchor_sent": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of <a> Nijisanji </a>.",
"annotation_doc_entity_title": "Nijisanji",
"mention": "Nijisanji",
"original_sentence": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.",
"original_sentence_mention_start": 75,
"original_sentence_mention_end": 84
},
"1": {
"document_title": "Melissa Kinrenka",
"anchor_sent": "<a> Melissa Kinrenka </a> (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.",
"annotation_doc_entity_title": "Melissa Kinrenka",
"mention": "Melissa Kinrenka",
"original_sentence": "Melissa Kinrenka (メリッサ・キンレンカ) is a Japanese Virtual YouTuber and member of Nijisanji.",
"original_sentence_mention_start": 0,
"original_sentence_mention_end": 16
}
}
}
各キーに紐づく値はそれぞれ以下のようになっています。
key | value |
---|---|
document_title |
アノテーションが存在する記事ページのタイトル |
anchor_sent |
アノテーション部分を <a> および </a> で囲んだ記事。エンティティ・リンキングを行う場合に使用します。 |
annotation_doc_entity_title |
アノテーションの紐付け先記事のタイトル |
mention |
実際の文中のメンション(固有表現) |
original_sentence |
メンションが出現した、アンカー無しオリジナル文。 |
original_sentence_mention_start |
アンカー無しオリジナル文において、メンションの開始位置。 |
original_sentence_mention_end |
アンカー無しオリジナル文において、メンションの終端位置。 |
ライセンスはWikiaのライセンスに従います。
Wikiaとは?
有志によるファンサイトコミュニティで、MediaWikiを利用したウィキ形式のウェブサイトです。MediaWiki形式なので、WikiExtractorを用いて前処理を行うことが可能です。
下はStar Warsからの引用です。
各コミュニティは、ウィキのページの集合から成り立ちます。それぞれのウィキ(例えば、上の"Ched Varga"の場合)を見てみると、登場人物の説明から始まり、説明文が続きます。
説明文の中には、同じコミュニティ内の別のページに飛ぶハイパーリンクが存在し、この部分を用いることで固有表現抽出とエンティティ・リンキング用のデータセットを作製することが可能です。
実際に行った前処理
Wikiextractorを用いた、リンクあり記事の抽出
前処理を行いたいコミュニティウィキのダンプファイルが[コミュニティ名].fandom.com/wiki/Special:Statistics
に存在するので、それをダウンロードして解凍した後、前処理を行います。(例:リンク)
wikiExtractorを用いて、リンクを残しつつ記事を抽出します。
python -m wikiextractor.WikiExtractor ./dataset/virtualyoutuber_pages_current.xml --links --json
外部リンクの削除
このままだと、外部リンクと<a>
タグが残ったままの記事になり、固有表現抽出タスクには使用できません。なので、情報を残しつつ削除を行います。
BeautifulSoupを用いた<a>
タグ削除
soup = BeautifulSoup(sentence, "html.parser")
for link in soup.find_all("a"):
if "http" in link.get("href"):
link.unwrap()
return str(soup)
これにより、ハイパーリンクを残しつつ外部リンクを削除します。例えば、<a href="Nijisanji">Nijisanji</a>
などはハイパーリンクなので残ります。
正規表現を用いた、アノテーションスパンの抽出
この次に行われる、文章分割の精度を上げるため、テキストからハイパーリンク情報を分離します。
今回は正規表現を用いてアノテーション位置を同定し、a
タグを除去した文章と位置情報を返す関数を実装しました。
def _convert_a_tag_to_start_and_end_position(self, text_which_may_contain_a_tag: str):
a_tag_regex = "<a>(.+?)</a>"
pattern = re.compile(a_tag_regex)
a_tag_remaining_text = copy.copy(text_which_may_contain_a_tag)
mention_positions = list()
while '<a>' in a_tag_remaining_text:
result = re.search(pattern=pattern, string=a_tag_remaining_text)
if result == None:
break
original_start, original_end = result.span()
a_tag_removed_start = copy.copy(original_start)
a_tag_removed_end = copy.copy(original_end) - 7
mention = result.group(1)
original_text_before_mention = a_tag_remaining_text[:original_start]
original_text_after_mention = a_tag_remaining_text[original_end:]
one_mention_a_tag_removed_text = original_text_before_mention + mention + original_text_after_mention
assert mention == one_mention_a_tag_removed_text[a_tag_removed_start: a_tag_removed_end]
mention_positions.append((a_tag_removed_start, a_tag_removed_end))
a_tag_remaining_text = copy.copy(one_mention_a_tag_removed_text)
return a_tag_remaining_text, mention_positions
spaCyを用いた文章分割
a
タグを除去することが出来たので、spaCyを用いて文章分割を行います。
wikia はその性質上、lit.
という略語がよく現れます。(参考)
このlit.
による文章分割を防ぎつつ、文章分割を行います。
import spacy
from spacy.language import Language
from spacy.symbols import ORTH
nlp = spacy.load("en_core_web_md")
@Language.component('set_custom_boundaries')
def set_custom_boundaries(doc):
for token in doc[:-1]:
if token.text in ('lit.', 'Lit.', 'lit', 'Lit'):
doc[token.i].is_sent_start = False
return doc
nlp.add_pipe('set_custom_boundaries', before="parser")
nlp.tokenizer.add_special_case('lit.', [{ORTH: 'lit.'}])
nlp.tokenizer.add_special_case('Lit.', [{ORTH: 'Lit.'}])
アノテーションの追加
wikiaのページでは、コミュニティの運営者によって、ドキュメント内の各文書にハイパーリンクが付与されますが、常にすべての固有表現にアノテーションがされるわけではありません。
例えば、以下の例を見てみましょう。(Geroge Lucas のページから引用)
George Lucasはこの記事の中でも幾度となく出現しますが、それら全てにはアノテーションは付与されていません。なぜなら、このページがそもそも"George Lucas"を説明する記事であるからです。
また、以下のような例もあります。(同じく"Geroge Lucas"の記事内から引用)
この場合は"Lucas"が"Geroge Lucas"を指すことは言うまでもありません。
今回のデータセット作製時には、このように、ハイパーリンクが付与されていないものの、エンティティであると認められる場合についてもアノテーションを付与しました。
実際には、コミュニティ内のタイトル集合との文字列マッチを用いて、アノテーションを行うようプログラムを作製しました。
まとめ
wikiaのダンプファイルを用いて、固有表現抽出とエンティティ・リンキングに使用可能なデータセットを生成するツールを作製しました。
このツールを使用することで、wikiaに存在する各コミュニティに特化した固有表現抽出モデルや、エンティティ・リンキングモデルを訓練するためのデータセット作製が可能になります。
コードはこちらになります。