從皮膚科、眼科、病理科的有趣例子出發,認識醫療影像的模型訓練流程、資料集建構、可能面臨的挑戰與各自因應的解決之道。
電腦視覺 Computer Vision 輔助疾病偵測・學習筆記

皮膚科醫師需要評估「痣」是否為皮膚癌的可能性,像黑色素瘤 Melanoma 如果在比較晚期才發現,五年存活率會大大地降低;以 129,000 張痣的照片加上是否為癌症的標籤作為輸入,用 CNN 可以訓練出判斷照片中皮膚上的痣癌化機率的模型,再讓皮膚科醫師和電腦來檢查一組新的照片,比較誰的預測結果較佳,像這篇 Nature 的研究發現演算法表現得和皮膚科醫師一樣好。


眼科醫師需要評估「眼底」是否有糖尿病視網膜病變 Diabetic Retinopathy,早期介入以避免病人失明,訓練有素的專科醫師需要親自花許多時間來判讀影像;以 128,000 張眼底的照片加上是否有糖尿病視網膜病變的標籤作為輸入(只有 30% 的照片有病變,醫學上常見的資料不平衡 Data Imbalance),用 CNN 可以訓練出判斷照片中眼底是否發生糖尿病視網膜病變的模型,再讓一群眼科專家的共識和電腦來檢查一組新的照片,比較誰的預測結果較佳,像這篇 Eye 的研究發現演算法表現得和眼科醫師一樣好。

病理科醫師需要評估顯微鏡底下的「組織切片」Whole-Slide Image 中癌症擴散的程度,例如有沒有轉移到淋巴結,幫助臨床醫師判斷疾病的奇別、規劃治療、評估預後。2017 年一篇只用了 270 張片子訓練出的演算法,看片能力和病理科醫師居然不相上下。順道一提,高解析度的組織學數位影像檔通常很大且佔空間,研究通常會在高倍率下把原檔切割成不同的視野,沿用原本的 Label,再餵到模型做深度學習,因此即使只有 270 張片子,被切割後還是能有足夠的資料來使用。

看完了 3 個有趣的例子,我們來瞧瞧如何建立一個能透過醫療影像來偵測多種疾病的深度學習模型。
首先,是最常見、一年大概有 20 億張被產出的醫學影像之一:胸部 X 光片,從中我們會檢視是否有肺炎、肺癌、心臟肥大等問題。以判斷是否片子中有腫塊(Mass,胸部 X 光片中看到直徑超過 3 公分的組織病灶)為例,我們「教」演算法學習辨別的方式,就是先給它一組標記有腫塊、另一組標記沒腫塊的胸部 X 光片,但不會告訴它特定的規則,接著給它一張沒看過的胸部 X 光片,讓演算法去「想」這張新圖的特徵是否跟之前看過的那兩組照片中,哪一組特別相像類似,像是腫塊組會多出一坨白白的、非腫塊組沒有,因而有較高的信心判斷這張片子也有腫塊。


具體的訓練方式,是我們讓演算法看標記為「腫塊(Mass)」與「正常(Normal)」的胸部 X 光片,其實跟我們人類醫師學看片時蠻像的,只是沒有老師或書本跟我們逐條練判讀口訣或重點提示。演算法學習之後,當它看到新的胸部 X 光片,會輸出「是否出現腫塊」的機率預測;訓練前演算法基本上是用猜的,跟正確答案相差甚遠,舉例來說,有腫塊出現的機率是 1、沒有腫塊是 0,最開始模型是隨意猜測它所看到的片子有沒有腫塊,機率大概接近 0.5,錯得蠻離譜的吧?這時,透過計算損失函數(Loss Function)來量測演算法算出的機率和實際 Label 之間的落差,反覆訓練後,模型所算出的機率會逐漸逼近真實資料的答案,通常會用到數萬到數十萬張的片子來做模型的訓練。


就像深度學習能判斷照片中是不是企鵝,我們也能用機器學習來評估胸部 X 光片是否有腫塊的出現。和企鵝不一樣的是,醫療影像的模型訓練會面臨三大挑戰,也有各自因應的解決之道:
1️⃣ 類別比例不平衡 Class Imbalance 👉 Weighted Loss / Resampling

以胸部 X 光片來說,通常 100 張片子裡才好不容易看到 1 張有腫塊的,也就是「有病」和「沒病」的比例是不對稱的,和疾病的盛行率(Prevalence)有關,盛行率越低,罹病的人數越少,健康的人越多。這會讓模型的學習效果變差,因為它幾乎都在學正常的片子,像我們寫考古題時發現以前 100 題裡只有 1 題的答案是「否」,那只要今年的考題沒有變化太多,隨便猜「否」大概也能考高分。

為了改善這個問題,我們可以從 Loss Function 來下手;針對 Output 介在 0 到 1 的模型,可用 Binary Cross-Entropy Loss 來評估分類的表現。比如說,給模型看一張有腫塊(Ground Truth = y = 1)的片子 X1,我們的演算法預測有腫塊的機率是 0.2,這時 Loss Function 就要用 y = 1 的那一行來算 -log*P(0.2) = 0.70;再給模型看一張沒有腫塊(Ground Truth = y = 0)的片子 X2,我們的演算法預測有腫塊的機率是 0.7,意即沒有腫塊的機率是 0.3,這時 Loss Function 就要用 y = 0 的那一行來算 -log*P(0.3) = 0.52。

如果手上有好幾張片子,要怎麼使用 Loss Function 呢?假設我們有 8 個病人的片子,其中 2 位是有腫塊的,在訓練開始之前,模型基本上都隨便亂猜每張片子有 0.5 的機率出現腫塊。因為有和沒有腫塊的預測機率都是 0.5,代進去 Loss Function 會發現算出來的值是一樣的 0.3,而對於總損失來說,有腫塊的兩個病人貢獻了 0.3 * 2 = 0.6,沒有腫塊的六個病人貢獻了 0.3 * 6 = 1.8,可見沒有腫瘤的病例貢獻得比有腫塊的病例還來得多,因此演算法會以辨識出沒有腫塊的片子作為優化的方向,但不太會在有腫塊的病例上增加權重,導致分類腫塊的表現不佳。

要解決類別不平衡的問題,一個方式 Weighted Loss 是改變損失函數對於正常和腫塊組別的權重,分別為 Wn 和 Wp。為了讓正常和腫塊組對整體損失的貢獻相等,我們把權重比例取人數的倒數,像是正常 6 人、腫塊 2 人,比例是 3:1,權重則讓兩個組別的比例是 1:3,在分母為兩組人數的總和,分子則放對方組別的人數,本例為 2 / (2 + 6) 和 6 / (2 + 6),將 Wn 以 2 / 8、Wp 以 6/ 8 代入,算出兩組的 Loss 都是 0.45。


另一個方式 Resampling 讓我們可以把兩個類別的病例數變成一樣多,先將兩類的病例放在一起,再從兩組的母群體中各自選出總數一半的樣本,正常組從 6 名病例中隨機挑選 4 名,腫塊組則需要複製片子才能滿足 4 個病例的數量。用這個方法,損失函數的權重不需調整,也能得到兩組相同的 Loss 都是 1.2。


2️⃣ 多重任務 Multi-Task 👉 Multi-Label Loss
剛剛我們看的是演算法能否辨識胸部 X 光片是否出現腫瘤,但現實臨床工作中,除了腫塊的有無,還要判斷有沒有感染肺炎、積水、肺水腫等病灶,當然可以各自寫一個偵測肺炎、積水、肺水腫的模型來輔助判斷,但很可能耗費運算資源且沒有效率,何不讓演算法和臨床醫師一樣,學習多種疾病特徵後,也能一眼就看出這張片子有沒有出現這些問題呢?

辨識腫塊只需要 1 個標籤,在多工模式下我們要針對每個疾病或問題都各自提供標註好的 Ground Truth,同樣 0 代表沒病、1 則為有病,模型會分別對每個臨床問題做出預測,以 0 到 1 的機率來表示;損失函數也需要對每個疾病做計算,最後再加總成多重標籤或多工損失 Multi-Label / Multi-Task Loss,每個疾病也同樣能援引 Weighted Loss 來處理各自類別病例不平均的問題。


3️⃣ 資料集大小 Dataset Size 👉 Transfer Learning + Data Augmentation
2D(如 X 光)、3D(如 CT 電腦斷層)、訊號(如心電圖)等醫療影像分類的問題,會用卷積神經網路(Convolutional Neural Network, CNN)的架構處理,包含常見由不同 Building Block 建構而成的 Inception、ResNet、DenseNet、ResNeXt、EfficientNets,我們通常會跑許多 Model 並找出表現最好的來使用。

大家已經知道,要訓練好影像分類的模型,資料的數量及品質非常重要,像區分圖中是否出現企鵝用上了幾百萬張有標註過的照片,但醫療影像經常只有近萬或近十萬張標註過的資料,要怎麼解決資料太少的問題呢?一個方法是「遷移學習」,首先預訓練 Pretraining 神經網路,譬如先用幾百萬張企鵝照片訓練好分類企鵝的演算法作為學習醫療影像分類的基礎。像是要區分圖中有沒有企鵝,模型要去「看」圖中物體的邊緣,這個能力可以用在「看」X 光片中肺的邊界,因此能幫助它進一步學習判斷疾病的有無,根據新任務的挑戰去進化能力,稱作「精細調整 Fine-Tuning」。

神經網絡通常有許多層,比較前面的層數捕捉的通常是廣泛一般的低階特徵,後面的層數則會去看細節、和特定任務相關的高階特徵,像區分企鵝身體邊緣、辨識企鵝的頭都算是高階特徵辨識能力,不過找到企鵝的頭對胸部 X 光片判讀沒有太大的幫助,因此我們可以凍結前面比較一般性或部分高階特徵辨識的層數,只要精細調整最後一層或最後幾層就好,這在處理訓練資料集數量較小的情境下是個不錯的選擇。

另一個方法是增幅資料 Data Augmentation,「騙」演算法說我們有比手上資料數量更多的影像資料,像是對一張片子做旋轉、平移、放大、縮小、調整對比或明暗度,也可以把這些手法相互組合,產出更多張片子後再餵給演算法。

使用資料增幅時,我們要問自己兩個問題:(1)我們轉換原資料的方式,是否能類推到臨床場域或測試資料集來使用?譬如,真實世界中拍片的對比度常常變異很大,因此我們才選擇用調整對比度的方式創造出新的一張片子。(2)轉換後的資料,在分類上是否還是和轉換前的資料相同?譬如把胸部 X 光片左右倒轉,心臟會跑到右側,這時影像的標註就會變成 Dextrocardia 心臟轉位,不再是原本的 Normal 正常,但原則上我們還是希望資料的標註在轉換後能維持和原本一樣。

除了 X 光片之外,旋轉、左右翻轉、裁切、調整組織染色後顏色對比與雜訊等轉換的方式,對皮膚照片、病理切片的模型訓練是蠻受用的。

準備好資料集後,我們通常會切成訓練 Training 和測試 Testing 集(別稱 Holdout / Validation),訓練資料用來開發及篩選出適合的模型,測試集則為最終模型表現的評估結果。再更仔細地來講,訓練資料又再被切成訓練(別稱 Development)及驗證 Validation(別稱 Tuning / Depth)的子集,前者是讓模型學習、後者則是用於調整超參數並預測測試資料的預測表現,這個切分的過程在 Cross Validation 會做許多次以減少變異。在建構這些資料集時,我們依然可能會面臨三大挑戰,分別是(1)如何讓測試資料集獨立於訓練資料、(2)抽樣方法、(3)設定正確答案 Ground Truth 的方式。

1️⃣ 如何讓測試資料集獨立於訓練資料
首先來討論病人重複性的狀況,舉例來說,一位病人在 6 月和 11 月都照了一張胸部 X 光片,在拍片子的時候都戴著項鍊。結果一張被餵到訓練集、另一張則是測試資料,我們訓練後的深度學習演算法也正確地預測了測試集的該張片子是正常的,但問題在於模型很可能只是不經意地「背」下看到片子裡有項鍊的罕見或獨特特徵,就跟我們看到選擇題選項裡出現「一定」、「絕對」就不要選一樣(?)但可能會高估模型的表現,因為它並沒有真正地學習怎麼看胸部 X 光片,只是有點無腦地背答案。

回應這個問題的方式之一,就是把同一位病人的資料都整理在某個資料集,即使訓練集中某病人出現獨特的特徵,因為測試集的片子中並不會看到這個病人的影像,預測的表現不會受到影響。

2️⃣ 抽樣方法
測試集的資料數量,有時會設定在資料總量的 10%,如果需要專家來標註正確答案,少說也要幾百筆影像檔案,但問題有時是出在原本總樣本數較小、或疾病病例不多的資料庫,隨機從中撈出來的測試集資料,都是正常而沒有罹病的,導致無法探究模型在辨識上的表現。
一種處理方式,是預先設定好每種類別要有 X% 的比例,像是要保證測試集資料中每 100 張片子至少有 X 張是有病的;常見的 X 是訂在 50,也就是一半。這麼做能確保我們有足量的影像來做演算法對於有病和沒病影像預測表現的評估。

測試集準備好以後,下一步是驗證的資料集,最後才是訓練集;因為我們會期待驗證資料的分佈近似於測試集,X% 的設定大多和訓練集是相同的。剩下的資料就會放到訓練集裡,但因為有病的影像蠻多都已經在驗證和測試集了,訓練集的非正常病例會變得很少。

3️⃣ 設定正確答案 Ground Truth / Reference Standard 的方式
第三個醫療影像機器學習資料集建構常遇到的問題,是決定正確標註的方式。針對同一張胸部 X 光片,胸腔科醫師可能覺得是肺炎,另一位影像科醫師可能認為是其他疾病,這類觀察者間的不一致(Inter-Observer Disagreement)相當常見。
但我們總得解決觀察者間不一致的問題,給出一個最終的答案吧?第一個解決方案是,我們可以用「共識投票 Consensus Voting」的方式,讓專家們各自投票採多數決,或大家一起坐下來商研後一起做出決定。

此外,我們也能結合不同的檢測工具獲得更豐富充足的資訊,作為判斷的依據。舉例來說,如果胸部 X 光片無法確定是否有肺部腫瘤,切個 CT 電腦斷層獲取 3D 結構能讓我們擁有更多參考的資訊,進而給胸部 X 光片相對正確的答案標註。光靠皮膚照片很難界定是否有癌化可能?那就做切片檢視組織學變化來確定。

第二個方法的缺點是,額外的檢測不見得有辦法做到,像拍胸部 X 光片的病人不可能每個都去切 CT,也不會每個皮膚出現可疑病灶的人都有做切片。因此,現行醫療 AI 研究大多採第一個共識投票的方式處理。

#CC