Zhang+'22 - Multi-View Document Representation Learning for Open-Domain Dense Retrieval (ACL)

https://arxiv.org/abs/2203.08372

1. どんなもの?

  • 文書を multi-view として表現する Dense Retrieval 手法を提案。SQuAD, NQ, TriviaQA における検索性能で SoTA を達成。

2. 先行研究と比べてどこがすごい?

  • 文書を一つのベクトルで表現する従来手法に対して、文書から複数の意味表現を獲得することで、詳細な表現のアラインメントを学習。 f:id:catshun:20220318115350p:plain

  • ColBERT や PolyEncoder, DRPQ などの手法とは異なり、直接的な近傍探索が可能。 f:id:catshun:20220318124433p:plain

3. 技術や手法のキモはどこ?

f:id:catshun:20220317205809p:plain
f:id:catshun:20220318121635p:plain

  • [CLS] トークンの代わりに、ランダムに初期化された複数の [VIE] トークンを使用する。各 [VIE] トークンに対して、質問の [VIE] トークンとの内積値を計算し、その最大値を global 類似度とする。viewer の数はハイパーパラメータ。 f:id:catshun:20220318120742p:plain
    f:id:catshun:20220318121602p:plain

  • multi-view 埋め込み空間の uniformity を改善するため、Local Uniformity Loss を提案。最大値となる [VIE] に対して近づける & その他の [VIE] に対して遠ざけるように学習を行う。 f:id:catshun:20220318121759p:plain

  • 温度パラメータは以下のように設定。t は学習エポック数。 f:id:catshun:20220318123141p:plain

4. どうやって有効だと検証した?

f:id:catshun:20220318124013p:plain

Viewer 数

f:id:catshun:20220318124144p:plain

アブレーション

f:id:catshun:20220318124308p:plain

5. 議論はある?

6. 次に読むべき論文は?

  • Luan+'21 - Sparse, Dense, and Attentional Representations for Text Retrieval (TACL) [ACL Anthology][arXiv]

Klein+'22 - SCD: Self-Contrastive Decorrelation for Sentence Embeddings (ACL)

1. どんなもの?

  • 自己教師の枠組みで文の類似表現を学習する Self-Contrastive Decorrelation (SCD) を提案。STS タスクにおいて、contrastive pair を用いずに SoTA と同程度の性能を達成。

2. 先行研究と比べてどこがすごい?

  • 一般的にバッチサイズを大きくすると検索性能(表現学習の効率)が良くなるとされているが、本研究では contrastive pair を使用せずに同一文に対する pos-neg を作成する拡張法を提案。
  • 入力文に対して摂動を与える対照学習では、摂動に対する意味表現の変化(どの摂動が正例・負例を作成するか)について不明だった。self-contrastive divergence (repulsion) および feature decorrelation (attraction) の異なるロス関数を設計する。

f:id:catshun:20220318090413p:plain

3. 技術や手法のキモはどこ?

f:id:catshun:20220317205931p:plain

  • repulsion/attraction の役割を持つ二つのロスに対して共同ロスを最小化する(ことで対照ロスにおける埋め込み表現の悪化を回避する):
    • Self-Contrastive Divergence (repulsion): 異なる high/low dropout 率を用いて埋め込み表現を用いる(通常これらは attraction として用いられるが、本研究では repulsion を行う)。
      • N はバッチサンプルサイズ f:id:catshun:20220318082046p:plain
    • Feature Decorrelation (attraction):
      • p は線形層で投影された埋め込み表現、i はバッチ内サンプル、j は各次元のインデックスを表す。 f:id:catshun:20220318085359p:plain
        f:id:catshun:20220318090039p:plain

4. どうやって有効だと検証した?

TBA

Semantic Textual Similarity タスク

f:id:catshun:20220317213134p:plain

アブレーション

f:id:catshun:20220317213219p:plain

5. 議論はある?

  • dropout 摂動を repulsion として使用した(low-high dropout 率)ことで、摂動による埋め込み表現間の揺れに対して頑健になる?
  • 学習時のメモリ使用量が大きく改善するかも?

6. 次に読むべき論文は?

Xu+'22 - LaPraDoR: Unsupervised Pretrained Dense Retriever for Zero-Shot Text Retrieval (ACL)

https://arxiv.org/abs/2203.06169)

1. どんなもの?

  • 教師なし事前学習によるテキスト検索モデル LaPraDoR (Large-scale Pretrained Dense Zero-shot Retriever) の提案。BEIR ベンチマークにおいて SoTA を達成。

2. 先行研究と比べてどこがすごい?

  • Training Efficiency

    • 負例数が多いほど高い検索性能を示すことが一般的に知られているが、本研究ではバッチサイズを大きくする代わりにキャッシュを用いた Iterative Contrastive Learning (ICoL) を提案
  • Versatility

    • マルチドメインの C4 を用いて学習
    • 汎化性を高めるため、クエリ→文書 だけでなく、[tex: 22] パターンの検索を組み合わせる
    • BM25 を密検索と結びつけるための Lexicon-Enhanced Dense Retrieval (LEDR) を提案。

3. 技術や手法のキモはどこ?

  • 正例の作成

    • Inverse Cloze Task (Lee+'19): 文書からランダムに選択した文をクエリとして、その他の文を正例とみなす。クロスモダリティに対して使用し、負例はキャッシュされたキューからサンプルしたものを使用する。
    • Dropout as Positive Instance (Gao+'21): 同一文に対して異なる drop mask を適用した二つの出力を正例とみなす。同一モダリティに対して使用し、負例はランダムサンプルしたものを使用する。

  • Iterative Contrastive Learning f:id:catshun:20220317193948p:plain

    • クエリエンコーダの学習(文書エンコーダは凍結):
      • query-to-query ( \mathcal{L_{qq}}):正例は異なる drop mask を適用した文、負例はランダムにサンプルした文を使用。 f:id:catshun:20220317194502p:plain
      • query-to-document ( \mathcal{L_{qd}}):正例は ICT により作成された文書、負例はキャッシュされたキューに含まれている文書からサンプルした文書を使用。
    • 文書エンコーダの学習(クエリエンコーダは凍結):
      • document-to-document ( \mathcal{L_{dd}}):正例は異なる drop mask を適用した文、負例はランダムにサンプルした文を使用。
      • document-to-query ( \mathcal{L_{dq}}):正例は ICT により作成された文書、負例はキャッシュされたキューに含まれている文書からサンプルした文書を使用。
    • 負例にはインバッチネガティブを使用する。

  • キャッシュ

    • Wu+'18 に基づき、GPU 間で共有されるキューを使用。
    • キューに格納されるモダリティは凍結されたエンコーダを使用するため勾配を必要としない。

  • Lexical-Enhanced Dense Retrieval

    • 推論時に Dense Retrieval の類似度と BM25 のスコア値(q∩d のトークンに対する総和)の積を最終スコアとして使用。
    • Dense Retrieval で難しいとされていた汎化性について、BM25 を使用することで補完する。

4. どうやって有効だと検証した?

  • モデル:BERT を用いた Bi-Encoder

f:id:catshun:20220317202348p:plain

Contrastive Strategies

f:id:catshun:20220317202038p:plain

Lexicon-Enhanced Dense Retrieval

f:id:catshun:20220317202304p:plain

Unsupervised Methods

f:id:catshun:20220317202231p:plain

5. 議論はある?

  • ICT を使用しているので、エンコーダ間のパラメータ共有が有効かも?
  • 同一モダリティに対する NCE については、様々な手法が考えられそう
    • Huang+'21 - Multilingual Multimodal Pre-training for Zero-Shot Cross-Lingual Transfer of Vision-Language Models (NAACL) [ACL Anthology]
  • サンプリング方法がランダムなので、改善の余地あり
  • キャッシュを用いた負例の使用、様々なモダリティ間のマッチング、これらの話は詳細なマッチングを使用するモデルでも応用できそう。
  • LEDR についても改善の余地あり(第二回 AI 王の一位解法のクロスエンコーダによるリランキングなども使用できそう)

6. 次に読むべき論文は?

Izacard+'21 - A Memory Efficient Baseline for Open Domain Question Answering

1. どんなもの?

  • DPR のような密なベクトル表現を用いた文書検索では、検索対象のベクトル表現をインデックスとして保持する必要があり、メモリ使用量が高くなってしまう。
  • 本研究では、① 次元数削減 ② ベクトル量子化 ③ 文書フィルタリングの三つの手法が、検索性能に与える影響について調査する。

2. 先行研究と比べてどこがすごい?

  • 検索におけるメモリ効率化について調査

3. 技術や手法のキモはどこ?

  • 設定

    • retriever-generator (DPR-FiD) 型のモデルで評価。FiD は T5 のパラメータで初期化され、DPR の学習では FiD のアテンションスコアに基づくラベルを使用し、知識蒸留の枠組み(Izacard+'20)で学習を行う。
    • デフォルトの設定において、Wikipedia 文書のファイルサイズは 21GB (xz で 3.8GB、lrzip で 3.1GB に圧縮)、文書数は 26M であり、各文書を 100 単語ごとに分割、また 768 次元(float32)のエンコード結果を登録した文書インデックスのサイズは 75GB。
  • 以下でメモリ効率化を行う

    • 線形層+Layer Normalization 層(LN層)で次元数削減(LN層を追加した方が経験的に良い)
    • float32 (4 bytes/dim) → float16 (2 bytes/dim)
    • 直積量子化を使用して  d/n 次元に削減( n はサブベクトルの個数)
    • 線形分類器を用いた文書フィルタリング
      • カテゴリリストとタイトルから各 Wikipedia の文書表現を作成。
      • 学習データの質問に対して DPR で検索された文書を正例とし、学習時は self-training の枠組みで、各イテレーションで質問に対して最も高いスコアで負例と判断された文書を次のイテレーションで負例文書として扱う(初期値はランダムサンプルされた文書とする)。
      • 🤔 実際に検索対象に対するフィルタリングではどのように使用するか詳細な記載がない

        We perform a few iterations of this scheme, and apply the final classifier to filter the Wikipedia dump.

4. どうやって有効だと検証した?

  • オープン QA の設定で、TriviaQA / Natural Question の評価データを使用。
  • 評価基準は Exact Match / accuracy@k

次元数削減 × 直積量子化

  • インデックスサイズを 1.6 GB 程度に抑えても検索性能の低下は僅か
    • 各カーブはベクトルの次元数を表し、各ポイントは次元数あたりのビット数を示す(左から  { 1, 2, \cdots, 32 } bit/dim)。

f:id:catshun:20220313133106p:plain

文書フィルタリング

  • 二つの設定で評価
    • 赤:256 dim, 64 subvectors/passage (full index size 1.67GB)
    • 青:128 dim, 16 subvectors/passage (full index size 0.42GB)

f:id:catshun:20220313134121p:plain

5. 議論はある?

  • インデックスサイズが支配的になることを考慮すると、モデルサイズを大きくしても(docker image size の影響に)問題はなさそう。
    • T5-large を使用すると、ほぼ性能差を抑えることが出来る(256 dim, 2 bits/dim, 18M passages で評価)。

f:id:catshun:20220313134656p:plain

6. 次に読むべき論文は?

  • 知識蒸留

    • Izacard+'20 - Distilling Knowledge from Reader to Retriever for Question Answering [arXiv]
  • メモリ効率化

    • Luan+'21 - Sparse, Dense, and Attentional Representations for Text Retrieval (TACL) [ACL Anthology]
    • Ma+'21 - Simple and Effective Unsupervised Redundancy Elimination to Compress Dense Vectors for Passage Retrieval (EMNLP) [ACL Anthology][GitHub]
      • ベクトルサイズ別の調査。また PCA や直積量子化の次元数削減が性能を維持したまま効率化に繋がることを示す。

Wu+'22 - Sentence-aware Contrastive Learning for Open-Domain Passage Retrieval (ACL)

#OpenQA #DCSR

1. どんなもの?

f:id:catshun:20220312120020p:plain

  • DPR など一般的なデュアルエンコーダでは、文書 CLS と質問 CLS によるマッチングを行うため、文書内に複数文が存在するような一対多の場合は、質問に対する適切な文書を取得することが難しい(各文の内容が異なる場合は Contrastive Conflicts が生じる)
  • 本研究では、二種類の Contrastive Conflicts を問題視:
    • Transitivity of Similarity:文書内に存在する意味的に異なる複数の文に対して、DPR ではこれらの表現間の類似性を考慮せずにグローバルな文書表現として同様の最適化を行ってしまう(SQuAD データで DPR の性能が低い要因の一つ。SQuAD では、一つの文書に対して 2.66 の質問が作成される)
    • Multiple References in Large Batch Size:一般的にバッチサイズを大きくすると検索性能が向上することが知られている(例:Qu+'21 - cross-batch negative sampling)。これらの負例サンプリングでは、(ミニバッチ内など)他の質問に対する正例文書を負例文書とみなすため、負例文書の対象に質問に関連する正例文書が含まれてしまう可能性がある
  • 本研究ではこれらの問題に対処するため文書を文レベルの表現として考慮し、同一文書内における多様な文表現の生成を行う

2. 先行研究と比べてどこがすごい?

  • デュアルエンコーダで課題とされてきた質問と文書間の一対多関係から生じる Contrastive Conflict に対して、文レベルの詳細な意味表現を獲得することでこの問題に対処
  • 一文ずつエンコードする必要はなく文書全体を入力とするため、文の数に比例することなく latency の増加を抑えることができる
  • また文書内の負例サンプリング(in-passage negative sampling)手法を提案。同一文書内の正例文に対して、近隣の文をサンプリングすることでハード負例を作成

3. 技術や手法のキモはどこ?

Training

f:id:catshun:20220312154520p:plain

  • 文ごとに特殊トーク[SENT] を挿入した系列を入力とする:

    • [CLS] [SENT] s1 [SENT] s2 ... [SEP]
    • 各文の埋め込み表現は [SENT] のベクトル表現を用いる
  • 最適化のための正例・負例文(not 文書)は以下のように作成する

    • 正例文:質問の解答を含む文
    • 負例文:
      • easy negatives:in-batch negatives
      • hard negatives:質問に対する BM25 スコアに基づいて作成された負例文書セットからランダムに一つの負例文書(= 複数の負例文)をサンプル
      • in-passage negatives:正例文書のうち正解を含まない文からランダムに一つ選択(全ての文に正解が含まれる場合は、BM25 の負例セットから一つ選択)

  • 🤔 最適化の際の挙動がいまいちよく分かっていない

    We refine the original contrastive learning framework by creating sentence-aware positive and negative samples. (3.2)

    • 図を見ても文単位でサンプルしているっぽい(自分の理解では以下のようにサンプル)
      • 正例文書からは、正例文を一つ、負例文を一つサンプル?
      • BM25 で作成された一つのハード負例文書からは、負例文を一つサンプル?
      • in-batch negatives からは、各文書に対して負例文を一つずつサンプル?

Retrieval

f:id:catshun:20220312155315p:plain

  • 文単位で FAISS に登録して、質問に対するオフライン検索を行う(入力は文書、各 [SENT] の埋め込み表現を登録)
  • 一つの文書は複数の文を持つため、上位 100×k 文を検索(k は文書に含まれる文数の平均)

  • 検索対象は文書(not 文)なので、以下の Score Normalization を行い、文スコアから文書スコアを導出する

    • 検索文(100×k)に対して softmax 関数で正規化
    • 文書  \mathcal{P} に含まれる文(おそらく 100×k に含まれる文を対象)が  s_1, \cdots, s_n であるとき、文書  \mathcal{P} が質問に対する関連スコア(解答が含まれる確率)は、以下のように計算される:
      • f:id:catshun:20220312161727p:plain
    • 本実験の設定では最終的に上位 100 件の文書を取得する

4. どうやって有効だと検証した?

TBA

5. 議論はある?

  • 一つの文書から質問が生成される SQuAD(下図)に対して、Wikipedia のタイトルが正解となるような問題設定(一つの記事自体を正解対象とする場合)では、問題意識にズレが生じる。とはいえ、Contrastive Conflicts について有効な手段であると考えられる。 f:id:catshun:20220312145335p:plain

6. 次に読むべき論文は?

  • Akkalyoncu Yilmaz+'19 - Cross-Domain Modeling of Sentence-Level Evidence for Document Retrieval (EMNLP) ACL Anthology

    • 文レベルの表現を使用するが、リランキングに焦点(本研究では検索自体に焦点)
  • Khattab+'20 - ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT (SIGIR) [arXiv][GitHub]

    • MaxSim(トークン単位の類似度における Maxpooling + Summation)による late interaction を用いることで、詳細な類似度を考慮
  • Lee+'21 - Learning Dense Representations of Phrases at Scale (ACL) [ACL Anthology][GitHub]

ParlAI の TeacherAgent について

logo_parlai

本ページでは、ParlAI の TeacherAgent について簡単に説明する。 実際に新たなタスクを定義する場合は、本ページの記述内容では十分でない場合があるのでドキュメントやソースコードを参照されたい。

また ParlAI に関連する記事については以下を参照されたい。

目次

TeacgerAgent の概要

parl.ai

ParlAI を理解した気持ちになる - catshun’s blog でも紹介したが、TeacherAgent ではデータセットを StudentAgent に提供する DataLoader の役割を持つ。

ParlAI では huggingface/datasets のように、多くのデータセットが提供されている [一覧]

各 Teacher は parlai/tasks/{taskname} 下に定義されており、以下のディレクトリ構造を持つ。

- parlai/tasks/:
  - {taskname}/:
    - __init__.py:  
    - agents.py:   Teacher Agent が定義される。
    - build.py:    データのダウンロードや設定が記述される。
  - task_list.py:  タスクに関するリストが記述される。新たにタスクを定義する場合は追記する。

コマンドラインからデータセットにアクセス(ロード + 一部表示)する場合は、以下を実行する。

# parlai display-data
$ python parlai/scripts/display_data.py --task squad --datapath {dir_data} --datatype valid

...(省略)
loading: {dir_data}/SQuAD/dev-v1.1.json
- - - NEW EPISODE: squad - - -
Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi's Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.
Which NFL team represented the AFC at Super Bowl 50?
   Denver Broncos|Denver Broncos|Denver Broncos

TeacherAgent の呼び出し

parlai/scripts/display_data.py では、 agent/world の定義、および act の受け取りを、以下のように行っている。

agent = FixedResponseAgent(opt)
world = create_task(opt, agent)
act = world.get_acts()[0]

""" example of squad
act = {
    "text": context + "\n" + question,
    "labels": answers,
}
"""

ロード関数を辿ると、parlai/core/loader.py#load_teacher_module で TeacherAgent が import される。 ここでは --task {taskname} で指定したタスク名に基づいて parlai/tasks/{taskname}/agents.py で作成する TeacherAgent(DefaultTeacherGenerationTeacher)が返る。

# parlai/core/loader.py
# 一部修正

def load_teacher_module(taskname: str):
    """
    :taskname: `--task (-t)` で指定される `squad`, `image_chat:Generation` のこと
    :return: 例:parlai/tasks/squad/agents.py 内で定義された TeacherAgent
    """

    task_module = load_task_module(taskname)    
    # 例:task_module = import_module("parlai.tasks.image_chat.agents")
    task_path_list, repo = _get_task_path_and_repo(taskname)    
    # 例:repo = "parlai"
    # 例:task_path_list = ["image_chat", "Generation"]

    def upper_fn(word, sep=""):
        upper = lambda x: x[0].upper() + x[1:]
        return "".join([upper(w) for w in word.split(sep)])

    if len(task_path_list) > 1 and "=" not in task_path_list[1]:
        teacher = upper_fn(task_path_list[1])
        if "." not in task_path_list[0] and "Teacher" not in teacher:
            teacher = upper_fn(teacher, "_") + "Teacher"
            # 例:`--task image_chat:Generation` と指定した場合
            # 例:teacher = "GenerationTeacher"
    else:
        teacher = "DefaultTeacher"

    return getattr(task_module, teacher)

新しいタスクを定義する場合

parl.ai

ここでは、キャプション生成用に STAIR Captions のデータを追加する。 データ形式については COCO のデータ形式 を参照されたい。

# 分かりやすいように Teacher を定義するためのファイル群を予め作成する。

taskname=stair_captions
dest=parlai/tasks/${taskname}

mkdir ${dest}
touch ${dest}/__init__.py
touch ${dest}/agents.py    # Teacher クラスを定義する
touch ${dest}/build.py       # データをダウンロードする

1. task_list にタスク名を追加

parlai/tasks/task_list.py における COCO_Captions の記述を踏襲し、 STAIR_Captions の記述を、以下のように追記する。

{
    {
        "id": "COCO_Captions",
        "display_name": "COCO_Captions",
        "task": "coco_caption",
        "tags": ["Visual"],
        "description": (
            "COCO annotations derived from the 2015 COCO Caption Competition. "
        ),
        "links": {"website": "http://cocodataset.org/"},
    },
    {
        "id": "Stair_Captions",
        "display_name": "Stair_Captions",
        "task": "stair_caption",
        "tags": ["Visual"],
        "description": (
            "STAIR Captions are annotated from the 2014 COCO Caption."
        ),
        "links": {
            "coco": "http://cocodataset.org/",
            "stair": "https://github.com/STAIR-Lab-CIT/STAIR-captions",
            "github": "http://captions.stair.center/download/",
            "arxiv": "https://arxiv.org/abs/1705.00823",
        },
    },
}

2. build.py を作成

parlai/tasks/stair_captions/build.py では、データファイルが存在しない場合にダウンロードするスクリプトを作成する。 ファイルをダウンロードするための DownloadableFile クラスでは、(1) URL もしくは Google Drive に対する HEAD リクエストの送信・ファイルのダウンロード、(2) 圧縮ファイルの解凍、(3) ダウンロード済みであるか確認、するためのメソッドを持つ。

import os

import parlai.core.build_data as build_data
from parlai.core.build_data import DownloadableFile
from parlai.tasks.coco_caption.build_2014 import buildImage

RESOURCES = [
    DownloadableFile(
        "https://github.com/STAIR-Lab-CIT/STAIR-captions/raw/master/stair_captions_v1.2.tar.gz",
        "stair_captions_v1.2.tar.gz",
        "8d47b7971d2883bffcba92e2ab44918e70e22c434d80dfffc01670c1654a7735"
    )
]


def build(opt):
    dpath = os.path.join(opt["datapath"], "stair")
    image_path = os.path.join(opt["datapath"], "COCO-IMG-2014")
    version = "1.2"
    if not build_data.built(dpath, version):
        print("[building data: " + dpath + "]")
        if build_data.built(dpath):
            # An older version exists, so remove these outdated files.
            build_data.remove_dir(dpath)
        build_data.make_dir(dpath)

        # Download the data.
        for downloadable_file in RESOURCES:
            downloadable_file.download_file(dpath)

        build_data.mark_done(dpath, version)

    if (
        not build_data.built(image_path, version)
        and not opt.get("coco")
        and opt.get("load_images")
    ):
        buildImage(opt)

2.1 agents.py に TeacherAgent を作成

TeacherAgent では、一般的に以下三つのクラスを継承する(もちろんカスタマイズも可能)。

簡単のため、以下では DialogTeacher を使用する。 ただし、ここでは画像のロードは行わないので注意、画像のロードを行う場合は 2.2 を参照されたい。 DialogTeacherを継承する場合は、データファイルを読み込んで episode 単位で yield する setup_data 関数を実装する。

from typing import Optional

from .build import build
from parlai.core.image_featurizers import ImageLoader
from parlai.core.opt import Opt
from parlai.core.params import ParlaiParser
from parlai.core.teachers import DialogTeacher
from parlai.utils.typing import TShared
from parlai.utils.io import PathManager


class DefaultTeacher(DialogTeacher):
    @classmethod
    def add_cmdline_args(
        cls, parser: ParlaiParser, partial_opt: Optional[Opt] = None
    ) -> ParlaiParser:
        super().add_cmdline_args(parser, partial_opt)
        agent = parser.add_argument_group("STAIR Captions arguments")
        agent.add_argument(
            "--load-images",
            action="store_true",
            help="Specify whether to load images",
        )

    def __init__(self, opt: Opt, shared: TShared = None):
        suffix = "train" if opt["datatype"].startswith("train") else "val"
        opt["datafile"] = os.path.join(opt["datapath"], "stair", f"stair_captions_v1.2_{suffix}.json")
        self.id = "stair"
        self.datatype = opt["datatype"].split(":")[0]
        super().__init__(opt, shared)
        build(opt)

    def setup_data(self, data_path: str):
        """ ファイルの読み込み+データの提供を行う """
        print("loading: " + data_path)
        with PathManager.open(data_path) as f:
            data = json.load(f)
        images = {i["id"]:i for i in data["images"]}
        for caption in data["annotations"]:
            image = images.get(caption["image_id"])
            if image is None:
                continue
            yield {
                "text": "",
                "labels": [caption["caption"]],
                "image_id": image["id"],
                "caption_id": caption["id"],
                "file_name": image["file_name"],
                "coco_url": image["coco_url"],
                "flickr_url": image["flickr_url"],
                "height": image["height"],
                "width": image["width"],
            }, True

ここで以下を実行すると、STAIR Captions のデータ例が表示される。

python parlai/scripts/display_data.py --task stair_captions:default --datapath {dir_data}

# 出力例
- - - NEW EPISODE: stair_captions - - -
   山の中を赤い電車が走っている

2.2 agents.py に TeacherAgent を作成(画像のロードを行う場合)

画像データのロード

画像データをロードする場合、parlai/core/image_featurizers.py を用いる。ここでは、ImageLoader が定義されており、torchvision や detectron2 上で実装されている ResNet / ResNext / Faster R-CNN などが使用できる。

ImageLoader では transform 関数に、以下が使用される。

# parlai/core/image_featurizers.py
# 一部省略・修正
import torchvision.transforms

class ImageLoader:
    def _init_transform(self):
        self.transforms = torchvision.transforms
        self.transform = self.transforms.Compose(
            [
                self.transforms.Scale(self.image_size),
                self.transforms.CenterCrop(self.crop_size),
                self.transforms.ToTensor(),
                self.transforms.Normalize(
                    mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
                ),
            ]
        )

    def load(self, path):
        if self.opt.get('image_mode', 'raw') == 'raw':
            return self._load_image(path)
        prepath, imagefn = self._get_prepath(path)
        dpath = os.path.join(prepath, mode)
        imagefn = imagefn.split('.')[0]
        new_path = os.path.join(prepath, mode, imagefn)
        with PathManager.open(new_path, 'rb') as f:
            return torch.load(f)

Teacher 内部での ImageLoader の扱い

parl.ai

画像データを扱う際は、Teacher 内部で submit_load_request メソッドを呼び出し、Teacher から DataLoader にロード要求を送信・次の episode_idx を取得する。

DataLoader のスレッドプールに含まれるデータは、Teacher の act メソッドにより取得され、取得後は submit_load_request が再度実行されることで、次の episode_idx における画像データを事前にロードしておく。

# parlai/tasks/stair_captions/agent.py
# 一部省略・修正

class StairCaptionsTeacher(DefaultTeacher):

    def __init__(self)
        self.data_loader = DataLoader(opt)
        self.image_loader = ImageLoader(opt)

    def receive_data(self, future: concurrent.futures.Future):
        data = future.result()
        self.data_queue.put(data)

    def submit_load_request(self, image_id: str):
        """ `--datapath (-dp)` で指定した COCO データから画像をロードするための関数 """
        img_path = os.path.join(self.image_path, f"COCO_{self.suffix}2014_{image_id:012d}.jpg".format(image_id))
        self.data_loader.request_load(
            self.receive_data, self.image_loader.load, (img_path,)
        )

DataLoader

DataLoaderrequest_load が実行されることで、リクエストを queue に入れる。

# parlai/core/teachers.py
# 一部省略・修正
import concurrent.futures
import queue
from threading import Thread


class DataLoader(Thread):
    def __init__(self, opt):
        Thread.__init__(self, daemon=True)
        self.num_workers = opt.get('num_load_threads', 1)
        self.request_queue = queue.Queue()

    def request_load(self, receive_fn, load_fn, args):
        self.request_queue.put((receive_fn, load_fn, args))

    def run(self):
        executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=self.num_workers, thread_name_prefix=self.name
        )
        with executor:
            receive_fn, load_fn, args = self.request_queue.get()
            if receive_fn is StopIteration:
                return
            future = executor.submit(load_fn, **args)
            self.last_future = future
            receive_fn(future)

DefaultTeacher(画像ロードする場合)

上記ではコードの概要を把握するため一部コード内容を省略・修正していたが、ここではコピー・ペーストして使用できるように parlai/tasks/stair_captions/agents.py の内容を記載する。 なお本スクリプトは著者が作成したものであるため、改善箇所やエラーが生じる可能性があることを考慮いただきたい(参考程度にしてもらえると嬉しい)。

#!/usr/bin/env python3
# Copyright (c) Facebook, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import Optional
from parlai.core.params import ParlaiParser
import json
import os
import random
from typing import Tuple, Dict, List

from parlai.core.message import Message
from parlai.core.opt import Opt
from parlai.core.teachers import DialogTeacher, FixedDialogTeacher
from parlai.core.image_featurizers import ImageLoader
from parlai.utils.typing import TShared
from parlai.utils.io import PathManager
from .build import build


def _path(opt: Opt) -> Tuple[str, str, str]:
    """
    Return appropriate datapaths.

    :param opt:
        options

    :return (data path, personalities path, image_path):
        path to data, personalities, and images
    """
    dt = opt["datatype"].split(":")[0]
    data_path = os.path.join(opt["datapath"], "stair_captions/stair_captions_v1.2_{}.json".format(dt))
    image_path = os.path.join(opt["datapath"], "COCO-IMG-2014", f"{dt}2014")
    return data_path, image_path


class DefaultTeacher(FixedDialogTeacher):
    @classmethod
    def add_cmdline_args(
        cls, parser: ParlaiParser, partial_opt: Optional[Opt] = None
    ) -> ParlaiParser:
        super().add_cmdline_args(parser, partial_opt)
        agent = parser.add_argument_group("STAIR Captions arguments")
        agent.add_argument(
            "--load-images",
            action="store_true",
            help="Specify whether to load images",
        )

    def __init__(self, opt: Opt, shared: TShared = None):
        super().__init__(opt, shared)
        self.data = []
        self.image_mode = opt.get("image_mode", "no_image_model")
        self.data_path, self.image_path = _path(opt)
        self.suffix = "train" if opt["datatype"].startswith("train") else "val"
        opt["datafile"] = os.path.join(opt["datapath"], "stair_captions", f"stair_captions_v1.2_{self.suffix}.json")
        self.id = "stair_captions"
        self.datatype = opt["datatype"].split(":")[0]
        build(opt)
        if shared:
            if "annotation" in shared:
                self.annotation = shared["annotation"]
            self.image_loader = shared["image_loader"]
        else:
            self._setup_data(opt, self.data_path)
            self.image_loader = ImageLoader(opt)
        self.reset()

    def _setup_data(self, opt, data_path):
        print("loading: " + data_path)
        with PathManager.open(data_path) as f:
            _data = json.load(f)
        images = {i["id"]:i for i in _data["images"]}
        for caption in _data["annotations"]:
            image = images.get(caption["image_id"])
            if image is None:
                continue
            obj = {
                "text": "",
                "labels": [caption["caption"]],
                "image_id": image["id"],
                "caption_id": caption["id"],
                "file_name": image["file_name"],
                "coco_url": image["coco_url"],
                "flickr_url": image["flickr_url"],
                "height": image["height"],
                "width": image["width"],
            }
            self.data.append(obj)
        print(f"load ... {len(self.data)} examples")

    def reset(self):
        super().reset()
        self.example = None

    def num_episodes(self) -> int:
        return len(self.data)

    def num_examples(self) -> int:
        return len(self.data)

    def submit_load_request(self, image_id: str):
        img_path = os.path.join(self.image_path, f"COCO_{self.suffix}2014_{image_id:012d}.jpg".format(image_id))
        self.data_loader.request_load(
            self.receive_data, self.image_loader.load, (img_path,)
        )
    
    def get(self, episode_idx: int, entry_idx: int = 0):
        action = self.data[episode_idx]
        action.update({
            "episode_done": True,
        })
        return action
        
    def next_example(self):
        """
        Returns the next example from this dataset after starting to queue up the next
        example.
        """
        ready = None
        # pull up the currently queued example
        if self.example is not None:
            if self.image_mode != 'no_image_model' and 'image_id' in self.example:
                # move the image we loaded in the background into the example
                image = self.data_queue.get()
                self.example['image'] = image
            ready = (self.example, self.imageEpochDone)
        # get the next base example: super().next_example() calls self.get()
        self.example, self.imageEpochDone = super().next_example()
        if self.image_mode != 'no_image_model' and 'image_id' in self.example:
            # load the next image in the background
            image_id = self.example['image_id']
            self.submit_load_request(image_id)
        # Try to return the previously cached example
        if ready is None:
            return self.next_example()
        else:
            return ready

    def share(self) -> TShared:
        shared = super().share()
        shared['data'] = self.data
        shared['image_loader'] = self.image_loader
        return shared

Karpukhin+'20 - Dense Passage Retrieval for Open-Domain Question Answering (EMNLP)

Dense Passage Retrieval (DPR)

Open-domain question answering relies on efficient passage retrieval to select candidate contexts, where traditional sparse vector space models, such as TF-IDF or BM25, are the de facto method. In this work, we show that retrieval can be practically implemented using dense representations alone, where embeddings are learned from a small number of questions and passages by a simple dual-encoder framework. When evaluated on a wide range of open-domain QA datasets, our dense retriever outperforms a strong Lucene-BM25 system greatly by 9%-19% absolute in terms of top-20 passage retrieval accuracy, and helps our end-to-end QA system establish new state-of-the-art on multiple open-domain QA benchmarks.

AI王 ~クイズAI日本一決定戦 より

f:id:catshun:20220222032109p:plain

1. どんなもの?

オープンドメイン質問応答における de-facto standard モデル。① 質問に関連する文書を検索する検索モジュール (retriever) と ② 検索された関連文書集合から解答スパンを推定する解答モジュール (reader) の retriever-reader 型のシステムで質問の解答を行う。retriever のアーキテクチャは、質問エンコーダおよび文書エンコーダの二つのエンコーダを用いるデュアルエンコーダ型で、訓練時は対照学習に基づいて質問ベクトルと関連する文書ベクトルとのベクトル内積値を高くする(関連しないベクトルは低くする)ように学習を行う。関連度に基づくマッチングをエンコーダ外部で行うため、faiss を用いて高速に推論を行うことができる。従来の TF-IDF や BM25 のような表層情報を用いたマッチングとは異なり、密なベクトル表現による意味的なマッチングが可能である。

2. 先行研究と比べてどこがすごい?

  • TF-IDF や BM25 などの表層マッチングによる検索手法に対して、BERT による contextualized embeddings を使用した semantic search を行うため、表記揺れなどに対して柔軟な対応が期待される。
  • クロスエンコーダに対して二つの BERT エンコーダを使用することで、高速に推論を行うオフライン検索が可能。

3. 技術や手法のキモはどこ?

AI王 ~クイズAI日本一決定戦 より

4. どうやって有効だと検証した?

TBA

5. 議論はある?

  • 負例作成に関する工夫

    • ミニバッチ内から負例を作成 → 推論時における検索対象数との大きな差が検索性能に影響
    • クエリと文書間の関係のみモデル化 → 文書間同士の類似関係を考慮しない
    • ハード負例文書を語のマッチングにより作成 → false negative 文書が負例となる可能性あり
  • 検索対象のメモリ効率化

    • 検索対象の文書数が膨大 → メモリコストが大きい
  • クエリとのマッチング強化

    • CLS トークンによるマッチング → トークンレベルなど粒度の高いマッチングが難しい

6. 次に読むべき論文は?

  • 負例作成に関する工夫

  • ベクトルのメモリ効率化

    • Izacard+'20 - A Memory Efficient Baseline for Open Domain Question Answering [arXiv]
    • Yamada+'21 - Efficient Passage Retrieval with Hashing for Open-domain Question Answering (ACL/IJCNLP) [ACL Anthology][GitHub]
    • Santhanam+'21 - ColBERTv2: Effective and Efficient Retrieval via Lightweight Late Interaction [arXiv]
    • Ma+'21 - Simple and Effective Unsupervised Redundancy Elimination to Compress Dense Vectors for Passage Retrieval (EMNLP) [ACL Anthology][GitHub]
    • Zhan+'22 - Learning Discrete Representations via Constrained Clustering for Effective and Efficient Dense Retrieval (WSDM) [arXiv]
  • クエリとのマッチング強化

  • 汎化性能向上

    • Sciavolino+'21 - Simple Entity-Centric Questions Challenge Dense Retrievers (EMNLP) [arXiv]
    • Zhuang+'21 - Dealing with Typos for BERT-based Passage Retrieval and Ranking (EMNLP) [ACL Anthology][arXiv][GitHub]
    • Ni+'21 - Large Dual Encoders Are Generalizable Retrievers [arXiv]
    • Liu+'21 - Improving Embedding-based Large-scale Retrieval via Label Enhancement (EMNLP) [ACL Anthology]
    • Chen+'21- Salient Phrase Aware Dense Retrieval: Can a Dense Retriever Imitate a Sparse One? [arXiv]
    • Wang+'21 - GPL: Generative Pseudo Labeling for Unsupervised Domain Adaptation of Dense Retrieval [arXiv][GitHub]
  • データ・クエリ拡張

    • Lee+'19 - Latent Retrieval for Weakly Supervised Open Domain Question Answering (ACL) [ACL Anthology][arXiv]
    • Guu+'20 - REALM: Retrieval-Augmented Language Model Pre-Training (ICML) [Google AI Blog][arXiv][GitHub]
    • Qu+'21 - RocketQA: An Optimized Training Approach to Dense Passage Retrieval for Open-Domain Question Answering (NAACL) [ACL Anthology][arXiv][GitHub]
    • Izacard+'21 - Towards Unsupervised Dense Information Retrieval with Contrastive Learning [arXiv][GitHub]
    • Mao+'21 - Generation-Augmented Retrieval for Open-Domain Question Answering (ACL/IJCNLP) [ACL Anthology][GitHub]
  • アーキテクチャの工夫

    • Izacard+'20 - Leveraging Passage Retrieval with Generative Models for Open Domain Question Answering (EACL) [ACL Anthology][arXiv][GitHub]
    • Cheng+'21 - UnitedQA: A Hybrid Approach for Open Domain Question Answering (ACL/IJCNLP) [ACL Anthology][arXiv]
    • Lee+'21 - You Only Need One Model for Open-domain Question Answering [arXiv]
    • Tay+'22 - Transformer Memory as a Differentiable Search Index [arXiv]