无需langchian,5分钟搭建专属论文阅读助手

330 阅读5分钟

一 论文结构

langchain是一个非常热门的大模型应用开发框架,可以帮助我们快速开发基于大模型的下层应用。本文不打算使用langchain开发,而是用少量代码实现论文阅读助手这个功能。

对于PDF格式的论文,一般的格式都是双列布局,在处理这些文档时,我们需要解决以下难点:

  • 中英文混杂:国内的期刊论文一般以中文为主,但是某些段落如摘要这些可能会附加英文论述;
  • 行列布局混乱:论文不是单纯严格按照行或者列布局,而是混合在一起,如图2所示(数字为阅读顺序)。
  • 格式多样:除了文字,论文中还包含图表,公式等,这往往会影响到文字提取的准确性。

论文布局-首页

论文布局-次页

二 版本分析策略

本文实现的论文阅读助手,无需精确识别其中的图表,只需要保证绝大部分文字信息都能获取,以及顺序正确,再结合大语言模型的归纳能力,一般会有不错的表现。 版面分析用到了pdfplumber 这个库,按照前面描述的格式,需要判断当前页面是否存在“行”(如图2中的1,4区域)把当前页面横向切开,有的话需要先读左边的内容,再读右边的内容;读完之后再把“行”的内容读进去;循环这个过程。以下是代码:

 page_center是一个经验值,一般设置在300左右,如果发现文档识别效果不好可以再调整
import pdfplumber as ppl

# page_center是一个经验值,即pdf文档页面中间的x坐标,用于把字符分成左右两部分
def extract_paper(filename, page_center=290):

    def _is_chinese(word):
        for ch in word:
            if '\u4e00' <= ch <= '\u9fff':
                return True
        return False
        
    pdf = ppl.open(filename)
    paras = []
    for page in pdf.pages:
        page_words = page.extract_words()
        # x0:单词的第一个字符的x坐标
        # x1:单词的最后一个字符的x坐标
        # top:单词的第一个字符的y坐标
        sentence = [[word['text'], round(word['x0']), round(word['x1']), round(word['top'])] for word in page_words]
        rows = []
        tmp = []
        for word in sentence:
            # 前后单词y坐标相差大于4(这里4也是一个经验值,一般不超过10),说明不是同一行
            if tmp and abs(word[-1] - tmp[-1][-1]) > 4:
                # 另起一行
                rows.append(tmp)
                tmp = []
            tmp.append(word)
        if tmp:
            rows.append(tmp)

        rows_in_page = []
        left_block, right_block = [], []

        for row in rows:
            is_two_columns=False
            row_left, row_right = [], []
            # row是一个二维list,表示一行的内容,形式如:[[text, x0, x1, top], [[text, x0, x1, top]], ...]
            for i in range(len(row)):

                # 用单词最后一个字符的x坐标判断属于左边还是右边
                if row[i][2]<page_center:
                    row_left.append(row[i])
                else:
                    row_right.append(row[i])
                # 前后两个单词相距超过10(说明中间有较长的空白),并且前后两个单词分别属于左右两边,则说明这一行是列式布局
                if i>0 and row[i][1]- row[i-1][2]>10 and row[i][1]>page_center and row[i-1][2]<page_center:
                    is_two_columns=True
            if is_two_columns:
                left_block.extend(row_left)
                right_block.extend(row_right)
            else:
                # 把之前的left和right添加,先左后右
                if left_block:
                    rows_in_page.extend(left_block)
                if right_block:
                    rows_in_page.extend(right_block)
                # 这一行不是列式布局,则正常添加当前行
                rows_in_page.extend(row)
                
                left_block=[]
                right_block=[]

        # 中英文字符空格处理
        txt = [x[0] if _is_chinese(x[0]) else ' '+x[0] for x in rows_in_page]
        paras.extend(txt)

    return "".join(paras)

三 效果

这里使用了白嫖的阿里云GPU算力平台进行演示。使用教程可以看:白嫖免费算力搞AI 使用到的论文

提取效果 可以看到识别效果跟原论文排版差距不大,整体效果可以接受。


首轮对话:

with torch.no_grad():
    response, history = model.chat(tokenizer, f"请帮我总结这篇文章的内容:{file_content}", history=[])
print(response)
本文分析了2008年末出台的玉米临时收储政策及其改革对玉米市场的影响。主要结论有:

1. 玉米临时收储政策实现了粮食增产和农民增收的目标,但也产生了下游产业成本上升、国产玉米竞争力下降、种植结构失衡和生态资源破坏等负面影响。

2. 2016年玉米收储政策取消后,玉米价格大幅下跌,农民收入减少,市场出现卖粮难,畜产品市场供给过剩。 

3. 玉米市场化改革方案亟待完善,包括确定目标价格水平、创新农民增收思路、优化玉米收购市场结构、建立市场预警机制和推进产业转型升级等。

4. 玉米市场化改革对农民和市场产生双重影响,需要进一步研究完善改革方案,引导农民结构调整,维护市场稳定。

多轮对话:

# 代入历史对话,注意这里可以不用加原文了
with torch.no_grad():
    response, history = model.chat(tokenizer, f"文章的第二章讲述了什么?", history=history)
print(response)
文章的第二章主要讲述了玉米临时收储政策效应及其终结。

本章首先介绍了玉米临时收储政策出台的背景和目标,包括调动农民生产积极性、增加粮食产量、保证农民收入稳定等。然后分析了玉米临时收储政策对下游产业、种植结构、生态资源等方面的负面影响。

接着,本章比较了玉米临时收储政策与水稻和小麦的最低收购价政策的区别,指出玉米临时收储政策对玉米产量和农民收入的贡献,以及其负面效应。最后,本章分析了玉米临时收储政策终结后,市场化和补贴政策对农民、市场和产业的影响。

多轮对话效果和准确率在线,对于简单的论文辅助阅读来说应该够用了,建议可以根据自己的目的尝试不同的prompt:如提问核心观点、论文提出的方法和现有方法的对比等。

附录

# DSW内调用chatglm3
from modelscope import AutoTokenizer, AutoModel, snapshot_download
import torch
model_dir = snapshot_download("ZhipuAI/chatglm3-6b-32k", revision = "v1.0.0")
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)

# 显存不够只能量化
model = AutoModel.from_pretrained(model_dir,trust_remote_code=True).quantize(8).cuda()
model = model.eval()