当前位置: 首页 > article >正文

论文速读 - Cleaner Pretraining Corpus Curation with Neural Web Scraping

这是论文 Cleaner Pretraining Corpus Curation with Neural Web Scraping 的速读笔记,同时简要分析这篇论文作者的实现代码. 论文的主要工作是提出了基于神经网络的高效crawler.

这里先澄清scrapercrawler的区别,一图胜千言.

web-crawler-vs-web-scraper

Abstract

The web contains large-scale, diverse, and abundant information to satisfy the informationseeking needs of humans. Through meticulous data collection, preprocessing, and curation, webpages can be used as a fundamental data resource for language model pretraining. However, when confronted with the progressively revolutionized and intricate nature of webpages, rule-based/feature-based web scrapers are becoming increasingly inadequate. This paper presents a simple, fast, and effective Neural web Scraper (NeuScraper) to help extract primary and clean text contents from webpages. Experimental results show that NeuScraper surpasses the baseline scrapers by achieving more than a 20% improvement, demonstrating its potential in extracting higher-quality data to facilitate the language model pretraining. All of the code is available at https: //github.com/OpenMatch/NeuScraper.

网络包含大规模、多样化和丰富的信息,以满足人类的信息需求。通过精心的数据收集、预处理和整理,网页可以作为语言模型预训练的基本数据资源。然而,面对不断革新和复杂的网页性质,基于规则/基于特征的scraper越来越显得不够用。本文介绍了一种简单、快速且有效的神经网络网页scraper(NeuScraper),以帮助从网页中提取主要和干净的文本内容。实验结果表明,NeuScraper通过实现超过20%的改进,超过了基线scraper,展示了其在提取更高质量数据以促进语言模型预训练方面的潜力。所有代码可在 https://github.com/OpenMatch/NeuScraper 上找到。

摘要中指出了网页因为多样化,以及丰富的信息,可以作为LLM训练的基本数据源,而现有的scraper不够用。因此作者提出了NeuScraper这种基于神经网络的方法,帮助从网页中提取主要干净文本, 并且在和baseline比较的时候,得到20%的性能提升.

Introduction

简介中,介绍了当前已有的一些工作。

LLMs在各种NLP任务重,随着模型参数规模的扩大,展现出了非常令人印象深刻的性能. 但在对scaling law研究的过程中发现表面,模型参数大小和训练数据的大小应该等比例的扩大,这在过去足够大的训练数据集方面构成了挑战,甚至引发了对数据稀缺的担忧.

为了给预训练整理更多的数据,研究人员更加注重从网络上收集更有价值的数据。像CommonCrawl这样的网络爬取数据集已经被广泛用于预训练,促进了语言模型的发展。然而,先前的研究表明,即使经过积极的清洗,CommonCrawl提供的预提取文本的质量仍然未能达到预期,原因在于广告、横幅、超链接等有害内容通常混杂在页面的主要内容中,因此仅提取这些主要内容会为预训练带来大量噪声. 先前的工作主要是rule basedheuristic,但在网页越来越复杂的现在,不仅效果不好,且很难维护这些scraper.

因此论文提出了一个简单、快速且有效的神经网络scraper(NeuScraper),它被设计用来从网页中提取主要内容。NeuScraper采用了浅层神经网络架构,并整合了布局信息以实现高效的抓取。实验表明,NeuScraper超越了基线,性能提升了20%,并为语言模型预训练生成了更高质量的语料库,尤其在GPU上展现了非常好的处理速度.
在这里插入图片描述
上图展现了NeuScraper的Content Extraction处理流程,可以看到,将原始的html提取转化为序列,经过NeuScarper的处理(事实上为分类处理),提取出需要的内容.

Related Work

相关工作中主要介绍了从网页中提取内容的一些方法, 例如基于规则和基于特征.

基于规则

基于规则的网页抓取方法,以及其中涉及的一些关键技术和挑战:

  1. 网页包装器(Web Wrappers):
  • 早期的网页抓取常使用网页包装器作为起点。
  • 网页包装器通常需要人工设计或使用包装器归纳系统生成。
  • 每个网页都需要定制包装器,难以处理大规模网页。
  1. 文档对象模型(DOM)树:
  • 更常见的方法是创建DOM树,用于构建基于规则的抓取器或比较网页。
  • DOM树表示网页的结构化数据,便于提取内容。
  1. 其他辅助技术:
  • 标签累积分布(Tag Cumulative Distributions)
  • 文本密度(Text Density)
  • 标签比例(Tag Ratios)
  • 这些技术可以帮助从网页中提取有用内容。

总的来说,基于规则的网页抓取方法需要针对不同网页定制规则和工具,难以扩展到大规模应用。研究者们提出了各种辅助技术,如DOM树、标签分布等,来改进内容提取的效果。但是,这些方法仍然需要大量的人工参与和专业知识,自动化程度有限。

基于特征

介绍了基于特征的网页内容提取方法,与基于规则的方法相比,这种方法更加灵活和智能。

  1. 基于特征的方法概述:

    • 将网页分割成多个块
    • 从这些块中提取大量手工设计的特征
    • 将这些特征输入到机器学习模型中进行分类
  2. 网页分块方法:

    • 基于HTML标签或DOM树结构制定规则
    • 将网页划分为若干个块
  3. 特征提取:
    从每个块中提取多种特征,包括:

    • 标记特征
    • 文本/文档特征
    • 语言学特征
    • 结构特征
    • 视觉特征
    • 基于DOM树的特征
  4. 机器学习模型:
    将提取的特征输入到各种机器学习模型中,如:

    • 支持向量机(SVM)
    • 条件随机场(CRF)
    • 逻辑回归
    • 卷积神经网络(CNN)
  5. 分类目标:
    判断每个块中的文本是否属于网页的主要内容

这种基于特征的方法相比于简单的规则基础方法更加灵活,能够处理更复杂的网页结构。通过结合机器学习技术,这种方法可以自动学习判断主要内容的规则,而不需要人工设计复杂的规则集。这种方法在处理多样化的网页时具有更好的适应性和准确性。

Neural Web Scraper

这部分介绍NeuScraper模型的工作流程和backbone.

先前的工作已经证明了结构和视觉特征在帮助识别主要内容方面的有效性。因此,为了保留网页布局信息,我们依赖DOM树结构将网页转换为文本序列。具体来说,我们使用BeautifulSoup4工具包为每个网页构建DOM树,对树进行深度优先遍历,并将访问顺序视为表示节点的附加位置信息。在这个过程中,只有包含纯文本的节点、表格节点(用<table>标记)和列表节点(用<ol>、<ul>或<dl>标记)被保留下来,以产生最终的文本序列 X = { x 1 , x 2 , ⋯   , x n } X = \{x_1, x_2, \cdots, x_n\} X={x1,x2,,xn},其中n表示保留的DOM节点的数量。处理后,网络爬取任务主要涉及确定节点xi是否包含网页的主要内容以供评估。

然后是这篇论文的最核心工作,如何处理这个序列 X = { x 1 , x 2 , ⋯   , x n } X = \{x_1, x_2, \cdots, x_n\} X={x1,x2,,xn}.

为了保障高效处理,NeuScraper用了XML-Roberta对这个序列进行encode, 把DOM node编码成 dim=768的向量.
h i = XLMRoberta-Layer ⁡ 1 ( x i ) , \begin{align} h_i=\operatorname{XLMRoberta-Layer}^1(x_i), \end{align} hi=XLMRoberta-Layer1(xi),
这里的 h i h_i hi实际上就是BERT中的[CLS]标签.

经过这一步处理后,结果会被放到一个3层transformer架构中(8个Attention head)
h i ^ = Transformer ⁡ ( Linear ⁡ ( h i ) ) , \begin{align} \hat{h_i} = \operatorname{Transformer}(\operatorname{Linear}(h_i)), \end{align} hi^=Transformer(Linear(hi)),
根据前人的工作,这些DOM node可以被分为6类:

  1. primary content
  2. heading
  3. title,
  4. paragraph
  5. table
  6. list

根据这6个分类,用 y k y^k yk表示第 k k k种标签(也就是类型), 计算DOM node属于某一种类型的概率 P ( y i k = 1 ∣ x i ) P(y_i^k=1\mid x_i) P(yik=1xi)
P ( y i k = 1 ∣ x i ) = Sigmoid ⁡ ( MLP ⁡ ( h i ^ ) ) \begin{align} P(y_i^k=1\mid x_i) = \operatorname{Sigmoid}(\operatorname{MLP}(\hat{h_i})) \end{align} P(yik=1xi)=Sigmoid(MLP(hi^))
然后与标签 Y i k \mathcal{Y}_i^k Yik计算交叉熵损失得到损失函数:
L = ∑ k = 1 6 ∑ i = 1 n CrossEntropy ⁡ ( P ( y i k ∣ x i ) , Y i k ) \begin{align} L = \sum_{k=1}^{6}\sum_{i=1}^n\operatorname{CrossEntropy}(P(y_i^k\mid x_i),\mathcal{Y}_i^k) \end{align} L=k=16i=1nCrossEntropy(P(yikxi),Yik)
至此,主要的工作就完成了.

实现

论文作者在github上给出了模型的实现, 本文简要分析一下模型结构代码src/scraper/model.py

import math
import torch.nn as nn
from metrics import *
from transformers import BertConfig, XLMRobertaConfig, XLMRobertaModel
from transformers.models.bert.modeling_bert import BertEncoder


class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        self.LayerNorm = nn.LayerNorm(d_model)

        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len / 2, dtype=torch.float).unsqueeze(1)
        position = position.repeat(1, 2).view(-1, 1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer("pe", pe)

    def forward(self, x):
        x = x + self.pe[: x.size(0), :]
        return self.dropout(x)

class MLP(nn.Module):
    def __init__(self, input_dim, output_dim, hidden_dim):
        super(MLP, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.hidden_dim = hidden_dim
        current_dim = input_dim
        self.layers = nn.ModuleList()
        if len(hidden_dim) >= 1:
            for hdim in hidden_dim:
                self.layers.append(nn.Linear(current_dim, hdim))
                self.layers.append(nn.ReLU())
                current_dim = hdim
        self.layers.append(nn.Linear(current_dim, output_dim))

    def forward(self, x):
        for layer in self.layers[:-1]:
            x = layer(x)
        out = self.layers[-1](x)
        return out


class ContentExtractionTextEncoder(nn.Module):
    def __init__(self, config):
        super().__init__()

        self.model_version = config.model_version
        self.sigmoid = nn.Sigmoid()
        self.relu = nn.ReLU()
        self.max_sequence_len = config.max_sequence_len
        self.num_classes = config.num_classes
        self.text_in_emb_dim = config.text_in_emb_dim
        self.text_emb_dim = config.text_emb_dim
        self.hidden = MLP(self.text_emb_dim, config.num_classes, []) 
        self.max_token_len = config.max_token_len
        self.enable_positional_encoding = not config.disable_positional_encoding

        print("Positional Encoding Enabled?: " + str(self.enable_positional_encoding))

        if self.enable_positional_encoding:
            self.pos_encoder = PositionalEncoding(d_model=self.text_emb_dim, max_len=config.max_sequence_len)

        self.textlinear = nn.Linear(
            config.text_in_emb_dim, config.text_emb_dim
        )  # 768 -> 256

        configuration = BertConfig(
            num_hidden_layers=config.num_layers,
            num_attention_heads=config.num_heads,
            hidden_size=self.text_emb_dim,
            intermediate_size=1024,
            output_hidden_states=False,
            output_attentions=False,
        )
        self.encoder = BertEncoder(configuration)

        text_roberta_config = XLMRobertaConfig.from_pretrained(
            "xlm-roberta-base",
            num_attention_heads=12,
            num_hidden_layers=config.text_encoder_num_hidden_layer,
        )
        self.text_roberta = XLMRobertaModel(text_roberta_config)


    def forward(self, x):
        [token_ids, token_masks] = x
        seq_len = self.max_sequence_len
        text_in_emb_dim = self.text_in_emb_dim
        max_token_len = self.max_token_len

        token_ids = token_ids.view(-1, max_token_len)  # [batch * max_sequence_len, max_token_len]
        token_masks = token_masks.view(-1, max_token_len)  # [batch * max_sequence_len, max_token_len]

        features = []

        text_output = self.text_roberta(input_ids=token_ids, attention_mask=token_masks)
        all_text_emb = text_output.pooler_output.reshape(-1, seq_len, text_in_emb_dim)

        text_x = self.textlinear(all_text_emb)
        features.append(text_x)

        text_visual_x = torch.cat(features, 2)

        if self.enable_positional_encoding:
            text_visual_x = text_visual_x.permute(1, 0, 2)

            text_visual_x = self.pos_encoder(text_visual_x)
            text_visual_x = text_visual_x.permute(1, 0, 2)

             
        if 'bert' in self.model_version:
            emb_output = self.encoder(text_visual_x, head_mask=[None, None, None])[0]
        else:
            emb_output = text_visual_x

        x_hidden = self.hidden(emb_output)
        output = self.sigmoid(x_hidden)
        return output

代码很清晰,主要定义了:

  • PostionEncoding 实现位置编码,这个在主网络中属于可选项
  • MLP 用来做最后的classification
  • ContentExtractionTextEncoder 处理已经经过XLMRobertaModelBertEncoder处理的序列

整个实现借助了XLMRoberta 这个强大的模型.

总结

这篇论文的内容不是非常多,也很容易理解,提出了对html内容提取的很好的思路,也很容易实现。但根据介绍训练的话需要8卡A100, 个人还是没法进行from scratch 的训练的,finetune方法正在研究中,欢迎沟通交流.


http://www.kler.cn/news/367502.html

相关文章:

  • SMA-BP时序预测 | Matlab实现SMA-BP黏菌算法优化BP神经网络时间序列预测
  • 让你的 IDEA 使用更流畅 | IDEA内存修改
  • IPC 进程间通信 信号量集合 Linux环境 C语言实现
  • 手把手教你安装最强文生图工具ComfyUI
  • linux中的PATH环境变量
  • Vue3 + TypeScript 实现 iframe 嵌入与通信的完整指南以及全屏弹窗方案
  • C++标准库之std::begin、std::end、std::pre和std::next
  • #深度学习:从基础到实践
  • 华为配置 之 划分VLAN
  • 蓝桥杯第二十场小白入门赛
  • 【作业6】基于CNN的XO识别
  • 为什么会有树这样的数据结构,使用树有什么好处 和其他数据结构对比
  • Qt:QtCreator使用
  • 可以拖动屏幕的简单页面播放示例
  • 深入探讨TCP/IP协议基础
  • 【C++】—— 模板进阶
  • 数字加% 循环后两个都变了只能深拷贝
  • 《计算机原理与系统结构》学习系列——处理器(中)
  • Linux:socket实现两个进程之间的通信
  • #单体到微服务架构服务演化过程
  • Mermaid流程图完全指南
  • 2024年10月25日练习(双指针算法)
  • Redis 主从同步 问题
  • python一键运行所有bat脚本
  • 机器学习(10.14-10.20)(Pytorch GRU的原理及其手写复现)
  • P1588 [USACO07OPEN] Catch That Cow S