摘要:如何使用 Pytorch(或Pytorchlightning) 和 huggingface Transformers 做文本摘要生成任務(wù),包括數(shù)據(jù)集的加載、模型的加載、模型的微調(diào)、模型的驗證、模型的保存、ROUGE指標(biāo)分?jǐn)?shù)的計算、loss的可視化。
? NLP 研 0 選手的學(xué)習(xí)筆記
一、需要的環(huán)境
● python
需要 3.8+
numpy==1.19.2
pandas==1.3.4
torch>=1.7.0, !1.8.0(我的是1.11.0)
transformers==4.23.1
pytorch-lightning==1.5.10 (pip install pytorch-lightning==1.5.10)
scikit-learn==0.24.2 (pip install scikit-learn==0.24.2)
rouge==1.0.1
SentencePiece==0.1.97
nltk==3.8.1
rouge-score==0.1.2
● 文件相對地址:mian.py
和 train.csv
是必須要有的。mian.py
中寫入所有的代碼、train.csv
中裝的是訓(xùn)練集。這張圖里面,我沒有放測試集,但在 “五、項目鏈接” 中,我放了的。
二、任務(wù)說明
● 為什么要用 pytorch-lighting 呢?
pytorch-lighting(簡稱pl),它其實就是一個輕量級的 PyTorch 庫,用于高性能人工智能研究的輕量級 PyTorch 包裝器??s放你的模型,而不是樣板。
它可以清晰地抽象和自動化ML模型所附帶的所有日常樣板代碼,允許您專注于實際的ML部分(這些也往往是最有趣的部分)。除了自動化樣板代碼外,Lightning還可以作為一種樣式指南,用于構(gòu)建干凈且可復(fù)制的ML系統(tǒng)。
pytorch 和 pl 本質(zhì)上代碼是完全相同的。只不過pytorch需要自己造輪子(如model, dataloader, loss, train,test,checkpoint, save model等等都需要自己寫),而pl 把這些模塊都結(jié)構(gòu)化了(類似keras)。
————————————————
原文鏈接:pytorch-lightning入門(一)—— 初了解
● 我是基于 T5(text-to-text-transfer-transformer)模型的源代碼修改的,地址:https://github.com/Shivanandroy/simpleT5。主要修改點如下:
??1. 取消了 checkpoint
保存機(jī)制,只保存最后一輪的模型和訓(xùn)練日志
??2. 加入早停機(jī)制,使得模型在訓(xùn)練過程中,若發(fā)現(xiàn) val_loss
(驗證集的損失) 沒有下降,就及時停止訓(xùn)練(以防止過擬合)。
??3. 使用了 CNN/Daily Mail 的報刊新聞(一部分) 作為我的數(shù)據(jù)集,train.csv
有 9000
個樣本(我將其以 9:1
的形式劃分成了訓(xùn)練集和驗證集),無測試集。
??4. 加入了 ROUGE
指標(biāo),主要在模型訓(xùn)練完后,對驗證集進(jìn)行 ROUGE
分?jǐn)?shù)計算。
??5. 將所有重要的英文注釋翻譯為中文,并加入詳細(xì)的注釋。
● 部分重要超參數(shù)列表如下:
參數(shù)名 | 值 | 說明 |
---|---|---|
batch_size | 4 | 我用的 3060,12GB 顯存,跑不了多少… |
max_epochs | 10 | 最大訓(xùn)練輪數(shù) |
source_max_token_len | 768 | 最大文檔(source_text )長度,如果超過該長度,則截斷 |
target_max_token_len | 64 | 最大摘要(target_text )長度,如果超過該長度,則截斷 |
precision | 32 | 精度設(shè)置為全精度 |
early_stopping_patience_epochs | 2 | 在第 2 輪結(jié)束時開始監(jiān)視 val_loss ,如果 val_loss 沒有改善(即下降),則停止訓(xùn)練 |
num_beams | 4 | 在進(jìn)行預(yù)測時 beams search 的數(shù)量(和 T5 論文中的一樣) |
length_penalty | 0.6 | 基于 beams search 生成所使用的長度的指數(shù)懲罰(和 T5 論文中的一樣) |
learning_rate | 0.0001 | 學(xué)習(xí)率 |
● 補充說明:我在代碼中設(shè)置了隨機(jī)種子(42
),便于大家復(fù)現(xiàn)。
三、完整代碼
from abc import ABC
import pandas as pd
from sklearn.model_selection import train_test_split
from torch.utils.data import Dataset, DataLoader
import pytorch_lightning as pl
from torch.optim import AdamW
import numpy as np
import torch
from pytorch_lightning.callbacks.progress import TQDMProgressBar
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
from rouge import Rouge
from transformers import (
T5ForConditionalGeneration,
PreTrainedTokenizer,
T5TokenizerFast as T5Tokenizer,
)
class myDataModule(Dataset):
""" Pytorch 類型的數(shù)據(jù)集類 """
def __init__(
self,
data: pd.DataFrame,
tokenizer: PreTrainedTokenizer,
source_max_token_len: int = 512,
target_max_token_len: int = 512,
):
"""
為輸入數(shù)據(jù)初始化 PyTorch 數(shù)據(jù)集模塊
參數(shù):
data (pd.DataFrame): 輸入為 pandas dataframe 形式. Dataframe 必須有 2 列 --> "source_text" 和 "target_text"
tokenizer (PreTrainedTokenizer): 一個預(yù)訓(xùn)練好的分詞器 (例如 T5Tokenizer, MT5Tokenizer 或 ByT5Tokenizer)
source_max_token_len (int, optional): 源文本的最大 token 長度. 默認(rèn)值為 512.
target_max_token_len (int, optional): 目標(biāo)文本的最大 token 長度. 默認(rèn)值為 512.
"""
self.tokenizer = tokenizer
self.data = data
self.source_max_token_len = source_max_token_len
self.target_max_token_len = target_max_token_len
def __len__(self):
""" 返回數(shù)據(jù)的長度 """
return len(self.data)
def __getitem__(self, index: int):
""" 返回一個 batch_size 的 input, 以便之后輸入模型 """
# 1. 獲取數(shù)據(jù)集中第 index 批次的數(shù)據(jù)
data_row = self.data.iloc[index]
# 2. 獲取源文檔
source_text = data_row["source_text"]
# 3. 對源文檔進(jìn)行分詞編碼
source_text_encoding = self.tokenizer(
source_text,
max_length=self.source_max_token_len,
padding="max_length", # 如果文檔的長度未達(dá)到 max_length(即self.source_max_token_len), 就用<pad>填充滿
truncation=True, # 如果文檔的長度超過 max_length, 則截斷后面的文本, 只取前面的
return_attention_mask=True, # 要求返回 attention 的掩碼張亮
add_special_tokens=True, # 要求增加特殊的 token, 比如加入 <pad> 位
return_tensors="pt", # 以 pytorch 的 tensor 類型返回
)
# 4. 對目標(biāo)文檔(即摘要)進(jìn)行分詞編碼
target_text_encoding = self.tokenizer(
data_row["target_text"],
max_length=self.target_max_token_len,
padding="max_length",
truncation=True,
return_attention_mask=True,
add_special_tokens=True,
return_tensors="pt",
)
# 5. 獲取目標(biāo)文本(即摘要)的 token ids
labels = target_text_encoding["input_ids"]
# 6. 將摘要中 token id 為 0(即<pad>位) 的轉(zhuǎn)為 -100, 便于后期 "省略" 掉
labels[labels == 0] = -100
# 7. 以字典(dict)的形式返回這一批次的數(shù)據(jù)
return dict(
source_text_input_ids=source_text_encoding["input_ids"].flatten(), # .flatten() 是為了
source_text_attention_mask=source_text_encoding["attention_mask"].flatten(),
labels=labels.flatten(),
labels_attention_mask=target_text_encoding["attention_mask"].flatten(),
)
class myLightningDataModule(pl.LightningDataModule, ABC):
""" PyTorch Lightning 類型的數(shù)據(jù)集類, 它繼承了 "PyTorch 類型的數(shù)據(jù)集類" 的所有東西, 并附加了其他功能 """
def __init__(
self,
train_df: pd.DataFrame,
test_df: pd.DataFrame,
tokenizer: PreTrainedTokenizer,
batch_size: int = 4,
source_max_token_len: int = 512,
target_max_token_len: int = 512,
num_workers: int = 4, # 要為數(shù)據(jù)使用多少子進(jìn)程裝載。'0'表示將在主進(jìn)程中加載數(shù)據(jù)(默認(rèn)值:'0')
):
"""
初始化 PyTorch Lightning 類型的數(shù)據(jù)模塊
參數(shù):
train_df (pd.DataFrame): training dataframe. Dataframe 必須有 2 列 --> "source_text" 和 "target_text"
test_df (pd.DataFrame): validation dataframe. Dataframe 必須有 2 列 --> "source_text" 和 "target_text"
tokenizer (PreTrainedTokenizer): 一個預(yù)訓(xùn)練好的分詞器 (例如 T5Tokenizer, MT5Tokenizer 或 ByT5Tokenizer)
batch_size (int, optional): batch size. 默認(rèn)為 4.
source_max_token_len (int, optional): 源文本的最大 token 長度. 默認(rèn)值為 512.
target_max_token_len (int, optional): 目標(biāo)文本的最大 token 長度. 默認(rèn)值為 512.
"""
super().__init__()
self.train_df = train_df
self.test_df = test_df
self.batch_size = batch_size
self.tokenizer = tokenizer
self.source_max_token_len = source_max_token_len
self.target_max_token_len = target_max_token_len
self.num_workers = num_workers
self.train_dataset = None
self.test_dataset = None
def setup(self, stage=None):
self.train_dataset = myDataModule(
self.train_df,
self.tokenizer,
self.source_max_token_len,
self.target_max_token_len,
)
self.test_dataset = myDataModule(
self.test_df,
self.tokenizer,
self.source_max_token_len,
self.target_max_token_len,
)
def train_dataloader(self):
""" 訓(xùn)練集 dataloader """
return DataLoader(
self.train_dataset,
batch_size=self.batch_size,
shuffle=True, # 隨機(jī)打亂訓(xùn)練集
num_workers=self.num_workers,
)
def val_dataloader(self):
""" 驗證集 dataloader """
return DataLoader(
self.test_dataset,
batch_size=self.batch_size,
shuffle=False,
num_workers=self.num_workers,
)
def test_dataloader(self):
""" 測試集 dataloader """
return DataLoader(
self.test_dataset,
batch_size=self.batch_size,
shuffle=False,
num_workers=self.num_workers,
)
class myLightningModel(pl.LightningModule, ABC):
""" PyTorch Lightning 模型類 """
def __init__(
self,
tokenizer,
model,
output_dir: str = "outputs",
save_only_last_epoch: bool = False,
):
"""
初始化一個 PyTorch Lightning 模型
Args:
tokenizer : T5/MT5/ByT5 分詞器
pretrained_model : T5/MT5/ByT5 的預(yù)訓(xùn)練模型
output_dir (str, optional): 保存模型檢查點的輸出目錄, 默認(rèn)為 "outputs"
save_only_last_epoch (bool, optional): 如果為 True, 則只保存最后一個 epoch, 否則將為每個 epoch 保存模型
"""
super().__init__()
self.model = model
self.tokenizer = tokenizer
self.output_dir = output_dir
self.average_training_loss = None # 訓(xùn)練時的平均 loss
self.average_validation_loss = None # 驗證時的平均 loss
self.save_only_last_epoch = save_only_last_epoch
def forward(self, input_ids, attention_mask, decoder_attention_mask, labels=None):
""" forward step """
output = self.model(
input_ids,
attention_mask=attention_mask,
labels=labels,
decoder_attention_mask=decoder_attention_mask,
)
return output.loss, output.logits # loss 的計算為 = CrossEntropyLoss(ignore_index=-100)
def training_step(self, batch, batch_size): # 自動打開模型的 train() 模型
""" 當(dāng)用訓(xùn)練集訓(xùn)練模型時, 執(zhí)行該代碼 """
input_ids = batch["source_text_input_ids"]
attention_mask = batch["source_text_attention_mask"]
labels = batch["labels"]
labels_attention_mask = batch["labels_attention_mask"]
loss, outputs = self( # 直接調(diào)用 forward() 源代碼只有 self
input_ids=input_ids,
attention_mask=attention_mask,
decoder_attention_mask=labels_attention_mask,
labels=labels,
)
self.log( # 寫入日志
"train_loss", loss, prog_bar=True, logger=True, on_epoch=True, on_step=True
)
return loss
def validation_step(self, batch, batch_size): # 自動打開模型的 eval() 模型
""" 當(dāng)用驗證集測試模型時, 執(zhí)行該代碼 """
input_ids = batch["source_text_input_ids"]
attention_mask = batch["source_text_attention_mask"]
labels = batch["labels"]
labels_attention_mask = batch["labels_attention_mask"]
loss, outputs = self(
input_ids=input_ids,
attention_mask=attention_mask,
decoder_attention_mask=labels_attention_mask,
labels=labels,
)
self.log(
"val_loss", loss, prog_bar=True, logger=True, on_epoch=True, on_step=True
)
# return {'loss': loss, 'input_ids': input_ids, 'labels': labels} # 因為在驗證集測試結(jié)束時什么都不做, 故不返回東西
def test_step(self, batch, batch_size): # 自動打開模型的 eval() 模型. 因為我沒有放入測試集進(jìn)來, 所以不會執(zhí)行該段代碼
""" 當(dāng)用測試集測試模型時, 執(zhí)行該代碼 """
input_ids = batch["source_text_input_ids"]
attention_mask = batch["source_text_attention_mask"]
labels = batch["labels"]
labels_attention_mask = batch["labels_attention_mask"]
loss, outputs = self(
input_ids=input_ids,
attention_mask=attention_mask,
decoder_attention_mask=labels_attention_mask,
labels=labels,
)
self.log("test_loss", loss, prog_bar=True, logger=True)
return loss
def configure_optimizers(self): # 在該類初始化時即被調(diào)用
""" 配置優(yōu)化器(optimizers) """
return AdamW(self.parameters(), lr=0.0001)
def training_epoch_end(self, training_step_outputs):
""" 在每一輪訓(xùn)練結(jié)束時保存訓(xùn)練了的分詞器和模型 """
self.average_training_loss = np.round(
# torch.stack(): 把多個 2 維的張量湊成一個3維的張量; 多個 3 維的湊成一個4維的張量...以此類推, 也就是在增加新的維度進(jìn)行堆疊.
torch.mean(torch.stack([x["loss"] for x in training_step_outputs])).item(),
4, # 小數(shù)位數(shù)后保留 4 位
)
path = f"{self.output_dir}/simple_T5"
if self.save_only_last_epoch:
if self.current_epoch == self.trainer.max_epochs - 1:
self.tokenizer.save_pretrained(path) # 保存分詞器到路徑 path 底下
self.model.save_pretrained(path) # 保存模型到路徑 path 底下
else:
self.tokenizer.save_pretrained(path)
self.model.save_pretrained(path)
def validation_epoch_end(self, validation_step_outputs):
""" 在每一輪驗證集測試結(jié)束時做點什么呢? """
pass
class myModel_for_Train:
""" 自定義的 T5 模型類 """
def __init__(self) -> None:
""" 初始化自定義的模型類 """
self.model = None
self.T5_Model = None
self.tokenizer = None
self.device = None
self.data_module = None
def from_pretrained(self, model_name="t5-base") -> None:
"""
加載預(yù)訓(xùn)練 T5 模型進(jìn)行訓(xùn)練/微調(diào)
參數(shù):
model_name (str, optional): 確切的模型體系結(jié)構(gòu)名稱,"t5-base" 或 "t5-large". 默認(rèn)為 "t5-base".
"""
self.tokenizer = T5Tokenizer.from_pretrained(f"{model_name}")
self.model = T5ForConditionalGeneration.from_pretrained(
f"{model_name}", return_dict=True # 是否返回一個 ~utils.ModelOutput類 而不是普通的元組
)
def train(
self,
train_df: pd.DataFrame,
eval_df: pd.DataFrame,
source_max_token_len: int = 512,
target_max_token_len: int = 512,
batch_size: int = 8,
max_epochs: int = 5,
use_gpu: bool = True,
output_dir: str = "outputs",
early_stopping_patience_epochs: int = 2, # 0 表示禁用提前停止功能
precision=32,
logger="default",
dataloader_num_workers: int = 2,
save_only_last_epoch: bool = False, # 不設(shè)置只保留最后一輪, 而是保留在驗證集上效果最好的一輪
):
"""
在自定義數(shù)據(jù)集上訓(xùn)練 T5 模型
參數(shù):
train_df (pd.DataFrame): training dataframe. Dataframe 必須有 2 列 --> "source_text" 和 "target_text"
eval_df ([type], optional): validation dataframe. Dataframe 必須有 2 列 --> "source_text" 和 "target_text"
source_max_token_len (int, optional): 源文本的最大 token 長度. 默認(rèn)值為 512.
target_max_token_len (int, optional): 目標(biāo)文本的最大 token 長度. 默認(rèn)值為 512.
batch_size (int, optional): batch size. 默認(rèn)值為 8.
max_epochs (int, optional): 最大的 epochs 值. 默認(rèn)為 5.
use_gpu (bool, optional): 如果為True, 則模型使用 gpu 進(jìn)行訓(xùn)練. 默認(rèn)為 True.
output_dir (str, optional): 保存模型 checkpoints 的輸出目錄. 默認(rèn)為 "outputs".
early_stopping_patience_epochs (int, optional): 在 epoch 結(jié)束時監(jiān)視 val_loss, 如果 val_loss 在指定的 epoch 數(shù)之后沒有改善(即下降),
則停止訓(xùn)練. 若設(shè)置 0 表示禁用提前停止. 默認(rèn)為 0(禁用)
precision (int, optional): 設(shè)置精度訓(xùn)練-雙精度(64), 全精度(32)或半精度(16). 默認(rèn)值為 32.
logger (pytorch_lightning.loggers) : PyTorch Lightning支持的任何記錄器. 默認(rèn)為 "default". 如果為 "default",
則使用 pytorch lightning default logger, 用于記錄訓(xùn)練過程.
dataloader_num_workers (int, optional): 設(shè)置加載 train/test/val dataloader 的進(jìn)程數(shù)量
save_only_last_epoch (bool, optional): 如果為 True, 則僅保存最后一個 epoch, 否則將保存每個 epoch 的分詞器和模型
"""
self.data_module = myLightningDataModule(
train_df=train_df,
test_df=eval_df,
tokenizer=self.tokenizer,
batch_size=batch_size,
source_max_token_len=source_max_token_len,
target_max_token_len=target_max_token_len,
num_workers=dataloader_num_workers,
)
self.T5_Model = myLightningModel(
tokenizer=self.tokenizer,
model=self.model,
output_dir=output_dir, # 保存 tokenizer 和 model 的路徑
save_only_last_epoch=save_only_last_epoch, # 只保存最后一輪的 checkpoint
)
# 添加回調(diào)方法, 用于顯示模型訓(xùn)練的進(jìn)度, 更新頻率為 5
callbacks = [TQDMProgressBar(refresh_rate=5)]
if early_stopping_patience_epochs > 0:
early_stop_callback = EarlyStopping(
monitor="val_loss",
min_delta=0.00,
patience=early_stopping_patience_epochs,
verbose=True,
mode="min",
)
callbacks.append(early_stop_callback)
# 如果有 gpu, 則添加
gpus = 1 if use_gpu else 0
# 添加 logger(日志器)
loggers = True if logger == "default" else logger
# prepare trainer(訓(xùn)練器)
trainer = pl.Trainer(
default_root_dir='./', # 日志 和 checkpoint 的路徑
logger=loggers,
enable_checkpointing=False, # 不保存 checkpoint
callbacks=callbacks,
max_epochs=max_epochs,
gpus=gpus,
precision=precision,
log_every_n_steps=1, # 每訓(xùn)練 1 步(step)就記錄一下日志.
)
# fit trainer(訓(xùn)練器)
trainer.fit(self.T5_Model, self.data_module)
def load_model(
self,
model_dir: str = "outputs",
use_gpu: bool = False
):
"""
加載某一個 checkpoint, 即加載模型
參數(shù):
model_type (str, optional): "t5" 或 "mt5". 默認(rèn)為 "t5".
model_dir (str, optional): 模型目錄的路徑. 默認(rèn)為 "outputs".
use_gpu (bool, optional): 如果為 True, 模型使用 gpu 進(jìn)行推理/預(yù)測. 默認(rèn)為 True.
"""
self.model = T5ForConditionalGeneration.from_pretrained(f"{model_dir}")
self.tokenizer = T5Tokenizer.from_pretrained(f"{model_dir}")
if use_gpu:
if torch.cuda.is_available():
self.device = torch.device("cuda")
else:
raise "exception ---> no gpu found. set use_gpu=False, to use CPU"
else:
self.device = torch.device("cpu")
def predict(
self,
source_text: str,
max_length: int = 512,
num_return_sequences: int = 1,
num_beams: int = 4, # 按照 t5 論文里寫的來
top_k: int = 50,
top_p: float = 0.95,
# do_sample: bool = True,
repetition_penalty: float = 2.5,
length_penalty: float = 0.6, # 按照 t5 論文里寫的來
early_stopping: bool = True,
skip_special_tokens: bool = True,
clean_up_tokenization_spaces: bool = True,
):
"""
生成 T5 模型的預(yù)測(文本)
參數(shù):
source_text (str): 任何用于生成預(yù)測的文本, 即源文本
max_length (int, optional): 預(yù)測的最大 token 長度. 默認(rèn)值為 512.
num_return_sequences (int, optional): 要返回的預(yù)測數(shù). 默認(rèn)值為 1.
num_beams (int, optional): beams 搜索的數(shù)量. 默認(rèn)值為 2.
top_k (int, optional): 用于 top-k 篩選的最高概率詞匯表 tokens 的數(shù)量. 默認(rèn)值為 50.
top_p (float, optional): 如果設(shè)置為 float < 1, 那只有概率加起來等于 top_p 或更高的最可能 token 的最小集被保留用于生成. 默認(rèn)值為 0.95.
do_sample (bool, optional): 是否使用抽樣; 否則使用貪婪解碼.默認(rèn)值為 True.
repetition_penalty (float, optional): 重復(fù)懲罰的參數(shù). 1.0 意味著沒有懲罰. 更多細(xì)節(jié)請參見[本文]
(https://arxiv.org/pdf/1909.05858.pdf)。默認(rèn)值為 2.5.
length_penalty (float, optional): 基于 beam 生成所使用的長度的指數(shù)懲罰. length_penalty > 0.0 表示促進(jìn)更長的序列,
而 length_penalty < 0.0 表示鼓勵較短的序列. 默認(rèn)值為 1.0.
early_stopping (bool, optional): 是否在每批至少有 num_beams 條語句完成時停止 beam search. 默認(rèn)值為 True.
skip_special_tokens (bool, optional): 是否跳過特殊的 token, 例如 <pad>, 默認(rèn)值為 True.
clean_up_tokenization_spaces (bool, optional): 是否清理 tokens 的空間. 默認(rèn)值為 True.
返回:
list[str]: 返回預(yù)測的文本, 即摘要
"""
input_ids = self.tokenizer.encode(
source_text,
return_tensors="pt",
add_special_tokens=True
)
if torch.cuda.is_available():
self.device = torch.device("cuda")
self.model = self.model.to(self.device)
input_ids = input_ids.to(self.device) # 放入 gpu
generated_ids = self.model.generate(
input_ids=input_ids,
num_beams=num_beams,
max_length=max_length,
repetition_penalty=repetition_penalty,
length_penalty=length_penalty,
early_stopping=early_stopping,
top_k=top_k,
top_p=top_p,
num_return_sequences=num_return_sequences,
)
predictions_text = [
self.tokenizer.decode(
every_ids,
skip_special_tokens=skip_special_tokens,
clean_up_tokenization_spaces=clean_up_tokenization_spaces,
)
for every_ids in generated_ids
]
return predictions_text
if __name__ == '__main__':
torch.cuda.empty_cache()
pl.seed_everything(42) # 設(shè)置隨機(jī)種子, 方便復(fù)現(xiàn)
train_data_path = "train.csv"
t5_train_df = pd.read_csv(train_data_path, sep='\t')
print('顯示前 5 個樣例:\n', t5_train_df.head(), '\n總的新聞和摘要對數(shù)為::', len(t5_train_df))
# T5 模型要求數(shù)據(jù)幀(dataframe)有2列: "source_text" 和 "target_text"
t5_train_df = t5_train_df.rename(columns={"summary": "target_text", "document": "source_text"})
t5_train_df = t5_train_df[['source_text', 'target_text']]
# T5 模型需要一個與任務(wù)相關(guān)的前綴(prefix): 因為它是一個摘要任務(wù), 我們將添加一個前綴 "summary:"
t5_train_df['source_text'] = "summarize: " + t5_train_df['source_text']
print('顯示新改造的所有(新聞和摘要對)樣例:\n', t5_train_df) # 平均的文檔長度為 1212, 摘要長度為 82
t5_train_df, t5_valid_df = train_test_split(t5_train_df, test_size=0.1)
print(t5_train_df.shape, t5_valid_df.shape)
# **************** 以下是模型的訓(xùn)練代碼 ****************
t5_model = myModel_for_Train()
t5_model.from_pretrained(model_name="t5-base")
t5_model.train(
train_df=t5_train_df,
eval_df=t5_valid_df,
source_max_token_len=768,
target_max_token_len=64,
batch_size=4,
max_epochs=10,
use_gpu=True,
)
print('模型已經(jīng)訓(xùn)練好了!')
四、訓(xùn)練結(jié)果
● 通過在 Anaconda Prompt (Anaconda3)
終端,對應(yīng)的環(huán)境里面輸入 tensorboard --logdir=你的events.out.tfevents文件路徑
,然后再用瀏覽器打開 http://localhost:6006/
網(wǎng)址。
比如我的 events.out.tfevents 文件路徑是:
tensorboard --logdir=C:\Users\xxx\Desktop\simpleT5-main\t5_summary_test\lightning_logs\version_0
● 顯示訓(xùn)練過程類似如下:可見,當(dāng) val_loss
在 step=100
時達(dá)到最小,即保存此時刻的模型(注:此圖不是上述代碼的結(jié)果,上述結(jié)果的 val_loss
一直在上升…,就不太好看,所以我隨便找了一個訓(xùn)練過程)。
● 在 Pycharm 中的終端,我截了部分輸出圖如下:
● 然后,我對驗證集進(jìn)行 ROUGE
分?jǐn)?shù)計算:
if __name__ == '__main__':
...
...
...
# **************** 以下是驗證集的Rouge評測和生成測試 ****************
test_t5_model = myModel_for_Train()
test_t5_model.load_model("outputs/simple_T5", use_gpu=True)
print('模型已經(jīng)加載好了!')
# 對驗證集進(jìn)行 ROUGE 分?jǐn)?shù)計算
my_rouge = Rouge()
rouge_1, rouge_2, rouge_l_f1, rouge_l_p, rouge_l_r = 0, 0, 0, 0, 0
for ind in range(len(t5_valid_df)):
input_text = t5_valid_df.iloc[ind]['source_text']
output_text = test_t5_model.predict(input_text)
label_text = t5_valid_df.iloc[ind]['target_text']
result = my_rouge.get_scores(output_text, [label_text], avg=True) # 取一個 batch 的平均
rouge_1 += result['rouge-1']['f']
rouge_2 += result['rouge-2']['f']
rouge_l_f1 += result['rouge-l']['f']
rouge_l_p += result['rouge-l']['p']
rouge_l_r += result['rouge-l']['r']
# print(ind, rouge_1 / (ind + 1), rouge_2 / (ind + 1), rouge_l_f1 / (ind + 1), rouge_l_p / (ind + 1),
# rouge_l_r / (ind + 1))
print('驗證集平均的 Rouge_1: {}, Rouge_2: {}, Rouge_l_f1: {}, Rouge_l_p: {}, Rouge_l_r: {}'.format(
rouge_1 / len(t5_valid_df), rouge_2 / len(t5_valid_df), rouge_l_f1 / len(t5_valid_df),
rouge_l_p / len(t5_valid_df), rouge_l_r / len(t5_valid_df)))
text_to_summarize = """summarize: Rahul Gandhi has replied to Goa CM Manohar Parrikar's letter,
which accused the Congress President of using his "visit to an ailing man for political gains".
"He's under immense pressure from the PM after our meeting and needs to demonstrate his loyalty by attacking me,"
Gandhi wrote in his letter. Parrikar had clarified he didn't discuss Rafale deal with Rahul.
"""
print('生成的摘要為:', test_t5_model.predict(text_to_summarize))
● 運行結(jié)果如下:
驗證集平均的 Rouge_1: 0.3369, Rouge_2: 0.1432, Rouge_l_f1: 0.3158, Rouge_l_p: 0.4136, Rouge_l_r: 0.2692
生成的摘要為:['Goa CM Manohar Parrikar accuses the Congress president of using his visit to an ailing man for political gains. Gandhi says he needs to demonstrate his loyalty by attacking me.']
● 如果加入 scheduler
(即把余弦學(xué)習(xí)率加入進(jìn) configure_optimizers()
中),結(jié)果會更好一點:
def configure_optimizers(self): # 在該類初始化時即被調(diào)用
""" 配置優(yōu)化器(optimizers) """
optimizer = AdamW(self.parameters(), lr=0.0001)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10, eta_min=0) # T_max表示半個周期的大小
return {
"optimizer": optimizer,
"lr_scheduler": {"scheduler": scheduler}
}
# return AdamW(self.parameters(), lr=0.0001)
驗證集平均的 Rouge_1: 0.4199, Rouge_2: 0.2320, Rouge_l_f1: 0.4033, Rouge_l_p: 0.5157, Rouge_l_r: 0.3468
五、項目鏈接
● 啊哈,2023-3-15 寫的文章,到了 2023-7-9 才把項目鏈接掛到 github 上,大伙久等了??
● 項目鏈接:https://github.com/Wangdoudou8/text-summarization-csdn/tree/main
六、補充說明
● 若有寫得不對的地方,或有疑問,歡迎評論交流。文章來源:http://www.zghlxwxcb.cn/news/detail-448112.html
?? ?? ??文章來源地址http://www.zghlxwxcb.cn/news/detail-448112.html
到了這里,關(guān)于如何用pytorch做文本摘要生成任務(wù)(加載數(shù)據(jù)集、T5 模型參數(shù)、微調(diào)、保存和測試模型,以及ROUGE分?jǐn)?shù)計算)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!