質問応答システム(QA)におけるBERTを用いたAI回答精度の向上

脱・キーワード一致!BERTファインチューニングで自作する高精度FAQ検索システム【Python実装】

約18分で読めます
文字サイズ:
脱・キーワード一致!BERTファインチューニングで自作する高精度FAQ検索システム【Python実装】
目次

この記事の要点

  • BERTによる質問文脈の深い理解と高精度な回答生成
  • キーワード一致型検索の限界を克服し、ユーザー意図を正確に捉える
  • FAQシステムやチャットボットの回答品質を大幅に向上

実務の現場では、毎日のように「検索精度」という壁にぶつかることがあります。ユーザーは「領収書が欲しい」と入力するのに、システム側のデータベースには「受領証」として登録されている。たったこれだけの違いで、検索結果は「0件」。ユーザーはフラストレーションを抱え、サポートチケットを切る。開発現場は、辞書登録という終わりのないモグラ叩きに追われる……。

皆さんもそんな経験、ありませんか?

今、世の中はChatGPTをはじめとするLLM(大規模言語モデル)の話題で持ちきりです。「AIに聞けばなんでも答えてくれる」時代が来たように見えます。しかし、経営者視点とエンジニア視点を融合させ、冷静にコストとパフォーマンスを見極めるとき、すべてのタスクに巨大なLLMを使うのが正解とは限りません。特に、社内のFAQ検索や特定のドキュメント検索において、LLMは「牛刀」すぎることがあります。推論コスト、レイテンシ(応答速度)、そしてデータの機密性。これらを考慮すると、より軽量で、かつ自社ドメインに特化したモデルが求められます。

そこで今回のテーマは、「BERTを用いた質問応答システム(QA)の自作と精度向上」です。

具体的には、Sentence-BERTを自社のFAQデータでファインチューニング(追加学習)し、ユーザーの意図を汲み取る「意味検索(Semantic Search)」を構築します。これは、多くのプロジェクトで採用され、実際に成果を上げている「高コスパ・高精度」なアプローチです。

理論の解説は最小限に、実際に手を動かしてコードを書く時間を最大化しましょう。「まず動くものを作る」プロトタイプ思考で、仮説を即座に形にして検証していきましょう。

1. なぜキーワード検索では「欲しい回答」に辿り着けないのか

まずは、課題の本質を理解することから始めましょう。なぜ従来のシステムだけでは、ユーザーの意図を汲み取れないのでしょうか。

表記揺れと類義語の壁

従来の検索システムの多くは、Elasticsearchなどの転置インデックスを用いたキーワードマッチング(BM25アルゴリズムなど)に依存しています。これは「文書の中にその単語が含まれているか」を高速に判定するには現在でも有効な技術であり、最新のベクトル検索システムでもハイブリッド検索の一要素として利用されています。しかし、単独では「言葉の意味」までは理解していません。

例えば、社内ヘルプデスクでよくあるシナリオを考えてみてください。

  • ユーザーの検索: 「PCが動かない」
  • FAQのタイトル: 「パソコンが起動しない場合の対処法」

人間なら一瞬で同じ意味だとわかりますが、単純なキーワード検索エンジンにとっては、「PC」と「パソコン」、「動かない」と「起動しない」は全く別の文字列として扱われます。これを解決するために、膨大な類義語辞書(Synonym Dictionary)を手動でメンテナンスし続けるのは、エンジニアのリソースを浪費する大きな要因となります。

ベクトル検索(意味検索)が解決する課題

ここで登場するのが、BERT(Bidirectional Encoder Representations from Transformers)などのモデルを用いたベクトル検索です。

BERTは、文章を「密なベクトル(数値の配列)」に変換します。これを埋め込み表現(Embedding)と呼びます。重要なのは、意味が近い文章同士は、ベクトル空間上でも近くに配置されるように学習されている点です。

  • 「PCが動かない」 $\rightarrow$ ベクトルA
  • 「パソコンが起動しない」 $\rightarrow$ ベクトルB

このベクトルAとBの距離(類似度)を計算すれば、単語が一致していなくても「意味が近い」ことを数学的に判定できます。これがセマンティック検索(意味検索)の本質です。

LLMではなくBERTを選択する理由

「でも、それならOpenAIのEmbeddings APIや最新のLLMを使えばいいのではないか?」

鋭い質問です。確かにAPIは手軽で、OpenAIの最新モデルなどは非常に高性能です。しかし、以下の理由から、あえて自社でのBERT運用(特にSentence-BERTなどの軽量モデル)を推奨するケースは少なくありません。

  1. コストの予測可能性: APIは従量課金です。検索リクエストが数万、数十万となるとコストは無視できません。また、外部サービスの価格改定やモデルの廃止・統合(例:古いモデルのサポート終了など)の影響を受けるリスクもあります。自前のBERTなら、一度学習させれば推論コストはインフラ費用のみでコントロール可能です。
  2. 速度とレイテンシ: 外部APIへの通信レイテンシが発生しません。オンプレミスや自社クラウド内で完結するため、ミリ秒単位の高速なレスポンスが可能です。これはリアルタイム性が求められる検索UXにおいて重要です。
  3. データ適合性とセキュリティ: 汎用的なモデルは「一般的な日本語」には強いですが、「社内用語」や「業界固有の隠語」までは深く理解していません。また、機密性の高いデータを外部APIに送信することへの懸念もあります。ファインチューニングによって、自社特有の言葉のニュアンスをモデルに教え込み、かつデータを社内に留められる点が最大の強みです。

今回は、この「自社データへの適合」と「セキュアな高速検索」を最大のゴールとして進めていきます。

2. 開発環境のセットアップとベースラインの構築

では、ここからエンジニアリングの時間です。まずは環境を構築し、チューニング前のモデルの実力を測ってみましょう。これがいわゆる「ベースライン」となり、後の改善効果を測る基準になります。

Google ColabとGPU環境の準備

手軽にGPU環境を利用できるGoogle Colabを使用することを前提に進めます。ローカル環境の場合は、CUDA対応のGPUがあることが望ましいですが、学習データが少なければCPUでも(時間はかかりますが)可能です。

まずは必要なライブラリをインストールします。ここでは互換性を考慮し、主要なライブラリを指定します。

# 必要なライブラリのインストール
!pip install transformers sentence-transformers faiss-gpu fugashi ipadic pandas
  • transformers: Hugging Faceのトランスフォーマーモデル用ライブラリ。
  • sentence-transformers: BERTなどを用いて高品質な文埋め込みを生成するためのフレームワーク。
  • faiss-gpu: Meta(旧Facebook)が開発した高速なベクトル検索ライブラリ。
  • fugashi, ipadic: 日本語処理に必要なトークナイザーと辞書。

※ライブラリのバージョンは頻繁に更新されます。特定のバージョンに依存する機能を使用する場合や、互換性のエラーが発生した際は、各ライブラリの公式ドキュメントを参照して適切なバージョンを指定してください。

事前学習済みモデルによるゼロショット推論の試行

まずは、日本語に対応した事前学習済みモデルを使って、一切追加学習を行わない状態(ゼロショット)でどの程度の類似度が出るか確認してみましょう。

ここでは、日本語のSentence-BERTモデルとして実績のある sonoisa/sentence-bert-base-ja-mean-tokens-v2 を使用して検証します。

専門家の視点:モデル選定のトレンド
BERT(2018年発表)はNLPの基礎となる重要なモデルですが、現在はより高性能な後継アーキテクチャ(DeBERTa-v3やE5シリーズなど)が登場しています。実務で高精度な検索システムを構築する際は、Hugging Face Hubなどで最新の日本語対応モデル(DeBERTaベースやmultilingual-e5など)の採用を検討することをお勧めします。今回は学習プロセスと改善幅を明確にするため、標準的なBERTベースのモデルを使用します。

from sentence_transformers import SentenceTransformer, util
import torch

# モデルのロード(初回はダウンロードに時間がかかります)
# 実務では用途に合わせて最新の日本語対応モデルを選定してください
model_name = "sonoisa/sentence-bert-base-ja-mean-tokens-v2"
model = SentenceTransformer(model_name)

# テスト用の文章
query = "VPNに繋がらない"
docs = [
    "社外からイントラネットへの接続方法",  # 正解に近い
    "VPN接続トラブルシューティング",      # 正解
    "PCの電源が入らない場合の対処",        # 全く関係ない
    "有給休暇の申請方法"                  # 全く関係ない
]

# ベクトル化(Embedding)
query_embedding = model.encode(query, convert_to_tensor=True)
doc_embeddings = model.encode(docs, convert_to_tensor=True)

# コサイン類似度の計算
cosine_scores = util.cos_sim(query_embedding, doc_embeddings)

# 結果の表示
print(f"質問: {query}")
for i, score in enumerate(cosine_scores[0]):
    print(f"回答候補: {docs[i]} | 類似度: {score:.4f}")

実行結果の例(想定):

質問: VPNに繋がらない
回答候補: 社外からイントラネットへの接続方法 | 類似度: 0.6521
回答候補: VPN接続トラブルシューティング | 類似度: 0.8105
回答候補: PCの電源が入らない場合の対処 | 類似度: 0.1203
回答候補: 有給休暇の申請方法 | 類似度: 0.0542

どうでしょう? 「VPN」という単語が含まれている候補は高いスコアが出ていますが、「社外からイントラネットへ...」のような、単語は違うが意味が近いもののスコアは、期待するほど高くないかもしれません(0.6台など)。

汎用モデルは「一般的な日本語の意味」では近いと判断しますが、「この組織の文脈において、VPNトラブルとイントラネット接続は密接に関連している」というドメイン固有の知識までは持っていません。

このギャップを埋めるのが、次のステップであるファインチューニングです。

3. ドメイン特化のためのデータセット作成戦略

2. 開発環境のセットアップとベースラインの構築 - Section Image

AIプロジェクトの成否の8割はデータで決まると言っても過言ではありません。どれほど高性能なモデルアーキテクチャを採用しても、入力するデータ(Garbage In)が粗悪であれば、出力もまた粗悪(Garbage Out)になります。モデル構築の前に、どのようなデータを「食わせる」か、その戦略を練る必要があります。

FAQデータからの学習ペア(Anchor-Positive)生成

既存のFAQリスト(質問と回答のペア)が手元にあると仮定しましょう。Sentence-BERTの学習には、基本的に「意味的に似ている文章のペア」が必要です。

最もシンプルなアプローチは、以下のペアを作ることです。

  • Anchor(基準): ユーザーが入力しそうな質問文
  • Positive(正例): FAQの「質問文」または「回答文」

例えば、FAQデータが以下のようなCSVだとします。

ID Question (FAQの質問) Answer (回答)
1 パソコンが起動しません 電源ケーブルを確認し...

ここから学習データを作る際、単に「FAQの質問」と「回答」をペアにするだけでなく、「ユーザーが実際に検索したクエリログ」を活用するのがベストプラクティスです。

  • ユーザー入力: 「PC つかない」 $\leftrightarrow$ FAQ質問: 「パソコンが起動しません」

このように、異なる表現だけど同じ意図を持つペアを大量に用意することで、モデルは「PC=パソコン」「つかない=起動しない」という等価関係を学習します。

さらに、現代のデータセット作成において無視できないのが生成AI(LLM)の活用です。
実際のクエリログが不足している場合でも、ChatGPTの最新モデルなどを活用して、「この質問文の言い換えパターンを10個生成して」と指示することで、多様な表現を含む高品質な学習データ(Synthetic Data)を効率的に量産することが可能です。これは「データ拡張(Data Augmentation)」の一種として、非常に強力な手法です。

あえて間違わせる「ハードネガティブ」データの重要性

さらに精度を高めるための高度なテクニックとして「Hard Negative(ハードネガティブ)」があります。

通常のネガティブ(不正解)データは、ランダムに選んだ全く関係のない文章(例:「PCが動かない」に対して「カレーの作り方」)を使います。これだとモデルは簡単に「違う」と見抜けてしまい、学習効果が限定的になりがちです。

ハードネガティブとは、「一見似ているけれど、実は違う」文章のことです。

  • Anchor: 「iPhoneの電源が入らない」
  • Positive: 「スマートフォンの起動トラブル」
  • Hard Negative: 「iPhoneの着信音が鳴らない」

このHard Negativeを含めた3つのデータ(Triplets形式)で学習させることで、モデルは「iPhone」という単語の共通性だけに引きずられず、「電源」と「着信音」という文脈の決定的な違いをより繊細に区別できるようになります。

特に、キーワード検索(BM25など)とベクトル検索を組み合わせるハイブリッド検索が主流となりつつある現在、ベクトルモデルには「単語の一致」以上の「文脈理解」が求められます。ハードネガティブによる学習は、この識別能力を養うために不可欠です。

学習データのフォーマット変換

今回は、実装のシンプルさと効果のバランスが良い InputExample 形式でデータを準備します。ここではCSVからデータを読み込む想定のコードを示します。

import pandas as pd
from sentence_transformers import InputExample

# データの準備(実際にはCSVから読み込むなどを想定)
# train_data = pd.read_csv("my_faq_data.csv")

# サンプルデータ(Anchor, Positive)
# 実践的には、ここでLLMを用いて生成した言い換えデータも含めると効果的です
train_examples = [
    InputExample(texts=['PCが動かない', 'パソコンが起動しない場合の対処法']),
    InputExample(texts=['ネットに繋がらない', 'Wifi接続のトラブルシューティング']),
    InputExample(texts=['パスワードを忘れた', 'アカウントロックの解除申請']),
    # ... ここに数百〜数千ペアを用意する
]

print(f"学習データ数: {len(train_examples)}")

4. 実践:Sentence-BERTのファインチューニング

データセットの準備が整ったら、モデルのトレーニングフェーズに入ります。ここでは、検索精度の向上に定評のある MultipleNegativesRankingLoss を採用し、実装を進めていきます。

損失関数(Contrastive Loss / Triplet Loss)の選択

なぜ数ある損失関数の中で MultipleNegativesRankingLoss が推奨されるのでしょうか。最大の理由は、「Positive(正解)なペアだけ用意すれば機能する」という実用性の高さにあります。

従来のTriplet Lossなどでは、質問に対して「正解」と「不正解(Negative)」のペアを明示的に用意する必要がありました。しかし、MultipleNegativesRankingLoss は、バッチ内の他のすべてのデータを自動的に「Negative(不正解)」として扱います(In-batch Negatives)。

例えば、バッチサイズが16の場合、ある質問ペアにとって、自分以外の15個のペアの回答はすべて「不正解」と見なされます。これにより、FAQのような「特定の質問には特定の回答がある」データセットにおいて、アノテーションコストを最小限に抑えつつ、効率的に学習させることが可能です。これは、キーワードの一致率に依存するBM25などの従来手法では捉えきれない、文脈的な類似性をモデルに教え込む上で非常に強力なアプローチとなります。

学習ループの実装とハイパーパラメータ設定

sentence-transformers ライブラリを使用することで、複雑な学習ループもシンプルに記述できます。以下は、標準的な学習コードの実装例です。

from torch.utils.data import DataLoader
from sentence_transformers import losses, SentenceTransformer
import math

# モデルの読み込み(事前学習済みモデルを指定)
# 日本語対応の多言語モデルなどを選択
model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')

# 1. DataLoaderの定義
# バッチサイズはGPUメモリに合わせて調整(一般的に16, 32, 64)
# In-batch Negativesの効果を高めるため、メモリが許す限り大きめが望ましい
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)

# 2. 損失関数の定義
# MultipleNegativesRankingLossを使用
train_loss = losses.MultipleNegativesRankingLoss(model=model)

# 3. ハイパーパラメータの設定
num_epochs = 3  # エポック数。通常は3〜5周で収束することが多い
# ウォームアップ期間の設定(学習の安定化のため最初の10%で学習率を徐々に上げる)
warmup_steps = int(len(train_dataloader) * num_epochs * 0.1)

# 4. モデルの保存パス
model_save_path = 'output/fine-tuned-faq-model'

# 5. 学習の実行
print("学習を開始します...")
model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=num_epochs,
    warmup_steps=warmup_steps,
    output_path=model_save_path,
    show_progress_bar=True
)

print("学習完了!")

学習経過のモニタリングと過学習の回避

学習プロセスにおいて最も警戒すべきは「過学習(Overfitting)」です。これは、学習データには完璧に適合するものの、未知のユーザー入力に対しては精度が落ちてしまう現象です。

これを防ぐためのベストプラクティスとして、以下の点に注意してください:

  1. 検証データ(Validation Set)の活用:
    学習データとは別に、学習に使わないデータセット(全体の10〜20%程度)を用意します。sentence-transformers には InformationRetrievalEvaluator などの評価モジュールが用意されており、これらを使用してエポックごとに検証データでの検索精度を測定することが推奨されます。

  2. Lossの挙動確認:
    Loss(損失)の値が順調に下がっているか確認します。ただし、Lossが下がり続けていても、検証データのスコアが悪化し始めたら過学習のサインです。

学習が完了すると、指定した output ディレクトリにモデルが保存されます。このモデルは、単なるキーワードマッチングを超え、特定のドメイン特有の用語や言い回しを理解する専用の検索エンジンとして機能します。

5. 高速検索エンジンFaissとの結合と精度評価

4. 実践:Sentence-BERTのファインチューニング - Section Image

モデルができたら、それを使って検索システムを組み上げます。データ量が数千件程度なら全件計算でも間に合いますが、数万件を超えると計算時間がネックになります。そこで、Facebook AI Research(現Meta AI)が開発した Faiss を使って、ベクトルの類似度検索を爆速化します。

ベクトルインデックスの構築

まず、保存したモデルをロードし、全てのFAQデータをベクトル化してFaissのインデックスに登録します。

import faiss
import numpy as np
from sentence_transformers import SentenceTransformer

# ファインチューニング済みモデルのロード
fine_tuned_model = SentenceTransformer(model_save_path)

# FAQデータベース(検索対象)
faq_corpus = [
    "パソコンが起動しない場合の対処法",
    "Wifi接続のトラブルシューティング",
    "アカウントロックの解除申請",
    "VPN接続トラブルシューティング",
    "社外からイントラネットへの接続方法",
    # ... その他多数のFAQ
]

# 全FAQをベクトル化
corpus_embeddings = fine_tuned_model.encode(faq_corpus)

# Faissインデックスの作成
# ベクトルの次元数を取得(BERT baseなら通常768次元)
dimension = corpus_embeddings.shape[1]

# L2距離(ユークリッド距離)に基づくインデックスを作成
# コサイン類似度を使いたい場合は、事前にベクトルを正規化して内積(IndexFlatIP)を使うのが一般的
# ここでは簡易的に正規化+内積を使用
faiss.normalize_L2(corpus_embeddings)
index = faiss.IndexFlatIP(dimension)
index.add(corpus_embeddings)

print(f"インデックス構築完了: {index.ntotal} 件")

類似度検索の実装とレスポンス速度の計測

インデックスができれば、検索は一瞬です。実運用では、ユーザーのクエリを受け取り、リアルタイムに類似度を計算して結果を返します。

import time

def search_faq(query, k=3):
    start_time = time.time()
    
    # クエリのベクトル化と正規化
    query_embedding = fine_tuned_model.encode([query])
    faiss.normalize_L2(query_embedding)
    
    # 検索実行(上位k件を取得)
    # D: 距離(類似度スコア), I: インデックス(ID)
    D, I = index.search(query_embedding, k)
    
    end_time = time.time()
    print(f"検索時間: {(end_time - start_time)*1000:.2f} ms")
    
    results = []
    for i in range(k):
        idx = I[0][i]
        score = D[0][i]
        results.append((faq_corpus[idx], score))
    return results

# テスト
query = "VPNに繋がらない"
results = search_faq(query)

for text, score in results:
    print(f"スコア: {score:.4f} | 回答: {text}")

Before/AfterでのMRR(Mean Reciprocal Rank)比較

実際にファインチューニングの効果はどうだったでしょうか?

ベースライン(学習前)ではスコアが低かった「社外からイントラネットへの接続方法」などが、学習後には高いスコアで上位に来るようになっているはずです。これは、学習データに含まれる「VPN」と「イントラネット」の関連性をモデルが学習したためです。

定量的な評価指標としては、MRR(Mean Reciprocal Rank)がよく使われます。これは「正解が何番目に表示されたか」の逆数の平均です。

  • 1位に正解 $\rightarrow$ 1/1 = 1.0
  • 2位に正解 $\rightarrow$ 1/2 = 0.5
  • 圏外 $\rightarrow$ 0

一般的に、汎用モデルでMRR 0.45程度だったものが、適切なデータセットを用いたファインチューニングによってMRR 0.80以上に向上することも珍しくありません。これはユーザー体験として「劇的な改善」と言えます。

さらなる精度向上へ:ハイブリッド検索とリランキング

ベクトル検索だけでは、特定の製品型番や固有名詞の完全一致検索に弱い場合があります。最新の検索システム構築においては、以下の手法を組み合わせて精度を高めるのがトレンドです。

  1. ハイブリッド検索: ベクトル検索(意味理解)とBM25などのキーワード検索(単語一致)を組み合わせ、それぞれのスコアを統合する手法です。Azure AI SearchやMilvusなどの最新バージョンでも、このハイブリッド構成が推奨されています。
  2. リランキング(Rerank): ベクトル検索で広めに候補(例: 50件)を取得した後、Cohere Rerankなどの高精度なクロスエンコーダーモデルを用いて、クエリとの関連度を再計算し並べ替える手法です。計算コストはかかりますが、最終的なTop-3の精度は大幅に向上します。

まずはFaissによるベクトル検索を実装し、ユーザーのフィードバックを見ながら、これらの高度な手法を検討していくのが良いでしょう。

6. 本番運用に向けた最適化と次のステップ

PoC(概念実証)で精度が出たら、本番環境へのデプロイを考えましょう。ここでは、プロフェッショナルな運用に向けたヒントをいくつか共有します。

CPU環境での推論最適化(ONNX化など)

本番環境でGPUサーバーを用意するのはコストがかかります。FAQ検索程度であれば、CPUインスタンスで十分運用可能です。その際、モデルを ONNX (Open Neural Network Exchange) 形式に変換し、ONNX Runtime で推論させることで、PythonネイティブのPyTorchよりも2〜3倍高速化できることがあります。

また、量子化(Quantization)技術を使って、モデルの重みを32bit浮動小数点から8bit整数に変換すれば、モデルサイズを4分の1にし、推論速度をさらに上げることができます。精度劣化はわずかです。

定期的な再学習パイプラインの構想

言葉は生きています。新しい製品名、新しいトラブル、新しい社内用語。これらに対応するためには、モデルを作りっぱなしにするのではなく、MLOps(Machine Learning Operations)の考え方が必要です。

  1. ユーザーの検索ログとクリックログを蓄積する。
  2. 「検索結果が0件だったクエリ」や「検索結果の2ページ目がクリックされたクエリ」を抽出する。
  3. これらを教師データに追加し、月に1回程度、自動で再学習を回す。

このサイクルを作ることができれば、システムは使われれば使われるほど賢くなっていきます。

回答生成(RAG)への拡張可能性

最後に、今回のシステムは今流行りの RAG (Retrieval-Augmented Generation) の基盤そのものであることを伝えておきます。

RAGとは、検索(Retrieval)で見つけたドキュメントを、ChatGPTなどのLLM(大規模言語モデル)に「参考資料」として渡し、「この資料に基づいて回答を作って」と指示する技術です。

ここで重要になるのが、ハイブリッド検索というアプローチです。今回構築したBERTによる「意味検索(ベクトル検索)」に加え、伝統的な「キーワード検索(BM25)」を組み合わせることで、検索精度を相互補完できます。
MilvusやAzure AI Searchなどの最新の検索基盤では、このハイブリッド構成がサポートされており、さらにCohere Rerankのようなクロスエンコーダーを用いた再順位付け(Reranking)を行うことで、精度を劇的に向上させる手法が業界標準となりつつあります。

OpenAIのChatGPTやAnthropicのClaudeなど、LLMの最新モデルは推論能力やコーディング能力が飛躍的に進化していますが、社内固有の情報を正確に扱うためには、依然として検索部分(Retrieval)の精度が命です。「嘘をつかない(ハルシネーションを抑えた)AI」を作る鍵は、LLMの賢さ以上に、渡す情報の質にあると断言します。

まとめ

4. モデルの保存パス - Section Image 3

BERTを用いたFAQ検索システムの構築は、派手な生成AIに比べると地味に見えるかもしれません。しかし、コスト、速度、そして何より「自社の言葉を理解する」という点において、非常に堅実で強力なソリューションです。

今回紹介したコードは、あくまでスタート地点です。まずは手元のFAQデータを使って、小さなモデルを育ててみてください。自分たちが教えた言葉をAIが理解してくれた時の感動は、開発現場にとって何物にも代えがたいものです。

脱・キーワード一致!BERTファインチューニングで自作する高精度FAQ検索システム【Python実装】 - Conclusion Image

参考リンク

コメント

コメントは1週間で消えます
コメントを読み込み中...