處理到主動學習全流程)
簡介這份PDF文檔面向天文數(shù)據(jù)處理與機器學習方向的學習者、研究者聚焦恒星光譜數(shù)據(jù)的自動分類問題提出將偏差估計與卷積神經(jīng)網(wǎng)絡(luò)相結(jié)合的解決思路。內(nèi)容圍繞數(shù)據(jù)預(yù)處理、偏差估計、特征提取、分類模型訓練與結(jié)果評估五個環(huán)節(jié)展開并分析了該方法在高噪聲、高偏差數(shù)據(jù)下準確性較高、處理效率較好、可靈活調(diào)整等優(yōu)勢同時指出其計算資源消耗大、依賴大量訓練樣本等局限適合具備一定深度學習基礎(chǔ)、希望將CNN遷移到光譜分析場景的讀者參考。資源包內(nèi)僅含1個PDF文件壓縮后約1.26MB輕量便于下載與本地閱讀。目前已有143人學習可作為恒星光譜分類課題的入門方案與建模思路參考幫助讀者快速理解偏差估計與卷積網(wǎng)絡(luò)結(jié)合的技術(shù)路線、實驗流程及優(yōu)缺點權(quán)衡。1. 偏差估計 CNN 做恒星光譜自動分類這套方案到底解決什么問題恒星光譜自動分類這件事聽起來像是天文領(lǐng)域的專屬問題但它的技術(shù)骨架其實和很多工業(yè)場景一模一樣輸入是一維序列信號輸出是離散類別中間夾雜著大量噪聲、缺失和分布偏移。傳統(tǒng)做法靠人工定義特征——等效寬度、譜線比值、溫度指數(shù)——再喂給 SVM 或隨機森林。這套流程在 SDSS、LAMOST 這類大規(guī)模巡天數(shù)據(jù)面前越來越吃力因為光譜數(shù)量從幾萬條漲到幾百萬條人工特征工程根本跟不上。偏差估計卷積神經(jīng)網(wǎng)絡(luò)恒星光譜數(shù)據(jù)自動分類這個方向核心思路是用 CNN 自動從光譜通量序列里學特征同時在網(wǎng)絡(luò)內(nèi)部或訓練流程中引入偏差估計機制用來校正儀器響應(yīng)差異、流量定標誤差和類別不平衡帶來的系統(tǒng)性偏移。它適合兩類人一是手頭有幾千到幾十萬條光譜、想從零搭一套分類流水線的工程師二是已經(jīng)用了 CNN 但發(fā)現(xiàn)模型在新一批數(shù)據(jù)上精度掉得厲害、想搞清楚偏差從哪來的人。我自己的血淚經(jīng)驗是光譜分類模型在訓練集上跑到 95% 不難難的是換一臺望遠鏡、換一個巡天項目之后還能穩(wěn)住 80% 以上。偏差估計就是那個幫你穩(wěn)住的東西。2. 光譜數(shù)據(jù)預(yù)處理與偏差來源拆解從 FITS 到模型輸入2.1 恒星光譜的物理特性與分類體系恒星光譜本質(zhì)上是波長-流量的一維序列。不同光譜型O、B、A、F、G、K、M的差異主要體現(xiàn)在連續(xù)譜形狀、吸收線位置和強度上。O 型星在藍端流量強、氫線弱M 型星在紅端流量強、TiO 分子帶明顯。分類任務(wù)通常分兩個層級粗分類7 個光譜型和細分類帶光度型如 G2V、K5III。對 CNN 來說輸入不是原始波長-流量對而是經(jīng)過重采樣和歸一化后的固定長度向量。常見做法是重采樣到 3800–9000 ? 范圍、步長 1–2 ?得到 2600–5200 個點。這個長度對一維 CNN 來說完全可控。偏差來源主要有四個儀器響應(yīng)函數(shù)差異、流量定標誤差、星際消光、信噪比不均勻。前兩個是系統(tǒng)性的后兩個更接近隨機噪聲。偏差估計要處理的主要是前兩個。2.2 用 Python 讀取 FITS 并做統(tǒng)一重采樣下面這段代碼是我常用的預(yù)處理入口從 FITS 文件讀取波長、流量和頭信息重采樣到統(tǒng)一波長網(wǎng)格并做初步的壞點剔除。import numpy as np from astropy.io import fits from scipy.interpolate import interp1d def load_spectrum(fits_path, wave_grid): 讀取單條 FITS 光譜重采樣到 wave_grid。 wave_grid: 目標波長網(wǎng)格單位埃等間距。 with fits.open(fits_path) as hdul: header hdul[0].header data hdul[1].data if len(hdul) 1 else hdul[0].data wave data[loglam] if loglam in data.names else data[wave] flux data[flux] # SDSS 的 loglam 是對數(shù)波長需要轉(zhuǎn)回線性 if loglam in data.names: wave 10 ** wave # 剔除 NaN 和 inf mask np.isfinite(flux) np.isfinite(wave) wave, flux wave[mask], flux[mask] # 按波長排序有些文件是亂序的 idx np.argsort(wave) wave, flux wave[idx], flux[idx] # 插值到統(tǒng)一網(wǎng)格 f interp1d(wave, flux, kindlinear, bounds_errorFalse, fill_value0.0) flux_resampled f(wave_grid) # 連續(xù)譜歸一化用中值濾波估計連續(xù)譜 from scipy.signal import medfilt continuum medfilt(flux_resampled, kernel_size301) continuum[continuum 0] 1.0 flux_norm flux_resampled / continuum return flux_norm, header邏輯說明先處理 SDSS 特有的對數(shù)波長再做有限值過濾和排序插值到統(tǒng)一網(wǎng)格。連續(xù)譜歸一化用 301 點中值濾波這個窗口對應(yīng)大約 300–600 ?能平滑掉吸收線但保留連續(xù)譜形狀。參數(shù)kernel_size需要根據(jù)波長步長調(diào)整——步長 1 ? 時 301 合適步長 2 ? 時用 151。2.3 偏差估計的三種切入方式偏差估計不是單一技術(shù)而是一個思路。在光譜分類里我見過三種落地方式第一種是輸入層偏差校正。在預(yù)處理階段估計每條光譜的儀器響應(yīng)殘差用低階多項式擬合連續(xù)譜比值把偏差扣掉再送進網(wǎng)絡(luò)。優(yōu)點是簡單直接缺點是需要一個參考光譜模板。第二種是網(wǎng)絡(luò)內(nèi)部偏差模塊。在 CNN 的某個中間層加一個偏差估計分支預(yù)測一個偏移向量然后從主特征中減去。這類似 Domain Adaptation 里的殘差校正。實現(xiàn)上可以用一個小的全連接分支輸出與特征同維度的偏移量。第三種是訓練策略層面的偏差估計。用對抗訓練或重加權(quán)讓模型對不同儀器來源的數(shù)據(jù)學出一致的表示。這種方式不需要改網(wǎng)絡(luò)結(jié)構(gòu)但訓練調(diào)參更玄學。我一般會先從第一種入手因為最容易驗證效果。如果預(yù)處理做完偏差還在再考慮第二種。2.4 訓練集構(gòu)建與類別不平衡處理光譜分類的類別不平衡非常嚴重。SDSS 里 G 型和 K 型星占了大半O 型和 M 型少得多。直接訓練的話模型會把所有樣本往多數(shù)類推。常見做法是分層采樣加類別權(quán)重。下面是一個構(gòu)建 PyTorch DataLoader 的示例import torch from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler class SpectrumDataset(Dataset): def __init__(self, flux_array, labels): # flux_array: (N, L) 歸一化后的流量 # labels: (N,) 整數(shù)類別標簽 self.flux torch.FloatTensor(flux_array).unsqueeze(1) # (N,1,L) self.labels torch.LongTensor(labels) def __len__(self): return len(self.labels) def __getitem__(self, idx): return self.flux[idx], self.labels[idx] def make_balanced_loader(flux, labels, batch_size64): class_counts np.bincount(labels) weights 1.0 / class_counts[labels] sampler WeightedRandomSampler( weightstorch.DoubleTensor(weights), num_sampleslen(labels), replacementTrue ) dataset SpectrumDataset(flux, labels) return DataLoader(dataset, batch_sizebatch_size, samplersampler, num_workers4)WeightedRandomSampler的核心參數(shù)是weights這里用類別頻率的倒數(shù)。num_samples設(shè)成總樣本數(shù)保證每個 epoch 看到的樣本量和原始一致。replacementTrue允許重復(fù)采樣少數(shù)類。注意num_workers在 Windows 上設(shè) 0否則容易出問題。3. 偏差估計 CNN 的網(wǎng)絡(luò)結(jié)構(gòu)與訓練流程3.1 一維 CNN 主干設(shè)計卷積核大小怎么選光譜是一維序列用一維卷積。卷積核大小直接決定模型能捕捉多寬的譜線特征。吸收線的典型寬度是 5–20 ?對應(yīng) 5–20 個點步長 1 ?。所以第一層卷積核建議 7–11能覆蓋一條完整吸收線。后面幾層可以逐步增大到 15–21捕捉更寬的特征如分子帶。通道數(shù)從 32 起步逐層翻倍到 256 或 512。層數(shù)不用太深4–6 層卷積加全局池化就夠了。光譜分類不像圖像分類需要上百層因為一維信號的局部模式相對簡單。下面是一個帶偏差估計分支的網(wǎng)絡(luò)定義import torch.nn as nn import torch.nn.functional as F class BiasEstimationCNN(nn.Module): def __init__(self, input_len4000, n_classes7): super().__init__() # 主干特征提取 self.conv1 nn.Conv1d(1, 32, kernel_size9, padding4) self.conv2 nn.Conv1d(32, 64, kernel_size11, padding5) self.conv3 nn.Conv1d(64, 128, kernel_size15, padding7) self.conv4 nn.Conv1d(128, 256, kernel_size21, padding10) self.bn1 nn.BatchNorm1d(32) self.bn2 nn.BatchNorm1d(64) self.bn3 nn.BatchNorm1d(128) self.bn4 nn.BatchNorm1d(256) self.pool nn.MaxPool1d(4) self.global_pool nn.AdaptiveAvgPool1d(1) # 偏差估計分支從中間特征預(yù)測一個偏移向量 self.bias_fc nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Linear(128, 256), nn.Sigmoid() # 偏移量限制在 0-1 之間作為縮放因子 ) # 分類頭 self.classifier nn.Sequential( nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, n_classes) ) def forward(self, x): # x: (B, 1, L) x self.pool(F.relu(self.bn1(self.conv1(x)))) x self.pool(F.relu(self.bn2(self.conv2(x)))) x self.pool(F.relu(self.bn3(self.conv3(x)))) x self.pool(F.relu(self.bn4(self.conv4(x)))) # 全局池化得到特征向量 feat self.global_pool(x).squeeze(-1) # (B, 256) # 偏差估計預(yù)測縮放因子并校正特征 bias self.bias_fc(feat) # (B, 256) feat_corrected feat * bias # 逐元素縮放 return self.classifier(feat_corrected)邏輯說明主干四層卷積每層后接 BatchNorm 和 ReLU再用 MaxPool 降采樣。偏差分支從全局特征預(yù)測一個 256 維縮放向量用 Sigmoid 限制在 0–1然后逐元素乘到特征上。這個設(shè)計假設(shè)偏差表現(xiàn)為特征幅度的系統(tǒng)性縮放這在儀器響應(yīng)差異場景下是合理的。參數(shù)方面kernel_size從 9 到 21 遞增對應(yīng)不同寬度的譜線特征。Dropout(0.3)防止過擬合光譜數(shù)據(jù)量不大時很關(guān)鍵。偏差分支的 Sigmoid 是設(shè)計選擇——如果你認為偏差是加性的換成Tanh并做加法。3.2 損失函數(shù)交叉熵 偏差正則項只用交叉熵訓練偏差分支可能學不到有意義的東西。我一般會加一個正則項約束偏差估計的平滑性——相鄰波段的偏差不應(yīng)該跳變。def bias_regularized_loss(logits, labels, bias_vector, lambda_smooth0.01): logits: (B, n_classes) labels: (B,) bias_vector: (B, 256) 偏差估計分支的輸出 lambda_smooth: 平滑正則權(quán)重 ce_loss F.cross_entropy(logits, labels) # 平滑正則偏差向量的相鄰維度差異 diff bias_vector[:, 1:] - bias_vector[:, :-1] smooth_loss torch.mean(diff ** 2) return ce_loss lambda_smooth * smooth_losslambda_smooth控制正則強度從 0.001 到 0.1 之間調(diào)。太大模型會忽略偏差分支太小偏差估計會過擬合噪聲。我一般從 0.01 開始看驗證集精度和偏差向量的可視化結(jié)果再調(diào)。3.3 訓練循環(huán)與學習率調(diào)度訓練流程本身不復(fù)雜關(guān)鍵是學習率調(diào)度和早停。光譜分類模型通常在 30–50 個 epoch 收斂。from torch.optim import Adam from torch.optim.lr_scheduler import CosineAnnealingLR def train_model(model, train_loader, val_loader, epochs50, lr1e-3): optimizer Adam(model.parameters(), lrlr, weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_maxepochs, eta_min1e-6) best_val_acc 0.0 patience 8 wait 0 for epoch in range(epochs): model.train() for flux, label in train_loader: optimizer.zero_grad() logits model(flux) # 這里需要模型返回偏差向量實際實現(xiàn)時調(diào)整 forward loss F.cross_entropy(logits, label) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0) optimizer.step() scheduler.step() # 驗證 model.eval() correct, total 0, 0 with torch.no_grad(): for flux, label in val_loader: pred model(flux).argmax(dim1) correct (pred label).sum().item() total label.size(0) val_acc correct / total if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_model.pth) wait 0 else: wait 1 if wait patience: print(fEarly stop at epoch {epoch}) break return best_val_accclip_grad_norm_設(shè) 5.0 是防止梯度爆炸光譜數(shù)據(jù)里偶爾有極端值。CosineAnnealingLR比 StepLR 更平滑eta_min1e-6保證最后階段學習率不會歸零。早停 patience 設(shè) 8因為驗證精度波動通常不會超過 5 個 epoch。3.4 偏差估計效果驗證t-SNE 可視化與跨儀器測試訓練完之后怎么知道偏差估計起作用了我一般做兩件事。第一件是把倒數(shù)第二層的特征拿出來做 t-SNE看不同儀器來源的樣本是否混在一起。如果偏差估計有效同一光譜型但不同儀器的樣本應(yīng)該在 t-SNE 圖上聚成一團而不是按儀器分開。第二件是留出一個儀器來源的數(shù)據(jù)做測試。比如用 SDSS 訓練用 LAMOST 測試。如果加了偏差估計后跨儀器精度比不加時高 5 個百分點以上說明偏差模塊確實在起作用。如果沒差別可能是偏差來源不在特征幅度上需要換一種偏差建模方式。4. 避坑與排查光譜分類里最容易翻車的五個地方4.1 歸一化方式選錯導致模型學不到譜線特征現(xiàn)象訓練精度一直上不去卡在 60% 左右loss 下降很慢。原因用了全局歸一化整條光譜除以總流量把連續(xù)譜形狀信息也抹掉了。連續(xù)譜形狀恰恰是區(qū)分光譜型的重要特征——O 型星藍端強、M 型星紅端強全局歸一化之后這個差異被壓平了。解決改用連續(xù)譜歸一化即用中值濾波或多項式擬合估計連續(xù)譜然后做除法。這樣保留連續(xù)譜形狀同時把吸收線的相對強度凸顯出來。中值濾波窗口根據(jù)波長步長選步長 1 ? 時用 301步長 2 ? 時用 151。4.2 類別權(quán)重設(shè)得太大導致少數(shù)類過擬合現(xiàn)象加了類別權(quán)重后少數(shù)類O 型、M 型的召回率上去了但精確率暴跌大量 G 型星被誤判成 O 型。原因權(quán)重設(shè)成了類別頻率倒數(shù)的平方或更高次方少數(shù)類樣本被重復(fù)采樣太多次模型把少數(shù)類的噪聲也學進去了。解決權(quán)重用頻率倒數(shù)的一次方就夠了配合WeightedRandomSampler時num_samples不要超過總樣本數(shù)的 1.5 倍。另外可以在損失函數(shù)里用 Focal Loss 替代加權(quán)交叉熵讓模型更關(guān)注難分類樣本而不是簡單粗暴地加權(quán)。4.3 卷積核太大導致過擬合到噪聲現(xiàn)象訓練集精度 99%驗證集精度 70%差距巨大。原因第一層卷積核設(shè)成了 31 或更大感受野覆蓋了 30 多個 ?把噪聲模式也當成特征學了。光譜里的噪聲在相鄰像素間沒有相關(guān)性大卷積核反而給了模型擬合噪聲的自由度。解決第一層卷積核控制在 7–11后續(xù)層再逐步增大。同時加 Dropout 和權(quán)重衰減。如果數(shù)據(jù)量少于 5000 條考慮用更淺的網(wǎng)絡(luò)3 層卷積或加數(shù)據(jù)增強波長抖動、流量擾動。4.4 偏差估計分支梯度消失導致形同虛設(shè)現(xiàn)象偏差分支的輸出在所有樣本上幾乎一樣沒有區(qū)分度。原因偏差分支接在全局池化之后梯度要穿過整個主干才能回傳。如果主干用了 Sigmoid 激活或沒有 BatchNorm梯度會衰減得很厲害。解決偏差分支從更靠前的層接出來比如第三層卷積之后。或者在偏差分支前加一個獨立的 BatchNorm。另外檢查偏差分支的學習率——如果和主干用同一個學習率偏差分支可能學得太慢??梢越o偏差分支單獨設(shè)一個更大的學習率比如主干的 5 倍。4.5 跨儀器測試時精度斷崖式下跌現(xiàn)象SDSS 上訓練到 92%換 LAMOST 數(shù)據(jù)測試直接掉到 55%。原因兩個巡天的波長覆蓋范圍、分辨率、流量定標方式都不同。如果預(yù)處理時沒有統(tǒng)一到相同的波長網(wǎng)格和分辨率模型看到的輸入分布完全變了。解決預(yù)處理階段強制統(tǒng)一波長網(wǎng)格和分辨率。具體做法是先對高分辨率光譜做高斯卷積降分辨率再插值到統(tǒng)一網(wǎng)格。另外檢查流量單位——SDSS 用 nanomaggieLAMOST 用相對流量需要做流量定標轉(zhuǎn)換。如果這些做完精度還是掉說明偏差不在輸入層需要把偏差估計模塊加到網(wǎng)絡(luò)中間層。5. 進階技巧用偏差估計做主動學習與模型迭代5.1 用偏差向量篩選需要人工標注的樣本偏差估計分支的輸出本身就是一個有用的信號。偏差向量幅度大的樣本說明模型對這條光譜的特征表示不確定很可能是訓練集里少見的類型或者儀器狀態(tài)異常的樣本。我一般會把偏差向量 L2 范數(shù)最大的前 5% 樣本挑出來優(yōu)先送人工標注。這比隨機采樣效率高得多——實測下來同樣標注 500 條按偏差篩選的能讓驗證精度多漲 3–4 個百分點。def select_samples_by_bias(model, unlabeled_loader, top_k500): 按偏差向量幅度篩選最需要標注的樣本 model.eval() bias_magnitudes [] indices [] with torch.no_grad(): for i, (flux, _) in enumerate(unlabeled_loader): # 獲取偏差分支輸出 feat model.extract_features(flux) bias model.bias_fc(feat) mag torch.norm(bias, dim1) # L2 范數(shù) bias_magnitudes.extend(mag.cpu().numpy()) indices.extend(range(i * unlabeled_loader.batch_size, i * unlabeled_loader.batch_size len(mag))) # 按偏差幅度降序排列取前 top_k sorted_idx np.argsort(bias_magnitudes)[::-1][:top_k] return [indices[i] for i in sorted_idx]extract_features需要你在模型里單獨定義返回全局池化后的特征向量。top_k根據(jù)標注預(yù)算定一般 300–1000 條一輪。每輪標注完重新訓練偏差向量會重新分布下一輪又能篩出新的難樣本。5.2 偏差估計的在線更新策略模型上線之后新數(shù)據(jù)會持續(xù)進來。如果每次新數(shù)據(jù)都重新訓練成本太高。我一般用滑動窗口加微調(diào)保留最近 3 個月的數(shù)據(jù)做微調(diào)集只更新偏差分支和分類頭的最后兩層主干凍結(jié)。這樣每次微調(diào)只需要 5–10 個 epoch單卡幾十分鐘就能跑完。微調(diào)時學習率設(shè)成初始訓練的 1/10偏差正則權(quán)重加倍。原因是新數(shù)據(jù)的偏差分布可能和訓練集不同需要更強的正則來防止偏差分支過擬合到新數(shù)據(jù)的噪聲上。5.3 一個容易被忽略的驗證指標偏差一致性除了分類精度我還會看一個指標同一顆星在不同觀測輪次下的偏差向量余弦相似度。如果同一顆星兩次觀測的偏差向量方向差異很大說明偏差估計不穩(wěn)定模型可能把隨機噪聲當成了系統(tǒng)偏差。具體做法是找那些有重復(fù)觀測的樣本計算偏差向量的余弦相似度取平均值。這個值在 0.85 以上算正常低于 0.7 就要檢查偏差分支的設(shè)計了。我踩過的坑是偏差分支用了 Tanh 激活但沒加 BatchNorm導致輸出對輸入的小擾動非常敏感余弦相似度只有 0.5 左右。加了 BatchNorm 之后直接升到 0.9。這套方案從預(yù)處理到偏差估計到主動學習整條鏈路我跑過不下十遍。最深的教訓是偏差估計不是萬能藥它只能處理系統(tǒng)性的、有結(jié)構(gòu)的偏差。如果偏差來源是隨機的信噪比波動再復(fù)雜的偏差模塊也沒用老老實實做數(shù)據(jù)清洗和信噪比篩選更實在。希望幫到你。本文還有配套的精品資源點擊獲取