MLflowでカスタムLLM判定メトリクスを作ってローカルLLMを評価する
はじめに
前回はLLM判定メトリクスを使ってローカルLLMを評価してみました。今回はLLM判定メトリクスをカスタマイズしてみたいと思います。
以下に今回のコードを置いてあります。
ゴール
MLflowでカスタムLLM判定メトリクスを作ってローカルLLMを評価できている
環境
本記事の動作確認は以下の環境で行いました。
- MacBook Pro
- 14 インチ 2021
- チップ:Apple M1 Pro
- メモリ:32GB
- macOS:15.5(24F74)
カスタムLLM判定メトリクスを作る
本記事では、カスタムLLM判定メトリクスを定義するprofessionalism_metrics.py
と、そのメトリクスを使ってLLMを評価するmlflow_evaluate.py
の2つのファイルを作成します。
-
professionalism_metrics.py
: カスタムLLM判定メトリクス(professionalism
)の定義と、その評価例(EvaluationExample
)を記述します。これにより、メトリクス自体を再利用可能な形で定義し、評価ロジックを独立させます。 -
mlflow_evaluate.py
: 定義したカスタムメトリクスを実際にMLflowの評価プロセスに組み込み、LLMの評価を実行するメインスクリプトです。
前回まででLLM判定メトリクスを使ってローカルLLMを評価できる状態が構築できています。
このLLM判定メトリクスをカスタマイズします。
MLflowは、毒性(toxicity)や可読性(flesch_kincaid_grade_level)など、汎用的なLLM判定メトリクスを標準で提供しています。しかし、特定のビジネス要件やドメイン固有の品質基準(例:専門性、特定の業界用語の使用、ブランドイメージへの適合性など)を評価するには、これらの既存メトリクスだけでは不十分な場合があります。このような場合に、独自の評価基準をLLMに判断させるカスタムLLM判定メトリクスが必要となります。
以下の情報を参照して作業していきます。
mlflow.metrics.genai.make_genai_metric()
APIを使うことで、カスタムLLM判定メトリクスを作成することができます。以下の情報が必要になります。
- name: カスタムメトリクスの名前
- definition: メトリクスがしていることの説明
- grading_prompt: スコアリングの指標を説明する。このプロンプトがLLMに直接与えられ、LLMがどのようにスコアリングを行うかを指示します。スコア0から4までの具体的な基準は、この
grading_prompt
内に詳細に定義されています。 - examples (Optional): input/output の例とスコアのセット
こちらに詳細な情報があります。
内部的には、definition
とgrading_prompt
とexamples
を組み合わせて、長いプロンプトとしてLLMに与えることになります。
カスタムLLM判定メトリクスである "professionalism" を作ってみましょう。これはoutputが専門的であるかどうかを評価します。
まずmlflow.metrics.genai.EvaluationExample()を使ってLLM判定が使う例を作成しましょう。
そのためには以下の内容が必要になります。
- input: モデルの入力
- output: モデルの出力
- score: モデルの出力を評価するスコア
- justification: スコアの根拠
以下のような例になります。
今回は professionalism_metrics.py
として以下のファイルを作成します。
import mlflow
##################
# 専門性の評価例を定義
##################
professionalism_example_score_2 = mlflow.metrics.genai.EvaluationExample(
input="What is MLflow?",
output=(
"MLflow is like your friendly neighborhood toolkit for managing your machine learning projects. It helps "
"you track experiments, package your code and models, and collaborate with your team, making the whole ML "
"workflow smoother. It's like your Swiss Army knife for machine learning!"
),
score=2,
justification=(
"The response is written in a casual tone. It uses contractions, filler words such as 'like', and "
"exclamation points, which make it sound less professional. "
),
)
professionalism_example_score_4 = mlflow.metrics.genai.EvaluationExample(
input="What is MLflow?",
output=(
"MLflow is an open-source platform for managing the end-to-end machine learning (ML) lifecycle. It was "
"developed by Databricks, a company that specializes in big data and machine learning solutions. MLflow is "
"designed to address the challenges that data scientists and machine learning engineers face when "
"developing, training, and deploying machine learning models.",
),
score=4,
justification=("The response is written in a formal language and a neutral tone. "),
)
#########################
# 専門性の評価メトリクスを定義
#########################
def professionalism(
model: str,
): # answer_similarity 等既存のメトリクスに合わせてmodelを引数にしました
return mlflow.metrics.genai.make_genai_metric(
name="professionalism",
definition=(
"Professionalism refers to the use of a formal, respectful, and appropriate style of communication that is "
"tailored to the context and audience. It often involves avoiding overly casual language, slang, or "
"colloquialisms, and instead using clear, concise, and respectful language."
),
grading_prompt=(
"Professionalism: If the answer is written using a professional tone, below are the details for different scores: "
"- Score 0: Language is extremely casual, informal, and may include slang or colloquialisms. Not suitable for "
"professional contexts."
"- Score 1: Language is casual but generally respectful and avoids strong informality or slang. Acceptable in "
"some informal professional settings."
"- Score 2: Language is overall formal but still have casual words/phrases. Borderline for professional contexts."
"- Score 3: Language is balanced and avoids extreme informality or formality. Suitable for most professional contexts. "
"- Score 4: Language is noticeably formal, respectful, and avoids casual elements. Appropriate for formal "
"business or academic settings. "
),
# ここで評価例を渡しています
examples=[professionalism_example_score_2, professionalism_example_score_4],
model=model,
parameters={"temperature": 0.0},
aggregations=["mean", "variance"],
greater_is_better=True,
)
上記のprofessionalism
メトリクス定義において、parameters={"temperature": 0.0}
を設定しています。これは、LLMの出力のランダム性を制御するパラメータであり、0.0
に設定することで、LLMが最も確信度の高い、一貫性のある評価結果を生成するようになります。これにより、評価の再現性と信頼性が向上します。
上記をインポートして評価してみます。
mlflow_evaluate.py
を以下のように変更します。
from typing import Any
import mlflow
import mlflow.pyfunc
import pandas as pd # type: ignore
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from mlflow.deployments import set_deployments_target
from professionalism_metrics import professionalism
eval_data = pd.DataFrame(
{
"inputs": [
"What is MLflow?",
"What is Spark?",
],
"ground_truth": [
"MLflow is an open-source platform for managing the end-to-end machine learning "
"lifecycle. It was developed by Databricks, a company that specializes in big data and "
"machine learning solutions. MLflow is designed to address the challenges that data "
"scientists and machine learning engineers face when developing, training, and deploying "
"machine learning models.",
"Apache Spark is an open-source, distributed computing system designed for big data "
"processing and analytics. It was developed in response to limitations of the Hadoop "
"MapReduce computing model, offering improvements in speed and ease of use. Spark "
"provides libraries for various tasks such as data ingestion, processing, and analysis "
"through its components like Spark SQL for structured data, Spark Streaming for "
"real-time data processing, and MLlib for machine learning tasks",
],
}
)
# MLflowサーバーのURIを設定
mlflow.set_tracking_uri("http://localhost:5001")
mlflow.set_experiment("my-genai-experiment")
# MLflow AI gatewayのターゲットを設定
set_deployments_target("http://localhost:5002")
# 既存のアクティブなrunがあれば終了
if mlflow.active_run():
mlflow.end_run()
# mlflow.pyfunc.log_model にわたすために必要な関数を実装する
class ChatOpenAIWrapper(mlflow.pyfunc.PythonModel): # type: ignore
def __init__(self):
self.system_prompt = "Answer the following question in two sentences"
def predict(
self,
context: mlflow.pyfunc.PythonModelContext, # type: ignore
model_input: pd.DataFrame,
) -> list[str | list[str | dict[Any, Any]]]:
# contextからモデル設定を取得することも可能(今回は使用しない)
# model_config = context.artifacts if context else {
# ここで使いたいモデルを指定する
llm = ChatOpenAI(
base_url="http://localhost:1234/v1",
api_key=None,
temperature=0.7,
name="google/gemma-3-12b",
)
# 結果はまとめて返却する
predictions = []
for question in model_input["inputs"]:
response = llm.invoke(
[
SystemMessage(content=self.system_prompt),
HumanMessage(content=question),
]
)
predictions.append(response.content)
return predictions
with mlflow.start_run() as run:
# 入力例を作成
input_example = pd.DataFrame({"inputs": ["What is MLflow?"]})
# モデルのsignatureを推論するための予測を実行
model_instance = ChatOpenAIWrapper()
# カスタムモデルをMLflowモデルとしてログ(signatureとinput_exampleを指定)
logged_model_info = mlflow.pyfunc.log_model(
name="model",
python_model=model_instance,
input_example=input_example,
)
# デプロイ済みのエンドポイントを使って専門性を評価する
my_professionalism_metrics = professionalism(model="endpoints:/chat") # 変更点はここ!!!!
# 事前定義された question-answering metrics を使って評価する
results = mlflow.evaluate(
logged_model_info.model_uri,
eval_data,
targets="ground_truth",
model_type="question-answering",
# LLM判定メトリクスを追加
extra_metrics=[
my_professionalism_metrics,
],
)
print(f"See aggregated evaluation results below: \n{results.metrics}")
# `results.tables` でデータ毎の結果を取得できる
eval_table = results.tables["eval_results_table"]
print(f"See evaluation table below: \n{eval_table}")
実行してみます。
$ pipenv run python3 ./mlflow_evaluate.py
以下の結果となりました
{
"toxicity/v1/mean": 0.00014185939653543755,
"toxicity/v1/variance": 3.664199391347333e-12,
"toxicity/v1/p90": 0.0001433907644241117,
"toxicity/v1/ratio": 0.0,
"flesch_kincaid_grade_level/v1/mean": 15.784054878048781,
"flesch_kincaid_grade_level/v1/variance": 0.6344996737433081,
"flesch_kincaid_grade_level/v1/p90": 16.421298780487806,
"ari_grade_level/v1/mean": 18.37282393292683,
"ari_grade_level/v1/variance": 3.700858050816681,
"ari_grade_level/v1/p90": 19.911833079268295,
"exact_match/v1": 0.0,
"professionalism/v1/mean": 3.5,
"professionalism/v1/variance": 0.25
}
professionalismが出力されています!
数値の妥当性などは別途評価する必要がありそうですが、カスタムLLM判定メトリクスを作成し使うことができました。
おわりに
今回はLLM判定メトリクスをカスタマイズしてローカルLLMの評価をしてみました。
エンドポイントの指定方法など作法があり、躓く部分もありましたがさほど難しくはありませんでした。
次はローカルLLMのRAG構成について調べてみたいと思います。
Discussion