chabot项目介绍

news2024/10/9 18:25:26

项目介绍

  • 整体的目录如下所示:
    在这里插入图片描述
  • 上述的项目结构中出了model是必须的外,其他的都可以根据训练的代码参数传入进行调整,有些不需要一定存在
  • data
    • train.pkl:对原始训练语料进行tokenize之后的文件,存储一个list对象,list的每条数据表示一个多轮对话,表示一条训练数据
  • model:存放对话生成的模型
    - config.json:模型参数的配置文件
    - pytorch_model.bin:模型文件
  • vocab
    • vocab.txt:字典文件。默认的字典大小为13317,若需要使用自定义字典,需要将confog.json文件中的vocab_size字段设为相应的大小。
  • sample:存放人机闲聊生成的历史聊天记录
  • train.py:训练代码
  • interact.py:人机交互代码
  • preprocess.py:数据预处理代码

项目的整体运行流程

  • 第一步:数据模块, 根据后面的数据集地址介绍进行数据集的下载,里面有各个地方的数据集来源以及数据合并的代码
  • 第二步:将得到的数据通过preprocess.py文件进行训练数据处理,得到train.pkl文件,得到后再整个项目目录下创建data文件夹,并将得到的train.pkl文件移动到data文件夹下面
  • 第三步:去huggingface网站上面下载gpt2预训练模型下的文件,具体需要下载的文件如下所示:
    在这里插入图片描述
  • 第四步:运行train.py文件训练得到再收集的数据集上面的微调模型
  • 第五步:通过chatbot.py对模型进行人机交互和推理

数据集地址

  • https://github.com/codemayq/chinese-chatbot-corpus, 安装上面的readme运行就可以得到相应的数据集
  • 然后再运行上面的preprocess.py就可以得到相关的训练数据集train.pkl

训练代码train.py


import argparse
import math
import time
import torch
import torch.nn.functional as F
import torch.optim as optim
import logging
from datetime import datetime
import os
from torch.utils.data import Dataset, DataLoader
from os.path import join, exists
from torch.nn import CrossEntropyLoss
from tqdm import tqdm
from torch.nn import DataParallel
import transformers
import pickle
import sys
from pytorchtools import EarlyStopping
from sklearn.model_selection import train_test_split
from data_parallel import BalancedDataParallel
from transformers import GPT2TokenizerFast, GPT2LMHeadModel, GPT2Config
from transformers import BertTokenizerFast
import pandas as pd
import torch.nn.utils.rnn as rnn_utils
import numpy as np
from dataset import MyDataset


def set_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('--device', default='3', type=str, required=False, help='设置使用哪些显卡')
    parser.add_argument('--no_cuda', action='store_true', help='不使用GPU进行训练')
    parser.add_argument('--vocab_path', default='vocab/vocab.txt', type=str, required=False,
                        help='词表路径')
    parser.add_argument('--model_config', default='config/config.json', type=str, required=False,
                        help='设置模型参数')
    parser.add_argument('--train_path', default='data/train.pkl', type=str, required=False, help='训练集路径')
    parser.add_argument('--max_len', default=150, type=int, required=False, help='训练时,输入数据的最大长度')

    parser.add_argument('--log_path', default='data/train.log', type=str, required=False, help='训练日志存放位置')
    parser.add_argument('--log', default=True, help="是否记录日志")
    parser.add_argument('--ignore_index', default=-100, type=int, required=False, help='对于ignore_index的label token不计算梯度')
    # parser.add_argument('--input_len', default=200, type=int, required=False, help='输入的长度')
    parser.add_argument('--epochs', default=20, type=int, required=False, help='训练的最大轮次')
    parser.add_argument('--batch_size', default=64, type=int, required=False, help='训练的batch size')
    parser.add_argument('--gpu0_bsz', default=10, type=int, required=False, help='0号卡的batch size')
    parser.add_argument('--lr', default=2.6e-5, type=float, required=False, help='学习率')
    parser.add_argument('--eps', default=1.0e-09, type=float, required=False, help='衰减率')
    parser.add_argument('--log_step', default=1, type=int, required=False, help='多少步汇报一次loss')
    parser.add_argument('--gradient_accumulation_steps', default=4, type=int, required=False, help='梯度积累')
    parser.add_argument('--max_grad_norm', default=2.0, type=float, required=False)
    parser.add_argument('--save_model_path', default='model_new', type=str, required=False,
                        help='模型输出路径')
    parser.add_argument('--pretrained_model', default='./pretrained_model', type=str, required=False,
                        help='预训练的模型的路径')
    # parser.add_argument('--seed', type=int, default=None, help='设置种子用于生成随机数,以使得训练的结果是确定的')
    parser.add_argument('--num_workers', type=int, default=0, help="dataloader加载数据时使用的线程数量")
    parser.add_argument('--patience', type=int, default=0, help="用于early stopping,设为0时,不进行early stopping.early stop得到的模型的生成效果不一定会更好。")
    parser.add_argument('--warmup_steps', type=int, default=4000, help='warm up步数')
    # parser.add_argument('--label_smoothing', default=True, action='store_true', help='是否进行标签平滑')
    parser.add_argument('--val_num', type=int, default=8000, help='验证集大小')
    args = parser.parse_args()
    return args


def create_logger(args):
    """
    将日志输出到日志文件和控制台
    """
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)

    formatter = logging.Formatter(
        '%(asctime)s - %(levelname)s - %(message)s')

    # 创建一个handler,用于写入日志文件
    file_handler = logging.FileHandler(
        filename=args.log_path)
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    logger.addHandler(file_handler)

    # 创建一个handler,用于将日志输出到控制台
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG)
    console.setFormatter(formatter)
    logger.addHandler(console)

    return logger


def collate_fn(batch):
    input_ids = rnn_utils.pad_sequence(batch, batch_first=True, padding_value=0)
    labels = rnn_utils.pad_sequence(batch, batch_first=True, padding_value=-100)
    return input_ids, labels


# def padding_batch(data_list, pad_id):
#     """
#     使用pad_id将data_list的每条数据,填充至data_list中最长的长度
#     :param data_list:
#     :param pad_id:
#     :return:
#     """
#     # 统计data_list中的最大长度
#     max_len = 0
#     for data in data_list:
#         max_len = max_len if max_len > len(data) else len(data)
#
#     # 对数据进行padding
#     new_data_list = []
#     for data in data_list:
#         new_data = data + [pad_id] * (max_len - len(data))
#         new_data_list.append(new_data)
#     return new_data_list


def load_dataset(logger, args):
    """
    加载训练集和验证集
    """
    logger.info("loading training dataset and validating dataset")
    train_path = args.train_path

    with open(train_path, "rb") as f:
        input_list = pickle.load(f)

    # 划分训练集与验证集
    val_num = args.val_num
    input_list_train = input_list[val_num:]
    input_list_val = input_list[:val_num]
    # test
    # input_list_train = input_list_train[:24]
    # input_list_val = input_list_val[:24]

    train_dataset = MyDataset(input_list_train, args.max_len)
    val_dataset = MyDataset(input_list_val, args.max_len)

    return train_dataset, val_dataset


def train_epoch(model, train_dataloader, optimizer, scheduler, logger,
                epoch, args):
    model.train()
    device = args.device
    # pad_id = args.pad_id
    # sep_id = args.sep_id
    ignore_index = args.ignore_index
    epoch_start_time = datetime.now()
    total_loss = 0  # 记录下整个epoch的loss的总和

    # epoch_correct_num:每个epoch中,output预测正确的word的数量
    # epoch_total_num: 每个epoch中,output预测的word的总数量
    epoch_correct_num, epoch_total_num = 0, 0

    for batch_idx, (input_ids, labels) in enumerate(train_dataloader):
        # print(f"the input_ids is: {input_ids}, and the labels is : {labels} !!!")
        # 捕获cuda out of memory exception
        try:
            input_ids = input_ids.to(device)
            labels = labels.to(device)
            outputs = model.forward(input_ids, labels=labels)
            logits = outputs.logits
            loss = outputs.loss
            loss = loss.mean()

            # 统计该batch的预测token的正确数与总数
            batch_correct_num, batch_total_num = calculate_acc(logits, labels, ignore_index=ignore_index)
            # 统计该epoch的预测token的正确数与总数
            epoch_correct_num += batch_correct_num
            epoch_total_num += batch_total_num
            # 计算该batch的accuracy
            batch_acc = batch_correct_num / batch_total_num

            total_loss += loss.item()
            if args.gradient_accumulation_steps > 1:
                loss = loss / args.gradient_accumulation_steps

            loss.backward()
            # 梯度裁剪
            torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)

            # 进行一定step的梯度累计之后,更新参数
            if (batch_idx + 1) % args.gradient_accumulation_steps == 0:
                # 更新参数
                optimizer.step()
                # 更新学习率
                scheduler.step()
                # 清空梯度信息
                optimizer.zero_grad()

            if (batch_idx + 1) % args.log_step == 0:
                logger.info(
                    "batch {} of epoch {}, loss {}, batch_acc {}, lr {}".format(
                        batch_idx + 1, epoch + 1, loss.item() * args.gradient_accumulation_steps, batch_acc, scheduler.get_lr()))

            del input_ids, outputs

        except RuntimeError as exception:
            if "out of memory" in str(exception):
                logger.info("WARNING: ran out of memory")
                if hasattr(torch.cuda, 'empty_cache'):
                    torch.cuda.empty_cache()
            else:
                logger.info(str(exception))
                raise exception

    # 记录当前epoch的平均loss与accuracy
    epoch_mean_loss = total_loss / len(train_dataloader)
    epoch_mean_acc = epoch_correct_num / epoch_total_num
    logger.info(
        "epoch {}: loss {}, predict_acc {}".format(epoch + 1, epoch_mean_loss, epoch_mean_acc))

    # save model
    logger.info('saving model for epoch {}'.format(epoch + 1))
    model_path = join(args.save_model_path, 'epoch{}'.format(epoch + 1))
    if not os.path.exists(model_path):
        os.mkdir(model_path)
    model_to_save = model.module if hasattr(model, 'module') else model
    model_to_save.save_pretrained(model_path)
    logger.info('epoch {} finished'.format(epoch + 1))
    epoch_finish_time = datetime.now()
    logger.info('time for one epoch: {}'.format(epoch_finish_time - epoch_start_time))

    return epoch_mean_loss


def validate_epoch(model, validate_dataloader, logger, epoch, args):
    logger.info("start validating")
    model.eval()
    device = args.device
    # pad_id = args.pad_id
    # sep_id = args.sep_id
    ignore_index = args.ignore_index
    epoch_start_time = datetime.now()
    total_loss = 0
    # 捕获cuda out of memory exception
    try:
        with torch.no_grad():
            for batch_idx, (input_ids, labels) in enumerate(validate_dataloader):
                input_ids = input_ids.to(device)
                labels = labels.to(device)
                outputs = model.forward(input_ids, labels=labels)
                logits = outputs.logits
                loss = outputs.loss
                loss = loss.mean()

                total_loss += loss.item()
                del input_ids, outputs

            # 记录当前epoch的平均loss
            epoch_mean_loss = total_loss / len(validate_dataloader)
            logger.info(
                "validate epoch {}: loss {}".format(epoch+1, epoch_mean_loss))
            epoch_finish_time = datetime.now()
            logger.info('time for validating one epoch: {}'.format(epoch_finish_time - epoch_start_time))
            return epoch_mean_loss
    except RuntimeError as exception:
        if "out of memory" in str(exception):
            logger.info("WARNING: ran out of memory")
            if hasattr(torch.cuda, 'empty_cache'):
                torch.cuda.empty_cache()
        else:
            logger.info(str(exception))
            raise exception


def train(model, logger, train_dataset, validate_dataset, args):
    train_dataloader = DataLoader(
        train_dataset, batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=collate_fn,
        drop_last=True
    )
    validate_dataloader = DataLoader(validate_dataset, batch_size=args.batch_size, shuffle=True,
                                     num_workers=args.num_workers, collate_fn=collate_fn, drop_last=True)
    early_stopping = EarlyStopping(args.patience, verbose=True, save_path=args.save_model_path)
    t_total = len(train_dataloader) // args.gradient_accumulation_steps * args.epochs
    optimizer = transformers.AdamW(model.parameters(), lr=args.lr, eps=args.eps)
    # scheduler = transformers.WarmupLinearSchedule(optimizer, warmup_steps=args.warmup_steps, t_total=t_total)
    scheduler = transformers.get_linear_schedule_with_warmup(
        optimizer, num_warmup_steps=args.warmup_steps, num_training_steps=t_total
    )

    logger.info('starting training')

    # 用于记录每个epoch训练和验证的loss
    train_losses, validate_losses = [], []
    # 记录验证集的最小loss
    best_val_loss = 10000
    # 开始训练
    for epoch in range(args.epochs):
        # ========== train ========== #
        train_loss = train_epoch(
            model=model, train_dataloader=train_dataloader,
            optimizer=optimizer, scheduler=scheduler,
            logger=logger, epoch=epoch, args=args)
        train_losses.append(train_loss)

        # ========== validate ========== #
        validate_loss = validate_epoch(
            model=model, validate_dataloader=validate_dataloader,
            logger=logger, epoch=epoch, args=args)
        validate_losses.append(validate_loss)

        # 保存当前困惑度最低的模型,困惑度低,模型的生成效果不一定会越好
        if validate_loss < best_val_loss:
            best_val_loss = validate_loss
            logger.info('saving current best model for epoch {}'.format(epoch + 1))
            model_path = join(args.save_model_path, 'min_ppl_model'.format(epoch + 1))
            if not os.path.exists(model_path):
                os.mkdir(model_path)
            model_to_save = model.module if hasattr(model, 'module') else model
            model_to_save.save_pretrained(model_path)

        #  如果patience=0,则不进行early stopping
        if args.patience == 0:
            continue
        early_stopping(validate_loss, model)
        if early_stopping.early_stop:
            logger.info("Early stopping")
            break
    logger.info('training finished')
    logger.info("train_losses:{}".format(train_losses))
    logger.info("validate_losses:{}".format(validate_losses))


def caculate_loss(logit, target, pad_idx, smoothing=True):
    if smoothing:
        logit = logit[..., :-1, :].contiguous().view(-1, logit.size(2))
        target = target[..., 1:].contiguous().view(-1)

        eps = 0.1
        n_class = logit.size(-1)

        one_hot = torch.zeros_like(logit).scatter(1, target.view(-1, 1), 1)
        one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1)
        log_prb = F.log_softmax(logit, dim=1)

        non_pad_mask = target.ne(pad_idx)
        loss = -(one_hot * log_prb).sum(dim=1)
        loss = loss.masked_select(non_pad_mask).mean()  # average later
    else:
        # loss = F.cross_entropy(predict_logit, target, ignore_index=pad_idx)
        logit = logit[..., :-1, :].contiguous().view(-1, logit.size(-1))
        labels = target[..., 1:].contiguous().view(-1)
        loss = F.cross_entropy(logit, labels, ignore_index=pad_idx)
    return loss


def calculate_acc(logit, labels, ignore_index=-100):
    logit = logit[..., :-1, :].contiguous().view(-1, logit.size(-1))
    labels = labels[..., 1:].contiguous().view(-1)

    _, logit = logit.max(dim=-1)  # 对于每条数据,返回最大的index
    # 进行非运算,返回一个tensor,若labels的第i个位置为pad_id,则置为0,否则为1
    non_pad_mask = labels.ne(ignore_index)
    n_correct = logit.eq(labels).masked_select(non_pad_mask).sum().item()
    n_word = non_pad_mask.sum().item()
    return n_correct, n_word


def main():
    # 初始化参数
    args = set_args()

    # 设置使用哪些显卡进行训练
    os.environ["CUDA_VISIBLE_DEVICES"] = args.device

    args.cuda = not args.no_cuda

    if args.batch_size < 2048 and args.warmup_steps <= 4000:
        print('[Warning] The warmup steps may be not enough.\n' \
              '(sz_b, warmup) = (2048, 4000) is the official setting.\n' \
              'Using smaller batch w/o longer warmup may cause ' \
              'the warmup stage ends with only little data trained.')

    # 创建日志对象
    logger = create_logger(args)
    # 当用户使用GPU,并且GPU可用时
    args.cuda = torch.cuda.is_available() and not args.no_cuda
    device = 'cuda:0' if args.cuda else 'cpu'
    args.device = device
    logger.info('using device:{}'.format(device))

    # 初始化tokenizer
    tokenizer = BertTokenizerFast(vocab_file=args.vocab_path, sep_token="[SEP]", pad_token="[PAD]", cls_token="[CLS]")
    args.sep_id = tokenizer.sep_token_id
    args.pad_id = tokenizer.pad_token_id
    args.cls_id = tokenizer.cls_token_id

    # 创建模型的输出目录
    if not os.path.exists(args.save_model_path):
        os.mkdir(args.save_model_path)

    # 创建模型
    if args.pretrained_model:  # 加载预训练模型
        model = GPT2LMHeadModel.from_pretrained(args.pretrained_model)
    else:  # 初始化模型
        model_config = GPT2Config.from_json_file(args.model_config)
        model = GPT2LMHeadModel(config=model_config)
    model = model.to(device)
    logger.info('model config:\n{}'.format(model.config.to_json_string()))
    assert model.config.vocab_size == tokenizer.vocab_size

    # 并行训练模型
    if args.cuda and torch.cuda.device_count() > 1:
        model = DataParallel(model).cuda()
        # model = BalancedDataParallel(args.gpu0_bsz, model, dim=0).cuda()
        logger.info("use GPU {} to train".format(args.device))

    # 计算模型参数数量
    num_parameters = 0
    parameters = model.parameters()
    for parameter in parameters:
        num_parameters += parameter.numel()
    logger.info('number of model parameters: {}'.format(num_parameters))

    # 记录参数设置
    logger.info("args:{}".format(args))

    # 加载训练集和验证集
    # ========= Loading Dataset ========= #
    train_dataset, validate_dataset = load_dataset(logger, args)

    train(model, logger, train_dataset, validate_dataset, args)


if __name__ == '__main__':
    main()

dataset.py文件


from torch.utils.data import Dataset
import torch
class MyDataset(Dataset):
    """

    """

    def __init__(self, input_list, max_len):
        self.input_list = input_list
        self.max_len = max_len

    def __getitem__(self, index):
        input_ids = self.input_list[index]
        input_ids = input_ids[:self.max_len]
        input_ids = torch.tensor(input_ids, dtype=torch.long)
        return input_ids

    def __len__(self):
        return len(self.input_list)

训练数据处理代码preprocess.py


from tokenizers import BertWordPieceTokenizer
from transformers import BertTokenizer
from transformers import BertTokenizerFast
import argparse
import pandas as pd
import pickle
from tqdm import tqdm
from transformers import GPT2TokenizerFast, GPT2LMHeadModel
import logging
import numpy as np


def create_logger(log_path):
    """
    将日志输出到日志文件和控制台
    """
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.INFO)

    formatter = logging.Formatter(
        '%(asctime)s - %(levelname)s - %(message)s')

    # 创建一个handler,用于写入日志文件
    file_handler = logging.FileHandler(
        filename=log_path)
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    logger.addHandler(file_handler)

    # 创建一个handler,用于将日志输出到控制台
    console = logging.StreamHandler()
    console.setLevel(logging.DEBUG)
    console.setFormatter(formatter)
    logger.addHandler(console)

    return logger


def preprocess():
    """
    对原始语料进行tokenize,将每段对话处理成如下形式:"[CLS]utterance1[SEP]utterance2[SEP]utterance3[SEP]"
    """
    # 设置参数
    parser = argparse.ArgumentParser()
    parser.add_argument('--vocab_path', default='vocab/vocab.txt', type=str, required=False,
                        help='词表路径')
    parser.add_argument('--log_path', default='data/preprocess.log', type=str, required=False, help='训练日志存放位置')
    parser.add_argument('--train_path', default='50w_qa_data', type=str, required=False, help='训练日志存放位置')
    parser.add_argument('--save_path', default='data/train.pkl', type=str, required=False, help='tokenize的训练数据集')
    args = parser.parse_args()

    # 初始化日志对象
    logger = create_logger(args.log_path)

    # 初始化tokenizer
    tokenizer = BertTokenizerFast(vocab_file=args.vocab_path, sep_token="[SEP]", pad_token="[PAD]", cls_token="[CLS]")
    sep_id = tokenizer.sep_token_id
    cls_id = tokenizer.cls_token_id
    logger.info("preprocessing data,data path:{}, save path:{}".format(args.train_path, args.save_path))

    # 读取训练数据集
    with open(args.train_path, 'rb') as f:
        data = f.read().decode("utf-8")

    # 需要区分linux和windows环境下的换行符
    if "\r\n" in data:
        train_data = data.split("\r\n\r\n")
    else:
        train_data = data.split("\n")
    logger.info("there are {} dialogue in dataset".format(len(train_data)))

    # 开始进行tokenize
    # 保存所有的对话数据,每条数据的格式为:"[CLS]utterance1[SEP]utterance2[SEP]utterance3[SEP]"
    dialogue_len = []  # 记录所有对话tokenize之后的长度,用于统计中位数与均值
    dialogue_list = []
    with open(args.save_path, "w", encoding="utf-8") as f:
        for index, dialogue in enumerate(tqdm(train_data)):
            if "\r\n" in data:
                utterances = dialogue.split("\r\n")
            else:
                utterances = dialogue.split("\t")

            input_ids = [cls_id]  # 每个dialogue以[CLS]开头
            for utterance in utterances:
                input_ids += tokenizer.encode(utterance, add_special_tokens=False)
                input_ids.append(sep_id)  # 每个utterance之后添加[SEP],表示utterance结束
            dialogue_len.append(len(input_ids))
            dialogue_list.append(input_ids)
    # len_mean = np.mean(dialogue_len)
    # len_median = np.median(dialogue_len)
    # len_max = np.max(dialogue_len)
    with open(args.save_path, "wb") as f:
        pickle.dump(dialogue_list, f)
    # logger.info("finish preprocessing data,the result is stored in {}".format(args.save_path))
    # logger.info("mean of dialogue len:{},median of dialogue len:{},max len:{}".format(len_mean, len_median, len_max))


if __name__ == '__main__':
    preprocess()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1568308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解密AI人工智能的整体分层架构:探索智能科技的未来之路

随着人工智能技术的迅猛发展&#xff0c;AI已经渗透到我们生活的方方面面。而支撑AI人工智能系统运作的核心是其整体分层架构。本文将深入探讨AI人工智能的整体分层架构&#xff0c;揭示其中的奥秘&#xff0c;探索智能科技的未来之路。 ### AI人工智能整体分层架构的重要性 …

Linux上安装DM8(达梦数据库),SpringBoot集成达梦

1.达梦数据库在Linux上的安装 官方手册:https://eco.dameng.com/document/dm/zh-cn/start/install-dm-linux-prepare.html 1.1下载安装包 官网:https://www.dameng.com/list_103.html 点击”服务与合作”--> “下载中心” 这里选择对应的cpu和操作系统(举个例子:windows版本…

Java基础 - 代码练习

第一题&#xff1a;集合的运用&#xff08;幸存者&#xff09; public class demo1 {public static void main(String[] args) {ArrayList<Integer> array new ArrayList<>(); //一百个囚犯存放在array集合中Random r new Random();for (int i 0; i < 100; …

JimuReport 积木报表

一款免费的数据可视化报表&#xff0c;含报表和大屏设计&#xff0c;像搭建积木一样在线设计报表&#xff01;功能涵盖&#xff0c;数据报表、打印设计、图表报表、大屏设计等&#xff01; Web 版报表设计器&#xff0c;类似于 excel 操作风格&#xff0c;通过拖拽完成报表设计…

从零到百万富翁:ChatGPT + Pinterest

原文&#xff1a;Zero to Millionaire Online: ChatGPT Pinterest 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 在社交媒体上赚取百万美元 - 逐步指南&#xff0c;如何在线赚钱版权 献给&#xff1a; 我将这本书&#xff0c;“从零到百万富翁在线&#xff1a;Chat…

【御控物联】JavaScript JSON结构转换(18):数组To对象——多层属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、案例之《JSON数组 To JSON对象》三、代码实现四、在线转换工具五、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0c;生成新的JS…

基于PHP的校园招聘管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的校园招聘管理系统 一 介绍 此校园招聘管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为个人用户&#xff0c;企业和管理员三种。 技术栈&#xff1a;phpmysqlbootstrapphpstudyvscode 二…

JS——判断节假日(假日包括周末,不包括调休上班的周末)

思路&#xff1a;创建两个数组&#xff0c;数组1为节假日数组&#xff0c;数组2为是周末上班日期数组。如果当前日期&#xff08;或某日期&#xff09;同时满足2个条件&#xff08;1.在节假日数组内或在周末。2.不在周末上班日期数组&#xff09;即为节假日&#xff0c;否则即为…

Mybatis-Plus05(分页插件)

分页插件 MyBatis Plus自带分页插件&#xff0c;只要简单的配置即可实现分页功能 1. 添加配置类 Configuration MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处 public class MybatisPlusConfig {Bean public MybatisPlusIntercepto…

LeetCode-105. 从前序与中序遍历序列构造二叉树【树 数组 哈希表 分治 二叉树】

LeetCode-105. 从前序与中序遍历序列构造二叉树【树 数组 哈希表 分治 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;暴力搜索解题思路二&#xff1a;哈希查找解题思路三&#xff1a; 题目描述&#xff1a; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 pr…

通过vite创建项目

一、VUE3官网 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org) 二、通过Vite创建项目 1、在cmd窗口下&#xff0c;全局安装vite //使用国内镜像源 npm config set registryhttps://registry.npmmirror.com//安装最新版vite npm install -g vitelatest Vite | 下一代…

Python爬虫——基于JWT的模拟登录爬取实战

基于JWT的模拟登录爬取实战 JWT&#xff08;JSON Web Token&#xff09;主要由三部分组成&#xff1a; Header&#xff1a;包含了Token的类型&#xff08;“typ”&#xff09;和签名算法&#xff08;“alg”&#xff09;信息。通常情况下&#xff0c;这个部分会指定为{"…

ChatGPT 之创造力与沟通的演化

原文&#xff1a;ChatGPT … The Evolution of Creativity and Communication 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 作者注 亲爱的读者&#xff0c; ChatGPT&#xff0c;一位 AI 专家和一位律师走进了一家酒吧。酒保看着他们说&#xff1a;“这是什么&…

企业邮箱给谷歌Gmail报错550-5.7.25解决方案

企业邮箱给谷歌Gmail报错550-5.7.25解决方案 问题表现 今天接到同事报告企业邮箱发送报错的问题&#xff0c;具体问题表现如下&#xff1a; 我司内部邮箱 xxXXX.com 邮箱给国内的163和新浪和企业内部发送邮件可以成功给Hotmail发送邮件&#xff0c;成功。给Gmail发送邮件&am…

java计算机网络(一)-- url,tcp,udp,socket

网络编程&#xff1a; 计算机网络 计算机网络指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统、网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统。 网络协议…

前端工程师————CSS学习

选择器分类 选择器分为基础选择器和复合选择器 基础选择器包括&#xff1a;标签选择器&#xff0c;类选择器&#xff0c;id选择器&#xff0c;通配符选择器标签选择器 类选择器 语法&#xff1a;.类名{属性1&#xff1a; 属性值&#xff1b;} 类名可以随便起 多类名使用方式&am…

华为openEuler-22.03-LTS-SP3配置yum源

先有华为后有天&#xff0c;遥遥领先&#xff01; 1 确定使用的OS版本 # cat /etc/os-release NAME"openEuler" VERSION"22.03 (LTS-SP3)" ID"openEuler" VERSION_ID"22.03" PRETTY_NAME"openEuler 22.03 (LTS-SP3)" ANSI…

深度解析大语言模型中的词向量

在学习完语言模型中的编码器与解码器知识后&#xff0c;让我们继续深入学习大语言模型中一个非常关键的技术&#xff1a;词向量表示&#xff0c;以及如何通过Transformer模型实现对next token的预测。 1、词向量 要了解语言模型的工作原理&#xff0c;首先需要了解它们如何表示…

VSCode美化

今天有空收拾了一下VSCode&#xff0c;页面如下&#xff0c;个人觉得还是挺好看的~~ 1. 主题 Noctis 色彩较多&#xff0c;有种繁杂美。 我使用的是浅色主题的一款Noctis Hibernus 2. 字体 Maple Mono 官网&#xff1a;Maple-Font 我只安装了下图两个字体&#xff0c;使…

UE4_普通贴图制作法线Normal材质

UE4 普通贴图制作法线Normal材质 2021-07-02 10:46 导入一张普通贴图&#xff1a; 搜索节点&#xff1a;NormalFromHeightmap 搜索节点&#xff1a;TextureObjectparameter&#xff0c;并修改成导入的普通贴图&#xff0c;连接至HeightMap中 创建参数normal&#xff0c;连接…