bearing-fault-nano
46,964 parameters. 188 KB. Which rolling-element bearing fault does this vibration signature
resemble — outer race, inner race, ball, or none?
Built to test a thesis: if one 47K architecture spans a camera, a microphone and an accelerometer,
then "nano model" is a method, not a computer-vision trick. It is the same network as this
project's image models, fed a spectrogram.
Domain measured / deployment domain tested: measured on synthesised vibration signatures; deployment domain: no real machine tested, ever. (Fifth line of the card standard, added 2026-09-02: a number is only as good as the domain it was measured in.)
Scope — read this first
This is a MAINTENANCE-OPERATIONS screening aid. It reports that a vibration signature is
consistent with a fault class, so a human can schedule an inspection.
It must not be used to certify machine health or to gate a safety decision. It has never been
validated on a real machine, it cannot see load, temperature, lubrication or history, and a
bearing can fail in ways this does not model. Do not place it anywhere a missed detection causes
harm.
Not a diagnosis. Fault classes are named for the frequency families they match, not for a
confirmed physical defect.
How it was built
Fault frequencies follow standard kinematics — for shaft rate f_r, n elements, and
r = (d/D)·cos α:
| class | frequency | distinguishing structure |
|---|
| outer race | BPFO = (n/2)(1−r)·f_r | periodic impacts, constant amplitude |
| inner race | BPFI = (n/2)(1+r)·f_r | impacts modulated at shaft rate |
| ball | BSF = (D/2d)(1−r²)·f_r | impacts modulated at cage rate |
| healthy | — | shaft harmonics only |
Each impact excites a structural resonance, modelled as a decaying sinusoid burst, so the
envelope carries the class — which is why this sits where a nano model wins and a scalar loses.
Shaft rate, element count, geometry ratio, resonance, decay and impact jitter are all randomised.
Measured
1,600 held-out examples, SNR −6 to +12 dB, chance 0.250:
| accuracy |
|---|
| nano model | 0.943 |
| best single scalar, fitted in-sample | 0.463 |
| majority | 0.250 |
Per-class recall: healthy 0.980 · outer race 0.925 · inner race 0.938 · ball 0.927.
By SNR — the number that matters, since a real installation sets it:
| SNR | −6…−2 dB | −2…+2 | +2…+6 | +6…+12 |
|---|
| accuracy | 0.790 | 0.955 | 1.000 | 0.998 |
Below about −2 dB it degrades. Mount the accelerometer well.
Known failure modes — the big one first
- Entirely synthetic. It has never heard a real bearing. Real machines add gear mesh, blade
pass, structural modes, mounting resonances, variable load and speed drift. This is the
dominant risk and it is untested. Treat published accuracy as an upper bound.
The same architecture trained this way on audio scored 0.987 false alarms on unfamiliar noise
until real backgrounds were added — expect a similar gap here until real data is used.
- Constant shaft speed within the window. Run-up and coast-down are not modelled.
- One second at 12 kHz. Faults below roughly 20 Hz shaft rate get too few impacts per window.
- Single fault assumed. Combined faults are not represented.
- No severity. It suggests a class, not how bad it is.
Usage
1import numpy as np, cv2, onnxruntime as ort
2from scipy import signal
3SR = 12000
4CLASSES = ["healthy", "outer_race", "inner_race", "ball"]
5sess = ort.InferenceSession("bearing_fault.onnx", providers=["CPUExecutionProvider"])
6
7def spec(x): # x: 1 s of accelerometer data at 12 kHz
8 f, t, S = signal.stft(x, SR, nperseg=512, noverlap=384)
9 P = np.log10(np.abs(S) + 1e-8)
10 P = (P - P.mean()) / (P.std() + 1e-8) # per-window standardise: absolute level is ignored
11 return cv2.resize(P.astype(np.float32), (64, 64), interpolation=cv2.INTER_AREA)
12
13print(CLASSES[int(sess.run(None, {"input": spec(x)[None,None]})[0][0].argmax())])
The per-window standardisation is required — the model is deliberately blind to absolute
amplitude, so it responds to structure rather than to how hard you clamped the sensor.
Training
5,200 train / 1,600 test windows · 4 conv layers (16→32→48→64) · Adam 3e-3 · 22 epochs.
Same architecture as resoajoe/alarm-nano, resoajoe/depth-nano and
resoajoe/camera-motion-nano, unchanged.
Verification
ONNX vs PyTorch, both CPU: max relative logit difference 2.5e-07, 100% argmax
agreement.
Deployment note: cap the ONNX Runtime thread pool
Measured on a Jetson AGX Orin. ONNX Runtime sizes its intra-op thread pool to the CPU core count,
and those workers spin-wait between inferences. Running two 47K-parameter models this way left
~18 threads busy-waiting at ~10.6% of a core each — about 1.9 cores burned continuously to run
inferences that take 0.28 ms. A model this small cannot use intra-op parallelism at all.
1so = ort.SessionOptions()
2so.intra_op_num_threads = 1
3so.inter_op_num_threads = 1
4so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
5so.add_session_config_entry("session.intra_op.allow_spinning", "0")
6sess = ort.InferenceSession("bearing_fault.onnx", sess_options=so, providers=["CPUExecutionProvider"])
Measured effect on the same workload: idle CPU 192% → 16.5% of one core, active 231% → 88%,
thread count 45 → 22, throughput unchanged. On edge hardware this is the difference between
"runs alongside everything else" and "saturates the machine".
What "scalar baseline" means on this card
Every margin quoted here is against a stated baseline, because a margin without one is not a
measurement. The baseline is the best single-threshold classifier over ten cheap statistics,
fitted optimistically:
mean · std · lapvar · hf (high-frequency energy ratio) · grad (Sobel magnitude) ·
entropy · centre_edge · radial_slope · row_fft_peak · col_fft_peak
The last four are spatially aware, added after an earlier six-statistic baseline — all global
aggregates — was found to systematically overstate model value on spatially structured tasks. A
baseline that cannot see where anything is loses to a CNN by default. On one test task that flaw
inflated an apparent margin from +0.060 to +0.261.
Two questions are asked with it, and they disagree:
- in-sample — threshold fitted on the data it is scored on. Deliberately generous. Answers
is there structure beyond a low-order statistic?
- transferred — threshold fitted on the training corpus, applied unchanged to the target.
Answers what should I ship? On one task the in-sample figure was 0.954 and the transferred
figure 0.565.
Where this card quotes a single scalar figure without qualification, it is the in-sample one.
Provenance
Fully synthetic, generated from published bearing kinematics. No proprietary machine data, no
personal data.