Skip to content

Commit 5505b20

Browse files
committed
feat: integrate XJTU-SY dataset and validate cross-domain reliability
1 parent 338effb commit 5505b20

4 files changed

Lines changed: 349 additions & 33 deletions

File tree

cnsd/datasets/xjtusy.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import glob
2+
import os
3+
4+
import numpy as np
5+
import pandas as pd
6+
7+
from cnsd.datasets.contract import Dataset
8+
from cnsd.physics.configs import XJTUSY_PHYSICS
9+
10+
# Known fault mappings per XJTU-SY paper
11+
# We map Bearing ID to CNSD fault class: 1=Outer, 2=Inner, 3=Cage
12+
XJTUSY_FAULTS = {
13+
'35Hz12kN': {
14+
'Bearing1_1': 1, # Outer
15+
'Bearing1_2': 1, # Outer
16+
'Bearing1_3': 1, # Outer
17+
},
18+
'37.5Hz11kN': {
19+
'Bearing2_1': 2, # Inner
20+
'Bearing2_2': 1, # Outer
21+
'Bearing2_4': 1, # Outer
22+
'Bearing2_5': 1, # Outer
23+
},
24+
'40Hz10kN': {
25+
'Bearing3_1': 1, # Outer
26+
'Bearing3_2': 2, # Inner
27+
'Bearing3_3': 2, # Inner
28+
'Bearing3_4': 2, # Inner
29+
'Bearing3_5': 1, # Outer
30+
},
31+
}
32+
33+
34+
def load_xjtusy_domain_split(
35+
data_dir=r'E:\301\CNSD\data\XJTU-SY\XJTU-SY_Bearing_Datasets',
36+
window_size=32768,
37+
train_cond='35Hz12kN',
38+
test_cond='37.5Hz11kN',
39+
):
40+
"""
41+
Loads authentic XJTU-SY dataset and strictly splits by condition (Domain Shift).
42+
Uses the run-to-failure nature to grab the first 15% as Healthy (0) and the
43+
last 15% as the Fault label.
44+
"""
45+
46+
def _load_condition(cond_folder, rpm_val):
47+
X, y, cond = [], [], []
48+
cond_path = os.path.join(data_dir, cond_folder)
49+
50+
if not os.path.exists(cond_path):
51+
return [], [], []
52+
53+
for bearing_folder in os.listdir(cond_path):
54+
bearing_path = os.path.join(cond_path, bearing_folder)
55+
if not os.path.isdir(bearing_path):
56+
continue
57+
58+
fault_label = XJTUSY_FAULTS.get(cond_folder, {}).get(bearing_folder, None)
59+
if (
60+
fault_label is None or bearing_folder == 'Bearing1_5'
61+
): # skip 1_5 to avoid mixed labels
62+
continue
63+
64+
csv_files = sorted(
65+
glob.glob(os.path.join(bearing_path, '*.csv')),
66+
key=lambda x: int(os.path.splitext(os.path.basename(x))[0]),
67+
)
68+
69+
total_files = len(csv_files)
70+
if total_files < 10:
71+
continue # ignore wildly corrupted directories
72+
73+
healthy_count = max(1, int(total_files * 0.20))
74+
fault_count = max(1, int(total_files * 0.20))
75+
76+
# Sliding window parameters
77+
step_size = 1024
78+
79+
# Extract Healthy
80+
for fpath in csv_files[:healthy_count]:
81+
df = pd.read_csv(fpath)
82+
sig = df.iloc[:, 0].values # Horizontal acceleration
83+
sig = (sig - np.mean(sig)) / (np.std(sig) + 1e-8)
84+
85+
# Slicing the 32768 array into overlapping 4096 windows
86+
for start_idx in range(0, len(sig) - window_size + 1, step_size):
87+
X.append(sig[start_idx : start_idx + window_size])
88+
y.append(0)
89+
cond.append(rpm_val)
90+
91+
# Extract Fault
92+
for fpath in csv_files[-fault_count:]:
93+
df = pd.read_csv(fpath)
94+
sig = df.iloc[:, 0].values
95+
sig = (sig - np.mean(sig)) / (np.std(sig) + 1e-8)
96+
97+
for start_idx in range(0, len(sig) - window_size + 1, step_size):
98+
X.append(sig[start_idx : start_idx + window_size])
99+
y.append(fault_label)
100+
cond.append(rpm_val)
101+
102+
return X, y, cond
103+
104+
X_train, y_train, c_train = _load_condition(train_cond, 2100.0)
105+
X_test, y_test, c_test = _load_condition(test_cond, 2250.0)
106+
107+
if not X_train or not X_test:
108+
raise FileNotFoundError(
109+
f'Could not load data. Ensure {data_dir} contains extracted {train_cond} and {test_cond} folders.'
110+
)
111+
112+
ds_train = Dataset.from_arrays(
113+
X=np.array(X_train, dtype=np.float32),
114+
y=np.array(y_train, dtype=np.int32),
115+
cond=np.array(c_train, dtype=np.float32),
116+
fs=25600,
117+
physics=XJTUSY_PHYSICS,
118+
taxonomy={0: ('Normal', 'None'), 1: ('Outer Race', 'Medium'), 2: ('Inner Race', 'High')},
119+
name='XJTUSY_Train',
120+
)
121+
122+
ds_test = Dataset.from_arrays(
123+
X=np.array(X_test, dtype=np.float32),
124+
y=np.array(y_test, dtype=np.int32),
125+
cond=np.array(c_test, dtype=np.float32),
126+
fs=25600,
127+
physics=XJTUSY_PHYSICS,
128+
taxonomy={0: ('Normal', 'None'), 1: ('Outer Race', 'Medium'), 2: ('Inner Race', 'High')},
129+
name='XJTUSY_Test',
130+
)
131+
132+
return ds_train, ds_test

cnsd/physics/configs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,10 @@ class PhysicsConfig:
3131
fs=20000,
3232
name='SEU-Gearbox',
3333
)
34+
35+
XJTUSY_PHYSICS = PhysicsConfig(
36+
bearing={'n_balls': 8, 'd_ball': 7.94, 'd_pitch': 34.55, 'contact_angle': 0.0},
37+
cond_to_rpm={2100.0: 2100.0, 2250.0: 2250.0, 2400.0: 2400.0},
38+
fs=25600,
39+
name='XJTU-SY-LDK-UER204',
40+
)

evaluate_baselines.py

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import argparse
12
import os
23
import sys
34
import traceback
@@ -7,39 +8,81 @@
78

89
try:
910
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
10-
1111
import tensorflow as tf
1212

1313
from cnsd import Dataset
1414
from cnsd.diagnosis.system import CNSD
1515
from cnsd.perception.cnn import _train_cnn
16-
from cnsd.physics import PhysicsConfig
17-
from validate_pu import load_pu_domain_split
1816

19-
print('Loading Authentic PU dataset (Cross-Domain RPM Split)...')
20-
(X_train_full, y_train_full, cond_train_full), (X_target, y_target, cond_target) = (
21-
load_pu_domain_split()
22-
)
17+
# Argparse
18+
parser = argparse.ArgumentParser()
19+
parser.add_argument('--dataset', type=str, required=True, choices=['cwru', 'pu', 'xjtusy'])
20+
args = parser.parse_args()
2321

24-
unique_rpm = set(cond_train_full).union(set(cond_target))
25-
rpm_map = {float(r): float(r) for r in unique_rpm}
26-
pu_physics = PhysicsConfig(
27-
bearing={'n_balls': 8, 'd_ball': 6.75, 'd_pitch': 28.5, 'contact_angle': 0.0},
28-
cond_to_rpm=rpm_map,
29-
fs=64000,
30-
name='PU-6203',
31-
)
32-
pu_taxonomy = {
33-
0: ('Normal', 'None'),
34-
1: ('Outer Race', 'Medium'),
35-
2: ('Inner Race', 'High'),
36-
}
22+
print(f'Loading {args.dataset.upper()} dataset...')
23+
24+
if args.dataset == 'pu':
25+
from cnsd.physics import PhysicsConfig
26+
from validate_pu import load_pu_domain_split
27+
28+
(X_train_full, y_train_full, cond_train_full), (X_target, y_target, cond_target) = (
29+
load_pu_domain_split()
30+
)
31+
unique_rpm = set(cond_train_full).union(set(cond_target))
32+
rpm_map = {float(r): float(r) for r in unique_rpm}
33+
physics = PhysicsConfig(
34+
bearing={'n_balls': 8, 'd_ball': 6.75, 'd_pitch': 28.5, 'contact_angle': 0.0},
35+
cond_to_rpm=rpm_map,
36+
fs=64000,
37+
name='PU-6203',
38+
)
39+
taxonomy = {0: ('Normal', 'None'), 1: ('Outer Race', 'Medium'), 2: ('Inner Race', 'High')}
40+
fs = 64000
41+
42+
elif args.dataset == 'cwru':
43+
from validate_run import CWRU, TAXONOMY, load_cwru
44+
45+
X, y, cond = load_cwru()
46+
X = np.asarray(X, np.float32)
47+
y = np.asarray(y)
48+
cond = np.asarray(cond)
49+
50+
train_mask = cond < 3
51+
target_mask = cond == 3
52+
53+
X_train_full = X[train_mask]
54+
y_train_full = y[train_mask]
55+
cond_train_full = cond[train_mask]
56+
57+
X_target = X[target_mask]
58+
y_target = y[target_mask]
59+
cond_target = cond[target_mask]
60+
61+
physics = CWRU
62+
taxonomy = TAXONOMY
63+
fs = 12000
64+
65+
elif args.dataset == 'xjtusy':
66+
from cnsd.datasets.xjtusy import load_xjtusy_domain_split
67+
68+
train_ds, target_ds = load_xjtusy_domain_split(window_size=4096)
69+
70+
X_train_full = train_ds.X
71+
y_train_full = train_ds.y
72+
cond_train_full = train_ds.cond
73+
74+
X_target = target_ds.X
75+
y_target = target_ds.y
76+
cond_target = target_ds.cond
77+
78+
physics = target_ds.physics
79+
taxonomy = target_ds.taxonomy
80+
fs = target_ds.fs
3781

3882
def get_matched_coverage_gap(score, correct, target_n):
3983
if target_n == 0:
4084
return float('nan')
4185
# Score is higher for MORE confident
42-
# Sort descending by score
4386
sorted_indices = np.argsort(score)[::-1]
4487
hi_indices = sorted_indices[:target_n]
4588

@@ -76,10 +119,10 @@ def clone_for_mc(layer):
76119
X_target,
77120
y_target,
78121
cond_target,
79-
fs=64000,
80-
physics=pu_physics,
81-
taxonomy=pu_taxonomy,
82-
name='PU_Test',
122+
fs=fs,
123+
physics=physics,
124+
taxonomy=taxonomy,
125+
name=f'{args.dataset.upper()}_Test',
83126
)
84127
sig_te = np.stack([test_ds.X[i].reshape(-1) for i in range(len(test_ds.X))]).astype(np.float32)
85128
yte = test_ds.y
@@ -112,10 +155,22 @@ def clone_for_mc(layer):
112155
)
113156

114157
train_ds = Dataset.from_arrays(
115-
X_tr, y_tr, cond_tr, fs=64000, physics=pu_physics, taxonomy=pu_taxonomy, name='PU_Train'
158+
X_tr,
159+
y_tr,
160+
cond_tr,
161+
fs=fs,
162+
physics=physics,
163+
taxonomy=taxonomy,
164+
name=f'{args.dataset.upper()}_Train',
116165
)
117166
calib_ds = Dataset.from_arrays(
118-
X_ca, y_ca, cond_ca, fs=64000, physics=pu_physics, taxonomy=pu_taxonomy, name='PU_Calib'
167+
X_ca,
168+
y_ca,
169+
cond_ca,
170+
fs=fs,
171+
physics=physics,
172+
taxonomy=taxonomy,
173+
name=f'{args.dataset.upper()}_Calib',
119174
)
120175

121176
# 2. Train Primary Model (Bypass SCM to prevent multiprocess deadlocks)
@@ -209,13 +264,10 @@ def clone_for_mc(layer):
209264
results['mc_gap'].append(mc_gap)
210265

211266
# Ensemble at matched coverage
212-
ens_preds_probs = np.stack(
213-
[m.predict(Xin_te, batch_size=128, verbose=0) for m in ens]
214-
) # (3, n, c)
215-
ens_mean = ens_preds_probs.mean(0) # (n, c)
267+
ens_preds_probs = np.stack([m.predict(Xin_te, batch_size=128, verbose=0) for m in ens])
268+
ens_mean = ens_preds_probs.mean(0)
216269
ens_pred_class = ens_mean.argmax(1)
217270
ens_correct = ens_pred_class == yte
218-
# Score = negative entropy of ensemble mean
219271
ens_score = (ens_mean * np.log(ens_mean + eps)).sum(1)
220272
ens_gap = get_matched_coverage_gap(ens_score, ens_correct, target_n)
221273
results['ens_gap'].append(ens_gap)
@@ -235,7 +287,6 @@ def clone_for_mc(layer):
235287
sig_n = sig_te + rng.randn(*sig_te.shape).astype(np.float32) * np.sqrt(npow)
236288

237289
Xin_n = sig_n[..., None]
238-
# Use ensemble mode vote to define 'unanimous' exactly like Abhi's template
239290
ep_class = np.stack(
240291
[m.predict(Xin_n, batch_size=128, verbose=0).argmax(1) for m in ens]
241292
)

0 commit comments

Comments
 (0)