CoolFace
Modelpublic

ZJF-Thunder/ChineseBert_text_analysis_system

sourceHugging Faceupdated 3y agoView on Hugging Face
0likes
Text_Classification.py811 linesDownload Raw Back to root
1"""2功能:加载微博谣言数据集,微调预训练模型并以此进行判断输入文本是否为谣言3"""4import json5import os6import random7import zipfile8import jieba9import torch10import time11import datetime12import logging13import matplotlib.pyplot as plt14from matplotlib.animation import FuncAnimation15from torch.utils.data import DataLoader, TensorDataset16from transformers import BertTokenizer, BertForSequenceClassification17from transformers import get_linear_schedule_with_warmup, BertConfig18from sklearn.metrics import accuracy_score, f1_score, recall_score, precision_score19from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score20from sklearn.metrics import precision_recall_curve, roc_curve, average_precision_score, auc21from torchmetrics.functional import hamming_distance22 23 24# 解压原始数据集,将Rumor_Dataset.zip解压至data目录下25# 并解析出谣言和非谣言数据,并将所有数据写入all_data.txt26def data_preprocessing(src_path, target_path):27    """功能:28    解压原始数据集,将Rumor_Dataset.zip解压至data目录下,29    并解析出谣言和非谣言数据,并将所有数据写入all_data.txt30 31    :param src_path: 需要解压的文件的路径32    :param target_path: 解压之后的文件存放路径33    :return: 所有数据写入all_data.txt34    """35    if not os.path.isdir(target_path):36        z = zipfile.ZipFile(src_path, 'r')37        z.extractall(path=target_path)38        z.close()39 40    rumor_class_dirs = os.listdir(target_path + "/Chinese_Rumor_Dataset-master/CED_Dataset/rumor-repost/")41    non_rumor_class_dirs = os.listdir(target_path + "/Chinese_Rumor_Dataset-master/CED_Dataset/non-rumor-repost/")42    original_microblog = target_path + "/Chinese_Rumor_Dataset-master/CED_Dataset/original-microblog/"43 44    # 谣言为0,非谣言为145    rumor_label = "0"46    non_rumor_label = "1"47    # 谣言和非谣言的数量48    rumor_num = 049    non_rumor_num = 050    # 谣言和非谣言列表51    all_rumor_list = []52    all_non_rumor_list = []53 54    # 解析谣言数据55    for rumor_class_dir in rumor_class_dirs:56        if rumor_class_dir != '.DS_Store':57            with open(original_microblog + rumor_class_dir, 'r', encoding='utf-8') as f:58                rumor_content = f.read()59            rumor_dict = json.loads(rumor_content)60            all_rumor_list.append(rumor_label + "\t" + rumor_dict["text"] + "\n")61            rumor_num += 162 63    # 解析非谣言数据64    for non_rumor_class_dir in non_rumor_class_dirs:65        if non_rumor_class_dir != '.DS_Store':66            with open(original_microblog + non_rumor_class_dir, 'r', encoding='utf-8') as f2:67                non_rumor_content = f2.read()68            non_rumor_dict = json.loads(non_rumor_content)69            all_non_rumor_list.append(non_rumor_label + "\t" + non_rumor_dict["text"] + "\n")70            non_rumor_num += 171 72    print("谣言数据总量为:" + str(rumor_num))73    print("非谣言数据总量为:" + str(non_rumor_num))74    logging.info("谣言数据总量为:" + str(rumor_num))75    logging.info("非谣言数据总量为:" + str(non_rumor_num))76    # 将谣言列表和非谣言列表连接成一个新的列表77    all_data_list = all_rumor_list + all_non_rumor_list78    # 将文本打乱顺序79    random.shuffle(all_data_list)80    # 返回随机打乱的数据列表81    all_data_path = "./data/all_data.txt"82    all_rumor_path = "./data/rumor_data.txt"83    all_non_rumor_path = "./data/non_rumor_data.txt"84    # 清空文本的数据,并往all_data_path的txt文本中写入所有文本数据85    with open(all_data_path, 'w') as f:86        f.seek(0)87        f.truncate()88    with open(all_data_path, 'a', encoding='utf-8') as f:89        for data in all_data_list:90            f.write(data)91    # 清空文本的数据,并往all_rumor_path的txt文本中写入所有谣言文本数据92    with open(all_rumor_path, 'w') as f:93        f.seek(0)94        f.truncate()95    with open(all_rumor_path, 'a', encoding='utf-8') as f:96        for data in all_rumor_list:97            f.write(data)98    # 清空文本的数据,并往all_non_rumor_path的txt文本中写入所有非谣言文本数据99    with open(all_non_rumor_path, 'w') as f:100        f.seek(0)101        f.truncate()102    with open(all_non_rumor_path, 'a', encoding='utf-8') as f:103        for data in all_non_rumor_list:104            f.write(data)105    print("数据文本生成成功!")106    logging.info("数据文本生成成功!")107 108 109# 获取谣言和非谣言的数据条数110def get_rumor_norumor_num(data_path):111    """功能:112    获取谣言和非谣言的数据条数113 114    :param data_path: 扩充后的数据集地址115    :return: 无116    """117    rumor_count = 0118    nonrumor_count = 0119    with open(data_path, 'r', encoding='utf-8') as f:120        lines = f.readlines()121        for line in lines:122            label, text = line.strip().split('\t')123            if label == '0':124                rumor_count += 1125            else:126                nonrumor_count += 1127    print(f"数据集总数:{len(lines)} 条")128    print(f"谣言总数:{rumor_count} 条")129    print(f"非谣言总数:{nonrumor_count} 条")130    logging.info(f"数据集总数:{len(lines)} 条")131    logging.info(f"谣言总数:{rumor_count} 条")132    logging.info(f"非谣言总数:{nonrumor_count} 条")133 134 135# 按照特定比例划分数据集,默认比例为9:1136def splitting_dataset(data_path, train_size=0.9):137    """功能:138    按照特定比例划分数据集,默认比例为9:1139 140    :param data_path: 需要划分的数据的地址141    :param train_size: 训练集的占比大小,默认为0.9142    :return: 训练集和测试集的数据地址143    train_data_path = './data/train_data.txt'144    test_data_path = './data/test_data.txt'145    """146    with open(data_path, 'r', encoding='utf-8') as f:147        data = f.readlines()148    random.shuffle(data)149    train_data = data[:int(len(data) * train_size)]150    test_data = data[int(len(data) * train_size):]151    train_data_path = './data/train_data.txt'152    test_data_path = './data/test_data.txt'153    with open(train_data_path, 'w', encoding='utf-8') as f:154        f.writelines(train_data)155    with open(test_data_path, 'w', encoding='utf-8') as f:156        f.writelines(test_data)157    print("成功划分训练集和测试集!")158    logging.info("成功划分训练集和测试集!")159    print(f"训练集:{len(train_data)} 条")160    print(f"测试集:{len(test_data)} 条")161    logging.info(f"训练集:{len(train_data)} 条")162    logging.info(f"测试集:{len(test_data)} 条")163 164    return train_data_path, test_data_path165 166 167# 读取数据并将其转换为模型所需的格式168def read_data(filename, tokenizer, max_seq_length=256):169    """功能:170    读取数据并将其转换为模型所需的格式171 172    :param filename: 需要转换的数据的路径173    :param tokenizer: 模型的分词器174    :param max_seq_length: 最长文本长度,大于则截断,小于则填充175    :return: 转换成模型所需格式的数据176    """177    with open(filename, 'r', encoding='utf-8') as f:178        lines = f.readlines()179 180    data = []181    for line in lines:182        label, text = line.strip().split('\t')183        # 使用jieba进行高级别的精确分词,并去除停用词184        words = jieba.lcut(text, cut_all=False)185        text = ' '.join(words)  # 将分词结果用空格拼接186        # 这里面涉及到分词的操作其实是encod_plus调用了tokenizer.tokenize函数来进行分词187        inputs = tokenizer.encode_plus(188            text,189            add_special_tokens=True,  # 添加特殊标记[CLS]和[SEP]到序列190            padding='max_length',  # 将序列填充到最大长度191            truncation=True,  # 如果序列超过最大长度,则将其截断到最大长度192            max_length=max_seq_length,  # 指定编码序列的最大长度193            mask_type='word',  # 启用全词掩码策略194            mask_prob=0.15,  # 指定遮盖比例为15%195            return_tensors='pt',  # 返回PyTorch张量196            is_split_into_words=True)197        """"198        add_special_tokens = True将特殊标记如[CLS]和[SEP]添加到输入序列的开头和结尾。199        padding = 'max_length'  将序列填充到最大长度。200        truncation = True 如果序列超过最大长度,则将其截断到最大长度。201        max_length = 256 指定编码序列的最大长度。202        return_attention_mask = True返回一个注意力掩码,指示哪些标记是填充标记。203        return_token_type_ids = True返回标记类型ID,指示哪些标记属于哪个段落(对于问答等任务很有用)。204        return_tensors = 'pt' 返回PyTorch张量而不是列表。205        """206        data.append((inputs.input_ids, inputs.token_type_ids, inputs.attention_mask, int(label)))207    # 将数据转换为PyTorch张量,并在GPU上计算208    """改进版本:下面两种方式都可以,不会警告"""209    # data_tensor = [210    #     (torch.as_tensor(input_ids).cuda(), torch.as_tensor(token_type_ids).cuda(),211    #      torch.as_tensor(attention_mask).cuda(), torch.tensor([[label]]).cuda())212    #     for input_ids, token_type_ids, attention_mask, label in data]213    data_tensor = [214        (input_ids.clone().clone().detach().cuda(), token_type_ids.clone().clone().detach().cuda(),215         attention_mask.clone().clone().detach().cuda(),216         torch.tensor([[label]]).cuda()) for input_ids, token_type_ids, attention_mask, label in data]217    return data_tensor218 219 220# 对标签进行独热编码221def one_hot_encoding(label_ids, num_labels):222    """功能:对标签进行独热编码223 224    :param label_ids: 标签,一般是个list或者tensor225    :param num_labels: 标签数量226    :return: 独热编码之后的标签227    """228    labels = torch.zeros((len(label_ids), num_labels))229    labels[torch.arange(len(label_ids)), label_ids] = 1230    return labels231 232 233# 训练模型234def model_train(model, train_tensor):235    """功能:236    输入训练数据,训练(微调)模型237 238    :param model: 定义的预训练模型239    :param train_tensor: 输入的训练数据,数据类型为tensor240    :return: 训练(微调)好的模型241    """242    train_tensor_stack = [torch.stack(tensors) for tensors in zip(*train_tensor)]243    train_dataset = TensorDataset(*train_tensor_stack)244    """数据加载器,一个Batch(批次)等于样本总数/Batch_size245    例:训练数据一共3048个,batch_size为16,所以一共3048/16个batch,取整为190个batch246    所以train_loader的长度为190,它的作用是将3040个样本分割为190个batch,用于训练"""247    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=Batch_size, drop_last=True)248 249    # 将数据加载到模型中进行微调250    optimizer = torch.optim.Adam(model.parameters(), lr=Learning_rate)251    """252    # 定义交叉熵损失函数 253    # criterion1 = torch.nn.CrossEntropyLoss()254    # 定义二分类交叉损失函数255    # criterion2 = torch.nn.BCEWithLogitsLoss()256    """257    # 增加学习率动态调整机制258    total_steps = len(train_loader) * Epochs259    scheduler = get_linear_schedule_with_warmup(optimizer,260                                                num_warmup_steps=0,  # 预热步数261                                                num_training_steps=total_steps)  # 总的训练步数262    # 将模型移动到GPU设备263    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")264    model.to(device)265    model.train()266 267    # 记录每一次迭代的损失268    train_losses = []269    print("————————————————模型训练开始————————————————")270    logging.info("————————————————模型训练开始————————————————")271    # 训练开始时间272    start_time = time.time()273    for epoch in range(Epochs):274        # 每一个Epoch的平均loss275        epoch_loss = 0276        # 迭代一次就是迭代一个batch(批次)277        for batch, data in enumerate(train_loader):278            # data = (input_ids, token_type_ids, attention_mask, labels)279            # 正向传播280            input_ids = data[0].to(device).squeeze(1)281            token_type_ids = data[1].to(device).squeeze(1)282            attention_mask = data[2].to(device).squeeze(1)283            labels = data[3].to(device).squeeze()  # 形状为1维:(16,)284            # 将标签进行独热编码,变成长度为2的向量,和模型的输出层的长度一样,然后输入到模型中,并且调用二分类交叉损失函数285            labels = one_hot_encoding(labels, Num_labels)286            labels = labels.cuda()287            # 将模型的参数梯度清零,以免上一个epoch的梯度对当前epoch的训练产生影响288            optimizer.zero_grad()289            # 前向传播290 291            outputs = model(input_ids,292                            token_type_ids=token_type_ids,293                            attention_mask=attention_mask,294                            labels=labels)295 296            """调用模型自带的损失函数,297            如果是二分类任务,模型会自动调用二分类交叉熵损失函数计算损失298            直接loss = outputs.loss调用即可"""299            loss = outputs.loss300            """301            # 调用自定义的交叉熵损失函数计算loss,这时候label不需要进行独热编码302            # loss = criterion1(outputs.logits, labels)303            # 调用自定义的二分类交叉熵损失函数计算损失,labels进行独热编码之后,本身就是浮点数,所以加不加float都一样304            # loss = criterion2(outputs.logits, labels.float())305            """306            # 反向传播307            loss.backward()308            # 参数更新309            optimizer.step()310            # 调整学习率311            scheduler.step()312            # 记录一个Batch的loss,用以计算一个Epoch的平均loss313            epoch_loss += loss.item()314            train_losses.append(loss.item())315            # 每十个批次输出一次训练信息316            if batch % 10 == 0:317                print('Epoch: {}, Batch: {}, Loss: {}'.format(epoch, batch, loss.item()))318                logging.info('Epoch: {}, Batch: {}, Loss: {}'.format(epoch, batch, loss.item()))319        avg_epoch_loss = epoch_loss / len(train_loader)320        print("Epoch [{}/{}], Average Loss: {:.4f}".format(epoch + 1, Epochs, avg_epoch_loss))321        logging.info("Epoch [{}/{}], Average Loss: {:.4f}".format(epoch + 1, Epochs, avg_epoch_loss))322 323    end_time = time.time()324    total_time = end_time - start_time325    print("————————————————模型训练完成————————————————")326    print("训练开始时间:", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time)))327    print("训练结束时间:", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time)))328    print(f"模型训练总时间为: {total_time:.2f} 秒")329    logging.info("————————————————模型训练完成————————————————")330    logging.info("训练开始时间:" + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time)))331    logging.info("训练结束时间:" + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time)))332    logging.info(f"模型训练总时间为: {total_time:.2f} 秒")333    # 获取当前时间334    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(end_time))335    save_dir = './模型保存'  # 保存模型的相对路径336    if not os.path.exists(save_dir):337        os.makedirs(save_dir)  # 如果目录不存在,创建目录338    model_name = os.path.join(save_dir, "ChineseBert_{}.pt".format(local_time))  # 模型的相对路径339    torch.save(model.state_dict(), model_name)340    print("————————————————模型保存成功————————————————")341    logging.info("————————————————模型保存成功————————————————")342 343    # 绘制并保存训练损失的静态图344    # 获取当前日期345    today = datetime.date.today()346    date_str = today.strftime('%Y-%m-%d')347    # 获取当前具体时间348    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())349    # 创建日志文件路径350    img_save_dir = os.path.join('./images/training_loss/', f"{date_str}_Training_loss")351    # 判断文件夹是否存在,如果不存在则创建352    if not os.path.exists(img_save_dir):353        os.makedirs(img_save_dir)354        print(f"文件夹 {img_save_dir} 创建成功!")355 356    else:357        print(f"文件夹 {img_save_dir} 已存在!")358 359    plt.figure(figsize=(10, 5))360    # 散点图361    # plt.scatter(range(len(train_losses)), train_losses)362    # 折线图363    plt.clf()  # 首先清空当前图像364    plt.plot(range(len(train_losses)), train_losses, scalex=10, label='Training Loss')365    plt.legend(loc='upper right')  # 添加图例366    plt.title("Training Loss")367    plt.xlabel("Batch")368    plt.ylabel("Loss")369    images = os.path.join(img_save_dir, "training_loss_{}.png".format(local_time))370    plt.savefig(images)371 372    # 绘制训练损失的动态图373    show_animation(train_losses)374 375    return model376 377 378# 展示训练损失的动态过程图379def show_animation(train_losses):380    """功能:展示训练损失的动态过程图381 382    :param train_losses: 训练的总损失383    :return: 无384    """385    # 获取当前日期386    today = datetime.date.today()387    date_str = today.strftime('%Y-%m-%d')388    # 获取当前具体时间389    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())390    # 创建日志文件路径391    img_save_dir = os.path.join('./images/training_loss/', f"{date_str}_Training_loss")392    # 判断文件夹是否存在,如果不存在则创建393    if not os.path.exists(img_save_dir):394        os.makedirs(img_save_dir)395        print(f"文件夹 {img_save_dir} 创建成功!")396    else:397        print(f"文件夹 {img_save_dir} 已存在!")398 399    fig, ax = plt.subplots()400    ax.set(xlim=(0, len(train_losses)), ylim=(0, 1))401 402    # 定义内部函数,用于保存动画完成后最后完整的图像403    def save_final_frame(train_losses, fig, ax):404        ax.clear()405        ax.plot(range(len(train_losses)), train_losses, label='Training Loss')406        ax.legend(loc='upper right')407        ax.set(xlabel='Batch', ylabel='Loss', title='Training Loss')408        if not hasattr(save_final_frame, 'saved'):409            images = os.path.join(img_save_dir, "training_loss_{}.png".format(local_time))410            # fig.savefig(images)  # 如果不保存,则注释掉这句411            # 设置 saved 标志为 True,表示已经保存最终的图像412            save_final_frame.saved = True413 414    # 定义在每一帧调用的动画函数415    def animate(i):416        ax.clear()417        ax.plot(range(i + 1), train_losses[:i + 1], label='Training Loss')418        ax.legend(loc='upper right')419        ax.set(xlabel='Batch', ylabel='Loss', title='Training Loss')420        plt.pause(0.1)421 422    ani = FuncAnimation(fig, animate, frames=len(train_losses), repeat=False)423    ani.event_source.add_callback(save_final_frame, train_losses=train_losses, fig=fig, ax=ax)424    plt.show()  # 注释打开则播放训练损失动画425 426 427# 定义性能指标函数428def compute_metrics(y_true, y_pred):429    """功能:定义计算模型性能指标函数430 431    :param y_true: 真实的标签列表432    :param y_pred: 预测的标签列表433    :return:返回各项计算结果434    """435    # 准确率、准确度436    accuracy = accuracy_score(y_true, y_pred)437    # 精确度、精度:所有被分类器正确分类的正样本占所有被分类器分类为正样本的样本数的比例438    precision = precision_score(y_true, y_pred, average='macro')439    # 召回率:被正确分类的正样本占所有实际正样本的比例440    recall = recall_score(y_true, y_pred, average='macro')441    # F1:精确度和召回率的调和平均值442    f1 = f1_score(y_true, y_pred, average='macro')443    # AUC值:ROC曲线下的面积,取值范围在0到1之间,越接近1代表模型的性能越好444    auc = roc_auc_score(y_true, y_pred)445    result = {'accuracy': accuracy,446              'precision': precision,447              'recall': recall,448              'f1_score': f1,449              'AUC': auc}450    return result451 452 453# 测试模型454def model_eval(model, test_tensor):455    """功能:测试训练好的模型的性能456 457    :param model: 训练(微调)好的模型458    :param test_tensor: 输入的测试数据,数据类型为tensor459    :return: 无460    """461    # 在测试集上进行评估462    test_tensor_stack = [torch.stack(tensors) for tensors in zip(*test_tensor)]463    test_dataset = TensorDataset(*test_tensor_stack)464    test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=Batch_size, drop_last=True)465    # 将模型移动到GPU设备466    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")467    model.to(device)468    model.eval()469    # print("模型信息:", model)470    correct = 0471    total = 0472    # 存放真实标签和预测标签473    label_true = []474    label_pred = []475    print("————————————————模型测试开始————————————————")476    logging.info("————————————————模型测试开始————————————————")477    with torch.no_grad():478        for i, data in enumerate(test_loader):479            # data = (input_ids, token_type_ids, attention_mask, labels)480            # 正向传播481            input_ids = data[0].to(device).squeeze(1)  # 降维度482            token_type_ids = data[1].to(device).squeeze(1)483            attention_mask = data[2].to(device).squeeze(1)484            labels = data[3].to(device).squeeze()485            print("输入实际标签为:", labels)486            logging.info("输入实际标签为:" + str(labels.tolist()))487 488            outputs = model(input_ids,489                            token_type_ids=token_type_ids,490                            attention_mask=attention_mask)491            """492            # 这里不能加item()来取出预测值,因为验证的时候是用一个Batch_size大小的样本同时预测的,一个批次就是16个样本493            # 这里predictiongs不是一个样本,是一个有着16个样本预测标签的类似列表或者张量494            predictions = (torch.sigmoid(outputs.logits) >= 0.5).int()[:, 1]  # 使用阈值0.5将概率转为二元标签495            predictions = torch.argmax(torch.sigmoid(outputs.logits), dim=1).cuda()496            """497            predictions = torch.argmax(torch.softmax(outputs.logits, dim=1), dim=1).cuda()498            print("输入预测标签为:", predictions)499            logging.info("输入预测标签为:" + str(predictions.cuda().tolist()))500 501            correct += (predictions == labels).sum().item()502            total += labels.size(0)503 504            # sklearn的指标  全部tensor转换成list505            """extend和append的区别:506            extend是直接在后面追加所有元素,如果追加的是一个列表,则是直接追加这个列表的所有值507            append是在后面追加一个单独的元素,这个元素可以是一个列表或者一个值"""508            label_true.extend(labels.cuda().tolist())509            label_pred.extend(predictions.cuda().tolist())510 511    print("————————————————模型测试结束————————————————")512    logging.info("————————————————模型测试结束————————————————")513    accuracy = correct / total514    print("总测试样本:{}个".format(total))515    print("预测正确样本:{}个".format(correct))516    print('测试准确度:', accuracy)517    logging.info('总测试样本: %d个', total)518    logging.info('预测正确样本: %d个', correct)519    logging.info('测试准确度: %f', accuracy)520 521    # sklearn的指标522    metrics = compute_metrics(label_true, label_pred)523    print("-----------------------------")524    print(f"Accuracy: {metrics['accuracy']}")525    print(f"Precision: {metrics['precision']}")526    print(f"Recall: {metrics['recall']}")527    print(f"F1 Score: {metrics['f1_score']}")528    print(f"AUC Score: {metrics['AUC']}")529    print("-----------------------------")530    print(classification_report(label_true, label_pred))531    print("-----------------------------")532    print("-----------混淆矩阵------------")533    print(confusion_matrix(label_true, label_pred))534    print("-----------------------------")535 536    logging.info("-----------------------------")537    logging.info(f"Accuracy: {metrics['accuracy']}")538    logging.info(f"Precision: {metrics['precision']}")539    logging.info(f"Recall: {metrics['recall']}")540    logging.info(f"F1 Score: {metrics['f1_score']}")541    logging.info(f"AUC Score: {metrics['AUC']}")542    logging.info("-----------------------------")543    logging.info("\n" + classification_report(label_true, label_pred))544    logging.info("-----------------------------")545    logging.info("-----------混淆矩阵------------")546    logging.info("\n" + str(confusion_matrix(label_true, label_pred)))547    logging.info("-----------------------------")548 549    # 计算average_precision_score的值550    average_precision = average_precision_score(label_true, label_pred)551    print('Average precision-recall score: {0:0.2f}'.format(average_precision))552    # 计算汉明损失553    hamming_loss = hamming_distance(torch.tensor(label_pred), torch.tensor(label_true))554    print(f"Hamming Loss: {hamming_loss}")555 556    # 计算不同概率阈值的精度-召回率对557    precision, recall, thresholds = precision_recall_curve(label_true, label_pred)558    # 计算AUC-PR559    auc_PR = auc(recall, precision)560    # 绘制PR曲线:纵轴是精度(precision),横轴是召回率(recall)561    plt.clf()  # 首先清空当前图像562    plt.plot(recall, precision, lw=2, color='blue', label='AUC-PR = %0.4f' % auc_PR)563    plt.xlim([0.0, 1.0])564    plt.ylim([0.0, 1.05])565    plt.xlabel('Recall')566    plt.ylabel('Precision')567    plt.title('PR Curve')568    plt.legend(loc="lower right")569    img_save_dir = './images/PR曲线'570    if not os.path.exists(img_save_dir):571        os.makedirs(img_save_dir)572    # 获取当前时间573    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time()))574    images = os.path.join(img_save_dir, "PR_{}.png".format(local_time))575    plt.savefig(images)576    # plt.show()577 578    # 计算假阳性率(FPR)和真阳性率(TPR)和阈值(thresholds)579    FPR, TPR, thresholds = roc_curve(label_true, label_pred)580    # 计算AUC-ROC581    auc_ROC = auc(FPR, TPR)582    # 绘制ROC曲线:纵轴是真阳率(true positive rate),横轴是假阳率(false positive rate)583    plt.clf()  # 首先清空当前图像584    plt.plot(FPR, TPR, lw=2, color='red', label='AUC-ROC = %0.4f' % auc_ROC)585    plt.xlim([0.0, 1.0])586    plt.ylim([0.0, 1.05])587    plt.xlabel('False Positive Rate')588    plt.ylabel('True Positive Rate')589    plt.title('ROC Curve')590    plt.legend(loc="lower right")591    img_save_dir = './images/ROC曲线'592    if not os.path.exists(img_save_dir):593        os.makedirs(img_save_dir)594    # 获取当前时间595    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time()))596    images = os.path.join(img_save_dir, "ROC_{}.jpg".format(local_time))597    plt.savefig(images)598    # plt.show()599 600 601# 定义预测函数602def predicted(text, model, tokenizer):603    """功能:输入一条文本,调用模型,返回预测结果604 605    :param text: 输入的文本,类型为str606    :param model: 训练好的模型607    :param tokenizer: 预训练模型的分词器608    :return: 返回预测的标签和概率609    """610    words = jieba.lcut(text, cut_all=False)611    text = ' '.join(words)  # 将分词结果用空格拼接612    """613    # 将文本转换成数字编码,这里是直接调用tokenizer类,614    # 其实内部处理分词的函数还是tokenizer.tokenize()615    # 并且内部encode的函数也是tokenizer.encode_plus()函数616    # input_ids = tokenizer(text,617    #                       padding=True,618    #                       truncation=True,619    #                       max_length=Max_seq_length,620    #                       return_tensors='pt').input_ids.cuda()621    """622    # 这里可以直接调用tokenizer.encode_plus()函数来将token变成token id623    inputs = tokenizer.encode_plus(text,624                                   padding=True,625                                   truncation=True,626                                   max_length=Max_seq_length,627                                   return_tensors='pt',628                                   is_split_into_words=True)629    input_ids = inputs['input_ids'].cuda()630 631    model.cuda()632    # 预测类别633    with torch.no_grad():634        outputs = model(input_ids)635        # 使用softmax将输出转换成概率分布636        probs = torch.softmax(outputs.logits, dim=1)637        # 输出概率列表:probs.tolist()[0]638        # 获取最大概率对应的类别标签及其对应的概率639        pred_label = torch.argmax(probs, dim=1).item()640        pred_prob = probs[0, pred_label].item()641 642    return pred_label, pred_prob643 644 645# 调用预测模型来预测文本类别646def Text_predict(trained_model, tokenizer):647    """功能:调用预测模型来预测文本类别,展示结果648 649    :param trained_model: 训练好的模型650    :param tokenizer: 预训练模型的分词器651    :return: 无652    """653    print("————————————————预测文本类别————————————————")654    logging.info("————————————————预测文本类别————————————————")655    text = '有着800多年历史的克里姆林宫,再次迎来中国国家主席。当地时间3月21日下午,' \656           '俄罗斯总统普京在大克里姆林宫二层乔治大厅为习近平主席举行隆重的欢迎仪式,随后两国元首举行会谈'657    predicted_label, predicted_prob = predicted(text, trained_model, tokenizer)658    # 另一个样本的标签及其概率659    other_prob = 1 - predicted_prob660    other_prob_label = 1661    # 定义标签662    labels = {0: "谣言", 1: "非谣言"}663    if predicted_label == 1:664        other_prob_label = 0665        print(f"这条微博有{predicted_prob * 100:.2f}%的概率为{labels[predicted_label]},"666              f"有{other_prob * 100:.2f}%的概率为{labels[other_prob_label]}")667        logging.info(f"这条微博有{predicted_prob * 100:.2f}%的概率为{labels[predicted_label]},"668                     f"有{other_prob * 100:.2f}%的概率为{labels[other_prob_label]}")669    else:670        print(f"这条微博有{predicted_prob * 100:.2f}%的概率为{labels[predicted_label]},"671              f"有{other_prob * 100:.2f}%的概率为{labels[other_prob_label]}")672        logging.info(f"这条微博有{predicted_prob * 100:.2f}%的概率为{labels[predicted_label]},"673                     f"有{other_prob * 100:.2f}%的概率为{labels[other_prob_label]}")674 675 676# 检查GPU是否可用,不可用则退出程序677def cuda_is_available():678    """679    功能:检查GPU是否可用,不可用则退出程序680    """681    if torch.cuda.is_available():682        print('GPU is available')683        logging.info('GPU is available')684    else:685        print('GPU is not available')686        logging.info('GPU is not available')687        print("————————正在退出程序——————————")688        logging.info("————————正在退出程序——————————")689        exit(1)690 691 692# 日志配置693def log_config():694    """695    功能:配置日志696    """697    # 获取当前日期698    today = datetime.date.today()699    date_str = today.strftime('%Y-%m-%d')700    # 获取当前具体时间701    local_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime())702    # 创建日志文件路径703    log_file_path = os.path.join('./运行日志/', f"{date_str}日志")704    # 判断文件夹是否存在,如果不存在则创建705    if not os.path.exists(log_file_path):706        os.makedirs(log_file_path)707        print(f"文件夹 {log_file_path} 创建成功!")708    else:709        print(f"文件夹 {log_file_path} 已存在!")710    log_filename = '{}/output_{}.log'.format(log_file_path, local_time)711    # 配置日志712    logging.basicConfig(713        level=logging.INFO,714        format='%(asctime)s - %(levelname)s - %(message)s',715        handlers=[716            # logging.StreamHandler(),  # 输出到终端717            logging.FileHandler(log_filename, mode='w', encoding='utf-8')  # 输出到文件718        ]719    )720 721 722# 主函数入口723def main():724    log_config()725    # 输出日志726    logging.info('——————程序运行开始——————')727    logging.info("——————————————————————")728    # 输出训练超参数729    logging.info("——————输出训练参数——————")730    logging.info("Batch_size:%s", Batch_size)731    logging.info("Epochs:%s", Epochs)732    logging.info("Learning_rate:%s", Learning_rate)733    logging.info("max_seq_length:%s", Max_seq_length)734    logging.info("num_labels:%s", Num_labels)735    logging.info("——————输出数据信息——————")736    # 检查GPU是否可用737    cuda_is_available()738    # 待解压文件路径739    src_path = "./data/Rumor_Dataset.zip"740    # 解压之后文件路径741    target_path = "./data//Chinese_Rumor_Dataset-master"742    # 所有数据的txt文本路径743    all_data_path = "./data/all_data.txt"744    # 将数据集解压,并将json文件数据处理保存成txt文本  没有扩充之前的数据集745    # data_preprocessing(src_path, target_path)746    # 划分训练集和测试集747    # train_data, test_data = splitting_dataset(all_data_path, train_size=0.9)748 749    # 获取谣言和非谣言的数据条数750    get_rumor_norumor_num("./data/all_data3.txt")751    # 用扩充之后的数据集按照特定比例划分训练集和测试集,all_data2.txt为扩充之后的数据集752    train_data, test_data = splitting_dataset("./data/all_data3.txt", train_size=0.9)753 754    # 可以在这里指定字典的路径755    # tokenizer = BertTokenizer('gdrive/My Drive/Colab Notebooks/vocab.txt')756    # 定义预训练模型757    # bert = './models/chinese-roberta-wwm-ext'758    # bert = './models/bert-base-chinese'759    bert = './models/chinese-bert-wwm-ext'760    # 加载原始config文件761    config = BertConfig.from_pretrained(bert, num_labels=Num_labels)762    config.save_pretrained('my_chinesebert_config')763    """# 加载配置文件,可以在参数中修改配置文件764    # config = BertConfig.from_pretrained(bert, output_hidden_states=True, hidden_dropout_prob=0.2,765    # 也可以单独定义配置文件中的参数,以此来修改配置文件,然后保存766    # attention_probs_dropout_prob=0.2)767    # 如果修改了,就保存修改后的配置文件,如果没有修改参数则和原始参数相同"""768 769    # 用BertTokenizer来作为模型加载770    # 加载预训练的BERT模型和中文分词器,并返回一个BertTokenizer对象,可以用于对中文文本进行分词和编码771    tokenizer = BertTokenizer.from_pretrained(bert, num_classes=Num_labels)772    model = BertForSequenceClassification.from_pretrained(bert, config=config)773 774    # 读取训练集和测试集775    train_tensor = read_data(train_data, tokenizer, max_seq_length=Max_seq_length)776    test_tensor = read_data(test_data, tokenizer, max_seq_length=Max_seq_length)777 778    # # 训练模型779    trained_model = model_train(model, train_tensor)780    # # 测试模型781    # model_eval(trained_model, test_tensor)782    # # 预测函数783    # Text_predict(trained_model, tokenizer)784 785    """用于测试已有模型"""786    # trained_model_path = './models/chinese-bert-wwm-ext'  # 未经过微调的原始模型1787    # trained_model_path = './models/bert-base-chinese'  # 未经过微调的原始模型2788    # trained_model_path = './模型保存/chinesebert.pth'  # 微调的最早期的模型789    # trained_model_path = './模型保存/ChineseBert_2023-03-29_16-27-07_0.949.pt'790    # trained_model_path = './模型保存/ChineseBert_2023-03-25_17-10-39_0.95.pt'791    # trained_model_path = './模型保存/ChineseBert_2023-04-05_16-30-38_0.9970.pt'792    trained_model_path = './模型保存/ChineseBert_2023-04-07_20-12-29_0.999.pt'793 794    # trained_model_path = './模型保存/ChineseBert_2023-04-12_15-02-05__chinese-roberta-wwm-ext_0.999.pt'795    model = BertForSequenceClassification.from_pretrained(trained_model_path, config=config)796    model_eval(model, test_tensor)797    Text_predict(model, tokenizer)798 799    logging.info("——————————————————————")800    logging.info('——————程序运行结束——————')801 802 803if __name__ == '__main__':804    # 设置训练超参数805    Batch_size = 12806    Epochs = 2  # 4807    Learning_rate = 2e-5808    Num_labels = 2809    Max_seq_length = 256810    main()811