📈

センサーデータ(から計算した値)に最頻値フィルタを適用してみる

2024/12/02に公開

これは SMat Advent Calendar 2024 の12/2分の記事です。

こんにちは、株式会社エスマットにてバックエンドを中心に開発をしています、野島といいます。

弊社では在庫の重量を計測し、在庫数を推定する機能を有する在庫管理プロダクトを提供します。なにかを計測する場合、計測値は誤差を含み、この誤差をどう扱うかという課題があります。弊社の重量を測るスマートマットも計測値に誤差を含み[1]、誤差の扱いが課題として存在します。

本記事では誤差による影響を簡単に説明し、データに最頻値フィルタを適用する実験を行います。

誤差による影響

誤差が在庫数の推定に影響を与える一例として、以下のグラフを示します。

在庫数推定の課題感

このグラフでは、赤い破線が真の在庫数(真値)を表し、オレンジの線が計測値を基に推定した在庫数(推定値)を表しています。誤差を含む重量計測値をシミュレートし、在庫1つあたりの重量で割ることで在庫数を算出しています。

誤差が載った推定値は、真値と比較すると細かく上下に揺れる動きが現れています。
実際には在庫数が変動していないにも関わらず、誤差の影響で変動しているように見えてしまうという問題があります。

最頻値フィルタを適用する実験

最頻値フィルタは、データを平滑化しつつエッジを保持します[2]
弊社が扱う在庫データには次のような特徴があります。

  1. 在庫が変動していない期間
    この期間では計測値は誤差の範囲内で安定しており、在庫数の推定値もほぼ一定を保ちます。

  2. 在庫が変動したタイミング
    在庫が減少した、または補充された場合、計測値が大きく変化しそれに伴い推定される在庫数も変動します。このタイミングは、在庫データにおいてもエッジとして現れます。

在庫が変化していない期間を平滑化しつつ、在庫数が実際に変動した時のデータの動きは保持したいので、最頻値フィルタは適している方法と考えます。

今回の実験では、対象の値を中心に前後2つの範囲で最頻値を計算する実装をします。たとえば、在庫数の推移を示す配列 [4,4,5,4,3, ...] があり、index=2 を対象にフィルタを適用する場合、index=0,1,2,3,4 のデータの最頻値を取るとします。この場合4が最頻値なので結果、[4,4,4,4,3, ...] となります。

出現回数が同じ数値が存在する場合は、対象の値により近い値を採用するとします。たとえば、[5,4,4,3,3, ...] というようなデータで、index=2 を対象にフィルタをかける場合、4を採用します。

冒頭で示したデータに最頻値フィルタを適用した結果が下記です。

真値VSフィルタ適用後

赤い破線が真の在庫数(真値)を表し、オレンジの線が在庫数の推定値にフィルタをかけた値を表しています。フィルタを適用することで、オレンジの線から誤差による細かな揺れを削減しつつ、真値とほぼ同じ値で推移しています。いい感じに平滑化しつつ、実際の在庫数の変動に追従できていますね。

以下 R による実装です。

apply_mode_filter <- function(vec, radius) {
  n <- length(vec)
  filtered_vec <- vec
  
  for (i in 1:n) {
    # インデックス範囲を計算
    start_idx <- max(1, i - radius)
    end_idx <- min(n, i + radius)
    
    # ウィンドウを取得
    window <- vec[start_idx:end_idx]
    
    # 最頻値を計算
    freq_table <- sort(table(window), decreasing = TRUE)
    max_freq <- freq_table[1]
    mode_candidates <- as.numeric(names(freq_table[freq_table == max_freq]))
    
    # 最頻値が複数ある場合は元の値に最も近いものを採用
    if (length(mode_candidates) > 1) {
      target_value <- vec[i]
      mode_value <- mode_candidates[which.min(abs(mode_candidates - target_value))]
    } else {
      mode_value <- mode_candidates[1]
    }
    
    # フィルタ後の値を設定
    filtered_vec[i] <- mode_value
  }
  
  return(filtered_vec)
}

# ZaikodNum に最頻値フィルタを適用
simulated$FilteredZaikodNum <- apply_mode_filter(simulated$ZaikodNum, radius = 2)

まとめなど

本記事では、平滑化しつつエッジを保つ方法として最頻値フィルタを適用する実験を行いました。在庫の実際の変化を反映しつつ、誤差の影響を低減できました。

今回の実験では、シミュレーションデータを用いて検証しましたが、本番環境のデータを使って検証するなど検証・評価をしていくことで、オフライン処理による在庫推定の精度向上が期待できます。今後の課題として取り組んでいきたいです。

最後に、弊社技術イベントの宣伝です。ご飯を食べながら技術の話などする会です!
少しでも興味出た方はぜひ!

https://s-mat.connpass.com/event/336943/

脚注
  1. スマートマットの精度に基づき、計測重量と実際の重量とで誤差が発生します。https://smartmat.zendesk.com/hc/ja/articles/14616658175001-スマートマットの計測誤差

    ↩︎
  2. The mode filter is an edge-preserving smoothing filter by taking the mode of the empirical density. https://warwick.ac.uk/fac/sci/statistics/staff/research_students/ip/postphd/

    ↩︎
GitHubで編集を提案
株式会社エスマット

Discussion