在資料分析與機器學習專案中,處理缺失值往往是最關鍵的前置工作之一。特別是對於時間序列資料,如銷售資料、感測器讀數或金融指標,缺失值不僅會影響模型的準確性,更可能導致錯誤的業務決策。本文將探討各種處理時間序列缺失值的技術,並分析它們的優缺點與適用場景。
缺失值填補的關鍵考量因素
在選擇缺失值處理策略時,我們需要考慮以下幾個關鍵因素:
- 資料的時間相依性 - 時間序列資料點之間通常存在強相關性
- 缺失模式 - 隨機缺失與非隨機缺失需要不同處理策略
- 缺失區間大小 - 短期缺失與長期缺失適用不同的補值方法
- 資料的波動特性 - 高度波動的資料可能需要更複雜的插補技術
在實務中,我發現沒有一種「放諸四海皆準」的方法,選擇合適的策略需要對資料特性有深入理解,並結合業務邏輯進行判斷。
前向填充與後向填充:最直覺的補值方法
最簡單與常用的缺失值處理方法是前向填充(forward-fill)和後向填充(back-fill)。這兩種方法本質上是一種樸素的插補技術,將已知值「延伸」到缺失區域。
前向填充(Forward-Fill)基本原理
前向填充使用缺失值前的最後一個已知值來填補缺失值。這種方法假設資料在短期內具有持續性,適合處理相對穩定的時間序列。
# 使用前向填充處理缺失值
dataframe.fillna(method="ffill")
以下是應用前向填充的結果:
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 2.0 # 使用2月的值填充
2010-04-30 2.0 # 使用2月的值填充
2010-05-31 5.0
這段程式碼使用pandas的fillna()
方法,並指定method="ffill"
引數來執行前向填充。當遇到缺失值(NaN)時,它會使用該缺失值之前最近的有效值來填補。在這個例子中,3月和4月的值都被2月的銷售值(2.0)所填充。這種方法實作簡單,計算效率高,但可能會在長期缺失情況下產生不夠精確的估計。
後向填充(Back-Fill)與其應用場景
相對地,後向填充則使用缺失值後的第一個已知值來填補缺失值。這種方法在某些情境下更為合適,例如當我們認為未來的觀測值能更好地代表缺失期間的狀態時。
# 使用後向填充處理缺失值
dataframe.fillna(method="bfill")
應用後向填充的結果如下:
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 5.0 # 使用5月的值填充
2010-04-30 5.0 # 使用5月的值填充
2010-05-31 5.0
這段程式碼使用method="bfill"
引數來執行後向填充。與前向填充相反,它會使用缺失值之後的第一個有效值來填補空缺。在此例中,3月和4月的缺失值都被5月的銷售資料(5.0)所填補。後向填充在處理某些特定類別的時間序列時特別有用,例如當資料點受到未來事件影響較大時。
線性插補:平滑過渡的補值技術
當時間序列資料呈現出明顯的趨勢,或者我們有理由相信缺失值應該落在已知值之間的某個位置時,線性插補通常是一個更好的選擇。
線性插補的數學原理
線性插補的基本思想是在兩個已知資料點之間「畫一條直線」,並使用這條線上的值來填補缺失資料。數學上,它根據以下公式:
$y = y_1 + \frac{(x - x_1) \times (y_2 - y_1)}{(x_2 - x_1)}$
其中$(x_1, y_1)$和$(x_2, y_2)$是缺失值兩側的已知點。
# 使用線性插補處理缺失值
dataframe.interpolate()
線性插補的結果:
Sales
2010-01-31 1.000000
2010-02-28 2.000000
2010-03-31 3.000000 # 線性計算的值
2010-04-30 4.000000 # 線性計算的值
2010-05-31 5.000000
上面的程式碼使用pandas的interpolate()
方法進行線性插補。這是interpolate()
的預設行為,它會根據索引位置計算插值。在這個例子中,由於時間是均勻分佈的(每月一個資料點),線性插補會產生一個從2月值(2.0)到5月值(5.0)的平滑漸變,使3月和4月的值分別為3.0和4.0。這種方法在處理具有明顯線性趨勢的資料時非常有效。
線性插補的優勢與侷限性
線性插補相比前/後向填充的主要優勢在於它能創造更平滑的過渡,避免資料中的「階梯效應」。在我的實務經驗中,這種方法特別適合處理以下情境:
- 資料呈現明顯的線性趨勢
- 缺失值區間不太長
- 資料波動性較低
然而,線性插補也有其侷限性。當資料高度波動或存在季節性模式時,簡單的線性插補可能無法捕捉這些複雜模式,導致不準確的估計。
非線性插補技術:處理複雜時間序列
對於更複雜的時間序列,特別是那些呈現非線性趨勢或季節性模式的資料,我們可能需要更先進的插補方法。
二次插補與多項式插補
如果我們相信資料點之間的關係不是線性的,可以使用二次插補或其他多項式插補方法:
# 使用二次插補處理缺失值
dataframe.interpolate(method="quadratic")
應用二次插補的結果:
Sales
2010-01-31 1.000000
2010-02-28 2.000000
2010-03-31 3.059808
2010-04-30 4.038069
2010-05-31 5.000000
這段程式碼使用method="quadratic"
引數來執行二次插補。與線性插補不同,二次插補使用二次多項式來擬合資料點,這允許曲線而非直線的插值。在這個例子中,我們可以看到3月和4月的插值結果(3.059808和4.038069)與線性插補的結果(3.0和4.0)有所不同,這反映了曲線擬合的效果。二次插補在處理非線性趨勢的資料時特別有用。
限制插補範圍:處理大區塊缺失值
在實際應用中,有時候我們可能面對大區塊的缺失值,而我們不希望將插補應用於整個缺失區域。這種情況下,我們可以使用limit
和limit_direction
引數來限制插補的範圍:
# 限制插補範圍
dataframe.interpolate(limit=1, limit_direction="forward")
限制插補範圍的結果:
Sales
2010-01-31 1.0
2010-02-28 2.0
2010-03-31 3.0 # 只插補了第一個缺失值
2010-04-30 NaN # 保持缺失
2010-05-31 5.0
這段程式碼使用limit=1
引數來限制只對每個連續缺失區塊的第一個值進行插補,而limit_direction="forward"
指定了插補方向是從最後一個已知值向前推進。在這個例子中,只有3月的值被插補為3.0,而4月的值仍然保持為NaN。這種方法在處理長期缺失資料時特別有用,因為對於長期缺失,插補的可靠性通常隨著與已知值的距離增加而降低。
選擇最佳插補策略:實用建議
在選擇缺失值處理策略時,我建議遵循以下步驟:
- 分析缺失值模式 - 瞭解缺失值的分佈和可能原因
- 考慮業務邏輯 - 某些領域有特定的缺失值處理慣例
- 評估不同方法 - 可以在一個子集上測試不同方法的效果
- 結合多種技術 - 對不同類別的缺失使用不同的方法
值得注意的是,前向填充和後向填充其實可以被視為一種特殊的插補形式,它們等同於從已知值畫一條水平線來填補缺失值。這種簡單方法的一個優勢是不需要在缺失值兩側都有已知值,而大多數插補方法都有這個要求。
時間序列資料處理與影像處理:跨領域的技術視角
處理完時間序列資料的缺失值後,讓我們將視角轉向另一個資料密集型領域:影像處理。雖然看似不同,這兩個領域在資料預處理方面有許多共通之處,特別是在處理缺失或雜訊資料時。
影像處理的基礎:OpenCV 簡介
影像分類別機器學習中最令人興奮的應用領域之一。電腦識別影像中的模式和物體的能力是我們工具箱中非常強大的工具。然而,在將機器學習應用於影像之前,我們通常需要先將原始影像轉換為學習演算法可用的特徵。
為了處理影像,我們會使用開放原始碼電腦視覺函式庫penCV)。雖然有許多優秀的函式庫選擇,但OpenCV是最流行與檔案最完善的影像處理函式庫用OpenCV的最大障礙之一是安裝它。不過,如果我們使用Python 3,可以使用Anaconda的套件管理工具conda在終端中用一行程式碼裝OpenCV:
conda install --channel https://conda.anaconda.org/menpo opencv3
安裝後,我們可以透過開啟一個筆記本,匯入OpenCV並檢查版本號來確認安裝:
import cv2
cv2.__version__
這段程式碼簡單地匯入OpenCV函式庫示其版本號。在資料科學專案中,確認使用的函式庫是非常重要的步驟,因為不同版本的API可能有差異,這會影響程式碼的執行結果。在我的實務經驗中,記錄和確認環境設定是確保實驗可重複性的關鍵步驟。
載入與檢視影像
在開始影像處理前,第一步是載入影像。OpenCV提供了簡單的方法來實作這一點:
# 載入函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以灰階模式載入影像
image = cv2.imread("images/plane.jpg", cv2.IMREAD_GRAYSCALE)
若要檢視影像,可以使用Python繪圖函式庫tplotlib:
# 顯示影像
plt.imshow(image, cmap="gray"), plt.axis("off")
plt.show()
上面的程式碼使用OpenCV的imread()
函式載入影像,第二個引數cv2.IMREAD_GRAYSCALE
指定以灰階模式載入。這會將彩色影像轉換為單一通道的灰階影像,大幅簡化後續處理。plt.imshow()
用
影像銳化:提升對比與細節
銳化是影像處理中的重要技術,可以增強影像中的邊緣和細節,使整體畫面看起來更加清晰。與模糊化相反,銳化技術不是平均鄰近畫素值,而是突顯目標畫素,讓邊緣間的對比更加明顯。
卷積核銳化技術實作
銳化的核心概念是使用一種特殊設計的卷積核(kernel),該卷積核會強調中心畫素,同時減弱周圍畫素的影響。以下是使用OpenCV實作影像銳化的範例:
# 載入必要函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以灰階方式載入影像
image = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 建立銳化卷積核
kernel = np.array([
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
])
# 應用銳化處理
image_sharp = cv2.filter2D(image, -1, kernel)
# 顯示處理後的影像
plt.imshow(image_sharp, cmap="gray")
plt.axis("off")
plt.show()
這段程式碼展示瞭如何使用銳化卷積核處理影像。卷積核是一個3x3的矩陣,中心值為5,周圍的四個相鄰位置為-1,其餘為0。這種設計的原理在於:當中心值大於1時,會強調目標畫素;而周圍的負值則會減弱鄰近畫素的影響,進而強化邊緣對比度。
filter2D
函式將卷積核應用於影像,引數-1
表示輸出影像的深度與原影像相同。銳化後的影像邊緣會更加明顯,細節更加清晰,尤其適合處理略顯模糊的影像,或為後續的邊緣檢測做準備。
對比度增強:突顯影像特徵
適當的對比度對於影像分析至關重要,能讓影像中的物體和形狀更加突出。直方圖均衡化(Histogram Equalization)是一種強大的對比度增強工具,能有效地重新分配畫素強度,使影像利用更廣泛的畫素強度範圍。
灰階影像的直方圖均衡化
# 載入必要函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 載入灰階影像
image = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 應用直方圖均衡化增強對比度
image_enhanced = cv2.equalizeHist(image)
# 顯示增強後的影像
plt.imshow(image_enhanced, cmap="gray")
plt.axis("off")
plt.show()
這段程式碼直接使用OpenCV的equalizeHist
函式來增強灰階影像的對比度。直方圖均衡化的原理是重新分配影像的亮度分佈,使其盡可能地覆寫整個強度範圍(通常是0-255)。這種處理能讓原本過暗或過亮的區域變得更加清晰可辨,特別適合那些對比度不足的影像。
在影像分析任務中,如物體識別或特徵提取,均衡化處理往往是提高準確率的關鍵步驟,因為它能夠強化原本不明顯的特徵。
彩色影像的對比度增強
彩色影像的處理稍微複雜一些,我們需要轉換色彩空間,只對亮度通道進行均衡化,然後再轉換回來:
# 載入彩色影像
image_bgr = cv2.imread("images/plane.jpg")
# 轉換為YUV色彩空間
image_yuv = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2YUV)
# 只對亮度通道(Y)應用直方圖均衡化
image_yuv[:, :, 0] = cv2.equalizeHist(image_yuv[:, :, 0])
# 轉換回RGB色彩空間以顯示
image_rgb = cv2.cvtColor(image_yuv, cv2.COLOR_YUV2RGB)
# 顯示增強後的影像
plt.imshow(image_rgb)
plt.axis("off")
plt.show()
彩色影像的直方圖均衡化需要特別處理,因為直接對RGB通道分別均衡化會導致色彩失真。這裡採用的方法是先將BGR影像轉換為YUV色彩空間,其中Y代表亮度(輝度),UV代表色彩訊息。
我們只對Y通道(索引0)進行均衡化處理,保持色彩通道不變,這樣可以增強對比度的同時保留原有的色彩訊息。處理完成後,再將影像從YUV轉換回RGB以便顯示。這種方法在保留色彩的同時有效增強了影像的細節和對比度。
顏色分離:目標顏色識別與提取
在許多電腦視覺應用中,分離特定顏色是一個常見需求,例如在機器人視覺、物體追蹤或產品檢測中。OpenCV提供了強大的工具來實作顏色分離。
HSV色彩空間中的顏色遮罩技術
# 載入必要函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 載入影像
image_bgr = cv2.imread('images/plane_256x256.jpg')
# 將BGR轉換為HSV色彩空間
image_hsv = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2HSV)
# 定義HSV中藍色的範圍
lower_blue = np.array([50, 100, 50])
upper_blue = np.array([130, 255, 255])
# 建立遮罩
mask = cv2.inRange(image_hsv, lower_blue, upper_blue)
# 應用遮罩到原始影像
image_bgr_masked = cv2.bitwise_and(image_bgr, image_bgr, mask=mask)
# 轉換為RGB以顯示
image_rgb = cv2.cvtColor(image_bgr_masked, cv2.COLOR_BGR2RGB)
# 顯示結果
plt.imshow(image_rgb)
plt.axis("off")
plt.show()
這個程式碼展示瞭如何在影像中分離特定顏色。關鍵步驟包括:
- 將BGR影像轉換為HSV色彩空間,因為HSV更適合顏色分離(相比RGB)
- 定義目標顏色的HSV範圍 - 這裡設定了藍色的範圍
- 使用
inRange
函式建立二值遮罩,符合顏色範圍的區域為白色(255),其餘為黑色(0) - 使用
bitwise_and
函式將原始影像與遮罩結合,只保留遮罩中白色區域的原始顏色
HSV色彩空間的優勢在於它將顏色的色相(Hue)、飽和度(Saturation)和明度(Value)分開表示,使得定義特定顏色範圍更加直觀。找到適合的HSV範圍往往需要實驗和調整,這是顏色分離中最具挑戰性的部分。
影像二值化:簡化與雜訊去除
二值化是將灰階影像轉換為只有黑白兩種顏色的過程,這對於文字識別、形狀分析和特徵提取等任務非常有用。
自適應閾值處理技術
相較於全域閾值,自適應閾值考慮了畫素的區域性鄰域,能更好地處理光照不均的影像:
# 載入必要函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以灰階方式載入影像
image_grey = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 應用自適應閾值處理
max_output_value = 255
neighborhood_size = 99
subtract_from_mean = 10
image_binarized = cv2.adaptiveThreshold(
image_grey,
max_output_value,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY,
neighborhood_size,
subtract_from_mean
)
# 顯示二值化結果
plt.imshow(image_binarized, cmap="gray")
plt.axis("off")
plt.show()
這段程式碼使用自適應閾值將灰階影像轉換為二值影像。與全域閾值(固定一個閾值應用於整個影像)不同,自適應閾值會根據畫素周圍的鄰域決定該畫素的閾值,這使它能更好地適應影像中光照條件的變化。
adaptiveThreshold
函式的關鍵引數說明:
max_output_value
:超過閾值的畫素將被設為此值(通常是255,代表白色)cv2.ADAPTIVE_THRESH_GAUSSIAN_C
:使用高斯加權平均計算閾值,周圍畫素的權重隨距離遞減neighborhood_size
:計算閾值時考慮的鄰域大小(必須是奇數)subtract_from_mean
:從計算出的閾值中減去的常數值,用於微調結果
二值化處理可以有效去除影像中的雜訊和不必要的細節,只保留最重要的元素,非常適合文字識別、條形碼掃描等應用場景。
均值自適應閾值處理
除了高斯加權平均,我們也可以使用簡單的算術平均來計算閾值:
# 應用均值自適應閾值
image_mean_threshold = cv2.adaptiveThreshold(
image_grey,
max_output_value,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY,
neighborhood_size,
subtract_from_mean
)
# 顯示結果
plt.imshow(image_mean_threshold, cmap="gray")
plt.axis("off")
plt.show()
這個變體使用cv2.ADAPTIVE_THRESH_MEAN_C
引數,表示閾值計算採用鄰域畫素的簡單算術平均,而不是加權平均。相比高斯方法,均值法計算速度更快,但對雜訊的敏感度可能更高。
不同的自適應閾值方法和引數設定會產生不同的二值化效果。在實際應用中,需要根據具體的影像特性和任務需求進行選擇和調整。二值化是許多高階影像處理任務的前處理步驟,例如輪廓檢測、形狀分析和文字識別等。
背景移除:前景提取技術
在許多影像處理應用中,分離前景和背景是一個基本而重要的任務。OpenCV提供了GrabCut演算法,這是一種強大的前景提取工具。
使用GrabCut演算法提取前景
# 載入必要函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 載入影像並轉換為RGB
image_bgr = cv2.imread('images/plane_256x256.jpg')
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
# 定義前景區域的矩形:起始x, 起始y, 寬度, 高度
rectangle = (0, 56, 256, 150)
# 建立初始遮罩
mask = np.zeros(image_rgb.shape[:2], np.uint8)
# 建立GrabCut所需的臨時陣列
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
# 執行GrabCut演算法
cv2.grabCut(
image_rgb, # 輸入影像
mask, # 遮罩
rectangle, # 包含前景的矩形
bgdModel, # 背景的臨時陣列
fgdModel, # 前景的臨時陣列
5, # 迭代次數
cv2.GC_INIT_WITH_RECT # 使用矩形初始化
)
# 建立新遮罩:將確定和可能的背景設為0,其他設為1
mask_2 = np.where((mask==2) | (mask==0), 0, 1).astype('uint8')
# 將新遮罩應用於影像以移除背景
## 影像背景移除與特徵偵測實戰技巧
在電腦視覺領域中,影像前處理是建立強大視覺應用的基礎。從我多年開發經驗來看,背景移除、邊緣偵測與角點識別是最常用與最具價值的基礎技術。這些技巧不僅能提取影像中的關鍵訊息,還能大幅降低後續處理的複雜度。
### 遮罩應用與背景移除
在處理影像時,我們經常需要專注於特定物件而忽略背景。透過遮罩技術,我們可以精確地分離前景與背景:
```python
# 顯示遮罩
plt.imshow(mask_2, cmap='gray')
plt.axis("off")
plt.show()
上述程式碼展示了一個二值遮罩,其中白色區域(值為1)代表我們想保留的前景,黑色區域(值為0)代表我們想移除的背景。這個遮罩通常是透過合併黑色和灰色區域所建立的第二層遮罩,能夠精確標記出影像中的主體。遮罩顯示使用了灰階色彩對映(cmap='gray'
),並關閉了座標軸以便清晰觀察。
這個遮罩隨後會應用到原始影像上,只保留前景部分,達到背景移除的效果。這種技術在產品攝影、電腦視覺識別系統和影像編輯中極為實用。
邊緣偵測技術
邊緣是影像中資訊密度最高的區域,代表物體的輪廓和重要特徵。在開發視覺識別系統時,我發現邊緣偵測是降低計算複雜度同時保留關鍵資訊的絕佳方法。
# 載入函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以灰階方式載入影像
image_gray = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 計算中位數強度
median_intensity = np.median(image_gray)
# 設定閾值為中位數強度上下一個標準差
lower_threshold = int(max(0, (1.0 - 0.33) * median_intensity))
upper_threshold = int(min(255, (1.0 + 0.33) * median_intensity))
# 應用Canny邊緣偵測器
image_canny = cv2.Canny(image_gray, lower_threshold, upper_threshold)
# 顯示影像
plt.imshow(image_canny, cmap="gray")
plt.axis("off")
plt.show()
這段程式碼實作了Canny邊緣偵測器,這是電腦視覺中最常用的邊緣偵測方法之一。程式首先計算影像的中位數強度,然後設定兩個閾值:
- 下閾值:中位數強度減去33%
- 上閾值:中位數強度加上33%
這種動態閾值設定方法比固定閾值更為人工智慧夠適應不同光照條件下的影像。
Canny偵測器將低於下閾值的畫素視為非邊緣,高於上閾值的視為強邊緣,而介於兩者之間的則視為弱邊緣,只有當弱邊緣與強邊緣相連時才被保留。
在我的實際專案中,我發現這種方法對於識別物體輪廓特別有效,但有時需要針對特定影像集手動調整閾值以獲得最佳結果。
角點偵測實作
角點是兩個邊緣的交點,代表影像中資訊最密集的區域。在開發特徵比對和物體追蹤系統時,角點偵測是不可或缺的技術。
# 載入函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 載入影像
image_bgr = cv2.imread("images/plane_256x256.jpg")
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
image_gray = np.float32(image_gray)
# 設定角點偵測引數
block_size = 2
aperture = 29
free_parameter = 0.04
# 偵測角點
detector_responses = cv2.cornerHarris(image_gray,
block_size,
aperture,
free_parameter)
# 擴大角點標記
detector_responses = cv2.dilate(detector_responses, None)
# 只保留超過閾值的偵測回應,標記為白色
threshold = 0.02
image_bgr[detector_responses > threshold * detector_responses.max()] = [255, 255, 255]
# 轉換為灰階
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
# 顯示影像
plt.imshow(image_gray, cmap="gray")
plt.axis("off")
plt.show()
這段程式碼實作了Harris角點偵測器,它能夠找出影像中的角點位置。核心原理是:當視窗(鄰域)在各個方向移動時,如果畫素值變化劇烈,則該點可能是角點。
主要引數包括:
block_size
:角點偵測時每個畫素周圍的鄰域大小aperture
:Sobel運算元的孔徑大小free_parameter
:Harris偵測器的自由引數,值越大檢測到的角點越「柔和」
偵測後,我們使用膨脹操作(dilate
)放大角點標記,使其更加明顯。然後透過閾值篩選,只保留最可能的角點,並標記為白色。
在實際應用中,我發現Harris角點偵測器對於特徵比對和運動分析特別有用,但需要謹慎調整引數以避免過度偵測或漏檢。
Shi-Tomasi角點偵測:更精確的選擇
在某些應用中,我們需要更加精確地控制偵測到的角點數量。Shi-Tomasi角點偵測器提供了這種能力:
# 載入影像
image_bgr = cv2.imread('images/plane_256x256.jpg')
image_gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
# 設定要偵測的角點數量
corners_to_detect = 10
minimum_quality_score = 0.05
minimum_distance = 25
# 偵測角點
corners = cv2.goodFeaturesToTrack(image_gray,
corners_to_detect,
minimum_quality_score,
minimum_distance)
corners = np.float32(corners)
# 在每個角點繪製白色圓圈
for corner in corners:
x, y = corner[0]
cv2.circle(image_bgr, (x,y), 10, (255,255,255), -1)
# 轉換為灰階
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
# 顯示影像
plt.imshow(image_rgb, cmap='gray')
plt.axis("off")
plt.show()
Shi-Tomasi角點偵測器(透過goodFeaturesToTrack
函式實作)是Harris偵測器的改良版本。它的主要優勢在於可以精確控制偵測到的角點數量,並確保角點之間保持最小距離。
關鍵引數包括:
corners_to_detect
:要偵測的角點數量上限minimum_quality_score
:角點品質的最小閾值(0到1之間)minimum_distance
:角點之間的最小歐幾裡德距離
在影像分析和特徵追蹤方面,我發現Shi-Tomasi方法更為實用,特別是在需要限制特徵點數量的場景中。例如,在我開發的一個物體追蹤系統中,使用固定數量的高品質角點大大提高了演算法的穩定性和效率。
為機器學習建立特徵向量
將影像轉換為機器學習演算法可用的特徵向量是電腦視覺與機器學習結合的關鍵步驟。這裡我分享一個簡單但實用的方法:
# 載入影像
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以灰階方式載入影像
image = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 將影像縮放為10x10畫素
image_10x10 = cv2.resize(image, (10, 10))
# 將影像資料轉換為一維向量
feature_vector = image_10x10.flatten()
print(feature_vector)
這段程式碼展示瞭如何將影像轉換為機器學習可用的特徵向量。首先將影像縮放到較小的尺寸(10x10畫素),然後使用flatten()
函式將二維陣列轉換為一維向量。
縮放影像是降低特徵維度的有效方法,因為原始大小的影像通常擁有過多特徵,容易導致「維度災難」問題。例如,一張256x256的彩色影像展平後將有196,608個特徵!
這種簡化方法看似粗糙,但在許多實際應用中效果驚人。我曾使用類別技術開發過一個簡單的手寫數字識別系統,即使是極度縮小的影像也能保留足夠的識別訊息。
特徵維度的挑戰與解決方案
當處理大型影像時,特徵數量爆炸是一個嚴重挑戰:
# 以灰階方式載入原始大小影像
image_256x256_gray = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_GRAYSCALE)
# 將影像資料轉換為一維向量,顯示維度
print(image_256x256_gray.flatten().shape) # 輸出: (65536,)
# 以彩色方式載入影像
image_256x256_color = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_COLOR)
# 將影像資料轉換為一維向量,顯示維度
print(image_256x256_color.flatten().shape) # 輸出: (196608,)
這段程式碼展示了影像特徵維度的挑戰。即使是中等大小的256x256影像:
- 灰階影像:產生65,536個特徵
- 彩色影像:產生196,608個特徵(3通道 × 65,536)
這種高維度特徵會導致幾個問題:
- 計算成本高昂
- 需要更多訓練資料
- 容易過度擬合
- 可能遇到「維度災難」
從我的實踐經驗來看,解決這個問題的有效策略包括:
- 降維技術:使用主成分分析(PCA)或t-SNE等方法
- 特徵提取:使用HOG、SIFT或深度學習特徵提取器
- 影像縮放:如範例所示,縮小影像尺寸
- 選擇性取樣:只選取影像中最具訊息量的區域
- 使用卷積神經網路:CNN能自動學習關鍵特徵,避免手動特徵工程
應用這些技術時,關鍵是在特徵減少的同時保留識別所需的關鍵訊息。
電腦視覺特徵工程的實用
透過上述技術,我們可以構建一個完整的影像特徵工程流程:
- 預處理:裁剪、縮放、標準化影像
- 增強訊息:應用邊緣或角點偵測突出關鍵特徵
- 降維:縮小影像尺寸或應用降維演算法
- 特徵向量化:將處理後的影像轉換為一維特徵向量
- 特徵選擇:選擇最有辨別力的特徵子集
這套流程已在我的多個視覺識別專案中證明有效,特別是在計算資源有限的環境中。
電腦視覺中的特徵工程是技術與藝術的結合。瞭解不同技術的優缺點,以及何時應用它們,是開發成功視覺系統的關鍵。隨著深度學習的發展,手動特徵工
影像特徵工程與維度降維的實務技巧
在機器學習專案中,當特徵數量過多時,容易導致模型過度複雜化、效能下降,甚至出現維度災難問題。這個挑戰在處理影像資料時特別明顯,因為單一影像就可能包含數十萬個特徵。本文將探討如何從影像中提取有意義的色彩特徵,並介紹降低特徵維度的有效技術。
從影像色彩中提取特徵
影像的顏色資訊是分析與分類別重要特徵來源。我們可以透過計算平均色彩值和色彩分佈來建立代表影像的特徵向量。
使用平均色彩值作為特徵
每張影像中的畫素都由多個色彩通道(通常是紅、綠、藍三通道)組成。我們可以計算每個通道的平均值,產生代表整體影像主色調的特徵:
# 載入必要的函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 以 BGR 模式載入影像
image_bgr = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_COLOR)
# 計算各通道的平均值
channels = cv2.mean(image_bgr)
# 將 BGR 順序調換為 RGB
observation = np.array([(channels[2], channels[1], channels[0])])
# 顯示平均通道值
print(observation)
# 輸出: array([[ 90.53204346, 133.11735535, 169.03074646]])
這段程式碼從影像中提取了最基本的顏色特徵。首先,使用 OpenCV 讀取影像,預設採用 BGR 色彩順序(而非常見的 RGB)。接著,我們使用 cv2.mean()
函式運算每個通道的平均值,得到影像整體的色調資訊。由於 OpenCV 使用 BGR 順序,但大多數視覺化工具使用 RGB 順序,因此需要將通道順序調換。最終得到的 observation
是一個包含三個特徵值的向量,分別代表影像紅、綠、藍通道的平均值。
這種特徵提取方法雖然簡單,但在某些影像分類別務中非常有效,尤其是當影像的整體色調對分類別果有決定性影響時。例如,區分白天與夜晚的照片、海景與森林景觀等。
我們可以直接將這些平均值視覺化:
# 顯示影像
plt.imshow(observation)
plt.axis("off")
plt.show()
執行上述程式碼會顯示一個單色方塊,其顏色就是原始影像的平均色彩。這提供了直覺性的方式來理解影像的主色調。
使用色彩直方圖作為特徵
平均色彩值雖然簡單實用,但無法捕捉影像中色彩的分佈情況。色彩直方圖則能提供更詳細的色彩分佈資訊:
# 載入函式庫
import cv2
import numpy as np
from matplotlib import pyplot as plt
# 載入影像
image_bgr = cv2.imread("images/plane_256x256.jpg", cv2.IMREAD_COLOR)
# 轉換為 RGB
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
# 建立特徵值列表
features = []
# 計算每個色彩通道的直方圖
colors = ("r","g","b")
# 針對每個通道:計算直方圖並加入特徵值列表
for i, channel in enumerate(colors):
histogram = cv2.calcHist([image_rgb], # 影像
[i], # 通道索引
None, # 遮罩
[256], # 直方圖大小
[0,256]) # 值域範圍
features.extend(histogram)
# 建立觀測值的特徵向量
observation = np.array(features).flatten()
# 顯示觀測值前五個特徵的數值
print(observation[0:5])
# 輸出: array([ 1008., 217., 184., 165., 116.], dtype=float32)
這段程式碼建立了更為詳盡的色彩特徵。不同於只計算平均值,我們現在計算每個色彩通道的直方圖,也就是統計每個可能的色彩強度值(0-255)在影像中出現的頻率。
首先將 BGR 影像轉換為 RGB 格式,然後針對紅、綠、藍三個通道分別計算直方圖。cv2.calcHist()
函式會計算指定通道中畫素值的分佈情況,產生一個包含 256 個數值的陣列(因為每個通道有 256 個可能的值)。這些直方圖被合併成一個大型特徵向量,總共包含 768 個數值(3 通道 × 256 個值)。
這種特徵編碼方式捕捉了影像中顏色的完整分佈情況,而不僅僅是平均值。這對於區分具有相似平均顏色但色彩分佈不同的影像特別有用。例如,一張藍天白雲的照片和一張藍色海洋的照片可能有相似的平均顏色,但它們的顏色分佈(直方圖)會有明顯差異。
瞭解色彩直方圖的概念
為了更好地理解直方圖的概念,我們可以先看一個簡單的例子:
# 匯入 pandas
import pandas as pd
# 建立一些資料
data = pd.Series([1, 1, 2, 2, 3, 3, 3, 4, 5])
# 顯示直方圖
data.hist(grid=False)
plt.show()
在這個例子中,我們有一個包含 9 個數字的序列:兩個 1,兩個 2,三個 3,一個 4 和一個 5。直方圖會顯示每個數值出現的頻率,x 軸是數值,y 軸是出現次數。
我們可以將這個概念應用到影像的色彩通道上。對於每個通道,我們計算 256 個可能值(0-255)各自出現的頻率:
# 計算每個色彩通道的直方圖
colors = ("r","g","b")
# 針對每個通道:計算直方圖並繪製
for i, channel in enumerate(colors):
histogram = cv2.calcHist([image_rgb], # 影像
[i], # 通道索引
None, # 遮罩
[256], # 直方圖大小
[0,256]) # 值域範圍
plt.plot(histogram, color=channel)
plt.xlim([0,256])
# 顯示圖表
plt.show()
產生的直方圖顯示了影像中每個色彩通道值的分佈情況。例如,從藍色通道的直方圖可以看出,畫素值介於 0 到約 180 之間的藍色很少出現,而介於 190 到 210 之間的藍色值則較為常見。
這些直方圖不僅是視覺化工具,它們實際上提供了 768 個特徵值(每個通道 256 個),代表影像中顏色的分佈情況。這些特徵可以用於機器學習演算法中進行影像分類別相似度分析。
維度災難與特徵提取的必要性
當處理高維度資料時,我們常常面臨所謂的「維度災難」問題。以一張 256 × 256 畫素的彩色影像為例,它包含 196,608 個特徵(256 × 256 × 3 通道)。由於每個畫素可以有 256 種可能的值,理論上這張影像可以有 256^196608 種不同的組合。
這個數字龐大得難以想像,意味著我們永遠無法收集足夠的樣本來涵蓋所有可能的組合。在實際應用中,這導致機器學習演算法難以從有限的訓練資料中找出有效的模式,造成效能不佳或過度擬合。
特徵提取的目標是將原始特徵集 p_original 轉換為新的特徵集 p_new,使得 p_original > p_new,同時保留資料中的大部分資訊。換句話說,我們希望減少特徵數量,同時只犧牲少量的預測能力。
特徵提取的缺點在於,產生的新特徵通常不具有人類解釋性。它們包含與原始特徵相當的預測能力,但對人眼來說只是一組看似隨機的數字。如果我們希望維持模型的可解釋性,特徵選擇(而非特徵提取)可能是更好的選擇。
使用主成分分析(PCA)降低特徵維度
主成分分析(Principal Component Analysis, PCA)是一種常用的線性維度降低技術。它可以將原始特徵投影到保留最大方差的主成分上,有效減少特徵數量:
# 載入函式庫
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn import datasets
# 載入資料
digits = datasets.load_digits()
# 標準化特徵矩陣
features = StandardScaler().fit_transform(digits.data)
# 建立一個保留 99% 方差的 PCA
pca = PCA(n_components=0.99, whiten=True)
# 執行 PCA
features_pca = pca.fit_transform(features)
# 顯示結果
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_pca.shape[1])
# 輸出:
# 原始特徵數量: 64
# 降維後特徵數量: 54
PCA 是一種無監督的技術,它不使用目標向量的資訊,而是專注於特徵矩陣本身的特性。PCA 尋找資料中方差最大的方向(主成分),並將資料投影到這些方向上。
在這個例子中,我使用了 scikit-learn 的數字辨識資料集,它包含 64 個特徵(8×8 畫素的灰階影像)。在應用 PCA 之前,我們先使用 StandardScaler
對資料進行標準化,這是 PCA 的常見前處理步驟。
接著,我設定 PCA 保留 99% 的方差(n_components=0.99
),並啟用白化選項(whiten=True
),這有助於使轉換後的特徵具有相似的方差,對後續機器學習步驟有益。
結果顯示,原本的 64 個特徵被縮減為 54 個,同時仍保留了原始資料 99% 的方差。這意味著我們減少了約 16% 的特徵數量,卻只損失了 1% 的資料資訊。
PCA 的直觀理解
我們可以用一個簡單的例子來理解 PCA 的工作原理。想像一個二維資料集,資料點分佈如同一根長雪茄,有很長的長度但很小的高度。換句話說,資料在一個方向(長度)上的方差明顯大於另一個方向(高度)。
在 PCA 中,我們把具有最大方差的方向稱為第一主成分,具有第二大方差的方向稱為第二主成分。如果方差差異很大,我們可能只需保留第一主成分就能捕捉資料的大部分變異性,從而將二維資料降為一維。
在實際應用中,PCA 特別適用於:
- 高維度資料的視覺化:可以將高維資料投影到二或三維空間進行視覺化
- 資料壓縮:在保留大部分資訊的同時減少資料量
- 降噪:透過只保留主要成分,可以濾除可能代表噪音的微小方差成分
- 多重共線性處理:移除特徵之間的相關性,使特徵更加獨立
影像處理與維度降維的實務應用
在影像處理領域,維度降維技術具有廣泛的應用:
影像壓縮:使用 PCA 或其他維度降維技術可以有效壓縮影像資料,保留主要資訊的同時減少儲存空間需求。
**
降維技術的實戰應用與選擇策略
在處理高維度資料時,降維是提高模型效能與理解資料結構的關鍵技術。在我多年開發機器學習系統的經驗中,選擇適合的降維技術往往能解決模型訓練慢、過擬合和解釋性差等問題。本文將探討幾種主流降維技術的實際應用場景與實作方法。
主成分分析(PCA):基礎但強大的降維工具
主成分分析是最常用的線性降維技術,其核心思想是找出資料中變異量最大的方向。以二維資料為例,如果我們需要將資料從二維降至一維,PCA會找出資料分佈最「寬」的那個方向作為主成分,並將所有資料點投影到這個方向上。
在實務應用中,當我需要保留資料的大部分變異量同時減少特徵數量時,PCA往往是我的首選技術。讓我們看一個實際的例子:
# 載入必要的函式庫
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
# 載入資料集
digits = load_digits()
features = digits.data
# 建立PCA模型並進行擬合轉換
pca = PCA(n_components=0.99, whiten=True, svd_solver="randomized")
features_pca = pca.fit_transform(features)
# 顯示降維結果
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_pca.shape[1])
print("保留的主成分數量:", pca.n_components_)
這段程式碼展示了使用PCA進行降維的基本流程。特別注意n_components=0.99
引數設定,這表示我們希望保留99%的變異量。PCA會自動計算需要多少主成分才能達到這個保留率。
whiten=True
引數將每個主成分的值轉換為零均值與單位方差,這在後續機器學習任務中很有用。而svd_solver="randomized"
則使用隨機演算法來找出主成分,對於大型資料集能顯著提高計算效率。
處理非線性資料:核主成分分析(Kernel PCA)
在實務中,我常遇到線性方法難以處理的資料。例如,當資料的類別邊界是非線性的(如同心圓形狀),標準PCA的效果會大打折扣。這種情況下,核PCA成為更好的選擇。
核PCA的優勢在於它能將原本在低維空間中非線性可分的資料,對映到高維空間使其變得線性可分,同時仍能達到降維的目的。以下是處理非線性可分資料的例項:
# 載入必要的函式庫
from sklearn.decomposition import KernelPCA
from sklearn.datasets import make_circles
import matplotlib.pyplot as plt
# 建立非線性可分的資料
features, labels = make_circles(n_samples=1000, random_state=1, noise=0.1, factor=0.1)
# 視覺化原始資料
plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
plt.scatter(features[:, 0], features[:, 1], c=labels, cmap='viridis')
plt.title('原始二維資料')
# 應用核PCA
kpca = KernelPCA(kernel="rbf", gamma=15, n_components=1)
features_kpca = kpca.fit_transform(features)
# 視覺化降維後的資料
plt.subplot(1, 2, 2)
plt.scatter(features_kpca, np.zeros_like(features_kpca), c=labels, cmap='viridis')
plt.title('核PCA降至一維後')
plt.tight_layout()
plt.show()
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_kpca.shape[1])
這個例子展示了核PCA處理非線性資料的威力。我們使用make_circles
函式生成一個經典的非線性可分資料集,其中一個類別被另一個類別環繞。
核心是KernelPCA
的設定,我選擇了RBF核函式(kernel="rbf"
),這是處理非線性資料的常用選擇。gamma=15
引數控制RBF核的複雜度,較高的值能捕捉更細微的非線性關係。透過將二維資料降至一維(n_components=1
),核PCA仍能保持類別的可分性,這是標準PCA難以做到的。
在選擇核函式和引數時,我通常採取「嘗試與驗證」的方法,透過交叉驗證選擇最適合特定資料集的設定。常用的核函式包括:
rbf
:高斯徑向基函式,最常用poly
:多項式核函式sigmoid
:S型核函式linear
:線性核函式,等同於標準PCA
針對分類別題的降維:線性判別分析(LDA)
當我處理分類別題與希望降維時,線性判別分析(LDA)往往優於PCA。這是因為LDA不僅考慮資料的變異量,還特別關注如何最大化不同類別之間的分離度。
以下是使用LDA進行降維的例項:
# 載入必要的函式庫
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn import datasets
import numpy as np
# 載入鳶尾花資料集
iris = datasets.load_iris()
features = iris.data
target = iris.target
# 建立並執行LDA
lda = LinearDiscriminantAnalysis(n_components=1)
features_lda = lda.fit(features, target).transform(features)
# 顯示降維結果
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_lda.shape[1])
print("解釋變異量比例:", lda.explained_variance_ratio_)
# 視覺化降維結果
plt.figure(figsize=(10, 6))
colors = ['navy', 'turquoise', 'darkorange']
for color, i, target_name in zip(colors, [0, 1, 2], iris.target_names):
plt.scatter(features_lda[target == i], np.zeros_like(features_lda[target == i]),
color=color, alpha=.8, lw=2, label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('LDA降維後的鳶尾花資料')
plt.show()
這段程式碼展示了LDA應用於經典的鳶尾花資料集。LDA將原本4維的特徵(萼片長度、萼片寬度、花瓣長度、花瓣寬度)降至1維,同時最大化三個花種類別之間的分離度。
explained_variance_ratio_
屬性顯示了每個輸出特徵解釋的變異量比例。在這個例子中,單一輸出特徵解釋了超過99%的變異量,這表明LDA非常有效地保留了分類別需的資訊。
當我們需要確定要保留多少特徵時,可以分析變異量解釋比例,並選擇能解釋足夠變異量(通常是95%或99%)的最少特徵數量:
# 建立並執行LDA,不限制輸出特徵數量
lda_full = LinearDiscriminantAnalysis(n_components=None)
lda_full.fit(features, target)
# 計算累積變異量解釋比例
cumulative_variance = np.cumsum(lda_full.explained_variance_ratio_)
# 選擇能解釋至少95%變異量的特徵數量
n_components = np.argmax(cumulative_variance >= 0.95) + 1
print(f"需要{n_components}個特徵才能解釋至少95%的變異量")
如何選擇適當的降維技術
在實務應用中,我發現選擇降維技術需要考慮以下因素:
資料特性:
- 如果資料大致呈線性關係,PCA通常效果良好
- 對於非線性關係明顯的資料,核PCA往往更適合
- 若資料有明確的類別標籤與目標是分類別LDA通常優於PCA
計算資源:
- 標準PCA計算效率高,適合大型資料集
- 核PCA計算成本較高,對於特徵數量和樣本數量都有敏感性
- 對於超大型資料集,可考慮隨機化PCA(
svd_solver="randomized"
)
模型解釋性:
- PCA產生的主成分是原始特徵的線性組合,相對容易解釋
- 核PCA產生的特徵在原始空間中難以直觀解釋
- LDA產生的特徵直接與類別分離相關,在分類別題中具有良好解釋性
矩陣分解:另一種降維思路
除了上述方法,矩陣分解技術也是降維的重要工具,特別是處理非負值矩陣時。非負矩陣分解(NMF)是一種特別適合處理如文字頻率、畫素強度等非負特徵的技術。
以下是NMF的基本應用:
# 載入必要的函式庫
from sklearn.decomposition import NMF
from sklearn.datasets import fetch_20newsgroups_vectorized
# 載入新聞組資料集(已向量化的TF-IDF特徵)
data = fetch_20newsgroups_vectorized(subset='all')
features = data.data
# 建立並執行NMF
nmf = NMF(n_components=20, init='random', random_state=0)
features_nmf = nmf.fit_transform(features)
# 顯示降維結果
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_nmf.shape[1])
# 檢視每個主題的前10個特徵詞
feature_names = data.feature_names
for topic_idx, topic in enumerate(nmf.components_):
top_features_idx = topic.argsort()[:-11:-1]
top_features = [feature_names[i] for i in top_features_idx]
print(f"主題 #{topic_idx}: {' '.join(top_features)}")
這段程式碼展示了NMF在文字分析中的應用。NMF將檔案-詞彙矩陣分解為兩個非負矩陣的乘積:一個檔案-主題矩陣和一個主題-詞彙矩陣。
n_components=20
設定了我們希望提取的主題數量。每個主題是詞彙的非負線性組合,而每個檔案則可表示為這些主題的非負線性組合。這種分解特別適合處理大型稀疏矩陣,如文字分析中的TF-IDF矩陣。
NMF的優勢在於它產生的結果具有直觀的可解釋性。在文字分析中,每個元件可以解釋為一個「主題」,其中權重最高的詞彙共同定義了主題的含義。
降維技術的實用提示
根據我在多個專案中的經驗,以下是一些使用降維技術的實用建議:
預處理的重要性:在應用降維前,確保資料已經過標準化或歸一化,特別是特徵尺度差異大時。
解決維度災難:當特徵數量遠大於樣本數量時,降維能有效緩解「維度災難」問題,提高模型的泛化能力。
特徵選擇與降維的結合:有時將特徵選擇與降維結合使用效果更佳,例如先使用特徵選擇去除無關特徵,再用PCA降低剩餘特徵的相關性。
視覺化輔助:降至2或3維後,視覺化結果能幫助我們直觀理解資料結構和潛在模式。
迭代調整:降維是一個迭代過程,需要根據模型效能和業務需求不斷調整引數和方法。
降維技術是資料科學家和機器學習工程師必備的工具。掌握這些技術及其適用場景,能幫助我們更有效地處理高維資料,構建更高效、更準確的機器學習模型。在實際應用中,選擇合適的降維技術往往需要考慮資料特性、模型需求和計算資源等多方面因素,並透過實驗比較不
特徵降維:從高維到低維的資料轉換之旅
特徵降維是機器學習中不可或缺的技術,它讓我們能夠在保留資料關鍵訊息的同時大幅減少特徵數量。在處理高維資料時,降維不僅可以加速模型訓練,還能避免維度災難,提升模型泛化能力。從我多年實踐經驗來看,選擇合適的降維技術對模型效能有著決定性影響。
特徵降維主要分為兩大類別特徵提取和特徵選擇。特徵提取建立全新的特徵集,而特徵選擇則專注於從原始特徵中選出最有價值的子集。今天,玄貓要帶大家深入瞭解兩種強大的特徵提取技術:非負矩陣分解(NMF)與截斷奇異值分解(TSVD)。
使用非負矩陣分解(NMF)降低特徵維度
非負矩陣分解的基本原理
非負矩陣分解是一種將高維非負矩陣分解為兩個低維非負矩陣的技術。這種方法特別適合處理那些自然含義上不應有負值的資料,如影像畫素值、文字詞頻等。
NMF的核心思想是:將原始特徵矩陣V(d×n)分解為兩個矩陣的乘積W(d×r)和H(r×n),其中r遠小於d,這樣就實作了降維。數學表示為:
V ≈ WH
其中V是原始特徵矩陣(d個特徵,n個樣本),W和H分別代表特徵與潛在因子、潛在因子與樣本之間的關係矩陣。
實戰案例:使用NMF降維數字影像資料
讓我們透過實際案例來理解NMF的應用。以下是使用scikit-learn實作NMF降維的完整流程:
# 載入必要的函式庫
from sklearn.decomposition import NMF
from sklearn import datasets
# 載入手寫數字資料集
digits = datasets.load_digits()
# 取得特徵矩陣
features = digits.data
# 建立、訓練並應用NMF模型
nmf = NMF(n_components=10, random_state=1)
features_nmf = nmf.fit_transform(features)
# 顯示結果
print("原始特徵數量:", features.shape[1])
print("降維後特徵數量:", features_nmf.shape[1])
這段程式碼展示瞭如何使用NMF將64維的手寫數字影像資料降至10維。讓我們逐步解析:
- 首先匯入必要的函式庫NMF
用於執行非負矩陣分解,
datasets`用於載入內建資料集 - 載入手寫數字資料集,這是一個包含0-9手寫數字的經典資料集,每個數字由8×8畫素組成
- 取得特徵矩陣
features
,這是一個包含所有數字影像畫素值的矩陣 - 建立NMF模型,設定
n_components=10
表示我們希望將原始64維特徵降至10維 - 呼叫
fit_transform
方法同時完成模型訓練和資料轉換 - 最後列印結果,確認特徵數量已從64減少到10
執行結果會顯示:
原始特徵數量: 64
降維後特徵數量: 10
NMF的優勢與限制
從我在實際專案中的應用經驗來看,NMF具有以下幾點優勢:
- 可解釋性強:分解後的矩陣W和H具有實際含義,在某些應用(如主題建模)中特別有用
- 保留非負性:對於本質上非負的資料(如影像、音訊、文字)保留了資料的物理意義
- 發現潛在模式:能夠發現資料中的隱藏模式和結構
然而,NMF也存在一些限制:
- 僅適用於非負資料:輸入矩陣必須是非負的,這限制了其應用範圍
- 缺乏解釋變異量的能力:與PCA不同,NMF不提供每個元件解釋的變異量訊息
- 收斂時間:在大規模資料上可能收斂較慢
在選擇最佳的n_components
引數時,由於NMF不提供解釋變異量訊息,最好的方法是嘗試不同值,並選擇在下游任務中表現最好的那個。我通常會將n_components
作為超引數納入交叉驗證過程中進行最佳化
使用截斷奇異值分解(TSVD)處理稀疏特徵
為什麼需要專門處理稀疏資料?
在許多實際應用中,我們經常遇到稀疏資料——大部分元素為零的資料矩陣。例如,文字的詞袋錶示、推薦系統中的使用者物品互動矩陣等。這類別料若用常規方法處理,不僅計算效率低,還可能導致數值不穩定。
截斷奇異值分解(TSVD)是專門為處理稀疏矩陣設計的降維技術,它是標準奇異值分解(SVD)的變體。
TSVD的技術原理
標準SVD將矩陣分解為三個矩陣的乘積:U、Σ和V^T。其中Σ是對角矩陣,包含奇異值。TSVD則只保留前k個最大的奇異值及其對應的奇異向量,從而實作降維。
TSVD的數學表示式為: A ≈ U_k Σ_k V_k^T
其中A是原始矩陣,U_k、Σ_k和V_k^T分別是截斷後的矩陣。
實戰案例:對稀疏矩陣進行降維
以下是使用TSVD對稀疏矩陣進行降維的完整實作:
# 載入必要的函式庫
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import TruncatedSVD
from scipy.sparse import csr_matrix
from sklearn import datasets
import numpy as np
# 載入數字資料集
digits = datasets.load_digits()
# 標準化特徵矩陣
features = StandardScaler().fit_transform(digits.data)
# 建立稀疏矩陣
features_sparse = csr_matrix(features)
# 建立TSVD模型
tsvd = TruncatedSVD(n_components=10)
# 對稀疏矩陣進行TSVD
features_sparse_tsvd = tsvd.fit(features_sparse).transform(features_sparse)
# 顯示結果
print("原始特徵數量:", features_sparse.shape[1])
print("降維後特徵數量:", features_sparse_tsvd.shape[1])
這段程式碼演示瞭如何使用TSVD對稀疏矩陣進行降維處理:
- 匯入必要的函式庫StandardScaler
用於標準化資料,
TruncatedSVD實作截斷SVD演算法,
csr_matrix`用於建立壓縮稀疏行矩陣 - 載入數字資料集,並使用
StandardScaler
對特徵進行標準化處理 - 將標準化後的特徵矩陣轉換為稀疏矩陣格式(雖然此例中資料不是真正的稀疏矩陣,但這一步驟在實際應用中很重要)
- 建立TSVD模型,設定保留10個成分
- 對稀疏矩陣進行TSVD,注意這裡分開呼叫了
fit
和transform
- 列印結果,顯示特徵數量從64減少到10
執行結果:
原始特徵數量: 64
降維後特徵數量: 10
TSVD與PCA的關係及其優勢
TSVD與PCA關係密切,實際上PCA常常在內部使用SVD來實作。它們的主要區別在於:
- TSVD可以直接應用於稀疏矩陣,而標準PCA不行
- TSVD不要求對資料進行中心化處理
- 在計算效率上,對於稀疏矩陣,TSVD通常比PCA更高效
TSVD的一個技術細節是,由於其使用隨機數生成器,每次擬合可能導致輸出的符號發生翻轉。為避免這個問題,最佳實踐是在預處理管道中只使用一次fit
,然後多次使用transform
。
如何選擇最佳成分數量?
選擇最佳成分數量是應用TSVD時的關鍵問題。有兩種常用方法:
- *超引數最佳化:將
n_components
作為超引數,在模型選擇過程中最佳化2. 解釋變異量:選擇能解釋原始資料一定比例變異量的成分數量
對於第二種方法,TSVD提供了explained_variance_ratio_
屬性,可以檢視每個成分解釋的變異量比例:
# 檢視前三個成分解釋的變異量比例之和
tsvd.explained_variance_ratio_[0:3].sum()
輸出結果約為0.3,表示前三個成分解釋了原始資料約30%的變異量。
為了自動化選擇過程,可以編寫一個函式來計算解釋指定比例變異量所需的成分數量:
# 建立並執行TSVD,設定成分數為原特徵數減1
tsvd = TruncatedSVD(n_components=features_sparse.shape[1]-1)
features_tsvd = tsvd.fit(features)
# 取得解釋變異量比例列表
tsvd_var_ratios = tsvd.explained_variance_ratio_
# 建立函式運算所需的成分數量
def select_n_components(var_ratio, goal_var):
# 初始化已解釋的變異量
total_variance = 0.0
# 初始化成分數量
n_components = 0
# 遍歷每個成分的解釋變異量
for explained_variance in var_ratio:
# 累加解釋變異量
total_variance += explained_variance
# 成分數加1
n_components += 1
# 如果達到目標解釋變異量
if total_variance >= goal_var:
# 結束迴圈
break
# 回傳所需的成分數量
return n_components
# 執行函式,目標解釋95%的變異量
select_n_components(tsvd_var_ratios, 0.95)
這個函式會回傳40,表示需要40個成分才能解釋原始資料95%的變異量。