工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

Vega-Liteで、Precision、Recall、F1スコアを算出して表示する

概要

Vega-Liteで、Precision、Recall、F1スコアを算出して表示するためのコードを作成し、動作を確認した。



背景と目的

最近たまたまVega、Vega-Liteを使った環境において機械学習モデルの性能指標を表示する機会があったが、Vega、Vega-Liteを使ったことがなく苦労したので、メモしておく。



詳細

0. 環境

Vega-Lite Example Editor上で、サンプルを改造しながら作成することにした。

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  :
}


1. データ

対象データは、モデル出力predictedと正解値actualがセットとなっている以下のようなデータをサンプルとして用いることにする。 2値分類を想定したラベル名 "1" と "0" になっているが、多値分類の場合でも動作する想定とする。

  "data": {
    "values": [
      {"predicted": "1", "actual": "1"}, 
      {"predicted": "1", "actual": "0"}, 
      {"predicted": "0", "actual": "1"},
      {"predicted": "1", "actual": "1"}, 
      {"predicted": "1", "actual": "1"}, 
      {"predicted": "0", "actual": "1"},
      {"predicted": "1", "actual": "1"}, 
      {"predicted": "1", "actual": "1"},
      {"predicted": "0", "actual": "1"}, 
      {"predicted": "1", "actual": "0"},
      {"predicted": "0", "actual": "0"}
    ]
  }

混同行列を書いておくと以下。

actual
10
predicted1527
0314
8311


2. 材料になるデータを算出

まず、view-levelにtransformを追加して、各数値を算出するのに必要な材料データを追加していく。

{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "data": {
    上記
  },
  "transform": [
    ここに追加していく
  ]

2.1 actualの集計

joinaggregateは、集計を行い、その結果を新たな列として元データに追加すること。actualが値 "1" or "0"毎にいくつあるかを集計するので、opにcount、groupbyにactualを設定。

    {
      "joinaggregate": [
        {
          "field": "actual",
          "op": "count",
          "as": "actual-count"
        }
      ],
      "groupby": ["actual"]
    },

2.2 predictedの集計

actualと同様。

    {
      "joinaggregate": [
        {
          "field": "predicted",
          "op": "count",
          "as": "predicted-count"
        }
      ],
      "groupby": ["predicted"]
    },

2.3 混同行列

groupbyの条件を工夫すると、混同行列の各セルの値が算出できる。この場合、fieldは要らない。

    {
      "joinaggregate": [
        {
          "op": "count",
          "as": "confusion-matrix"
        }
      ],
      "groupby": ["predicted", "actual"]
    },


3. 性能指標を算出

transformの中に、さらに追加していく。 calculateを用いて、2で追加した値を使って算出する。

3.1 Recall

    {
      "calculate": "datum['confusion-matrix'] / datum['actual-count']",
      "as": "recall"
    },

3.2 Precision

    {
      "calculate": "datum['confusion-matrix'] / datum['predicted-count']",
      "as": "precision"
    },

3.3 F1スコア

    {
      "calculate": "2 / (1 / datum.recall + 1 / datum.precision)",
      "as": "F1-score"
    },

ここまでで、追加された列を含めたテーブルを確認すると以下のようになる。 Vega EditorのDATA VIEWERを使って確認したところ以下のようになった。各値が正しく算出されている。

3.4 無意味な値を削除

上記で算出した値のうち、

predictedとactualの値が異なるレコード

に対してはRecallとPrecisionは無意味である。そこで、それらを除外しておくため、transformに以下のfilterを追加。

    {
      "filter": "datum.predicted == datum.actual"
    }

算出結果として、以下のテーブルが得られる。


4. 可視化

4.1 スコアをバーチャートで表示

以下をview-levelに追加。yでaggregateをaverageとすることで、3.4のテーブルが集計されるが、predictedとactualの値が同じものはすべて同じなのでそのaverageをとっても同じ、という仕組みでほしい値を表示する。

  "repeat": {"layer": ["recall", "precision", "F1-score"]},
  "spec": {
    "mark": "rect",
    "encoding": {
      "x": {
        "field": "predicted",
        "type": "nominal"
      },
      "y": {
        "field": {"repeat": "layer"},
        "type": "quantitative",
        "title": "score"
      },
      "color": {"datum": {"repeat": "layer"}, "title": "性能指標"},
      "xOffset": {"datum": {"repeat": "layer"}}
    }
  },
  "config": {
    "mark": {"invalid": null}
  }

表示結果は以下。 2値分類の場合、"0"すなわち陰性に対してはRecallやprecisionという値は算出しないが、多値分類のときにその分類に対しての値を表示できるようにするため、敢えて表示したままにしている。"0"を削除したければ、transformにfilterを追加すればよい。



参考


まとめと今後の課題

Vega-Liteで、Precision、Recall、F1スコアを算出して表示するためのコードを作成できた。今後さらにいろいろ活用したい。