100本ノック / 異常検知 / 異常検知100本ノック

製造業の異常検知をAutoencoderで実践|再構成誤差・しきい値・時系列をPython解説

設備センサの「いつもと違う」を捉える:深層学習による異常検知10本ノック(No.071〜No.080)

本記事では、架空のモータ組立ラインを題材に、Autoencoder(オートエンコーダ)の再構成誤差を使った異常検知を、考え方・実装・しきい値・時系列化・運用判断まで一貫して確認します。正解ラベルが乏しい製造現場で、保全担当者が「どの設備を、いつ確認するか」を決める材料にすることが目的です。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。 掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

はじめに:この記事で扱う製造業の実務課題

モータの振動、温度、電流、音圧、回転数は相互に関係します。単変量の上限監視では、各値が規格内でも「負荷の割に温度が高い」「回転数と振動の組合せが通常と違う」といった予兆を見逃します。本記事では正常運転の関係を学習し、復元しにくい観測を点検候補として順位付けします。

現場でよくある状況

  • 故障ラベルは少ないが、正常時のログは大量にある
  • 品種・負荷・時間帯によって正常範囲が変わる
  • 誤報が多いとアラートが無視される
  • 高精度モデルでも、保全フローに接続できなければ価値が出ない

なぜこの問題は判断が難しいのか

異常は希少で種類も変化します。したがって、過去の故障だけを教師として全パターンを学ぶことは困難です。また、再構成誤差が大きいことは故障を証明せず、「学習した正常パターンからのずれ」を示すにすぎません。しきい値は統計的根拠だけでなく、見逃し損失、点検能力、工程停止リスクを踏まえて決める必要があります。

今回扱うノックの全体像

No.071〜075で表形式センサのAutoencoderを構築し、再構成誤差としきい値を扱います。No.076〜078で時系列・LSTM・画像への拡張を考え、No.079で過学習、No.080で実務採用の判断基準を整理します。

Python環境の準備

外部データには依存しません。再現性のため乱数シードを固定し、グラフはmatplotlibで描画します。ここでは追加の深層学習基盤を要求しないよう、多層ニューラルネットワークでAutoencoderと同じ「入力を復元する」学習を実装します。実案件ではPyTorchやTensorFlowへの置換が可能です。

import sys
import warnings
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import confusion_matrix

warnings.filterwarnings("ignore", category=UserWarning)
SEED = 42
rng = np.random.default_rng(SEED)
pd.set_option("display.max_columns", 20)
print(f"Python: {sys.version.split()[0]}")
print(f"numpy: {np.__version__}, pandas: {pd.__version__}, matplotlib: {matplotlib.__version__}")
Python: 3.13.1
numpy: 2.5.1, pandas: 3.0.3, matplotlib: 3.11.0

架空データの作成

5分間隔のモータ運転ログ2,400件を生成します。前半1,600件を「正常履歴」として学習に使い、後半800件に正常データと三種類の架空異常(軸受劣化、冷却不良、負荷変動)を混在させます。実務では、学習期間に停止・段取り替え・既知異常が混ざっていないかを設備履歴と照合することが重要です。

n = 2400
t = np.arange(n)
load = np.clip(65 + 12*np.sin(2*np.pi*t/288) + rng.normal(0, 5, n), 35, 95)
rpm = 1450 + 4.2*load + rng.normal(0, 18, n)
current = 8 + 0.16*load + rng.normal(0, 0.45, n)
temperature = 31 + 0.34*load + 0.7*np.sin(2*np.pi*t/576) + rng.normal(0, 0.8, n)
vibration = 0.55 + 0.010*load + rng.normal(0, 0.07, n)
sound = 61 + 0.055*rpm/10 + 1.8*vibration + rng.normal(0, 0.7, n)

labels = np.zeros(n, dtype=int)
anomaly_type = np.full(n, "normal", dtype=object)
idx_bearing = np.arange(1780, 1810)
idx_cooling = np.arange(2040, 2070)
idx_load = np.arange(2260, 2285)
vibration[idx_bearing] += np.linspace(0.35, 1.15, len(idx_bearing)); sound[idx_bearing] += 3.8
temperature[idx_cooling] += np.linspace(4, 10, len(idx_cooling)); current[idx_cooling] += 1.2
rpm[idx_load] += rng.choice([-1, 1], len(idx_load))*rng.uniform(90, 170, len(idx_load)); current[idx_load] += 2.0
for idx, name in [(idx_bearing,"bearing"),(idx_cooling,"cooling"),(idx_load,"load_shift")]:
    labels[idx] = 1; anomaly_type[idx] = name

features = ["load_pct","rpm","current_a","temperature_c","vibration_mm_s","sound_db"]
df = pd.DataFrame({"timestamp": pd.date_range("2026-01-01", periods=n, freq="5min"),
                   "load_pct":load,"rpm":rpm,"current_a":current,"temperature_c":temperature,
                   "vibration_mm_s":vibration,"sound_db":sound,
                   "is_anomaly":labels,"anomaly_type":anomaly_type})
print(df.shape)
df.head().round(2)
(2400, 9)
timestamp load_pct rpm current_a temperature_c vibration_mm_s sound_db is_anomaly anomaly_type
0 2026-01-01 00:00:00 66.52 1721.37 18.66 54.30 1.21 72.43 0 normal
1 2026-01-01 00:05:00 60.06 1719.90 17.85 52.02 1.20 72.66 0 normal
2 2026-01-01 00:10:00 69.28 1746.02 19.08 55.00 1.30 72.08 0 normal
3 2026-01-01 00:15:00 70.49 1733.49 19.72 55.05 1.26 71.84 0 normal
4 2026-01-01 00:20:00 56.29 1670.83 16.80 49.75 1.18 72.44 0 normal

No.071:Autoencoderによる異常検知の考え方を理解する

実務での意味

正常時のセンサ関係を学習し、その関係から外れた運転を点検候補にできます。故障名を当てるモデルではなく、未知の兆候を含む「通常と異なる運転」のスクリーニングです。

分析・モデル化の考え方

Autoencoderは入力 x\mathbf{x} を低次元表現 z=fθ(x)\mathbf{z}=f_\theta(\mathbf{x}) に圧縮し、x^=gϕ(z)\hat{\mathbf{x}}=g_\phi(\mathbf{z}) と復元します。正常データで平均二乗誤差

L(θ,ϕ)=1ni=1nxix^i22L(\theta,\phi)=\frac{1}{n}\sum_{i=1}^{n}\lVert\mathbf{x}_i-\hat{\mathbf{x}}_i\rVert_2^2

を小さくすると、正常パターンは復元しやすく、未学習のパターンは復元しにくいと期待できます。ただし、モデル容量が大きすぎると異常まで上手に復元するため、圧縮と正則化が必要です。

Pythonで確認する

まず標準化し、入力6次元を3次元のボトルネックへ圧縮するネットワークを定義します。

train_end = 1600
scaler = StandardScaler()
X_train = scaler.fit_transform(df.loc[:train_end-1, features])
X_all = scaler.transform(df[features])
ae = MLPRegressor(hidden_layer_sizes=(8, 3, 8), activation="tanh", solver="adam",
                  alpha=0.001, max_iter=500, random_state=SEED, early_stopping=True,
                  validation_fraction=0.15, n_iter_no_change=25)
print(ae)
MLPRegressor(activation='tanh', alpha=0.001, early_stopping=True,
             hidden_layer_sizes=(8, 3, 8), max_iter=500, n_iter_no_change=25,
             random_state=42, validation_fraction=0.15)

結果の読み取り

ボトルネックを設けることで、単なる値のコピーではなく主要な相関構造を学ばせます。標準化は、rpmのように桁が大きい変数だけが損失を支配することを防ぎます。

No.072:簡単なAutoencoderを実装する

実務での意味

PoCでは、複雑なモデルの前に、小さなネットワークが正常運転を再現できるかを確認します。学習時間、再現性、再学習手順も精度と同じく運用要件です。

分析・モデル化の考え方

正常期間だけを入力と目的変数の両方に与えます。評価対象を学習へ混ぜると、異常を正常として覚えるデータリーケージが起きます。

Pythonで確認する

ae.fit(X_train, X_train)
train_recon = ae.predict(X_train)
print(f"学習反復回数: {ae.n_iter_}")
print(f"正常学習データの平均再構成誤差: {np.mean((X_train-train_recon)**2):.4f}")
print(f"最終学習loss: {ae.loss_:.4f}")
学習反復回数: 500
正常学習データの平均再構成誤差: 0.0455
最終学習loss: 0.0228

結果の読み取り

平均誤差が小さいことは正常履歴の再現性を示しますが、異常検知性能を単独では保証しません。次に観測単位の誤差を計算し、正常期間と評価期間を分離して比較します。

No.073:再構成誤差を計算する

実務での意味

設備ログ1件ごとに異常スコアを付けることで、巡回点検の優先順位を作れます。さらに変数別誤差は、原因究明ではなく「最初に確認するセンサ」の候補になります。

分析・モデル化の考え方

観測 ii のスコアを ei=1pj=1p(xijx^ij)2e_i=\frac{1}{p}\sum_{j=1}^{p}(x_{ij}-\hat{x}_{ij})^2 とします。標準化空間で計算するため、異なる単位のセンサを比較できます。

Pythonで確認する

X_recon = ae.predict(X_all)
sq_error = (X_all-X_recon)**2
df["reconstruction_error"] = sq_error.mean(axis=1)
for j, col in enumerate(features):
    df[f"err_{col}"] = sq_error[:, j]
top = df.loc[train_end:].nlargest(8, "reconstruction_error")
top[["timestamp","anomaly_type","reconstruction_error"] + [f"err_{c}" for c in features]].round(3)
timestamp anomaly_type reconstruction_error err_load_pct err_rpm err_current_a err_temperature_c err_vibration_mm_s err_sound_db
1809 2026-01-07 06:45:00 bearing 14.955 1.540 1.648 2.499 1.800 62.548 19.692
1808 2026-01-07 06:40:00 bearing 14.435 2.922 1.237 2.817 4.001 61.959 13.675
1804 2026-01-07 06:20:00 bearing 11.719 2.548 0.798 2.310 1.023 44.297 19.336
1801 2026-01-07 06:05:00 bearing 10.634 1.556 3.690 2.012 1.476 41.910 13.158
1805 2026-01-07 06:25:00 bearing 9.941 4.848 4.123 6.508 1.469 32.806 9.892
1807 2026-01-07 06:35:00 bearing 9.926 2.612 1.218 3.052 2.127 41.054 9.493
1802 2026-01-07 06:10:00 bearing 9.426 2.579 1.490 2.222 1.616 32.748 15.901
1803 2026-01-07 06:15:00 bearing 9.398 3.134 0.549 3.252 2.999 33.328 13.125

結果の読み取り

上位ログでは、軸受異常なら振動・音圧、冷却異常なら温度などの誤差が相対的に増えます。ただし各変数の誤差は相関の影響を受けるため、故障原因の断定には現物確認と保全記録が必要です。

No.074:再構成誤差の分布を可視化する

実務での意味

平均値だけでなく分布の裾を見ると、誤報がどれほど発生しそうか、異常群が正常群から分離するかを把握できます。

分析・モデル化の考え方

誤差は右に長い分布になりやすいため、ヒストグラムを対数軸で確認します。ラベルはモデル学習ではなく、事後評価のためだけに使います。

Pythonで確認する

normal_eval = df.loc[(df.index>=train_end) & (df.is_anomaly==0), "reconstruction_error"]
anomaly_eval = df.loc[(df.index>=train_end) & (df.is_anomaly==1), "reconstruction_error"]
plt.figure(figsize=(9,4.5))
plt.hist(normal_eval, bins=45, alpha=.7, label="normal", color="#4C78A8")
plt.hist(anomaly_eval, bins=30, alpha=.7, label="injected anomaly", color="#E45756")
plt.yscale("log")
plt.title("Distribution of autoencoder reconstruction error")
plt.xlabel("Mean squared reconstruction error (standardized scale)")
plt.ylabel("Count (log scale)")
plt.grid(True, alpha=.3); plt.legend(); plt.tight_layout(); plt.show()

png

結果の読み取り

異常群の誤差が右側へ寄れば、点検候補の抽出に使える可能性があります。重なりがある以上、スコアだけで自動停止させず、連続回数や他アラームとの組合せで段階的に運用します。

No.075:再構成誤差にしきい値を設定する

実務での意味

しきい値は点検件数と見逃しの交換条件です。現場が1日に確認できる件数を超える設計は継続しません。

分析・モデル化の考え方

正常学習誤差の99パーセンタイルを暫定しきい値 τ=Q0.99(e)\tau=Q_{0.99}(e) とします。これは故障確率99%を意味しません。正常履歴の約1%を超過させる運用上の基準です。

Pythonで確認する

threshold = np.quantile(df.loc[:train_end-1,"reconstruction_error"], .99)
eval_df = df.loc[train_end:].copy()
eval_df["detected"] = (eval_df["reconstruction_error"] > threshold).astype(int)
tn, fp, fn, tp = confusion_matrix(eval_df.is_anomaly, eval_df.detected).ravel()
metrics = pd.Series({"threshold":threshold,"alerts":int(eval_df.detected.sum()),
                     "precision":tp/(tp+fp),"recall":tp/(tp+fn),
                     "false_positive_rate":fp/(fp+tn)})
metrics.round(3)
threshold               0.189
alerts                 91.000
precision               0.912
recall                  0.976
false_positive_rate     0.011
dtype: float64

結果の読み取り

Recallは埋め込んだ異常を拾った割合、Precisionはアラート中の異常割合です。実務では故障ラベルが完全でないため、誤検知に見えるログが未記録の兆候である可能性もあります。保全レビューで判定結果を蓄積し、しきい値を更新します。

No.076:時系列データをAutoencoderに入力する

実務での意味

単時点では正常でも、急上昇、周期崩れ、緩やかなドリフトは時系列として異常です。直近12点(1時間)を一つの入力にすると、変化の形を扱えます。

分析・モデル化の考え方

窓長は検知したい現象の時間スケールと通知遅延で決めます。窓を重ねるとサンプル間が独立でなくなるため、ランダム分割ではなく時間順に評価します。

Pythonで確認する

seq_features = ["current_a","temperature_c","vibration_mm_s"]
seq_scaler = StandardScaler().fit(df.loc[:train_end-1,seq_features])
Z = seq_scaler.transform(df[seq_features])
window = 12
X_seq = np.array([Z[i-window+1:i+1].ravel() for i in range(window-1,n)])
end_index = np.arange(window-1,n)
train_mask = end_index < train_end
seq_ae = MLPRegressor(hidden_layer_sizes=(12,4,12), activation="tanh", max_iter=400,
                      alpha=.002, random_state=SEED, early_stopping=True, n_iter_no_change=20)
seq_ae.fit(X_seq[train_mask], X_seq[train_mask])
seq_error = np.mean((X_seq-seq_ae.predict(X_seq))**2, axis=1)
plt.figure(figsize=(10,4))
plt.plot(df.timestamp.iloc[end_index], seq_error, lw=.8, color="#4C78A8")
plt.axhline(np.quantile(seq_error[train_mask],.99), color="#E45756", ls="--", label="99% threshold")
plt.title("Sequence autoencoder error over time")
plt.xlabel("Timestamp"); plt.ylabel("Window reconstruction error")
plt.grid(True, alpha=.3); plt.legend(); plt.tight_layout(); plt.show()

png

結果の読み取り

窓型スコアは異常の開始後しばらく高止まりします。そのためアラート件数ではなく、連続超過を一つのイベントとして束ねる必要があります。窓長12なら最大約55分の履歴を含む点も、通知時刻の解釈に加えます。

No.077:LSTM Autoencoderの考え方を理解する

実務での意味

LSTM Autoencoderは、長めの履歴や運転順序に意味がある場合の候補です。昇温工程と冷却工程のように、同じ値の集合でも順番が違えば意味が変わる現象に向きます。

分析・モデル化の考え方

LSTMはゲート機構で過去の情報を保持し、系列を潜在表現へ符号化して復元します。一方、学習・推論・監視は重くなります。ここでは実装を増やす前の診断として、時系列順序を崩した窓で現在モデルの誤差が変わるかを確認します。

Pythonで確認する

sample_windows = X_seq[~train_mask][:120].reshape(-1,window,len(seq_features))
reversed_windows = sample_windows[:,::-1,:].reshape(-1,window*len(seq_features))
ordered_windows = sample_windows.reshape(-1,window*len(seq_features))
comparison = pd.DataFrame({
    "input":["ordered sequence","reversed sequence"],
    "mean_error":[np.mean((ordered_windows-seq_ae.predict(ordered_windows))**2),
                  np.mean((reversed_windows-seq_ae.predict(reversed_windows))**2)]})
comparison.round(4)
input mean_error
0 ordered sequence 0.3374
1 reversed sequence 0.3395

結果の読み取り

順序反転で誤差が変われば、系列の並びが識別材料になっています。ただし、この全結合モデルは時間構造を明示的に表現しません。長期依存で改善余地が見込め、十分な正常系列と計算基盤がある場合にLSTMを比較候補へ入れます。

No.078:画像データの異常検知の考え方を理解する

実務での意味

外観検査では傷、欠け、汚れの種類を網羅できない場合があります。正常画像の局所パターンを復元しにくい領域を示せれば、検査員の確認箇所を絞れます。

分析・モデル化の考え方

実務では畳み込みAutoencoderや事前学習特徴を使います。重要なのは撮像条件の固定、位置合わせ、製品型式別の正常定義です。ここでは架空の表面画像と平滑化復元の差で、再構成誤差マップの読み方だけを確認します。

Pythonで確認する

size=48
yy,xx=np.mgrid[:size,:size]
normal_img=.55+.08*np.sin(xx/3)+.06*np.cos(yy/5)+rng.normal(0,.018,(size,size))
defect_img=normal_img.copy(); defect_img[19:24,12:37] -= .30
def smooth(a):
    p=np.pad(a,1,mode="reflect")
    return sum(p[i:i+size,j:j+size] for i in range(3) for j in range(3))/9
reconstructed=smooth(defect_img)
error_map=(defect_img-reconstructed)**2
fig,ax=plt.subplots(1,3,figsize=(10,3.2))
for a,img,title in zip(ax,[defect_img,reconstructed,error_map],["Inspection image","Reconstructed","Squared error map"]):
    im=a.imshow(img,cmap="gray" if title!="Squared error map" else "magma")
    a.set_title(title); a.set_xlabel("X pixel"); a.set_ylabel("Y pixel"); a.grid(False)
fig.suptitle("Conceptual image anomaly localization")
plt.tight_layout(); plt.show()

png

結果の読み取り

誤差マップの明るい領域は確認候補です。ただし照明むらや位置ずれも強く反応します。画像モデル導入ではアルゴリズム以前に、照明、距離、露光、背景、型式切替の標準化が成否を左右します。

No.079:深層学習モデルの過学習を確認する

実務での意味

学習データだけをよく復元するモデルは、新しい正常ロットを誤報にすることがあります。設備・季節・品種をまたぐ汎化を確認しなければなりません。

分析・モデル化の考え方

正常履歴を時間順に学習用80%と検証用20%に分け、反復ごとの誤差を比較します。学習誤差が下がり続ける一方、検証誤差が上がれば過学習の兆候です。早期終了、モデル縮小、正則化、学習期間の多様化が対策です。

Pythonで確認する

split=int(len(X_train)*.8); Xa,Xv=X_train[:split],X_train[split:]
monitor=MLPRegressor(hidden_layer_sizes=(20,8,20), activation="tanh", solver="adam",
                     alpha=1e-5, max_iter=1, warm_start=True, random_state=SEED)
train_curve=[]; valid_curve=[]
for epoch in range(80):
    monitor.fit(Xa,Xa)
    train_curve.append(np.mean((Xa-monitor.predict(Xa))**2))
    valid_curve.append(np.mean((Xv-monitor.predict(Xv))**2))
best_epoch=int(np.argmin(valid_curve))+1
plt.figure(figsize=(8,4))
plt.plot(train_curve,label="train"); plt.plot(valid_curve,label="time-based validation")
plt.axvline(best_epoch-1,color="#E45756",ls="--",label=f"best epoch={best_epoch}")
plt.title("Learning curves for overfitting monitoring")
plt.xlabel("Epoch"); plt.ylabel("Reconstruction MSE")
plt.grid(True,alpha=.3); plt.legend(); plt.tight_layout(); plt.show()
print(f"検証誤差が最小のepoch: {best_epoch}, 最小値: {min(valid_curve):.4f}")

png

検証誤差が最小のepoch: 80, 最小値: 0.0083

結果の読み取り

採用する反復数は学習誤差の最小点ではなく、時間順検証誤差の最小点を候補にします。さらに別日・別ロットを完全なホールドアウトとして残し、データ更新後も同じ監視を続けます。

No.080:深層学習を実務で使う判断基準を整理する

実務での意味

深層学習は目的ではありません。既存の管理図やルールより、停止損失の低減、点検時間の短縮、品質流出の防止に寄与する場合に採用します。

分析・モデル化の考え方

データ量、非線形性、説明性、保守性、推論制約をスコア化した簡易判断表を作ります。これは合否の自動判定ではなく、製造・保全・品質・ITの論点を揃えるためのチェックリストです。

Pythonで確認する

criteria=pd.DataFrame({
 "判断項目":["正常データの量と網羅性","非線形・画像・長期系列の必要性","誤報と見逃しの検証可能性",
          "説明・原因調査の補助設計","再学習・版管理・監視体制","エッジ推論の時間・計算制約"],
 "現状スコア(1-5)":[4,4,3,2,2,4],
 "Goの目安":["複数条件・季節を含む","単純法との差を検証","保全結果を記録できる",
           "変数別誤差等を併設","責任者と更新手順がある","要求時間内で安定稼働"]})
criteria["要対応"] = criteria["現状スコア(1-5)"] < 3
display(criteria)
print("暫定判断:", "限定ラインでPoC" if criteria["現状スコア(1-5)"].mean()>=3 else "データ・運用基盤を先に整備")
判断項目 現状スコア(1-5) Goの目安 要対応
0 正常データの量と網羅性 4 複数条件・季節を含む False
1 非線形・画像・長期系列の必要性 4 単純法との差を検証 False
2 誤報と見逃しの検証可能性 3 保全結果を記録できる False
3 説明・原因調査の補助設計 2 変数別誤差等を併設 True
4 再学習・版管理・監視体制 2 責任者と更新手順がある True
5 エッジ推論の時間・計算制約 4 要求時間内で安定稼働 False
暫定判断: 限定ラインでPoC

結果の読み取り

この例ではデータと計算制約は比較的良好ですが、説明補助とモデル更新体制が弱いため、全社展開ではなく限定ラインでのPoCが妥当です。ベースライン、Autoencoder、運用コストを同じ期間で比較し、効果を金額と工数で評価します。

対象ノックを通して見える実務上の示唆

  1. 再構成誤差は故障確率ではなく、正常モデルからの乖離度である。
  2. しきい値は統計だけでなく、点検能力と損失構造から決める。
  3. 単点、時系列、画像では、異常の定義とデータ品質管理が異なる。
  4. 高性能化より先に、学習期間の汚染防止、イベント集約、保全結果の記録を設計する。
  5. モデルはアラートを出して終わりではなく、判断・点検・フィードバックまで含む業務システムである。

実務導入する場合に必要なこと

  • 対象設備、検知リードタイム、許容誤報数、回避したい損失を合意する
  • センサ校正、欠損、時刻同期、設備状態・品種・保全履歴を管理する
  • 管理図やIsolation Forest等のベースラインと同条件で比較する
  • 注意・要確認・緊急の段階通知、連続超過、抑止時間を設計する
  • モデル、特徴量、しきい値、学習データ、承認者を版管理する
  • ドリフト、アラート件数、点検結果、回避損失を定期レビューする

まとめ

No.071〜080では、Autoencoderによる正常パターン学習から、再構成誤差、しきい値、時系列・LSTM・画像への拡張、過学習、採用判断までを確認しました。深層学習の価値は複雑な関係を捉える点にありますが、成果はモデル単体ではなく、データ品質と保全業務への接続で決まります。まず限定範囲でベースラインとの差と点検負荷を測り、効果が再現できる範囲から広げることが現実的です。

法人向けのご相談

数理工房では、製造業の異常検知PoC、センサ・画像データの評価、しきい値とアラート運用設計、既存システムへの組込みまで、課題整理からご支援します。

📩 お問い合わせ: surikobo.co.jp/contact まずはお気軽にご相談ください。