トランスフォーマーと OpenVINO™ による多言語書籍の調整

この Jupyter ノートブックはオンラインで起動でき、ブラウザーのウィンドウで対話型環境を開きます。ローカルにインストールすることもできます。次のオプションのいずれかを選択します。

Binder Google Colab GitHub

言語間のテキスト配置は、相互に翻訳されたペアのテキスト内の文を照合するタスクです。このノートブックでは、ディープラーニング・モデルを使用して英語とドイツ語の対訳を作成する方法を学びます。

この方法は言語の学習に役立ちますが、機械翻訳モデルのトレーニングに使用できる対訳テキストも提供します。これは、いずれかの言語のリソースが少ない場合、または本格的な翻訳モデルをトレーニングするのに十分なデータがない場合に便利です。

このノートブックでは、OpenVINO™ フレームワークを使用して、パイプラインの最も計算コストがかかる部分 (文からベクトルを取得する) を高速化する方法を示します。

パイプライン

このノートブックは、生のテキストの取得から、整列した文章の視覚化の構築まで、並列ブックを作成するプロセス全体をガイドします。パイプライン図は次のとおりです。

image0

結果を視覚化すると、図に示すように、パイプライン・ステップの改善領域を特定できます。

必要条件

  • requests - 本を入手する

  • pysbd - 文を分割する

  • transformers[torch] および openvino_dev - 文の埋め込みを取得する

  • seaborn - アライメント行列を視覚化する

  • ipywidgets - ノートブックに HTML と JS の出力を表示する

目次

%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu requests pysbd transformers[torch] "openvino>=2023.1.0" matplotlib seaborn ipywidgets

書籍を入手

最初のステップは、作業に使用する本を入手することです。このノートブックでは、レオ・トルストイの『アンナ・カレーニナ』の英語版とドイツ語版を使用します。テキストは Project Gutenberg サイトから入手できます。著作権法は複雑で国によって異なるため、お住まいの国で書籍が法的に入手可能かどうかを確認してください。詳細については、Project Gutenberg の権限、ライセンス、およびその他の一般的なリクエストのページを参照してください。

Project Gutenberg の検索ページで書籍を検索し、各書籍の ID を取得します。テキストを取得するには、ID を Gutendex API に渡します。

import requests


def get_book_by_id(book_id: int, gutendex_url: str = "https://gutendex.com/") -> str:
    book_metadata_url = gutendex_url + "/books/" + str(book_id)
    request = requests.get(book_metadata_url, timeout=30)
    request.raise_for_status()

    book_metadata = request.json()
    text_format_key = "text/plain"
    text_plain = [k for k in book_metadata["formats"] if k.startswith(text_format_key)]
    book_url = book_metadata["formats"][text_plain[0]]
    return requests.get(book_url).text


en_book_id = 1399
de_book_id = 44956

anna_karenina_en = get_book_by_id(en_book_id)
anna_karenina_de = get_book_by_id(de_book_id)

テキストの一部を表示して、正しい本を入手したかどうかを確認してみましょう。

print(anna_karenina_en[:1500])
The Project Gutenberg eBook of Anna Karenina

This ebook is for the use of anyone anywhere in the United States and
most other parts of the world at no cost and with almost no restrictions
whatsoever. You may copy it, give it away or re-use it under the terms
of the Project Gutenberg License included with this ebook or online
at www.gutenberg.org. If you are not located in the United States,
you will have to check the laws of the country where you are located
before using this eBook.

Title: Anna Karenina


Author: graf Leo Tolstoy

Translator: Constance Garnett

Release date: July 1, 1998 [eBook #1399]
                Most recently updated: April 9, 2023

Language: English



* START OF THE PROJECT GUTENBERG EBOOK ANNA KARENINA *
[Illustration]




 ANNA KARENINA

 by Leo Tolstoy

 Translated by Constance Garnett

Contents


 PART ONE
 PART TWO
 PART THREE
 PART FOUR
 PART FIVE
 PART SIX
 PART SEVEN
 PART EIGHT




PART ONE

Chapter 1


Happy families are all alike; every unhappy family is unhappy in its
own way.

Everything was in confusion in the Oblonskys’ house. The wife had
discovered that the husband was carrying on an intrigue with a French
girl, who had been a governess in their family, and she had announced
to her husband that she could not go on living in the same house with
him. This position of affairs had now lasted three days, and not only
the husband and wife themselves, but all the me

生の形式では次のようになります。

anna_karenina_en[:1500]
'ufeffThe Project Gutenberg eBook of Anna Kareninarn    rnThis ebook is for the use of anyone anywhere in the United States andrnmost other parts of the world at no cost and with almost no restrictionsrnwhatsoever. You may copy it, give it away or re-use it under the termsrnof the Project Gutenberg License included with this ebook or onlinernat www.gutenberg.org. If you are not located in the United States,rnyou will have to check the laws of the country where you are locatedrnbefore using this eBook.rnrnTitle: Anna KareninarnrnrnAuthor: graf Leo TolstoyrnrnTranslator: Constance GarnettrnrnRelease date: July 1, 1998 [eBook #1399]rn                Most recently updated: April 9, 2023rnrnLanguage: Englishrnrnrnrn*** START OF THE PROJECT GUTENBERG EBOOK ANNA KARENINA ***rn[Illustration]rnrnrnrnrn ANNA KARENINA rnrn by Leo Tolstoy rnrn Translated by Constance Garnett rnrnContentsrnrnrn PART ONErn PART TWOrn PART THREErn PART FOURrn PART FIVErn PART SIXrn PART SEVENrn PART EIGHTrnrnrnrnrnPART ONErnrnChapter 1rnrnrnHappy families are all alike; every unhappy family is unhappy in itsrnown way.rnrnEverything was in confusion in the Oblonskys’ house. The wife hadrndiscovered that the husband was carrying on an intrigue with a Frenchrngirl, who had been a governess in their family, and she had announcedrnto her husband that she could not go on living in the same house withrnhim. This position of affairs had now lasted three days, and not onlyrnthe husband and wife themselves, but all the me'
anna_karenina_de[:1500]
'The Project Gutenberg EBook of Anna Karenina, 1. Band, by Leo N. TolstoirnrnThis eBook is for the use of anyone anywhere at no cost and withrnalmost no restrictions whatsoever.  You may copy it, give it away orrnre-use it under the terms of the Project Gutenberg License includedrnwith this eBook or online at www.gutenberg.orgrnrnrnTitle: Anna Karenina, 1. BandrnrnAuthor: Leo N. TolstoirnrnRelease Date: February 18, 2014 [EBook #44956]rnrnLanguage: GermanrnrnCharacter set encoding: ISO-8859-1rnrn*** START OF THIS PROJECT GUTENBERG EBOOK ANNA KARENINA, 1. BAND ***rnrnrnrnrnProduced by Norbert H. Langkau, Jens Nordmann and thernOnline Distributed Proofreading Team at http://www.pgdp.netrnrnrnrnrnrnrnrnrnrn                             Anna Karenina.rnrnrn                        Roman aus dem Russischenrnrn                                  desrnrn                         Grafen Leo N. Tolstoi.rnrnrnrn                  Nach der siebenten Auflage übersetztrnrn                                  vonrnrn                              Hans Moser.rnrnrn                              Erster Band.rnrnrnrn                                Leipzigrnrn                Druck und Verlag von Philipp Reclam jun.rnrn                   *       *       *       *       *rnrnrnrnrn                              Erster Teil.rnrn                               »Die Rache ist mein, ich will vergelten.«rnrn                                   1.rnrnrnAlle glücklichen Familien sind einander ähnlich; jede unglücklich'

テキストのクリーンナップ

ダウンロードした書籍には、本文の前後にサービス情報が含まれる場合があります。テキストには、異なる書式スタイルとマークアップが含まれる場合があります。例えば、強調または斜体にするために、異なる言語のフレーズがアンダースコアで囲まれている場合があります。

Yes, Alabin was giving a dinner on glass tables, and the tables sang, Il mio tesoro—not Il mio tesoro though, but something better, and there were some sort of little decanters on the table, and they were women, too,” he remembered.

テキストをクリーンアップして正規化しない限り、パイプラインの次のステージを完了することは困難です。形式が異なる場合があるため、このステージでは手動作業が必要です。例えば、ドイツ語版のメインコンテンツは *       *       *       *       * で囲まれているため、これらのアスタリスクが最初に出現する前と最後に出現した後はすべて削除しても安全です。

ヒント: 一般的な欠陥を除去するテキスト・クリーニング・ライブラリーがあります。テキストのソースが分かっている場合は、そのソース用に設計されたライブラリー (gutenberg_cleaner など) を探すことができます。これらのライブラリーは手動作業を軽減し、プロセスを自動化することもできます。

import re
from contextlib import contextmanager
from tqdm.auto import tqdm


start_pattern_en = r"\nPART ONE"
anna_karenina_en = re.split(start_pattern_en, anna_karenina_en)[1].strip()

end_pattern_en = "*** END OF THE PROJECT GUTENBERG EBOOK ANNA KARENINA ***"
anna_karenina_en = anna_karenina_en.split(end_pattern_en)[0].strip()
start_pattern_de = "*       *       *       *       *"
anna_karenina_de = anna_karenina_de.split(start_pattern_de, maxsplit=1)[1].strip()
anna_karenina_de = anna_karenina_de.rsplit(start_pattern_de, maxsplit=1)[0].strip()
anna_karenina_en = anna_karenina_en.replace("\r\n", "\n")
anna_karenina_de = anna_karenina_de.replace("\r\n", "\n")

このノートブックでは、最初の章のみを扱います。

chapter_pattern_en = r"Chapter \d?\d"
chapter_1_en = re.split(chapter_pattern_en, anna_karenina_en)[1].strip()
chapter_pattern_de = r"\d?\d.\n\n"
chapter_1_de = re.split(chapter_pattern_de, anna_karenina_de)[1].strip()

それを切り抜いて、いくつかのクリーニング関数を定義しましょう。

def remove_single_newline(text: str) -> str:
    return re.sub(r"\n(?!\n)", " ", text)


def unify_quotes(text: str) -> str:
    return re.sub(r"['\"»«“”]", '"', text)


def remove_markup(text: str) -> str:
    text = text.replace(">=", "").replace("=<", "")
    return re.sub(r"_\w|\w_", "", text)

クリーニング機能を単一のパイプラインに結合します。tqdm ライブラリーは、実行の進行状況を追跡するの使用されます。進行状況インジケーターが必要ない場合は、コンテキスト・マネージャーを定義して、オプションで進行状況インジケーターを無効にします。

disable_tqdm = False


@contextmanager
def disable_tqdm_context():
    global disable_tqdm
    disable_tqdm = True
    yield
    disable_tqdm = False


def clean_text(text: str) -> str:
    text_cleaning_pipeline = [
        remove_single_newline,
        unify_quotes,
        remove_markup,
    ]
    progress_bar = tqdm(text_cleaning_pipeline, disable=disable_tqdm)
    for clean_func in progress_bar:
        progress_bar.set_postfix_str(clean_func.__name__)
        text = clean_func(text)
    return text


chapter_1_en = clean_text(chapter_1_en)
chapter_1_de = clean_text(chapter_1_de)
0%|          | 0/3 [00:00<?, ?it/s]
0%|          | 0/3 [00:00<?, ?it/s]

テキストの分割

テキストを文に分割するのは、テキスト処理において困難な作業です。この問題は文境界の曖昧さの解消と呼ばれ、ヒューリスティックまたはマシンラーニング・モデルを使用して解決できます。このノートブックでは、テキストを文に分割するルールが言語によって異なる可能性があるため、pysbd ライブラリーのセグメンターを使用します。セグメンターは ISO 言語コードで初期化されます。

ヒント: Gutendex から取得した book_metadata には言語コードも含まれており、パイプラインのこの部分の自動化が可能になります。

import pysbd


splitter_en = pysbd.Segmenter(language="en", clean=True)
splitter_de = pysbd.Segmenter(language="de", clean=True)


sentences_en = splitter_en.segment(chapter_1_en)
sentences_de = splitter_de.segment(chapter_1_de)

len(sentences_en), len(sentences_de)
(32, 34)

文の埋め込みを取得

次のステップは、文章をベクトル表現に変換することです。BERT などのトランスフォーマー・エンコーダー・モデルは高品質の埋め込みを提供しますが、速度が低下する可能性があります。さらに、モデルは選択した両方の言語をサポートする必要があります。言語ペアごとに個別のモデルをトレーニングするとコストがかかる可能性があるため、複数の言語で同時に事前トレーニングされたモデルが多数あります。例えば、次のとおりです。

LaBSE は、Language-agnostic BERT Sentence Embedding の略で、109 以上の言語をサポートします。BERT モデルと同じアーキテクチャーを持っていますが、翻訳ペアに対して同一の埋め込みを生成する別のタスクでトレーニングされています。

image01

そのため、LaBSE はここでのタスクに最適な選択肢となり、さまざまな言語ペアで再利用しても良好な結果が得られます。

from typing import List, Union, Dict
from transformers import AutoTokenizer, AutoModel, BertModel
import numpy as np
import torch
from openvino.runtime import CompiledModel as OVModel
import openvino as ov


model_id = "rasa/LaBSE"
pt_model = AutoModel.from_pretrained(model_id)
tokenizer = AutoTokenizer.from_pretrained(model_id)

モデルには、last_hidden_statepooler_output という 2 つの出力があります。埋め込みを生成するには、特別な [CLS] トークンに対応する last_hidden_state の最初のベクトル、または 2 番目の入力のベクトル全体のいずれかを使用できます。通常は 2 番目のオプションが使用されますが、このタスクでは最初のオプションもうまく機能するため、最初のオプションを使用します。さまざまな出力を自由に試して、最適な出力を見つけてください。

def get_embeddings(
    sentences: List[str],
    embedding_model: Union[BertModel, OVModel],
) -> np.ndarray:
    if isinstance(embedding_model, OVModel):
        embeddings = [
            embedding_model(tokenizer(sent, return_tensors="np").data)[
                "last_hidden_state"
            ][0][0]
            for sent in tqdm(sentences, disable=disable_tqdm)
        ]
        return np.vstack(embeddings)
    else:
        embeddings = [
            embedding_model(**tokenizer(sent, return_tensors="pt"))[
                "last_hidden_state"
            ][0][0]
            for sent in tqdm(sentences, disable=disable_tqdm)
        ]
        return torch.vstack(embeddings)


embeddings_en_pt = get_embeddings(sentences_en, pt_model)
embeddings_de_pt = get_embeddings(sentences_de, pt_model)
0%|          | 0/32 [00:00<?, ?it/s]
0%|          | 0/34 [00:00<?, ?it/s]

LaBSE モデルは非常に大きく、一部のハードウェアでは推論が遅くなる可能性があるため、OpenVINO を使用して最適化しましょう。モデル変換 API は、PyTorch/Transformers モデル・オブジェクトとモデル入力に関する追加情報を受け入れます。PyTorch は推論中にモデル実行グラフを動的に構築するため、モデル実行グラフをトレースするには example_input が必要です。変換されたモデルは、使用する前に Core オブジェクトを使用してターゲットデバイス用にコンパイルする必要があります。

# 3 inputs with dynamic axis [batch_size, sequence_length] and type int64
inputs_info = [([-1, -1], ov.Type.i64)] * 3
ov_model = ov.convert_model(
    pt_model,
    example_input=tokenizer("test", return_tensors="pt").data,
    input=inputs_info,
)

core = ov.Core()
compiled_model = core.compile_model(ov_model, "CPU")

embeddings_en = get_embeddings(sentences_en, compiled_model)
embeddings_de = get_embeddings(sentences_de, compiled_model)
0%|          | 0/32 [00:00<?, ?it/s]
0%|          | 0/34 [00:00<?, ?it/s]

インテル® Core™ i9-10980XE CPU 上で、PyTorch モデルは 1 秒あたり 40 ~ 43 文を処理しました。OpenVINO による最適化後、処理速度は 1 秒あたり 56 ~ 60 文に向上しました。わずか数行のコードでパフォーマンスが約 40% 向上します。モデルの予測が許容範囲内にあるか確認してみましょう。

np.all(np.isclose(embeddings_en, embeddings_en_pt.detach().numpy(), atol=1e-3))
True

文の配置を計算

前のステップの埋め込み行列を使用して、アライメントを計算できます。

  1. 文の各ペア間の文の類似性を計算します。
  2. 類似度行列の行と列の値を、[-1, 1] などの指定された範囲に変換します。
  3. 値をしきい値と比較して、0 と 1 のブール行列を取得します。
  4. 両方の行列に 1 を含む文のペアは、モデルに従って整列される必要があります。

結果の行列を視覚化し、変換されたモデルの結果が元のモデルと同じであることも確認します。

import seaborn as sns
import matplotlib.pyplot as plt


sns.set_style("whitegrid")


def transform(x):
    x = x - np.mean(x)
    return x / np.var(x)


def calculate_alignment_matrix(
    first: np.ndarray, second: np.ndarray, threshold: float = 1e-3
) -> np.ndarray:
    similarity = first @ second.T  # 1
    similarity_en_to_de = np.apply_along_axis(transform, -1, similarity)  # 2
    similarity_de_to_en = np.apply_along_axis(transform, -2, similarity)  # 2

    both_one = (similarity_en_to_de > threshold) * (
        similarity_de_to_en > threshold
    )  # 3 and 4
    return both_one


threshold = 0.028

alignment_matrix = calculate_alignment_matrix(embeddings_en, embeddings_de, threshold)
alignment_matrix_pt = calculate_alignment_matrix(
    embeddings_en_pt.detach().numpy(),
    embeddings_de_pt.detach().numpy(),
    threshold,
)

graph, axis = plt.subplots(1, 2, figsize=(10, 5), sharey=True)

for matrix, ax, title in zip(
    (alignment_matrix, alignment_matrix_pt), axis, ("OpenVINO", "PyTorch")
):
    plot = sns.heatmap(matrix, cbar=False, square=True, ax=ax)
    plot.set_title(f"Sentence Alignment Matrix {title}")
    plot.set_xlabel("German")
    if title == "OpenVINO":
        plot.set_ylabel("English")

graph.tight_layout()
../_images/220-cross-lingual-books-alignment-with-output_31_0.png

アライメント行列を視覚化して比較した後、Python でのアライメントの操作をより便利にするため、それらを辞書に変換しましょう。辞書のキーは英語の文番号、値はドイツ語の文番号のリストになります。

def make_alignment(alignment_matrix: np.ndarray) -> Dict[int, List[int]]:
    aligned = {idx: [] for idx, sent in enumerate(sentences_en)}
    for en_idx, de_idx in zip(*np.nonzero(alignment_matrix)):
        aligned[en_idx].append(de_idx)
    return aligned


aligned = make_alignment(alignment_matrix)
aligned
{0: [0],
 1: [2],
 2: [3],
 3: [4],
 4: [5],
 5: [6],
 6: [7],
 7: [8],
 8: [9, 10],
 9: [11],
 10: [13, 14],
 11: [15],
 12: [16],
 13: [17],
 14: [],
 15: [18],
 16: [19],
 17: [20],
 18: [21],
 19: [23],
 20: [24],
 21: [25],
 22: [26],
 23: [],
 24: [27],
 25: [],
 26: [28],
 27: [29],
 28: [30],
 29: [31],
 30: [32],
 31: [33]}

後処理文の位置合わせ

英語の文 #14 がドイツ語の文にマッピングされていないなど、結果の配置にはいくつかのギャップがあります。これについて考えられる理由は次のとおりです。

  1. 他の文には同等の文はありませんが、そのような場合、モデルは正しく機能しています。

  2. この文には別の言語の同等の文がありますが、モデルはそれを識別できませんでした。しきい値が高すぎるか、モデルの感度が十分でない可能性があります。これに対処するには、しきい値を下げるか、別のモデルを試してください。

  3. 文には別の言語の同等のテキスト部分があります。これは、文の分割が細かすぎるか粗すぎることを意味します。この問題を解決するには、テキストのクリーニングと分割のステップを調整してみてください。

  4. 2 と 3 の組み合わせ。モデルの感度とテキストの準備手順の両方を調整する必要があります。

この問題に対処するもう 1 つの解決策は、ヒューリスティックを適用することです。ご覧のとおり、英語の文 13 はドイツ語の 17、15 から 18 に対応しています。おそらく、英語の文 14 はドイツ語の文 17 または 18 の一部です。モデルを使用して類似性を比較することで、最適なアライメントを選択できます。

文の配置を視覚化

最終的な調整を評価し、パイプラインの結果を改善する最適な方法を選択するため、HTML と JS を使用して対話型のテーブルを作成します。

from IPython.display import display, HTML
from itertools import zip_longest
from io import StringIO


def create_interactive_table(
    list1: List[str], list2: List[str], mapping: Dict[int, List[int]]
) -> str:
    def inverse_mapping(mapping):
        inverse_map = {idx: [] for idx in range(len(list2))}

        for key, values in mapping.items():
            for value in values:
                inverse_map[value].append(key)

        return inverse_map

    inversed_mapping = inverse_mapping(mapping)

    table_html = StringIO()
    table_html.write(
        '<table id="mappings-table"><tr><th>Sentences EN</th><th>Sentences DE</th></tr>'
    )
    for i, (first, second) in enumerate(zip_longest(list1, list2)):
        table_html.write("<tr>")
        if i < len(list1):
            table_html.write(f'<td id="list1-{i}">{first}</td>')
        else:
            table_html.write("<td></td>")
        if i < len(list2):
            table_html.write(f'<td id="list2-{i}">{second}</td>')
        else:
            table_html.write("<td></td>")
        table_html.write("</tr>")

    table_html.write("</table>")

    hover_script = (
        """
    <script type="module">
      const highlightColor = '#0054AE';
      const textColor = 'white'
      const mappings = {
        'list1': """
        + str(mapping)
        + """,
        'list2': """
        + str(inversed_mapping)
        + """
      };

      const table = document.getElementById('mappings-table');
      let highlightedIds = [];

      table.addEventListener('mouseover', ({ target }) => {
        if (target.tagName !== 'TD' || !target.id) {
          return;
        }

        const [listName, listId] = target.id.split('-');
        const mappedIds = mappings[listName]?.[listId]?.map((id) => `${listName === 'list1' ? 'list2' : 'list1'}-${id}`) || [];
        const idsToHighlight = [target.id, ...mappedIds];

        setBackgroud(idsToHighlight, highlightColor, textColor);
        highlightedIds = idsToHighlight;
      });

      table.addEventListener('mouseout', () => setBackgroud(highlightedIds, ''));

      function setBackgroud(ids, color, text_color="unset") {
        ids.forEach((id) => {
            document.getElementById(id).style.backgroundColor = color;
            document.getElementById(id).style.color = text_color
        });
      }
    </script>
    """
    )
    table_html.write(hover_script)
    return table_html.getvalue()
html_code = create_interactive_table(sentences_en, sentences_de, aligned)
display(HTML(html_code))
英語の文ドイツ語の文
Happy families are all alike; every unhappy family is unhappy in its own way.Alle glücklichen Familien sind einander ähnlich; jede unglückliche Familie ist auf hr Weise unglücklich.
Everything was in confusion in the Oblonskys’ house.--
The wife had discovered that the husband was carrying on an intrigue with a French girl, who had been a governess in their family, and she had announced to her husband that she could not go on living in the same house with him.Im Hause der Oblonskiy herrschte allgemeine Verwirrung.
This position of affairs had now lasted three days, and not only the husband and wife themselves, but all the members of their family and household, were painfully conscious of it.Die Dame des Hauses hatte in Erfahrung gebracht, daß ihr Gatte mit der im Hause gewesenen französischen Gouvernante ein Verhältnis unterhalten, und ihm erklärt, sie könne fürderhin nicht mehr mit ihm unter einem Dache bleiben.
Every person in the house felt that there was no sense in their living together, and that the stray people brought together by chance in any inn had more in common with one another than they, the members of the family and household of the Oblonskys.Diese Situation währte bereits seit drei Tagen und sie wurde nicht allein von den beiden Ehegatten selbst, nein auch von allen Familienmitgliedern und dem Personal aufs Peinlichste empfunden.
The wife did not leave her own room, the husband had not been at home for three days.Sie alle fühlten, daß in ihrem Zusammenleben kein höherer Gedanke mehr liege, daß die Leute, welche auf jeder Poststation sich zufällig träfen, noch enger zu einander gehörten, als sie, die Glieder der Familie selbst, und das im Hause geborene und aufgewachsene Gesinde der Oblonskiy.
The children ran wild all over the house; the English governess quarreled with the housekeeper, and wrote to a friend asking her to look out for a new situation for her; the man-cook had walked off the day before just at dinner time; the kitchen-maid, and the coachman had given warning.Die Herrin des Hauses verließ ihre Gemächer nicht, der Gebieter war schon seit drei Tagen abwesend.
Three days after the quarrel, Prince Stepan Arkadyevitch Oblonsky—Stiva, as he was called in the fashionable world—woke up at his usual hour, that is, at eight o’clock in the morning, not in his wife’s bedroom, but on the leather-covered sofa in his study.Die Kinder liefen wie verwaist im ganzen Hause umher, die Engländerin schalt auf die Wirtschafterin und schrieb an eine Freundin, diese möchte ihr eine neue Stellung verschaffen, der Koch hatte bereits seit gestern um die Mittagszeit das Haus verlassen und die Köchin, sowie der Kutscher hatten ihre Rechnungen eingereicht.
He turned over his stout, well-cared-for person on the springy sofa, as though he would sink into a long sleep again; he vigorously embraced the pillow on the other side and buried his face in it; but all at once he jumped up, sat up on the sofa, and opened his eyes.Am dritten Tage nach der Scene erwachte der Fürst Stefan Arkadjewitsch Oblonskiy -- Stiwa hieß er in der Welt -- um die gewöhnliche Stunde, das heißt um acht Uhr morgens, aber nicht im Schlafzimmer seiner Gattin, sondern in seinem Kabinett auf dem Saffiandiwan.
"Yes, yes, how was it now?" he thought, going over his dream.Er wandte seinen vollen verweichlichten Leib auf den Sprungfedern des Diwans, als wünsche er noch weiter zu schlafen, während er von der andern Seite innig ein Kissen umfaßte und an die Wange drückte.
"Now, how was it? To be sure! Alabin was giving a dinner at Darmstadt; no, not Darmstadt, but something American. Yes, but then, Darmstadt was in America. Yes, Alabin was giving a dinner on glass tables, and the tables sang, l mio tesor—not l mio tesor though, but something better, and there were some sort of little decanters on the table, and they were women, too," he remembered.Plötzlich aber sprang er empor, setzte sich aufrecht und öffnete die Augen.
Stepan Arkadyevitch’s eyes twinkled gaily, and he pondered with a smile."Ja, ja, wie war doch das?" sann er, über seinem Traum grübelnd.
"Yes, it was nice, very nice. There was a great deal more that was delightful, only there’s no putting it into words, or even expressing it in one’s thoughts awake.""Wie war doch das?
And noticing a gleam of light peeping in beside one of the serge curtains, he cheerfully dropped his feet over the edge of the sofa, and felt about with them for his slippers, a present on his last birthday, worked for him by his wife on gold-colored morocco.Richtig; Alabin gab ein Diner in Darmstadt; nein, nicht in Darmstadt, es war so etwas Amerikanisches dabei.
And, as he had done every day for the last nine years, he stretched out his hand, without getting up, towards the place where his dressing-gown always hung in his bedroom.Dieses Darmstadt war aber in Amerika, ja, und Alabin gab das Essen auf gläsernen Tischen, ja, und die Tische sangen: Il mio tesoro -- oder nicht so, es war etwas Besseres, und gewisse kleine Karaffen, wie Frauenzimmer aussehend," -- fiel ihm ein.
And thereupon he suddenly remembered that he was not sleeping in his wife’s room, but in his study, and why: the smile vanished from his face, he knitted his brows.Die Augen Stefan Arkadjewitschs blitzten heiter, er sann und lächelte.
"Ah, ah, ah! Oo!..." he muttered, recalling everything that had happened."Ja, es war hübsch, sehr hübsch. Es gab viel Ausgezeichnetes dabei, was man mit Worten nicht schildern könnte und in Gedanken nicht ausdrücken."
And again every detail of his quarrel with his wife was present to his imagination, all the hopelessness of his position, and worst of all, his own fault.Er bemerkte einen Lichtstreif, der sich von der Seite durch die baumwollenen Stores gestohlen hatte und schnellte lustig mit den Füßen vom Sofa, um mit ihnen die von seiner Gattin ihm im vorigen Jahr zum Geburtstag verehrten gold- und saffiangestickten Pantoffeln zu suchen; während er, einer alten neunjährigen Gewohnheit folgend, ohne aufzustehen mit der Hand nach der Stelle fuhr, wo in dem Schlafzimmer sonst sein Morgenrock zu hängen pflegte.
"Yes, she won’t forgive me, and she can’t forgive me. And the most awful thing about it is that it’s all my fault—all my fault, though I’m not to blame. That’s the point of the whole situation," he reflected.Hierbei erst kam er zur Besinnung; er entsann sich jäh wie es kam, daß er nicht im Schlafgemach seiner Gattin, sondern in dem Kabinett schlief; das Lächeln verschwand von seinen Zügen und er runzelte die Stirn.
"Oh, oh, oh!" he kept repeating in despair, as he remembered the acutely painful sensations caused him by this quarrel."O, o, o, ach," brach er jammernd aus, indem ihm alles wieder einfiel, was vorgefallen war.
Most unpleasant of all was the first minute when, on coming, happy and good-humored, from the theater, with a huge pear in his hand for his wife, he had not found his wife in the drawing-room, to his surprise had not found her in the study either, and saw her at last in her bedroom with the unlucky letter that revealed everything in her hand.Vor seinem Innern erstanden von neuem alle die Einzelheiten des Auftritts mit seiner Frau, erstand die ganze Mißlichkeit seiner Lage und -- was ihm am peinlichsten war -- seine eigene Schuld.
She, his Dolly, forever fussing and worrying over household details, and limited in her ideas, as he considered, was sitting perfectly still with the letter in her hand, looking at him with an expression of horror, despair, and indignation."Ja wohl, sie wird nicht verzeihen, sie kann nicht verzeihen, und am Schrecklichsten ist, daß die Schuld an allem nur ich selbst trage -- ich bin schuld -- aber nicht schuldig!
"What’s this? this?" she asked, pointing to the letter.Und hierin liegt das ganze Drama," dachte er, "o weh, o weh!"
And at this recollection, Stepan Arkadyevitch, as is so often the case, was not so much annoyed at the fact itself as at the way in which he had met his wife’s words.Er sprach voller Verzweiflung, indem er sich alle die tiefen Eindrücke vergegenwärtigte, die er in jener Scene erhalten.
There happened to him at that instant what does happen to people when they are unexpectedly caught in something very disgraceful.Am unerquicklichsten war ihm jene erste Minute gewesen, da er, heiter und zufrieden aus dem Theater heimkehrend, eine ungeheure Birne für seine Frau in der Hand, diese weder im Salon noch im Kabinett fand, und sie endlich im Schlafzimmer antraf, jenen unglückseligen Brief, der alles entdeckte, in den Händen.
He did not succeed in adapting his face to the position in which he was placed towards his wife by the discovery of his fault.Sie, die er für die ewig sorgende, ewig sich mühende, allgegenwärtige Dolly gehalten, sie saß jetzt regungslos, den Brief in der Hand, mit dem Ausdruck des Entsetzens, der Verzweiflung und der Wut ihm entgegenblickend.
Instead of being hurt, denying, defending himself, begging forgiveness, instead of remaining indifferent even—anything would have been better than what he did do—his face utterly involuntarily (reflex spinal action, reflected Stepan Arkadyevitch, who was fond of physiology)—utterly involuntarily assumed its habitual, good-humored, and therefore idiotic smile."Was ist das?" frug sie ihn, auf das Schreiben weisend, und in der Erinnerung hieran quälte ihn, wie das oft zu geschehen pflegt, nicht sowohl der Vorfall selbst, als die Art, wie er ihr auf diese Worte geantwortet hatte.
This idiotic smile he could not forgive himself.Es ging ihm in diesem Augenblick, wie den meisten Menschen, wenn sie unerwartet eines zu schmählichen Vergehens überführt werden.
Catching sight of that smile, Dolly shuddered as though at physical pain, broke out with her characteristic heat into a flood of cruel words, and rushed out of the room.Er verstand nicht, sein Gesicht der Situation anzupassen, in welche er nach der Entdeckung seiner Schuld geraten war, und anstatt den Gekränkten zu spielen, sich zu verteidigen, sich zu rechtfertigen und um Verzeihung zu bitten oder wenigstens gleichmütig zu bleiben -- alles dies wäre noch besser gewesen als das, was er wirklich that -- verzogen sich seine Mienen ("Gehirnreflexe" dachte Stefan Arkadjewitsch, als Liebhaber von Physiologie) unwillkürlich und plötzlich zu seinem gewohnten, gutmütigen und daher ziemlich einfältigen Lächeln.
Since then she had refused to see her husband.Dieses dumme Lächeln konnte er sich selbst nicht vergeben.
"It’s that idiotic smile that’s to blame for it all," thought Stepan Arkadyevitch.Als Dolly es gewahrt hatte, erbebte sie, wie von einem physischen Schmerz, und erging sich dann mit der ihr eigenen Leidenschaftlichkeit in einem Strom bitterer Worte, worauf sie das Gemach verließ.
"But what’s to be done? What’s to be done?" he said to himself in despair, and found no answer.Von dieser Zeit an wollte sie ihren Gatten nicht mehr sehen.
"An allem ist das dumme Lächeln schuld," dachte Stefan Arkadjewitsch.
"Aber was soll ich thun, was soll ich thun?" frug er voll Verzweiflung sich selbst, ohne eine Antwort zu finden.

パイプラインがドイツ語のテキストを完全にクリーンアップしていないため、2 番目の文が -- のみで構成されるなどの問題が発生していることが分かります。良い点としては、ドイツ語翻訳の分割された文が単一の英語の文と正しく並んでいることです。全体として、パイプラインはすでにうまく機能していますが、まだ改善の余地があります。

将来使用できるように OpenVINO モデルをディスクに保存します。

from openvino.runtime import serialize


ov_model_path = "ov_model/model.xml"
serialize(ov_model, ov_model_path)

ディスクからモデルを読み取るには、Core オブジェクトの read_model メソッドを使用します。

ov_model = core.read_model(ov_model_path)

埋め込み計算を高速化

パイプラインの最も計算が複雑な部分である、埋め込みの取得を高速化する方法を見てみましょう。OpenVINO を使用する場合、モデルを読み込んだ後にコンパイルする必要があるのに疑問を持つかもしれません。これには主に2つの理由があります。

  1. さまざまなデバイスとの互換性。モデルは、CPU、GPU、GNA などの特定のデバイス上で実行するようにコンパイルできます。各デバイスは、異なるデータタイプを操作し、異なる機能をサポートして、特定のコンピューティング・モデルのニューラル・ネットワークを変更することでパフォーマンスを向上させることができます。OpenVINO を使用すると、それぞれのハードウェアに最適化されたネットワークのコピーを複数保存する必要がなくなります。ユニバーサルな OpenVINO モデル表現で十分です。
  2. さまざまなシナリオに合わせた最適化。例えば、1 つのシナリオでは、モデル推論の開始と終了の間の時間を最小限に抑えることが優先されます (レイテンシー指向の最適化)。ここでは、モデルが 1 秒あたりにどれだけのテキストを処理できるか (スループット指向の最適化) のほうが重要です。

スループットが最適化されたモデルを取得するには、コンパイル中にパフォーマンスのヒントを構成として渡します。次に、OpenVINO は、利用可能なハードウェアでの実行に最適なパラメーターを選択します。

from typing import Any


compiled_throughput_hint = core.compile_model(
    ov_model,
    device_name="CPU",
    config={"PERFORMANCE_HINT": "THROUGHPUT"},
)

ハードウェアの使用率をさらに最適化するため、推論モードを同期 (Sync) から非同期 (Async) に変更しましょう。同期 API は使い始めるのが簡単かもしれませんが、運用コードでは非同期 (コールバック・ベース) API を使用することを推奨します。これは、任意の数の要求に対するフロー制御を実装する最も一般的でスケーラブルな方法です。

非同期モードで作業するには、次の 2 つを定義する必要があります。

  1. AsyncInferQueue をインスタンス化して、推論要求を設定できます。

  2. 出力要求が実行され、その結果が処理された後に呼び出されるコールバック関数を定義します。

モデル入力に加えて、後処理に必要なデータをキューに渡すことができます。事前にゼロ埋め込み行列を作成し、推論要求が実行されるときにそれを埋めることができます。

def get_embeddings_async(sentences: List[str], embedding_model: OVModel) -> np.ndarray:
    def callback(infer_request: ov.InferRequest, user_data: List[Any]) -> None:
        embeddings, idx, pbar = user_data
        embedding = infer_request.get_output_tensor(0).data[0, 0]
        embeddings[idx] = embedding
        pbar.update()

    infer_queue = ov.AsyncInferQueue(embedding_model)
    infer_queue.set_callback(callback)

    embedding_dim = (
        embedding_model.output(0).get_partial_shape().get_dimension(2).get_length()
    )
    embeddings = np.zeros((len(sentences), embedding_dim))

    with tqdm(total=len(sentences), disable=disable_tqdm) as pbar:
        for idx, sent in enumerate(sentences):
            tokenized = tokenizer(sent, return_tensors="np").data

            infer_queue.start_async(tokenized, [embeddings, idx, pbar])

        infer_queue.wait_all()

    return embeddings

モデルを比較して結果をプロットしてみましょう。

注: より正確なベンチマークを取得するには、ベンチマーク Python ツールを使用します。

number_of_chars = 15_000
more_sentences_en = splitter_en.segment(clean_text(anna_karenina_en[:number_of_chars]))
len(more_sentences_en)
0%|          | 0/3 [00:00<?, ?it/s]
112
import pandas as pd
from time import perf_counter


benchmarks = [
    (pt_model, get_embeddings, "PyTorch"),
    (compiled_model, get_embeddings, "OpenVINO\nSync"),
    (
        compiled_throughput_hint,
        get_embeddings_async,
        "OpenVINO\nThroughput Hint\nAsync",
    ),
]

number_of_sentences = 100
benchmark_data = more_sentences_en[: min(number_of_sentences, len(more_sentences_en))]

benchmark_results = {name: [] for *_, name in benchmarks}

benchmarks_iterator = tqdm(benchmarks, leave=False, disable=disable_tqdm)
for model, func, name in benchmarks_iterator:
    printable_name = name.replace("\n", " ")
    benchmarks_iterator.set_description(f"Run benchmark for {printable_name} model")
    for run in tqdm(
        range(10 + 1), leave=False, desc="Benchmark Runs: ", disable=disable_tqdm
    ):
        with disable_tqdm_context():
            start = perf_counter()
            func(benchmark_data, model)
            end = perf_counter()
        benchmark_results[name].append(len(benchmark_data) / (end - start))

benchmark_dataframe = pd.DataFrame(benchmark_results)[1:]
0%|          | 0/3 [00:00<?, ?it/s]
Benchmark Runs:   0%|          | 0/11 [00:00<?, ?it/s]
Benchmark Runs:   0%|          | 0/11 [00:00<?, ?it/s]
Benchmark Runs:   0%|          | 0/11 [00:00<?, ?it/s]
cpu_name = core.get_property("CPU", "FULL_DEVICE_NAME")

plot = sns.barplot(benchmark_dataframe, errorbar="sd")
plot.set(
    ylabel="Sentences Per Second", title=f"Sentence Embeddings Benchmark\n{cpu_name}"
)
perf_ratio = benchmark_dataframe.mean() / benchmark_dataframe.mean()[0]
plot.spines["right"].set_visible(False)
plot.spines["top"].set_visible(False)
plot.spines["left"].set_visible(False)
../_images/220-cross-lingual-books-alignment-with-output_48_0.png

インテル® Core™ i9-10980XE プロセッサーでは、OpenVINO モデルは元の PyTorch モデルと比較して 1 秒あたり 45% 多くの文を処理しました。スループットヒント付きの非同期モードを使用すると、パフォーマンスが 3.21 倍 (または 221%) 向上します。

このノートブックで使用されているテクニックに関する情報が記載された便利なリンクを以下に示します。