精华内容
下载资源
问答
  • 文本生成:基于GPT-2的中文新闻文本生成

    千次阅读 多人点赞 2020-03-07 00:42:27
    BERT类预训练模型基于MLM,融合了双向上下文信息,不是天然匹配文本生成类任务(也有针对BERT模型进行改进的多种方式完善了BERT的这个缺点,如UniLM)。openAI的GPT-2模型天然适合文本生成类任务,因此使用GPT-2模型...

    文本生成一直是NLP领域内研究特别活跃的一个任务,应用前景特别广泛。BERT类预训练模型基于MLM,融合了双向上下文信息,不是天然匹配文本生成类任务(也有针对BERT模型进行改进的多种方式完善了BERT的这个缺点,如UniLM)。openAI的GPT-2模型天然适合文本生成类任务,因此使用GPT-2模型来完成中文新闻文本生成任务。
    代码及数据、模型下载链接:网盘链接,提取码:8goz

    数据集

    数据集是THUCnews的,清华大学根据新浪新闻RSS订阅频道2005-2011年间的历史数据筛选过滤生成。总共有体育、财经等共10个类别新闻,筛选出其中的财经类新闻作为训练语料。使用BERT的tokenizer,可以分词也可以不分词。选择分词的话最好根据训练语料提取一份词表,不分词的话可以直接使用原BERT模型提供的词表。GPT-2模型训练要求语料连续,最好是段落或者大文档类不能太短,否则影响模型表现。训练前将语料预处理切分。勾选raw,自动处理语料。

    def build_files(data_path, tokenized_data_path, num_pieces, full_tokenizer, min_length):
        with open(data_path, 'r', encoding='utf8') as f:
            print('reading lines')
            lines = json.load(f)
            lines = [line.replace('\n', ' [SEP] ') for line in lines]  # 用[SEP]表示换行, 段落之间使用SEP表示段落结束
    
        all_len = len(lines)
        if not os.path.exists(tokenized_data_path):
            os.makedirs(tokenized_data_path)
        for i in tqdm(range(num_pieces)):
            sublines = lines[all_len // num_pieces * i: all_len // num_pieces * (i + 1)]
            if i == num_pieces - 1:
                sublines.extend(lines[all_len // num_pieces * (i + 1):])  # 把尾部例子添加到最后一个piece
            sublines = [full_tokenizer.tokenize(line) for line in sublines if
                        len(line) > min_length]  # 只考虑长度超过min_length的句子
            sublines = [full_tokenizer.convert_tokens_to_ids(line) for line in sublines]
            full_line = []
            for subline in sublines:
                full_line.append(full_tokenizer.convert_tokens_to_ids('[MASK]'))  # 文章开头添加MASK表示文章开始
                full_line.extend(subline)
                full_line.append(full_tokenizer.convert_tokens_to_ids('[CLS]'))  # 文章之间添加CLS表示文章结束
            with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'w') as f:
                for id in full_line:
                    f.write(str(id) + ' ')
        print('finish')
    

    模型训练

    模型代码使用huggingface提供的GPT-2 model,使用AdamW优化器,warmup2000个step,使用线性衰减,初始学习率设置为1.5e-4,总共训练了5个epoch,大概35K个iteration。

    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument('--device', default='0,1,2,3', type=str, required=False, help='设置使用哪些显卡')
        parser.add_argument('--model_config', default='models/model_config.json', type=str, required=False,
                            help='选择模型参数')
        parser.add_argument('--tokenizer_path', default='models/vocab.txt', type=str, required=False, help='选择词库')
        parser.add_argument('--raw_data_path', default='./data/cnews.json', type=str, required=False, help='原始训练语料')
        parser.add_argument('--tokenized_data_path', default='data/tokenized/', type=str, required=False,
                            help='tokenized语料存放位置')
        parser.add_argument('--raw', action='store_true', help='是否先做tokenize')
        parser.add_argument('--epochs', default=5, type=int, required=False, help='训练循环')
        parser.add_argument('--batch_size', default=1, type=int, required=False, help='训练batch size')
        parser.add_argument('--lr', default=1.5e-4, type=float, required=False, help='学习率')
        parser.add_argument('--warmup_steps', default=2000, type=int, required=False, help='warm up步数')
        parser.add_argument('--log_step', default=10, type=int, required=False, help='多少步汇报一次loss,设置为gradient accumulation的整数倍')
        parser.add_argument('--stride', default=768, type=int, required=False, help='训练时取训练数据的窗口步长')
        parser.add_argument('--gradient_accumulation', default=1, type=int, required=False, help='梯度积累')
        parser.add_argument('--fp16', action='store_true', help='混合精度')
        parser.add_argument('--fp16_opt_level', default='O1', type=str, required=False)
        parser.add_argument('--max_grad_norm', default=1.0, type=float, required=False)
        parser.add_argument('--num_pieces', default=50, type=int, required=False, help='将训练语料分成多少份')
        parser.add_argument('--min_length', default=128, type=int, required=False, help='最短收录文章长度')
        parser.add_argument('--output_dir', default='models/cnews', type=str, required=False, help='模型输出路径')
        parser.add_argument('--pretrained_model', default='', type=str, required=False, help='模型训练起点路径')
        parser.add_argument('--writer_dir', default='runs/', type=str, required=False, help='Tensorboard路径')
        parser.add_argument('--segment', action='store_true', help='中文以词为单位')
    
        args = parser.parse_args()
        print('args:\n' + args.__repr__())
    
        if args.segment:
            from tokenization import tokenization_bert_word_level as tokenization_bert
        else:
            from tokenization import tokenization_bert
    
        os.environ["CUDA_VISIBLE_DEVICES"] = args.device  # 此处设置程序使用哪些显卡
    
        model_config = GPT2Config.from_json_file(args.model_config)
        print('config:\n' + model_config.to_json_string())
    
        n_ctx = model_config.n_ctx
    
        full_tokenizer = tokenization_bert.BertTokenizer(vocab_file=args.tokenizer_path)
        full_tokenizer.max_len = 999999
        device = 'cuda' if torch.cuda.is_available() else 'cpu'
        print('using device:', device)
    
        raw_data_path = args.raw_data_path
        tokenized_data_path = args.tokenized_data_path
        raw = args.raw  # 选择是否从零开始构建数据集
        epochs = args.epochs
        batch_size = args.batch_size
        lr = args.lr
        warmup_steps = args.warmup_steps
        log_step = args.log_step
        stride = args.stride
        gradient_accumulation = args.gradient_accumulation
        fp16 = args.fp16  # 不支持半精度的显卡请勿打开
        fp16_opt_level = args.fp16_opt_level
        max_grad_norm = args.max_grad_norm
        num_pieces = args.num_pieces
        min_length = args.min_length
        output_dir = args.output_dir
    
        assert log_step % gradient_accumulation == 0
    
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)
    
        if raw:
            print('building files')
            build_files(data_path=raw_data_path, tokenized_data_path=tokenized_data_path, num_pieces=num_pieces,
                        full_tokenizer=full_tokenizer, min_length=min_length)
            print('files built')
    
        if not args.pretrained_model:
            model = GPT2LMHeadModel(config=model_config)
        else:
            model = GPT2LMHeadModel.from_pretrained(args.pretrained_model)
        model.train()
        model.to(device)
    
        num_parameters = 0
        parameters = model.parameters()
        for parameter in parameters:
            num_parameters += parameter.numel()
        print('number of parameters: {}'.format(num_parameters))
    
        multi_gpu = False
        full_len = 0
        print('calculating total steps')
        for i in tqdm(range(num_pieces)):
            with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'r') as f:
                full_len += len([int(item) for item in f.read().strip().split()])
        total_steps = int(full_len / stride * epochs / batch_size / gradient_accumulation)
        print('total steps = {}'.format(total_steps))
    
        optimizer = AdamW(model.parameters(), lr=lr, correct_bias=True)
        scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps,
                                                              num_training_steps=total_steps)
        if fp16:
            try:
                from apex import amp
            except ImportError:
                raise ImportError("Please install apex from https://www.github.com/nvidia/apex to use fp16 training.")
            model, optimizer = amp.initialize(model, optimizer, opt_level=fp16_opt_level)
    
        if torch.cuda.device_count() > 1:
            print("Let's use", torch.cuda.device_count(), "GPUs!")
            model = DataParallel(model, device_ids=[int(i) for i in args.device.split(',')])
            multi_gpu = True
        print('starting training')
        overall_step = 0
        total_loss = 0
        logging_loss = 0
        tb_writer = SummaryWriter(log_dir=args.writer_dir)
        for epoch in trange(epochs):
            print('epoch {}'.format(epoch + 1))
            now = datetime.now()
            print('time: {}'.format(now))
            x = np.linspace(0, num_pieces - 1, num_pieces, dtype=np.int32)
            random.shuffle(x)
            for i in x:
                with open(tokenized_data_path + 'tokenized_train_{}.txt'.format(i), 'r') as f:
                    line = f.read().strip()
                tokens = line.split()
                tokens = [int(token) for token in tokens]
                start_point = 0
                samples = []
                while start_point < len(tokens) - n_ctx:
                    samples.append(tokens[start_point: start_point + n_ctx])
                    start_point += stride
                if start_point < len(tokens):
                    samples.append(tokens[len(tokens)-n_ctx:])
                random.shuffle(samples)
                for step in range(len(samples) // batch_size):  # drop last
    
                    #  prepare data
                    batch = samples[step * batch_size: (step + 1) * batch_size]
                    batch_inputs = []
                    for ids in batch:
                        int_ids = [int(x) for x in ids]
                        batch_inputs.append(int_ids)
                    batch_inputs = torch.tensor(batch_inputs).long().to(device)
    
                    #  forward pass
                    outputs = model.forward(input_ids=batch_inputs, labels=batch_inputs)
                    loss, logits = outputs[:2]
    
                    #  get loss
                    if multi_gpu:
                        loss = loss.mean()
                    if gradient_accumulation > 1:
                        loss = loss / gradient_accumulation
    
                    #  loss backward
                    if fp16:
                        with amp.scale_loss(loss, optimizer) as scaled_loss:
                            scaled_loss.backward()
                            torch.nn.utils.clip_grad_norm_(amp.master_params(optimizer), max_grad_norm)
                    else:
                        loss.backward()
                        torch.nn.utils.clip_grad_norm_(model.parameters(), max_grad_norm)
    
                    total_loss += loss.item()
                    #  optimizer step
                    if (overall_step + 1) % gradient_accumulation == 0:
                        optimizer.step()
                        optimizer.zero_grad()
                        scheduler.step()
                    if (overall_step + 1) % log_step == 0:
                        scale_loss = (total_loss - logging_loss) / log_step
                        tb_writer.add_scalar('loss', scale_loss, overall_step)
                        tb_writer.add_scalar('lr',scheduler.get_lr()[0],overall_step)
                        print('Step {} epoch {}, loss {}'.format(
                            overall_step + 1,
                            epoch + 1,
                            scale_loss))
                        logging_loss = total_loss
                    overall_step += 1
    
            print('saving model for epoch {}'.format(epoch + 1))
            if not os.path.exists(os.path.join(output_dir, 'model_epoch{}'.format(epoch + 1))):
                os.makedirs(os.path.join(output_dir, 'model_epoch{}'.format(epoch + 1)))
            model_to_save = model.module if hasattr(model, 'module') else model
            model_to_save.save_pretrained(os.path.join(output_dir, 'model_epoch{}'.format(epoch + 1)))
            # torch.save(scheduler.state_dict(), output_dir + 'model_epoch{}/scheduler.pt'.format(epoch + 1))
            # torch.save(optimizer.state_dict(), output_dir + 'model_epoch{}/optimizer.pt'.format(epoch + 1))
            print('epoch {} finished'.format(epoch + 1))
    
            then = datetime.now()
            print('time: {}'.format(then))
            print('time for one epoch: {}'.format(then - now))
    
        print('training finished')
        model_to_save = model.module if hasattr(model, 'module') else model
        model_to_save.save_pretrained(output_dir)
    

    模型结果评估

    模型loss曲线如下:
    在这里插入图片描述模型生成时提供了top-k以及top-p选择,temperature可调以及repetition_penalty,针对生成时解码较慢的情况,huggingface还贴心的提供了past回传。运行generation.py文件,即可查看模型生成效果:
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述

    分析

    基于GPT-2的中文文本生成还有很大很广阔的发展研究空间以及很多任务待解决,其应用领域也很多。

    展开全文
  • b-ber既是一种方法又是一种应用程序,可以从多种来源生成EPUB 3,Mobi / KF8,静态网站,PDF和XML文件等各种格式的出版物,可以将其导入到InDesign中以进行打印版式。由纯文本文件和其他资产组成。 b-ber还可以用作...
  • 给大家带来一款功能强大的批量文本提取器,该软件非常不错,当前支持支持正则表达式,批量提取文本,...可将提取信息生成文本文件、HTM网页文件、MDB数据库文件 可设置提取文件的间隔,免除下载网页过快,服务器不响应
  • 自动生成文本摘要

    万次阅读 2018-10-06 14:33:38
    什么是文本摘要生成 如何从网页上提取数据 如何清洗数据 如何构建直方图 如何计算句子分数 如何提取分数最高的句子/摘要 &amp;amp;amp;nbsp; 在继续往下阅读之前,我假设你已经了解下面几...

    欢迎大家关注我们的网站和系列教程:http://panchuang.net/ ,学习更多的机器学习、深度学习的知识!

    Revolver

    本文提及的主要内容有:
    • 什么是文本摘要生成
    • 如何从网页上提取数据
    • 如何清洗数据
    • 如何构建直方图
    • 如何计算句子分数
    • 如何提取分数最高的句子/摘要
     

    在继续往下阅读之前,我假设你已经了解下面几个方面的基础知识:

    • 正则表达式
    • 自然语言处理
    • 网页爬虫
     

     

    一、什么是文本摘要生成

    生成文本摘要的过程其实就是总结文本文档中心意思的过程,目的是创建包含原始文档主要内容的摘要。

    生成摘要的主要思想是找到包含整个数据集“信息”的数据子集。这种技术在当今行业内被广泛使用。搜索引擎就是一个例子; 其他还包括文档摘要生成,图像收集和视频处理。文档摘要生成算法试图通过查找信息量最大的句子来创建整个文档的代表性摘要,而在图像摘要中,计算机则试图找到最具代表性的显著的图像。对于监控视频,人们可能希望从平静的环境影像中提取出重要事件。

    自动摘要有两种通用方法:提取和抽象。

     

    二、如何从网页上提取数据?

    第1步:导入库/包

    • Beautiful Soup(bs)是一个Python库,用于从HTML和XML文件中提取数据。你可以把它和你最喜欢的解析器一起搭配使用,它们一起提供了一种符合我们习惯的导航,搜索和修改解析树的方法。这通常可以节省程序员数小时或数天的工作量。
    • Urllib是一个集合多个URL处理模块的软件包:
     

    urllib.request 用于打开url链接并读取内容

    urllib.error 包含由urllib.request抛出的异常值

    urllib.parse 用于解析URL

    urllib.robotparser用于解析 robots.txt 文件

     

    • re模块提供了类似于在Perl语言里的正则表达式匹配操作功能。
    • nltk是一个帮助构建处理人类语言数据的Python程序的强大平台。它为 50多种语料库和词法资源提供了易于使用的接口(如WordNet),还提供了一套用于分类,分词,词干提取,标注,解析和语义推理的文本处理工具库。
    • heapq这个模块提供了一个堆队列算法的实现,也称为优先队列算法。
     

    import bs4 as bs

    import urllib.request

    import re

    import nltk

    import heapq

     

    接下来检查一下数据包中的stopwords包(停用词)和punkt包是否更新到最新。

     

    nltk.download(‘stopwords’)

    nltk.download(‘punkt’)

    第2步:提取数据

    这里选用了维基上的 Artificial Neural Network 这个页面为例子来说明。你可以根据需要选择其他任何文章。

     

    page = urllib.request.urlopen(“https://en.wikipedia.org/wiki/Artificial_neural_network”).read()

    soup = bs.BeautifulSoup(page,‘lxml’)

    print(page) #print the page

    现在你可以看到我们提取出来的内容,但它看起来有点丑。我们使用BeautifulSoup来解析文档, 并以漂亮的方式来呈现文本。我还使用了prettify函数来使html语法看起来更美观。

     

    print(soup.prettify)

    注意:维基百科中的大多数文章都是在<p>标签下编写的,但不同的网站可能采取不同的方式。例如,一些网站会把文字内容写在<div>标签下。

     

    text = “”

    for paragraph in soup.find_all(‘p’):

    text += paragraph.text

    print(text)

    三、如何清洗数据

    数据清洗指的是对数据集,数据表或数据库中的所有数据,检测并纠正(或删除)损坏的或不准确的记录的过程,也即识别数据中不完整,不正确,不准确或不相关的部分,然后替换,修改,或删除这部分脏数据。

     

    text = re.sub(r’[[0-9]*]’,’ ',text)

    text = re.sub(r’\s+’,’ ',text)

    clean_text = text.lower()

    clean_text = re.sub(r’\W’,’ ',clean_text)

    clean_text = re.sub(r’\d’,’ ',clean_text)

    clean_text = re.sub(r’\s+’,’ ',clean_text)

    sentences = nltk.sent_tokenize(text)

    stop_words = nltk.corpus.stopwords.words(‘english’)

    print(sentences)

     

    第1行:删除文本中由类似[1],[2]表示的所有引用(参见上面的输出文本段)

    第2行:用单个空格替换了所有额外的空格

    第3行:转换成小写

    第4,5,6行:移除所有额外的标点符号,数字,额外空格等。

    第7行:使用sent_tokenize()函数将大段的文本分解为句子

    stop_words #list

    四、如何构建直方图

    构建一个直方图可以帮助你直观地发现文章中比较特别的单词。例如“Geoffrey Hinton is the god father of deep learning. And I love deep learning“这一句,需要计算每个不同的单词出现在句子中的次数,例如”deep“和”learning“都出现两次,其余的单词在一个句子中只出现一次。但在现实世界中,你有成千上万条句子,要具体找出每个单词出现多少次就需要构建直方图来表现。

     

    word2count = {} #line 1

    for word in nltk.word_tokenize(clean_text): #line 2

    if word not in stop_words: #line 3

    if word not in word2count.keys():

    word2count[word]=1

    else:

    word2count[word]+=1

    for key in word2count.keys(): #line 4

    word2count[key]=word2count[key]/max(word2count.values())

     

    第1行:创建一个空字典

    第2行:使用word_tokenize分词clean _text分词,对每个单词循环

    第3行:检查单词是否在stop_words中,然后再次检查单词是否在word2count的键集中,不在则把word2count [word]置为1,否则word2count [word] 加1。

    第4行:计算加权直方图(参见下面的输出,你可以看到对每个单词计算了权重而不是计数。 例如有 ‘artificial’:0.3620689等)

    五、如何计算句子分数

     

    第1行:创建一个空字典

    第2行:对sentences中每个sentence进行循环

    第3行:将sentence转换成小写并分词,对每个word循环

    第4行:使用if检查word2count.keys()中是否存在该单词

    第5行:这里我指定计算句子长度小于30的那部分,你可以根据需要更改

    第6行:再次使用if-else条件,判断如果句子不存在于sentence2keys()中,则执行 sent2score [sentence] = word2count [word],否则执行 sent2score [sentence] + = word2count [word]

     

    计算句子分数

    sent2score = {}

    for sentence in sentences:

    for word in nltk.word_tokenize(sentence.lower()):

    if word in word2count.keys():

    if len(sentence.split(’ '))<30:

    if sentence not in sent2score.keys():

    sent2score[sentence]=word2count[word]

    else:

    sent2score[sentence]+=word2count[word]

     

     

    六、查看句子得分

    七、如何提取分数最高的句子作为简短摘要

     

    使用heapq从文章中找到得分最高的七个句子。

     

    best_sentences = heapq.nlargest(7,sent2score,key=sent2score.get)

    for sentences in best_sentences:

    print(sentences,’\n’)

     

    关于Artificial Neural Network这篇文章七条概括得最好的句子

     

    原文链接

    https://towardsdatascience.com/text-summarization-96079bf23e83

     

    Github源码链接

    https://github.com/mohitsharma44official/Text--Summarization

     

    本篇文章出自http://www.tensorflownews.com,对深度学习感兴趣,热爱Tensorflow的小伙伴,欢迎关注我们的网站!

    展开全文
  • 常用的C/C++文本文件的读写函数

    万次阅读 2015-12-28 22:12:39
    C和C++的标准库中提供了多种文本文件的读写方法,我们一般都会掌握其中比较习惯使用的一种或几种方法。解决问题的方法不在多,而在精。经过在网上的资料搜索和总结,这里我对实际编程中经常用到的几类方法总结如下。...

    一. 概述

    文本文件的读写是算法研究和工程应用中常见的功能模块。C和C++的标准库中提供了多种文本文件的读写方法,我们一般都会掌握其中比较习惯使用的一种或几种方法。解决问题的方法不在多,而在精。经过在网上的资料搜索和总结,这里我对实际编程中经常用到的几类方法总结如下。对C和C++分开介绍,目的是让大家即能用C++读写文本文件,又能用C读写文本文件。不多不少,恰到好处地掌握好该掌握的读写知识。
    文本文件内容:后面在代码示例中读入的文本文件text.txt的内容如下:

    Never give up, it's truth.
    012  34 

    二. C读写函数

    这里我们使用简捷好用的流式文件操作,即FILE结构,需要加入头文件fstream.h。可以通过FILE *fopen(const char *filename,const char *mode)打开文件流。其中,参数filename指向要打开的文件名,mode表示打开状态的字符串,其取值如下:

    • r 以只读方式打开文件
    • rb 以只读方式打开二进制文件
    • a 以追加(写)方式打开文件
    • w 以只写方式打开文件
    • wb 以只写方式打开二进制文件
    • r+ 以读/写方式打开文件,如无文件出错
    • w+ 以读/写方式打开文件,如无文件生成新文件

    下面通过特别设置的实例对相应的函数进行介绍和说明。

    1. 文本文件

    读操作

    函数fscanf的格式控制非常方便,基本上能应对所有的文本文件读取任务。有时候为了更便捷,需要使用某些特定功能的函数:
    int fseek(FILE *stream, long offset, int whence)
    此函数一般用于二进制模式打开的文件中,功能是定位到流中指定的位置。参数offset是移动的字符数,whence是移动的基准可取值为:SEEK_SET 0 文件开头;SEEK_CUR 1 当前读写的位置;SEEK_END 2 文件尾部。
    char *fgets(char *s, int n, FILE *stream)
    此函数表示从流中读一行或指定个字符(n-1),参数s是来接收字符串,如果成功则返回s的指针,否则返回NULL。

    string filePath = "text.txt";
    FILE *fp = fopen(filePath.c_str(), "r");
    if (fp == nullptr)
    {
        cout<<"Invalid file path\n";
        return;
    }
    char str[512];
    fscanf(fp, "%s", &str);
    int offset = sizeof(char)*9;
    fseek(fp, offset, 1);
    char str1[512];
    int n = 12;
    fgets(str1, n+1, fp);
    cout<<str1<<endl;
    int no, k1, k2;
    fscanf(fp, "%d%s%d%d", &no, &str, &k1, &k2);
    cout<<no<<" "<<k1<<" "<<k2<<endl; 

    写操作

    函数fprintf主要要掌握的就是对于输出格式的控制,语法规则是百分符号%+代表类型的字母,格式控制就写在二者之间。

    • %d 整数int型
    • %f 单精度float型
    • %lf 双进度double型
    • %e 以科学计数法表示
    • %s 字符串char[]型

    下列代码中,%6.2lf表示以6个字符宽度、2位小数的精度输出;%03d表示以3个字符宽度不足部分以0填充的格式输出。

    string filePath = "text.txt";
    FILE *fp = fopen(filePath.c_str(), "a");
    if (fp == nullptr)
    {
        cout<<"Invalid file path\n";
        return;
    }
    int a = 91;
    fprintf(fp, "C appending %03d\n", a);
    double k = 192.3345;
    fprintf(fp, "%6.2lf\n", k);
    fclose(fp);

    2. 二进制文件

    相比文本文件而言,二进制文件的读写不需要考虑输出格式的相关问题。原因有二,一是二进制文件内容是用户看不懂的二进制字节;二是二进制文件的读写操作都是按字节个数读取的,无需顾及格式。所以,写二进制文件的时候千万不要输出那些格式控制相关的字符,如\n,那样徒增数据读取的难度,百害而无一益。

    读操作

    函数fread的原型是size_t fread(void ptr, size_t size, size_t n, FILE *stream),参数ptr是保存读取的数据,void的指针可用任何类型的指针来替换,如char*、int *等来替换;size是每块的字节数;n是读取的块数,如果成功,返回实际读取的块数(不是字节数)。

    string filePath = "binary.bin";
    FILE *fp = fopen(filePath.c_str(), "rb");
    int intSize = sizeof(int);
    int charSize = sizeof(char);
    char *buffer1 = new char[13];
    int *buffer2 = new int[3];
    fread(buffer1, charSize, 13, fp);
    fread(buffer2, intSize, 3, fp);
    cout<<buffer1<<endl;
    cout<<buffer2[0]<<" "<<buffer2[1]<<" "<<buffer2[2]<<endl;
    fclose(fp);

    写操作

    函数fwrite的形参类型与上文提到的函数fread的形参类型一致,此处不再赘述。

    string filePath = "binary.bin";
    FILE *fp = fopen(filePath.c_str(), "wb");
    int intSize = sizeof(int);
    int charSize = sizeof(char);
    int doubleSize = sizeof(double);
    char *buffer1 = "Hello, world!";
    int *buffer2 = new int[3];
    buffer2[0] = 0; buffer2[1] = 12; buffer2[2] = 34;
    double *buffer3 = new double[1];
    buffer3[0] = 192.3;
    fwrite(buffer1, charSize,  13, fp);
    fwrite(buffer2, intSize,    3, fp);
    fwrite(buffer3, doubleSize, 1, fp);
    fclose(fp);

    三. C++读写函数

    在C++中,对文件的操作是通过stream的子类fstream来实现的。要用这种方式操作文件,就必须加入头文件fstream.h。。

    1. 文本文件

    读操作

    函数open的原型是*void open(const char filename,int mode,int access)。参数filename:要打开的文件名;参数mode:要打开文件的方式;access:打开文件的属性。打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下(可以用“|”或者“+”把以下属性连接起来):

    • ios::in 以只读方式打开文件
    • ios::out 以只写方式打开文件
    • ios::app 以追加(写)方式打开文件
    • ios::_Nocreate 不建立文件,所以文件不存在时打开失败
    • ios::_Noreplace 不覆盖文件,所以打开文件时如果文件存在失败
    • ios::binary 以二进制方式打开文件

    另外,此处代码中的函数seekggetline的用法和功能与C读写函数中的fseekfgets类似,故略之。

    string filePath = "text.txt";
    ifstream fin;
    fin.open(filePath.c_str(), ios::in);
    char str[512];
    fin>>str;
    int offset = 9;
    fin.seekg(offset, ios::cur);
    int n = 12;
    char str1[512];
    fin.getline(str1, n+1);
    cout<<str1<<endl;
    int no, k1, k2;
    fin>>no;
    int n = 2;  //! to skip ':' and ' '
    fin.seekg(n+1, ios::cur);
    fin>>k1>>k2;
    cout<<no<<" "<<k1<<" "<<k2<<endl;
    fin.close();

    写操作

    在C++语法中,文本文件输出函数的重点也是对输出格式的控制。这里是通过调用标准库中相应的输出格式设置函数来实现的,调用时需要包含头文件iomanip.h。比较常用的格式控制函数有:字符宽度设置函数setw;默认填充字符设置函数setfill;输出精度设置函数setprecision。具体的用法如下列代码中的调用方法。

    string filePath = "text.txt";
    ofstream fout;
    fout.open(filePath.c_str(), ios::app | ios::_Nocreate);
    char str[512] = "C++ appending ";
    fout<<str;
    fout<<0<<" : "<<oct<<12<<" "<<dec<<34<<endl;
    fout<<setw(3)<<setfill('0')<<91<<" ";
    double k = 192.9773;
    fout<<setprecision(6)<<k<<endl;
    fout.close();

    2. 二进制文件

    在C++中,对二进制的读写是通过函数readwrite来实现。这组函数的用法和功能与C中的函数freadfwrite相一致,这里不再做过多介绍。值得注意的是,函数的第一个形参类型必须是 unsigned char *,因此当数据的真实类型不是unsigned char时,需要进行类型转换。

    读操作

    string filePath = "binary.bin";
    ifstream fin;
    fin.open(filePath.c_str(), ios::binary);
    int intSize = sizeof(int);
    int charSize = sizeof(char);
    char *buffer1 = new char[13];
    char *buffer2 = new char[3*intSize];
    fin.read(buffer1, charSize*13);
    fin.read(buffer2, intSize*3);
    cout<<buffer1<<endl;
    cout<<(int)buffer2[0]<<" "<<(int)buffer2[1]<<endl;
    fin.close();

    写操作

    string filePath = "binary.bin";
    ofstream fout;
    fout.open(filePath.c_str(), ios::binary);
    int intSize = sizeof(int);
    int charSize = sizeof(char);
    int doubleSize = sizeof(double);
    char *buffer1 = "Hello, world!";
    char *buffer2 = new char[3*intSize];
    buffer2[0] = 0; buffer2[1] = 12; buffer2[2] = 34;
    char *buffer3 = new char[doubleSize*1];
    buffer3[0] = 192.3;
    fout.write(buffer1, charSize*13);
    fout.write(buffer2, intSize*3);
    fout.write(buffer3, doubleSize*1);
    fout.close();

    四. 总结

    本文总结了C和C++中常用的读写文本文件和二进制文件的函数。目的是为C/C++初学者提供一份简单实用、详略得当的关于文件读写的学习资料。当然,要真正掌握其用法,还必须是亲身实践和多次练习,实践出真知!

    展开全文
  • 引言 文本生成(Text Generation)具体可以细分成文本摘要、机器翻译、故事续写等几种任务。...本项目我们将会先用生成式摘要的方法构建一个 Seq2seq+Attention的模型作为 baseline,然后构建一个结合了生成式和抽取式

    引言

    文本生成(Text Generation)具体可以细分成文本摘要、机器翻译、故事续写等几种任务。本项目主要用到的是文本摘要(Summarization)的技术。我们生成摘要的方式是选取其中关键的句子摘抄下来。相反,生成式摘要则是希望通过学习原文的语义信息后相应地生成一段较短但是能反应其核心思想的文本作为摘要。生成式
    摘要相较于抽取式摘要更加灵活,但也更加难以实现。本项目我们将会先用生成式摘要的方法构建一个 Seq2seq+Attention的模型作为 baseline,然后构建一个结合了生成式和抽取式两种方法的 Pointer-Generator Network(PGN)模型。

    在本项目中,可以学习到

    • 熟练掌握(Seq2seq、Attention、LSTM、PGN 、converage等模型)的实现。
    • 熟练掌握如何训练神经网络(调参、debug、可视化)。
    • 熟练掌握如何实现 Beam Search 算法来生成文本。
    • 熟练掌握文本生成任务的评估方法
    • 掌握深度学习训练的一些优化技巧(Scheduled sampling、Weight tying等)。
    • 了解如何使用多种文本增强技术处理少样本问题。

    项目任务简介

    文本生成任务中,我们作为输入的原文称之为 source,待生成的目标文本称之为 target 或者 hypothesis,用来作为 target 好坏的参考文本称之为reference。在本项目的数据源来自于京东电商的发现好货栏目,source 主
    要由三部分构成:1 是商品的标题,2 是商品的参数,3 是商品宣传图片里提取出来的宣传文案(借助OCR)。

    参考文本如下图所示:

    商品的标题:

    商品的参数:

    商品宣传图片:


    0. 数据预处理

    首先数据格式如下图所示,为一个json文件,里面的title、kb以及ocr为我们利用的source,reference可以看作我们的标签。

    0.1 将json文件转化成txt文件

    就是一个读取json,写入txt的文件,不再赘述。

    json_path = os.path.join(abs_path, '服饰_50k.json')
    with open(json_path, 'r', encoding='utf8') as file:
        jsf = json.load(file)
    for jsobj in jsf.values():
        title = jsobj['title'] + ' '  # Get title.
        kb = dict(jsobj['kb']).items()  # Get attributes.
        kb_merged = ''
        for key, val in kb:
            kb_merged += key+' '+val+' '  # Merge attributes.
        ocr = ' '.join(list(jieba.cut(jsobj['ocr'])))  # Get OCR text.
        texts = []
        texts.append(title + ocr + kb_merged)  # Merge them.
        reference = ' '.join(list(jieba.cut(jsobj['reference'])))
        for text in texts:
            sample = text+'<sep>'+reference  # Seperate source and reference.
            samples.add(sample)
    write_path = os.path.join(abs_path, '../files/samples.txt')
    write_samples(samples, write_path)
    

    0.2 词典处理

    add_words函数:向词典⾥加⼊⼀个新词,需要完成对word2index、index2word和word2count三个变量的更新。

    class Vocab(object):
        def __init__(self):
            self.word2index = {}
            self.word2count = Counter()
            self.reserved = ['<PAD>', '<SOS>', '<EOS>', '<UNK>']
            self.index2word = self.reserved[:]
            self.embeddings = None
        def add_words(self, words):
            """Add a new token to the vocab and do mapping between word and index.
            """
            for word in words:
                if word not in self.word2index:
                    self.word2index[word] = len(self.index2word)
                    self.index2word.append(word)
            self.word2count.update(words)
    

    build_vocab函数:需要实现控制数据集词典的⼤⼩(从config.max_vocab_size)读取这⼀参数。我们这里使⽤python的collection模块中的Counter来做。

    def build_vocab(self, embed_file: str = None) -> Vocab:
            """Build the vocabulary for the data set.
            """
            # word frequency
            word_counts = Counter()
            count_words(word_counts, [src + tgr for src, tgr in self.pairs])
            vocab = Vocab()
            # Filter the vocabulary by keeping only the top k tokens in terms of
            # word frequncy in the data set
            for word, count in word_counts.most_common(config.max_vocab_size):
                vocab.add_words([word])
            if embed_file is not None:
                count = vocab.load_embeddings(embed_file)
                print("%d pre-trained embeddings loaded." % count)
            return vocab
    
    
    def count_words(counter, text):
        '''Count the number of occurrences of each word in a set of text'''
        for sentence in text:
            for word in sentence:
                counter[word] += 1
    

    0.3 自定义数据集SampleDataset(Dataset类)

    我们知道用dataset和dataloader来进行数据加载读取训练和方便,这里我们自定义了SampleDataset,如下所示,其中我们要具体讲一讲__getitem__函数。

    class SampleDataset(Dataset):
        """The class represents a sample set for training.
        """
        def __init__(self, data_pair, vocab):
            self.src_sents = [x[0] for x in data_pair]
            self.trg_sents = [x[1] for x in data_pair]
            self.vocab = vocab
            self._len = len(data_pair)
        def __getitem__(self, index):
            x, oov = source2ids(self.src_sents[index], self.vocab)
            return {
                'x': [self.vocab.SOS] + x + [self.vocab.EOS],
                'OOV': oov,
                'len_OOV': len(oov),
                'y': [self.vocab.SOS] +
                abstract2ids(self.trg_sents[index],
                             self.vocab, oov) + [self.vocab.EOS],
                'x_len': len(self.src_sents[index]),
                'y_len': len(self.trg_sents[index])
            }
        def __len__(self):
            return self._len
    
    

    其中我们的__getitem__函数就是想取第index个数据,我们这里想要做的是返回第index个文本数据的id token,和以前不同的是这个id token包括了对OOV的处理,我们具体来说。

    对于source文本,我们通过source2ids将字符串映射成id。将源单词映射到它们的id并返回源代码中的oov列表,OOV由它们的临时源OOV编号表示。如果词表大小是5000并且源单词中含有三个OOV,那么OOV编号的大小是5001、5002和5003。

    def source2ids(source_words, vocab):
        ids = []
        oovs = []
        unk_id = vocab.UNK
        for w in source_words:
            i = vocab[w]
            if i == unk_id:  
                if w not in oovs:  
                    oovs.append(w)
                # This is 0 for the first source OOV, 1 for the second source OOV
                oov_num = oovs.index(w)
                # This is e.g. 20000 for the first source OOV, 50001 for the second
                ids.append(vocab.size() + oov_num)
            else:
                ids.append(i)
        return ids, oovs
    

    对于reference文本,我们通过成abstract2ids函数将reference文本映射成Id。由于PGN可以⽣成在source⾥⾯出现过的OOV tokens,所以这次我们对reference的token ids需要换⼀种映射⽅式,即将在source⾥出现过的OOV tokens也记录下来并给⼀个临时的id,⽽不是直接替换为“”,以便在训练时计算损失更加准确。

    def abstract2ids(abstract_words, vocab, source_oovs):
        """Map tokens in the abstract (reference) to ids.
           OOV tokens in the source will be remained.
        """
        ids = []
        unk_id = vocab.UNK
        for w in abstract_words:
            i = vocab[w]
            if i == unk_id:  # If w is an OOV word
                if w in source_oovs:  # If w is an in-source OOV
                    # Map to its temporary source OOV number
                    vocab_idx = vocab.size() + source_oovs.index(w)
                    ids.append(vocab_idx)
                else:  # If w is an out-of-source OOV
                    ids.append(unk_id)  # Map to the UNK token id
            else:
                ids.append(i)
        return ids
    

    0.4 生成Dataloader进行训练

    Dataloader可以更方便的在数据集中取batch进行批训练,其中最重要的是collate_fn函数,它的作用是将数据集拆分为多个批,并对每个批进行填充;其中我们确定一个batch的最大长度,是根据sort_batch_by_len函数实现的。

    train_data = SampleDataset(dataset.pairs, v)
    val_data = SampleDataset(val_dataset.pairs, v)
    train_dataloader = DataLoader(dataset=train_data,
                                      batch_size=config.batch_size,
                                      shuffle=True,
                                      collate_fn=collate_fn)
    
    def collate_fn(batch):
        """Split data set into batches and do padding for each batch.
        """
        def padding(indice, max_length, pad_idx=0):
            pad_indice = [item + [pad_idx] * max(0, max_length - len(item)) for item in indice]
            return torch.tensor(pad_indice)
        data_batch = sort_batch_by_len(batch)
        x = data_batch["x"]
        x_max_length = max([len(t) for t in x])
        y = data_batch["y"]
        y_max_length = max([len(t) for t in y])
        OOV = data_batch["OOV"]
        len_OOV = torch.tensor(data_batch["len_OOV"])
        x_padded = padding(x, x_max_length)
        y_padded = padding(y, y_max_length)
        x_len = torch.tensor(data_batch["x_len"])
        y_len = torch.tensor(data_batch["y_len"])
        return x_padded, y_padded, x_len, y_len, OOV, len_OOV
    

    1. seq2seq+Attention

    我们的基线模型如下图所示。文章中的token被逐个送入编码器(单层双向LSTM),产生一系列编码器隐藏状态 h i h_i hi。在每个步骤t中,解码器(单层单向LSTM)接收前一个单词的单词嵌入(在训练时,这是参考摘要的前一个单词;在测试时,它是解码器发出的前一个单词),并且具有解码器状态 s t s_t st

    在这里插入图片描述

    注意分布 a t a_t at的计算如Bahdanau等人所述:
    在这里插入图片描述
    加权求和生成内容向量:
    在这里插入图片描述

    解码端得到单词分布:
    在这里插入图片描述

    1.1 Encoder

    class Encoder(nn.Module):
        def __init__(self,vocab_size, embed_size, hidden_size, rnn_drop: float = 0):
            super(Encoder, self).__init__()
            self.embedding = nn.Embedding(vocab_size, embed_size)
            self.hidden_size = hidden_size
            self.lstm = nn.LSTM(embed_size,
                                hidden_size,
                                bidirectional=True,
                                dropout=rnn_drop,
                                batch_first=True)
        def forward(self, x):
            """Define forward propagation for the endoer.
            """
            embedded = self.embedding(x)
            output, hidden = self.lstm(embedded)
            return output, hidden
    
    

    1.2 Decoder

    class Decoder(nn.Module):
        def __init__(self, vocab_size, embed_size, hidden_size, enc_hidden_size=None, is_cuda=True):
            super(Decoder, self).__init__()
            self.embedding = nn.Embedding(vocab_size, embed_size)
            self.DEVICE = torch.device('cuda') if is_cuda else torch.device('cpu')
            self.vocab_size = vocab_size
            self.hidden_size = hidden_size
            self.lstm = nn.LSTM(embed_size, hidden_size, batch_first=True)
            self.W1 = nn.Linear(self.hidden_size * 3, self.hidden_size)
            self.W2 = nn.Linear(self.hidden_size, vocab_size)
    
        def forward(self, x_t, decoder_states, context_vector):
            """Define forward propagation for the decoder.
            """
            decoder_emb = self.embedding(x_t)
            decoder_output, decoder_states = self.lstm(decoder_emb, decoder_states)
            # concatenate context vector and decoder state
            decoder_output = decoder_output.view(-1, config.hidden_size)
            concat_vector = torch.cat([decoder_output, context_vector], dim=-1)
            # calculate vocabulary distribution
            FF1_out = self.W1(concat_vector)
            FF2_out = self.W2(FF1_out)
            p_vocab = F.softmax(FF2_out, dim=1)
            h_dec, c_dec = decoder_states
            s_t = torch.cat([h_dec, c_dec], dim=2)
            return p_vocab, decoder_states
    

    1.3 Attention

    a. 处理decoder的隐状态 h h h c c c,将⼆者拼接得到 s t s_t st,并处理成合理的shape。
    b. 参考论⽂中的公式(1)和(2),实现attention weights的计算。
    c. 由于训练过程中会对batch中的样本进⾏padding,对于进⾏了padding的输⼊我们需要把填充的位置的attention weights给过滤掉(padding mask),然后对剩下位置的attention weights进⾏归⼀化。
    d. 根据论⽂中的公式(3)计算context vector

    class Attention(nn.Module):
        def __init__(self, hidden_units):
            super(Attention, self).__init__()
            # Define feed-forward layers.
            self.Wh = nn.Linear(2*hidden_units, 2*hidden_units, bias=False)
            self.Ws = nn.Linear(2*hidden_units, 2*hidden_units)
    
        def forward(self,
                    decoder_states,
                    encoder_output,
                    x_padding_masks,
                    coverage_vector):
            """Define forward propagation for the attention network.
            """
            # Concatenate h and c to get s_t and expand the dim of s_t.
            h_dec, c_dec = decoder_states
            s_t = torch.cat([h_dec, c_dec], dim=2)
            s_t = s_t.transpose(0, 1)
            s_t = s_t.expand_as(encoder_output).contiguous()
            # calculate attention scores
            encoder_features = self.Wh(encoder_output.contiguous())
            decoder_features = self.Ws(s_t)
            att_inputs = encoder_features + decoder_features
            score = self.v(torch.tanh(att_inputs))
            attention_weights = F.softmax(score, dim=1).squeeze(2)
            attention_weights = attention_weights * x_padding_masks
            # Normalize attention weights after excluding padded positions.
            normalization_factor = attention_weights.sum(1, keepdim=True)
            attention_weights = attention_weights / normalization_factor
            context_vector = torch.bmm(attention_weights.unsqueeze(1),
                                       encoder_output)
            context_vector = context_vector.squeeze(1)
            return context_vector, attention_weights
    

    1.4 ReduceState

    我们的encoder⽤了BiLSTM,⽽decoder⽤的是单向的LSTM,使⽤encoder的输出作为decoder初始隐状态时,需要对encoder的隐状态进⾏降维。实现的⽅式可以有多种,可以对两个⽅向的隐状态简单相加,也可以定义⼀个前馈层来做这个事情。这⾥我们⽤⼀个ReduceState的模块以简单相加的形式来实现,具体⻅代码。

    class RetduceSate(nn.Module):
        def __init__(self):
            super(ReduceState, self).__init__()
    
        def forward(self, hidden):
            """The forward propagation of reduce state module.
            """
            h, c = hidden
            h_reduced = torch.sum(h, dim=0, keepdim=True)
            c_reduced = torch.sum(c, dim=0, keepdim=True)
            return (h_reduced, c_reduced)
    
    

    1.5 Seq2seq的整体Forward

    1. 对输⼊序列x进⾏处理,对于oov的token,需要将他们的index转换成 UNK token 。
    2. ⽣成输⼊序列x的padding mask 。
    3. 得到encoder的输出和隐状态,并对隐状态进⾏降维后作为decoder的初始隐状态。
    4. 对于每⼀个time step,以输⼊序列y的 y t y_t yt作为输⼊, y t + 1 y_{t+1} yt+1作为target,计算attention,然后⽤
      decoder得到 p v o c a b p_{vocab} pvocab,找到target对应的词在 p v o c a b p_{vocab} pvocab中对应的概率 t a r g e t p r o b s target_{probs} targetprobs ,然后计算time step t t t的损失,然后加上padding mask。
    5. 计算整个序列的平均loss。
    6. 计算整个batch的平均loss并返回。
    class Seq2seq(nn.Module):
        def __init__(self, v):
            super(Seq2seq, self).__init__()
            self.v = v
            self.DEVICE = config.DEVICE
            self.attention = Attention(config.hidden_size)
            self.encoder = Encoder(len(v),config.embed_size,config.hidden_size,)
            self.decoder = Decoder(len(v),config.embed_size,config.hidden_size,)
            self.reduce_state = ReduceState()
    
     
        def forward(self, x, x_len, y, len_oovs, batch, num_batches):
            """Define the forward propagation for the model.
            """
            x_copy = replace_oovs(x, self.v)
            x_padding_masks = torch.ne(x, 0).byte().float()
            encoder_output, encoder_states = self.encoder(x_copy)
            # Reduce encoder hidden states.
            decoder_states = self.reduce_state(encoder_states)
            # Calculate loss for every step.
            step_losses = []
            for t in range(y.shape[1]-1):
                # Do teacher forcing.
                x_t = y[:, t]
                x_t = replace_oovs(x_t, self.v)
                y_t = y[:, t+1]
                # Get context vector from the attention network.
                context_vector, attention_weights = self.attention(decoder_states, encoder_output, x_padding_masks, coverage_vector)
                # Get vocab distribution and hidden states from the decoder.
                p_vocab, decoder_states= self.decoder(x_t.unsqueeze(1), decoder_states, context_vector)
                # Get the probabilities predict by the model for target tokens.
                y_t = replace_oovs(y_t, self.v)
                target_probs = torch.gather(p_vocab, 1, y_t.unsqueeze(1))
                target_probs = target_probs.squeeze(1)
                # Apply a mask such that pad zeros do not affect the loss
                mask = torch.ne(y_t, 0).byte()
                # Do smoothing to prevent getting NaN loss because of log(0).
                loss = -torch.log(target_probs + config.eps)
                mask = mask.float()
                loss = loss * mask
                step_losses.append(loss)
    
            sample_losses = torch.sum(torch.stack(step_losses, 1), 1)
            # get the non-padded length of each sequence in the batch
            seq_len_mask = torch.ne(y, 0).byte().float()
            batch_seq_len = torch.sum(seq_len_mask, dim=1)
            # get batch loss by dividing the loss of each batch
            # by the target sequence length and mean
            batch_loss = torch.mean(sample_losses / batch_seq_len)
            return batch_loss
    

    2. PGN+coverage

    我们这里说一下seq2seq+attention的缺点,上面这方法虽然可以自由的生成文本,但是表现出很多表现不佳的行为,包括但不限于不准确地再现事实细节、无法处理词汇表外(OOV)单词以及生成重复的单词。

    指针生成器网络(PGN)有助于通过指针从源文本复制单词,这提高了OOV单词的准确性和处理能力,同时保留了生成新词的能力。这个网络可以看作是提取和抽象方法之间的平衡。

    我们还加入了Coverage vector,从神经机器翻译,我们用来跟踪和控制源文件的重复范围。我们证明Converage对于消除重复是非常有效的。

    具体框架如下图所示:
    在这里插入图片描述
    对于每个译码器时间步,计算生成概率 p g e n ∈ [ 0 , 1 ] p_{gen}∈[0,1] pgen[0,1],该概率加权从词汇表生成单词的概率,而不是从源文本复制单词的概率。对词汇分布和注意分布进行加权和求和,得到最终分布,并据此进行预测。
    生成概率计算如下:
    在这里插入图片描述

    单词概率计算如下:
    在这里插入图片描述

    我们的converage模型中,我们保持覆盖向量 C t C_t Ct,它是所有先前解码器时间步的注意力分布的总和:
    在这里插入图片描述

    Converage向量被用作注意力机制的额外输入:
    在这里插入图片描述
    我们定义一个覆盖损失,以惩罚重复到同一位置分配过多的注意力:
    在这里插入图片描述

    2.1 Encoder

    Encoder端没有变化,这里不再赘述。

    2.2 Decoder

    多了一个实现 p g e n p_{gen} pgen的计算,代码如下所示:

        def forward(self, x_t, decoder_states, context_vector):
            """Define forward propagation for the decoder.
            """
            decoder_emb = self.embedding(x_t)
            decoder_output, decoder_states = self.lstm(decoder_emb, decoder_states)
            # concatenate context vector and decoder state
            decoder_output = decoder_output.view(-1, config.hidden_size)
            concat_vector = torch.cat(
                [decoder_output,
                 context_vector],
                dim=-1)
            # calculate vocabulary distribution
            FF1_out = self.W1(concat_vector)
            FF2_out = self.W2(FF1_out)
            p_vocab = F.softmax(FF2_out, dim=1)
            h_dec, c_dec = decoder_states
            s_t = torch.cat([h_dec, c_dec], dim=2)
            p_gen = None
            if config.pointer:
                # Calculate p_gen.
                x_gen = torch.cat([context_vector,s_t.squeeze(0),decoder_emb.squeeze(1)], dim=-1)
                p_gen = torch.sigmoid(self.w_gen(x_gen))
            return p_vocab, decoder_states, p_gen
    

    2.3 Attention

    多了两部分的改进:
    a. 计算attention weights时加⼊coverage vector。
    b. 对coverage vector进⾏更新。

        def forward(self, decoder_states, encoder_output, x_padding_masks, coverage_vector):
            """Define forward propagation for the attention network.
            """
            # Concatenate h and c to get s_t and expand the dim of s_t.
            h_dec, c_dec = decoder_states
            s_t = torch.cat([h_dec, c_dec], dim=2)
            s_t = s_t.transpose(0, 1)
            s_t = s_t.expand_as(encoder_output).contiguous()
            # calculate attention scores
            encoder_features = self.Wh(encoder_output.contiguous())
            decoder_features = self.Ws(s_t)
            att_inputs = encoder_features + decoder_features
            # Add coverage feature.
            if config.coverage:
                coverage_features = self.wc(coverage_vector.unsqueeze(2))  # wc c
                att_inputs = att_inputs + coverage_features
            score = self.v(torch.tanh(att_inputs))
            attention_weights = F.softmax(score, dim=1).squeeze(2)
            attention_weights = attention_weights * x_padding_masks
    
            # Normalize attention weights after excluding padded positions.
            normalization_factor = attention_weights.sum(1, keepdim=True)
            attention_weights = attention_weights / normalization_factor       
            context_vector = torch.bmm(attention_weights.unsqueeze(1), encoder_output)
            context_vector = context_vector.squeeze(1)
            # Update coverage vector.
            if config.coverage:
                coverage_vector = coverage_vector + attention_weights
            return context_vector, attention_weights, coverage_vector
    

    2.4 ReduceState

    ReduceState模块也没有变化。

    2.5 get_final_distribution函数

    所谓的 pointer 本质是根据 attention 的分布 (source 中每个 token 的概率分布)来挑选输出的词,是从
    source 中 挑选最佳的 token 作为输出;所谓的 generator 的本质是根据 decoder 计算得到的字典概率
    分布 P_vocab 来挑选输出的词,是从字典中挑 选最佳的 token 作为输出。

    所以应该能发现:Attention 的分布和 P_vocab 的分布的⻓度和对应位置代表的 token 是不⼀样的,所以在计算 final distribution 的时候应该如何对应上呢? 这⾥推荐的⽅式是,先对 P_vocab 进⾏扩展,将 source 中的 oov 添 加到 P_vocab 的尾部,得到 P_vocab_extend 这样 attention weights 中的每⼀个 token 都能在 P_vocab_extend 中找到对应的位 置,然后 将对应的 attention weights 叠加到扩展后的 P_vocab_extend 中的对 应位置,得到 final distribution。

        def get_final_distribution(self, x, p_gen, p_vocab, attention_weights, max_oov):
            """Calculate the final distribution for the model.
            """
            batch_size = x.size()[0]
            # Clip the probabilities.
            p_gen = torch.clamp(p_gen, 0.001, 0.999)
            # Get the weighted probabilities.
            p_vocab_weighted = p_gen * p_vocab
            attention_weighted = (1 - p_gen) * attention_weights
    
            # Get the extended-vocab probability distribution    
            extension = torch.zeros((batch_size, max_oov)).float().to(self.DEVICE)
            p_vocab_extended = torch.cat([p_vocab_weighted, extension], dim=1)
            # Add the attention weights to the corresponding vocab positions.
            final_distribution =  p_vocab_extended.scatter_add_(dim=1, index=x, src=attention_weighted)
            return final_distribution
    

    2.6 PGN的整体Forward

    整体forward和Seq2seq+attention没有多少区别,多了一个计算Converage Loss 的过程。

    def forward(self, x, x_len, y, len_oovs, batch, num_batches):
            """Define the forward propagation for the model.
            """
            x_copy = replace_oovs(x, self.v)
            x_padding_masks = torch.ne(x, 0).byte().float()
            encoder_output, encoder_states = self.encoder(x_copy)
            # Reduce encoder hidden states.
            decoder_states = self.reduce_state(encoder_states)
            # Initialize coverage vector.
            coverage_vector = torch.zeros(x.size()).to(self.DEVICE)
            # Calculate loss for every step.
            step_losses = []
            for t in range(y.shape[1]-1):
                # Do teacher forcing.
                x_t = y[:, t]
                x_t = replace_oovs(x_t, self.v)
    
                y_t = y[:, t+1]
                # Get context vector from the attention network.
                context_vector, attention_weights, next_coverage_vector = \
                    self.attention(decoder_states,
                                   encoder_output,
                                   x_padding_masks,
                                   coverage_vector)
                # Get vocab distribution and hidden states from the decoder.
                p_vocab, decoder_states, p_gen = self.decoder(x_t.unsqueeze(1),
                                                              decoder_states,
                                                              context_vector)
    
                final_dist = self.get_final_distribution(x,
                                                         p_gen,
                                                         p_vocab,
                                                         attention_weights,
                                                         torch.max(len_oovs))
    
                # Get the probabilities predict by the model for target tokens.
                target_probs = torch.gather(final_dist, 1, y_t.unsqueeze(1))
                target_probs = target_probs.squeeze(1)
    
                # Apply a mask such that pad zeros do not affect the loss
                mask = torch.ne(y_t, 0).byte()
                # Do smoothing to prevent getting NaN loss because of log(0).
                loss = -torch.log(target_probs + config.eps)
    
                # Add coverage loss.
                ct_min = torch.min(attention_weights, coverage_vector)
                cov_loss = torch.sum(ct_min, dim=1)
                loss = loss + config.LAMBDA * cov_loss
                coverage_vector = next_coverage_vector
    
                mask = mask.float()
                loss = loss * mask
                step_losses.append(loss)
    
            sample_losses = torch.sum(torch.stack(step_losses, 1), 1)
            # get the non-padded length of each sequence in the batch
            seq_len_mask = torch.ne(y, 0).byte().float()
            batch_seq_len = torch.sum(seq_len_mask, dim=1)
    
            # get batch loss by dividing the target sequence length and mean
            batch_loss = torch.mean(sample_losses / batch_seq_len)
            return batch_loss
    
    在这里插入代码片
    

    3. 模型训练+可视化

    3.1 pytorch训练模板

    optimizer = None # Choose an optimizer from torch.optim
    batch_losses = []
    for batch in batches:
    	model.train() # Sets the module in training mode.
    	optimizer.zero_grad() # Clear gradients.
    	batch_loss = model(**params)# Calculate loss for a batch.
    	batch_losses.append(loss.item())
    	batch_loss.backward() # Backpropagation.
    	optimizer.step() # Update weights.
    epoch_loss = torch.mean(batch_losses)
    

    3.2 TensorBoard可视化

    1. 创建⼀个SummaryWriter对象,调⽤add_scalar函数来记录损失,记得写完要调⽤close函数。
    2. 并且在适当的位置实现梯度剪裁(clip_grad_norm_函数)。

    整体的代码如下:

        optimizer = optim.Adam(model.parameters(), lr=config.learning_rate)
        writer = SummaryWriter(config.log_path)
        # tqdm: A tool for drawing progress bars during training.
        with tqdm(total=config.epochs) as epoch_progress:
            for epoch in range(start_epoch, config.epochs):
                batch_losses = []  # Get loss of each batch.
                num_batches = len(train_dataloader)
                with tqdm(total=num_batches//100) as batch_progress:               
                    for batch, data in enumerate(tqdm(train_dataloader)):
                        x, y, x_len, y_len, oov, len_oovs = data
                        model.train()  # Sets the module in training mode.
                        optimizer.zero_grad()  # Clear gradients.
                        # Calculate loss.
                        loss = model(x, x_len, y, len_oovs, batch=batch, num_batches=num_batches)
                        batch_losses.append(loss.item())
                        loss.backward()  # Backpropagation.
                        # Do gradient clipping to prevent gradient explosion.
                        clip_grad_norm_(model.encoder.parameters(),
                                        config.max_grad_norm)
                        clip_grad_norm_(model.decoder.parameters(),
                                        config.max_grad_norm)
                        clip_grad_norm_(model.attention.parameters(),
                                        config.max_grad_norm)
                        optimizer.step()  # Update weights.
                        # Output and record epoch loss every 100 batches.
                        if (batch % 100) == 0:
                            batch_progress.set_description(f'Epoch {epoch}')
                            batch_progress.set_postfix(Batch=batch,
                                                       Loss=loss.item())
                            batch_progress.update()
                            # Write loss for tensorboard.
                            writer.add_scalar(f'Average loss for epoch {epoch}',
                                              np.mean(batch_losses),
                                              global_step=batch)
                # Calculate average loss over all batches in an epoch.
                epoch_loss = np.mean(batch_losses)
                epoch_progress.set_description(f'Epoch {epoch}')
                epoch_progress.set_postfix(Loss=epoch_loss)
                epoch_progress.update()
                avg_val_loss = evaluate(model, val_data, epoch)
                print('training loss:{}'.format(epoch_loss), 'validation loss:{}'.format(avg_val_loss))
    
        writer.close()
    
    

    4. 模型解码

    4.1 实现Greedy search

    这⼀块⽐较简单,⽤encoder编码输⼊,传递每⼀个time step的信息给decoder,计算attention,得到decoder的p_vocab,根据p_vocab选出概率最⼤的词作为下⼀个token。代码如下:

        def greedy_search(self,
                          x,
                          max_sum_len,
                          len_oovs,
                          x_padding_masks):
            """Function which returns a summary by always picking
            """
    
            # Get encoder output and states.
            encoder_output, encoder_states = self.model.encoder(
                replace_oovs(x, self.vocab))
    
            # Initialize decoder's hidden states with encoder's hidden states.
            decoder_states = self.model.reduce_state(encoder_states)
    
            # Initialize decoder's input at time step 0 with the SOS token.
            x_t = torch.ones(1) * self.vocab.SOS
            x_t = x_t.to(self.DEVICE, dtype=torch.int64)
            summary = [self.vocab.SOS]
            coverage_vector = torch.zeros((1, x.shape[1])).to(self.DEVICE)
            # Generate hypothesis with maximum decode step.
            while int(x_t.item()) != (self.vocab.EOS) \
                    and len(summary) < max_sum_len:
                context_vector, attention_weights, coverage_vector = \
                    self.model.attention(decoder_states,
                                         encoder_output,
                                         x_padding_masks,
                                         coverage_vector)
                p_vocab, decoder_states, p_gen = \
                    self.model.decoder(x_t.unsqueeze(1),
                                       decoder_states,
                                       context_vector)
                final_dist = self.model.get_final_distribution(x,
                                                               p_gen,
                                                               p_vocab,
                                                               attention_weights,
                                                               torch.max(len_oovs))
                # Get next token with maximum probability.
                x_t = torch.argmax(final_dist, dim=1).to(self.DEVICE)
                decoder_word_idx = x_t.item()
                summary.append(decoder_word_idx)
                x_t = replace_oovs(x_t, self.vocab)
            return summary
    

    4.2 实现Beam search

    我们的实现对Beam search进行了优化(加入了Length normalization, Coverage normalization以及End of sentence normalization),关于Beam search,我的博客里也有详细介绍,大体可以分为三步:

    首先,首先定义一个 Beam 类,作为一个存放候选序列的容器,属性需维护当前序列中的 token 以及对应的对数概率,同时还需维护跟当前 timestep 的 Decoder 相关的一些变量。此外,还需要给 Beam 类实现两个函数:一个 extend 函数用以扩展当前的序列(即添加新的 time step的 token 及相关变量);一个 score 函数用来计算当前序列的分数(在Beam类下的seq_score函数中有Length normalization以及Coverage normalization)。

    
    class Beam(object):
        def __init__(self,
                     tokens,
                     log_probs,
                     decoder_states,
                     coverage_vector):
            self.tokens = tokens
            self.log_probs = log_probs
            self.decoder_states = decoder_states
            self.coverage_vector = coverage_vector
    
        def extend(self,
                   token,
                   log_prob,
                   decoder_states,
                   coverage_vector):
            return Beam(tokens=self.tokens + [token],
                        log_probs=self.log_probs + [log_prob],
                        decoder_states=decoder_states,
                        coverage_vector=coverage_vector)
        def seq_score(self):
            """
            This function calculate the score of the current sequence.
            """
            len_Y = len(self.tokens)
            # Lenth normalization
            ln = (5+len_Y)**config.alpha / (5+1)**config.alpha
            cn = config.beta * torch.sum(  # Coverage normalization
                torch.log(
                    config.eps +
                    torch.where(
                        self.coverage_vector < 1.0,
                        self.coverage_vector,
                        torch.ones((1, self.coverage_vector.shape[1])).to(torch.device(config.DEVICE))
                    )
                )
            )
            score = sum(self.log_probs) / ln + cn
            return score
    

    接着我们需要实现一个 best_k 函数,作用是将一个 Beam 容器中当前 time step 的变量传入 Decoder 中,计算出新一轮的词表概率分布,并从中选出概率最大的 k 个 token 来扩展当前序列(其中加入了End of sentence normalization),得到 k 个新的候选序列。

        def best_k(self, beam, k, encoder_output, x_padding_masks, x, len_oovs):
            """Get best k tokens to extend the current sequence at the current time step.
            """
            # use decoder to generate vocab distribution for the next token
            x_t = torch.tensor(beam.tokens[-1]).reshape(1, 1)
            x_t = x_t.to(self.DEVICE)
    
            # Get context vector from attention network.
            context_vector, attention_weights, coverage_vector = \
                self.model.attention(beam.decoder_states,
                                     encoder_output,
                                     x_padding_masks,
                                     beam.coverage_vector)
    、
            p_vocab, decoder_states, p_gen = \
                self.model.decoder(replace_oovs(x_t, self.vocab),
                                   beam.decoder_states,
                                   context_vector)
    
            final_dist = self.model.get_final_distribution(x,
                                                           p_gen,
                                                           p_vocab,
                                                           attention_weights,
                                                           torch.max(len_oovs))
            # Calculate log probabilities.
            log_probs = torch.log(final_dist.squeeze())
            # EOS token penalty. Follow the definition in
            log_probs[self.vocab.EOS] *= \
                config.gamma * x.size()[1] / len(beam.tokens)
            log_probs[self.vocab.UNK] = -float('inf')
            # Get top k tokens and the corresponding logprob.
            topk_probs, topk_idx = torch.topk(log_probs, k)
            best_k = [beam.extend(x,
                      log_probs[x],
                      decoder_states,
                      coverage_vector) for x in topk_idx.tolist()]
            return best_k
    

    最后我们实现主函数 beam_search。初始化encoder、attention和decoder的输⼊,然后对于每⼀个decodestep,对于现有的k个beam,我们分别利⽤best_k函数来得到各⾃最佳的k个extended beam,也就是每个decode step我们会得到k*k个新的beam,然后只保留分数最⾼的k个,作为下⼀轮需要扩展的k个beam。为了只保留分数最⾼的k个beam,我们可以⽤⼀个堆(heap)来实现,堆的中只保存k个节点,根结点保存分数最低的beam。

        def beam_search(self,
                        x,
                        max_sum_len,
                        beam_width,
                        len_oovs,
                        x_padding_masks):
            """Using beam search to generate summary.
            """
            # run body_sequence input through encoder
            encoder_output, encoder_states = self.model.encoder(
                replace_oovs(x, self.vocab))
            coverage_vector = torch.zeros((1, x.shape[1])).to(self.DEVICE)
            # initialize decoder states with encoder forward states
            decoder_states = self.model.reduce_state(encoder_states)
            # initialize the hypothesis with a class Beam instance.
            init_beam = Beam([self.vocab.SOS],
                             [0],
                             decoder_states,
                             coverage_vector)
            k = beam_width
            curr, completed = [init_beam], []
            # use beam search for max_sum_len (maximum length) steps
            for _ in range(max_sum_len):
                # get k best hypothesis when adding a new token
                topk = []
                for beam in curr:
                    # When an EOS token is generated, add the hypo to the completed
                    if beam.tokens[-1] == self.vocab.EOS:
                        completed.append(beam)
                        k -= 1
                        continue
                    for can in self.best_k(beam,
                                           k,
                                           encoder_output,
                                           x_padding_masks,
                                           x,
                                           torch.max(len_oovs)
                                           ):
                        # Using topk as a heap to keep track of top k candidates.
                        add2heap(topk, (can.seq_score(), id(can), can), k)
    
                curr = [items[2] for items in topk]
                # stop when there are enough completed hypothesis
                if len(completed) == beam_width:
                    break
            completed += curr
            # sort the hypothesis by normalized probability and choose the best one
            result = sorted(completed,
                            key=lambda x: x.seq_score(),
                            reverse=True)[0].tokens
            return result
    
    

    5. Rouge评估

    我们在本次项目中使用ROUGE-1、ROUGE-2以及ROUGE-L评估。ROUGE分数的我博客里也有详细介绍。首先我们建立自带的ROUGE容器初始化,然后预测函数得到我们生成对文本,将他们Build hypotheses,最后比较答案得到分数。

    class RougeEval():
        def __init__(self, path):
            self.path = path
            self.scores = None
            self.rouge = Rouge()
            self.sources = []
            self.hypos = []
            self.refs = []
            self.process()
        def process(self):
            print('Reading from ', self.path)
            with open(self.path, 'r') as test:
                for line in test:
                    source, ref = line.strip().split('<sep>')
                    ref = ''.join(list(jieba.cut(ref))).replace('。', '.')
                    self.sources.append(source)
                    self.refs.append(ref)
            print(f'Test set contains {len(self.sources)} samples.')
    
    
        def build_hypos(self, predict):
            print('Building hypotheses.') 
            count = 0
            for source in self.sources:
                count += 1
                if count % 100 == 0:
                    print(count)
                self.hypos.append(predict.predict(source.split()))
    
        def get_average(self):
            assert len(self.hypos) > 0, 'Build hypotheses first!'
            print('Calculating average rouge scores.')
            return self.rouge.get_scores(self.hypos, self.refs, avg=True)
    
        def one_sample(self, hypo, ref):
            return self.rouge.get_scores(hypo, ref)[0]
    rouge_eval = RougeEval(config.test_data_path)
    predict = Predict()
    rouge_eval.build_hypos(predict)
    result = rouge_eval.get_average()
    print('rouge1: ', result['rouge-1'])
    print('rouge2: ', result['rouge-2'])
    print('rougeL: ', result['rouge-l'])
    



    6. 数据增强

    少样本问题是 NLP 领域经常面临的,尤其是在金融或者医疗等垂直领域,更是缺乏高质量的标注语料,所以数据增强是一种常用的技术。这一环节实现以下几种数据增强的技术:单词替换,回译,半监督学习。

    6.1 单词替换

    由于中文不像英文中有 WordNet 这种成熟的近义词词典可以使用,我们选择在embedding 的词向量空间中寻找语义最接近的词。通过使用在大量数据上预训练好的中文词向量,我们可以到每个词在该词向量空间中语义最接近的词,然后替换原始样本中的词,得到新的样本。但是有一个问题是,如果我们替换了样本中的核心词汇,比如将文案中的体现关键卖点的词给替换掉了,可能会导致核心语义的丢失。对此,我们有两种解决办法:1. 通过 tfidf 权重对词表里的词进行排序,然后替换排序靠后的词;2. 先通过无监督的方式挖掘样本中的主题词,然后只替换不属于主题词的词汇。

    任务1,extract_keywords函数。
    根据TFIDF确认需要排除的核⼼词汇。

        def extract_keywords(self, dct, tfidf, threshold=0.2, topk=5):
            """find high TFIDF socore keywords
            """
            tfidf = sorted(tfidf, key=lambda x: x[1], reverse=True)
            return list(islice(
                [dct[w] for w, score in tfidf if score > threshold], topk
                ))
    

    任务2,replace函数。
    embedding 的词向量空间中寻找语义最接近的词进⾏替换。

        def replace(self, token_list, doc):
            """replace token by another token which is similar in wordvector 
            """
            keywords = self.extract_keywords(self.dct, self.tfidf_model[doc])
            num = int(len(token_list) * 0.3)
            new_tokens = token_list.copy()
            while num == int(len(token_list) * 0.3):
                indexes = np.random.choice(len(token_list), num)
                for index in indexes:
                    token = token_list[index]
                    if isChinese(token) and token not in keywords and token in self.wv:
                        new_tokens[index] = self.wv.most_similar(
                            positive=token, negative=None, topn=1
                            )[0][0]
                num -= 1
            return ' '.join(new_tokens)
    
    

    任务3,generate_samples函数。

        def generate_samples(self, write_path):
            """generate new samples file
            """
            replaced = []
            count = 0
            for sample, token_list, doc in zip(self.samples, self.refs, self.corpus):
                count += 1
                if count % 100 == 0:
                    print(count)
                    write_samples(replaced, write_path, 'a')
                    replaced = []
                replaced.append(
                    sample.split('<sep>')[0] + ' <sep> ' + self.replace(token_list, doc)
                    )
    

    替换全部的reference,和对应的source形成新样本

    6.2 回译

    我们可以使用成熟的机器翻译模型,将中文文本翻译成一种外文,然后再翻译回中文,由此可以得到语义近似的新样本。

    利⽤百度translate API 接⼝将source ,reference翻译成英语,再由英语翻译成汉语,形成新样本。具体请参看官网。另外不同的语⾔样本的训练效果会有所不同,建议多尝试⼏种中间语⾔,⽐如⽇语等。

    任务1:translate函数
    建⽴http连接,发送翻译请求,以及接收翻译结果。关于这⼀部分可以参考百度接⼝translate API 的demo。

    def translate(q, source, target):
        """translate q from source language to target language
        """
        #  refer to the official documentation   https://api.fanyi.baidu.com/  
        # There are demo on the website ,  register on the web site ,and get AppID, key, python3 demo.
        appid = ''  # Fill in your AppID
        secretKey = ''  # Fill in your key
        httpClient = None
        myurl = '/api/trans/vip/translate'
        fromLang = source  # The original language
        toLang = target  # The target language
        salt = random.randint(32768, 65536)
        sign = appid + q + str(salt) + secretKey
        sign = hashlib.md5(sign.encode()).hexdigest()
        myurl = '/api/trans/vip/translate' + '?appid=' + appid + '&q=' + urllib.parse.quote(
            q) + '&from=' + fromLang + '&to=' + toLang + '&salt=' + str(
            salt) + '&sign=' + sign
        try:
            httpClient = http.client.HTTPConnection('api.fanyi.baidu.com')
            httpClient.request('GET', myurl)
            # response is HTTPResponse object
            response = httpClient.getresponse()
            result_all = response.read().decode("utf-8")
            result = json.loads(result_all)
            return result
        except Exception as e:
            print(e)
        finally:
            if httpClient:
                httpClient.close()
    

    任务2:back_translate函数
    对数据进⾏回译。

    def back_translate(q):
        """back_translate
        """
        en = translate(q, "zh", 'en')['trans_result'][0]['dst']
        time.sleep(1.5)
        target = translate(en, "en", 'zh')['trans_result'][0]['dst']
        time.sleep(1.5)
        return target
    

    6.3 自助式样本生成

    当我们训练出一个文本生成模型后,我们可以利用训练好的模型为我们原始样本中的 reference 生成新的 source,并作为新的样本继续训练我们的模型。

    semi_supervised函数
    我们可以使⽤训练好的PGN 模型将reference送⼊模型,⽣成新的source:

    def semi_supervised(samples_path, write_path, beam_search):
        """use reference to predict source
        """
        pred = Predict()
        print('vocab_size: ', len(pred.vocab))
        # Randomly pick a sample in test set to predict.
        count = 0
        semi = []
        with open(samples_path, 'r') as f:
            for picked in f:
                count += 1
                source, ref = picked.strip().split('<sep>')
                prediction = pred.predict(ref.split(), beam_search=beam_search)
                semi.append(prediction + ' <sep> ' + ref)
                if count % 100 == 0:
                    write_samples(semi, write_path, 'a')
                    semi = []
    

    下面给一个样例:
    source:

    帕莎 太阳镜 男⼠ 太阳镜 偏光 太阳眼镜 墨镜 潮 典雅 灰 颜⾊ 选择 , 细节 特⾊ 展示 , ⻆度 展示 , ⻛ 尚 演 , ⾦属, 透光 量 , 不易 变形 , 坚固耐⽤ , 清晰 柔和 , 镜⽚ 材质 , 尼⻰ ⾼ , 型号 , 镜架 材质 , ⾦属 镜腿 , 镜布 镜盒 , ⿐ 间距 , 轻盈 ⿐托 , 品牌 刻印 , ⽆缝 拼接 眼镜 配件类型 镜盒 功 能 偏光 类型 偏光太阳镜 镜⽚材质 树脂 ⻛格 休闲⻛ 上市时间 2016年夏季 镜框形状 ⽅形 适⽤性别 男 镜架材质 合⾦

    reference:

    夏天 到 了 , 在 刺眼 的 阳光 下 少不了 这 款 时尚 的 男⼠ 太阳镜 ! 时尚 的 版型 , 适⽤ 各种 脸型 , 突出 您 的 型 男 ⻛范 。 其⾼ 镍 ⾦属 材质 的 镜架 , ⼗分 的 轻盈 , 带给 您 舒适 的 佩戴 体验 。

    将reference送⼊模型后⽣成的数据new source:

    版型 设计 , 时尚 百搭 , 适合 多种 场合 佩戴 。 时尚 ⾦属 拉链 , 经久耐⽤ 。 带给 你 舒适 的 佩戴 体验 。 ⻛范 与 铰链 的 镜架 , 舒适 耐磨 , 不易 变形 。 带给 你 意想不到 的 修身 效果 , 让 你 的 夏 季 充满 着 不 ⼀样 的 魅⼒ , 让 这个 夏天 格外 绚烂 , 让 这个 夏天 格外 绚烂 。 时尚 的 版型 , 让 你 穿 起来 更 有 男⼈味 , 让 这个 夏天格外 绚烂 绚烂 , 让 这个 夏天 格外 绚烂 。 时尚 的 版型 , 让 你 穿 起来 更 有 男⼈味 , 让 这个 夏天 格外 绚烂 绚烂 , 是 秋装 上 上 之选 。



    7. 优化技巧

    在 Seq2seq 模型中,由于 Decoder 在预测阶段需要根据上一步的输出来的
    生成当前 time step 的输出,所以会面临“Exposure bias”的问题:在训练
    阶段我们使用 ground truth 作为 decoder 的输入(称之为 Teacher forcing),
    预测阶段却只能使用 decoder 上一步的输出,导致输入样本分布不一样,而
    影响 decoder 的表现。对此,我们有两种技巧进行优化:

    7.1 Weight tying

    即共享 Encoder 和 Decoder 的 embedding 权重矩阵,使得其输入的词向量表达具有一致性。使⽤的是three-way tying,即Encoder的input embedding,Decoder的input emdedding和Decoder的output embedding之间的权重共享。
    encoder端中加入如下代码:

            if config.weight_tying:
                embedded = decoder_embedding(x)
            else:
                embedded = self.embedding(x)
    

    Decoder端在output中加入如下代码:

        if config.weight_tying:
                FF2_out = torch.mm(FF1_out, torch.t(self.embedding.weight))
            else:
                FF2_out = self.W2(FF1_out)
    

    7.2 Scheduled sampling

    即在训练阶段,将 ground truth 和 decoder的输出混合起来使用作为下一个 time step 的 decoder 的输入,具体的做法是每个 time step 以一个 p p p 的概率进行 Teacher forcing,以 ( 1 − p ) (1-p) (1p)的概率不进行 Teacher forcing。 p p p 的大小可以随着 batch 或者 epoch衰减,即开始训练的阶段完全使用 groud truth 以加快模型收敛,到后面逐渐将 ground truth 替换成模型自己的输出,到训练后期就与预测阶段的输出一致了。

    代码也很简单,如下所示:

    实现ScheduledSampler即可,进入每个epoch都要去确定是否要进行teacher_forcing。可以通过epoch或者batch数控制按照⼀定的概率给出是否需要进⾏teacher forcing的指示。

    class ScheduledSampler():
        def __init__(self, phases):
            self.phases = phases
            self.scheduled_probs = [i / (self.phases - 1) for i in range(self.phases)]
    
        def teacher_forcing(self, phase):
            """According to a certain probability to choose whether to execute teacher_forcing
            """
            sampling_prob = random.random()
            if sampling_prob >= self.scheduled_probs[phase]:
                return True
            else:
                return False
    

    如果epoch=0,那么百分之百进行Teacher forcing,如果epoch=num_eopch-1,那么百分之零进行Teacher forcing。


    8. 实验结果

    下面实验结果大家可以作为一个参考,对于不同数据不同任务不同超参数可能会有差异,不过也大致反映了一些规律。如下表所示:

    模型ROUGE1ROUGE2ROUGEL
    seqseq+att0.2306320.04070.13037
    PGN0.245650.0426630.154976
    PGN+converage0.2675540.046470.162138
    PGN+Scheduled sampling0.2467210.042840.15831
    PGN+weight_tying0.2480960.043340.15791
    PGN+big_samples0.256310.049950.16245

    感觉优化效果最好的是Converage和数据增广。

    展开全文
  • 在linux中经常要对一些动态的文本文件抽取指定的字符串,比如执行ps命令后想要获取指定的运行进程(如ps自己)的PID号(同一个进程每次启动的时候pid号是随机分配的)。该怎么办呢?当然,可以用一些截取字符串的方法...
  • 格式是无后缀的文本格式。将其转换为统一XML格式。源文件名:zmonitor.2017-04-06-14_28 172.17.8.64 saptmqas_cpu 431 CPU utilization percentage : 1% 172.17.8.95 saptmprd_cpu 426 CPU utilization p
  • 目录 效果图 安装方法 使用方法 效果图 先上效果图 手写文章生成脚本,可模仿手写字体。 安装方法 1.下载脚本地址:...5.将handwriting.vba文件中的内容复制到vbs编辑器中保存...
  • 该工具允许 - 创建模板,称为“生成器”,由“列”组成,其列由“内容”组成。... - 创建文本文件或“SQL INSERT”文件,这些文件可进一步用于提供应用程序以进行测试。 使用的工具/库:C++ 和 QT(使用 QTCreator)
  • 编程实现根据指定文本生成电子印章(hnust)

    千次阅读 多人点赞 2020-06-10 14:16:24
    编程前准备:利用隶书56点阵汉字字库文本文件LiShu56.txt(在老师提供的软件包中)制作印章字库文本文件XXX.txt(用记事本软件即可制作完成),要求与印章的摆放顺序一致。其中XXX指自定义的任意名称。如图2所示,是...
  • JavaScript读写文本文件

    千次阅读 2020-09-16 00:43:20
    JavaScript读写文本文件 (JavaScript Read and Write to Text File) 方法1:使用Node.js (Method 1: Using Node.js) First is by using writefile and readFile method in node.js environment. 首先是在node.js...
  • java 生成 word 文档的多种方法(三)

    万次阅读 2019-01-25 14:49:03
    这篇文章给大家介绍java 通过POI技术生成word ,这种实现方式项目中也很常用,比如生成常用表格,报表等。废话少说,下面就给大家详细介绍实现原理! A、word文档正文段落概述: 一个word文档包含多个段落,一个...
  • Android读写Txt文本文件代码

    千次阅读 2011-02-14 11:07:00
    在Android平台中经常要用到Txt文本文件的读写操作,Android平台中处理Text这样的文本MIME的文件可以使用Java虚拟机的FileWriter类比较简单方便。该类位于java.io.FileWriter,提供了多种重写方法。 ...
  • Java实现PDF生成并且打印pdf文件(附demo) 目录: 0. 效果预览 1. 准备环境 2. Java如何调用打印机进行打印 3. Java如何生成pdf打印文件 4. 实现pdf内数据动态填充(可按需求改变数据,有一个模板就行) ...
  • 在linux上创建文本文件

    千次阅读 2019-03-17 00:09:17
    touch +xxx.txt(文件名) vi xxx.txt 在vim编辑器中编辑xxx.txt文件 或者这个文件在destop,直接打开编辑也是ok的 esc 退出编辑模式 wq 保存并退出
  • C# 上传文件多种方法

    万次阅读 2011-09-06 16:37:34
    方案一: 注意:要开启虚拟目录的“写入”权限,要...工作中用到winform上传文件(-_-!,很少用winform,搞了半天) 碰到一点问题,解决如下 1、501 为实现错误 解决方法: 先把IISWEB服务扩展中的WebDev打开
  • Qt开发:生成pdf文件

    万次阅读 2015-02-07 15:56:49
    Qt4中用QPrinter实现 QPrinter不止可以操作打印机来打印纸张文件,并且可以将文件保存至磁盘,存储为pdf格式的文件。 首先在pro文件中加入  QT+=printsupport ... //文本生成不要设置resolution,否
  • 总结起来就是:1 :%s//r/2 :%s//r//g3 :%s/[ctrl-v][ctrl-m]//g(中间CTRL部分不是输入,而是按键,显示在屏幕上是 :%s/^M//g)4 还有一个解决方法就是对这类文本进行 转换。vim内部就可以做这件事情。首先打开...
  • 相同点: csv、tsv和txt都属于文本文件。 不同点:csv和tsv文件的字段间分别由逗号和tab键隔开,而txt文件则没有明确要求,可使用逗号/制表符/空格等 多种不同的符号。 文件类型 全称 字段间的分隔符 ...
  • 如何生成文件列表及合成文件

    千次阅读 2009-11-12 10:14:00
    如何合并txt文件(有多种方法) http://jiaren.org/2008/12/28/merge-txt-dos/2.用DOS命令生成文件列表目录 http://www.hackbase.com/tech/2009-03-27/51856.html 1、打开需要合并的txt所在的文件夹; 2、如果要...
  • 资源文件生成器 (Resgen.exe)

    千次阅读 2009-01-21 15:49:00
    资源文件生成器 (Resgen.exe) 资源文件生成器将 .txt 文件和 .resx(基于 XML 的资源格式)文件转换为公共语言运行库二进制 .resources 文件,该文件可嵌入运行库二进制可执行文件或编译成附属程序集。有关部署和...
  • pdf报表生成,可以生成各类报表 iText作为一个文本输出的java开源代码,提供了PDF、Html、Rtf等多种文件格式的输出功能。为输出各种文本提供了一个比较好的封装。
  • dom(一)——获取文本内容的方法

    万次阅读 2017-03-22 13:34:12
    在利用DOM获取节点之后 有多种方法可以获取节点中的文本内容   1. innerHTML innerHTML可以作为获取文本方法也可以作为修改文本内容的方法 element.innerHTML 会直接返回element节点下所有的HTML化的文本内容...
  • 音频文件文本 web-API

    千次阅读 2018-10-19 17:28:58
    这里讲把音频文件转为文字的服务,不是... 最近发现autosub不错,利用Google Web Speech API,免费生成SRT字幕,而且速度挺快,两个小时的音频几分钟就转换好了,目前支持90多种语言。 有一点要注意的是如果音频语言...
  • Android之Xml序列化器生成xml文件

    千次阅读 2016-03-14 12:34:33
    文本文件可以方便地穿越防火墙,在不同操作系统上的不同系统之间通信。而作为纯文本文件格式,XML同样具有这个优点。 3)规范统一 XML具有统一的标准语法,任何系统和产品所支持的XML文档,都具有统一的格式和语法...
  • 将您的简历内容保存在简单的文本文件中,并自动生成多种格式的不同版本的简历(目前支持生成文本和 Microsoft Word .docx 格式的简历) 允许您将简历内容分解为多个文件,以便您可以为每个生成的版本挑选所需的部分...
  • 一直好奇程序的编译过程到底做了哪些工作,后来学会在Ubuntu上使用gcc编译程序,知道了生成可执行文件需要分为预编译、编译、汇编和链接4个步骤,逐渐了解了其中的细节,但是过一段时间之后总是记不太清楚了,所以...
  • QT 编写Rtf(富文本格式) 文件实例

    千次阅读 2016-01-10 14:11:25
    在rtf文档中可以嵌入图像等文件,RTF是word为了与其他字处理软件兼容而能够保存的文档格式,类似 DOC格式(Word文档)的文件,有很好的兼容性。 用什么软件可以打开?使用Windows“附件”中的“写字

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 129,843
精华内容 51,937
关键字:

多种文本文件的生成方法