機械学習・RAG / 手元で切り分ける
困りごとチートシート
モデルの評価に納得がいかないときや、保存したモデルが動かないときは、設定を変える前に原因を切り分ける必要があります。このページでは、作業中の症状に合わせて確認手順を選び、その結果から次に調べる箇所を絞れるようにまとめました。
確認コードは手元のPython環境で実行できます。詳しい比較条件や測定結果が必要になったら、各手順の末尾にある検証記事へ進んでください。
11の症状をすべて見る
01 / 評価
RMSEの目安が分からない。どれくらいなら使える?
- 用意するもの
- 学習側の実測値と、評価側の実測値・予測値に加え、用途から決めた許容誤差を用意します。NumPyを使って手元で計算するため、実データをサイトへ送る必要はありません。
- まず確認
- モデルのRMSEを、学習側の平均を返すだけの基準予測と同じ評価行・単位で比べます。単純な予測からの改善幅が分かったら、用途に必要な精度を満たすかを見るため、許容誤差を超える件数も数えます。
確認結果から対処を選ぶ
- 標準化・対数変換後の数値を見ている
- 実測値と予測値を同じ単位へ戻してRMSEを計算する。RMSEの数値だけをexpで戻す方法は使わない。
- 基準予測よりRMSEが悪い
- 行順・単位・前処理・分割を照合する。モデルの調整へ進む前に、単純な基準予測も候補に残す。
- 基準より良いが許容誤差を超える行がある
- 大外れの行と、過小・過大予測の内訳を確認する。用途上の損失を満たすかを判断し、実際に起こる失敗を黙って除かない。
- この評価では改善し、超過もない
- 別の妥当な分割・期間で確認する。対処法は検証データで選び、条件を固定して最終テストを行う。
確認コードと結果の読み方
Python 3.12.13・NumPy 2.5.4で確認した4行の架空例です。末尾の4入力を置き換えて手元で実行します。単一の目的変数・行の重みが等しい評価が対象です。
import numpy as np
def check_regression(y_train, y_eval, y_pred, tolerance):
# 単一の目的変数、各行を同じ重みで評価する
train, truth, pred = [np.asarray(v, dtype=float)
for v in (y_train, y_eval, y_pred)]
if any(v.ndim != 1 or v.size == 0 or not np.isfinite(v).all()
for v in (train, truth, pred)):
raise ValueError("空でない1次元の有限な数値配列を入れてください")
if truth.shape != pred.shape:
raise ValueError("評価用の実測値と予測値の長さを揃えてください")
if not np.isfinite(tolerance) or tolerance < 0:
raise ValueError("許容誤差には0以上の有限な数値を入れてください")
# 基準値は学習側だけで決め、評価側の平均や中央値は使わない
with np.errstate(over="raise", under="raise", invalid="raise", divide="raise"):
mean_prediction = np.mean(train)
median_prediction = np.median(train)
error = pred - truth # 正なら過大予測、負なら過小予測
absolute_error = np.abs(error)
rmse = float(np.sqrt(np.mean(error ** 2)))
baseline_rmse = float(np.sqrt(np.mean((truth - mean_prediction) ** 2)))
mae = float(np.mean(absolute_error))
baseline_mae = float(np.mean(np.abs(truth - median_prediction)))
reduction = None if baseline_rmse == 0 else (1 - rmse / baseline_rmse) * 100
if reduction is not None and not np.isfinite(reduction):
reduction = None
return {
"n": len(truth), "rmse": rmse, "baseline_rmse": baseline_rmse,
"reduction_percent": reduction,
"mae": mae, "baseline_mae": baseline_mae,
"over_tolerance": int(np.sum(absolute_error > tolerance)),
"under": int(np.sum(error < -tolerance)),
"over": int(np.sum(error > tolerance)),
"max_absolute_error": float(absolute_error.max()),
}
# 以下は計算確認用の架空の例。実モデルの性能を示すデータではない
# y_trainだけが学習側。y_evalとy_predは未学習の同じ行順・元の単位で揃える
y_train = [80., 100., 100., 160.]
y_eval = [80., 100., 120., 100.]
y_pred = [82., 101., 118., 116.]
tolerance = 5. # 説明用の仮定。実際は用途から事前に決める
r = check_regression(y_train, y_eval, y_pred, tolerance)
print(f"RMSE: モデル {r['rmse']:.3f} / 学習側平均の基準 {r['baseline_rmse']:.3f}")
rate = r["reduction_percent"]
print("RMSE改善率:", "計算不能" if rate is None else f"{rate:.1f}%")
print(f"MAE: モデル {r['mae']:.3f} / 学習側中央値の基準 {r['baseline_mae']:.3f}")
print(f"許容誤差を超えた行: {r['over_tolerance']}/{r['n']}")
print(f"超過の内訳: 過小予測 {r['under']} / 過大予測 {r['over']}")
print(f"最大絶対誤差: {r['max_absolute_error']:.3f}")この例の実行結果
RMSE: モデル 8.139 / 学習側平均の基準 17.321 RMSE改善率: 53.0% MAE: モデル 5.250 / 学習側中央値の基準 10.000 許容誤差を超えた行: 1/4 超過の内訳: 過小予測 0 / 過大予測 1 最大絶対誤差: 16.000
この例では基準から改善しても、仮の許容誤差5を超える行が1件残るため、改善率だけでは採用を決められません。また、基準が0の場合は改善率を計算できず、時系列では時間順の分割や前期値などとの比較も必要です。下の検証記事では、こうした条件を含めて結果の読み方を説明しています。
ここまで確認できたら 単位、基準からの差、許容誤差の超過件数が分かれば、モデルを採用するために何が足りないかを判断できます。一律の合格点や1回の評価で決めず、別の分割・期間でも確かめます。
判断するときの注意点 基準より誤差が小さくても、用途に必要な精度を満たすとは限りません。比較を甘くしないよう、基準予測は学習側だけから作り、許容誤差も評価結果を見る前に決めておきます。
RMSEの改善幅を計算し、許容誤差と比べる →ページ先頭へ戻る02 / 評価
RMSEとMAEで、良いモデルが違う
- 用意するもの
- 同じ評価行の実測値、2モデルの予測値、外したときの損失の考え方。コード例はNumPyを使います。
- まず確認
- 同じ評価データへの予測を比べていることを確かめたうえで、大きな誤差が出た行を調べます。平均・中央値を返すだけの予測とも比べると、モデルによる改善幅と、大外れの影響を分けて考えられます。
確認結果から対処を選ぶ
- 行の順序・単位・評価対象が違う
- まず揃えて再計算する。対数変換後の予測なら、比較したい単位に戻っているか確認する。
- 少数の大外れでRMSEが悪化している
- 入力ミスか実際に起こる事象かを確認する。実際の大外れの損失が重いなら、その行を除かず評価へ含める。
- 条件は同じで、指標ごとに順位が違う
- 通常の誤差と大外れのどちらを減らしたいかを決める。損失が未定なら、まだ1モデルに決めない。
確認コードと結果の読み方
説明用の4行で、MAEとRMSEの順位が割れることを確認できます。手元の値へ置き換える際は、学習データへの予測ではなく評価用の予測を使ってください。
import numpy as np
# 同じ行順・同じ単位の評価用実測値と、2モデルの予測値に置き換える
# 以下は説明用の小さな例。実モデルの性能ではない
truth = np.array([10., 10., 10., 10.])
predictions = {"A": [10., 10., 10., 18.], "B": [13., 13., 13., 13.]}
if truth.ndim != 1 or len(truth) == 0 or not np.isfinite(truth).all():
raise ValueError("truthには空でない1次元の有限な数値を入れてください")
for name, values in predictions.items():
pred = np.asarray(values, dtype=float)
if pred.shape != truth.shape or not np.isfinite(pred).all():
raise ValueError("予測は実測値と同じ長さの、欠損のない1次元配列にしてください")
error = np.abs(pred - truth)
print(name, "MAE", round(float(error.mean()), 3),
"RMSE", round(float(np.sqrt(np.mean(error ** 2))), 3),
"最大絶対誤差", round(float(error.max()), 3))この例の実行結果
A MAE 2.0 RMSE 4.0 最大絶対誤差 8.0 B MAE 3.0 RMSE 3.0 最大絶対誤差 3.0
Aは多くの行で誤差が小さい一方、大外れがあります。この出力だけではA・Bのどちらが業務に適するかは決まりません。基準モデルも比べるなら、学習側だけから求めた平均・中央値を評価側へ予測します。
ここまで確認できたら 同一条件での誤差と、どの誤りを重く見るかを説明できる。指標名だけで採用モデルを決めない。
判断するときの注意点 指標の多数決でモデルを選ばない。MAEの改善幅は、平均予測だけでなく中央値予測とも比べる。
6指標で順位が変わる条件と確認コード →ページ先頭へ戻る03 / 評価
MAPEが巨大になる・RMSLEが計算できない
- 用意するもの
- 実測値と予測値の最小値、ゼロ・負の値の件数、指標を計算した関数と版。
- まず確認
- MAPEでは実測値を分母にするため、0や極端に小さい値が含まれていないかを確認します。RMSLEで計算できない場合は、実測値と予測値の両方について、負の値が入った件数を数えます。
確認結果から対処を選ぶ
- 実測値が0または0に近い
- MAPEの分母とゼロ処理を確認する。割合の誤差が必要な理由を見直し、MAE・RMSEも併記する。
- RMSLEに負の実測値・予測値が入る
- その指標はそのまま使えない。データミスなら修正し、負が正当な値なら別の指標を選ぶ。
ここまで確認できたら 計算から外した行や値の変換を説明できる。巨大な値や計算不能を、モデル性能だけの問題として扱わない。
判断するときの注意点 0の行を黙って除いたり、負の予測を0に直したりすると評価条件が変わる。ライブラリによってゼロの扱いも異なる。
MAPEのゼロ・RMSLEの負の値への対応 →ページ先頭へ戻る04 / 評価
正解率は高いのに、見つけたい少数派を拾えない
見かけるメッセージ: UndefinedMetricWarning / precision・recall・F1が0
- 用意するもの
- 2値分類の実測ラベルと予測ラベル。検出したいクラスを1、それ以外を0として揃えます。確率はこのコードへ直接渡しません。
- まず確認
- 混同行列で少数派の実際の件数と見逃した件数を数え、正解率と並べて確認します。少数派をすべて見逃していても高い正解率になる場合があるため、集計したスコアだけでは判断できません。
確認結果から対処を選ぶ
- TP + FN = 0(評価データに陽性がない)
- このデータではrecallを評価できない。分割やラベルの対応を確認し、評価対象を組み直す。閾値の調整へ進まない。
- 陽性はあるが、TP + FP = 0(陽性と予測した行がない)
- ラベルの対応と予測スコアを確認する。スコアが得られるなら、検証データで閾値と誤検知件数の兼ね合いを調べる。
- 陽性の実測・予測は両方ある
- FN(見逃し)とFP(誤検知)を別々に見る。どちらを何件まで許容できるかを決めて対処を比べる。
確認コードと結果の読み方
NumPyを使用。以下は陽性2件を両方見逃す説明用の例です。多クラス分類をそのまま入れることはできません。
import numpy as np
# 2値分類。先に、検出したいクラスを1、それ以外を0に対応づける
# 同じ行順の実測ラベルと予測ラベルに置き換える(確率ではない)
y_true = np.array([0, 0, 0, 0, 1, 1])
y_pred = np.array([0, 0, 0, 0, 0, 0])
if (y_true.ndim != 1 or y_true.size == 0 or y_true.shape != y_pred.shape
or not np.isin(y_true, [0, 1]).all() or not np.isin(y_pred, [0, 1]).all()):
raise ValueError("同じ長さの0/1ラベル配列を入れてください")
TP = int(((y_true == 1) & (y_pred == 1)).sum())
FN = int(((y_true == 1) & (y_pred == 0)).sum())
FP = int(((y_true == 0) & (y_pred == 1)).sum())
TN = int(((y_true == 0) & (y_pred == 0)).sum())
print({"TP": TP, "FN": FN, "FP": FP, "TN": TN})
print("実際の陽性件数:", TP + FN, "予測した陽性件数:", TP + FP)
print("recall:", TP / (TP + FN) if TP + FN else "計算不能: 評価に陽性なし")
print("precision:", TP / (TP + FP) if TP + FP else "計算不能: 陽性予測なし")この例の実行結果
{'TP': 0, 'FN': 2, 'FP': 0, 'TN': 4}
実際の陽性件数: 2 予測した陽性件数: 0
recall: 0.0
precision: 計算不能: 陽性予測なし実測の陽性が2件あるためrecallは0。陽性と予測した行がないのでprecisionの分母は0です。ライブラリが表示する0と、分母が0で計算できない状態を分けて読みます。
ここまで確認できたら 「陽性がない」と「陽性を拾えない」を区別し、見逃し・誤検知の実数を説明できる。警告を非表示にするだけでは解決にならない。
判断するときの注意点 recallだけを上げると誤検知が増える場合がある。テスト結果を見ながら閾値を選び直さない。検証・テスト側は再サンプリングせず、運用時に近いクラス比率で評価する。precisionは比率でも変わる。
少数派の件数から評価する手順 →ページ先頭へ戻る05 / 分割・再現性
交差検証は良いのに、新しい顧客・人では当たらない
- 用意するもの
- 各行の人・顧客・装置などのIDと、予測したい場面。IDを外部へ送る必要はありません。
- まず確認
- 評価したい対象が新規顧客なのか、既存顧客の将来なのかを明確にします。新規顧客への性能を知りたい場合は、同じ人・顧客の行が学習側と検証側の両方に入っていないかを調べます。
確認結果から対処を選ぶ
- 未知の対象を評価したいが、同じIDが両側にある
- そのIDでグループ分割し直して評価する。分割後のIDの重複とクラス件数を確認する。
- 既存の対象について、未来を予測したい
- 時間順序と予測時点で利用できる特徴量を確認する。ランダム分割へ戻してスコアを上げない。
- 重複も未来情報も見当たらない
- 前処理、入力の分布、運用時の対象の違いを調べる。性能低下をグループ漏洩と断定しない。
ここまで確認できたら 評価用の分割が、本番で出会う対象と時間の条件に対応している。
判断するときの注意点 既存顧客の将来予測には時間順序の検討も必要。グループ分割だけで、前処理や未来情報による漏洩まで防げるわけではない。
分割単位を選ぶための実測比較 →ページ先頭へ戻る06 / 分割・再現性
前処理やSMOTEを入れたら、スコアが不自然に上がった
- 用意するもの
- データ分割からfit・変換・再サンプリングまでの処理順。交差検証ならfoldごとの処理位置。
- まず確認
- 欠損補完・標準化・特徴選択・再サンプリングが、データを分ける前に実行されていないかを確認します。交差検証では、各foldの学習側だけから処理を決めているかまで追います。
確認結果から対処を選ぶ
- 検証・テストも使って前処理をfitしていた
- 学習側だけでfitする構成へ直し、前処理を含めて評価をやり直す。
- SMOTE後に分割、または評価側まで水増しした
- 元のデータを先に分割し、各foldの学習側だけを再サンプリングする。評価側の件数と比率を戻す。
- 処理順に問題が見つからない
- 特徴量に正解や未来情報が混じらないか、同じ対象が両側にないかを確認する。スコア差だけで漏洩判定をしない。
ここまで確認できたら 各処理がどのデータを見て学習したか説明でき、評価側が元の対象を保っている。
判断するときの注意点 scikit-learnのPipelineと、SMOTEなどに使うimbalanced-learnのPipelineを区別する。スコア上昇だけでは漏洩とは断定できない。
漏洩が起きる5パターンと対処 →ページ先頭へ戻る07 / 分割・再現性
random_stateを変えると、モデルの順位が逆転する
- 用意するもの
- 分割seed、モデルのseed、モデル間で共通の評価行、各回のスコア差。
- まず確認
- モデル同士を同じ分割で比較していることを確かめます。そのうえで、分割に使う乱数とモデル内部の乱数を分けて記録し、どちらを変えたときに順位が動くのかを調べます。
確認結果から対処を選ぶ
- モデルごとに評価行が違う
- 同じ分割で比較し直す。先に分割の条件を揃える。
- 分割だけを変えると順位が動く
- 分割ごとの対応した差を残し、評価件数・対象の構成・ばらつきを確認する。
- 同じ分割でもモデルseedで順位が動く
- モデル内部の乱数も反復する。精度が僅差なら、時間や運用条件も含めて判断する。
ここまで確認できたら どの乱数を変えると差が動いたかが分かり、最良seedだけを採用根拠にしていない。
判断するときの注意点 一番良いスコアが出るseedを採用しない。seed固定は再現のためであり、性能が安定している証明ではない。
乱数による順位の揺れを確かめる →ページ先頭へ戻る08 / 学習・予測
精度差は小さいのに、学習を何度も回すと時間がかかる
- 用意するもの
- 探索候補数×分割数、1回のfit時間、探索全体の時間、CPU・メモリ条件。
- まず確認
- 1回の学習時間と探索全体の時間を分けて測り、木の本数・探索回数・CPUスレッド数などの条件を記録します。処理自体が遅いのか、繰り返し回数が多いのかを区別してから対処を考えます。
確認結果から対処を選ぶ
- 1回のfitは速いが、探索全体が長い
- 候補数と分割数によるfit回数、再学習の有無を数える。探索範囲を絞る場合も、精度との兼ね合いを検証データで確認する。
- 1回のfit自体が長い
- データ量とモデルの設定を固定して測る。GBDTを使っている場合は、木の本数も確認し、同じ評価条件で別のライブラリを比較する。
- 並列度を上げたときだけ遅い
- 他の負荷とメモリ使用量を確認し、同じ小規模探索をn_jobs=1と比較する。並列化すれば必ず速いとは考えない。
ここまで確認できたら fitの回数と1回の時間を区別でき、精度と探索全体の時間を同条件で比較できる。
判断するときの注意点 既定設定の速度倍率は、チューニング全体の倍率ではない。CPUでの比較結果をGPUや別データへそのまま当てはめない。
3ライブラリの精度・学習時間の比較 →ページ先頭へ戻る09 / 学習・予測
保存したモデルが別環境で動かない・予測が変わる
見かけるメッセージ: InconsistentVersionWarning / node array from the pickle has an incompatible dtype
- 用意するもの
- 例外の種類、保存時の依存関係、入力列・型・順序。下のコードはモデルを読み込まず版だけを確認します。
- まず確認
- 保存時と読込時のPython・ライブラリ版に加え、入力列の順序・型と前処理を照合します。読み込みに成功した場合も、同じ確認用入力で予測を比べ、実行環境を移して結果が変わっていないかを確かめます。
確認結果から対処を選ぶ
- 版の警告・pickle内部のdtypeエラー
- 保存時のPython・依存関係を照合し、確認できた環境を隔離して再現する。移行先の版を使う必要があれば、その版で再学習・再評価する。推測した版で現在の環境を上書きしない。
- 読込は成功するが、入力列・特徴量数・型でエラー
- 学習時の入力仕様と前処理を確認する。ライブラリのダウングレードより先に列名・順序・型を照合する。
- 実行できるが予測が違う
- 同じ確認用入力で予測値・確率・最終判定を比較する。差の許容幅は用途に応じて決める。
- 保存時の環境や前処理が不明
- 作成時の記録・依存関係・入力仕様を確認する。推測した型をpickle内部へ足して直す方法を、通常の復旧手順にはしない。
確認コードと結果の読み方
Python標準ライブラリのみ。保存時の環境が残っていれば両環境で実行して比べます。表示される版は手元の環境によって異なります。
import platform
from importlib.metadata import version, PackageNotFoundError
# モデルの読み込みは行わない。保存した環境と読込先の両方で実行する
print("Python:", platform.python_version())
for package in ["scikit-learn", "numpy", "scipy", "pandas", "joblib"]:
try:
print(package + ":", version(package))
except PackageNotFoundError:
print(package + ": 未インストール")版が違えば互換性の確認へ進みます。版が揃っていても、列順・前処理・モデルファイルの違いは別に残ります。この一覧だけで原因を確定しません。
ここまで確認できたら 読込失敗と入力の不一致を区別できる。復旧後は同じ入力の予測比較まで行い、読込成功だけで完了としない。
判断するときの注意点 読み込めても同じ予測になるとは限らない。出所不明のpickle・joblibは読み込まない。
保存モデルの版互換性を調べた検証 →ページ先頭へ戻る10 / RAG
RAGの検索候補に、正解文書が入っていない
見かけるメッセージ: No relevant docs were retrieved / 検索結果が0件
- 用意するもの
- 答えの根拠が分かる質問、索引内の文書、順位順の検索候補。社内文書をここへ入力する必要はありません。
- まず確認
- 生成前の検索候補を直接確認し、答えの根拠を含む文書が索引に登録されているかを調べます。登録されていても、チャンク分割で必要な文脈が切れたり、埋め込みモデルの入力上限で文章が切り捨てられたりしていないかを追います。
確認結果から対処を選ぶ
- 索引内に正解の根拠がない・新しい文書だけ取れない
- 登録先と検索先、更新完了、チャンクの内容を照合する。検索モデルを替える前に、根拠が検索対象へ入っているか確認する。
- しきい値を外すと、候補が返る
- 距離か類似度か、変換後スコアかを確認する。0.8を「正しい確率80%」とは読まない。権限・テナントの絞り込みは外さず、許可された文書内で確認する。
- 根拠は索引にあるが、取得候補の外
- 埋め込みの入力上限、チャンクの欠落、検索件数、固有名詞の一致を順に調べる。リランカーの前に一次検索を直す。
- 候補には正解がある
- 次の「正解文書は候補にある」の項目へ。根拠が何位で、生成へ実際に渡ったかを調べる。
確認コードと結果の読み方
Python標準ライブラリのみ。検索や生成のAPIは呼びません。単一文書内の連続した根拠を、1チャンクが含むかを点検する例です。質問の答え文字列の一致だけで正解にしません。
# 検索APIを呼ばず、既に取得した候補を点検する例
# オフセットは同じ原文の文字位置。endは範囲に含めない
# ここでは、1つの文書の連続した範囲だけで答えられる質問を対象にする
# 以下を手元の正解根拠と、順位順の検索候補へ置き換える
gold = {"doc_id": "guide", "start": 20, "end": 45}
results = [
{"doc_id": "other", "start": 0, "end": 100},
{"doc_id": "guide", "start": 0, "end": 60},
]
def validate_span(span):
if (not isinstance(span["doc_id"], str)
or type(span["start"]) is not int or type(span["end"]) is not int
or not 0 <= span["start"] < span["end"]):
raise ValueError("文書IDと原文の有効なstart/endを入れてください")
validate_span(gold)
for item in results:
validate_span(item)
ranks = [i for i, item in enumerate(results, 1)
if item["doc_id"] == gold["doc_id"]
and item["start"] <= gold["start"] and item["end"] >= gold["end"]]
print("最初の正解順位:", min(ranks) if ranks else "取得候補の中にはない")この例の実行結果
最初の正解順位: 2
「取得候補の中にはない」は、原文に答えがないという意味ではありません。複数の根拠や複数チャンクを組み合わせる質問は対象外です。文字位置を記録していない場合は、候補を目視し、答えの根拠が初めて現れる順位をメモするところから始めてください。
ここまで確認できたら 失敗箇所を「登録・絞り込み・候補の取得・順位」のどこまで絞れたか説明できる。しきい値の最適値はここでは決めない。
判断するときの注意点 大きいチャンクや多い検索件数が常に良いわけではない。文書自体に答えがない質問は別に扱う。
チャンクと検索件数の比較条件 →ページ先頭へ戻る11 / RAG
正解文書は候補にあるのに、上位に出てこない
- 用意するもの
- 一次検索の候補と順位、最終的に生成LLMへ渡った文脈、回答。
- まず確認
- 一次検索の候補に正解が入っているかを調べ、候補からの取りこぼしと、候補内での順位の低さを分けます。正解が候補にない場合は、並べ替えの設定を変えても取り戻せません。
確認結果から対処を選ぶ
- 候補にはあるが、上位の採用件数から漏れる
- 同じ候補をリランカーで並べ替え、根拠の順位と追加時間を比較する。
- 上位にあるが、生成へ渡す文脈にはない
- 文脈を組み立てる処理、長さの上限、除去処理を確認する。検索順位を変える前に、実際に渡した内容を調べる。
- 根拠が生成へ渡っているのに回答が違う
- 検索の改善とは分け、指示・文脈の競合・回答と引用の対応を確認する。リランカーで解決するとは限らない。
ここまで確認できたら 順位の問題か、生成へ渡す工程か、渡った後の回答の問題かを区別できる。
判断するときの注意点 リランカーは候補の外にある文書を救えない。検索スコアとリランカーのスコアを同じ尺度で比べない。回答生成の正確性は別に確認する。
候補の並べ替えが効く条件 →ページ先頭へ戻るこの早見表で分かること
既存の検証記事から、原因を切り分ける手順をまとめています。対処の効果や最適な設定は、データと環境で変わります。実測値をそのまま手元の改善幅として見積もらず、同じ条件で変更前後を比較してください。
前処理と保存モデルの基本的な扱いは、scikit-learn公式の前処理・データ漏洩の注意点とモデルの保存と互換性も参照できます。
入力上限についてはSentence Transformersの仕様、スコアの扱いは使用中の検索基盤の仕様を確認してください。古いライブラリのエラー報告が現在も再現するとは限りません。
まだ解決しないときに残すメモ
まだ解決しない場合は、症状と期待した結果を起点に、実際に起きたこと、確認した条件、変更前後の差、未確認の点をまとめます。試したことと残っている疑問を分けておくと、同じ変更を繰り返さずに相談できます。その際は、実データやIDを架空の小さな例へ置き換え、APIキーや個人情報を含むログを渡さないようにしてください。
手元に置く場合は、ブラウザーの印刷からPDFに保存できます。コードも保存する場合は、必要な「確認コード」を開いてから印刷してください。