• 基于一个多分类问题比较bert单任务训练和多任务训练


    笔者在学习基于huggingface实现多分类问题时,使用了kaggle比赛中的Feedback Prize - Predicting Effective Arguments中的数据集。

    Feedback Prize - Predicting Effective Arguments/Dataset

    本次比赛的目标是将学生写作中的争论元素分类为“有效”、“充分”或“无效” 。

    提供的数据集包含美国 6-12 年级学生撰写的议论文。这些文章由专家评分者注释,用于议论文中常见的话语元素:

    Lead - 以统计数据、引文、描述或其他一些手段开始的介绍

    Position - 对主要问题的意见或结论

    Claim - 支持该立场的主张

    Counterclaim - 反驳另一项主张或对该立场提出相反理由的主张

    Rebuttal- 反驳反诉的主张

    Evidence - 支持主张、反诉或反驳的想法或例子

    Concluding Statement - 重申声明的结论性声明

    参赛者的任务是预测每个话语元素的质量等级。人类读者将每个修辞或论证元素按质量递增的顺序评为以下之一: Ineffective  Adequate  Effective

    我们这里主要使用这个数据集的train.csvtest.csv,它们的内容如下:

    1. train.csv ...
    2. discourse_id essay_id discourse_text discourse_type discourse_effectiveness
    3. 0 0013cc385424 007ACE74B050 Hi, i'm Isaac, i'm going to be writing about h... Lead Adequate
    4. 1 9704a709b505 007ACE74B050 On my perspective, I think that the face is a ... Position Adequate
    5. 2 c22adee811b6 007ACE74B050 I think that the face is a natural landform be... Claim Adequate
    6. 3 a10d361e54e4 007ACE74B050 If life was on Mars, we would know by now. The... Evidence Adequate
    7. 4 db3e453ec4e2 007ACE74B050 People thought that the face was formed by ali... Counterclaim Adequate
    8. test.csv ...
    9. discourse_id essay_id discourse_text discourse_type
    10. 0 a261b6e14276 D72CB1C11673 Making choices in life can be very difficult. ... Lead
    11. 1 5a88900e7dc1 D72CB1C11673 Seeking multiple opinions can help a person ma... Position
    12. 2 9790d835736b D72CB1C11673 it can decrease stress levels Claim
    13. 3 75ce6d68b67b D72CB1C11673 a great chance to learn something new Claim
    14. 4 93578d946723 D72CB1C11673 can be very helpful and beneficial. Claim

    当然,针对这个比赛不是仅仅只用一个预训练模型可以解决的,我们这里主要是借这个数据集来简单做一个bert多分类的尝试。

    那么一个思路就是使用bert微调,在bert的输出层中的分类头[CLS]取出,再映射到一层MLP中,例如在这个任务中,我们想要完成一个三分类的任务,希望我们输入的句子最后被分为Ineffective,AdequateEffective三类。

    那么直接上代码,这里的预训练模型我使用的是microsoft/deberta-base,在使用的角度来说,我们不是为了比较debertabert,所以就把它当做bert就行。

    1. from transformers import AutoConfig, AutoModel, AutoTokenizer
    2. import torch
    3. import time
    4. from transformers import get_cosine_schedule_with_warmup
    5. from d2l import torch as d2l
    6. import pandas as pd
    7. # 定义下游任务模型
    8. class Model(torch.nn.Module):
    9. def __init__(self, checkpoint, config):
    10. super().__init__()
    11. self.pretrained = AutoModel.from_pretrained(checkpoint, config=config)
    12. self.fc = torch.nn.Sequential(torch.nn.Linear(768, 3))
    13. def forward(self, input_ids, attention_mask, token_type_ids):
    14. logits = self.pretrained(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
    15. logits = logits.last_hidden_state[:, 0]
    16. logits = self.fc(logits)
    17. logits = logits.softmax(dim=1)
    18. return logits
    19. # 定义数据集
    20. class myDataset(torch.utils.data.Dataset):
    21. def __init__(self, sentences, attention_mask, token_type_ids,label ):
    22. super(myDataset, self).__init__()
    23. self.sentences = torch.tensor(sentences)
    24. self.attention_mask = torch.tensor(attention_mask)
    25. self.token_type_ids = torch.tensor(token_type_ids)
    26. self.label = torch.tensor(label)
    27. def __len__(self):
    28. return self.sentences.shape[0]
    29. def __getitem__(self, idx):
    30. return self.sentences[idx], self.attention_mask[idx], self.token_type_ids[idx], self.label[idx]
    31. # 定义测试数据集
    32. class testDataset(torch.utils.data.Dataset):
    33. def __init__(self, sentences, attention_mask, token_type_ids):
    34. super(testDataset, self).__init__()
    35. self.sentences = torch.tensor(sentences)
    36. self.attention_mask = torch.tensor(attention_mask)
    37. self.token_type_ids = torch.tensor(token_type_ids)
    38. def __len__(self):
    39. return self.sentences.shape[0]
    40. def __getitem__(self, idx):
    41. return self.sentences[idx], self.attention_mask[idx], self.token_type_ids[idx]
    42. # 读数据文件
    43. def load_data(file_path, tokenizer):
    44. df = pd.read_csv(file_path)
    45. sentences = df['discourse_text'].tolist()
    46. label_effectiveness = df['discourse_effectiveness'].replace({'Adequate':0, 'Effective':1, 'Ineffective':2}).tolist()
    47. token_type_ids, attention_mask, input_ids = [], [], []
    48. for sentence in sentences:
    49. encode_dict = tokenizer.encode_plus(sentence, max_length=512, padding="max_length", truncation=True)
    50. input_ids.append(encode_dict["input_ids"])
    51. token_type_ids.append(encode_dict["token_type_ids"])
    52. attention_mask.append(encode_dict["attention_mask"])
    53. return input_ids, label_effectiveness, token_type_ids, attention_mask
    54. # 训练函数
    55. def train(net, train_iter, lr, weight_decay, num_epochs, devices):
    56. total_time = 0
    57. train_len = len(Inputid_train)
    58. train_loss, train_acc = [], []
    59. net = torch.nn.DataParallel(net.to(devices[0]))
    60. loss = torch.nn.CrossEntropyLoss()
    61. optimizer = torch.optim.AdamW(net.parameters(), lr=lr, weight_decay=weight_decay)
    62. schedule = get_cosine_schedule_with_warmup(
    63. optimizer, num_warmup_steps=len(train_iter), num_training_steps=num_epochs*len(train_iter)
    64. )
    65. for epoch in range(num_epochs):
    66. start_of_epoch = time.time()
    67. cor = 0
    68. loss_sum = 0
    69. net.train()
    70. for idx,(ids,att_mask,type,y) in enumerate(train_iter):
    71. optimizer.zero_grad()
    72. ids, att_mask,type, y = ids.to(devices[0]), att_mask.to(devices[0]),type.to(devices[0]),y.to(devices[0])
    73. out_train = net(ids,att_mask,type)
    74. l = loss(out_train, y)
    75. l.backward()
    76. optimizer.step()
    77. schedule.step()
    78. loss_sum += l.item()
    79. if(idx + 1) % 20 == 0:
    80. print("Epoch {:04d} | Step {:06d}/{:06d} | Loss {:.4f} | Time {:.0f}".format(epoch + 1, idx + 1, len(train_iter), loss_sum / (idx + 1), time.time() - start_of_epoch))
    81. out_train = out_train.argmax(dim=1)
    82. cor += (out_train == y).sum()
    83. cor = float(cor)
    84. acc = float(cor /train_len)
    85. print(acc)
    86. if epoch % 1 == 0:
    87. print(f'epoch {epoch + 1}, train_loss {loss_sum / (len(train_iter))}, train_acc {acc}')
    88. train_loss.append(loss_sum / len(train_iter))
    89. train_acc.append(acc)
    90. end_of_epoch = time.time()
    91. print("epoch {} duration:".format(epoch + 1), end_of_epoch - start_of_epoch)
    92. total_time += end_of_epoch - start_of_epoch
    93. print("total training time: ",total_time)
    94. # 测试函数
    95. def eval(test_path, net, devices, test_batch_size):
    96. df = pd.read_csv(test_path)
    97. sentences = df['discourse_text'].tolist()
    98. token_type_ids, attention_mask, input_ids = [], [], []
    99. for sentence in sentences:
    100. encode_dict = tokenizer.encode_plus(sentence, max_length=512, padding="max_length", truncation=True)
    101. input_ids.append(encode_dict["input_ids"])
    102. token_type_ids.append(encode_dict["token_type_ids"])
    103. attention_mask.append(encode_dict["attention_mask"])
    104. test_iter = torch.utils.data.DataLoader(testDataset(input_ids, attention_mask, token_type_ids), test_batch_size, True)
    105. net.eval()
    106. with torch.no_grad():
    107. for ids, att, tpe in test_iter:
    108. ids, att, tpe = ids.to(devices[0]), att.to(devices[0]), tpe.to(devices[0])
    109. out_test = net(ids , att , tpe)
    110. return out_test
    111. checkpoint = 'microsoft/deberta-base'
    112. tokenizer = AutoTokenizer.from_pretrained(checkpoint)
    113. config = AutoConfig.from_pretrained(checkpoint)
    114. train_path = '/home/cjw/kaggle/feedback/train.csv'
    115. test_path = '/home/cjw/kaggle/feedback/test.csv'
    116. Inputid_train, Labelid_train, typeids_train, inputmask_train = load_data(train_path, tokenizer)
    117. batch_size = 8
    118. dataset = myDataset(Inputid_train, inputmask_train, typeids_train, Labelid_train)
    119. train_iter = torch.utils.data.DataLoader(dataset, batch_size, True)
    120. net = Model(checkpoint, config)
    121. num_epochs, lr, weight_decay, devices = 10, 2e-5, 1e-4, d2l.try_all_gpus()
    122. print("baseline:",checkpoint)
    123. print("training...")
    124. train(net, train_iter, lr, weight_decay, num_epochs, devices)
    125. print("evaling...")
    126. predictions = eval(test_path, net, devices, 10).cpu()
    127. submission = pd.read_csv('/home/cjw/kaggle/feedback/sample_submission.csv')
    128. submission['Adequate'] = predictions[:, 0]
    129. submission['Effective'] = predictions[:, 1]
    130. submission['Ineffective'] = predictions[:, 2]
    131. print(submission)
    132. submission.to_csv('submission.csv', index=False)

    这里只跑了10个epoch,因为数据集中text的长度大,跑起来也费时间,可以从结果看出lossaccuracy还在继续优化。

    结果如下:

    1. # 单任务
    2. baseline: microsoft/deberta-base
    3. training...
    4. ...
    5. epoch 6, train_loss 0.9447965523255608, train_acc 0.6066639466884265
    6. epoch 7, train_loss 0.934909532415649, train_acc 0.6165374677002584
    7. epoch 8, train_loss 0.9426709712978235, train_acc 0.6087583299333605
    8. epoch 9, train_loss 0.922136064427536, train_acc 0.6262749898000816
    9. epoch 10, train_loss 0.9017313893615317, train_acc 0.6450700394396844
    10. evaling...
    11. discourse_id Ineffective Adequate Effective
    12. 0 a261b6e14276 7.290736e-07 0.997184 0.002815
    13. 1 5a88900e7dc1 9.837714e-07 0.013967 0.986032
    14. 2 9790d835736b 3.147095e-07 0.999499 0.000500
    15. 3 75ce6d68b67b 3.179148e-07 0.999486 0.000514
    16. 4 93578d946723 3.764216e-07 0.999225 0.000775
    17. 5 2e214524dbe3 3.639196e-07 0.999282 0.000717
    18. 6 84812fc2ab9f 3.567264e-06 0.959672 0.040324
    19. 7 c668ff840720 3.426721e-06 0.143535 0.856462
    20. 8 739a6d00f44a 2.438440e-06 0.978529 0.021468
    21. 9 bcfae2c9a244 2.324955e-06 0.070954 0.929044

    紧接着我们来这样考虑一下,句子的“有效”、“充分”或“无效”三种情况与句子本身处于的话语元素肯定也是相关的,那么我们可不可以在训练中既将句子分类成为“有效”、“充分”或“无效”三种类别,并且也把它们分为七种话语元素类别呢,答案是肯定的。我们知道像bert在预训练时进行的就是一个多任务训练,mask language model任务sequence prediction任务

    我们这样简单尝试一下:两个任务都是分类,一般情况下,loss值应该是接近的,收敛速度可能也差不多,我们将两个分类任务的loss直接相加,然后优化。在模型上,我们设置两个MLP层,两个分类任务各使用一个MLP层。

    接下来验证我们的想法:

    1. from transformers import AutoConfig, AutoModel, AutoTokenizer
    2. import torch
    3. import time
    4. from transformers import get_cosine_schedule_with_warmup
    5. from d2l import torch as d2l
    6. import pandas as pd
    7. # 定义下游任务模型
    8. class Model(torch.nn.Module):
    9. def __init__(self, checkpoint, config):
    10. super().__init__()
    11. self.pretrained = AutoModel.from_pretrained(checkpoint, config=config)
    12. self.fc_a = torch.nn.Sequential(torch.nn.Linear(768, 3))
    13. self.fc_b = torch.nn.Sequential(torch.nn.Linear(768, 7))
    14. def forward(self, input_ids, attention_mask, token_type_ids, class_num=3):
    15. logits = self.pretrained(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)
    16. logits = logits.last_hidden_state[:, 0]
    17. if class_num == 3:
    18. logits = self.fc_a(logits)
    19. elif class_num == 7:
    20. logits = self.fc_b(logits)
    21. logits = logits.softmax(dim=1)
    22. return logits
    23. # 定义数据集
    24. class myDataset(torch.utils.data.Dataset):
    25. def __init__(self, sentences, attention_mask, token_type_ids, label_effectiveness, label_type):
    26. super(myDataset, self).__init__()
    27. self.sentences = torch.tensor(sentences)
    28. self.attention_mask = torch.tensor(attention_mask)
    29. self.token_type_ids = torch.tensor(token_type_ids)
    30. self.label_effectiveness = torch.tensor(label_effectiveness)
    31. self.label_type = torch.tensor(label_type)
    32. def __len__(self):
    33. return self.sentences.shape[0]
    34. def __getitem__(self, idx):
    35. return self.sentences[idx], self.attention_mask[idx], self.token_type_ids[idx], self.label_effectiveness[idx], self.label_type[idx]
    36. # 定义测试数据集
    37. class testDataset(torch.utils.data.Dataset):
    38. def __init__(self, sentences, attention_mask, token_type_ids):
    39. super(testDataset, self).__init__()
    40. self.sentences = torch.tensor(sentences)
    41. self.attention_mask = torch.tensor(attention_mask)
    42. self.token_type_ids = torch.tensor(token_type_ids)
    43. def __len__(self):
    44. return self.sentences.shape[0]
    45. def __getitem__(self, idx):
    46. return self.sentences[idx], self.attention_mask[idx], self.token_type_ids[idx]
    47. # 读数据文件
    48. def load_data(file_path, tokenizer):
    49. df = pd.read_csv(file_path)
    50. sentences = df['discourse_text'].tolist()
    51. label_effectiveness = df['discourse_effectiveness'].replace({'Adequate':0, 'Effective':1, 'Ineffective':2}).tolist()
    52. label_type = df['discourse_type'].replace({'Lead':0, 'Position':1, 'Claim':2, 'Counterclaim':3, 'Rebuttal':4, 'Evidence':5, 'Concluding Statement':6})
    53. token_type_ids, attention_mask, input_ids = [], [], []
    54. for sentence in sentences:
    55. encode_dict = tokenizer.encode_plus(sentence, max_length=512, padding="max_length", truncation=True)
    56. input_ids.append(encode_dict["input_ids"])
    57. token_type_ids.append(encode_dict["token_type_ids"])
    58. attention_mask.append(encode_dict["attention_mask"])
    59. return input_ids, label_effectiveness, token_type_ids, attention_mask, label_type
    60. # 训练函数
    61. def train(net, train_iter, lr, weight_decay, num_epochs, devices):
    62. total_time = 0
    63. train_len = len(Inputid_train)
    64. net = torch.nn.DataParallel(net.to(devices[0]))
    65. loss = torch.nn.CrossEntropyLoss()
    66. optimizer = torch.optim.AdamW(net.parameters(), lr=lr, weight_decay=weight_decay)
    67. schedule = get_cosine_schedule_with_warmup(
    68. optimizer, num_warmup_steps=len(train_iter), num_training_steps=num_epochs*len(train_iter)
    69. )
    70. for epoch in range(num_epochs):
    71. start_of_epoch = time.time()
    72. loss_sum = 0
    73. cor_a = 0
    74. net.train()
    75. for idx,(ids,att_mask,type,y_a, y_b) in enumerate(train_iter):
    76. optimizer.zero_grad()
    77. ids, att_mask,type, y_a, y_b = ids.to(devices[0]), att_mask.to(devices[0]), type.to(devices[0]), y_a.to(devices[0]), y_b.to(devices[0])
    78. output_a = net(ids, att_mask, type)
    79. output_b = net(ids, att_mask, type, class_num = 7)
    80. l_a = loss(output_a, y_a)
    81. l_b = loss(output_b, y_b)
    82. l = l_a + l_b
    83. l.backward()
    84. optimizer.step()
    85. schedule.step()
    86. loss_sum += l.item()
    87. if(idx + 1) % 20 == 0:
    88. print("Epoch {:04d} | Step {:06d}/{:06d} | Loss {:.4f} | Time {:.0f}".format(epoch + 1, idx + 1, len(train_iter), loss_sum / (idx + 1), time.time() - start_of_epoch))
    89. output_a = output_a.argmax(dim=1)
    90. cor_a += (output_a == y_a).sum()
    91. acc_a = float(cor_a /train_len)
    92. if epoch % 1 == 0:
    93. print(f'epoch {epoch + 1}, train_loss {loss_sum / (len(train_iter))}, train_acc_a {acc_a}')
    94. end_of_epoch = time.time()
    95. print("epoch {} duration:".format(epoch + 1), end_of_epoch - start_of_epoch)
    96. total_time += end_of_epoch - start_of_epoch
    97. print("total training time: ",total_time)
    98. # 测试函数
    99. def eval(test_path, net, devices, test_batch_size):
    100. df = pd.read_csv(test_path)
    101. sentences = df['discourse_text'].tolist()
    102. token_type_ids, attention_mask, input_ids = [], [], []
    103. for sentence in sentences:
    104. encode_dict = tokenizer.encode_plus(sentence, max_length=512, padding="max_length", truncation=True)
    105. input_ids.append(encode_dict["input_ids"])
    106. token_type_ids.append(encode_dict["token_type_ids"])
    107. attention_mask.append(encode_dict["attention_mask"])
    108. test_iter = torch.utils.data.DataLoader(testDataset(input_ids, attention_mask, token_type_ids), test_batch_size, True)
    109. net.eval()
    110. with torch.no_grad():
    111. for ids, att, tpe in test_iter:
    112. ids, att, tpe = ids.to(devices[0]), att.to(devices[0]), tpe.to(devices[0])
    113. out_test = net(ids , att , tpe)
    114. return out_test
    115. checkpoint = 'microsoft/deberta-base'
    116. tokenizer = AutoTokenizer.from_pretrained(checkpoint)
    117. config = AutoConfig.from_pretrained(checkpoint)
    118. train_path = '/home/cjw/kaggle/feedback/train.csv'
    119. test_path = '/home/cjw/kaggle/feedback/test.csv'
    120. Inputid_train, Labelid_train, typeids_train, inputmask_train, label_type = load_data(train_path, tokenizer)
    121. batch_size = 8
    122. train_iter = torch.utils.data.DataLoader(myDataset(Inputid_train, inputmask_train, typeids_train, Labelid_train, label_type), batch_size, drop_last = True)
    123. net = Model(checkpoint, config)
    124. num_epochs, lr, weight_decay, devices = 10, 2e-5, 1e-4, d2l.try_all_gpus()
    125. print("baseline:",checkpoint)
    126. print("training...")
    127. train(net, train_iter, lr, weight_decay, num_epochs, devices)
    128. print("evaling...")
    129. predictions = eval(test_path, net, devices, 10).cpu()
    130. submission = pd.read_csv('/home/cjw/kaggle/feedback/sample_submission.csv')
    131. submission['Adequate'] = predictions[:, 0]
    132. submission['Effective'] = predictions[:, 1]
    133. submission['Ineffective'] = predictions[:, 2]
    134. print(submission)
    135. submission.to_csv('submission.csv', index=False)

    同样只跑了10个epoch,我们控制变量,两个代码只有单任务和多任务的区别,也可以从结果看出,lossaccuracy还在继续优化,但我们这里只是比较一下单任务和多任务,所以不关注它最好的结果。

    1. # 多任务
    2. baseline: microsoft/deberta-base
    3. training...
    4. ...
    5. epoch 6, train_loss 2.396573264969835, train_acc_a 0.7231334447860718
    6. epoch 7, train_loss 2.3707293589823393, train_acc_a 0.7420101165771484
    7. epoch 8, train_loss 2.350535287120267, train_acc_a 0.7575955390930176
    8. epoch 9, train_loss 2.3366618679968134, train_acc_a 0.768774688243866
    9. epoch 10, train_loss 2.3300860160063865, train_acc_a 0.7741058468818665
    10. evaling...
    11. discourse_id Ineffective Adequate Effective
    12. 0 a261b6e14276 0.000032 0.749933 0.250035
    13. 1 5a88900e7dc1 0.000004 0.000244 0.999752
    14. 2 9790d835736b 0.000010 0.999960 0.000030
    15. 3 75ce6d68b67b 0.000047 0.993598 0.006354
    16. 4 93578d946723 0.000030 0.726694 0.273276
    17. 5 2e214524dbe3 0.000015 0.000096 0.999889
    18. 6 84812fc2ab9f 0.000065 0.988569 0.011366
    19. 7 c668ff840720 0.000003 0.000084 0.999913
    20. 8 739a6d00f44a 0.000016 0.999982 0.000002
    21. 9 bcfae2c9a244 0.000053 0.888959 0.110988

    我们通过对比两个结果的准确率,通过多任务训练的结果要比单任务训练的结果高13个百分点,可以得出在这个场景中,多提取一维特征对任务带来的提升。

    当然在此重申,我们这里主要是为了学习利用预训练模型进行多任务学习,如果去进行比赛或者落地时这样做太过奢侈,最好先进行特征过程再输入预训练模型,不然一定会超出显存设置或者要跑很长时间。

    另外如果两个任务差别比较大,学习率等超参数不一定要设置为一样。

  • 相关阅读:
    《Orange‘s 一个操作系统的实现》第四章
    基于 elementUI / elementUI plus,实现 主要色(主题色)的一件换色(换肤)
    Java面试题08
    车载电子电器防水防尘等级介绍
    Window 11中安装Rust编译环境和集成开发环境
    数商云S2B2B商城系统:商品管理功能亮点,为建筑建材企业采购提供极致网购体验
    关于TOMCAT9.0.33导致VMware内IDEA闪退
    javaee spring aop 切入点表达式
    mysql学习笔记
    大数据中心系统集成资质
  • 原文地址:https://blog.csdn.net/cjw838982809/article/details/126522421