基于CPM的中文文本生成学习过程

项目代码:GitHub - yangjianxin1/CPM: Easy-to-use CPM for Chinese text generation(基于CPM的中文文本生成)

论文地址: CPM: A large-scale generative Chinese Pre-trained language model - ScienceDirect

CPM(Chinese Pretrained Models)模型是北京智源人工智能研究院和清华大学发布的中文大规模预训练模型。

数据集

 这是作者从网上爬取的26W片中文作文中的一个例子,将它作为训练语料,从展示的图片中可以看到,每一篇作文的格式由”标题-日期-作者-文章内容“构成,这里只使用文章标题和内容进行训练。

对于每篇作文,将标题与内容通过[sep]连接,以[eod]作为结束符,即最终形式为:标题[sep]内容[eod]。

数据预处理

CpmTokenizer是一个基于中文预训练模型CPM(Chinese Pretrained Models)的分词器,用于中文文本的分词。使用convert_tokens_to_ids()方法将特殊标记<eod>转换为对应的标记ID,并且使用sep_token_id属性获取分词器的分隔符标记ID。

tokenizer = CpmTokenizer(vocab_file="vocab/chinese_vocab.model")
eod_id = tokenizer.convert_tokens_to_ids("<eod>")   # 文档结束符
sep_id = tokenizer.sep_token_id

接着通过tokenizer.encode()方法将标题和文章内容进行分词并转换为对应的标记ID,设置了add_special_tokens=False以避免添加特殊标记。接下来,我们将标题的标记ID、分隔符标记ID、文章内容的标记ID和文档结束符的标记ID按照指定的顺序组合成token_ids列表。

title_ids = tokenizer.encode(title, add_special_tokens=False)
article_ids = tokenizer.encode(article, add_special_tokens=False)
token_ids = title_ids + [sep_id] + article_ids + [eod_id]

我们对每篇文章的进行截断处理,以每200个字符进行截断,然后将截断后的语料加入到训练列表train_list中。

win_size = args.win_size  # 200
step = args.step  # 200
start_index = 0
end_index = win_size
data = token_ids[start_index:end_index]
train_list.append(data)
start_index += step
end_index += step
while end_index+50 < len(token_ids):  # 剩下的数据长度,大于或等于50,才加入训练数据集
    data = token_ids[start_index:end_index]
    train_list.append(data)
    start_index += step
    end_index += step

最后将处理好的训练语料进行保存,便于训练时进行加载。

# 序列化训练数据
with open(args.save_path, "wb") as f:
    pickle.dump(train_list, f)

数据集构建

这是一个用于加载训练数据的函数。

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

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

    # test
    # train_list = train_list[:24]
    logger.info('len of train data:{}'.format(len(train_list)))
    train_dataset = CPMDataset(train_list, args.max_len)

    return train_dataset

经过预处理和分词后的训练语料如下所示,因为是使用200的窗口进行截断的,所以每个训练预料的长度都为200。 

模型

模型整体采用GPT的结构,使用了100G的中文语料进行预训练,本质上是一个中文的GPT2模型。以往的中文预训练模型中,往往采用字符粒度的词表,也就是一个字符作为token。但是在中文中,词语往往由多个汉字构成,其中包含了丰富的语义信息,如果只使用汉字词表,将会损失这种语义的内在关联。所以在CPM模型中,作者同时使用字符和词语来构造模型词表。在代码中,作者使用SentencePiece来构造词表。

    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)

GPT2模型的结构与Transformer的Decoder大体相同。输入由单词嵌入和位置嵌入两部分组成,模型主要是由掩膜多头注意力、层归一化、全连接层和残差连接等结构组成。在训练的时候,掩膜矩阵为下三角矩阵,使用第n个位置的输出向量,预测第n+1个位置的单词。第n个位置的输出向量包含了前n个单词的语义信息,本质上也就是根据前n个单词,预测第n+1个单词。

模型训练

将训练语料依次送入模型中进行训练,每次更新参数并且计算模型预测token的正确率

    for batch_idx, (input_ids, labels) in enumerate(train_dataloader):
        # 捕获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 {}, step {}, loss {}, batch_acc {}, lr {}".format(
                        batch_idx + 1, epoch + 1, step, loss.item() * args.gradient_accumulation_steps, batch_acc, scheduler.get_lr()))
                step = epoch * len(train_dataloader) + batch_idx
                writer.add_scalar('train loss', loss.item()*args.gradient_accumulation_steps, step)
                writer.add_scalar('train acc', batch_acc, step)

            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

模型预测

在生成阶段,根据输入的标题和上文,在每一步中,模型输出下一个位置的单词概率分布,然后使用topk采样或者topp采样(核采样)生成下一个位置的单词。

首先是同样的将输入的标题和提示内容进行编码:

    title_ids = tokenizer.encode(title, add_special_tokens=False)
    context_ids = tokenizer.encode(context, add_special_tokens=False)
    input_ids = title_ids + [sep_id] + context_ids

接着将编码后的上文送入训练好的模型对下一个词进行预测,这里使用top-k和top-p(nucleus)采样策略对调整后的概率分布进行过滤,得到过滤后的概率filtered_logits。接着使用多项式分布进行抽样,根据过滤后的概率分布从候选集合中抽取一个下一个token的ID,使用torch.multinomial()函数实现。

def generate_next_token(input_ids):
    """
    对于给定的上文,生成下一个单词
    """
    outputs = model(input_ids=input_ids)
    logits = outputs.logits
    # next_token_logits表示最后一个token的hidden_state对应的prediction_scores,也就是模型要预测的下一个token的概率
    next_token_logits = logits[0, -1, :]
    next_token_logits = next_token_logits / args.temperature
    # 对于<unk>的概率设为无穷小,也就是说模型的预测结果不可能是[UNK]这个token
    next_token_logits[unk_id] = -float('Inf')
    filtered_logits = top_k_top_p_filtering(next_token_logits, top_k=args.topk, top_p=args.topp)
    # torch.multinomial表示从候选集合中选出无放回地进行抽取num_samples个元素,权重越高,抽到的几率越高,返回元素的下标
    next_token_id = torch.multinomial(F.softmax(filtered_logits, dim=-1), num_samples=1)
    return next_token_id

整个创作过程采用自回归的方式使用训练好的模型对上文不断对下一个词进行预测,再把预测到的词加入上文中形成新的语句再次对下一个词进行预测,这样不断循环直到生成结束符,生成代码如下:

def generate(max_len):
    # 对title与context进行tokenize
    title_ids = tokenizer.encode(title, add_special_tokens=False)
    context_ids = tokenizer.encode(context, add_special_tokens=False)
    input_ids = title_ids + [sep_id] + context_ids
    cur_len = len(input_ids)
    last_token_id = input_ids[-1]  # 已生成的内容的最后一个token
    input_ids = torch.tensor([input_ids], dtype=torch.long, device=device)

    while True:
        next_token_id = generate_next_token(input_ids[:, -args.context_len:])
        input_ids = torch.cat((input_ids, next_token_id.unsqueeze(0)), dim=1)
        cur_len += 1
        word = tokenizer.convert_ids_to_tokens(next_token_id.item())
        # if cur_len >= max_len:
        #     break
        # 超过最大长度,并且换行
        if cur_len >= max_len and last_token_id == 8 and next_token_id == 3:
            break
        # 超过最大长度,并且生成标点符号
        if cur_len >= max_len and word in [".", "。", "!", "!", "?", "?", ",", ","]:
            break
        # 生成结束符
        if next_token_id == eod_id:
            break
    result = tokenizer.decode(input_ids.squeeze(0))
    return result

以下是生成的样例:

参考文献:

基于CPM的中文作文生成模型,引经据典、修辞手法,信手拈来 (qq.com)

mirrors / yangjianxin1 / cpm · GitCode

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于不同深度学习框架实现中文文本分类 深度学习(Deep Learning,简称DL)是机器学习(Machine Learning,简称ML)领域中一个新的研究方向,其目标是让机器能够像人一样具有分析学习能力,识别文字、图像和声音等数据。深度学习通过学习样本数据的内在规律和表示层次,使机器能够模仿视听和思考等人类活动,从而解决复杂的模式识别难题。 深度学习的核心是神经网络,它由若干个层次构成,每个层次包含若干个神经元。神经元接收上一层次神经元的输出作为输入,通过加权和转换后输出到下一层次神经元,最终生成模型的输出结果。神经网络之间的权值和偏置是神经网络的参数,决定了输入值和输出值之间的关系。 深度学习的训练过程通常涉及反向传播算法,该算法用于优化网络参数,使神经网络能够更好地适应数据。训练数据被输入到神经网络中,通过前向传播算法将数据从输入层传递到输出层,然后计算网络输出结果与实际标签之间的差异,即损失函数。通过反向传播算法,网络参数会被调整以减小损失函数值,直到误差达到一定的阈值为止。 深度学习中还包含两种主要的神经网络类型:卷积神经网络(Convolutional Neural Networks,简称CNN)和循环神经网络(Recurrent Neural Networks,简称RNN)。卷积神经网络特别擅长处理图像数据,通过逐层卷积和池化操作,逐步提取图像中的高级特征。循环神经网络则适用于处理序列数据,如文本或时间序列数据,通过捕捉序列中的依赖关系来生成模型输出。 深度学习在许多领域都取得了显著的成果,包括计算机视觉及图像识别、自然语言处理、语音识别及生成、推荐系统、游戏开发、医学影像识别、金融风控、智能制造、购物领域、基因组学等。随着技术的不断发展,深度学习将在更多领域展现出其潜力。 在未来,深度学习可能会面临一些研究热点和挑战,如自监督学习、小样本学习、联邦学习、自动机器学习、多模态学习、自适应学习、量子机器学习等。这些研究方向将推动深度学习技术的进一步发展和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值