
機械故障敏感特征提取加決策樹分類這兩個詞拼在一起就是一套很典型的旋轉機械故障診斷流程。最近我把渥太華大學的軸承公開數據集完整跑了一遍從原始振動信號里抽特征、篩敏感項再用決策樹做分類整個流程用Python實現下來踩了不少坑也積累了不少經驗。這篇文章就圍繞這個項目把從數據準備、特征提取、特征篩選到決策樹建模評估的完整過程梳理一遍代碼可以直接拿去改換自己的數據集也能用。先說清楚這個項目適合誰。如果你是做設備健康管理、工業數據分析、故障診斷方向的學生或工程師這篇內容就是給你準備的。即使你是剛上手Python的初學者只要懂一點numpy、pandas和sklearn的基本操作跟著走也能把流程跑通。文章里不光是代碼更重要的是每一步背后的取舍邏輯——為什么選這些特征、為什么用決策樹、特征怎么才算“敏感”、模型效果不好往哪個方向調這些才是真正值錢的經驗。1. 項目到底在解決什么問題1.1 機械故障診斷的核心邏輯機械設備在運轉過程中軸承、齒輪、轉子這些關鍵部件一旦出現磨損、裂紋、點蝕等故障最直觀的表現就是振動信號發生變化。故障診斷本質上做的是兩件事第一把“正常”和“異常”區分開第二把“異常”進一步細分成具體的故障類型比如內圈故障、外圈故障、滾動體故障。但振動信號這東西說白了就是一堆密密麻麻的時間序列采樣點動輒幾萬幾十萬個點。直接把原始信號扔給分類器不僅計算量大而且噪聲干擾嚴重分類效果往往很差。所以行業內標準的做法是先做特征提取——從原始信號中壓縮出若干能刻畫信號本質屬性的數值指標再用這些指標作為輸入去訓練分類模型。特征提取的質量直接決定了故障診斷的上限。這里有一個非常關鍵的認知特征提取不是越多越好而是要“敏感”。所謂敏感特征就是在不同故障狀態下取值差異顯著、能夠穩定區分各類別的特征。有些特征你算了一堆但它們在四種狀態下數值幾乎重合這種特征不但沒幫助反而會干擾模型。所以“提取敏感特征”這個說法重點在“敏感”二字提取之后還得多一步篩選。1.2 為什么選渥太華大學的公開數據集做這個項目數據是一切的起點。渥太華大學的這個公開數據集在故障診斷圈子里被用得比較多網上有公開下載渠道數據可以通過校園公開資源或者學術數據集匯總站點獲取。當然如果暫時拿不到用凱斯西儲大學CWRU的軸承數據集也能做同樣的流程思路完全一致。當時選它主要考慮幾點數據是真實采集的振動信號不是仿真數據流程跑通后更有說服力。包含正常、外圈故障、內圈故障、滾動體故障等多個類別分類任務的典型性很高。采樣頻率、工況信息清晰方便做頻譜分析驗證。拿到數據后第一件事不是急著寫代碼而是先把數據結構搞清楚。一般解壓后是一堆CSV或MAT格式的文件文件名里通常會標注故障類型、故障尺寸、轉速、載荷等信息。先用幾行代碼掃一遍文件名和文件內容心里有數再往下走。1.3 為什么是“特征提取決策樹”的組合有人可能會問現在深度學習那么火端到端的故障診斷一大堆干嘛還要用“特征決策樹”這種傳統組合我的回答是傳統方法并不過時而且在實際工程場景中往往更實用。首先深度學習雖然效果好但需要大量標注數據、昂貴的GPU訓練成本以及漫長的調參周期。而在工業現場很多時候你只有幾萬條樣本、一臺普通電腦這時候傳統方法的性價比反而更高。其次決策樹模型的可解釋性極強——它能明確告訴你“哪個特征、在什么閾值范圍內、對應什么故障”這種透明的決策邏輯在工業落地時非常重要。工程師能看懂模型為什么這么判斷才敢放心讓它上線。另外特征提取本身也是一種降維和去噪的過程提取出來的敏感特征本身就是對設備狀態的一種壓縮表示即使后面換成隨機森林、SVM甚至神經網絡這套特征提取流程也完全復用。所以這個項目的價值不止于“跑通一個決策樹”而是搭建了一套可擴展的故障診斷基礎流程。2. 數據集準備與Python環境搭建2.1 數據集的下載與文件結構整理這個項目第一步是把數據集下載好并整理成統一格式。渥太華大學的數據集下載后通常是一批CSV文件每個文件對應一組工況下的振動信號。你需要確認自己的Python環境已安裝完成如果沒有python環境先去官網下載安裝Python 3.8以上版本建議用Anaconda安裝裝好后在命令行輸入conda create -n fault python3.8 -y建一個獨立環境避免依賴沖突。下載后先不要急著跑代碼把文件按故障類別歸檔并做一個簡單的文件清單。建議把所有文件放到一個目錄下命名格式大致如下data/ ├── normal/ # 正常狀態 ├── outer/ # 外圈故障 ├── inner/ # 內圈故障 ├── ball/ # 滾動體故障有些版本的數據集還包含保持架故障處理方式一樣。歸檔這一步雖然機械但極其重要——后續加載數據、打標簽全靠這個目錄結構省得在腳本里寫一堆容易出錯的條件判斷。2.2 依賴庫安裝這個項目用到的Python庫不多但有幾個必須提前裝好庫名用途安裝命令numpy數值計算pip install numpypandas數據讀取與表格操作pip install pandasscipy信號處理、FFTpip install scipyscikit-learn決策樹、特征篩選、評估pip install scikit-learnmatplotlib可視化pip install matplotlib如果你用的是conda直接conda install numpy pandas scipy scikit-learn matplotlib一次性搞定。建議在項目文件夾里建一個requirements.txt把依賴寫進去方便換機器時一鍵恢復環境。這一步踩過的坑是有時候直接pip install會裝到系統自帶的Python環境里結果項目里跑代碼又調不到包報“ModuleNotFoundError”。所以強烈建議在建好的conda環境里裝裝完在終端確認一下python -c import sklearn; print(sklearn.__version__)看到版本號就說明環境OK。2.3 加載數據與合理切分數據文件加載本身不難但有個細節值得專門說一個幾秒鐘的振動信號文件可能就有十幾萬個采樣點如果直接把整個文件作為一條樣本樣本數量太少模型學不到東西。正確做法是滑窗切分——把一段長信號按照固定窗口長度切成多個子樣本每個子樣本作為一個獨立的樣本參與特征提取和模型訓練。滑窗切分有兩個參數要設置窗口長度和步長。窗口長度取決于信號的采樣頻率和故障特征頻率。比如采樣頻率是20kHz轉速是1200rpm那么軸承旋轉一圈對應1000個采樣點提取特征時窗口至少要覆蓋幾個旋轉周期一般建議窗口長度取2000到4000個點。步長可以設置成窗口的一半這樣相鄰窗口之間有一定重疊樣本數量能多出不少。代碼示意import numpy as np import pandas as pd def sliding_window(data, window_size, step_size): 將一維信號切成多個窗口子序列 samples [] for start in range(0, len(data) - window_size, step_size): samples.append(data[start:start window_size]) return np.array(samples) # 示例讀取一個文件切成窗口 raw_signal np.genfromtxt(data/normal/normal_1.csv, delimiter,) windows sliding_window(raw_signal, window_size2048, step_size1024) print(windows.shape) # 比如 (500, 2048)切完之后統一打標簽normal對應0 outer對應1 inner對應2 ball對應3。將標簽存成一列和后續的特征矩陣拼在一起形成一個標準的數據表。這一步做得干凈后面的特征提取和模型訓練會省很多事。3. 核心環節故障敏感特征提取3.1 時域特征的選取與計算時域特征是從信號波形本身直接計算出來的統計指標計算量小物理意義直觀是故障診斷最基礎的一層特征。我常用的時域特征有這些均值信號的直流分量反映信號中心位置。標準差和均方根值反映信號能量大小軸承磨損加劇時均方根通常會上升。峰值信號最大絕對值對沖擊類故障比較敏感。峭度信號分布的四階矩歸一化指標滾動體或外圈剝落產生的周期性沖擊會讓峭度明顯增大。偏度信號分布的不對稱程度某些定向磨損會讓偏度改變。峰值因子峰值除以均方根值用于衡量信號中沖擊成分的相對強度。波形因子、脈沖因子、裕度因子思路類似都是為了放大沖擊特征。這些特征各有側重有的反映能量有的反映沖擊有的反映分布形態。單個特征往往說不清楚問題組合起來就能區分多種故障類型。def extract_time_features(x): 輸入一個窗口的一維信號返回時域特征列表 mean_val np.mean(x) std_val np.std(x) rms np.sqrt(np.mean(x ** 2)) peak np.max(np.abs(x)) kurtosis np.mean((x - mean_val) ** 4) / (std_val ** 4 1e-12) skewness np.mean((x - mean_val) ** 3) / (std_val ** 3 1e-12) crest_factor peak / (rms 1e-12) waveform_factor rms / (np.mean(np.abs(x)) 1e-12) pulse_factor peak / (np.mean(np.abs(x)) 1e-12) margin_factor peak / (np.mean(np.sqrt(np.abs(x))) ** 2 1e-12) return [mean_val, rms, peak, kurtosis, skewness, crest_factor, waveform_factor, pulse_factor, margin_factor]注意實現時每個除法都加了極小值1e-12防止信號幅值為零時出現除零錯誤。這種邊界處理的細節在一堆窗口數據里很容易碰到建議一開始就寫上。3.2 頻域特征的提取邏輯時域特征看的是波形本身頻域特征看的是振動能量在不同頻率上的分布。故障發生時特定頻率處的能量會發生變化比如軸承內圈故障會在故障特征頻率及其倍頻處出現明顯的譜峰。所以頻譜特征對故障診斷往往比時域特征更敏銳。頻域特征的提取通常分兩步先用快速傅里葉變換FFT把信號從時域變到頻域再計算頻譜的一些量化指標。常用的頻域特征包括頻譜重心能量集中的頻率位置不同故障導致的主頻偏移會反映在上面。均方頻率各頻率分量的加權平均反映頻譜整體的分布情況。頻率方差頻譜能量在頻域中相對重心的離散程度。特定頻帶能量占比把頻譜分為若干頻段統計每個頻段的能量占總能量的比例。from scipy.fft import fft def extract_freq_features(x, fs20000): 輸入一維信號返回頻域特征列表 n len(x) spectrum np.abs(fft(x))[:n // 2] freqs np.linspace(0, fs / 2, n // 2) total_energy np.sum(spectrum ** 2) 1e-12 spectral_centroid np.sum(freqs * spectrum ** 2) / total_energy mean_square_freq np.sum(freqs ** 2 * spectrum ** 2) / total_energy freq_variance np.sum((freqs - spectral_centroid) ** 2 * spectrum ** 2) / total_energy # 按1000Hz間隔劃分頻帶能量占比 band_ratios [] for low in range(0, int(fs / 2), 1000): mask (freqs low) (freqs low 1000) band_energy np.sum(spectrum[mask] ** 2) band_ratios.append(band_energy / total_energy) return [spectral_centroid, mean_square_freq, freq_variance] band_ratios這里有幾個細節值得注意。取頻譜時只取前一半因為實信號的FFT結果是關于奈奎斯特頻率對稱的后一半沒有獨立信息。頻帶劃分的寬度跟采樣頻率相關采樣頻率越高可分析的頻帶就越多。我在這套數據上把頻帶寬度設為1000Hz組合出來十幾個頻帶特征區分效果已經不錯了。3.3 時頻域特征的補充到了這一步時域特征和頻域特征已經能夠覆蓋大部分故障類型。但有些故障尤其是不穩定運行狀態下的早期微弱故障它的沖擊成分在時域上出現的位置和頻域上的分布都在變化單純看全局時域或全局頻譜會把這些瞬態信息平均掉。這時可以考慮補充時頻域特征。最簡單實用的時頻方法是小波包分解它能把信號分解到不同頻帶和時間分辨率上每個分解子帶的能量占比可以作為特征。實現上直接用現成的庫就行不需要自己寫小波算法。import pywt def extract_wavelet_features(x, waveletdb4, level3): 小波包分解計算各節點能量占比作為特征 wp pywt.WaveletPacket(datax, waveletwavelet, modesymmetric, maxlevellevel) nodes [node.path for node in wp.get_level(level, freq)] energies [] for node in nodes: data_node wp[node].data energies.append(np.sum(data_node ** 2)) total np.sum(energies) 1e-12 return [e / total for e in energies]注意處理之前要先把數據標準化否則小波系數幅值差異過大會影響能量占比的穩定性。3.4 特征敏感度的評估與篩選特征提取完我這里通常拿到幾十個特征。問題來了這些特征都“敏感”嗎不見得。有些特征在不同故障類別之間的分布幾乎重疊區分能力非常弱有些特征跟別的特征高度相關存在信息冗余。我篩特征的思路是“一粗一細”兩步走。粗篩單特征可區分性分析。把每個特征按類別畫箱線圖直觀看出這個特征在不同類別下的分布是否有明顯區分。如果四個類別的箱子高度重合這個特征基本可以放棄。量化指標可以用方差分析ANOVA的F值F值越大說明組間差異相對組內差異越顯著特征越敏感。from sklearn.feature_selection import f_classif # feature_matrix: (n_samples, n_features) # labels: (n_samples,) f_scores, p_values f_classif(feature_matrix, labels) for idx, f in enumerate(f_scores): print(f特征{idx}: F值 {f:.2f})細篩相關性去冗余。用皮爾遜相關系數矩陣看特征之間的相關性。相關系數超過0.95的兩個特征只需要保留那個單獨區分度更高的另一個可以考慮去掉。這一步能明顯減少特征數量讓模型更簡潔。corr_matrix np.corrcoef(feature_matrix, rowvarFalse) # 找出高相關特征對 high_corr_pairs [] n_features corr_matrix.shape[0] for i in range(n_features): for j in range(i 1, n_features): if abs(corr_matrix[i, j]) 0.95: high_corr_pairs.append((i, j, corr_matrix[i, j]))最后把所有篩選出來的特征拼成一個二維矩陣每一行是一個窗口樣本每一列是一個特征再配上標簽列。這就是可以用來訓練的最終數據集拿到手之后模型的輸入問題就徹底解決了。4. 決策樹分類建模與評估4.1 決策樹原理與參數選擇決策樹分類器做的事情本質上是學一串“if-else”判斷規則。它自動尋找特征空間中最合適的分割點把樣本一層層劃分下去直到每個葉子節點內部都是同一類別或純度足夠高。它的好處是訓練快、可解釋性強而且對特征尺度不敏感——這意味著前面提取的一些幅值差異大的特征不需要額外歸一化就能直接用省了很多預處理工作。用scikit-learn實現決策樹非常簡單from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( feature_matrix, labels, test_size0.3, random_state42, stratifylabels ) clf DecisionTreeClassifier( max_depth6, min_samples_split10, min_samples_leaf5, criteriongini, random_state42 ) clf.fit(X_train, y_train)有幾個參數需要特別留意一下。max_depth是樹的最大深度。深度太小模型學不到足夠的決策規則欠擬合深度太大樹會瘋狂生長把訓練集的細節和噪聲都記住嚴重過擬合。我習慣從小往大試從3開始每次加2觀察訓練集和測試集準確率的變化找一個“測試集準確率最高且和訓練集差距不太大”的深度。min_samples_split是節點繼續分裂所需的最小樣本數。它跟數據集規模強相關樣本量大的時候可以設大一點比如10到20樣本量小的時候設2到5免得樹根本長不起來。min_samples_leaf是葉子節點的最小樣本數。這個參數對抑制過擬合非常有效葉子節點樣本太少意味著決策規則訓練得太細泛化能力差一般設為樣本總量的1%左右比較穩妥。另外建議在切分數據時設stratifylabels做分層采樣確保訓練集和測試集中各個類別的占比與原始數據一致。否則萬一隨機切分的時候某一類的樣本全跑到測試集去了模型訓練時根本沒見這一類測試時一塌糊涂。4.2 模型評估指標的選擇準確率是最直觀的指標但對故障診斷來說光看準確率遠遠不夠。如果正常樣本占80%哪怕模型把所有故障樣本都判錯準確率還有80%看著挺高實際上模型完全不能用。所以必須看混淆矩陣以及每個類別的精確率、召回率和F1值。以四分類為例混淆矩陣是一個4x4的矩陣行是真實標簽列是預測標簽。對角線上的數字越大越好對角線之外的數字越大說明誤分類越嚴重。from sklearn.metrics import classification_report, confusion_matrix y_pred clf.predict(X_test) print(classification_report(y_test, y_pred, target_names[normal, outer, inner, ball]))實戰中我最關注的是“內圈故障被判成外圈故障”這種混淆。滾動體和內圈的故障特征頻率在某些工況下比較接近模型容易搞混。遇到這種情況不要急著換模型先去檢查這幾類的特征分布往往能通過增加特征或調整特征組合來改善。4.3 決策樹結構的可視化決策樹模型最好的地方在于可以把它畫出來讓人直接看到它到底學了一組什么樣的規則。這一步在工程匯報和方案評審的時候特別好用——你不光能告訴領導“模型準確率95%”還能告訴他“模型認為均方根超過0.85且峭度超過6的時候判為內圈故障”這個說服力是完全不同的。from sklearn.tree import export_graphviz import graphviz dot_data export_graphviz( clf, out_fileNone, feature_namesfeature_names, class_names[normal, outer, inner, ball], filledTrue, roundedTrue, special_charactersTrue ) graph graphviz.Source(dot_data) graph.render(fault_decision_tree) # 保存為PDF文件畫出來之后重點關注根節點附近的前兩三層的分裂規則這幾層是模型最主要的判別邏輯。后面如果有特征重要性輸出的需要可以直接讀取clf.feature_importances_它能告訴你哪個特征對分類的貢獻最大這又是一個極好的特征敏感性驗證手段。4.4 參數調優的實驗記錄我第一次跑的時候決策樹參數用的是默認配置max_depth不限制。結果訓練集準確率接近100%測試集準確率只有89%典型的過擬合。后來我做了幾組對比實驗記錄如下參數設置訓練集準確率測試集準確率是否過擬合不限制深度99.8%89.2%是max_depth896.5%93.1%輕微max_depth694.2%94.6%否max_depth6, min_samples_leaf592.8%95.3%否可以看到適當限制樹的復雜度測試集準確率反而上升了。這就是為什么我一直強調不要盲目追求訓練集上的完美表現。在真實工業故障診斷中訓練集和測試集往往還來自不同時間段、不同工況的數據泛化能力差一點點落地效果就會差非常多。5. 項目實操中的常見問題與排查技巧5.1 多分類標簽與特征矩陣的Shape不一致這個問題出現的頻率最高。特征提取往往是對每個窗口單獨運行的如果某個文件切出來的窗口數量不是整整齊齊的特征矩陣的行數和標簽列表的長度就對不上。訓練模型時sklearn會直接報錯說“Found input variables with inconsistent numbers of samples”。排查辦法很簡單在拼接特征矩陣和標簽之前打印一下兩邊的shape進行長度驗證。我習慣在每個處理階段都加一句斷言assert len(feature_list) len(label_list), 特征數與標簽數不一致請檢查滑窗邏輯這個小習慣能幫你省下大量排查錯誤的時間。5.2 數據集不平衡問題實際項目中正常狀態的數據往往非常多故障狀態的數據相對稀少。如果直接訓練決策樹會偏向樣本量大的類別導致少數類故障被漏報。而故障診斷中最不能容忍的就是漏報——設備壞了你說它沒事生產線上耽誤一分鐘都是損失。解決思路有兩個一是從數據層面做重采樣對樣本少的類別進行過采樣比如SMOTE或對樣本多的類別進行欠采樣二是在模型層面調整類別權重在sklearn里直接加class_weightbalanced參數讓決策樹在分裂時對不同類別給予不同的權重。實測下來數據量大的時候我一般先用class_weight效果不夠再上采樣方法。5.3 特征區分度不足分類準確率上不去這是我被問得最多的一個問題“老師我把特征都算出來了但決策樹準確率只有80%怎么辦”我的排查順序是這樣的。第一步先看箱線圖或特征的F值確認每個特征真的在不同類別間有差異。如果很多特征基本重合問題出在特征提取環節需要回到時頻域特征上去補。第二步看混淆矩陣判斷到底是哪些類別之間容易混淆。如果是內圈和外圈混淆考慮提取軸承故障特征頻率處的窄帶能量特征。第三步看是不是樣本量太少了窗口切分的重疊度不夠嘗試減小步長增加樣本量。第四步換模型交叉驗證——把決策樹換成隨機森林看看準確率有沒有明顯提升。如果隨機森林明顯更好說明單個決策樹方差太大用集成方式把多棵樹投票就能解決問題。5.4 原始信號文件讀取格式兼容問題不同來源的數據集格式差異很大有的CSV文件第一行是列名有的直接是純數據有的用逗號分隔有的用分號或制表符有的甚至要跳過起始幾行注釋。為此我專門寫了一個統一讀取函數def load_signal_file(path): 兼容多種分隔符的振動信號讀取 try: df pd.read_csv(path, headerNone, sep,) except Exception: try: df pd.read_csv(path, headerNone, sep\t) except Exception: df pd.read_csv(path, headerNone, sep;) # 只取第一列數據很多輸出文件第一列是時間戳 return df.iloc[:, 0].values.astype(np.float64)這個函數用三段式try-else把多種情況都兜住了數據加載這一塊的兼容性問題基本都能解決。當然前提是文件里確實有一列是振動幅值數據文件結構能正常解析。5.5 安裝依賴時遇到”缺少包”的錯誤如果運行時報錯提示缺少某個模塊先別亂裝一堆包。仔細看一下報錯信息里缺的是哪個庫按需安裝即可。比如提示ModuleNotFoundError: No module named pywt說明缺少小波庫執行pip install PyWavelets就行。如果提示需要安裝缺失節點或工作流相關的包比如某些comfyui場景下的報錯這說明項目環境與其他框架混在一起了建議重新建一個專用conda環境只安裝這個項目需要的庫避免環境互相污染。安裝后用pip list確認版本,并隨手記錄到requirements.txt中。6. 最后再分享兩個實用技巧第一個技巧是我多次調試后總結出來的在做特征提取時不要只算特征數值一定要把“工況信息”也記錄下來。同一型號的軸承在800rpm和1500rpm下提取的均方根、峭度差異非常大。如果你的數據里混了多種工況而不加區分模型會學得非常吃力。最省事的辦法是特征提取時把轉速或工況編號作為一個特征列加進去或者按工況分組建模。這個問題在工程現場極其常見很多人一開始忽略它后面數據分布一變模型就崩再來排查就晚了。第二個技巧關于決策樹的剪枝策略。sklearn的DecisionTreeClassifier自帶cost_complexity_pruning_path方法可以輸出不同ccp_alpha值對應的剪枝效果配合網格搜索能得到泛化能力更好的樹。我用了一版剪枝后的樹測試集準確率又提升了1到2個百分點。如果你對決策樹本身的要求比較高可以深入研究一下這條路。整套流程跑下來渥太華大學這個數據集的四分類準確率在我的實驗里能達到95%以上如果疊加工況分層和更精細的時頻特征沖到97%也不難。但這個項目給我最大的收獲不是那一個準確率數字而是把“特征到底怎么算、怎么篩、怎么用”這條鏈路徹底打通了。往后不論換什么數據、換什么模型這套做故障診斷的思維框架都是通用的。你要是剛接觸這個方向建議先照著代碼把流程完整跑一遍然后再換一個數據集試試同樣的方法——等你能把新數據集也做出一套像樣的分類結果這個知識點就真正是你的了。