Juman++, SentencePiece, BERT tokenizerの分かち書きを同じコードで書くための抽象クラス

0. 動機

自然言語処理のためには, 入力文を分かち書きし, 各トークンを数値に変換しなくてはなりません。

分かち書きのためのモジュールは Janome(MeCab), Juman++, SentencePiece, BERT tokenizer など色々提供されています。

しかし, 厄介なことに, これらは

など, 分かち書きをどの粒度で行うのか, 処理をどの段階まで行うのかの点でバラバラです。ややこしいですね。

さらに後述しますが, BERTの登場により, 1つの前処理なのに2つ以上の分かち書き器を組み合わせて使わなければならない場面も登場してきました。ますますややこしいですね。

そんなときに, どんな分かち書き器に対しても共通のコードで分かち書きをしたい!というのが動機です。

(もう一つの動機は, 単に私がPythonで抽象クラスを書く練習がしたかったというだけ)

注: 自然言語処理で tokenize, tokenizer という場合, 分かち書きを指すのか, IDへの変換も含めて意味するのかは文脈により曖昧な印象があります。このため, 一般的な表記ではないかもしれませんが本記事では分かち書き, 分かち書き器のように呼びます。

1. 基底クラスの定義

あらゆる分かち書き器に対応するための抽象クラスを定義しましょう。

まず, 使いたい分かち書き器が搭載している機能のうち

がどんなものであるかを指示するための空のメソッドを用意しておきます(@abstractmethodでデコレートされているもの)。

これらの空のメソッドはクラス継承時に必ずoverrideしなければなりませんが, そのoverrideさえきちんと行えば, 実際の分かち書きやBERT入力形式への変換作業はあらかじめ基底クラスに定義してあるため, あらためて考えなくてもよい設計にしています。

from abc import ABCMeta, abstractmethod

class BaseTextProcessorForDataField(object):
    """
                raw text     -┐[1]
          [2]┌- cleaned text <┘
             └> [words]      -┐[3]
          [4]┌- [wordpieces] <┘
             └> [token ids]  -┐[5]
                BERT input   <┘

            [1]: text cleaning
            [2]: base tokenization
            [3]: wordpiece tokenization
            [4]: convert to token ids
            [5]: convert to BERT input
    """

    __metaclass__ = ABCMeta

    def __init__(self):
        self.unk_id = -1
        self.cls_id = -1
        self.sep_id = -1
        self.pad_id = -1
        self.fix_length = 0
        self._enable_base_punctuation = True
        self._enable_wordpiece_punctuation = True
        self._enable_punctuation = True
        self._enable_tokenization = True
        self.errmsg = '{} is not offered in the tokenizer in use in the first place.'

    # MeCab, Juman++, SentencePiece, BertTokenizer等の処理工程の違いに対応する準備
    # 抽象クラスのメソッドを用意する
    # 抽象クラス継承時には必ずoverrideする必要がある
    @abstractmethod
    def clean(self, text):
        # [1]
        pass
    @abstractmethod
    def punctuate_base(self, text):
        # [2]
        pass
    @abstractmethod
    def punctuate_wordpiece(self, word):
        # [3]
        pass
    @abstractmethod
    def punctuate(self, text):
        # [2] + [3]
        pass
    @abstractmethod
    def convert_token_to_id(self, token):
        # [4]
        pass
    @abstractmethod
    def tokenize_at_once(self, text):
        # [2] + [3] + [4]
        pass

    # 以下は実際に分かち書きするためのメソッド(override不要)
    def to_words(self, text):
        """
        Apply only basic tokenization to text.
        ------------
            raw-text
               | <- (this function)
            cleaned-text
               | <- (this function)
            [tokens_words]
               |
            [tokens_wordpieces]
               |
            [token-ids]
               |
            [cls-id token-ids pad-ids sep-ids]
        ------------
        Inputs: text(str) - raw passage
        Outs: (list(str)) - passage split into tokens
        """
        # [1] + [2]
        if self._enable_base_punctuation:
            # Base puncuation を行う
            return self.punctuate_base(self.clean(text))
        else:
            print(self.errmsg.format('Base punctuation'))

    def to_wordpieces(self, text):
        """
        Apply basic tokenization & wordpiece tokenization to text.
        ------------
            raw-text
               | <- (this function)
            cleaned-text
               | <- (this function)
            [tokens_words]
               | <- (this function)
            [tokens_wordpieces]
               | <- (this function)
            [token-ids]
               |
            [cls-id token-ids pad-ids sep-ids]
        ------------
        Inputs: text(str) - raw passage
        Outs: (list(str)) - passage split into tokens
        """
        # [1] + [2] + [3]
        if self._enable_punctuation:
            # Base puncuation と Wordpiece Punctuation を行う
            # 分かち書き器が[2]+[3]を単一メソッドで提供している場合
            return self.punctuate(self.clean(text))
        elif self._enable_base_punctuation and self._enable_wordpiece_punctuation:
            # Base puncuation と Wordpiece Punctuation を行う
            # 分かち書き器が[2],[3]を別メソッドで提供している場合
            wordpieces = []
            for word in self.puncuate_base(self.clean(text)):
                wordpieces += self.punctuate_wordpiece(word)
            return wordpieces
        elif self._enable_base_punctuation:
            # Base puncuation のみ行う
            # 分かち書き器がWordpiece Punctuationに対応していない場合
            return self.to_words(text)

    def to_token_ids(self, text):
        """
        Apply cleaning, punctuation and id-conversion to text. 
        ------------
            raw-text
               | <- (this function)
            cleaned-text
               | <- (this function)
            [tokens_words]
               | <- (this function)
            [tokens_wordpieces]
               | <- (this function)
            [token-ids]
               | <- (this function)
            [cls-id token-ids pad-ids sep-ids]
        ------------
        Inputs: text(str) - raw passage
        Outs: (list(int)) - list of token ids
        """
        # [1] + [2] + [3] + [4]
        if self._enable_tokenization:
            # 分かち書き器が[2]+[3]+[4]を単一メソッドで提供している場合
            return self.tokenize_at_once(self.clean(text))
        else:
            # 分かち書き器が[2]+[3]+[4]を単一メソッドで提供していない場合
            return [ self.convert_token_to_id(token) for token in self.to_wordpieces(text) ]

    def to_bert_input(self, text):
        """
        Obtain BERT style token ids from text.
        ------------
            raw-text
               | <- (this function)
            cleaned-text
               | <- (this function)
            [tokens_words]
               | <- (this function)
            [tokens_wordpieces]
               | <- (this function)
            [token-ids]
               | <- (this function)
            [cls-id token-ids pad-ids sep-ids]
        ------------
        Inputs: text(str) - raw passage
        Outs: (list(int)) - list of token ids
        """
        # [1] + [2] + [3] + [4] + [5]
        # [CLS] <入力文のトークンID列> [PAD] ... [PAD] [SEP] 形式のID列にして返す
        padded = [self.cls_id] + self.to_token_ids(text) + [self.pad_id] * self.fix_length
        return padded[:self.fix_length-1] + [self.sep_id]

2. 実践

これで, どのような仕様の分かち書き器を使ったとしても

の好きな段階まで処理を行うことができ, コードを共通化することができます!
この抽象クラスを継承して, さまざまな分かち書きに対するコードを共通化させていきましょう。

2-1. Juman++ & BERT の場合

一般に, 訓練済みモデルをfine-tuningして使いたい場合, 分かち書きは訓練済みモデルの事前学習に使われたのと同じ方法で行われなければなりません。

これは, 京大黒橋研から公開されている訓練済み日本語BERTモデルを使いたい場合に特に問題になってきます。

京大黒橋研モデルはどのような分かち書きで事前学習されているのでしょうか? 公式HPによると

  • Juman++を用いて形態素に分割し,
  • その後, BERT付属のtokeizerを用いて形態素をサブワードへとさらに分割する

と説明されています。

つまり, 京大黒橋研モデルは事前学習時に Juman++とBERT wordpiece tokenizer を組み合わせた分かち書きを使っているため, fine-tuning時にも同じように Juman++とBERT wordpiece tokenizer を組み合わせなければなりません。

そこで, 先ほどの抽象クラスを継承した JumanppBERTTextProcessor クラスを定義していきましょう。
クラスの定義時に行うのは, 抽象クラスのメソッドのoverrideと, [UNK], [CLS], [SEP], [PAD]にあたるID番号を与えることです。

なお, PyKNPとHugging Face Transformersはすでにインストールされているものとします。

from pyknp import Juman
from transformers import BertTokenizer

PATH_VOCAB = './vocab.txt'    # 1行に1語彙が書かれたtxtファイル
jpp = Juman()
tokenizer = BertTokenizer(PATH_VOCAB, do_lower_case=False, do_basic_tokenize=False)

class JumanppBERTTextProcessor(BaseTextProcessorForDataField):
    """
    JumanppBERTTextProcessor(jppmodel, bertwordpiecetokenizer, fix_length) -> object

    Inputs
    ------
    jppmodel(pyknp.Juman):
        Juman++ tokenizer.
    bertwordpiecetokenizer(transformers.BertTokenizer):
        BERT tokenizer offered by huggingface.co transformers.
        This must be initialized with do_basic_tokenize=False.
    fix_length(int):
        Desired length of resulting BERT input including [CLS], [PAD] and [SEP].
        Longer sentences will be truncated.
        Shorter sentences will be padded.
        """
    def __init__(self, jppmodel, bertwordpiecetokenizer, fix_length):
        self.unk_id = bertwordpiecetokenizer.vocab['[UNK]']
        self.cls_id = bertwordpiecetokenizer.vocab['[CLS]']
        self.sep_id = bertwordpiecetokenizer.vocab['[SEP]']
        self.pad_id = bertwordpiecetokenizer.vocab['[PAD]']
        self.fix_length = fix_length
        self.enable_base_punctuation = True
        self.enable_wordpiece_punctuation = True
        self.enable_punctuation = False
        self.enable_tokenization = False

    # abstractメソッドのoverride
    def clean(self, text):
        return text
    def punctuate_base(self, text):
        return [mrph.midasi for mrph in jppmodel.analysis(self.clean(text)).mrph_list()]
    def punctuate_wordpiece(self, word):
        return bertwordpiecetokenizer.tokenize(word)
    def punctuate(self, text):
        pass
    def convert_token_to_id(self, token):
        try:
            return bertwordpiecetokenizer.vocab[token]
        except KeyError:
            return self.unk_id
    def tokenize_at_once(self, text):
        pass

さて, これで「Juman++で形態素へ, 続いてBERT tokenizerでサブワードへ分かち書きする」処理を短いコードで書くことができます。

jbp = JumanppBERTTextProcessor(jpp, bertwordpiecetokenizer, fix_length=256)
passage = '胸部単純CTを撮像しました。'

jbp.to_words(passage)
# ['胸部', '単純', 'CT', 'を', '撮像', 'し', 'ました']
jbp.to_wordpieces(passage)
# ['胸部', '単純', '[UNK]', 'を', '撮', '##像', 'し', 'ました']
jbp.to_token_ids(passage)
# [15166, 8420, 1, 10, 17015, 55083, 31, 4561]
jbp.to_bert_input(passage)
# [2, 15166, 8420, 1, 10, 17015, 55083, 31, 4561, 0, ..., 0, 3]

2-2. SentencePieceの場合

SentencePiece の場合はもっと単純です。
日本語の文法的な概念としての "単語" にこだわらない設計になっており, すべてがサブワードとして扱われます。
したがってBase tokenization と Wordpiece tokenization の区別もありません。

import sentencepiece as sp
SPM_MODEL_PATH = ''    # 訓練済みSentencePieceモデルのパス
spm = SentencePieceProcessor()
spm.Load(SPM_MODEL_PATH)

class SentencePieceTextProcessor(BaseTextProcessorForDataField):
    def __init__(self, spmodel, fix_length):
        super().__init__()
        self.unk_id = spmodel.PieceToId('<unk>')
        self.cls_id = spmodel.PieceToId('[CLS]')
        self.sep_id = spmodel.PieceToId('[SEP]')
        self.pad_id = spmodel.PieceToId('[PAD]')
        self.fix_length = fix_length
        self._enable_base_punctuation = False
        self._enable_wordpiece_punctuation = False
        self._enable_punctuation = True
        self._enable_tokenization = True
        self.spmodel = spmodel

    def clean(self, text):
        # ここではstopword除去などは行わない
        return text

    def punctuate_base(self, text):
        pass

    def punctuate_wordpiece(self, text):
        pass

    def punctuate(self, text):
        return self.spmodel.EncodeAsPieces(text)

    def convert_token_to_id(self, token):
        return self.spmodel.PieceToId(token)

    def tokenize_at_once(self, text):
        return self.spmodel.EncodeAsIds(text)

実際に SentencePiece で入力文を BERT入力形式に変換すると以下のようになります。

stp = SentencePieceTextProcessor(spm, fix_length=256)
passage = '胸部単純CTを撮像しました'

stp.to_words(passage)
# ['▁', '胸部', '単純', 'C', 'T', 'を', '撮', '像', 'しま', 'した']
stp.to_wordpieces(passage)
# ['▁', '胸部', '単純', 'C', 'T', 'を', '撮', '像', 'しま', 'した']
stp.to_token_ids(passage)
# [9, 20854, 9947, 4167, 0, 18, 10032, 1164, 3899, 29]
stp.to_bert_input(passage)
# [4, 9, 20854, 9947, 4167, 0, 18, 10032, 1164, 3899, 29, 3, ..., 3, 5]

3. 終わりに

どんな分かち書き器に対してもなるべく同じコードで分かち書きができるような抽象クラスを作りました。

利点

  • 単語IDの辞書が分かち書き器自体に保持されている場合にも, 外部にある場合にも, 全く同じコードで分かち書きできる
  • 京大黒橋研日本語BERTのように2つの分かち書き器を組み合わせなければならない場面でもコードが短くなる

欠点

  • 簡単なことを難しく書いている印象は否めない

Notebook環境でGPUメモリ使用量をリアルタイム監視する

TL;DR

GPUメモリの使用量をすぐ取得できるようなPython関数をつくってみた

はじめに

GPUメモリの利用状況を確認するためには nvidia-sminvidia-smi -q -d MEMORY などの各種コマンドを利用できます。

$ nvidia-smi -q -d MEMORY

>>> 
==============NVSMI LOG==============

Timestamp                           : Sat Nov 30 16:02:56 2019
Driver Version                      : 418.67
CUDA Version                        : 10.1

Attached GPUs                       : 1
GPU 00000000:00:04.0
    FB Memory Usage
        Total                       : 11441 MiB
        Used                        : 0 MiB
        Free                        : 11441 MiB
    BAR1 Memory Usage
        Total                       : 16384 MiB
        Used                        : 2 MiB
        Free                        : 16382 MiB

さらに watch コマンドと組み合わせると指定秒毎に情報を取得することができます。

$ watch -n 10 'nvidia-smi -q -d MEMORY'  # 10秒ごとに表示を繰り返す

これらの機能は, GPUメモリを溢れさせないためのモニタリングに適しています。

しかし問題なのは, どんな時でも使える訳ではなさそうという点です。

機械学習をシェルから走らせる場合にはいいかもしれませんが, Jupyter Notebook, Google Colab, Kaggle KernelなどのNotebook環境でこれをやろうとすると監視の無限ループだけが回り続けて他のセルが実行できなくなるので使い物になりません。

実装してみる

さっそく実装してみました。コードはこちらです。

import subprocess
import shlex

def gpuinfo():
    """
    Returns size of total GPU RAM and used GPU RAM.

    Parameters
    ----------
    None

    Returns
    -------
    info : dict
        Total GPU RAM in integer for key 'total_MiB'.
        Used GPU RAM in integer for key 'used_MiB'.
    """

    command = 'nvidia-smi -q -d MEMORY | sed -n "/FB Memory Usage/,/Free/p" | sed -e "1d" -e "4d" -e "s/ MiB//g" | cut -d ":" -f 2 | cut -c2-'
    commands = [shlex.split(part) for part in command.split(' | ')]
    for i, cmd in enumerate(commands):
        if i==0:
            res = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        else:
            res = subprocess.Popen(cmd, stdin=res.stdout, stdout=subprocess.PIPE)
    total, used = map(int, res.communicate()[0].decode('utf-8').strip().split('\n'))
    info = {'total_MiB':total, 'used_MiB':used}
    return info

実行してみます。

In:  gpuinfo()
Out: {'total_MiB': 11441, 'used_MiB': 0}

GPUのメモリのサイズと使用量が取得できました。
自動的に繰り返すとまではいきませんが, これでNotebookの好きな箇所でGPU使用状況を確認することができます。

表示を洗練させてみる

ログに残しやすいよう, 少し手を加えてメッセージ文字列を出力させてみます。

def gpulife():
    """
    Returns GPU usage information in string.

    Parameters
    ----------
    None

    Returns
    -------
    msg: str
    """

    def _gpuinfo():
        command = 'nvidia-smi -q -d MEMORY | sed -n "/FB Memory Usage/,/Free/p" | sed -e "1d" -e "4d" -e "s/ MiB//g" | cut -d ":" -f 2 | cut -c2-'
        commands = [shlex.split(part) for part in command.split(' | ')]
        for i, cmd in enumerate(commands):
            if i==0:
                res = subprocess.Popen(cmd, stdout=subprocess.PIPE)
            else:
                res = subprocess.Popen(cmd, stdin=res.stdout, stdout=subprocess.PIPE)
        return tuple(map(int, res.communicate()[0].decode('utf-8').strip().split('\n')))

    total, used = _gpuinfo()
    percent = int(used / total * 100)
    msg = 'GPU RAM Usage: {} {}/{} MiB ({:.1f}%)'.format('|' * (percent // 5) + '.' * (20 - percent // 5), used, total, used/total*100)
    return msg

このようになりました。

In:  gpulife()
Out: GPU RAM Usage: .................... 0/11441 MiB (0.0%)

なお, 上記のコードはいまのところ1GPUの場合にしか対応していません。
複数GPUの場合はそのままでは使えませんので, ご注意ください。
また, 動作確認はGoogle Colabでしか行っていません。その点もご容赦ください。

詳細

nvidia-smi -q -d MEMORY の出力結果をストリームエディタで加工しているだけです。
シェルのコマンドは subprocess モジュールを使えばPythonで実行できますが, パイプラインがある場合は単に subprocess.run() に渡すだけでは実行できないため, Python上でパイプラインを構築しています。

参考資料

公式ドキュメント subprocess --- サブプロセス管理
[Python]可読性を上げるための、docstringの書き方を学ぶ(NumPyスタイル)

Livedoorニュースコーパスを文書分類にすぐ使えるように整形する

はじめに

日本語文書分類タスクのための代表的なコーパスの1つ,Livedoorニュースコーパス
Livedoorニュースのニュース記事を収集して生成されており,9種類のニュース記事が計7367本収載されています。

登録なしで無償利用でき,便利なのですが,そのままでは機械学習で使えるデータセットの形になっていません。
そこで,シェルでささっと整形していきたいと思います。

本題

コーパスの入手&展開

早速コーパスを入手しましょう。
専用のdataset ディレクトリを作り, その内部にtarアーカイブをダウンロードし, 展開します。

$ mkdir dataset
$ cd dataset
$ wget https://www.rondhuit.com/download/ldcc-20140209.tar.gz
$ tar zxvf ldcc-20140209.tar.gz

するとこのような構成のディレクトリが出来上がります。

dataset
└ text
  ├ CHANGES.txt
  ├ README.txt
  ├ dokujo-tsushin
  │  ├ LICENSE.txt
  │  ├ dokujo-tsushin-4778030.txt
  │  │ ...  
  │  └ dokujo-tsushin-6915005.txt
  ├ it-life-hack
  │  ├ LICENSE.txt
  │  ├ it-life-hack-6292880.txt
  │  ...  
  ├ kaden-channel
  │  ├ LICENSE.txt
  │  ├ kaden-channel-6054293.txt
  │  ...  
  ├ livedoor-homme
  │  ├ LICENSE.txt
  │  ├ livedoor-homme-4568088.txt
  │  ...  
  ├ movie-enter
  │  ├ LICENSE.txt
  │  ├ movie-enter-5840081.txt
  │  ...  
  ├ peachy
  │  ├ LICENSE.txt
  │  ├ peachy-4289213.txt
  │  ...  
  ├ smax
  │  ├ LICENSE.txt
  │  ├ smax-6507397.txt
  │  ...  
  ├ sports-watch
  │  ├ LICENSE.txt
  │  ├ sports-watch-4597641.txt
  │  ...  
  └ topic-news
     ├ LICENSE.txt
     ├ topic-news-5903225.txt
      ...  

各カテゴリのニュース記事はdokujo-tsushinからtopic-newsまでの該当するディレクトリに格納されています。
ファイル名はいずれも <ディレクトリ名>-xxxxxxx.txt という形式です。

tsvファイルの作成

ここから,各ニュース記事の ①ファイル名,②本文,③カテゴリのone-hot encoding を格納したtsvファイルを作っていきましょう。
まず,こんな感じの↓カラムだけを作っていこうと思います。

filename article dokujo-tsushin it-life-hack kaden-channel livedoor-homme movie-enter peachy smax sports-watch topic-news

これはワンライナーで書くことができます。
カラムができたらdataset/text/livedoor.tsv に格納しましょう。

$ echo -e "filename\tarticle"$(for category in $(basename -a `find ./text -type d` | grep -v text | sort); do echo -n "\t"; echo -n $category; done) > ./text/livedoor.tsv

続いて,dokujo-tsushinからtopic-newsまでの各ディレクトリ内のニュース記事の情報をtsvファイルに追記していきます。
こちらも頑張れば1行で書けそうですが,今回はカテゴリ毎に別々のコマンドとして実行します。

$ for filename in `basename -a ./text/dokujo-tsushin/dokujo-tsushin-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/dokujo-tsushin/$filename`; echo -e "\t1\t0\t0\t0\t0\t0\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/it-life-hack/it-life-hack-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/it-life-hack/$filename`; echo -e "\t0\t1\t0\t0\t0\t0\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/kaden-channel/kaden-channel-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/kaden-channel/$filename`; echo -e "\t0\t0\t1\t0\t0\t0\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/livedoor-homme/livedoor-homme-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/livedoor-homme/$filename`; echo -e "\t0\t0\t0\t1\t0\t0\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/movie-enter/movie-enter-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/movie-enter/$filename`; echo -e "\t0\t0\t0\t0\t1\t0\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/peachy/peachy-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/peachy/$filename`; echo -e "\t0\t0\t0\t0\t0\t1\t0\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/smax/smax-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/smax/$filename`; echo -e "\t0\t0\t0\t0\t0\t0\t1\t0\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/sports-watch/sports-watch-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/sports-watch/$filename`; echo -e "\t0\t0\t0\t0\t0\t0\t0\t1\t0"; done >> ./text/livedoor.tsv
$ for filename in `basename -a ./text/topic-news/topic-news-*`; do echo -n "$filename"; echo -ne "\t"; echo -n `sed -e '1,3d' ./text/topic-news/$filename`; echo -e "\t0\t0\t0\t0\t0\t0\t0\t0\t1"; done >> ./text/livedoor.tsv

これでtsvファイルの準備ができました。 dataset/text/livedoor.tsv をPandasで開くと次のような表ができているはずです。あとは存分に機械学習していきましょう!

filename article dokujo-tsushin it-life-hack kaden-channel livedoor-homme movie-enter peachy smax sports-watch topic-news
dokujo-tsushin-4778030.txt <記事の本文> 1 0 0 0 0 0 0 0 0
... ... ... ... ... ... ... ... ... ... ...
topic-news-6907153.txt <記事の本文> 0 0 0 0 0 0 0 0 1
注意

Jupyter NotebookやGoogle Colaboratoryのセル内で実行させた場合,ディレクトリの遷移状態の違いによるエラーが起こることがあります。
その場合は上記スクリプト中のパス表記を ./text/hogehoge から /dataset/text/hogehoge のように修正してください。

自然言語処理タスクを概観する(6) Multi-modal task

NLP Progress という素晴らしいリポジトリを見つけました。整理の意味を込めてまとめます。
NLPの種々のタスクとそのSOTAが掲載されています。
NLPベンチマークとなる有名なデータセットも一緒に紹介されており,NLP論文を読むうえで大きな助けとなってくれるでしょう。

ここまで6本の記事でNLPタスクを概観してきましたが, これでいよいよ最後です。
マルチモーダルタスクを概観していきます。
近年可能となってきた, 文体を扱うタスクもここで見ていきます。
そろそろ NLP Progress のみでは内容が不足気味となってきました。ここでは Papers With Code の内容も織り交ぜていきます。

10. Text Style

10-1. 文体変換 Text Style Transfer

  • 概要
    • 入力文の内容を変えずに文体のみを変えたものを出力する.
    • タスク設定によって, 変換後の文体のparallel corpusが存在する場合としない場合がある.
    • 何をもって評価するか自体も議論の対象になっている. (例: Remi et al., NAACL 2019)
  • データセット
    • 英語
      • GYAFC (Grammarly's Yahoo Answers Formality Corpus) Dataset (2018) (PAPER)
      • あまり大規模なShared Taskは行われていない印象がある.
  • 上位モデル例

11. Multi-modal Task

11-1. 言語×画像

11-1-1. 画像のキャプション生成 (Image Captioning)
11-1-2. VQA (Visual Question Answering)
  • 概要
    • 画像とそれに関する質問文が与えられ, 正しい解答を出力する. タスクによって択一式解答か短文による解答かは異なる.
    • キャプション生成よりも定量的評価がしやすいためか, shared taskの対象となりやすい.
  • データセット

    • 英語
      • NLVR (Natural Language and Vision Readoning) (2017) (PAPER)
        • 丸や三角などの図形が多数配置された画像と, テキストが与えられ, テキストが画像の正しい描写になっているかどうかを True/False の二択で答えるデータセット.
      • VQA (Visual Question Answering) Dataset (2015) (PAPER)
        • MS COCO Dataset (主に物体認識などに特化) の約20万画像に, Abstract Scenes Datasetとして約5万画像を加えて構成している.
        • 解答形式は択一式と短文の両方に対応している.
      • VQA v2.0 Dataset (2017) (PAPER)
        • VQAの偏りを改善したデータセット.
        • 具体的には, 似たような画像群に対する正答の分布に生じていたバラつきを解消させるようなデータを追加している.
      • VG (Visual Genome) Dataset (2016) (PAPER)
        • 画像と言語のさまざまなデータ対が与えられたデータセット.
        • 画像全体に対し, scene graphが付与されている.
        • 画像中の各instanceに対し, bounding box・描写文・region graphが付与されている.
        • さらに, 質問応答のためのデータとして,
          • Region-Based QA: あるinstanceに関する質問文とそれに対する正答(正しい対象instanceと解答文)が付与されている.
          • Free-Form QA: 特にinstanceを限定しない質問文とそれに対する正答(解答文)が付与されている.
      • GQA Dataset (CVPR 2019) (PAPER)
        • VQAの問題点を改善させたデータセット.
          • VQAには事前知識が利用できてしまう場面があり (例: VQAに登場するトマトはだいたい赤い), 画像認識や質問応答の能力を正しく測れない可能性があった.
          • VQAの成績に対する, 画像認識と質問応答のそれぞれの寄与が分析しにくかった.
        • Visual Genomeの画像とScene Graphからデータセットを構成することで, 質問に含まれる情報をきちんと整えており, 回答の各プロセスを分離して評価しやすくなっている.
  • 上位モデル例

11-1-3. Visual Entailment
  • 概要
    • 含意関係認識 のマルチモーダルバージョン.
    • 前提 (premise) と仮説 (hypothesis) が両方テキストで用意されるのではなく,前提 (premise) が画像で仮説 (hypothesis) がテキストになっている.
    • つまり,画像とテキストが矛盾していないかどうかを Entailment, Neutral, Contradiction の3択で答えるタスク.
  • データセット

    • 英語
      • SNLI-VE Dataset (NeurIPS 2018 Visually Grounded Interaction and Language (ViGIL) Workshop) (PAPER)
        • Stanford Natural Language Inference (SNLI) データセットの前提文を Flickr30k データセットの画像で置き換えて機械的に作成したデータセット.
        • これが可能なのは,SNLI 自体も Flickr30k を用いて作られているため.
        • SNLI では前提文が Flickr30k のキャプション,仮説文がクラウドワーカーに前提文をもとに書かせた文になっている.
  • 上位モデル例

11-2. 言語×データ

11-2-1. Data-to-Text Generation

まとめ

Cross-modalなタスクに対しても, CNN+RNN → Attention → Transformerという進化の流れは同様に起きています.
今まさにホットな分野であるためか, 進歩が速く, NLP ProgressやPapers With Codeなどへの掲載が追いついていない最新の成果も多くみられます.
ここまで, 全6記事でNLPの各種タスクを概観してきました.
以前よりもNLPの全体像がよりよく描けるようになった気がしています.

自然言語処理タスクを概観する(5) 言語モデル, 情報抽出, 意味, 知識など

NLP Progress という素晴らしいリポジトリを見つけました。整理の意味を込めてまとめます。
NLPの種々のタスクとそのSOTAが掲載されています。
NLPベンチマークとなる有名なデータセットも一緒に紹介されており,NLP論文を読むうえで大きな助けとなってくれるでしょう。

ついにここまでやって来ました。言語の意味などを扱う,より高度なタスクを概観していきます。

7. 言語モデル Language Modeling

  • 概要
    • トークン列の文としての確からしさを評価する.
      • 古典的には, マルコフ仮定にもとづき, 最後のk単語から次の単語を予測するという問題設定だった.1
      • ニューラル言語処理の発展以降はマルコフ仮定を用いない言語モデルに移行している.
    • 評価指標はパープレキシティ preplexity.
      • 性能がよいほど小さくなる.
      • パープレキシティはコーパスから計算するため, 異なるコーパスに対しては比較できない.
  • データセット
  • 上位モデル例
    • Transformer系: Transformer XL etc.

8. 意味解析 Semantic Parsing

  • 概要
    • 自然言語を, 計算機が扱えるような意味表現へと変換するタスク.
    • 出力形式は以下に示すSQLまたはAMRが一般的.

8-1. 抽象的意味表現 AMR (Abstract Meaning Representation)

8-2. SQL parsing

  • 概要
    • 入力された質問文から, それに正しく応答するためのSQL文を自動生成する.
    • こちらは生成するのが文であるため, seq2seqなアプローチが利用可能.
  • データセット
    • 英語
      • ATIS
        • 飛行機のフライト予約に関する質問を受け, それに答えるためのSQL文を生成するためのデータセット.
      • Advising
        • 大学の履修に関する質問を受け, それに答えるためのSQL文を生成するためのデータセット.
      • GeoQuery
        • 地理に関する質問を受け, それに答えるためのSQL文を生成するためのデータセット.
      • Scholar
        • 学術論文のデータベースに関する質問を受け, それに答えるためのSQL文を生成するためのデータセット.
      • Spider
      • WikiSQL
  • 上位モデル例

9. 知識獲得など

9-1. Taxonomy Learning

9-2. Common Sense

  • 概要
  • データセット
    • 英語
      • Event2Mind
        • 複数の人物が登場する, 日常生活の出来事を描写した文が与えられる.
        • 登場人物の行動について, その意図や受け手の反応を推定する.
      • SWAG (Situations with Adversarial Generations)
        • 動画キャプション生成用データセットの, キャプション文のみから作成したもの.
          • LSMDC (Large Scale Movie Description Challenge)
          • Activity Net
        • キャプションの前半部分が与えられ, その続きとして整合性のあるものを4つの選択肢から (動画情報は使わずに) 正しく選ぶ.
      • Winograd Schema Challenge
      • WNLI (Winograd Schema Challenge NLI)
      • VCR (Visual Commonsense Reasnoning)
        • 視覚情報の理解をめざしたデータセット.
        • 画像とその内容についての質問文が与えられ, 質問への正答を選択肢からえらぶ.
        • さらに, なぜその選択肢を選んだのかという根拠もあわせて択一式で答えさせる.
      • ReCoRD (Reading Comprehension with Commonsense Reasnoning Dataset)
        • CNN / Daily News の記事から構成したデータセット.
        • 問題設定は機械読解のタスクと似ているが, 質問文の作成を自動化してある.
  • 上位モデル例
    • Transformer系: RoBERTa, XLNet, BERT etc.

9-3. 情報抽出 IE (Information Extraction)

9-3-1. Open Knowledge Graph Canonicalization

まとめ

ここまで来るとタスクが高度かつ複雑になるため,単一のできあいのモデルをそのまま使うだけでは太刀打ちできません(問題設定やデータセットにもよりますが)。
次回がこのシリーズの最後となる予定です。

自然言語処理タスクを概観する(4) 系列変換, 生成, 対話

NLP Progress という素晴らしいリポジトリを見つけました。整理の意味を込めてまとめます。
NLPの種々のタスクとそのSOTAが掲載されています。
NLPベンチマークとなる有名なデータセットも一緒に紹介されており,NLP論文を読むうえで大きな助けとなってくれるでしょう。

対話システムについては私はほとんど知識がなく, ごく簡素にしか書いていません。

5. 系列変換・生成タスクとその変形

5-1. 文法誤り訂正 GEC(Grammatical Error Correction)

  • 概要
    • 入力文の文法誤りを検出する. または, 訂正した結果を出力する.
  • データセット
    • 英語
      • CoNLL-2014
        • 英語の文法誤り訂正タスクでもっとも一般的に使用される.
        • CoNLL-2014 shared task test set: 2名の専門家がアノテーションしたもの.
        • CoNLL-2014 10 Annotations: 10名の専門家がアノテーションしたもの.
      • JFLEG (2017)
      • BEA Shared Task 2019
    • 日本語
  • 上位モデル例
    • Transformer系: Transformer+Pretrain with Pseudo Data, Copy-Augmented Transformer etc.
    • CNN系: CNN + Seq2Seq etc.
    • ほか: SMT + BiGRU

5-2. 語彙正規化 Lexical Normalization

  • 概要
    • 標準的でない語彙を標準的な語彙に変換する.
    • ソーシャルメディアの文書などが対象となることが多い.
    • 標準化に伴って文章の単語長が変化することもありうるが, そのような変換はこのタスクでは考慮しない.
      • つまり, 単語ごとの逐次的な変換のみを行い, 単語の挿入/削除/順序入れ替えを伴うような変換は行わない.
      • 例: new pix comming tomoroe -> new pictures coming tomorrow
  • データセット
  • 上位モデル例
    • 非ニューラルな手法が用いられている.

5-3. 機械翻訳 Machine Translation

  • 概要
    • Source Language の入力文を Target Language に翻訳して出力する.
    • 評価指標には BLEU, METEOR などがあるが, 一長一短ある.
  • データセット
    • 英語-ドイツ語
      • WMT 2014 EN-DE
    • 英語-フランス語
      • WMT 2014 EN-FR
  • 上位モデル例
    • Transformer系: Transformer Big + Back-Translation etc.
    • CNN系: ConvS2S

5-4. 平易化 Simplification

  • 概要
    • 入力文の意味を変えずに, 初学者などにとってより可読性の高い文に変換する.
    • 具体例:
      • Unusual concept を説明する.
        • 例: small mammals -> small mammals (such as mice or rats)
      • Unusual word を Familiar term/phrase に置き換える.
        • 例: comprising -> there are about
      • 重文や複文をいくつかの単文に分離する.
      • 重要でない情報を省略する.
    • 系列ラベリング, 文書要約, 機械翻訳, 情報抽出などの前処理としても用いられる.
  • データセット
    • 英語
      • Main-Simple English Wikipedia
      • PWKP/WikiSmall
      • Turk Corpus
      • Newsela
      • Splits
  • 上位モデル例
    • 汎用的なモデルはあまり用いられていない印象がある.

5-5. 文書要約 Summarization

  • 概要
    • 1つまたは複数の文書の内容を要約した新たな短い文書を出力する.
    • 評価指標には METEOR, ROUGE などを用いるが, 限界も多い.
  • データセット
    • 英語
      • CNN / Daily Mail
        • ニュース記事からその要約を生成するためのデータセット.
      • Gigaword
        • 短文のニュースからその見出しを生成するためのデータセット.
        • CNN / Daily Mail よりもコーパスの文長が短い.
      • DUC 2004 Task 1
        • 要約前の文書 (平均35.6トークン長) と要約後の文書 (平均10.4トークン長) の500組からなる.
        • データセットが小さいためtestにのみ使用されることが多い.
      • Webis-TLDR-17 Corpus
  • 上位モデル例

5-6. 圧縮 Sentence Compression

  • 概要
    • 入力文から, 文法的な正しさと重要な情報は保持したまま冗長な部分だけを削除する.
    • つまり出力文は入力文のsubsetとなる.
    • 評価指標は F1 score, Compression Rateなど.
  • データセット
    • 英語
  • 上位モデル例
    • RNN系: BiRNNLM, BiLSTM etc.

6. 対話 Dialogue

6-1. Dialogue Act Classification

6-2. Dialogue State Tracking

  • 概要
    • 対話の各場面で刻々と変化するユーザーの要求を正しく推定する.
  • データセット
    • 英語
      • DSTC2 (The Second Dialogue Systems Technology Challenges)
      • WoZ 2.0 (Wizard-of-Oz)
      • MultiWOZ
  • 上位モデル例
    • 単純な汎用モデルをそのまま適用している事例は少ない.

6-3. Retrieal-based Chatbots

6-4. Generative-based Chatbots

  • 概要
    • 対話システムのうち, 最適な応答文を生成して出力するもの.
  • データセット
    • 英語
      • ConvAI2 (The COnversational Intelligence Challenge 2)
  • 上位モデル例
    • Transformer系: Transformerの転移学習モデルetc.
    • RNN系: Seq2Seq2+Attention etc.

6-5. Disentanglement

  • 概要
    • 同一のチャンネル上で同時進行している複数の対話をそれぞれの対話に正しく分離する.
  • データセット
  • 上位モデル例
    • Transformer系: Transformerの転移学習モデルetc.
    • RNN系: Seq2Seq2+Attention etc.

まとめ

系列変換タスクでもBERT族は強さを発揮しています。
一方, 対話システムは対話履歴などを利用しなければならないため複数のモデルを組み合わせる傾向にあるようです。

自然言語処理タスクを概観する(3) 系列ラベリングとその変形

NLP Progress という素晴らしいリポジトリを見つけました。整理の意味を込めてまとめます。
NLPの種々のタスクとそのSOTAが掲載されています。
NLPベンチマークとなる有名なデータセットも一緒に紹介されており,NLP論文を読むうえで大きな助けとなってくれるでしょう。

4. 系列ラベリングとその変形

4-1. 品詞タグ付け POS(Part-of-speech) Tagging

4-2. 浅い構文解析 Shallow Syntax2

  • 概要
    • 入力文から特定の種類の句 (名詞句, 動詞句など) を正しく同定する.
    • 具体的には, 各トークンにBIOラベルを付与する.
      • 句に属さないトークンはO, それぞれの句の先頭のトークンはB, それ以外はI.
    • もっとも簡単な構文解析処理で, 句の階層構造や単語の係り受けを考慮する必要がない.
  • データセット
    • 英語
      • PTB (Penn Treebank)
  • 上位モデル例
    • RNN系: Flair embeddings + BiLSTM + CRF

4-3. 構文解析 Parsing (Syntactic Analysis)

4-3-1. 句構造解析 Constituency Parsing (Phrase Structure Analysis)
  • 概要
    • 入力文から構文木を作成する.
    • 近年のアプローチでは構文木を系列として表現し, seq2seqタスクに帰着するのが一般的.
    • 例: John sees Bill. -> (S (N) (VP V N))
  • データセット
    • 英語
      • PTB (Penn Treebank)
  • 上位モデル例
4-3-2. 依存構造解析(係り受け解析) Dependency Parsing3
4-3-2a. Cross-Lingual Zero-Shot Dependency Parsing
  • 概要
    • Source Languageで教師あり学習した依存構造解析器を, 新たな教師データなしでTarget Languageに使用する.
  • 上位モデル例
    • RNN系: Cross-Lingual ELMo
4-3-2b. 教師なし依存構造解析 Unsupervised Dependency Parsing
  • 概要
    • ラベル付き教師データを使用することなく依存構造解析を行う.
  • 上位モデル例
    • 非ニューラルアプローチが用いられる.

4-4. 深い構文解析

  • 概要
    • 通常の文脈自由文法 (CFG (Context Free Grammar)) による構文解析では扱い切れない曖昧性を扱う手法.
4-4-1. 組み合わせ範疇文法 CCG (Combinatory Categorical Grammar)
  • 概要
    • 品詞ラベルの発展形であるカテゴリラベルを利用して導出 Derivation を作成する.
    • 導出とは CCG における独特の用語で, 意味は構文木と同様.
4-4-1a. 組み合わせ範疇文法による構文解析
  • 概要
    • 入力文から導出を作成する.
  • データセット
  • 上位モデル例
    • RNN系: LSTM
4-4-1b. 組み合わせ範疇文法によるsupertagging
  • 概要
    • 処理の高速化を狙ってカテゴリラベルの付与だけを先に行ってしまう.
  • データセット
    • CCGBank
  • 上位モデル例
    • RNN系: BiLSTM + Cross View Training etc.
4-4-1c. 構文木への変換 Conversion to PTB
  • 概要
    • CCGによる導出を文脈自由文法による構文木に変換する.
  • データセット
    • CCGBank
  • 上位モデル例
    • 非ニューラル手法が用いられる.

4-5. 意味役割付与(述語項構造解析) Semantic Role Labeling

  • 概要
    • 入力文から形容詞や動詞を同定し, さらにそれらが掛かる箇所について, 正しい格に対応したBIOラベルを付与する.
    • 用意するラベルは表層格にもとづく場合と深層格にもとづく場合がある.
      • 英語などでは表層格を対象とすることは少ない (語順の情報が利用できるため)
  • データセット
  • 上位モデル例
    • LSTM系: BiLSTM+ELMo etc.

4-6. 共参照解析 Coreference Resolution

  • 概要
    • 文中のトークンのうち同一の実体 (Entity) を指すものの組を正しく同定する.
    • 共参照 coreference と照応 anaphora は共通する部分もあるが, 基本的には別の概念.
  • データセット
    • 英語
      • CoNLL 2012
  • 上位モデル例
    • ELMo+α.

4-7. 固有表現抽出 NER (Named Entity Recognition)

  • 概要
    • 入力文から固有表現 (人名, 地名など) を正しく同定する.
    • 具体的には, 各トークンに固有表現の種類に応じたBIOラベルを付与する.
  • データセット
    • 英語
      • CoNLL 2003
      • WNUT 2017
      • OntoNotes v5
  • 上位モデル例
    • Transformer系: LSTM+CRF+ELMo+BERT+Flair etc.
    • CNN系: CNN Large etc.
    • LSTM系: BiLSTM+CRF+ELMo etc.

4-8. エンティティリンキング Entity Linking

  • 概要
    • 固有表現抽出と語義曖昧性解消がセットになったようなタスク.
    • 文中の語にWikipediaなどへのリンクを適切に付与する.
      • End-to-End approach: 固有表現抽出とリンクの付与を同時に行う
      • Disambiguation-Only approach: 抽出済みの固有表現にリンクの付与を行う
  • データセット
    • 英語
      • AIDA CoNLL-YAGO Dataset
  • 上位モデル例
    • RNN系: DeepType

まとめ

ここまで来ると, トークンごとの多クラス分類問題もしくは系列変換としての性格が強くなってきますが, やはりBERT族が圧倒的な強さを発揮します。
個人的には教師なし学習がどのように進化していくのかが気になります。 さらに続きます。