背景
- ラベル付きの異常音データは少ない
- 正常音のみを学習させ、**“いつもと違う音”**を自動で検出したい
メルスペクトログラムとは?(用語解説)
- 音声波形を時間×周波数の画像にしたものを、人の聴感に近い Mel 尺度で並べ替えた表現。
- 作り方:STFT(短時間フーリエ変換:短い時間窓に区切って各区間の周波数成分を計算)→ Mel フィルタバンク → 対数(dB)。コントラストが上がり特徴が見えやすい。
- 主なパラメータ:
sample_rate
/n_fft
・win_length
(周波数分解能)、hop_length
(時間分解能)、n_mels
(帯域数)、fmin
/fmax
(注目帯域)。 - 使う理由:ノイズに比較的強く、CNN/Transformerの入力にそのまま使いやすい。正常分布からの外れ=異常が浮きやすい。
from torchaudio.transforms import MelSpectrogram, AmplitudeToDB
mel = MelSpectrogram(sample_rate=16000, n_fft=1024, hop_length=256,
n_mels=80, f_min=20, f_max=8000)
spec = mel(waveform) # (ch, n_mels, time)
log_mel = AmplitudeToDB()(spec)
メルフィルタバンクとは?
- STFTで得た周波数スペクトルをMel尺度上の多数の三角フィルタで加重平均し、帯域ごとのエネルギーに要約する仕組み。
- 目的:聴感に近い分解能/次元削減/微小な周波数ずれに頑健。
- 構造:低周波は狭く密、高周波は広く疎(周波数が上がるほどフィルタ幅が広がる)。
- 主パラメータ:
n_mels
,fmin
,fmax
。
import torch
from torchaudio.functional import create_fb_matrix
n_fft, sr = 1024, 16000
fb = create_fb_matrix(n_fft//2 + 1, sr, n_mels=80, f_min=20., f_max=8000.) # (freq_bins, n_mels)
mel_spec = power_spec @ fb # → (time, n_mels)
解決法:SimCLR を応用した音声特徴学習(先に結論)
SimCLR は画像で使われる自己教師あり学習法ですが、音声にも適用可能です。torchaudio
でメルスペクトログラムを作り、異なる時間帯から切り出した音を“擬似ペア”として学習させます(用語の詳細は後半参照)。
import torch
from torchaudio.transforms import MelSpectrogram
mel = MelSpectrogram(sample_rate=16000, n_fft=1024, hop_length=256, n_mels=80)
logmel = torch.log(mel(waveform) + 1e-6) # (ch, n_mels, time)
# 擬似ペア(Positive):同一クリップの別時間帯を切り出す
t0, t1, w = 100, 260, 96 # 任意の開始フレームと幅
x_i = logmel[..., t0:t0+w] # view 1
x_j = logmel[..., t1:t1+w] # view 2
# さらに微小変換で差分を作る(SpecAugment など)
def aug(x):
return x + 0.01 * torch.randn_like(x)
v_i, v_j = aug(x_i), aug(x_j) # ← これが「擬似ペア」
# 以降:エンコーダ f(·) → 投影ヘッド g(·) → InfoNCE 損失へ
なぜ効く?
「正常な音」のバリエーションを学習することで、「未知の異常音」が埋め込み空間で浮き上がる。
「別時間帯」とは
同じ録音(同じ機械・同じ環境)の中で、時間窓だけを変えて切り出した 2 片のこと。例:2.0–2.6 秒 と 5.3–5.9 秒。いずれも“正常”なので Positive(近づけるべきペア) とみなす。
「軽微な変換(Augmentation)」とは
画像の明るさ調整や小さな切り抜きに相当する、正常の範囲内のゆらぎを付与する操作。
- 小さな時間シフト/トリミング、±1–2 dB のゲイン調整、微量ノイズ付与
- 短い時間/周波数マスキング(弱めの SpecAugment)、ごく軽い残響 注意:大きなピッチ変更・広帯域のバンドストップ・強いタイムストレッチは故障音に似やすいので避けるか極力弱く。
「高密度の島」とは
学習では 同一源の別表現(別時間窓+軽微変換)を近く、別の源(他クリップ)を遠く にするよう最適化する。すると正常音はベクトル空間で ぎゅっと固まった塊(島) を形成。未知の異常音はその島から離れやすく、距離(例:マハラノビス) や 密度(例:kNN) で検知できる。
「正常の変換が偶然“異常”に寄る」問題と対策
- 弱い Aug から開始し、実運用で“正常の範囲のゆらぎ”として許容できる軽微な加工のみ採用(現場ヒアリング+聴感確認)。
- Positive は近接窓のみ(同一クリップ内・±数秒)に限定し、離れた区間は Positive にしない。
- Hard negative:似ているが別クリップ/別日を Negative に混ぜる。
- 正常のみの検証セットで監視:Aug 追加後に正常埋め込みの分散が縮むか(壊れていないか)を確認。
- 頑健なしきい値:ロバスト共分散推定や分位点で閾値設定。
- 微量監督の併用:少数の異常を負例(Outlier Exposure)として混ぜる/後段で One-Class(Center-loss, DeepSVDD)で微調整。
※ 実運用では、データと現場知見に基づく微調整が不可欠で、最終的には“職人技”に近い感覚も求められます。
これらにより、「同じ正常=近い」「別物=遠い」 という幾何配置を学び、島から外れた音を異常と見なせる。
データ作成と学習の詳細(Positive/Negative とハイパラの目安)
Positive(近づけるペア)の作り方
- 窓長 w: 0.5–1.0 秒(装置の周期に合わせて調整)。
- 時間差 Δt(同一クリップ内): ±2–3 秒以内を推奨。モード変化が少ない“近接”だけを Positive に。
- 重なり: 50% 程度まで許容。起動/停止直後などの遷移区間は除外(RMS の急変やスペクトルセントロイドの大きな変化でフィルタリング)。
- Augmentation: 弱い時間シフト、微量ノイズ、±1–2 dB ゲイン、短い時間/周波数マスク、軽い残響まで。ピッチ大変更や強いストレッチは避ける。
Augmentation の選定基準(現場運用)
実運用で“正常の範囲のゆらぎ”として許容できる軽微な加工のみ採用(故障に近い変化は入れない)
判断基準(例)
- クラス不変:加工しても“正常”のまま(故障らしさを生まない)
- 構造保持:基音・倍音、回転周期など機械固有のパターンが崩れない
- 強度は弱く:人が聞いて「雰囲気は同じ」と感じる程度
妥当な操作(例)
- 小さな時間シフト(±数十 ms)、短いトリミング
- ±1–2 dB のゲイン調整、SNR ≥ 25–30 dB の微量ノイズ付与
- 弱い SpecAugment(短い時間マスク、狭い周波数マスク)
- 軽い残響(実環境に近い IR でごく弱く)
避けたい操作(例)
- 大きなピッチ変更、強いタイムストレッチ
- 広帯域のバンドストップ/過度なノイズリダクション
- 長時間のマスキングや広い帯域マスク
実務での決め方
- 担当者の聴感チェック+現場ヒアリング(「この変化は正常の範囲かどうか?等」)
- 正常検証データで埋め込みの分散が縮むかを確認(悪化なら NG)
- 変更前後でしきい値の安定と誤検知率の推移を比較
Negative(遠ざけるペア)の作り方
- 同一クリップの遠い区間(Δt ≫ 10–30 秒)※ただし運転モードが一定なら使いすぎ注意。
- 別クリップ/別日/別機を混ぜ、ドメイン多様性を確保。
- Hard Negative: スペクトル距離が近いサンプルを意図的に混ぜ、判別境界をシャープに。
- やり方: ログメルや暫定埋め込みを使い、アンカーに最も似ている他サンプルを上位k件探索(コサイン類似度など)。同一 clip_id / 近接時間は除外。
- ねらい: 「似ているが同一ではない」例を Negative にし、周期・倍音比・ノイズ床・包絡などの微差まで識別できるようにする。
- 実務メモ: k=5〜20、距離はコサイン推奨、規模大ならFAISS等で近傍探索を高速化。メンテ直後のデータは除外。
- 誤負例を防ぐ: RMS/帯域エネルギー差が大きすぎるものは除外(環境ノイズの混入を避ける)。
Hard negative とは
- 定義: アンカー(基準の音)とよく似ているが、同一断片ではないサンプルを Negative(離すべき相手)として扱うこと。
- 例: 同じ機械の別日の録音、または別クリップから切り出した“ほぼ同じ音”。
- 目的/効果: 似ているのに同一ではない例を遠ざける学習により、周期・倍音比・ノイズ床・包絡などの微差を見分ける力がつく。正常クラスタが不必要に広がるのを抑え、しきい値が安定。
- 選び方(実務):
- ログメルや暫定埋め込みで近傍上位k件を探索。
- clip_id/日付が異なるものに限定(同一断片は除外)。
- 比率は20–30%程度に抑える(強すぎると正常が散る)。
- メンテ後などドメインが変わった日は除外(“似ている”基準が崩れる)。
- 注意: 自然な正常変動まで遠ざけると FPR↑。検証セットで分散やFPRを監視する。異常が学習データに混入していると埋め込みが歪むため、学習前に外れ値スクリーニングを行う。
近傍上位k件の探索(わかりやすい説明と擬似コード)
- ログメル:各クリップ(短い音の切片)を log-Mel スペクトログラムの特徴量にする。
- 暫定埋め込み:学習途中または事前学習済みエンコーダで得たベクトル表現(最終版でなくてOK)。
- 近傍上位k件を探索:ある切片(アンカー)に最も似ている他サンプルを k 個、類似度(例:コサイン類似度)で探す。こうして見つかった 「似ているが同一ではない」 サンプルを Hard negative 候補にする(同一 clip_id / 同時刻は除外)。
なぜやる?
- 「似ているのに別物」を Negative にすると、モデルは 周期・倍音比・ノイズ床・包絡などの微妙な違いまで識別するようになり、正常クラスタが不要に広がるのを抑制できる。
擬似コード
# X: 全切片の特徴(log-Mel でも、現時点の埋め込みでも可) shape=(N, D)
Xn = X / (X.norm(dim=1, keepdim=True) + 1e-8) # L2正規化
sim = Xn @ Xn.T # コサイン類似度 (N, N)
for i in range(N):
# 自分自身と同一clip/timeを除外した上で
idx = mask_valid_neighbors(i)
# 類似度の高い順に top-k を hard negative 候補として取得
hard_negs[i] = topk(sim[i, idx], k)
実務メモ
- 距離指標:コサイン推奨(またはユークリッド)。
- k の目安:5〜20(データ量とバッチ設計に応じて調整)。
- フィルタ:同一クリップ/近接時間は除外、メンテ直後などドメインが変わった日は除く。
- 規模が大きい場合は FAISS などで近傍探索を高速化。
バッチ設計と損失
- 1 バッチに N 個の音源 × 2 view(B=2N)。例: N=64 → B=128。
- 投影ヘッド: 2 層 MLP(例: 2048→128)+ BN、出力は L2 正規化。
- 温度 τ: 0.07–0.2。最初は 0.1 前後。
- Optimizer: AdamW、学習率 1e-3 前後 + ウォームアップ、Cosine Decay が扱いやすい。
- 損失: InfoNCE(擬似ペアの類似度↑、他サンプルの類似度↓)。
検証と早期停止(正常のみで監視)
- 埋め込みの分散(トレース)が縮むか、Positive のコサイン類似度が上がるかをモニタ。
- t-SNE/UMAP で正常クラスタの一貫性を目視確認。
- Aug を変えたら**誤検知率(FPR)**の推移を比較。
推論としきい値
- 正常埋め込みの平均 μ と共分散 Σ を推定し、マハラノビス距離でスコア化。
- 閾値は正常検証セットの 95–99 パーセンタイルから選定。
- 連続判定の安定化: 移動平均や k 連続フレーム超えでフラグ。
実務上の落とし穴
- サンプリング周波数の不一致、録音レベルのクリッピング、マイク位置のばらつき。
- 背景騒音の季節・時間帯差、機械メンテ後の音質変化(ドメインシフト)。
- 学習データに異常が混入していないかのスクリーニング(スペクトル的外れ値を事前除去)。
時間情報とモデル設計の考え方(モデル切替は不要)
- モデル切替はしない:Negative/Positive は学習時の相対的な信号で、時間帯ごとにモデルを分ける話ではありません。学習で表現空間の形(同じ正常=近い/別物=遠い)を作ります。
- モデルは時系列を扱える:ログメルは横軸が時間です。CNN/Conformer/Transformer は時間方向の周期・包絡・立ち上がりを特徴として埋め込みに取り込みます。長い文脈が必要なら連続 N 窓を入力し、時系列 Transformer/GRU を重ねます。
- 「距離だけ」で大丈夫? 距離は埋め込み上で測ります。埋め込みに時間的特徴が圧縮されるため、マハラノビスや kNNでも機能します。足りなければ「連続窓→時系列ヘッド→スコア」で補強し、移動平均/k 連続超過で安定化します。
- 同一録音の遠距離を Negative にするか:装置の運転状態が変わりやすいなら有効。全編ほぼ一定状態なら使わない方が安全。基本は近接 Positive + 別クリップ/別日 Negativeが無難です。
- 時間の表現(タイマー vs 時計):既定は相対時間(録音内オフセット)。昼夜・曜日・環境で音が変わるなら、時刻などを追加特徴にするか、モード別しきい値/クラスタ別の μ・Σを切り替えて判定します(モデルを切り替えるのではなく、参照分布や閾値を切り替える)。
- 実装レシピ(まとめ):
- 近接 Positive + 別クリップ Negative、窓 0.5–1.0 s。
- 埋め込みをクラスタリングし、クラスタ別に μ・Σ と閾値を持つ。
- 連続 N 窓を時系列ヘッドに入れ、移動平均や k 連続超過でフラグ。
時間情報のベクトル化(実装例)
- マイクロ時間(窓内の短い時系列): log-Mel の横軸=Tフレーム。各フレーム埋め込み
x_tokens (B, T, D)
に位置埋め込みを足し、Transformer/CNN/Conformerで周期・包絡・立ち上がりなどの時間パターンを埋め込みベクトルに圧縮する。
# x_tokens: (B, T, D) フレームごとの埋め込み
x = x_tokens + positional_encoding(T) # 時間位置をベクトルで付与
z = transformer_encoder(x) # 時間パターンを含んだ埋め込み
- マクロ時間(相対時刻・時計の時刻): 必要に応じて追加特徴として連結。録音内の相対時刻
t_rel
、日付/曜日/時間は周期特徴(sin/cos)で表現。
import math, torch
hour, dow = 13, 2 # 例: 13時・火曜
t_rel = 0.42 # 例: クリップ内の相対位置
time_feat = torch.tensor([
math.sin(2*math.pi*hour/24), math.cos(2*math.pi*hour/24),
math.sin(2*math.pi*dow/7), math.cos(2*math.pi*dow/7),
t_rel
])
# 末段で連結(形状は実装に合わせて調整)
y = torch.cat([z.mean(dim=1), time_feat], dim=-1)
- 運用指針: まずは時間特徴を明示せずに十分か検証。昼夜/曜日で分布が変わる根拠がある場合のみ追加。モデルを時間帯で切り替えるのではなく、必要に応じてモード別の参照分布(μ, Σ)や閾値を切り替える。
Tips
- WaveNetやConformerベースのエンコーダーと組み合わせてもOK
- 少量の異常例があれば fine-tuning で精度UP
かなり長くなってしまった上に掘り下げ方が不十分な個所が多く出てしまったため、別記事にて改めて深堀していきたい。本稿は音の異常検知の入門や用語に慣れるといった程度とする。