Skip to content

Commit daa54ed

Browse files
authored
Fix CWRU data loader and add cross-domain noise validation for Issue #8 (#10)
1 parent f61a726 commit daa54ed

2 files changed

Lines changed: 158 additions & 21 deletions

File tree

cross_domain_validation.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import numpy as np
2+
from cnsd import Dataset
3+
from cnsd.diagnosis.system import CNSD
4+
from validate_run import load_cwru, CWRU, TAXONOMY, headline_accuracy_by_verdict
5+
6+
def add_awgn(signals, snr_db):
7+
"""
8+
Adds Additive White Gaussian Noise (AWGN) to the signals at the specified SNR (in dB).
9+
"""
10+
noisy_signals = np.zeros_like(signals)
11+
for i in range(len(signals)):
12+
sig = signals[i]
13+
# Calculate signal power and noise power
14+
signal_power = np.mean(sig ** 2)
15+
if signal_power == 0:
16+
noise_power = 0
17+
else:
18+
noise_power = signal_power / (10 ** (snr_db / 10.0))
19+
# Generate Gaussian noise
20+
noise = np.random.normal(0, np.sqrt(noise_power), size=sig.shape)
21+
noisy_signals[i] = sig + noise
22+
return noisy_signals
23+
24+
def main():
25+
print('=' * 80)
26+
print('CNSD CROSS-DOMAIN VALIDATION (AWGN NOISE INJECTION)')
27+
print('=' * 80)
28+
29+
# 1. Load baseline data
30+
X, y, cond = load_cwru()
31+
X = np.asarray(X, np.float32)
32+
y = np.asarray(y)
33+
cond = np.asarray(cond)
34+
35+
train_mask = cond < 3
36+
test_mask = cond == 3
37+
38+
X_train, y_train, cond_train = X[train_mask], y[train_mask], cond[train_mask]
39+
X_test, y_test, cond_test = X[test_mask], y[test_mask], cond[test_mask]
40+
41+
train_data = Dataset.from_arrays(X_train, y_train, cond_train, fs=12000, physics=CWRU, taxonomy=TAXONOMY, name='CWRU_Train')
42+
print(f'[train_data] {train_data.summary()}')
43+
44+
# 2. Train baseline model (Clean Data)
45+
print('\nTraining CNSD Model on clean Training Data...')
46+
model = CNSD()
47+
model.fit(train_data, epochs=30)
48+
print('Training complete.\n')
49+
50+
# 3. Evaluate on noisy cross-domains
51+
snr_levels = [None, 0, -5, -10]
52+
53+
for snr in snr_levels:
54+
if snr is None:
55+
print('-' * 80)
56+
print('Evaluating on CLEAN TEST DATA (No Noise)')
57+
print('-' * 80)
58+
X_eval = X_test
59+
else:
60+
print('-' * 80)
61+
print(f'Evaluating on NOISY TEST DATA (SNR = {snr}dB)')
62+
print('-' * 80)
63+
X_eval = add_awgn(X_test, snr)
64+
65+
# Create evaluation dataset
66+
eval_data = Dataset.from_arrays(X_eval, y_test, cond_test, fs=12000, physics=CWRU, taxonomy=TAXONOMY, name=f'CWRU_Test_SNR_{snr}')
67+
68+
# Diagnose
69+
report = model.diagnose(eval_data)
70+
71+
# Print Headline Metric
72+
hb = headline_accuracy_by_verdict(report, eval_data.y)
73+
74+
print('[HEADLINE] CNN accuracy by physics verdict:')
75+
for v in ['CONFIRMED', 'CONFLICT', 'INCONCLUSIVE']:
76+
if v in hb:
77+
d = hb[v]
78+
print(f' {v:13}: acc={d["cnn_accuracy"]:.3f} (n={d["n"]})')
79+
80+
if 'CONFIRMED' in hb and 'CONFLICT' in hb:
81+
gap = hb['CONFIRMED']['cnn_accuracy'] - hb['CONFLICT']['cnn_accuracy']
82+
print(f' -> CONFIRMED minus CONFLICT accuracy gap: {gap:+.3f}')
83+
print('\n')
84+
85+
if __name__ == '__main__':
86+
main()

validate_run.py

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,67 @@
1919
import sys
2020
import numpy as np
2121

22-
from cnsd import CNSD, Dataset
22+
from cnsd import Dataset
23+
from cnsd.diagnosis.system import CNSD
2324
from cnsd.physics import PhysicsConfig
2425
from cnsd.causal import signal_kurtosis
2526
from cnsd.counterfactual import dowhy_gcm_available, build_scm, counterfactual_for_unit
2627

2728

2829
# ── the ONLY dataset-specific code: return raw arrays from your CWRU files ────
2930
def load_cwru():
30-
"""Return (X, y, cond) for CWRU. Replace the body with your loader.
31-
32-
X : (n, 1024) float non-overlapping test windows, per-window normalized
33-
y : (n,) int fault class 0..9 (0 = Normal)
34-
cond : (n,) int motor load 0..3
35-
"""
36-
raise NotImplementedError(
37-
"Wire your CWRU loader here: return X (n,1024), y (n,), cond (n,). "
38-
"Use Protocol B (train loads 0-2, test load 3) for the real result.")
31+
import os
32+
from scipy.io import loadmat
33+
base_dir = r"E:\301\CWRU-dataset"
34+
if not os.path.exists(base_dir):
35+
raise FileNotFoundError(f"CWRU dataset not found at {base_dir}")
36+
37+
X, y, cond = [], [], []
38+
39+
def read_mat(path, label, load):
40+
if not os.path.exists(path): return
41+
try:
42+
mat = loadmat(path)
43+
key = [k for k in mat.keys() if 'DE_time' in k]
44+
if not key: return
45+
time_series = mat[key[0]][:, 0]
46+
47+
length = 1024
48+
idx_last = -(time_series.shape[0] % length)
49+
if idx_last == 0:
50+
clips = time_series.reshape(-1, length)
51+
else:
52+
clips = time_series[:idx_last].reshape(-1, length)
53+
54+
for clip in clips:
55+
X.append(clip)
56+
y.append(label)
57+
cond.append(load)
58+
except Exception as e:
59+
print(f"Error reading {path}: {e}")
60+
61+
normal_dir = os.path.join(base_dir, "Normal")
62+
for f in os.listdir(normal_dir):
63+
if f.endswith('.mat'):
64+
load = int(f.split('_')[-1].split('.')[0])
65+
read_mat(os.path.join(normal_dir, f), 0, load)
66+
67+
fault_dir = os.path.join(base_dir, "12k_Drive_End_Bearing_Fault_Data")
68+
fault_map = {
69+
'B': { '007': 1, '014': 2, '021': 3 },
70+
'IR': { '007': 4, '014': 5, '021': 6 },
71+
'OR': { '007': 7, '014': 8, '021': 9 }
72+
}
73+
for ftype, size_map in fault_map.items():
74+
for size, label in size_map.items():
75+
dir_path = os.path.join(fault_dir, ftype, size)
76+
if not os.path.exists(dir_path): continue
77+
for f in os.listdir(dir_path):
78+
if f.endswith('.mat'):
79+
load = int(f.split('_')[-1].split('.')[0])
80+
read_mat(os.path.join(dir_path, f), label, load)
81+
82+
return np.array(X, dtype=np.float32), np.array(y), np.array(cond)
3983

4084

4185
# CWRU 6205 physics + taxonomy (this is config, not hardcoded engine logic)
@@ -73,14 +117,21 @@ def main():
73117
# 1. data
74118
X, y, cond = load_cwru()
75119
X = np.asarray(X, np.float32); y = np.asarray(y); cond = np.asarray(cond)
76-
data = Dataset.from_arrays(X, y, cond, fs=12000, physics=CWRU,
77-
taxonomy=TAXONOMY, name='CWRU')
78-
print(f'[data] {data.summary()}')
120+
121+
train_mask = cond < 3
122+
test_mask = cond == 3
123+
124+
train_data = Dataset.from_arrays(X[train_mask], y[train_mask], cond[train_mask], fs=12000, physics=CWRU, taxonomy=TAXONOMY, name='CWRU_Train')
125+
test_data = Dataset.from_arrays(X[test_mask], y[test_mask], cond[test_mask], fs=12000, physics=CWRU, taxonomy=TAXONOMY, name='CWRU_Test')
126+
full_data = Dataset.from_arrays(X, y, cond, fs=12000, physics=CWRU, taxonomy=TAXONOMY, name='CWRU_Full')
127+
128+
print(f'[train_data] {train_data.summary()}')
129+
print(f'[test_data] {test_data.summary()}')
79130

80131
# 2. fit + 3. diagnose (Layers 1,2,4 live)
81132
model = CNSD()
82-
model.fit(data, epochs=30)
83-
report = model.diagnose(data)
133+
model.fit(train_data, epochs=30)
134+
report = model.diagnose(test_data)
84135
print(f'\n[pipeline] {report.summary()}')
85136

86137
# 3. Layer-2 verification rate
@@ -90,7 +141,7 @@ def main():
90141
print(f' {k:13}: {v:.1%}')
91142

92143
# 4. HEADLINE: CNN accuracy split by verdict
93-
hb = headline_accuracy_by_verdict(report, y)
144+
hb = headline_accuracy_by_verdict(report, test_data.y)
94145
print('\n[HEADLINE] CNN accuracy by physics verdict:')
95146
for v, d in hb.items():
96147
print(f' {v:13}: acc={d["cnn_accuracy"]:.3f} (n={d["n"]})')
@@ -100,18 +151,18 @@ def main():
100151
f'({"physics is a real reliability signal" if gap > 0 else "investigate"})')
101152

102153
# 5. Layer-3 causal do(Z)
103-
eff = model.condition_effect(data)
154+
eff = model.condition_effect(full_data)
104155
print(f'\n[Layer 3] do(Z) operating-condition effect: rung={eff["rung"]} '
105156
f'max_contrast={eff["max_contrast"]:.4f} p={eff["p_value"]:.4f}')
106157

107158
# 6. Layer-3B: REAL Rung-3 counterfactual (must actually execute, not fallback)
108159
print(f'\n[Layer 3B] DoWhy available: {dowhy_gcm_available()}')
109160
if dowhy_gcm_available():
110-
feat = signal_kurtosis(data.X)
111-
scm = build_scm(data.cond, feat, data.y)
161+
feat = signal_kurtosis(full_data.X)
162+
scm = build_scm(full_data.cond, feat, full_data.y)
112163
if scm is not None:
113-
row = {'Z': float(data.cond[0]), 'X': float(feat[0]), 'Y': float(data.y[0] > 0)}
114-
cf_cond = int(min(np.unique(data.cond)))
164+
row = {'Z': float(full_data.cond[0]), 'X': float(feat[0]), 'Y': float(full_data.y[0] > 0)}
165+
cf_cond = int(min(np.unique(full_data.cond)))
115166
cf = counterfactual_for_unit(scm, row, cf_cond)
116167
print(f' counterfactual executed: {cf["method"]}')
117168
print(f' factual Y={cf["factual"]["Y"]:.2f} -> '

0 commit comments

Comments
 (0)