🔎大家好,我是Sonhhxg_柒,希望你看完之後,能對你有所幫助,不足請指正!共同學習交流🔎
📝個人首頁-Sonhhxg_柒的部落格_CSDN部落格 📃
🎁歡迎各位→點贊👍 + 收藏⭐️ + 留言📝
📣系列專欄 - 機器學習【ML】 自然語言處理【NLP】 深度學習【DL】
🖍foreword
✔說明⇢本人講解主要包括Python、機器學習(ML)、深度學習(DL)、自然語言處理(NLP)等内容。
如果你對這個系列感興趣的話,可以關注訂閱喲👋
文章目錄
技術要求
遷移學習入門
使用預訓練 ResNet-50 架構的圖像分類器
準備資料
提取資料集
預處理資料集
加載資料集
構模組化型
訓練模型
評估模型的準确性
使用 BERT 轉換器進行文本分類
收集資料
準備資料集
設定 DataLoader 執行個體
構模組化型
自定義輸入層
設定模型訓練和測試
設定模型測試
訓練模型
評估模型
概括
深度學習模型擁有的訓練資料越多,就越準确。最引人注目的深度學習模型,例如 ImageNet,需要在數百萬張圖像上進行訓練,并且通常需要大量的計算能力。從長遠來看,用于訓練 OpenAI 的 GPT3 模型的電量可以為整個城市供電。不出所料,從頭開始訓練這種深度學習模型的成本對于大多數項目來說都是令人望而卻步的。
這就引出了一個問題:我們真的需要每次都從頭開始訓練深度學習模型嗎?解決這個問題的一種方法,而不是從頭開始訓練深度學習模型,是從已經訓練過的模型中借用類似主題的表示。例如,如果你想訓練一個圖像識别模型來檢測人臉,你可以訓練你的卷積神經網絡( CNN ) 來學習每一層的所有表示——或者你可能會想,“世界上所有的人臉都有相似的表示,那麼為什麼不從其他已經在數百萬張臉上訓練過的模型中借用表示,并将其直接應用于我的資料集呢?” 這個簡單的想法叫做遷移學習。
遷移學習是一種幫助我們利用從先前建構的模型中獲得的知識的技術,該模型是為與我們的任務類似的任務而設計的。例如,要學習如何騎山地車,您可以利用以前在學習如何騎自行車時獲得的知識。遷移學習不僅适用于将學習到的表示從一組圖像轉移到另一組圖像,還适用于語言模型。
在機器學習社群中,有各種預先建構的模型,其權重由其作者共享。通過重用這些訓練過的模型權重,您可以避免更長的訓練時間并節省計算成本。
在本章中,我們将嘗試利用現有的預訓練模型來建構我們的圖像分類器和文本分類器。我們将使用一種流行的稱為 ResNet-50 的 CNN 架構來建構我們的圖像分類器,然後使用另一種稱為 BERT 的重磅變壓器架構來建構一個文本分類器。本章将向您展示如何使用 PyTorch Lightning 生命周期方法以及如何使用遷移學習技術構模組化型。
在本章中,我們将介紹以下主題:
- 遷移學習入門
- 使用預訓練 ResNet-50 架構的圖像分類器
- 使用 BERT 轉換器進行文本分類
技術要求
本章的代碼已經在 macOS 上使用 Anaconda 或在 Google Colab 中使用 Python 3.6 開發和測試。如果您使用的是其他環境,請對您的環境變量進行适當的更改。
在本章中,我們将主要使用以下 Python 子產品,并在其版本中提及:
- PyTorch Lightning (version: 1.5.2)
- Seaborn (version: 0.11.2)
- NumPy (version: 1.21.5)
- Torch (version: 1.10.0)
- pandas (version: 1.3.5)
請将所有這些子產品導入您的 Jupyter 環境。為了確定這些子產品一起工作并且不會不同步,我們使用了特定版本的 torch、torchvision、torchtext、torchaudio 和 PyTorch Lightning 1.5.2。您還可以使用互相相容的最新版 PyTorch Lightning 和torch compatible 。
!pip install torch==1.10.0 torchvision==0.11.1 torchtext==0.11.0 torchaudio==0.10.0 --quiet
!pip install pytorch-lightning==1.5.2 --quiet
如果您在導入包方面需要任何幫助,可以參考第 1 章PyTorch Lightning Adventure 。
本章的工作示例可以在此 GitHub 連結中找到:https ://github.com/PacktPublishing/Deep-Learning-with-PyTorch-Lightning/tree/main/Chapter03以下是源資料集的連結:
- 對于圖像分類,我們将使用我們在第 2 章中使用的相同資料集,第一個深度學習模型起步。您可以從 Kaggle 或直接從 PCam 網站下載下傳資料集:https://www.kaggle.com/c/histopathologic-cancer-detection。
- 對于文本分類案例,我們将使用公共衛生聲明資料集。該資料集在 MIT 許可下提供:https://huggingface.co/datasets/health_fact。
該資料集包含來自各種事實核查、新聞評論和新聞網站的 12,288 篇文章的集合。
遷移學習入門
遷移學習有許多有趣的應用,其中最引人入勝的應用之一是将圖像轉換為著名畫家的風格,例如梵高或畢加索。
圖 3.1 – 圖檔來源:藝術風格的神經算法 (https://arxiv.org/pdf/1508.06576v2.pdf)
前面的示例也稱為Style Transfer。有很多專門的算法完成這項任務,VGG-16、ResNet 和 AlexNet一些比較流行的架構。
在本章中,我們将從使用 ResNet-50 架構在 PCam 資料集上建立一個簡單的圖像分類模型開始,其中包含癌症組織的圖像掃描。後來,我們将建構一個文本分類模型,該模型使用來自 Transformers ( BERT )的雙向編碼器表示。
在這兩個例子中在本章中,我們将使用預訓練的模型及其權重并微調模型以使其适用于我們的資料集。預訓練模型的一大優勢是,由于它已經在大量資料集上進行了訓練,是以我們可以在更少的時期内獲得良好的結果。
任何使用遷移學習的模型通常都遵循以下結構:
- 通路預訓練模型。
- 配置預訓練模型。
- 構模組化型。
- 訓練模型。
- 評估模型的性能。
如果您之前使用過 Torch 并使用遷移學習建構了深度學習模型,您将看到與使用 PyTorch Lightning 的相似之處。唯一的差別是我們将使用 PyTorch Lightning 生命周期方法,這使事情變得更加簡單和容易。
使用預訓練 ResNet-50 架構的圖像分類器
ResNet-50代表Residual Network,這是一種 CNN 架構,最早出現在2015 年發表在題為Deep Residual Learning for Image Recognition的計算機視覺研究論文中,作者是 Kaiming He、Xiangyu Zhang、Shaoqing Ren 和 Jian Sun。
ResNet 是目前最流行的圖像相關任務架構。雖然它确實有效它非常适合圖像分類問題(我們将在下面看到),它作為編碼器同樣适用于學習更複雜任務(如自我監督學習)的圖像表示。ResNet 架構有多種變體,包括 ResNet-18、ResNet-34、ResNet-50 和 ResNet-152,具體取決于其具有的深層數。
ResNet-50 架構有 50 個深層,并在 ImageNet 資料集上進行訓練,該資料集有 1400 萬張圖像,屬于 1000 個不同的類别,包括動物、汽車、鍵盤、滑鼠、鋼筆和鉛筆。以下是 ResNet-50 的架構:
圖 3.2 – VGG-16 架構(圖檔來源:VGG-16 論文)
在 ResNet-50 模型架構中,有 48 個卷積層以及 1 個 AvgPool 層和 1 個 MaxPool 層。
ImageNet 上的 ResNet-50 模型已經在其計算過程中訓練了數周。再一次,正如引言中提到的,遷移學習的一個奇妙的好處是,我們不需要從頭開始訓練模型;相反,我們可以簡單地使用模型的權重并引導整個過程。
在本節中,我們将使用 ResNet-50 預訓練模型。我們将其配置為處理并訓練我們的 PCam 圖像資料集。使用預訓練的 ResNet-50 模型建構我們的圖像分類器基本上需要與之前詳述的相同基本步驟:
- 準備資料
- 構模組化型
- 訓練模型
- 評估模型的準确性
讓我們在以下小節中完成這些步驟。在執行任何代碼之前,請安裝正确版本的 PyTorch Lightning 和 opendatasets (有關說明,請參閱第 2 章,使用第一個深度學習模型起步,在收集資料集部分)。
準備資料
在 PyTorch Lightning 中有不同的方法來處理和處理資料集。一種方法是使用 PyTorch Lightning 的DataModule。現在,DataModule是一種很好的處理和結構化資料。您可以通過從 PyTorch Lightning 子產品繼承DataModule類來建立DataModule 。使用這個子產品的一個好處是它帶有一些生命周期方法。這些可以幫助我們完成資料準備的不同階段,例如加載資料、處理資料以及設定訓練、驗證和測試DataLoader執行個體。
PyTorch Lightning 的 DataLoader 期望圖像位于它們各自的子檔案夾中,是以我們需要在将資料輸入DataLoader執行個體之前對其進行預處理。我們将建立一個自定義LoadCancerDataset用于預處理資料集,我們将在後面的部分中看到。
提取資料集
在這裡,我們再次使用我們在第 2 章中使用的 PCam 資料集進行組織病理學癌症檢測,使用第一個深度學習模型起步。用于組織病理學癌症檢測的PatchCamelyon ( PCam ) 資料集包含 327,680 種顔色從淋巴結切片的組織病理學掃描中提取的圖像 (96 x 96px)。每個圖像都用二進制标簽進行注釋,表明存在轉移組織。有關資料集的更多資訊,請參見:https://www.kaggle.com/c/histopathologic-cancer-detection。
以下是來自 PCam 資料集的一些示例圖像:
圖 3.3 – 來自 PCam 資料集的 20 個帶有标簽的樣本圖像
我們将重用第 2 章中的代碼,使用第一個深度學習模型起步,來加載資料集。有關如何收集資料集的說明,請參閱收集資料集部分。
收集資料集後,我們可以開始加載資料集的過程。
我們首先需要為在下采樣資料中選擇的圖像提取标簽,如下所示:
selected_image_labels = pd.DataFrame()
id_list = []
label_list = []
for img in selected_image_list:
label_tuple = cancer_labels.loc[cancer_labels['id'] == img.split('.')[0]]
id_list.append(label_tuple['id'].values[0])
label_list.append(label_tuple['label'].values[0])
在前面的代碼中,我們建立了一個名為selected_image_labels的空資料框和兩個名為id_list和image_list的空清單來存儲圖像 ID 和相應的标簽。然後我們循環selected_image_list并将圖像 ID 添加到id_list以及将标簽添加到label_list。最後,我們将id_list和label_list這兩個清單作為列添加到selected_image_labels資料框,如下所示:
圖 3.4 – 圖像标簽驗證
資料集現在已準備好加載到 Dataloader 中。
預處理資料集
現在我們需要建立一個帶有标簽和 ID 的字典,将在我們的LoadCancerDataset類中使用:
img_class_dict = {k:v for k, v in zip(selected_image_labels.id, selected_image_labels.label)}
上述代碼從selected_image_labels資料框中提取 ID 和标簽,并将它們存儲在img_class_dict字典中。
建立自定義LoadCancerDataset類之前的最後一步是定義轉換器,如下所示:
data_T_train = T.Compose([
T.Resize(224),
T.RandomHorizontalFlip(),
T.ToTensor()
])
data_T_test = T.Compose([
T.Resize(224),
T.ToTensor()
])
在前面的代碼中,我們使用torchvision 變換子產品定義了訓練和測試變壓器。data_T_train轉換器将訓練圖像的大小從 96 像素的原始大小調整為 224 像素,因為 ResNet-50 模型期望圖像為 224 像素。我們還通過使用RandomHorizontalFlip函數來擴充訓練圖像,該函數将附加圖像添加到訓練資料集中。最後,轉換器将圖像轉換為張量。data_T_test轉換器對測試資料集執行類似的轉換。
資料集現在已準備好加載到 Dataloader 中。
加載資料集
後完成所有資料預處理步驟後,我們準備建立自定義LoadCancerDataset類,以使用 DataLoader 執行個體準備要加載的資料:
class LoadCancerDataset(Dataset):
def __init__(self, datafolder,transform = T.Compose([T.CenterCrop(32),T.ToTensor()]), labels_dict={}):
self.datafolder = datafolder
self.image_files_list = [s for s in os.listdir(datafolder)]
self.transform = transform
self.labels_dict = labels_dict
self.labels = [labels_dict[i.split('.')[0]] for i in self.image_files_list]
def __len__(self):
return len(self.image_files_list)
def __getitem__(self, idx):
img_name = os.path.join(self.datafolder, self.image_files_list[idx])
image = Image.open(img_name)
image = self.transform(image)
img_name_short = self.image_files_list[idx].split('.')[0]
label = self.labels_dict[img_name_short]
return image, label
在前面的代碼中,我們有以下内容:
- 此處定義的自定義類繼承自torch.utils.data.Dataset子產品。自定義LoadCancerDataset類在__init__方法中初始化,并接受三個參數:資料檔案夾的路徑,帶有預設值的轉換器将圖像裁剪為 32 大小,并将其轉換為張量和帶有資料集标簽和 ID 的字典的值。
- LoadCancerDataset讀取檔案夾中的所有圖像,并從檔案名中提取圖像名稱,這也是圖像的 ID。
- 然後将此圖像名稱與帶有标簽和 ID 的字典中的标簽進行比對。LoadCancerDataset傳回圖像及其标簽,然後可以在torch.utils.data的DataLoader子產品中使用,因為它現在可以讀取帶有相應标簽的圖像。
我們現在将調用LoadCancerDataset的執行個體來加載訓練和測試資料集,如下所示:
cancer_train_set = LoadCancerDataset(datafolder='/content/gdrive/My Drive/Colab Notebooks/histopathologic-cancer-detection/train_dataset/',
transform=data_T_train, labels_dict=img_class_dict)
cancer_test_set = LoadCancerDataset(datafolder='/content/gdrive/My Drive/Colab Notebooks/histopathologic-cancer-detection/test_dataset/',
transform=data_T_test, labels_dict=img_class_dict)
我們将三個必需的參數傳遞給我們的LoadCancerDataset類以建立cancer_train_set和cancer_test_set。第一個參數是我們之前建立的用于存儲的 Google Drive 持久存儲路徑訓練和測試圖像。第二個參數是我們在上一步中建立的轉換器,最後是帶有圖像标簽和 ID 的字典。
在這個預處理之後,我們現在準備調用DataLoader子產品的執行個體,我們在大部分章節中都廣泛使用了該子產品:
batch_size = 128
cancer_train_dataloader = DataLoader(cancer_train_set, batch_size, num_workers=2, pin_memory=True, shuffle=True)
cancer_test_dataloader = DataLoader(cancer_test_set, batch_size, num_workers=2, pin_memory=True)
如前面的代碼所示,我們将批量大小設定為 128,然後使用DataLoader子產品分别建立名為cancer_train_dataloader和cancer_test_dataloader的訓練和測試資料加載器,該子產品将自定義LoadCancerDataset類的輸出作為此處的輸入批量大小 (128)、worker 數量 (2) 以及自動記憶體固定 ( pin_memory ) 設定為True,這可以将資料快速傳輸到支援 CUDA 的 GPU。
至此,我們準備好了cancer_train_dataloader加載器,大約有 8,000 張圖像,我們的cancer_test_dataloader加載器,大約有 2,000 張圖像。所有圖像的大小均為 224 x 224,轉換為張量形式,并提供批量 128 張圖像。我們将使用cancer_train_dataloader來訓練我們的模型,并使用cancer_test_dataloader來衡量我們模型的準确性。
總而言之,我們首先執行資料工程步驟來下載下傳、下采樣和存儲資料。然後,我們對資料進行預處理,為DataLoader子產品做好準備,然後使用DataLoader子產品為 PCam 資料集建立訓練和測試資料加載器。
構模組化型
如上一章所述,我們在 PyTorch Lightning 中建構的任何模型都必須繼承來自閃電子產品類。讓我們從建立一個名為CancerImageClassifier的類開始:
class CancerImageClassifier(pl.LightningModule):
ResNet-50 模型在不同的資料集上進行訓練。為了使模型在 PCam 資料集上工作,我們需要對 ResNet-50 模型進行一些配置和調整,這是在模型初始化方法中完成的。
如前所述,在 ResNet-50 模型架構中,有 48 個卷積層,在卷積層之後,還有一個 MaxPool 層和一個 AvgPool 層。在上一章中,我們隻使用了 2 或 3 個卷積層,是以這種架構有 50 個卷積層,它會變得更加密集。
您可以在名為ResNet50.txt的書的 GitHub 頁面以及 PyTorch 上閱讀 ResNet-50 架構的完整實作。
在前面的 ResNet-50 架構中,有幾個卷積層,然後是 MaxPool 層和 AvgPool 層。最終分類器層的輸出為1000,因為該模型是為分類 1,000 個類而建構的,但對于我們使用 PCam 資料集訓練模型的用例,我們隻需要 2 個類。
可以在初始化方法中對 ResNet-50 模型進行更改,使其可以處理資料集。
init方法将學習率作為輸入,預設值為0.001,我們正在使用CrossEntropyloss函數來計算輸入和目标之間的交叉熵損失,如下所示:
def __init__(self, learning_rate = 0.001):
super().__init__()
self.learning_rate = learning_rate
self.loss = nn.CrossEntropyLoss()
在進行遷移學習時,重要的是當機現有層的權重以避免反向傳播和重新訓練,因為我們将利用現有的訓練模型。ResNet-50 模型已經在數百萬張圖像上進行了訓練,我們可以利用資料集的現有權重,是以我們當機了權重,如下所示:
self.pretrain_model = resnet50(pretrained=True)
self.pretrain_model.eval()
for param in self.pretrain_model.parameters():
param.requires_grad = False
在前面的代碼中,我們首先将 ResNet-50 模型加載為pretrain_model,該模型是從torchvision.models庫中導入的。然後我們将模式更改為評估模式,使用 eval 方法将 dropout 和批量歸一化層設定為評估模式。然後,我們周遊每個模型的參數并将required_grad值設定為False,以確定不會更改 ResNet-50 模型的目前權重。
現在我們需要更改 ResNet-50 模型的最後一層,以便我們可以對 PCam 資料集中的兩個類别進行分類,而 ResNet-50 模型的建構是為了将其分類為 1000 個不同的類别:
self.pretrain_model.fc = nn.Linear(2048, 2)
在這裡,我們正在改變最後一個線性的輸出,它将采用 2,048 個輸入特征和傳回 PCam 資料集的 2 個不同類别的 2 個機率。
重要的提示
ResNet-50 模型在所有卷積層之後,輸出 2,048 個特征。最後一個線性層的輸出為2,因為我們的 PCam 資料集僅包含 2 個類。
由于模型現在已準備好接受 PCam 資料集,我們将使用forward方法将資料傳遞給模型,如下所示:
def forward(self, input):
output=self.pretrain_model(input)
return output
forward方法是一個簡單的函數,它将資料作為輸入,将其傳遞給預訓練模型(本例中為 ResNet-50),然後傳回輸出。
我們需要為CancerImageClassifier配置優化器,我們将為此覆寫configure_optimizer生命周期方法,如下所示:
def configure_optimizers(self):
params = self.parameters()
optimizer = optim.Adam(params=params, lr = self.learning_rate)
return optimizer
configure_optimizer方法将Adam 設定為具有__init__方法中定義的學習率的優化器。此優化器随後由configure_optimizers方法作為輸出傳回。
下一步是通過覆寫生命周期方法來定義訓練步驟,如下所示:
def training_step(self, batch, batch_idx):
inputs, targets = batch
outputs = self(inputs)
preds = torch.argmax(outputs, dim=1)
train_accuracy = accuracy(preds, targets)
loss = self.loss(outputs, targets)
self.log('train_accuracy', train_accuracy, prog_bar=True)
self.log('train_loss', loss)
return {"loss":loss, "train_accuracy": train_accuracy}
training_step方法将批次和批次索引作為輸入。然後它存儲使用torch.argmax方法對這些輸入進行預測,該方法傳回輸入張量中所有元素的最大值的索引。使用來自torchmetrics.functional子產品的準确度方法通過将預測和實際目标作為輸入來計算訓練準确度。還使用輸出和實際目标計算損失,然後記錄損失和準确性。最後,training_step方法傳回訓練損失和訓練準确率,這有助于在訓練步驟中觀察模型的性能。
對測試資料集重複此方法,如下所示:
def test_step(self, batch, batch_idx):
inputs, targets = batch
outputs = self.forward(inputs)
preds = torch.argmax(outputs, dim=1)
test_accuracy = accuracy(preds, targets)
loss = self.loss(outputs, targets)
return {"test_loss":loss, "test_accuracy":test_accuracy}
前面的代碼塊重複了這裡為測試資料集解釋的所有過程。PyTorch Lightning 架構負責在DataModule中定義的DataLoader執行個體之間傳遞正确的資料;即資料從train_DataLoader分批傳到training_step,測試資料從test_DataLoader傳到test_step。在training_step方法中,我們正在将輸入資料傳遞給模型以計算并傳回損失值。PyTorch Lightning 架構負責反向傳播。在測試步驟中,我們使用來自torchmetrics.functional子產品的預建構準确度方法計算損失和準确度值。
重要的提示
在較新版本(1.5+)中,計算精度的方法與之前版本的 PyTorch Lightning 發生了顯着變化。pl.metrics.Accuracy ()函數已被棄用,是以我們現在将使用torchmetrics.functional子產品中的準确度方法來計算準确度。由于這個方法需要兩個參數——預測和目标,它不能再在__init__方法中定義。現在在train_step和test_step方法中調用此準确度方法。
訓練和測試生命周期方法的資料均以 128 批次傳遞。是以,模型在 train_step 中每 128 批次資料進行一次訓練,并且在train_step和train_step中也每 128 批次資料計算準确度和損失測試步驟。
是以,為了計算整個資料集的整體準确度,我們将利用一種名為test_epoch_end的生命周期方法。test_epoch_end生命周期方法在測試時期結束時調用,并輸出所有測試步驟的輸出,如下所示:
def test_epoch_end(self, outputs):
test_outs = []
for test_out in outputs:
out = test_out['test_accuracy']
test_outs.append(out)
total_test_accuracy = torch.stack(test_outs).mean()
self.log('total_test_accuracy', total_test_accuracy, on_step=False, on_epoch=True)
return total_test_accuracy
在test_epoch_end方法中,我們首先循環所有批次的輸出并将它們存儲在test_outs清單中。然後,我們使用torch.stack()函數連接配接各個批次的準确度的所有張量輸出,并使用均值方法計算總準确度。這是計算整個測試資料集的測試準确度的推薦方法。如果需要,此方法還可用于計算完整訓練和驗證資料集的準确性。
訓練模型
訓練模型的過程與我們在上一章中看到的相同。以下是使用trainer類訓練模型的代碼。
請注意,以下代碼使用 GPU。請確定您的環境中啟用了 GPU 來執行它。如果需要,您還可以将 GPU 替換為 CPU:
model = CancerImageClassifier()
trainer = pl.Trainer(max_epochs=10, gpus=-1)
trainer.fit(model, train_dataloaders=cancer_train_dataloader)
在前面的代碼中,我們調用了CancerImageClassifier的執行個體并将其儲存為模型。接下來,我們用最多 10 個 epoch 初始化訓練器類并将GPU設定為-1,相當于使用了所有可用的GPU。最後,将 PCam 資料集的模型和訓練資料加載器傳遞給fit方法。Pytorch Lightning 利用我們在DataModule類中定義的生命周期方法來通路和設定資料,并通路DataLoader執行個體以擷取訓練和測試資料。
圖 3.5 – 10 個時期的訓練圖像分類器
此時,我們的模型正在針對 PCam 資料集進行總共 10 個 epoch 的訓練。
在訓練階段,僅調用training_step生命周期方法。
評估模型的準确性
評估模型的準确性涉及測量模型的分類能力圖像分為兩個不同的類别。可以在測試資料集上測量準确性,如以下代碼所示:
trainer.test(test_dataloaders=cancer_test_dataloader)
這将産生以下輸出:
圖 3.6 – 測試資料集上的模型精度
在這裡,我們的模型被訓練了 10 個 epoch,并且在大約 2000 個測試圖像上達到了 85% 的準确度得分。
總而言之,首先,我們開始使用所有必需的生命周期方法建構DataModule執行個體,以與第 2 章“第一個深度學習模型起步”中完全相同的方式處理和服務DataLoader執行個體。後來,我們通過配置和調整 ResNet-50 預訓練模型來構模組化型,以在 PCam 資料集上進行訓練。我們在訓練資料集上僅對模型進行了 10 個 epoch 的訓練,并在測試資料集上測量了模型的性能。達到了 85% 的準确度分數,遠高于我們在第 2 章中運作的 500 個 epoch ,使用第一個深度學習模型起步,但準确度仍然較低。這不僅僅是因為我們有了更好的模型;它也便宜得多,花費的時間和計算更少。這應該證明遷移學習對你的價值。
即使隻有 10 個 epoch,我們也可以獲得更好的結果,因為遷移學習使用從 ImageNet 學習的圖像表示。如果沒有這些表示,則需要更多的時期和超參數調整才能達到我們所做的準确度得分。
使用 BERT 轉換器進行文本分類
文本分類using BERT transformers 是Google 開發的一種用于自然語言處理( NLP ) 的基于轉換器的機器學習技術。BERT 已建立并由 Jacob Devlin 于 2018 年出版。前BERT,對于語言任務,通常使用半監督模型,例如遞歸神經網絡( RNN ) 或序列模型。BERT 是第一個無監督的語言模型方法,在 NLP 任務上取得了最先進的性能。大型 BERT 模型由 24 個編碼器和 16 個雙向注意力頭組成。它接受了約 3,000,000,000 個單詞的 Book Corpora 單詞和英語 Wikipedia 條目的訓練。它後來擴充到 100 多種語言。使用預訓練的 BERT 模型,我們可以對文本執行多項任務,例如分類、資訊提取、問答、摘要、翻譯和文本生成。
圖 3.7 – BERT 架構圖(圖檔來源:紙質使用者生成資料:BERT 的緻命弱點)
在本節中,我們将使用預訓練的 BERT 模型建構我們的文本分類模型。在 PyTorch Lightning 中構模組化型有不同的方式,我們将在本書中介紹一些不同的方式和風格來編寫模型。在本節中,我們将僅使用 PyTorch Lightning 方法來構模組化型。
在我們進入模型建構之前,讓我們談談我們将用于文本分類器模型的文本資料。在本練習中,我們将使用公共衛生聲明資料集。這個資料集包括來自各種事實核查、新聞評論和新聞網站的 12,288 個公共衛生聲明的集合。以下是收集這些公共衛生聲明的來源的完整清單:
圖 3.8 – 資料集中公共衛生聲明的來源清單
以下是來自公共衛生聲明資料集的有關抗體的一些示例文本資料檢測冠狀病毒及其相關标簽 - 2(正确):
圖 3.9 – 來自公共衛生聲明資料集的樣本文本資料
我們的目标是使用遷移學習技術和 BERT 模型将文本分為四種不同的類别——真、假、未經證明和混合。以下是步驟參與建構我們的文本分類模型:
- 收集資料
- 構模組化型
- 訓練模型
- 評估模型
然而,在我們開始之前,像往常一樣;讓我們準備好我們的環境。
我們将安裝以下軟體包:
!pip install pytorch-lightning==1.5.2 --quiet
!pip install transformers==3.1.0 --quiet
并導入它們:
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
import matplotlib.pyplot as plt
%matplotlib inline
import pytorch_lightning as pl
from torchmetrics.functional import accuracy
import transformers
from transformers import BertModel, BertConfig
from transformers import AutoModel, BertTokenizerFast
import pandas as pd
現在我們都準備好了。
收集資料
我們将從已公開的 Google Drive 連結下載下傳資料集:
!gdown --id 1eTtRs5cUlBP5dXsx-FTAlmXuB6JQi2qj
!unzip PUBHEALTH.zip
這将産生以下輸出:
圖 3.10 – 下載下傳資料集
在前面的代碼中,我們正在下載下傳資料集,然後将其提取到 Google Colab 檔案夾中。現在我們要将資料集讀入 pandas 資料框:
pub_health_train = pd.read_csv("PUBHEALTH/train.tsv", sep='\t')
pub_health_test = pd.read_csv("PUBHEALTH/test.tsv", sep='\t')
我們到了将train.tsv和test.tsv檔案加載到名為pub_health_train和pub_health_test的 pandas 資料框中。
準備資料集
在裡面下一步,我們将驗證标簽并檢查缺失值:
pub_health_train = pub_health_train[pub_health_train['label'] != 'snopes']
pub_health_train = pub_health_train[['main_text','label']]
pub_health_train = pub_health_train.dropna(subset=['main_text', 'label'])
我們在前面的代碼中執行了以下資料處理步驟:
- 隻有 27 個執行個體将訓練資料歸類為“snopes”,而測試資料不包含任何此類執行個體,是以我們将其從訓練資料集中删除。
- 然後,我們隻選擇感興趣的兩列——“主文本”,其中包含公共衛生聲明的文本,以及“标簽”列,代表各自公共衛生聲明的四個類别之一。
- 最後,我們将删除在兩列之一中包含缺失值的任何行。我們将對測試資料集執行相同的步驟(完整代碼可在 GitHub 上獲得)。
圖 3.11 – 删除缺失值後的資料集視圖
公共衛生聲明的類别是錯誤的、混合的、真實的和未經證明的。這些需要将其轉換為數字,以便可以從标簽清單中建立張量:
pub_health_train['label'] = pub_health_train['label'].map({"true":0, "false":1, "unproven":2, "mixture":3})
pub_health_test['label'] = pub_health_test['label'].map({"true":0, "false":1, "unproven":2, "mixture":3})
在這裡,我們将标簽從 0 映射到 4,以利用 PyTorch 閃電的prepare_data生命周期方法。
準備資料的過程可能涉及加載資料、拆分資料、轉換、特征工程和許多其他活動,以提供更好的結果,更重要的是,被模型接受。到目前為止,我們已經執行了一些資料處理步驟來從原始資料集中提取相關資料。
現在,為了提取特征和加載資料,我們将利用 PyTorch Lightning 庫的prepare_data生命周期方法。這種資料轉換和任何特征工程都可以在我們的TextClassifier類之外完成;但是,PyTorch Lightning 允許我們将所有内容整合在一起。prepare_data生命周期方法在任何訓練開始之前觸發;在我們的例子中,它在其他生命周期方法之前觸發,例如train_dataloader、test_dataloader、training_step和testing_step。
在prepare_data方法中,我們首先初始化BertTokenizerFast。以下是這個的代碼片段:
tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')
# 對訓練集中的序列進行标記和編碼
tokens_train = tokenizer.batch_encode_plus(
pub_health_train["main_text"].tolist(),
max_length = self.max_seq_len,
pad_to_max_length=True,
truncation=True,
return_token_type_ids=False
)
# 在測試集中标記和編碼序列
tokens_test = tokenizer.batch_encode_plus(
pub_health_test["main_text"].tolist(),
max_length = self.max_seq_len,
pad_to_max_length=True,
truncation=True,
return_token_type_ids=False
)
在前面的代碼中,我們首先從BertTokenizerFast子產品為我們的 BERT 模型初始化标記器,并将其存儲為标記器對象。然後,我們使用标記器對象對pub_health_train和pub_health_test資料集中的main_text列進行标記。分詞器的batch_encode_plus方法傳回一個具有input_ids和attention_mask的對象。将使用input_ids和attention_mask作為我們的文本分類模型的特征。傳遞max_seq_len以截斷超過已建立的最大序列長度的任何文本資料。pad_to_max_length參數設定為True,這意味着将填充最大序列長度之後的任何内容。訓練和測試資料的标記分别存儲為tokens_train和tokens_test。現在我們需要建立特征并從資料集中提取目标變量。下面的代碼示範了這一點:
self.train_seq = torch.tensor(tokens_train['input_ids'])
self.train_mask = torch.tensor(tokens_train['attention_mask'])
self.train_y = torch.tensor(train_data["label"].tolist())
self.test_seq = torch.tensor(tokens_test['input_ids'])
self.test_mask = torch.tensor(tokens_test['attention_mask'])
self.test_y = torch.tensor(test_data["label"].tolist())
在前面的代碼中,我們提取了 input_ids和attention_mask并将它們存儲為訓練資料集的train_seq和train_mask ,以及測試資料集的test_seq和test_mask。此外,我們正在為訓練資料集建立一個目标變量train_y,為測試資料集建立一個test_y 。現在我們擁有了模型所需的所有特征和目标,它們将用于其他生命周期方法,我們将在稍後介紹。
在prepare_data生命中循環方法,我們首先加載我們的公共衛生聲明資料集,對資料進行标記,并建立特征和目标變量。
設定 DataLoader 執行個體
我們已經完成了在prepare_data生命周期方法中加載資料、準備特征、提取目标的資料處理步驟。是以,我們現在可以使用DataLoader生命周期方法為訓練和測試資料集建立DataLoader執行個體。以下是用于建立測試和訓練DataLoader執行個體的生命周期方法的代碼片段:
def train_dataloader(self):
train_dataset = TensorDataset(self.train_seq, self.train_mask, self.train_y)
self.train_dataloader_obj = DataLoader(train_dataset, batch_size=self.batch_size)
return self.train_dataloader_obj
def test_dataloader(self):
test_dataset = TensorDataset(self.test_seq, self.test_mask, self.test_y)
self.test_dataloader_obj = DataLoader(test_dataset, batch_size=self.batch_size)
return self.test_dataloader_obj
在前面的代碼中,我們有兩個生命周期方法,train_dataloader和test_dataloader。在這兩種方法中,我們都在上一步中建立的特征和目标上使用TensorDataset方法來建立train_data和test_data資料集。然後,我們傳遞train_dataset和test_dataset資料集以使用DataLoader子產品建立train_dataloader_obj和test_dataloader對象。兩種方法都傳回這個調用時的資料加載器對象。
構模組化型
到現在為止,你應該已經熟悉了,每當我們在 PyTorch 中構模組化型時Lightning,我們總是建立一個從 Lightning 子產品擴充/繼承的類,以便我們可以通路 Lightning 生命周期方法。
讓我們首先建立一個名為HealthClaimClassifier的類,如下所示:
class HealthClaimClassifier(pl.LightningModule):
我們現在将初始化HealthClaimClassifier,如下所示:
def __init__(self, max_seq_len=512, batch_size=128, learning_rate = 0.001):
在上述代碼中,HealthClaimClassifier接受三個輸入參數:
- max_seq_len:此參數控制 BERT 模型可以處理的序列的最大長度,換句話說,就是要考慮的單詞的最大長度。在這種情況下,預設值設定為512。
- batch_size:batch size 表示我們訓練樣本的子集,将用于訓練每個 epoch 的模型。預設值為128。
-
learning_rate:學習率控制着我們根據損失梯度調整網絡權重的程度。預設值為0.001。
重要的提示
任何小于max_seq_len值的輸入文本資料都将被填充,任何更大的文本資料都将被修剪掉。
我們現在将在__init__方法中初始化所需的變量和對象,如下所示:
super().__init__()
self.learning_rate = learning_rate
self.max_seq_len = max_seq_len
self.batch_size = batch_size
self.loss = nn.CrossEntropyLoss()
在__init__方法中,我們首先設定由HealthClaimClassifier接收的輸入—— learning_rate、max_seq_len和batch_size。
然後我們還使用torch.nn子產品中的CrossEntropyLoss函數建立了一個損失對象。我們将在本章後面部分讨論如何使用損失函數。
現在,我們将使用以下代碼片段設定我們的 BERT 模型:
self.pretrain_model = AutoModel.from_pretrained('bert-base-uncased')
self.pretrain_model.eval()
for param in self.pretrain_model.parameters():
param.requires_grad = False
在前面的代碼片段中,我們首先使用轉換器的AutoModel子產品将 BERT 基礎未封裝模型加載為pretrain_model。然後,我們将按照之前圖像分類部分中看到的相同步驟進行操作。是以,我們将模型切換到評估模式,然後将所有模型參數設定為false,以當機現有權重并防止重新訓練現有層。
自定義輸入層
現在我們需要調整預訓練模型以接受我們自己的自定義輸入,類似于我們在CancerImageClassifier子產品中做了。這可以在__init__方法中完成,如下代碼所示:
self.new_layers = nn.Sequential(
nn.Linear(768, 512),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(512,4),
nn.LogSoftmax(dim=1)
)
預訓練的 BERT 模型傳回大小為 768 的輸出,是以我們需要調整大小參數。為此,我們在前面的代碼中建立了一個序列層,它由兩個線性層、ReLU和LogSoftmax激活函數以及 dropout 組成。
第一個線性層接受大小為 768 的輸入,這是預訓練的 BERT 模型的輸出大小,并傳回大小為 512 的輸出。
然後,第二個線性層接收第一層大小為 512 的輸出,并傳回輸出 4,即公共衛生聲明資料集中的類别總數。
這是我們文本分類器的init方法的結尾。回顧一下,主要有以下三個方面:
- 我們首先設定所需的參數和損失對象。
- 然後,我們在評估模式下初始化預訓練的 BERT 模型并當機現有的權重。
- 最後,我們通過建立與 BERT 模型一起使用的順序層對預訓練模型進行了一些調整,以傳回資料集中可用的 4 種不同類别的公共衛生聲明的大小為 4 的輸出。
現在我們需要将預訓練的 BERT 模型與我們的序列層連接配接起來,以便模型傳回大小為 4 的輸出。輸入資料必須首先通過預訓練的 BERT 模型,然後進入我們的順序層。我們可以使用前向生命周期方法來連接配接這兩個模型。我們已經在上一節和前幾章中看到了forward方法,這是最好的方法,是以我們将在本書中繼續使用它。
以下是forward方法的代碼片段:
def forward(self, encode_id, mask):
_, output= self.pretrain_model(encode_id, attention_mask=mask)
output = self.new_layers(output)
return output
在前面的forward方法中,在prepare_data生命周期方法的 tokenize 步驟中提取的encode_id和mask被用作輸入資料。它們首先傳遞給預訓練的 BERT 模型,然後傳遞給我們的順序層。forward方法傳回最終順序層的輸出。
設定模型訓練和測試
你應該現在熟悉了,我們再次使用training_step生命周期方法來訓練我們的模型,如下面的代碼塊所示:
def training_step(self, batch, batch_idx):
encode_id, mask, targets = batch
outputs = self(encode_id, mask)
preds = torch.argmax(outputs, dim=1)
train_accuracy = accuracy(preds, targets)
loss = self.loss(outputs, targets)
self.log('train_accuracy', train_accuracy, prog_bar=True, on_step=False, on_epoch=True)
self.log('train_loss', loss, on_step=False, on_epoch=True)
return {"loss":loss, 'train_accuracy': train_accuracy}
在前面的training_step方法中,它以批次和批次索引作為輸入。然後,将特征(encode_id和mask)以及目标傳遞給模型并存儲輸出。然後,建立preds對象以擷取預測從使用torch.argmax函數的輸出。此外,使用torchmetrics.functional子產品中的準确度函數計算各個批次的訓練準确度。除了準确率,損失也通過交叉熵損失函數計算。準确性和損失都記錄在每個時期。
重要的提示
在training_step中,我們正在計算各個批次的準确度;這種準确性與完整的資料集無關。
設定模型測試
同樣,我們将再次使用test_step生命周期方法來評估模型。将為此方法通路來自DataLoader測試的測試資料。準确度由于test_step方法接受來自DataLoader執行個體的資料批次,是以在此步驟中計算的或損失将僅與特定批次有關。是以,這一步不會計算整個測試資料集的整體準确率。如上一節所示,我們将使用名為test_epoch_end的生命周期方法來計算完整測試資料集的準确度。此方法在測試時期結束時調用,并帶有所有測試步驟的輸出:
def test_step(self, batch, batch_idx):
encode_id, mask, targets = batch
outputs = self.forward(encode_id, mask)
preds = torch.argmax(outputs, dim=1)
test_accuracy = accuracy(preds, targets)
loss = self.loss(outputs, targets)
return {"test_loss":loss, "test_accuracy":test_accuracy}
在裡面前面的代碼片段,以下适用:
- 測試資料分批傳給模型,使用torchmetrics.functional.accuracy方法計算準确率,使用交叉熵損失函數計算loss;傳回損失和準确性。
- 在這裡,我們可以批量通路測試資料,是以計算每個批次的準确率和損失。為了計算整個測試資料集的準确性,我們可能需要等待測試資料集被處理。這可以使用名為test_epoch_end的生命周期方法來實作。
- test_epoch_end生命周期方法是在 test_step 生命周期方法中處理完所有資料後觸發的。下面是test_epoch_end方法的代碼:
作為在本章前面的圖像分類部分解釋過,我們使用test_epoch_end生命周期方法來計算總準确率。def test_epoch_end(self, outputs): test_outs = [] for test_out in outputs: out = test_out['test_accuracy'] test_outs.append(out) total_test_accuracy = torch.stack(test_outs).mean() self.log('total_test_accuracy', total_test_accuracy, on_step=False, on_epoch=True) return total_test_accuracy
訓練模型
我們将再次使用我們在上一節和上一章中使用的相同過程來訓練模型。下面是訓練模型的代碼片段:
model = HealthClaimClassifier()
trainer = pl.Trainer(max_epochs=10, gpus=-1)
trainer.fit(model)
在這裡,我們調用HealthClaimClassifier的一個執行個體作為模型,然後從 PyTorch Lightning 初始化訓練器。最大 epoch 數限制為 10,我們将gpu設定為-1以使用機器上所有可用的 GPU。最後,我們将模型傳遞給fit方法來訓練模型。PyTorch Lightning 在内部使用生命周期方法,包括我們之前建立的DataModule執行個體。fit方法中觸發的一系列生命周期方法如下:prepare_data、train_dataloader和training_step。
圖 3.12 – 為 10 個 epoch 訓練一個文本分類器
我們有現在使用預訓練的 BERT 模型對我們的HealthClaimClassifier進行了 10 個 epoch 的訓練。下一步是在測試資料集上評估模型。
評估模型
我們将調用test_step生命周期方法來測量模型在使用以下代碼測試資料集:
trainer.test()
當調用訓練器類中的測試方法時,會觸發以下生命周期方法序列:prepare_data、test_dataloader、test_step,然後是 test_epoch_end。在最後一步中,test_epoch_end計算測試資料集的總準确率,如下所示:
圖 3.13 – 完整測試資料集的文本分類器準确度
我們可以觀察到,我們已經能夠在測試資料集上達到 61% 的準确率,隻需 10 個 epoch 并且無需任何超參數調整。HealthClaimClassifier上的模型改進練習甚至可以幫助我們通過預訓練的 BERT 模型獲得更好的準确性。
回顧一下,我們使用傳輸從 BERT 轉換器模型建構了一個文本分類器模型學習。此外,我們使用 PyTorch Lightning 生命周期方法來加載資料、處理資料、設定資料加載器以及設定訓練和測試步驟。使用 PyTorch Lightning 方法在HealthClaimClassifier類中實作了一切,在HealthClaimClassifier類之外沒有太多資料處理。
概括
遷移學習是用于降低計算成本、節省時間和獲得最佳結果的最常用方法之一。在本章中,我們學習了如何使用 ResNet-50 和使用 PyTorch Lightning 預訓練的 BERT 架構構模組化型。
我們已經建構了一個圖像分類器和一個文本分類器,并且在此過程中,我們介紹了一些有用的 PyTorch Lightning 生命周期方法。我們已經學會了如何利用預訓練模型以更少的努力和更少的訓練時期來處理我們定制的資料集。即使模型調整很少,我們也能夠達到不錯的準确性。
雖然遷移學習方法效果很好,但也應牢記它們的局限性。它們非常适合語言模型,因為給定資料集的文本通常由與核心訓練集中相同的英語單詞組成。當核心訓練集與給定資料集有很大不同時,性能就會受到影響。例如,如果您想建構一個圖像分類器來識别蜜蜂,而 ImageNet 資料集中沒有蜜蜂,那麼您的遷移學習模型的準确性會更弱。您可能需要進行完整的教育訓練選擇。