使用python和qwen处理doc文件


今天帮同学处理了一些docx文件,主要是一些会议通知的文件,总共有一千多份doc、docx、pdf、wps等文件。

目标是提取这些会议通知中的时间、地点、名称、主办单位。

这些文本的格式相对统一,比如

1通知
2一、会议时间:
3xxx年xx月xx日xx点
4二、会议地点
5xxxx
6三、xxx
7
8xxx
9
10 【通知单位】
11 年月日​
12这种算是比较好的,还有啥
13
14通知
15一、请xxx于xxx月xx日到xxx参加xxx
16xxx
17xx​

两千份通知,横跨半年,至少四种文件格式(pdf、doc、docx、wps),大体格式至少十几种。

手写regex规则是不可能了,这辈子也不可能了。正好想到最近疯狂开源的大模型,文本直接喂给他们,可以帮我总结这些文本。

首先不考虑chatgpt,死贵死贵,2023年8月19日,116K context $0.003 / 1K tokens,两百万文本就要110人民币。其次,数据传到境外,明显不太安全,这里还是有些敏感数据的。

找了一圈,发现qwen和llama2还可以(早一个月遇到这个问题可能还没这好弄,感谢开源人士)。后边这个中文一般般,有LinkSoul微调的模型,我用下来效果一般,我这个任务可能不太对付的问题。前边qwen是阿里开源的,看他们公布的对比测试,很能打,阿里出品,看起来靠谱。(注:我只是对于我的文件进行测试,不代表两个相对好坏)

这两个试用都很方便,hugginface真是大大滴好。qwen文档:Qwen-7B/README_CN.md at main · QwenLM/Qwen-7B (github.com) 第一次用huggingface,还以为要手动一个个下载1pytorch_model-0001-0008.bin文件,原来代码里都处理好这些琐事了。直接跑,自动下载。 qwen还依赖flash-attention,官方推荐1git clone -b v1.0.8 ,就别用flash-attention2了,安装会遇到编译问题。

文件处理

找了半天没找到合适的库去读doc文件

1Traceback (most recent call last):
2 File "C:\Users\15258\.conda\envs\llm\Lib\site-packages\textract\parsers\utils.py", line 87, in run
3 pipe = subprocess.Popen(
4 ^^^^^^^^^^^^^^^^^
5 File "C:\Users\15258\.conda\envs\llm\Lib\subprocess.py", line 1026, in __init__
6 self._execute_child(args, executable, preexec_fn, close_fds,
7 File "C:\Users\15258\.conda\envs\llm\Lib\subprocess.py", line 1538, in _execute_child
8 hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
9 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10FileNotFoundError: [WinError 2] The system cannot find the file specified

处理pdf、doc都可以同word打开,然后重新saveas,pdf用word打开时会有个提示,按理来说可以在python加个-y的参数,自动选yes,没找到资料,跑脚本的时候手动点的yes,还好也就二三十个。有些pdf有加密,不让其他程序读取内容,这些处理不了,只能人工。

提示QWEN去提取信息

简单,直接加用

1f"找出以下通知的会议名称、会议时间、会议地点和主办单位,结果用逗号隔开\n{text}"

text是docx文本。

也就三个步骤:

  • 把pdf、doc转成docx
  • 读取docx
  • 文本加提示词喂给qwen

代码

1import os
2from win32com import client as wc #导入模块
3import textract
4
5
6word = wc.Dispatch("Word.Application") # 打开word应用程序
7
8
9base_dir = './docs'
10
11def find_all_file(base, extension):
12 for root, ds, fs in os.walk(base):
13 for f in fs:
14 fullname = os.path.join(root, f)
15 if f.endswith(extension):
16 yield os.path.abspath(fullname)
17
18
19for file in find_all_file(base_dir, ".pdf"):
20 print(file)
21 _file = file.replace("pdf", "docx")
22 print(_file)
23 if not os.path.exists(_file):
24 doc = word.Documents.Open(file) #打开word文件
25 doc.SaveAs(_file, 12)#另存为后缀为".docx"的文件,其中参数12指docx文件
26 doc.Close() #关闭原来word文件
27
28for file in find_all_file(base_dir, ".doc"):
29 print(file)
30 _file = file.replace("doc", "docx")
31 print(_file)
32 if not os.path.exists(_file):
33 doc = word.Documents.Open(file) #打开word文件
34 doc.SaveAs(_file, 12)#另存为后缀为".docx"的文件,其中参数12指docx文件
35 doc.Close() #关闭原来word文件
36
37word.Quit()
38
39import os
40from docx import Document
41from transformers import AutoModelForCausalLM, AutoTokenizer
42from transformers.generation import GenerationConfig
43
44# 请注意:分词器默认行为已更改为默认关闭特殊token攻击防护。
45tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B-Chat", trust_remote_code=True)
46
47# 打开bf16精度,A100、H100、RTX3060、RTX3070等显卡建议启用以节省显存
48# model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", device_map="auto", trust_remote_code=True, bf16=True).eval()
49# 打开fp16精度,V100、P100、T4等显卡建议启用以节省显存
50# model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", device_map="auto", trust_remote_code=True, fp16=True).eval()
51# 使用CPU进行推理,需要约32GB内存
52# model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", device_map="cpu", trust_remote_code=True).eval()
53# 默认使用自动模式,根据设备自动选择精度
54model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B-Chat", device_map="auto", trust_remote_code=True).eval()
55
56# 可指定不同的生成长度、top_p等相关超参
57model.generation_config = GenerationConfig.from_pretrained("Qwen/Qwen-7B-Chat", trust_remote_code=True)
58
59
60base_dir = './docs' # 通知文件的文件夹
61out_file = open("./doc_out2.txt", "w") #结果输出文件
62def find_all_file(base): #和上边代码重复了
63 for root, ds, fs in os.walk(base):
64 for f in fs:
65 fullname = os.path.join(root, f)
66 if f.endswith('.docx'):
67 yield fullname
68
69cnt = 0
70for i in find_all_file(base_dir):
71 document = Document(i) #读取docx
72 text = ""
73 for p in document.paragraphs:
74 text += p.text #拼接文本
75 text_format = f"找出以下通知的会议名称、会议时间、会议地点和主办单位,结果用逗号隔开\n{text}"
76 # 问模型
77 response, _ = model.chat(tokenizer, text_format, history = None)
78 cnt += 1
79 print(cnt, i)
80 out_file.write("\n" + "==" * 10 + "\n")
81 out_file.write(f"file_name = {i}\n") # 输出结果
82 out_file.write(response)
83

总结

有了large language model,这种任务一下子简单很多。

结果上,对于比较规整的文件,提取结果非常好。不太规整的文件,人力不好做,大模型也整不明白。

以我的经验,gpt4能把这个任务做的很好,qwen这些开源的7b模型,还是不如人意,比如

  • 输出了多余的提示信息,”您好”、”结果如下“等等
  • 分不清楚主办单位和被通知人,源文件确实复杂
  • 吃内存,4090 24G显存,也就处理4k左右的中文文本,多了爆显存