import os import sys import time import tqdm import glob import copy import logging import argparse import pandas as pd import numpy as np from scipy.stats import pearsonr, spearmanr from sklearn.metrics import accuracy_score, balanced_accuracy_score import torch import torch.nn as nn from torch.nn import DataParallel from torch.utils.data import Dataset, DataLoader import torch.nn.functional as F from torch_geometric.nn import GraphConv,GATv2Conv,RGCNConv from transformers import AutoTokenizer, AutoModel, get_linear_schedule_with_warmup os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2,3,4,5,6,7' device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') parser = argparse.ArgumentParser() # folders parser.add_argument('--data_folder', type=str, default='./data/monologue_split_orig', help='input data folder') parser.add_argument('--model_folder', type=str, default='./model/exp_name', help='model folder') parser.add_argument('--log_file', type=str, default='./log/exp_name.log', help='log file') parser.add_argument('--train_filename', type=str, default='train.csv', help='train data file') parser.add_argument('--valid_filename', type=str, default='valid.csv', help='valid data file') parser.add_argument('--test_filename', type=str, default='test.csv', help='test data file') # flags parser.add_argument('--train_flag', type=int, default=1, help='whether to train the model') parser.add_argument('--test_flag', type=int, default=1, help='whether to test the model') parser.add_argument('--real_time_flag', type=int, default=1, help='whether to test the model in real time setting, which means using only first several turns of a dialogue') #parser.add_argument('--data_split', type=str, default='split1', help='which data split to use') parser.add_argument('--max_length', type=int, default=512, help='max length of input sequence') parser.add_argument('--base_model_name', type=str, default='studio-ousia/luke-japanese-base', help='name of pretrained model in huggingface') parser.add_argument('--use_dropout', type=int, default=0, help='whether to use dropout after base model') parser.add_argument('--multilinear', type=int, default=1, help='whether to use multiple linear layers after base model 0 means single linear layer') parser.add_argument('--hidden_size', type=int, default=16, help='hidden size of the first linear layer if multilinear is 1') parser.add_argument('--critertion_type', type=str, default='mae', help='criterion type, choose from mae mse') parser.add_argument('--multitask', type=str, default='11111', help='whether to use multitask learning, 1 means use, 0 means not use, the order is n e o a c') parser.add_argument('--lr', type=float, default=1e-5) parser.add_argument('--warmup_steps', type=int, default=150) parser.add_argument('--max_epoch', type=int, default=20, help='max epoch for training') parser.add_argument('--patience', type=int, default=3, help='patience for early stopping') parser.add_argument('--batch_size', type=int, default=128, help='batch size for training') parser.add_argument('--context', type=int, default=0, help='whether to use dialogue scenario. 0 means monologue, 1 means dialogue') parser.add_argument('--context_model_type', type=str, default='linear', help='context model type, choose from linear gcn-nospk2pred-lastnode') parser.add_argument('--head_num', type=int, default=3, help='head number of gcn') parser.add_argument('--model_variant', type=str, default='hcgnn', help='model variant, choose from gcn gatv2 rgcn hcgnn22 hcgnn3rel') parser.add_argument('--ensemble', type=int, default=0, help='whether to use ensemble model') parser.add_argument('--ensemble_model_folder', type=str, default='/share03/song/person_rec/model/nocontext_ensemble', help='folder of ensemble models') args = parser.parse_args() # variables input_data_folder = args.data_folder model_folder = args.model_folder log_file = args.log_file train_filename = args.train_filename valid_filename = args.valid_filename test_filename = args.test_filename train_file = os.path.join(input_data_folder, train_filename) valid_file = os.path.join(input_data_folder, valid_filename) test_file = os.path.join(input_data_folder, test_filename) os.makedirs(model_folder, exist_ok=True) os.makedirs(os.path.dirname(log_file), exist_ok=True) logging.basicConfig(filename=log_file, level=logging.INFO, format='%(asctime)s:%(levelname)s:%(message)s') train_flag = args.train_flag test_flag = args.test_flag real_time_flag = args.real_time_flag max_length = args.max_length # settings for base model base_model_name = args.base_model_name dropout = args.use_dropout multilinear = args.multilinear hidden_size = args.hidden_size # settings for training critertion_type = args.critertion_type multitask_str = args.multitask multitask = [int(flag) for flag in multitask_str] lr = args.lr warmup_steps = args.warmup_steps max_epoch = args.max_epoch patience = args.patience batch_size = args.batch_size # settings for dialogue scenario model context = args.context context_str = 'context' if context == 1 else 'nocontext' context_model_type = args.context_model_type if ('gcn' in context_model_type): max_length = 64 head_num = args.head_num model_variant = args.model_variant pad_length = -1 # settings for ensemble (not really working well) ensemble = args.ensemble ensemble_model_folder = args.ensemble_model_folder # constants for trail normalization min_label = 1 max_label = 7 # constants (not used) #exp_folder = './' #input_data_folder = os.path.join(exp_folder, 'data', f'{context_str}_data', data_split) #name = f'{context_str}_{data_split}_contextmodeltype-{context_model_type}_headnum{head_num}_modelvariant-{model_variant}_1' #name = f'{context_str}_{data_split}_{context_model_type}_{model_variant}' #model_folder = os.path.join(exp_folder, 'model', name) #log_file = os.path.join(exp_folder, 'log', f'{name}.log') def gene_data_from_csv(file_path, tokenizer, max_sentence_num=0): csv_data = pd.read_csv(file_path) dict_data = [] for i in range(len(csv_data)): data_sample = csv_data.iloc[i] text = data_sample['dialogue'] text = text.replace('\n', '') if (context == 0): if (max_sentence_num > 0): if (text.startswith('[CLS]')): text = text[len('[CLS]'):] if (text.startswith('[SEP]')): text = text[len('[SEP]'):] sentences = text.split('[SEP]') sentences = sentences[:max_sentence_num] text = '[CLS]' + '[SEP]'.join(sentences) text = text.replace('[CLS]', tokenizer.cls_token) text = text.replace('[SEP]', tokenizer.sep_token) if (not text.startswith(tokenizer.cls_token)): text = tokenizer.cls_token + text else: if (max_sentence_num > 0): if (text.startswith('[CLS]')): text = text[len('[CLS]'):] if (text.startswith('[SPK1]')): text = text[len('[SPK1]'):] sentences = text.split('[SPK1]') sentences = sentences[:max_sentence_num] text = '[CLS]' + '[SPK1]'.join(sentences) text = text.replace('[CLS]', tokenizer.cls_token) if (not text.startswith(tokenizer.cls_token)): text = tokenizer.cls_token + text assert text.startswith(tokenizer.cls_token) labels = [data_sample['n'], data_sample['e'], data_sample['o'], data_sample['a'], data_sample['c']] normalized_labels = [(label - min_label) / (max_label - min_label) for label in labels] dict_data.append({ 'text': text, 'labels': normalized_labels, }) return dict_data def gene_context_data(data, pad_length, tokenizer): context_data = [] for sample in data: text = sample['text'] text = text.replace('[CLS]', tokenizer.cls_token) text = text.replace('[SEP]', tokenizer.sep_token) text = text.replace(tokenizer.cls_token, '').replace(tokenizer.sep_token, '') text_tmp = text.replace('[SPK1]', tokenizer.sep_token).replace('[SPK2]', tokenizer.sep_token) if text_tmp.startswith(tokenizer.sep_token): text_tmp = text_tmp[len(tokenizer.sep_token):] sentences = text_tmp.split(tokenizer.sep_token) sentences_with_speaker = [] for i, sentence in enumerate(sentences): if sentence == '': continue if sentence.startswith(tokenizer.cls_token): sentence = sentence[len(tokenizer.cls_token):] if i % 2 == 0: s = '[SPK1]' + sentence else: s = '[SPK2]' + sentence sentences_with_speaker.append(s) if len(sentences_with_speaker) < pad_length: sentences_with_speaker += ['' for _ in range(pad_length - len(sentences_with_speaker))] else: sentences_with_speaker = sentences_with_speaker[:pad_length] context_data.append({ 'text': sentences_with_speaker, # each sentence is [SPK*]text 'labels': sample['labels'], }) return context_data class ConversationDataset(Dataset): def __init__(self, data, tokenizer,max_length): self.data = data self.tokenizer = tokenizer self.max_length=max_length def __len__(self): return len(self.data) def __getitem__(self, idx): sample = self.data[idx] labels = torch.tensor(sample['labels'], dtype=torch.float) input_ids=[] attention_mask=[] length=0 for text_row in sample['text']: if text_row !='': length+=1 encoding = self.tokenizer(text_row, padding="max_length", max_length=self.max_length, truncation=True, return_tensors="pt", add_special_tokens=True) input_ids.append(encoding['input_ids'].squeeze()) attention_mask.append(encoding['attention_mask'].squeeze()) return { 'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': torch.tensor(labels), 'current_length':length } class MultiTaskDataset(Dataset): def __init__(self, data, tokenizer, max_length): self.data = data self.tokenizer = tokenizer self.max_length = max_length def __len__(self): return len(self.data) def __getitem__(self, idx): sample = self.data[idx] encoding = self.tokenizer(sample['text'], max_length=self.max_length, padding='max_length', truncation=True, return_tensors='pt', add_special_tokens=False) return { 'input_ids': encoding['input_ids'].squeeze(), 'attention_mask': encoding['attention_mask'].squeeze(), 'labels': torch.tensor(sample['labels'], dtype=torch.float) } class MultiTaskModel(nn.Module): def __init__(self, base_model, dropout_flag, multilinear_flag): super(MultiTaskModel, self).__init__() self.base_model = base_model self.dropout_flag = dropout_flag self.multilinear_flag = multilinear_flag self.dropout_layer = nn.Dropout(0.1) if (self.multilinear_flag == 0): self.task_heads = nn.ModuleList([ nn.Sequential( nn.Linear(base_model.config.hidden_size, 1), ) for _ in range(5) ]) else: self.task_heads = nn.ModuleList([ nn.Sequential( nn.Linear(base_model.config.hidden_size, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1), ) for _ in range(5) ]) def forward(self, input_ids, attention_mask): outputs = self.base_model(input_ids=input_ids, attention_mask=attention_mask) pooled_output = outputs['pooler_output'] if self.dropout_flag == 1: pooled_output = self.dropout_layer(pooled_output) regression_outputs = [] for task_head in self.task_heads: regression_outputs.append(task_head(pooled_output)) return torch.cat(regression_outputs, dim=1) class GCN(nn.Module): def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(GCN, self).__init__() self.relations=1 self.output_dim=output_dim self.conv2 = nn.ModuleList([GraphConv(input_dim, hidden_dim1) for _ in range(self.relations)]) self.conv3 = nn.ModuleList([GraphConv(hidden_dim1, output_dim) for _ in range(self.relations)]) def forward(self, x, relationsedge_indices_relations): relation_outputs=[] for i, conv_layer in enumerate(self.conv2): relation_output=(conv_layer(x, relationsedge_indices_relations[-1])) relation_output=F.relu(relation_output) relation_output=self.conv3[i](relation_output, relationsedge_indices_relations[-1]) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) x=torch.cat(relation_outputs, dim=1) return x class GAT(nn.Module): def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(GAT, self).__init__() self.relations=1 self.heads=heads self.output_dim=hidden_dim1*self.heads self.conv1 = nn.ModuleList([GATv2Conv(input_dim, hidden_dim1,heads=self.heads) for _ in range(self.relations)]) def forward(self, x, relationsedge_indices_relations): relation_outputs=[] for i, conv_layer in enumerate(self.conv1): relation_output=(conv_layer(x, relationsedge_indices_relations[-1])) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) x=torch.cat(relation_outputs, dim=1) return x class RGCN (nn.Module): def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(RGCN, self).__init__() self.relations=1 self.heads=heads self.output_dim=output_dim self.conv1 = nn.ModuleList([RGCNConv(input_dim, hidden_dim1,2) for _ in range(self.relations)]) self.conv2 = nn.ModuleList([GraphConv(hidden_dim1, output_dim) for _ in range(self.relations)]) def forward(self, x, relationsedge_indices_relations,edge_type): relation_outputs=[] for i, conv_layer in enumerate(self.conv1): relation_output=(conv_layer(x, relationsedge_indices_relations[-1],edge_type)) relation_output=F.relu(relation_output) relation_output=self.conv2[i](relation_output, relationsedge_indices_relations[-1]) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) x=torch.cat(relation_outputs, dim=1) return x class GATv2GCN22(nn.Module): def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(GATv2GCN22, self).__init__() self.relations=relations self.heads=heads self.output_dim=output_dim self.conv1 = nn.ModuleList([GATv2Conv(input_dim, hidden_dim1,heads=self.heads) for _ in range(self.relations)]) self.conv2 = nn.ModuleList([GATv2Conv(hidden_dim1*self.heads, hidden_dim1,heads=self.heads) for _ in range(self.relations)]) self.conv3 = nn.ModuleList([GraphConv(hidden_dim1*self.heads, hidden_dim1) for _ in range(self.relations)]) self.conv4 = nn.ModuleList([GraphConv(hidden_dim1, output_dim) for _ in range(self.relations)]) def forward(self, x, relationsedge_indices_relations): relation_outputs=[] for i, conv_layer in enumerate(self.conv1): relation_output=(conv_layer(x, relationsedge_indices_relations[i])) relation_output=F.relu(relation_output) relation_output=self.conv2[i](relation_output, relationsedge_indices_relations[i]) relation_output=F.relu(relation_output) relation_output=self.conv3[i](relation_output, relationsedge_indices_relations[i]) relation_output=F.relu(relation_output) relation_output=self.conv4[i](relation_output, relationsedge_indices_relations[i]) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) x=torch.cat(relation_outputs, dim=1) return x class GATv2GCN(nn.Module): """ GATv2+GCN model with 4/6 relations (GAT->GCN) """ def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(GATv2GCN, self).__init__() self.relations=relations self.heads=heads self.output_dim=output_dim self.conv1 = nn.ModuleList([GATv2Conv(input_dim, hidden_dim1,heads=self.heads) for _ in range(self.relations)]) self.conv2 = nn.ModuleList([GraphConv(hidden_dim1*self.heads, hidden_dim1) for _ in range(self.relations)]) self.conv3 = nn.ModuleList([GraphConv(hidden_dim1, output_dim) for _ in range(self.relations)]) def forward(self, x, relationsedge_indices_relations): relation_outputs=[] for i, conv_layer in enumerate(self.conv1): relation_output=(conv_layer(x, relationsedge_indices_relations[i])) relation_output=F.relu(relation_output) relation_output=self.conv2[i](relation_output, relationsedge_indices_relations[i]) relation_output=F.relu(relation_output) relation_output=self.conv3[i](relation_output, relationsedge_indices_relations[i]) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) x=torch.cat(relation_outputs, dim=1) return x class GATv2GCN3REL(nn.Module): def __init__(self,input_dim, hidden_dim1, output_dim, relations, heads): super(GATv2GCN3REL, self).__init__() self.relations=relations self.heads=heads self.output_dim=output_dim self.conv1 = nn.ModuleList([GATv2Conv(input_dim, hidden_dim1,heads=self.heads) for _ in range(self.relations)]) self.conv2 = nn.ModuleList([GraphConv(hidden_dim1*self.heads, hidden_dim1) for _ in range(self.relations)]) self.conv3 = nn.ModuleList([GraphConv(hidden_dim1, output_dim) for _ in range(self.relations)]) self.conv4 = RGCNConv(input_dim, hidden_dim1,2) self.conv5 = GraphConv(hidden_dim1,output_dim) def forward(self, x, relationsedge_indices_relations,edge_type): relation_outputs=[] for i, conv_layer in enumerate(self.conv1): relation_output=(conv_layer(x, relationsedge_indices_relations[i])) relation_output=F.relu(relation_output) relation_output=self.conv2[i](relation_output, relationsedge_indices_relations[i]) relation_output=F.relu(relation_output) relation_output=self.conv3[i](relation_output, relationsedge_indices_relations[i]) relation_output=relation_output.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output) relation_output_rgcn=self.conv4(x, relationsedge_indices_relations[-1],edge_type) relation_output_rgcn=self.conv5(relation_output_rgcn,relationsedge_indices_relations[-1]) relation_output_rgcn=relation_output_rgcn.reshape(-1,1,self.output_dim) relation_outputs.append(relation_output_rgcn) x=torch.cat(relation_outputs, dim=1) return x class ScaledDotProductAttention(nn.Module): def __init__(self, dim: int): super(ScaledDotProductAttention, self).__init__() self.sqrt_dim = np.sqrt(dim) def forward(self, query, key, value,mask=None): score = torch.bmm(query, key.transpose(1, 2)) / self.sqrt_dim if mask is not None: score.masked_fill_(mask.view(score.size()), -float('Inf')) attn = F.softmax(score, -1) context = torch.bmm(attn, value) return context, attn def edge_perms(length): """ Method to construct the edges of a graph (a utterance) considering the all the past utterances. return: list of tuples. tuple -> (vertice(int), neighbor(int)) """ user_sys = set() sys_user = set() user_user = set() sys_sys = set() eff_array_user=[] eff_array_sys=[] for i in range(length): if i%2==0: eff_array_user.append(i) else: eff_array_sys.append(i) for i in range (length): if i%2==0: for j in eff_array_user: user_user.add((i,j)) for j in eff_array_sys: user_sys.add((i,j)) else: for j in eff_array_user: sys_user.add((i,j)) for j in eff_array_sys: sys_sys.add((i,j)) user_sys_user=user_sys.union(user_user) sys_user_sys=sys_user.union(sys_sys) return [user_user,user_sys, sys_sys, sys_user, sys_user_sys,user_sys_user] def batch_graphify_hgcn(features, lengths, device): node_features, edge_index1,edge_index2,edge_index3,edge_index4, edge_index5, edge_index6,edge_type= [], [], [],[],[],[],[],[] batch_size = features.size(0) length_sum = 0 for j in range(batch_size): cur_len = lengths[j].item() node_features.append(features[j, :cur_len, :]) perms = edge_perms(cur_len) perms_rec0 = [(item[0] + length_sum, item[1] + length_sum) for item in perms[0]] perms_rec1= [(item[0] + length_sum, item[1] + length_sum) for item in perms[1]] perms_rec2 = [(item[0] + length_sum, item[1] + length_sum) for item in perms[2]] perms_rec3 = [(item[0] + length_sum, item[1] + length_sum) for item in perms[3]] perms_rec4= [(item[0] + length_sum, item[1] + length_sum) for item in perms[4]] perms_rec5= [(item[0] + length_sum, item[1] + length_sum) for item in perms[5]] length_sum += cur_len for item, item_rec in zip(perms[0], perms_rec0): edge_index1.append(torch.tensor([item_rec[0], item_rec[1]])) #user_user edge_type.append(torch.tensor([0])) for item, item_rec in zip(perms[1], perms_rec1): edge_index2.append(torch.tensor([item_rec[0], item_rec[1]]))#user_sys edge_type.append(torch.tensor([1])) for item, item_rec in zip(perms[2], perms_rec2): edge_index3.append(torch.tensor([item_rec[0], item_rec[1]])) #sys_sys for item, item_rec in zip(perms[3], perms_rec3): edge_index4.append(torch.tensor([item_rec[0], item_rec[1]])) #sys_user for item, item_rec in zip(perms[4], perms_rec4): edge_index5.append(torch.tensor([item_rec[0], item_rec[1]])) #sys_user_sys for item, item_rec in zip(perms[5], perms_rec5): edge_index6.append(torch.tensor([item_rec[0], item_rec[1]])) #user_sys_user node_features = torch.cat(node_features, dim=0).to(device) # [E, D_g] edge_index = [torch.stack(edge_index1).t().contiguous().to(device),torch.stack(edge_index2).t().contiguous().to(device),torch.stack(edge_index3).t().contiguous().to(device),torch.stack(edge_index4).t().contiguous().to(device), torch.stack(edge_index5).t().contiguous().to(device), torch.stack(edge_index6).t().contiguous().to(device)] # [2, E] edge_type = torch.stack(edge_type).to(device) edge_type=edge_type.squeeze(-1) return node_features, edge_index,edge_type class ContextModel(nn.Module): def __init__(self, base_model, batch_size, max_conver_num,device, context_model_type, head_num, model_variant): super(ContextModel, self).__init__() self.base_model=base_model self.max_conver_num = max_conver_num self.device = device self.heads=head_num self.context_model_type = context_model_type in_hidden_dim = base_model.config.hidden_size gcn_hidden_dim1 = 512 gcn_out_dim = 256 if ('nospk2pred' in self.context_model_type): self.relations=2 else: self.relations=4 if (model_variant == 'gcn'): self.gcn = GCN(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear(gcn_out_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) elif (model_variant == 'gatv2'): self.gcn = GAT(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear(self.heads*gcn_hidden_dim1, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) elif (model_variant == 'rgcn'): self.gcn = RGCN(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear(gcn_out_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) elif (model_variant == 'hcgnn22'): self.gcn = GATv2GCN22(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear(self.relations*gcn_out_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) elif (model_variant == 'hcgnn3rel'): self.gcn = GATv2GCN3REL(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear((self.relations+1)*gcn_out_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) else: self.gcn = GATv2GCN(input_dim=in_hidden_dim, hidden_dim1=gcn_hidden_dim1,output_dim=gcn_out_dim,relations=self.relations,heads=self.heads).to(device) self.task_heads = nn.ModuleList([nn.Sequential(nn.Linear(self.relations*gcn_out_dim, hidden_size), nn.ReLU(), nn.Linear(hidden_size, 1),) for _ in range(5)]) self.attention=ScaledDotProductAttention(gcn_out_dim) def forward(self, input_ids, attention_mask, current_length): input_id = input_ids.reshape(-1, input_ids.shape[-1]) #torch.Size([96, 64]) attention_mask = attention_mask.reshape(-1, attention_mask.shape[-1]) batch_embeddings=self.base_model(input_ids=input_id, attention_mask=attention_mask).pooler_output #torch.Size([192, 768]) batch_embeddings = batch_embeddings.reshape(input_ids.shape[0], -1, batch_embeddings.shape[-1]) #torch.Size([4, 48, 768]) if model_variant == 'rgcn': features, edge_index,edge_type = batch_graphify_hgcn(batch_embeddings, current_length, self.device) gcn_features = self.gcn(features, edge_index,edge_type) elif model_variant == 'hcgnn3rel': features, edge_index,edge_type = batch_graphify_hgcn(batch_embeddings, current_length, self.device) gcn_features = self.gcn(features, edge_index,edge_type) gcn_features, attn = self.attention(gcn_features, gcn_features, gcn_features) else: features, edge_index, edge_type = batch_graphify_hgcn(batch_embeddings, current_length, self.device) gcn_features = self.gcn(features, edge_index) gcn_features, attn = self.attention(gcn_features, gcn_features, gcn_features) gcn_features = gcn_features.reshape(gcn_features.shape[0], -1) regression_outputs = [] for task_head in self.task_heads: regression_outputs.append(task_head(gcn_features)) return torch.cat(regression_outputs, dim=1) def valid(model, valid_dataloader, criterion): model.eval() epoch_loss = 0 with torch.no_grad(): for batch in valid_dataloader: loss, outputs, labels = cal_loss(batch, model, criterion, context_model_type) epoch_loss += loss.item() return epoch_loss/len(valid_dataloader) def cal_loss(batch, model, criterion, context_model_type): if ('gcn' in context_model_type): input_ids = torch.stack(batch['input_ids'],dim=0).to(device).permute(1,0,2) # shape: [batch_size, max_conver_num, max_length] attention_mask = torch.stack(batch['attention_mask'],dim=0).to(device).permute(1,0,2) current_length=batch['current_length'].to(device) labels = batch['labels'].to(device) # [batch_size, 5] dialogue_labels = labels.repeat_interleave(current_length, dim=0) model_outputs = model(input_ids, attention_mask, current_length) if 'lastnode' in context_model_type: loss = 0 outputs = [] accumulate_num = 0 for i in range(len(current_length)): accumulate_num += current_length[i] outputs.append(model_outputs[accumulate_num-1]) loss += criterion(model_outputs[accumulate_num-1], dialogue_labels[accumulate_num-1]) outputs = torch.stack(outputs, dim=0).to(device) # torch.stack else: outputs = [] accumulate_num = 0 loss = 0 for i in range(len(current_length)): dialogue_loss = 0 accumulate_result = torch.zeros_like(model_outputs[0]) for j in range(current_length[i]): if (j % 2 == 0): accumulate_result += model_outputs[accumulate_num+j] sentence_loss = criterion(model_outputs[i], dialogue_labels[i]) dialogue_loss += sentence_loss accumulate_result = accumulate_result / (current_length[i]/2) #accumulate_result = accumulate_result / (current_length[i]) outputs.append(accumulate_result) accumulate_num += current_length[i] dialogue_loss /= (current_length[i]/2) loss += dialogue_loss outputs = torch.stack(outputs, dim=0).to(device) # torch.stack else: input_ids = batch['input_ids'].cuda() attention_mask = batch['attention_mask'].cuda() labels = batch['labels'].cuda() outputs = model(input_ids=input_ids, attention_mask=attention_mask) loss = criterion(outputs[:, [i for i, flag in enumerate(multitask) if flag == 1]], labels[:, [i for i, flag in enumerate(multitask) if flag == 1]]) return loss, outputs, labels def train(model, train_dataloader, valid_dataloader, criterion, patience, max_epoch): model.cuda() optimizer = torch.optim.Adam(model.parameters(), lr=lr) if (warmup_steps > 0): scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps, num_training_steps=max_epoch*len(train_dataloader)) best_loss = float('inf') best_epoch = -1 for epoch in range(max_epoch): model.train() epoch_loss = 0 for batch in tqdm.tqdm(train_dataloader): loss, outputs, labels = cal_loss(batch, model, criterion, context_model_type) epoch_loss += loss.item() optimizer.zero_grad() if (warmup_steps > 0): scheduler.step() loss.backward() optimizer.step() #torch.save(model.state_dict(), os.path.join(model_folder, f'model_{epoch}.pt')) print('epoch: {}, train loss: {}'.format(epoch, epoch_loss/len(train_dataloader))) logging.info('epoch: {}'.format(epoch)) logging.info('train loss: {}'.format(epoch_loss/len(train_dataloader))) valid_loss = valid(model, valid_dataloader, criterion) print('valid loss: {}'.format(valid_loss)) logging.info('valid loss: {}'.format(valid_loss)) if valid_loss < best_loss: best_loss = valid_loss best_epoch = epoch torch.save(model.state_dict(), os.path.join(model_folder, 'model.pt')) else: if epoch - best_epoch >= patience: break def eval_real_time(model, criterion, label_medians, test_file, tokenizer, max_sent, context): if ('gcn' in context_model_type): tmp_test_data = gene_data_from_csv(test_file, tokenizer, max_sent) tmp_test_data = gene_context_data(tmp_test_data, pad_length, tokenizer) tmp_test_dataset = ConversationDataset(tmp_test_data, tokenizer, max_length) tmp_test_dataloader= DataLoader(tmp_test_dataset, batch_size=batch_size, shuffle=False) tmp_res = evaluation(model, tmp_test_dataloader, criterion, label_medians) else: tmp_test_data = gene_data_from_csv(test_file, tokenizer, max_sent) tmp_test_dataset = MultiTaskDataset(tmp_test_data, tokenizer, max_length) tmp_test_dataloader= DataLoader(tmp_test_dataset, batch_size=batch_size, shuffle=False) tmp_res = evaluation(model, tmp_test_dataloader, criterion, label_medians) return tmp_res def evaluation(model, test_dataloader, criterion, label_medians, ensemble=False): if ensemble: model_list = model for m in model_list: m.eval() else: model.eval() epoch_loss = 0 epoch_labels = [] epoch_preds = [] with torch.no_grad(): for batch in test_dataloader: if ensemble: output_list = [] for m in model_list: batch_clone = copy.deepcopy(batch) loss, outputs, labels = cal_loss(batch_clone, m, criterion, context_model_type) output_list.append(outputs.clone()) outputs = torch.stack(output_list, dim=0) outputs = torch.mean(outputs, dim=0) else: loss, outputs, labels = cal_loss(batch, model, criterion, context_model_type) epoch_loss += loss.item() epoch_labels.append(labels.cpu().numpy()) epoch_preds.append(outputs.cpu().numpy()) avg_loss = epoch_loss/len(test_dataloader) labels = np.concatenate(epoch_labels) preds = np.concatenate(epoch_preds) # pearson correlation pearson_corr = [] pearson_p_values = [] spearman_corr = [] spearman_p_values = [] for i in range(preds.shape[1]): labels_i = labels[:, i] preds_i = preds[:, i] pearson_corr_i, pearson_p_value_i = pearsonr(labels_i, preds_i) spearman_corr_i, spearman_p_value_i = spearmanr(labels_i, preds_i) pearson_corr.append(pearson_corr_i) pearson_p_values.append(pearson_p_value_i) spearman_corr.append(spearman_corr_i) spearman_p_values.append(spearman_p_value_i) # acc for using 0.5 as the threshold labels_binary_05 = np.array([[1 if label >= 0.5 else 0 for i, label in enumerate(sample)] for sample in labels]) preds_binary_05 = np.array([[1 if pred >= 0.5 else 0 for i, pred in enumerate(sample)] for sample in preds]) acc_list_05 = [] balanced_acc_list_05 = [] for i in range(preds_binary_05.shape[1]): acc_list_05.append(accuracy_score(labels_binary_05[:, i], preds_binary_05[:, i])) balanced_acc_list_05.append(balanced_accuracy_score(labels_binary_05[:, i], preds_binary_05[:, i])) # acc labels_binary = np.array([[1 if label >= label_medians[i] else 0 for i, label in enumerate(sample)] for sample in labels]) preds_binary = np.array([[1 if pred >= label_medians[i] else 0 for i, pred in enumerate(sample)] for sample in preds]) acc_list = [] balanced_acc_list = [] for i in range(preds_binary.shape[1]): acc_list.append(accuracy_score(labels_binary[:, i], preds_binary[:, i])) balanced_acc_list.append(balanced_accuracy_score(labels_binary[:, i], preds_binary[:, i])) return { 'loss': avg_loss, 'pearson_corr_list': pearson_corr, 'pearson_p_values': pearson_p_values, 'spearman_corr_list': spearman_corr, 'spearman_p_values': spearman_p_values, 'acc_list_05': acc_list_05, 'balanced_acc_list_05': balanced_acc_list_05, 'acc_list': acc_list, 'balanced_acc_list': balanced_acc_list } def print_res(res, name='test'): print (res) logging.info(f'-----------{name}---------------------') logging.info('{} loss: {:.3f}'.format(name, res['loss'])) logging.info('pearson correlation: {}'.format([round(corr, 3) for corr in res['pearson_corr_list']])) logging.info('pearson p value: {}'.format([round(p_value, 3) for p_value in res['pearson_p_values']])) logging.info('spearman correlation: {}'.format([round(corr, 3) for corr in res['spearman_corr_list']])) logging.info('spearman p value: {}'.format([round(p_value, 3) for p_value in res['spearman_p_values']])) logging.info('acc using 0.5 as threshold: {}'.format([round(acc, 3) for acc in res['acc_list_05']])) logging.info('avg acc using 0.5 as threshold: {:.3f}'.format(np.mean(res['acc_list_05']))) logging.info('balanced acc using 0.5 as threshold: {}'.format([round(balanced_acc, 3) for balanced_acc in res['balanced_acc_list_05']])) logging.info('avg balanced acc using 0.5 as threshold: {:.3f}'.format(np.mean(res['balanced_acc_list_05']))) logging.info('acc: {}'.format([round(acc, 3) for acc in res['acc_list']])) logging.info('avg acc: {:.3f}'.format(np.mean(res['acc_list']))) logging.info('balanced acc: {}'.format([round(balanced_acc, 3) for balanced_acc in res['balanced_acc_list']])) logging.info('avg balanced acc: {:.3f}'.format(np.mean(res['balanced_acc_list']))) def main(): global pad_length logging.info('base model: {}'.format(base_model_name)) logging.info('batch size: {}'.format(batch_size)) logging.info('max length: {}'.format(max_length)) logging.info('max epoch: {}'.format(max_epoch)) logging.info('patience: {}'.format(patience)) logging.info('learning rate: {}'.format(lr)) logging.info('hidden size: {}'.format(hidden_size)) logging.info('multitask: {}'.format(multitask_str)) logging.info('dropout: {}'.format(dropout)) logging.info('multilinear: {}'.format(multilinear)) logging.info('warmup steps: {}'.format(warmup_steps)) logging.info('criterion type: {}'.format(critertion_type)) logging.info('context: {}'.format(context)) logging.info('context model type: {}'.format(context_model_type)) logging.info('loading data...') tokenizer=AutoTokenizer.from_pretrained(base_model_name) tokenizer.add_tokens(['[SPK1]','[SPK2]']) train_data = gene_data_from_csv(train_file, tokenizer) label_medians = np.median(np.array([sample['labels'] for sample in train_data]), axis=0) valid_data = gene_data_from_csv(valid_file, tokenizer) test_data = gene_data_from_csv(test_file, tokenizer) if ('gcn' in context_model_type): pad_length = 0 for sample in train_data: sentences_num = sample['text'].count('[SPK') pad_length = max(pad_length, sentences_num) train_data = gene_context_data(train_data, pad_length, tokenizer) valid_data = gene_context_data(valid_data, pad_length, tokenizer) test_data = gene_context_data(test_data, pad_length, tokenizer) train_dataset = ConversationDataset(train_data, tokenizer, max_length) valid_dataset = ConversationDataset(valid_data, tokenizer, max_length) test_dataset = ConversationDataset(test_data, tokenizer, max_length) else: train_dataset = MultiTaskDataset(train_data, tokenizer, max_length) valid_dataset = MultiTaskDataset(valid_data, tokenizer, max_length) test_dataset = MultiTaskDataset(test_data, tokenizer, max_length) train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) valid_dataloader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False) test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) logging.info('initializing model...') base_model = AutoModel.from_pretrained(base_model_name) base_model.resize_token_embeddings(len(tokenizer)) if ('gcn' in context_model_type): model = ContextModel(base_model, batch_size, pad_length, device, context_model_type, head_num, model_variant) else: model = MultiTaskModel(base_model=base_model, dropout_flag=dropout, multilinear_flag=multilinear) model = DataParallel(model) model.to(device) if (critertion_type=='mse'): criterion = nn.MSELoss() elif (critertion_type=='mae'): criterion = nn.L1Loss() if (train_flag==1): logging.info('start training...') train(model, train_dataloader, valid_dataloader, criterion, patience, max_epoch) if (test_flag==1): logging.info('start evaluation...') model.load_state_dict(torch.load(os.path.join(model_folder, 'model.pt'))) if (real_time_flag == 1): for max_sent in [2,3,4,5,10]: tmp_res = eval_real_time(model, criterion, label_medians, test_file, tokenizer, max_sent, context) print_res(tmp_res, name=f'test_max_sent{max_sent}') valid_res = evaluation(model, valid_dataloader, criterion, label_medians) res = evaluation(model, test_dataloader, criterion, label_medians) print_res(valid_res, 'valid') print_res(res, 'test') def eval_ensemble(): global pad_length logging.info('base model: {}'.format(base_model_name)) logging.info('batch size: {}'.format(batch_size)) logging.info('max length: {}'.format(max_length)) logging.info('max epoch: {}'.format(max_epoch)) logging.info('patience: {}'.format(patience)) logging.info('learning rate: {}'.format(lr)) logging.info('hidden size: {}'.format(hidden_size)) logging.info('multitask: {}'.format(multitask_str)) logging.info('dropout: {}'.format(dropout)) logging.info('multilinear: {}'.format(multilinear)) logging.info('warmup steps: {}'.format(warmup_steps)) logging.info('criterion type: {}'.format(critertion_type)) logging.info('context: {}'.format(context)) logging.info('context model type: {}'.format(context_model_type)) logging.info('loading data...') tokenizer=AutoTokenizer.from_pretrained(base_model_name) #tokenizer.add_tokens(['[SPK1]','[SPK2]']) train_data = gene_data_from_csv(train_file, tokenizer) label_medians = np.median(np.array([sample['labels'] for sample in train_data]), axis=0) test_data = gene_data_from_csv(test_file, tokenizer) test_dataset = MultiTaskDataset(test_data, tokenizer, max_length) test_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False) logging.info('initializing model...') base_model = AutoModel.from_pretrained(base_model_name) #base_model.resize_token_embeddings(len(tokenizer)) logging.info('start evaluation...') model_files = glob.glob(os.path.join(ensemble_model_folder, '*.pt'))[:2] # sort model_files = sorted(model_files) print (model_files) model_num = len(model_files) model_list = [] for i in range(model_num): if ('gcn' in context_model_type): model = ContextModel(base_model, batch_size, pad_length, device, context_model_type, head_num, model_variant) else: model = MultiTaskModel(base_model=base_model, dropout_flag=dropout, multilinear_flag=multilinear) model = DataParallel(model) model.to(device) model_list.append(model) if (critertion_type=='mse'): criterion = nn.MSELoss() elif (critertion_type=='mae'): criterion = nn.L1Loss() for model, model_name in zip(model_list, model_files): try: model.load_state_dict(torch.load(model_name)) print (model_name, 'loaded') except: print (model_name, 'failed') res = evaluation(model_list, test_dataloader, criterion, label_medians, ensemble=True) print_res(res, 'test') if __name__ == '__main__': if not ensemble: main() else: # no training only evaluation eval_ensemble()