1台のワークステーションでローカル PubMed/PMC 検索と QA を作るなら

1台のワークステーションでローカル PubMed/PMC 検索と QA を作るなら

1台のワークステーションだけで動く文献探索環境を考えます。PubMed のようなフィールド検索ができ、法的に利用できる場合は PMC の全文箇所も探せて、短い回答から正確な根拠へ戻れるものです。

最初から全件をダウンロードしたり、LLM を選んだりはしません。代表的な小さな範囲で役に立つ検索を作り、測定してから、必要性が確認できた部分だけを拡張します。

PubMed と PMC は別のデータ層

この違いがシステム全体を決めます。

  • PubMed は書誌情報と、存在する場合は抄録を提供します。NLM は年次 XML baseline と日次 update を公開しています。公式の Download PubMed Data は、baseline を先に読み、update を番号順に適用し、改訂・削除された引用を置換するよう説明しています。
  • PubMed Central(PMC) は一部文献の全文を提供します。しかし無料で読めることと、再利用できることは同じではありません。PMC Open Access Subset は、すべての PMC 記事がテキストマイニング対象ではないこと、ライセンスが記事ごとに異なること、自動取得には承認された PMC サービスを使うことを明記しています。

試作では、分野と期間を決め、公式 API から範囲を限定して取得します。継続運用するローカル PubMed mirror なら年次 baseline と順序付き日次 update に移ります。PMC 全文は承認された OA subset の経路だけを使い、記事ごとのライセンスを必ず保存します。

公開記事ページをスクレイピングしてはいけません。

取り込み前に、最初の有用な結果を定義する

ワークステーションが答えるべき質問を 40〜60 個作ります。次を混ぜます。

  1. PMID、PMCID、DOI、著者、タイトル句の完全一致;
  2. 略語と正式名称;
  3. 表現の異なる同一の生物医学概念;
  4. 年、雑誌、publication type、MeSH のフィルター付き検索;
  5. 既知の一箇所に答えがある質問;
  6. 論文同士が一致しない質問;
  7. コレクション内に十分な答えがない質問。

調整を始める前に、各質問に対応する正解記事や箇所を記録します。これが評価セットです。これがなければ、意味検索のデモがよく見えても、正確な識別子、フィルター、重要な「見つからない」という結果が壊れていても分かりません。

最初の到達点は狭くします。

一つの分野サンプルで、正解記事を上位10件に返し、該当する抄録または PMC section を開ける。

QA はこれが動いた後です。

一つの canonical article ledger を作る

識別子、版、権利、取り込み状態はリレーショナル DB で管理し、元 XML と派生 index は分けます。例えば次を保存します。

article_key
pmid | pmcid | doi | manuscript_id
title | abstract | journal | publication_date
authors | publication_types | mesh_descriptors
source_dataset | source_version | retrieved_at
license_code | license_url | reuse_class
xml_sha256 | parser_version | indexed_at

PMID、PMCID、DOI は alias であり、相互交換できる主キーではありません。公式の PMC ID Converter API は PMC に存在する記事の識別子を対応付け、版情報も返せます。大量処理には PMC が示す一括 ID table を使い、1件ずつ何百万回も API を呼ばないようにします。

次の三状態を分けて保存します。

  • PubMed の書誌レコードがある;
  • PMC の全文レコードがある;
  • その全文の利用条件が目的に合う。

これにより、すべての抄録にローカル全文がある、すべての PMC ページを再配布できる、という誤解を防げます。

単なる文字列ではなく構造を解析する

PubMed XML ではタイトル、抄録 section、著者、雑誌、日付、publication type、化学物質、MeSH heading を保持します。XML の偶然の並びに依存せず、NLM が公開する PubMed DTD と要素文書 に従います。

利用可能な PMC 全文では、記事タイトル、section 階層、段落順、図表 caption、参考文献、安定した識別子を残します。各 passage に次のような住所を付けます。

PMCID: PMC1234567
section: Results > Adverse events
paragraph: 4
source_sha256: ...

まず section と段落の境界で分割します。token 数による window は極端に長い section の補助にとどめます。passage は単独で読め、必ず元記事の該当 section を開けるようにします。

MeSH は版付き語彙として取り込みます。descriptor ID、preferred label、entry term、tree number を残し、候補語と filter に使います。すべての query を全 descendant へ勝手に展開してはいけません。

まず語彙検索を作る

生物医学検索には、embedding に推測させるべきでない文字列があります。遺伝子記号、trial ID、薬品名、用量、PMID、引用句などです。

Tantivy のような小さな inverted index を使い、次の field を用意します。

pmid, pmcid, doi       exact + stored
title                  text + positions + stored
abstract               text + positions + stored
body                    text + positions
mesh_id                 exact, repeated
publication_type        exact, repeated
journal, year, language filters
passage_id, section     stored

PubMed は記事単位、利用可能な PMC 全文は passage 単位で index します。同時に検索しても、結果では「書誌・抄録」と「全文」を明確に区別します。

vector を追加する前に評価セットを通します。ID 完全一致は決定的であるべきです。句検索と filter は大文字小文字や句読点の違いに耐えるべきです。各結果には title、source ID、日付、該当 snippet、match した field を表示します。

MedCPT は第二の検索経路として加える

語彙検索は本当の言い換えを見逃します。その時点で MedCPT を試します。動いている語彙 index と置き換えるのではありません。

NCBI は対になった MedCPT Query EncoderMedCPT Article Encoder を公開しています。model card では、短い質問・検索語には query encoder、title と abstract の組には article encoder を使い、両方が同じ vector 空間を生成します。Bioinformatics の論文 は PubMed 検索ログの query/article pair による学習と、生物医学 retrieval task での評価を説明しています。

最初はその input contract に従います。

query_vector = encode_query(user_query)
article_vector = encode_article([title, abstract])

vector は安定した内部 article ID と対応させます。最初の in-process 版なら Faiss で十分です。固定次元 vector を index に加え、近傍を返せます。metadata と rights filter は article ledger に残し、vector の通し番号だけを論文の identity にしません。

MedCPT の article model card が示すのは title と abstract です。任意の全文 passage embedding は新しい実験として扱い、保証された使い方とみなしません。passage semantic search が必要なら、正解ラベル付き subset を作り、別に測定します。

比較できない score ではなく順位を統合する

各 query について次を行います。

  1. PMID、PMCID、DOI、引用句を直接処理する;
  2. 語彙候補を100件程度取得する;
  3. MedCPT 候補を100件程度取得する;
  4. rights 条件やユーザー指定 filter に合わないものを除く;
  5. reciprocal-rank fusion で二つの順位を統合する;
  6. 必要なら上位20〜30件だけを rerank する;
  7. 生成する前に検索結果を表示する。

小さな rank-fusion 関数で足ります。

def rrf(*ranked_lists, k=60):
    score = {}
    for rows in ranked_lists:
        for rank, article_id in enumerate(rows, start=1):
            score[article_id] = score.get(article_id, 0.0) + 1.0 / (k + rank)
    return sorted(score, key=score.get, reverse=True)

BM25 と cosine の raw score は尺度が異なるため、そのまま平均しません。candidate 数や fusion の調整には、構築中に見ていない held-out query を使います。

QA を根拠の上に載せる

QA layer に corpus 全体ではなく、小さな evidence packet を渡します。

[E1] PMID ... — title + abstract sentence(s)
[E2] PMCID ... — Methods > Eligibility, paragraph 2
[E3] PMCID ... — Results > Primary outcome, paragraph 1

model には、これらの passage だけに基づく短い回答、重要 claim ごとの citation、意見が食い違う場合の分離、根拠不足なら「この collection には十分な根拠がない」と返すことを求めます。

その後、引用された evidence ID がすべて存在するか機械的に確認します。文章の横に evidence panel を表示し、元 passage を開けるようにします。流暢な一段落の末尾に link が三つあるだけでは provenance になりません。

生物医学文献では “Search only” を既定にします。QA は文献探索と整理のための view であり、診断や治療判断に直接使うものではありません。この境界は NCBI の MedCPT model card にも示されています。

全 feed へ広げる前に測る

四つの層を記録します。

Layer主な確認項目
Ingestionparse、reject、update、delete、rights 適合、hash 変更
RetrievalRecall@10、MRR、filter 正確性、exact-ID 成功率
QA根拠付き claim 率、citation 正確性、no-answer での abstention
Operationsindex 時間、peak RAM、記事・passage 当たり disk、query latency

壊れた XML、abstract 欠落、識別子重複、訂正、長い PMC section が含まれる程度の sample で走らせます。1 record 当たりの bytes と seconds を実測してから全体を外挿します。小さな demo だけで hardware を買ったり full-corpus build を約束したりしません。

768次元 float32 vector は、index と metadata の overhead を除いても 768 × 4 = 3,072 bytes を使います。選んだ index の実際の disk 使用量を測ります。float16 や quantization は、full-precision の retrieval baseline を作った後に試します。

更新処理も製品の一部にする

最初の index は簡単です。変化し続けても信頼できる index が本当の system です。

  • 各 ingestion batch の source filename、checksum、処理結果を保存する;
  • PubMed update を公開順に適用し、改訂と削除を明示的に処理する;
  • 年次 baseline が変わったら、長年の patch state を信用せず再構築する;
  • canonical title または abstract が変わった record だけ再 embedding する;
  • parser と model の version を manifest に残す;
  • active index の隣に新 generation を作り、test 後に atomic pointer を切り替える;
  • 次の generation が検証されるまで rollback 用を残す。

UI にはローカル dataset の日付を表示します。「local」が「古く、監査不能」を意味してはいけません。

私なら出荷する最小構成

approved NCBI downloads
        ↓
immutable XML + batch manifest
        ↓
canonical article / license / ID ledger
        ├── Tantivy article + passage index
        └── MedCPT article vectors → Faiss
                    ↓
          filtered rank fusion
                    ↓
        search results + evidence cards
                    ↓
          optional cited QA view

各層を独立して構築・評価できるため、1台の workstation に収めやすい構成です。まず分野を限定し、source ledger を安定させ、検索品質と storage の実測ができてから ingestion を拡大します。

私は LazyingArt のローカルかつ provenance 重視の document tooling を保守し、多言語書籍や研究 collection を扱ってきました。ただし、PMC 専用の顧客 deployment や顧客成果があるとは主張しません。LKT sample report は、範囲を限定した project-owned collection について data、rights、citation、go/no-go をどう整理するかを示すもので、生物医学 system の成果ではありません。

collection-fit の作業は、metadata と、利用権限が確認された代表 sample を既存の1台で調べるところから始まります。hardware、PubMed/PMC 全件 ingestion、production deployment、medical validation、clinical use は、この限定確認の対象外です。

Leave a Reply