Ahogrammer

Deep Dive Into NLP, ML and Cloud

『機械学習エンジニアのためのTransformers』が出ます

このたび、オライリー・ジャパンより、『Natural Language Processing with Transformers』の翻訳書である『機械学習エンジニアのためのTransformers』を出ることになりました。素直にタイトルを訳すと、『Transformersによる自然言語処理』のようになると思うのですが、1文字違いの本が出ていたので、このようなタイトルになっています。Amazonだと8月5日に発売のようです。表紙の鳥はゴシキセイガイインコで、オセアニアに生息しています。

本書はTransformersの開発元であるHugging Faceの開発者たちによって執筆された点が他書との大きな違いと言えるでしょう。内容の詳細については、オライリー公式ページを見ていただくとよいと思いますが、自然言語処理のタスクだけでなく、蒸留、量子化、枝刈りといったモデルの高速化技術、ゼロショット学習や少数事例学習、多言語転移やドメイン適応についても解説しています。

www.oreilly.co.jp

以下、翻訳の経緯などを思い出すために書いておきます。

翻訳の経緯

本書を翻訳するきっかけは何だったかなーと思いメールを検索してみたのですが、ちょうど1年ほど前に編集との間で話題にあがっていました。その後、2022年2月の原著の発売前後あたりから日本語版の企画が進み始め、おおよそ半年で発売という流れです。個人的には、1年以上前から、Hugging Faceの創業者の1人であるThomas Wolfが本を出すということで気になっていたのですが、その本の日本語版を出せてうれしく思います。

作業の効率化

記録を見ると、今回の翻訳はおおよそ1ヶ月程度で終えているようで、なかなかスピーディーだったなと思います。そのために、原稿として渡されるMarkdownファイルを解析して翻訳箇所だけを抽出するプログラムを書いたり(一部、言語判定をしてフィルタリングもしている)、校正を楽にするために300語程度の辞書やルールを整備して自動的にテキストを修正したりしました。これらのルールや辞書は、以前の翻訳の仕事で編集から得られたフィードバックをもとにしています。

改善点としては、不足していたルール(数字3桁ごとにカンマを入れる、キャプションはである調など)を追加することと、文脈を考慮する必要のある校正ルールを書くことが挙げられます。後者は単純な辞書マッチだと厳しいので、spaCyあたりの解析結果を利用して改善しようと考えています。あとは索引を作るのが意外と大変なので、そこを自動化したいなと。キーワード抽出を使って、章ごとに候補を列挙したりするとおもしろいかもしれません。

今後も、効率化できるところは効率化して、人間にしかできない仕事に注力して質を上げられるような仕組みを作りたいなと思います。

参考資料

hironsan.hatenablog.com

spaCyのSpanRulerを使ったルールベースの固有表現認識

一月ほど前の話になりますが、spaCy v3.3.1がリリースされました。いくつかの機能の追加とバグフィックスが行われているのですが、その1つとしてSpanRulerと呼ばれるコンポーネントが追加されています。このコンポーネントはルールベースで固有表現認識などを行うための機能を備えています。日本語での解説を見かけなかったので、本記事で簡単に紹介します。

SpanRulerとは?

spaCyでルールベースの固有表現認識をする場合、EntityRulerがよく使われてきました。EntityRulerはパターンに基づいて固有表現認識をするためのコンポーネントです。統計的なモデルと組み合わせて使うこともできるので、認識性能の向上にも役立ちます。パターンの書き方は、次に示すように、文字列を指定する方法と辞書を指定する方法があります。ラベルに関しては"label"キーで指定します。

# 文字列マッチ
{"label": "ORG", "pattern": "Apple"}

# 辞書によるパターンの指定
{"label": "GPE", "pattern": [{"LOWER": "san"}, {"LOWER": "francisco"}]}

後の説明のために、EntityRulerの使い方を簡単に紹介しておきましょう。典型的には、nlpオブジェクトのadd_pipeメソッドを呼び出してパイプラインにコンポーネントを追加します。そうすることで、doc.entsからマッチした固有表現を得ることができます。次のコードでは、先ほど紹介した2つの方法でパターンを指定しています。

from spacy.lang.ja import Japanese

nlp = Japanese()
ruler = nlp.add_pipe("entity_ruler")
patterns = [
    {"label": "FOOD", "pattern": "ナポリタン"},
    {"label": "FOOD", "pattern": [
        {"POS": "NOUN", "OP": "+"},
        {"TEXT": "スパゲティ"}
    ]}
]
ruler.add_patterns(patterns)

doc = nlp("ナポリタンとたらこスパゲティと和風きのこスパゲティ")
for ent in doc.ents:
    print(ent.text, ent.label_)

結果は次のようになります。

ナポリタン FOOD
たらこスパゲティ FOOD
和風きのこスパゲティ FOOD

SpanRulerEntityRulerと同様にルールベースで固有表現認識などをできるのですが、より一般化されたコンポーネントである点が異なります。EntityRulerでは認識したエンティティをdoc.entsに追加しましたが、SpanRulerではdoc.spansdoc.entsにスパンを追加できます。doc.entsは重複を許さないのに対し、doc.spansは次の画像に示すような重複を許容できます。

重複したスパン(Spancat: a new approach for span labelingより)

また、doc.entsは重複を除去するためにスパンのフィルタリングをしているのですが、SpanRulerでは独自のフィルタリングアルゴリズムを適用できる点が異なります。デフォルトではfilter_spans関数を使って、スパンが重なっている場合は、短いスパンよりも1番長いスパンを優先するようにフィルタリングしていますが、このアルゴリズムを変えることができるのです。

SpanRulerの使い方

EntityRulerと同じく、SpanRulerも通常はnlp.add_pipeで追加します。nlpオブジェクトがテキストに対して呼び出されると、doc内でマッチを見つけ、指定されたパターンラベルをエンティティラベルとして、doc.spans["ruler"]にスパンとして追加します。デフォルトのキー名は"ruler"ですが、設定により変更可能です。doc.entsとは異なり、doc.spansでは重複が許容されるのでフィルタリングは必要ありませんが、オプションでスパンにフィルタリングとソートを適用できます。

import spacy

nlp = spacy.blank("en")
ruler = nlp.add_pipe("span_ruler")
patterns = [
    {"label": "ORG", "pattern": "Apple"},
    {"label": "GPE", "pattern": [
        {"LOWER": "san"},
        {"LOWER": "francisco"}
    ]}
]
ruler.add_patterns(patterns)

doc = nlp("Apple is opening its first big office in San Francisco.")
print(doc.ents)
print([(span.text, span.label_) for span in doc.spans["ruler"]])

出力は次のようになります。デフォルトではdoc.spansに認識結果が格納されていることがわかります。

()
[('Apple', 'ORG'), ('San Francisco', 'GPE')]

上の例では、doc.entsが空でしたが、コンポーネントの設定としてannotate_ents: Trueを渡すことでdoc.entsに結果を格納できます。

nlp = spacy.blank("en")
config = {"annotate_ents": True}
ruler = nlp.add_pipe("span_ruler", config=config)
ruler.add_patterns(patterns)

doc = nlp("Apple is opening its first big office in San Francisco.")
print(doc.ents)
print(doc.spans)
(Apple, San Francisco)
{'ruler': [Apple, San Francisco]}

デフォルトのフィルタリングは1番長いスパンを残しましたが、カスタムフィルターを書けば、残すスパンを変更できます。まずは、デフォルトの場合について見てみましょう。今回の場合、パターンとして「San Francisco」「Giants」「San Francisco Giants」の3つを登録しています。

nlp = spacy.blank("en")
config = {"annotate_ents": True}
ruler = nlp.add_pipe("span_ruler", config=config)
patterns = [
    {"label": "GPE", "pattern": [
        {"LOWER": "san"},
        {"LOWER": "francisco"}
    ]},
    {"label": "ORG", "pattern": [{"LOWER": "giants"}]},
    {"label": "ORG", "pattern": [
        {"LOWER": "san"},
        {"LOWER": "francisco"},
        {"LOWER": "giants"}
    ]},
]
ruler.add_patterns(patterns)

doc = nlp("San Francisco Giants")
print(doc.ents)

結果は次のようになります。1番長いスパンを残していることがわかります。

(San Francisco Giants,)

では、カスタムフィルターを書いて、適用してみましょう。コンポーネントの設定にents_filterを渡すことで、カスタムフィルターを指定できます。

import itertools
from typing import Iterable, List


def filter_spans(spans: Iterable["Span"]) -> List["Span"]:
    sorted_spans = sorted(spans, key=lambda span: span.end)
    result = sorted_spans[:1]
    for span in sorted_spans[1:]:
        if result[-1].end <= span.start:
            result.append(span)
    return result


def filter_chain_spans(*spans: Iterable["Span"]) -> List["Span"]:
    return filter_spans(itertools.chain(*spans))


@spacy.registry.misc("spacy.maximized_spans_filter.v1")
def make_maximized_spans_filter():
    return filter_chain_spans


nlp = spacy.blank("en")
config = {
    "annotate_ents": True,
    "ents_filter": {'@misc': 'spacy.maximized_spans_filter.v1'}
}
ruler = nlp.add_pipe("span_ruler", config=config)
ruler.add_patterns(patterns)

doc = nlp("San Francisco Giants")
print(doc.ents)

結果は次のようになりました。フィルターを渡すことで、結果が変わることを確認できました。

(San Francisco, Giants)

ここで紹介した以外にも、いくつかのオプションを設定として渡すことができます。詳細は、SpanRulerを参照してください。

参考資料

オライリー・ジャパンから『実践 自然言語処理』という本を出します

このたび、オライリー・ジャパンより、『Practical Natural Language Processing』の翻訳書である『実践 自然言語処理』を出すことになりました。Amazonだと2月4日に発売のようです。表紙の鳥はオオハナインコで、オセアニアあたりに生息しています。

f:id:Hironsan:20220201151637p:plain

最近は日本語/英語に関わらず、自然言語処理に関連する書籍が増えてきて読むのを楽しみにしています。その中でも本書は、NLPの要素技術(単語埋め込み、テキスト分類、情報抽出、チャットボット、トピックモデルなど)の紹介に留まらず、SNS、Eコマース、医療、金融といった具体的なビジネスへの適用方法やNLPシステムを開発するためのベストプラクティスを学べるのが特徴的だと思います。

きっかけ

このような本を翻訳するきっかけになったのは、1年と少し前にオライリーの編集に「この本、良い本でしたよ」と何気なく紹介したことでした。そしたらすぐに企画が立ち上がり、翻訳することになりました。まさかあの一言で翻訳することになるとは…。ちなみに、その際には本書だけでなく『Building Machine Learning Pipelines』も紹介しており、こちらも2021年の9月にオライリー・ジャパンより『入門 機械学習パイプライン』として発売しています。まさか2冊翻訳することになるとは…。

f:id:Hironsan:20220201145227p:plain

大変だったこと

本を読んでいたときには気にもとめていなかったのですが、本書は実装がノートブックに切り出されており、必要であればそのうち重要な箇所を本の中で解説するというスタイルになっています。つまり、見かけのページ数以上にボリュームのある本だったのです。コードが大量に載せられている本だと、翻訳者的には訳す部分が減るので楽だと思うのですが、本書は翻訳者である私にとっては楽な本ではありませんでした。しかし、読者としては内容が豊富で楽しめる作りになっています。

その他、オリジナルのノートブックには実装の解説がほぼないので、日本語版を出すにあたって、解説をつけた点が大変だったでしょうか。60近くのノートブックがあったので、そのすべてに対して、古いコードを書き換え、ある程度の解説を付けるのはなかなか骨の折れる仕事でした。

楽しかったこと

今回は、巻末にspaCyを使った日本語処理について書かせていただきました。spaCy Projectsに触れている日本語の本は、もしかしたら初めてかもしれません。当初は、文分割のカスタマイズや固有表現認識のデータ拡張、GiNZaの使い方なども含めて書く予定だったのですが、書いているうちに分量が増えてしまって、泣く泣く削りました。これらについては、また何かの機会に書きたいなと思っています。自分で電子書籍を作って発売できたら楽しいなー、なんて漠然と考えているところです。