非结构文本、图片、视频等数据是待挖掘的数据矿藏, 在经管、社科等研究领域中谁拥有了从非结构提取结构化信息的能力,谁就拥有科研上的数据优势。



一、需求

现在有很多个电子发票PDF文件, 使用自动化工具帮我们批量自动从发票PDF提取出格式化信息。如从发票

提取出DICT_DATA

DICT_DATA = {
    "开票日期": "2023年01月06日",
    "应税货物(或服务)名称": "*信息技术服务*技术服务费",
    "价税合计(大写)": "",
    "税率": "6%",
    "备注": "230106163474406331"
}



二、Ollama介绍

Ollama是一款开源应用程序,可让您使用 MacOS、Linux 和 Windows 上的命令行界面在本地运行、创建和共享大型语言模型。

Ollama 可以直接从其库中访问各种 LLM,只需一个命令即可下载。下载后,只需执行一个命令即可开始使用。这对于工作量围绕终端窗口的用户非常有帮助。如果他们被困在某个地方,他们可以在不切换到另一个浏览器窗口的情况下获得答案。


2.1 特点和优点

这就是为什么 OLLAMA 是您的工具包中必备的工具:

  • 简单 :OLLAMA 提供简单的设置过程。您无需拥有机器学习博士学位即可启动和运行它。
  • 成本效益 :在本地运行模型意味着您无需支付云成本。您的钱包会感谢您。
  • 隐私 :使用 OLLAMA,所有数据处理都在您的本地机器上进行。这对于用户隐私来说是一个巨大的胜利。
  • 多功能性 :OLLAMA 不只是为 Python 爱好者准备的。它的灵活性使其可以用于各种应用程序,包括 Web 开发。

2.2 使用 Ollama 进行 LLM 选择

默认情况下,Openai Models 在 CrewAI 中用作 llm。有经费、有网络、不担心数据泄露等条件下, 力求达到最佳性能,可考虑使用 GPT-4 或 OpenAI 稍便宜的 GPT-3.5。

但本文是要 本地部署, 因此我们将使用 Meta Llama 3,这是迄今为止功能最强大的公开 LLM。Meta Llama 3 是 Meta Inc. 开发的模型系列,是最新推出的模型,具有 8B 和 70B 两种参数大小(预训练或指令调整)。Llama 3 指令调整模型针对对话/聊天用例进行了微调和优化,并且在常见基准测试中胜过许多可用的开源聊天模型。


2.3 安装ollama

点击前往网站 https://ollama.com/ ,下载ollama软件,支持win、Mac、linux


2.4 下载LLM

ollama软件目前支持多种大模型, 如阿里的(qwen、qwen2)、meta的(llama3、llama3.1),


以llama3为例,根据自己电脑显存性能, 选择适宜的版本。如果不知道选什么,那就试着安装,不合适不能用再删除即可。


打开电脑命令行cmd(mac是terminal), 网络是连网状态,执行模型下载(安装)命令

ollama pull llama3

等待 llama3:8b 下载完成。


2.5 安装python包

在python中调用ollama服务,需要ollama包。

打开电脑命令行cmd(mac是terminal), 网络是连网状态,执行安装命令

pip3 install ollama

2.6 启动ollama服务

在Python中调用本地ollama服务,需要先启动本地ollama服务, 打开电脑命令行cmd(mac是terminal), 执行

ollama serve

Run

2024/06/14 14:52:24 routes.go:1011: INFO server config env="map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST:http://127.0.0.1:11434 OLLAMA_KEEP_ALIVE: OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:1 OLLAMA_MAX_QUEUE:512 OLLAMA_MAX_VRAM:0 OLLAMA_MODELS:/Users/deng/.ollama/models OLLAMA_NOHISTORY:false OLLAMA_NOPRUNE:false OLLAMA_NUM_PARALLEL:1 OLLAMA_ORIGINS:[http://localhost https://localhost http://localhost:* https://localhost:* http://127.0.0.1 https://127.0.0.1 http://127.0.0.1:* https://127.0.0.1:* http://0.0.0.0 https://0.0.0.0 http://0.0.0.0:* https://0.0.0.0:* app://* file://* tauri://*] OLLAMA_RUNNERS_DIR: OLLAMA_TMPDIR:]"
time=2024-06-14T14:52:24.742+08:00 level=INFO source=images.go:725 msg="total blobs: 18"
time=2024-06-14T14:52:24.742+08:00 level=INFO source=images.go:732 msg="total unused blobs removed: 0"
time=2024-06-14T14:52:24.743+08:00 level=INFO source=routes.go:1057 msg="Listening on 127.0.0.1:11434 (version 0.1.44)"
time=2024-06-14T14:52:24.744+08:00 level=INFO source=payload.go:30 msg="extracting embedded files" dir=/var/folders/y0/4gqxky0s2t94x1c1qhlwr6100000gn/T/ollama4239159529/runners
time=2024-06-14T14:52:24.772+08:00 level=INFO source=payload.go:44 msg="Dynamic LLM libraries [metal]"
time=2024-06-14T14:52:24.796+08:00 level=INFO source=types.go:71 msg="inference compute" id=0 library=metal compute="" driver=0.0 name="" total="72.0 GiB" available="72.0 GiB"

cmd(mac是terminal)看到如上的信息,说明本地ollama服务已开启。



三、实验

3.1 代码结构

点击下载 本文代码

project
   |
  - 代码.ipynb   #代码
  - prompt.txt  #提示模板
  - data
      |--- 1.pdf #实验的发票
  - result.csv   #结果

3.2 读取pdf

import cntext as ct
#cntext版本为2.1.3,非开源, #需联系大邓372335839获取

text = ct.read_pdf('data/1.pdf')
print(ct.__version__)
text

Run

2.1.3

' 北京增值税电子普通发票发票代码: \n发票号码: 69453658\n开票日期: 2023年01月06日\n校 验 码: \n购\n买\n方名        称: 哈尔滨所以然信息技术有限公司\n密\n码\n区030898/5<32>*/0*440/63+79*08\n纳税人识别号: 91230109MABT7KBC4M /<54<1*6+49<-*+*>7<-8*04<+01\n地 址、电 话: 68+160026-45904*2<+3+15503>2\n开户行及账号: 98*2/*-*480145+-19*0917-1*61\n货物或应税劳务、服务名称 规格型号 单 位 数 量 单 价 金 额 税率 税 额\n*信息技术服务*技术服务费 1248.113208 248.11 6% 14.89\n合      计 ¥248.11 ¥14.89\n价税合计(大写)\n  贰佰陆拾叁元整             (小写)¥263.00\n销\n售\n方名        称: \n备\n注230106163474406331\n纳税人识别号: 91110108MA01WFY0X6\n地 址、电 话: \n开户行及账号: \n  收款人: 复核: 开票人: 销售方:(章)'

3.4 提取信息

使用ollama服务中的大模型 llama3:8b , 需要大模型提示信息及数据。这是我实验里设计的提示信息prompt

prompt = open('prompt.txt', encoding='utf-8').read()
print(prompt)

Run

发票文本内容
--- 
{TEXT} 
---

以 JSON 格式回答。 JSON 应包含如下信息, 依次为"开票日期", "应税货物(或服务)名称", "价税合计(大写)", "税率", "备注"; 

%%time

import ollama
import cntext as ct
#cntext版本为2.1.3,非开源, 需联系大邓372335839获取

#读取发票pdf
content = ct.read_pdf('data/1.pdf')
#读取prompt
prompt = open('prompt.txt', encoding='utf-8').read()

response = ollama.chat(model='llama3:8b', messages=[
      {'role': 'system','content': prompt},
      {'role': 'user','content': content},
    ])

result = response['message']['content']
result = eval(result.split('```\n')[1].split('\n```')[0])
result

Run

CPU times: user 20.5 ms, sys: 2.34 ms, total: 22.9 ms
Wall time: 3.58 s

{'开票日期': '2023年01月06日',
 '应税货物(或服务)名称': '*信息技术服务*技术服务费',
 '价税合计(大写)': '贰佰陆拾叁元整',
 '税率': '6%',
 '备注': '230106163474406331'}

3.3 封装成函数extract_info

实验成功,我们将其封装为函数extract_info, 为增强代码的鲁棒性, 函数内设置了异常处理机制,最多可重试3次。

import ollama
import re


def extract_info(text, prompt, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = ollama.chat(
                model='llama3:8b',
                messages=[
                    {'role': 'system', 'content': prompt},
                    {'role': 'user', 'content': text}
                ]
            )

            result = response['message']['content']
            result = eval(result.split('```\n')[1].split('\n```')[0])
            return result
        
        except Exception as e:
            if attempt < max_retries:
                print(f"An error occurred: {e}. Retrying ({attempt + 1}/{max_retries + 1})...")
            else:
                raise e
  
#读取prompt
prompt = open('prompt.txt', encoding='utf-8').read()
result = extract_info(text, prompt)
result

result与之前无异, 为了节省版面,这里就不显示result。


3.4 批量提取

假设data文件夹内有成百上千的发票(实际上只有一张发票), 对data文件夹进行批量信息提取,结果存储为csv。

%%time

import os
import ollama
#cntext版本为2.1.3,非开源, 需联系大邓372335839获取
import cntext as ct
import pandas as pd


def extract_info(text, prompt, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = ollama.chat(
                model='llama3:8b',
                messages=[
                    {'role': 'system', 'content': prompt},
                    {'role': 'user', 'content': text}
                ]
            )

            result = response['message']['content']
            result = eval(result.split('```\n')[1].split('\n```')[0])
            return result
        
        except Exception as e:
            if attempt < max_retries:
                print(f"An error occurred: {e}. Retrying ({attempt + 1}/{max_retries + 1})...")
            else:
                raise e
                
  
#当前代码所在的代码文件与data文件夹处于同一个文件夹内
#获取data内所有pdf的路径
pdf_files = [f'data/{file}' for file in os.listdir('data') if '.pdf' in file]
#读取prompt
prompt = open('prompt.txt', encoding='utf-8').read()

dict_datas = []
for pdf_file in pdf_files:
    text = ct.read_pdf(pdf_file)
    dict_data = extract_info(text, prompt)
    dict_datas.append(dict_data)

df = pd.DataFrame(dict_datas)
df.to_csv('result.csv', index=False)
df

Run

CPU times: user 32 ms, sys: 2.17 ms, total: 15.2 ms
Wall time: 3.8 s



四、讨论

本文只使用了一张发票进行实验, 实际上准确率没有这么高, 识别错误字段集中在销售方纳税识别号(案例没有展示销售方纳税识别号的识别)。原因主要是ct.read_pdf读入pdf时,文本比较杂乱。对大模型的语义理解有一定的挑战。目前大模型已经支持文本、图片、音频、视频、网址, 所以各位看官,不用等太久,就可克服此问题。

大模型会对每个输入,给出正确概率最大的回答,因此大模型提取数据时存在一定的错误识别风险。为降低该风险,尽量选择特别特殊、显眼,例如三张发票的价税合计(大写), 因为信息是特殊的中文大写数字, 在所有文本中是最醒目最特别的文本信息,这样大模型处理这类信息时会给这类信息尽可能高的权重,增大回答的准确率。



精选内容