文檔解析與DeepSeek實體抽取實戰(zhàn))
簡介面向證券投研、NLP與知識圖譜方向的從業(yè)者與研究者這份192頁方案系統(tǒng)講解如何基于DeepSeek-VL2多模態(tài)文檔理解技術從券商研報中自動抽取實體關系并完成知識圖譜構建適用于投研數據中臺、智能研報解析與金融知識服務等場景。文檔共51個大章節(jié)覆蓋本體設計、多模態(tài)數據預處理、表格與圖片信息抽取、數據標注與質量控制、Prompt語義擴充、增量預訓練、分布式環(huán)境搭建、長尾實體損失優(yōu)化、關系抽取聯合訓練、微調方案對比及跨券商泛化測試等內容。方案不僅給出技術選型依據還包含訓練監(jiān)控、梯度優(yōu)化、Prompt模板設計與蒸餾分析等工程細節(jié)便于讀者直接復用到同類證券文本處理任務中。資源共1個PDF文件壓縮包大小約11.15MB目前已有98人學習下載。1. 為什么證券投研需要可計算的知識圖譜一份券商深度研報通常在30到100頁之間少數行業(yè)框架報告會沖到192頁以上。真正決定信息價值的部分往往不在摘要和正文里而在跨頁的財務表格、產業(yè)鏈示意圖、產品對比圖和腳注的風險提示中。過去做研報知識圖譜卡在兩道墻PDF版面結構無法批量還原實體和關系只能靠規(guī)則模板硬抽。多模態(tài)文檔理解把第一道墻拆掉DeepSeek這類生成式模型則把實體關系抽取從特征工程變成指令工程。這套證券投研知識圖譜構建方案主線是研報從PDF變成結構化JSON再變成帶置信度的三元組經過實體鏈接寫入Neo4j最終服務于投研問答和產業(yè)鏈傳導分析。適合正在做投研系統(tǒng)、量化因子庫或企業(yè)知識中臺的工程師。2. 多模態(tài)研報解析把PDF版面中的表格、圖表和正文變成統(tǒng)一JSON2.1 研報PDF為什么難解析先分清三類內容研報PDF不是干凈的文本文檔問題集中體現在三個層面雙欄或三欄排版導致文本閱讀順序錯亂帶框線表格跨頁后表頭重復且列對齊失敗產業(yè)鏈圖和景氣度曲線以圖片形式嵌入導致數字完全不可見。若直接對整頁做純文本抽取碳酸鋰價格走勢這類關鍵圖表里的數據會全部丟失。多模態(tài)文檔理解在工程上的落地方式并不是只靠某個視覺大模型把整頁讀一遍而是把版面拆成文本流、表格結構、圖表圖像三軌分別解析后再融合成統(tǒng)一JSON。這樣做的理由是每一條解析路徑都可獨立調試、獨立緩存并且失敗時可以降級。文本層用pdfplumber提取表格層需要指定抽取策略圖表層則先把頁面渲染成圖片再交給OCR或視覺模型接管。| 內容形態(tài) | 解析工具 | 輸出結構 | | 正文文本 | pdfplumber extract_text | 純文本加坐標 | | 表格 | pdfplumber extract_tables | 二維數組轉markdown | | 圖表圖片 | PyMuPDF渲染加OCR | 圖片路徑加文字識別結果 | | 頁眉頁腳 | 版面過濾規(guī)則 | 丟棄或標記為噪聲 |券商研報的版權頁、評級說明和免責聲明集中在首尾頁通常與投研實體無關。解析階段直接按頁號范圍丟棄這兩部分既能減少噪聲也能降低后續(xù)LLM抽取的token消耗。2.2 一套可復現的PDF解析流水線我一般會把解析分成三個函數extract_text、extract_tables、render_pages最后按頁合并。下面這個實現基于PyMuPDF和pdfplumber用pip安裝后即可運行。import fitz import pdfplumber from pathlib import Path def parse_research_report(pdf_path: str, out_dir: str) - list: pages [] render_dir Path(out_dir) / pages render_dir.mkdir(parentsTrue, exist_okTrue) doc fitz.open(pdf_path) with pdfplumber.open(pdf_path) as pdf: for idx, page in enumerate(pdf.pages): # 文本層保留相對坐標用于后續(xù)按欄切分 text page.extract_text( x_tolerance1.5, # 字符間超過1.5pt視為列邊界 y_tolerance3.0, # 垂直方向的字符合并容差 layoutFalse # False得到帶換行的純文本 ) # 表格層優(yōu)先按線條切分研報表格通常帶完整框線 tables page.extract_tables( { vertical_strategy: lines, horizontal_strategy: lines, snap_tolerance: 3, } ) # 圖像層150DPI渲染整頁供視覺模型或OCR兜底 pix doc[idx].get_pixmap(dpi150) img_path str(render_dir / f{idx:04d}.png) pix.save(img_path) pages.append({ page_no: idx 1, text: text, tables: [t for t in tables if t], image_path: img_path, source: pdf_path }) doc.close() return pages代碼邏輯說明x_tolerance1.5控制字符間距達到多大時允許換行。研報雙欄場景下同欄字符間距通常遠小于欄間空隙設1.5能在保留欄內完整句子的同時避免把兩欄文本混在一起。y_tolerance3.0控制垂直方向多近的字符會被合并為同一行用來容忍行內輕微偏移。表格抽取采用lines策略因為券商研報表格幾乎都帶框線命中率高如果遇到無框線表格可以退回text策略按文本間隙識別。渲染150DPI是為了給視覺模型清晰輸入同時控制顯存占用。有一個容易忽略的點extract_tables返回的是二維數組跨頁表格需要自己檢測表頭重復。常見做法是記錄每頁第一行并和上一頁表頭行比較完全相同就丟棄本頁表頭避免實體抽取階段把單位億元當作數據行。2.3 表格與圖表內容如何進入知識抽取pdfplumber拿到的表格是二維數組不能直接塞給實體關系抽取。我習慣把表格轉成markdown再拼進文本流因為LLM對markdown表格的理解穩(wěn)定性遠高于裸數組且能保留表頭與列的對應關系。def table_to_markdown(table: list) - str: if not table: return lines [] for row in table: cells [str(c).replace(\n, ).strip() if c else for c in row] lines.append(| | .join(cells) |) return \n.join(lines)圖表部分則是另一條路徑。DeepSeek開放平臺目前的對話模型以文本輸入為主所以生產環(huán)境里常見做法是OCR識別圖內文字再結合圖注文本做一次摘要。把產業(yè)鏈圖中的公司名、箭頭關系、區(qū)域標簽全部OCR出來后按節(jié)點名、連線、邊方向重新組織就能補齊純文本缺失的關系信息。3. 實體關系自動抽取DeepSeek從提示詞到JSON的工程化配置3.1 先定義Schema再寫Prompt投研圖譜要抽什么實體抽取之前必須先畫清楚邊界。投研知識圖譜里的實體不是通用NER里的人名地名機構名而是業(yè)務對象上市公司、集團母公司、行業(yè)、產品、原材料、區(qū)域產能、財務指標、風險事件。關系也不是泛化的相關而是要支撐后續(xù)推理的類型。我一般用一張表固定schema并把表里的定義寫進system prompt讓模型每次輸出都落在預設軌道上。| 實體類型 | 別名覆蓋 | 示例 | | 上市公司 | 股票簡稱、曾用名 | 寧德時代、CATL | | 集團 | 母公司、控股平臺 | 華友鈷業(yè)、浙江華友 | | 行業(yè) | 申萬或中信分類 | 動力電池、正極材料 | | 產品與原材料 | 規(guī)格、俗稱 | 磷酸鐵鋰、六氟磷酸鋰 | | 財務指標 | 報告期、口徑 | 營收、歸母凈利潤 |關系類型固定為實控、主營、采購、銷售、競爭、產能布局、業(yè)績驅動、風險傳導。關系必須有方向prompt里寫死主語在前賓語在后。沒有方向的三元組在后續(xù)圖查詢里會變成全表掃描。3.2 DeepSeek API調用參數與JSON提取模板研報文本按1000到1500字切塊塊與塊之間重疊150字避免跨塊的三元組被拆散。切塊前把OCR錯誤、表格噪聲統(tǒng)一清洗保留原文所在頁碼。抽取prompt里最關鍵的三個約束只抽原文明確提到的信息不推斷不補全輸出嚴格JSON每個實體帶page字段。以下代碼兼容DeepSeek開放平臺的OpenAI協(xié)議設置好環(huán)境變量即可運行。import json import os from openai import OpenAI client OpenAI( api_keyos.environ[DEEPSEEK_API_KEY], base_urlhttps://api.deepseek.com ) SYSTEM_PROMPT 你是證券研報信息抽取引擎。 只抽取原文明確出現的事實禁止推測和補全。 實體類型限定為: 上市公司、集團、行業(yè)、產品、原材料、區(qū)域、財務指標、風險事件。 關系類型限定為: 實控、主營、采購、銷售、競爭、產能布局、業(yè)績驅動、風險傳導。 輸出JSON格式: {triples: [{head: {text: , type: , page: 1}, relation: , tail: {text: , type: , page: 1}}]} def extract_triples(chunk: str, page_offset: int) - dict: resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: f從以下研報片段抽取知識三元組\n{chunk}} ], temperature0, top_p0.1, response_format{type: json_object}, max_tokens2048 ) return json.loads(resp.choices[0].message.content)參數說明temperature0讓解碼近似貪心減少同一段文本反復抽取時的結果漂移top_p0.1進一步收窄采樣空間對抽取類任務比單獨調temperature更有效。response_format強制JSON輸出避免模型把三元組寫進markdown代碼塊。max_tokens2048是因為一塊研報文本最多可能抽出幾十組三元組太小會截斷成非法JSON。3.3 抽取結果的后處理去重、合法性校驗與置信度模型返回的三元組不能直接入庫。第一關是字符串校驗head和tail的text長度必須在2到60之間relation必須在schema枚舉內。第二關是重復合并同一公司全稱和簡稱在抽取結果里會出現多次用上市公司簡稱表先做一次歸一化。第三關是置信度近似三元組在原文中能找到連續(xù)原文片段的記為高置信只在表格中出現的記為中置信兩者都不滿足的直接丟棄。還有一個高頻坑研報正文里公司指代上一段的主語模型經常把三元組主語抽成公司兩個字。對策是在分塊前做簡單指代消解把公司替換為離它最近的實體全稱或者把段落標題拼進句子開頭再抽取。4. 實體鏈接與Neo4j圖譜構建把抽取結果對齊到統(tǒng)一實體庫4.1 為什么要做實體鏈接同名不同實體與異名同實體的邊界實體關系抽取得到的是文本片段還不是知識圖譜里的節(jié)點。同一家公司在不同研報里可能被寫成寧德時代CATL寧德而中信證券既指證券公司又指在上交所上市的標的在不同上下文里指代的對象完全不同。實體鏈接的任務就是把每個抽取到的mention映射到統(tǒng)一實體庫的唯一ID上。簡單做字符串匹配只能解決第一層問題。研報里大量簡稱變體會把精確匹配打穿湖南裕能被寫成裕能股份時僅靠名稱判等會全部落空。我采用的混合方案分兩步走候選生成階段用歸一化名稱加編輯距離候選排序階段用embedding相似度加行業(yè)上下文特征。| 問題類型 | 典型例子 | 處理階段 | | 異名同實體 | CATL、寧德時代 | 別名表加embedding排序 | | 同名不同實體 | 中信證券作為券商與上市公司 | 行業(yè)上下文過濾 | | 簡稱截斷 | 湖南裕能、裕能股份 | 編輯距離候選生成 |4.2 向量加規(guī)則的混合實體鏈接實現候選生成階段要快。先做字符串歸一化把全角括號、空格、后綴統(tǒng)一再查別名表。別名精確命中直接進入排序環(huán)節(jié)。沒有別名命中的mention用編輯距離找候選這一步控制在Top 20以內。候選排序階段用embedding把mention和候選實體的名稱、行業(yè)、主營產品拼成同一段描述文本分別向量化后算余弦相似度。def link_mention(mention: str, candidates: list, embed_fn, top_k5): mention: 抽取到的實體文本 candidates: 候選實體列表元素格式為 {id: , name: , seq: } embed_fn: 文本向量化函數接收字符串返回向量 m_vec embed_fn(mention) scored [] for cand in candidates: c_vec embed_fn(cand[seq]) sim cosine_similarity(m_vec, c_vec) scored.append((cand[id], sim)) scored.sort(keylambda x: x[1], reverseTrue) return scored[:top_k]注意這里的candidates必須來自別名表和模糊匹配不是全體實體庫。全庫檢索在實體規(guī)模超過十萬時會拖慢整條鏈路而且相似度噪聲大。行業(yè)上下文可以放在embedding拼接階段云鋁股份和云南鋁業(yè)在描述文本里的語義距離會因行業(yè)前綴趨近比單純比較名稱更穩(wěn)定。鏈接準確率至少要達到95%否則錯誤三元組會污染圖譜下游推理。4.3 Neo4j寫入約束、MERGE與批量提交知識圖譜存儲選Neo4j是投研場景的常見選擇Cypher在多跳關系查詢上的表達能力最適合產業(yè)鏈分析。落庫前先建唯一約束這是冪等寫入的前提。CREATE CONSTRAINT company_name IF NOT EXISTS FOR (n:Company) REQUIRE n.uid IS UNIQUE;寫入用MERGE而不是CREATE。MERGE會先按uid查重存在則跳過或更新屬性不存在才創(chuàng)建。關系同樣需要一個去重鍵否則同一份研報重跑會重復建邊。常見做法是把關系的source_report、page、triple序號拼接成字符串作為關系上的sig屬性。UNWIND $batch AS row MERGE (c:Company {uid: row.company_uid}) ON CREATE SET c.name row.company_name, c.first_seen row.process_time MERGE (p:Product {uid: row.product_uid}) ON CREATE SET p.name row.product_name MERGE (c)-[r:MAIN_PRODUCT {sig: row.edge_sig}]-(p) ON CREATE SET r.source_report row.report_id, r.confidence row.confidence, r.page row.page;MERGE關系時節(jié)點屬性只寫uid其余屬性放入ON CREATE SET這樣節(jié)點已存在時不會被反復覆蓋。后端批量提交時每個batch控制在500行以內事務提交更穩(wěn)失敗重試的成本也更低。5. 圖譜構建的評測與增量更新Precision之外還要盯住三種錯誤5.1 評測集與指標誰算對誰算錯沒有評測集就談不上優(yōu)化。從研報庫里按行業(yè)和年份分層抽樣取100段文本每段標注實體邊界、實體類型、關系方向、鏈接目標。標注規(guī)范要寫清楚兩個最容易扯皮的點跨句關系算不算隱含實體算不算。我的默認口徑是實體必須在原文出現關系允許跨兩個相鄰句子不允許跨段推斷。指標按三元組級別計算正確性要求比純文本抽取更嚴格。| 指標 | 計算方式 | 合格線 | | 實體F1 | 邊界和類型同時正確才算正例 | 0.85 | | 關系F1 | head、relation、tail全部正確 | 0.80 | | 鏈接準確率 | mention正確映射到標準實體ID的比例 | 0.95 |5.2 三種高頻錯誤與修正回路第一種是邊界錯誤。動力電池龍頭被抽成實體時模型常把龍頭帶上或者只給電池。修法是在prompt里強調實體字段必須是最小可獨立指稱的名詞短語不要修飾語。第二種是關系方向顛倒。公司采購碳酸鋰被抽成碳酸鋰采購公司。修法是把關系定義寫成主語是行為發(fā)出方賓語是行為承受方并在校驗階段檢查三元組的頭尾實體類型是否匹配關系定義。比如采購關系的head必須是公司或集團tail必須是產品或原材料。第三種是幻覺補充。模型依據研報標題補充了原文沒有的結論。修法有兩個層面prompt里明確沒有找到就輸出空數組后處理里用原文回溯若三元組在原文chunk里找不到任意兩個詞的共現直接丟棄。5.3 增量更新與回滾研報庫是流式的不是一次性導入投研場景中研報持續(xù)發(fā)布同一個公司會被多家券商覆蓋舊報告也可能被撤回。圖譜更新要有三個機制按報告冪等寫入、按時間戳回滾、按事件重算子圖。def rollback_report(graph, report_id: str): # 刪除某份研報寫入的全部關系保留節(jié)點 query MATCH (a)-[r]-(b) WHERE r.source_report $report_id DELETE r graph.execute(query, {report_id: report_id})回滾時只刪關系不刪節(jié)點因為同一節(jié)點可能被多份報告關聯。節(jié)點孤島清理放到夜間批任務中單獨執(zhí)行避免正常業(yè)務被誤刪。如果研報數據有合規(guī)要求可以把DeepSeek開放平臺接口替換成內網部署的推理服務模型權重不變抽取鏈路只改base_url指向評測和回滾邏輯不用動。增量更新調度建議按report_date做水位線每天只處理新導入的報告并記錄每個報告的處理版本號。6. 圖譜用起來把Neo4j子圖變上下文喂給DeepSeek的投研問答技巧圖譜構建完成后價值體現在問答和傳導分析。常見做法是圖檢索加文本檢索混合先用Cypher找到答案所在的子圖再把子圖序列化成證據文本喂給DeepSeek。這個子圖轉上下文的環(huán)節(jié)決定回答質量。我會把每個三元組轉成一句帶出處的證據鏈保留source_report、page和原始句子。LLM回答時必須引用證據不允許脫離上下文編造。MATCH (c:Company {uid: $uid})-[r:SUPPLY]-(m:Material) RETURN c.name, type(r), m.name, r.source_report, r.page LIMIT 20;查詢結果在應用層拼成根據某研報第X頁某公司采購某原材料再把多條證據拼接成上下文塊放進prompt。這個技巧能明顯減少DeepSeek在不相關實體上繞圈子的概率每個事實都能回溯出處。傳導分析走多跳查詢比如上游碳酸鋰漲價影響哪些正極材料公司。把圖查詢結果按最短路徑展開逐段解釋傳導機制本質是圖譜推理加語言模型生成。如果回答延遲偏高就把常用路徑預計算成物化視圖查詢直接走視圖讓響應時間穩(wěn)定在百毫秒級。本文還有配套的精品資源點擊獲取