個人原始文檔:https://www.yuque.com/lart/blog/lwgt38python
書接上文《我是如何使計算時間提速25.6倍》.git
上篇文章提到, F-measure使用累計直方圖能夠進一步加速計算, 可是E-measure卻沒有改出來. 在寫完上篇文章的那個晚上, 從新整理思路後, 我彷佛想到了如何去使用累計直方圖來再次提速.github
雖然使用"解耦"的思路能夠高效優化每個閾值下指標的計算過程, 可是總體的 for
循環確實仍然會佔用較大的時間. 又考慮到各個閾值下的計算實際上並沒有太大關聯, 若是能夠實現同時計算, 那必然能夠進一步提高速度. 這裏咱們又要把目光放回到在計算F-measure時大放光彩的累計直方圖的策略上.數組
在前面的解耦以後, 實際上得到的關鍵變量是 fg_fg_numel
和 fg_bg_numel
.app
fg_fg_numel = np.count_nonzero(binarized_pred & gt) fg_bg_numel = np.count_nonzero(binarized_pred & ~gt)
從這兩個變量自己入手, 若是使用累計直方圖的話, 實際上能夠同時得到 >=不一樣閾值
下的前景像素(值爲1)的數量, 計算的本質和 np.count_nonzero
是同樣的東西. 因此咱們能夠進行直觀的替換:函數
""" 函數內部變量命名規則: pred屬性(前景fg、背景bg)_gt屬性(前景fg、背景bg)_變量含義 若是僅考慮pred或者gt,則另外一個對應的屬性位置使用`_`替換 """ fg_fg_hist, _ = np.histogram(pred[gt], bins=bins) fg_bg_hist, _ = np.histogram(pred[~gt], bins=bins) fg_fg_numel_w_thrs = np.cumsum(np.flip(fg_fg_hist), axis=0) fg_bg_numel_w_thrs = np.cumsum(np.flip(fg_bg_hist), axis=0)
這樣咱們就得到了不一樣閾值下的對應的一系列 fg_fg_numel
和 fg_bg_numel
了. 這裏須要注意的是, 使用的劃分區間 bins
的設置. 因爲默認的 histogram
劃分的區間會包含最後一個端點, 因此比較合理的劃分是 bins = np.linspace(0, 256, 257)
, 這樣最後一個區間是 [255, 256]
, 就能夠包含到最大的值, 又不會和 254
重複計數.測試
爲了便於計算, 這裏將後面會用到的 pred
前景統計 fg___numel_w_thrs
和背景統計 bg____numel_w_thrs
直接寫出來, 便於使用:優化
fg___numel_w_thrs = fg_fg_numel_w_thrs + fg_bg_numel_w_thrs bg___numel_w_thrs = self.gt_size - fg___numel_w_thrs
後面的步驟和以前的基本一致, numpy的廣播機制使得不須要改動太多. 因爲這部分代碼實際上再多處位置會被使用, 因此提取成一個單獨的方法.ui
def generate_parts_numel_combinations(self, fg_fg_numel, fg_bg_numel, pred_fg_numel, pred_bg_numel): bg_fg_numel = self.gt_fg_numel - fg_fg_numel bg_bg_numel = pred_bg_numel - bg_fg_numel parts_numel = [fg_fg_numel, fg_bg_numel, bg_fg_numel, bg_bg_numel] mean_pred_value = pred_fg_numel / self.gt_size mean_gt_value = self.gt_fg_numel / self.gt_size demeaned_pred_fg_value = 1 - mean_pred_value demeaned_pred_bg_value = 0 - mean_pred_value demeaned_gt_fg_value = 1 - mean_gt_value demeaned_gt_bg_value = 0 - mean_gt_value combinations = [ (demeaned_pred_fg_value, demeaned_gt_fg_value), (demeaned_pred_fg_value, demeaned_gt_bg_value), (demeaned_pred_bg_value, demeaned_gt_fg_value), (demeaned_pred_bg_value, demeaned_gt_bg_value) ] return parts_numel, combinations
後面計算 enhanced_matrix_sum
的部分也就瓜熟蒂落比較天然的能夠寫出來:spa
parts_numel_w_thrs, combinations = self.generate_parts_numel_combinations( fg_fg_numel=fg_fg_numel_w_thrs, fg_bg_numel=fg_bg_numel_w_thrs, pred_fg_numel=fg___numel_w_thrs, pred_bg_numel=bg___numel_w_thrs, ) # 這裏雖然能夠使用列表來收集各個results_part,可是列表以後還須要再轉爲numpy數組來求和,倒不如直接一次性申請好空間後面直接裝入便可 results_parts = np.empty(shape=(4, 256), dtype=np.float64) for i, (part_numel, combination) in enumerate(zip(parts_numel_w_thrs, combinations)): align_matrix_value = 2 * (combination[0] * combination[1]) / \ (combination[0] ** 2 + combination[1] ** 2 + _EPS) enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 results_parts[i] = enhanced_matrix_value * part_numel enhanced_matrix_sum = results_parts.sum(axis=0)
主要邏輯已經搞定, 接下來就是將這些代碼與原始的代碼融合起來, 也就是整合原始代碼的 cal_em_with_threshold
和 cal_enhanced_matrix
兩個方法.
def cal_em_with_threshold(self, pred: np.ndarray, gt: np.ndarray, threshold: float) -> float: binarized_pred = pred >= threshold if self.gt_fg_numel == 0: binarized_pred_bg_numel = np.count_nonzero(~binarized_pred) enhanced_matrix_sum = binarized_pred_bg_numel elif self.gt_fg_numel == self.gt_size: binarized_pred_fg_numel = np.count_nonzero(binarized_pred) enhanced_matrix_sum = binarized_pred_fg_numel else: enhanced_matrix_sum = self.cal_enhanced_matrix(binarized_pred, gt) em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) return em
結合前面代碼中計算出的各個閾值下的前背景元素的統計值, 上面這裏的代碼實際上能夠經過使用現有運算結果進行化簡, 即 if
的前兩個分支. 另外閾值劃分也不須要顯式處理, 由於已經在累計直方圖中搞定了. 因此這裏的代碼對於動態閾值計算的狀況下, 是能夠被合併到 cal_enhanced_matrix
的計算過程當中的. 直接獲得最終的整合後的方法:
def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: """ 函數內部變量命名規則: pred屬性(前景fg、背景bg)_gt屬性(前景fg、背景bg)_變量含義 若是僅考慮pred或者gt,則另外一個對應的屬性位置使用`_`替換 """ pred = (pred * 255).astype(np.uint8) bins = np.linspace(0, 256, 257) fg_fg_hist, _ = np.histogram(pred[gt], bins=bins) fg_bg_hist, _ = np.histogram(pred[~gt], bins=bins) fg_fg_numel_w_thrs = np.cumsum(np.flip(fg_fg_hist), axis=0) fg_bg_numel_w_thrs = np.cumsum(np.flip(fg_bg_hist), axis=0) fg___numel_w_thrs = fg_fg_numel_w_thrs + fg_bg_numel_w_thrs bg___numel_w_thrs = self.gt_size - fg___numel_w_thrs if self.gt_fg_numel == 0: enhanced_matrix_sum = bg___numel_w_thrs elif self.gt_fg_numel == self.gt_size: enhanced_matrix_sum = fg___numel_w_thrs else: parts_numel_w_thrs, combinations = self.generate_parts_numel_combinations( fg_fg_numel=fg_fg_numel_w_thrs, fg_bg_numel=fg_bg_numel_w_thrs, pred_fg_numel=fg___numel_w_thrs, pred_bg_numel=bg___numel_w_thrs, ) results_parts = np.empty(shape=(4, 256), dtype=np.float64) for i, (part_numel, combination) in enumerate(zip(parts_numel_w_thrs, combinations)): align_matrix_value = 2 * (combination[0] * combination[1]) / \ (combination[0] ** 2 + combination[1] ** 2 + _EPS) enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 results_parts[i] = enhanced_matrix_value * part_numel enhanced_matrix_sum = results_parts.sum(axis=0) em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) return em
仍是爲了重用, cal_em_with_threshold
(該方法須要保留, 由於還有另外一種E-measure的計算狀況須要用到該方法)能夠被重構:
def cal_em_with_threshold(self, pred: np.ndarray, gt: np.ndarray, threshold: float) -> float: """ 函數內部變量命名規則: pred屬性(前景fg、背景bg)_gt屬性(前景fg、背景bg)_變量含義 若是僅考慮pred或者gt,則另外一個對應的屬性位置使用`_`替換 """ binarized_pred = pred >= threshold fg_fg_numel = np.count_nonzero(binarized_pred & gt) fg_bg_numel = np.count_nonzero(binarized_pred & ~gt) fg___numel = fg_fg_numel + fg_bg_numel bg___numel = self.gt_size - fg___numel if self.gt_fg_numel == 0: enhanced_matrix_sum = bg___numel elif self.gt_fg_numel == self.gt_size: enhanced_matrix_sum = fg___numel else: parts_numel, combinations = self.generate_parts_numel_combinations( fg_fg_numel=fg_fg_numel, fg_bg_numel=fg_bg_numel, pred_fg_numel=fg___numel, pred_bg_numel=bg___numel, ) results_parts = [] for i, (part_numel, combination) in enumerate(zip(parts_numel, combinations)): align_matrix_value = 2 * (combination[0] * combination[1]) / \ (combination[0] ** 2 + combination[1] ** 2 + _EPS) enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 results_parts.append(enhanced_matrix_value * part_numel) enhanced_matrix_sum = sum(results_parts) em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) return em
使用本地的845張灰度預測圖和二值mask真值數據進行測試比較, 從新跑了一遍, 整體時間對好比下:
方法 | 整體耗時(s) | 速度提高(倍) |
---|---|---|
'base' | 539.2173762321472s | x1 |
'best' | 19.94518733024597s | x27.0 (539.22/19.95) |
'cumsumhistogram' | 3.2935903072357178s | x163.8 (539.22/3.29) |
仍是那句話, 雖然具體時間可能還受硬件限制, 可是相對快慢仍是比較明顯的.
測試代碼可見個人 github
: https://github.com/lartpang/CodeForArticle/tree/main/sod_metrics