生錄取工作開始后,負責招生的老師需要完成一系列任務,其中包括確定招生錄取名單和及時向考生公布錄取情況。為了快速完成錄取查詢工作的發布,招生老師們可以采取以下步驟:
1. 整理錄取名單:招生老師們首先需要整理好錄取名單,確保其中包含了所有被錄取的考生的相關信息,如姓名、學號、專業等。他們可以通過學校的招生系統或數據庫來獲取這些信息。
2. 準備查詢頁面:招生老師們可以使用前面提到的創建錄取查詢頁面的步驟來準備一個查詢頁面。在該頁面上,他們可以設置一個查詢表單,要求考生輸入自己的相關信息,如姓名、學號等,然后點擊查詢按鈕。這個頁面可以利用易查分輕松實現,不需要任何費用,只需把錄取名單制作成Excel表格,導入易查分,就可以生成功能強大的錄取查詢頁面,考生輸入查詢條件,即可自己查錄取信息。
3. 導入錄取名單:招生老師們可以將整理好的錄取名單導入到查詢頁面的后端數據庫中。這樣,當考生輸入查詢條件后,系統可以根據錄取名單進行查詢,快速返回相應的錄取情況。
4. 測試查詢功能:在發布查詢頁面之前,招生老師們應該進行一次測試,確保查詢功能正常運行。他們可以模擬考生輸入不同的查詢條件,檢查系統是否能夠正確地返回相應的錄取情況。
5. 發布查詢頁面:一旦測試通過,招生老師們可以將查詢頁面鏈接發布給考生。他們可以通過學校的官方網站、招生宣傳渠道或郵件等方式,將查詢頁面的鏈接提供給考生和家長。
6. 監控查詢情況:在查詢頁面發布后,招生老師們應該密切關注查詢情況。他們可以設置一些監控機制,如記錄查詢次數和查詢結果等,以便及時發現和解決可能出現的問題。
通過以上步驟,招生老師們可以快速完成錄取查詢工作的發布。這樣,考生和家長可以方便地查詢自己的錄取情況,同時也減輕了招生老師們的工作負擔。
除此之外,老師們還可以通過以下傳統的制作錄取查詢頁面的步驟,來制作一個錄取查詢頁面:
1. 設計頁面布局:確定頁面的整體結構和布局,包括標題、查詢表單和查詢結果顯示區域等。考慮使用響應式設計,以適應不同設備的屏幕大小。
2. 創建查詢表單:在頁面上添加一個查詢表單,包括學生的相關信息,如姓名、學號、身份證號等。可以使用HTML表單元素,如輸入框、下拉菜單和復選框等,來收集用戶輸入的查詢條件。
3. 驗證查詢條件:在后端或前端進行查詢條件的驗證,確保輸入的信息格式正確和完整。可以使用正則表達式或其他驗證方法來檢查輸入的學號、身份證號等是否符合規定的格式。
4. 進行查詢操作:在后端處理查詢請求,根據用戶輸入的條件,在數據庫中進行查詢操作。根據查詢結果,可以返回相應的錄取信息或錯誤提示。
5. 顯示查詢結果:將查詢結果顯示在頁面上,可以使用表格或列表等形式展示。如果查詢結果為空,可以顯示相應的提示信息。
6. 添加樣式和交互效果:為查詢頁面添加適當的樣式和交互效果,以提升用戶體驗。可以使用CSS來美化頁面,如設置背景顏色、字體樣式和按鈕樣式等。可以使用JavaScript或其他前端框架,實現一些交互效果,如實時搜索和動態加載查詢結果等。
7. 測試和優化:進行頁面的測試,確保查詢功能正常運行,并檢查頁面在不同瀏覽器和設備上的兼容性。根據用戶的反饋和測試結果,進行必要的優化和改進。
8. 部署和發布:將查詢頁面部署到服務器上,并將查詢頁面的鏈接提供給學生和家長。確保服務器的穩定性和安全性,以保護學生信息的機密性。
以上是創建一個錄取查詢頁面的一般步驟,具體實現可以根據需求和技術要求進行調整和修改。
施 RAG 是一個挑戰,特別是在有效解析和理解非結構化文檔中的表格時。這對于掃描文檔或圖像格式的文檔尤其困難。這些挑戰至少有三個方面:
本文首先介紹RAG中管理表的關鍵技術。然后,在提出和實施新的解決方案之前,它會審查一些現有的開源解決方案。
該模塊的主要功能是從非結構化文檔或圖像中準確提取表結構。
附加功能:最好能提取對應的表格標題,方便開發者將表格標題與表格關聯起來。
根據我目前的理解,有以下幾種方法,如圖1所示:
圖 1:表解析器。圖片由作者提供。
(a) 利用多模態 LLM(例如GPT-4V)來識別表格并從每個 PDF 頁面中提取信息。
(b)利用專業的表檢測模型,如Table Transformer,來識別表結構。
(c)使用開源框架,例如非結構化框架和其他框架,它們也采用對象檢測模型(本文詳細介紹了非結構化的表檢測過程)。這些框架允許對整個文檔進行全面解析,并從解析結果中提取與表格相關的內容。
(d) 使用 Nougat、Donut等端到端模型來解析整個文檔并提取與表相關的內容。此方法不需要 OCR 模型。
值得一提的是,無論采用何種方法提取表格信息,都應包含表格標題。這是因為在大多數情況下,表格標題是文檔或論文作者對表格的簡要描述,可以很大程度上概括整個表格。
在上述四種方法中,方法(d)可以輕松檢索表格標題。這對開發人員來說是有益的,因為它允許他們將表格標題與表格關聯起來。這將在下面的實驗中進一步解釋。
根據索引的結構,解決方案大致可以分為以下幾類:
(e) 僅以圖像格式索引表。
(f)僅索引純文本或 JSON 格式的表。
(g)僅索引 LaTeX 格式的表。
(h)只索引表的摘要。
(i)從小到大或文檔摘要索引結構,如圖2所示。
圖 2:從小到大索引(上)和文檔摘要索引(中和下)的結構。圖片由作者提供。
如上所述,表格摘要通常是使用 LLM 生成的:
有些算法不需要表解析。
(j).將相關圖像(PDF頁面)和用戶的查詢發送到VQA模型(如DAN等)或多模態LLM,并返回答案。
(k)將相關文本格式的PDF頁面和用戶的查詢發送給LLM,然后返回答案。
(l)將相關圖像(PDF頁面)、文本塊和用戶查詢發送到多模態LLM(如GPT-4V等),并直接返回答案。
另外,下面是一些不需要索引的方法,如圖3和圖4所示:
圖 3:類別 (m)。圖片由作者提供。
(m)首先,應用類別(a)至(d)中的一種方法,將文檔中的所有表格解析為圖像形式。然后直接將所有表格圖像和用戶的查詢發送到多模態LLM(如GPT-4V等)并返回答案。
圖 4:類別 (n)。圖片由作者提供。
(n)使用(m)提取的圖像格式的表格,然后使用OCR模型識別表格中的所有文本,然后將表格中的所有文本和用戶的查詢直接發送給LLM并直接返回答案。
值得注意的是,有些方法不依賴于 RAG 過程:
上一節對 RAG 中表的關鍵技術進行了總結和分類。在提出本文實現的解決方案之前,讓我們先探索一些開源解決方案。
LlamaIndex 提出了四種方法,前三種使用多模態模型。
按照本文的分類:
經過測試,發現第三種方法總體效果最好。然而,根據我的測試,第三種方法很難檢測表格,更不用說正確地將表格標題與表格合并了。
Langchain也提出了一些解決方案,半結構化RAG的關鍵技術包括:
如圖5所示:
圖 5:Langchain 的半結構化 RAG。來源:半結構化 RAG
半結構化和多模態 RAG提出了三種解決方案,其架構如圖 6 所示。
圖 6:Langchain 的半結構化和多模式 RAG。來源:半結構化和多模式 RAG。
選項 1與本文的類別 (l)類似。它涉及使用多模態嵌入(例如CLIP)來嵌入圖像和文本,使用相似性搜索檢索兩者,并將原始圖像和塊傳遞到多模態 LLM 進行答案合成。
選項 2利用多模態 LLM(例如GPT-4V、LLaVA或FUYU-8b)從圖像生成文本摘要。然后,嵌入和檢索文本,并將文本塊傳遞給 LLM 進行答案合成。
選項 3使用多模態 LLM(例如GPT-4V、LLaVA或FUYU-8b)從圖像生成文本摘要,然后參考原始圖像(類別 (i))嵌入和檢索圖像摘要,然后傳遞原始圖像將圖像和文本塊傳輸到多模式法學碩士以進行答案合成。
本文對關鍵技術和現有解決方案進行了總結、分類和討論。基于此,我們提出了以下解決方案,如圖 7 所示。為了簡單起見,省略了一些 RAG 模塊,例如重新排名和查詢重寫。
圖 7:本文建議的解決方案。圖片由作者提供。
這種方法的優點是能夠高效地解析表格,同時綜合考慮表格匯總和表格之間的關系。它還消除了對多模態LLM的需求,從而節省了成本。
Nougat是基于Donut架構開發的。它通過網絡隱式識別文本,無需任何 OCR 相關輸入或模塊,如圖 8 所示。
圖 8:遵循Donut 的端到端架構。 Swin Transformer 編碼器獲取文檔圖像并將其轉換為潛在嵌入,隨后以自回歸方式將其轉換為標記序列。資料來源:Nougat:學術文檔的神經光學理解。
Nougat 解析公式的能力令人印象深刻。它在解析表方面也很出色。它可以方便地關聯表格標題,如圖 9 所示:
圖9:Nougat運行結果,結果文件為Mathpix Markdown格式(通過vscode插件打開),表格為LaTeX格式。
在我對十幾篇論文的測試中,我發現表格標題總是固定在表格后面的行上。這種一致性表明這并非偶然。因此,我們有興趣了解Nougat是如何實現這種效果的。
鑒于它是一個缺乏中間結果的端到端模型,它可能嚴重依賴于其訓練數據。
根據格式化訓練數據的代碼,對于 table 來說,緊接著的一行\end{table}是caption_parts,這看起來與提供的訓練數據的格式一致:
def format_element(
element: Element, keep_refs: bool=False, latex_env: bool=False
) -> List[str]:
"""
Formats a given Element into a list of formatted strings.
Args:
element (Element): The element to be formatted.
keep_refs (bool, optional): Whether to keep references in the formatting. Default is False.
latex_env (bool, optional): Whether to use LaTeX environment formatting. Default is False.
Returns:
List[str]: A list of formatted strings representing the formatted element.
"""
...
...
if isinstance(element, Table):
parts=[
"[TABLE%s]\n\\begin{table}\n"
% (str(uuid4())[:5] if element.id is None else ":" + str(element.id))
]
parts.extend(format_children(element, keep_refs, latex_env))
caption_parts=format_element(element.caption, keep_refs, latex_env)
remove_trailing_whitespace(caption_parts)
parts.append("\\end{table}\n")
if len(caption_parts) > 0:
parts.extend(caption_parts + ["\n"])
parts.append("[ENDTABLE]\n\n")
return parts
...
...
優點:
缺點:
首先,安裝相關Python包
pip install langchain
pip install chromadb
pip install nougat-ocr
安裝完成后,我們可以查看Python包的版本:
langchain 0.1 .12
langchain-community 0.0 .28
langchain-core 0.1 .31
langchain-openai 0.0 .8
langchain-text-splitters 0.0 .1
chroma-hnswlib 0.7 .3
chromadb 0.4 .24
nougat-ocr 0.1 .17
設置環境并導入:
import os
os.environ["OPENAI_API_KEY"]="YOUR_OPEN_AI_KEY"
import subprocess
import uuid
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_core.runnables import RunnablePassthrough
下載《Attention Is All You Need to》論文YOUR_PDF_PATH,運行nougat解析PDF文件,從解析結果中獲取latex格式的表格和文本格式的表格標題。第一次執行將下載必要的模型文件。
def june_run_nougat(file_path, output_dir):
# Run Nougat and store results as Mathpix Markdown
cmd=["nougat", file_path, "-o", output_dir, "-m", "0.1.0-base", "--no-skipping"]
res=subprocess.run(cmd)
if res.returncode !=0:
print("Error when running nougat.")
return res.returncode
else:
print("Operation Completed!")
return 0
def june_get_tables_from_mmd(mmd_path):
f=open(mmd_path)
lines=f.readlines()
res=[]
tmp=[]
flag=""
for line in lines:
if line=="\\begin{table}\n":
flag="BEGINTABLE"
elif line=="\\end{table}\n":
flag="ENDTABLE"
if flag=="BEGINTABLE":
tmp.append(line)
elif flag=="ENDTABLE":
tmp.append(line)
flag="CAPTION"
elif flag=="CAPTION":
tmp.append(line)
flag="MARKDOWN"
print('-' * 100)
print(''.join(tmp))
res.append(''.join(tmp))
tmp=[]
return res
file_path="YOUR_PDF_PATH"
output_dir="YOUR_OUTPUT_DIR_PATH"
if june_run_nougat(file_path, output_dir)==1:
import sys
sys.exit(1)
mmd_path=output_dir + '/' + os.path.splitext(file_path)[0].split('/')[-1] + ".mmd"
tables=june_get_tables_from_mmd(mmd_path)
該函數june_get_tables_from_mmd用于從圖 10 所示的文件中提取從 t\begin{table}到 的所有內容\end{table},包括 后面的行。\end{table}mmd
圖10:Nougat運行結果,結果文件為Mathpix Markdown格式(通過vscode插件打開),表格為latex格式。The function of june_get_tables_from_mmd is to extract the table information in the red box。圖片由作者提供。
值得注意的是,目前還沒有找到官方文檔規定表格標題必須放在表格下方或者表格以 開頭\begin{table}和結尾\end{table}。因此,june_get_tables_from_mmd是啟發式的。
以下是解析 PDF 中表格的結果:
Operation Completed!
----------------------------------------------------------------------------------------------------
\begin{table}
\begin{tabular}{l c c c} \hline \hline Layer Type & Complexity per Layer & Sequential Operations & Maximum Path Length \\ \hline Self-Attention & \(O(n^{2}\cdot d)\) & \(O(1)\) & \(O(1)\) \\ Recurrent & \(O(n\cdot d^{2})\) & \(O(n)\) & \(O(n)\) \\ Convolutional & \(O(k\cdot n\cdot d^{2})\) & \(O(1)\) & \(O(log_{k}(n))\) \\ Self-Attention (restricted) & \(O(r\cdot n\cdot d)\) & \(O(1)\) & \(O(n/r)\) \\ \hline \hline \end{tabular}
\end{table}
Table 1: Maximum path lengths, per-layer complexity and minimum number of sequential operations for different layer types. \(n\) is the sequence length, \(d\) is the representation dimension, \(k\) is the kernel size of convolutions and \(r\) the size of the neighborhood in restricted self-attention.
----------------------------------------------------------------------------------------------------
\begin{table}
\begin{tabular}{l c c c c} \hline \hline \multirow{2}{*}{Model} & \multicolumn{2}{c}{BLEU} & \multicolumn{2}{c}{Training Cost (FLOPs)} \\ \cline{2-5} & EN-DE & EN-FR & EN-DE & EN-FR \\ \hline ByteNet [18] & 23.75 & & & \\ Deep-Att + PosUnk [39] & & 39.2 & & \(1.0\cdot 10^{20}\) \\ GNMT + RL [38] & 24.6 & 39.92 & \(2.3\cdot 10^{19}\) & \(1.4\cdot 10^{20}\) \\ ConvS2S [9] & 25.16 & 40.46 & \(9.6\cdot 10^{18}\) & \(1.5\cdot 10^{20}\) \\ MoE [32] & 26.03 & 40.56 & \(2.0\cdot 10^{19}\) & \(1.2\cdot 10^{20}\) \\ \hline Deep-Att + PosUnk Ensemble [39] & & 40.4 & & \(8.0\cdot 10^{20}\) \\ GNMT + RL Ensemble [38] & 26.30 & 41.16 & \(1.8\cdot 10^{20}\) & \(1.1\cdot 10^{21}\) \\ ConvS2S Ensemble [9] & 26.36 & **41.29** & \(7.7\cdot 10^{19}\) & \(1.2\cdot 10^{21}\) \\ \hline Transformer (base model) & 27.3 & 38.1 & & \(\mathbf{3.3\cdot 10^{18}}\) \\ Transformer (big) & **28.4** & **41.8** & & \(2.3\cdot 10^{19}\) \\ \hline \hline \end{tabular}
\end{table}
Table 2: The Transformer achieves better BLEU scores than previous state-of-the-art models on the English-to-German and English-to-French newstest2014 tests at a fraction of the training cost.
----------------------------------------------------------------------------------------------------
\begin{table}
\begin{tabular}{c|c c c c c c c c|c c c c} \hline \hline & \(N\) & \(d_{\text{model}}\) & \(d_{\text{ff}}\) & \(h\) & \(d_{k}\) & \(d_{v}\) & \(P_{drop}\) & \(\epsilon_{ls}\) & train steps & PPL & BLEU & params \\ \hline base & 6 & 512 & 2048 & 8 & 64 & 64 & 0.1 & 0.1 & 100K & 4.92 & 25.8 & 65 \\ \hline \multirow{4}{*}{(A)} & \multicolumn{1}{c}{} & & 1 & 512 & 512 & & & & 5.29 & 24.9 & \\ & & & & 4 & 128 & 128 & & & & 5.00 & 25.5 & \\ & & & & 16 & 32 & 32 & & & & 4.91 & 25.8 & \\ & & & & 32 & 16 & 16 & & & & 5.01 & 25.4 & \\ \hline (B) & \multicolumn{1}{c}{} & & \multicolumn{1}{c}{} & & 16 & & & & & 5.16 & 25.1 & 58 \\ & & & & & 32 & & & & & 5.01 & 25.4 & 60 \\ \hline \multirow{4}{*}{(C)} & 2 & \multicolumn{1}{c}{} & & & & & & & & 6.11 & 23.7 & 36 \\ & 4 & & & & & & & & 5.19 & 25.3 & 50 \\ & 8 & & & & & & & & 4.88 & 25.5 & 80 \\ & & 256 & & 32 & 32 & & & & 5.75 & 24.5 & 28 \\ & 1024 & & 128 & 128 & & & & 4.66 & 26.0 & 168 \\ & & 1024 & & & & & & 5.12 & 25.4 & 53 \\ & & 4096 & & & & & & 4.75 & 26.2 & 90 \\ \hline \multirow{4}{*}{(D)} & \multicolumn{1}{c}{} & & & & & 0.0 & & 5.77 & 24.6 & \\ & & & & & & 0.2 & & 4.95 & 25.5 & \\ & & & & & & & 0.0 & 4.67 & 25.3 & \\ & & & & & & & 0.2 & 5.47 & 25.7 & \\ \hline (E) & \multicolumn{1}{c}{} & \multicolumn{1}{c}{} & & \multicolumn{1}{c}{} & & & & & 4.92 & 25.7 & \\ \hline big & 6 & 1024 & 4096 & 16 & & 0.3 & 300K & **4.33** & **26.4** & 213 \\ \hline \hline \end{tabular}
\end{table}
Table 3: Variations on the Transformer architecture. Unlisted values are identical to those of the base model. All metrics are on the English-to-German translation development set, newstest2013. Listed perplexities are per-wordpiece, according to our byte-pair encoding, and should not be compared to per-word perplexities.
----------------------------------------------------------------------------------------------------
\begin{table}
\begin{tabular}{c|c|c} \hline
**Parser** & **Training** & **WSJ 23 F1** \\ \hline Vinyals \& Kaiser et al. (2014) [37] & WSJ only, discriminative & 88.3 \\ Petrov et al. (2006) [29] & WSJ only, discriminative & 90.4 \\ Zhu et al. (2013) [40] & WSJ only, discriminative & 90.4 \\ Dyer et al. (2016) [8] & WSJ only, discriminative & 91.7 \\ \hline Transformer (4 layers) & WSJ only, discriminative & 91.3 \\ \hline Zhu et al. (2013) [40] & semi-supervised & 91.3 \\ Huang \& Harper (2009) [14] & semi-supervised & 91.3 \\ McClosky et al. (2006) [26] & semi-supervised & 92.1 \\ Vinyals \& Kaiser el al. (2014) [37] & semi-supervised & 92.1 \\ \hline Transformer (4 layers) & semi-supervised & 92.7 \\ \hline Luong et al. (2015) [23] & multi-task & 93.0 \\ Dyer et al. (2016) [8] & generative & 93.3 \\ \hline \end{tabular}
\end{table}
Table 4: The Transformer generalizes well to English constituency parsing (Results are on Section 23 of WSJ)* [5] Kyunghyun Cho, Bart van Merrienboer, Caglar Gulcehre, Fethi Bougares, Holger Schwenk, and Yoshua Bengio. Learning phrase representations using rnn encoder-decoder for statistical machine translation. _CoRR_, abs/1406.1078, 2014.
然后用LLM總結一下表格:
# Prompt
prompt_text="""You are an assistant tasked with summarizing tables and text. \
Give a concise summary of the table or text. The table is formatted in LaTeX, and its caption is in plain text format: {element} """
prompt=ChatPromptTemplate.from_template(prompt_text)
# Summary chain
model=ChatOpenAI(temperature=0, model="gpt-3.5-turbo")
summarize_chain={"element": lambda x: x} | prompt | model | StrOutputParser()
# Get table summaries
table_summaries=summarize_chain.batch(tables, {"max_concurrency": 5})
print(table_summaries)
以下是Attention Is All You Need中找到的四個表的摘要,如圖 11 所示:
圖 11: Attention Is All You Need中找到的四個表的表摘要。
使用Multi-Vector Retriever構建文檔摘要索引結構。
# The vectorstore to use to index the child chunks
vectorstore=Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
# The storage layer for the parent documents
store=InMemoryStore()
id_key="doc_id"
# The retriever (empty to start)
retriever=MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
search_kwargs={"k": 1} # Solving Number of requested results 4 is greater than number of elements in index..., updating n_results=1
)
# Add tables
table_ids=[str(uuid.uuid4()) for _ in tables]
summary_tables=[
Document(page_content=s, metadata={id_key: table_ids[i]})
for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, tables)))
一切準備就緒,構建一個簡單的RAG管道,并執行查詢:
# Prompt template
template="""Answer the question based only on the following context, which can include text and tables, there is a table in LaTeX format and a table caption in plain text format:
{context}
Question: {question}
"""
prompt=ChatPromptTemplate.from_template(template)
# LLM
model=ChatOpenAI(temperature=0, model="gpt-3.5-turbo")
# Simple RAG pipeline
chain=(
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
print(chain.invoke("when layer type is Self-Attention, what is the Complexity per Layer?")) # Query about table 1
print(chain.invoke("Which parser performs worst for BLEU EN-DE")) # Query about table 2
print(chain.invoke("Which parser performs best for WSJ 23 F1")) # Query about table 4
執行結果如下,說明幾個問題都得到了準確的解答,如圖12所示:
圖 12:三個查詢的結果。第一行對應Attention Is All You Need中的查詢表1 ,第二行對應表2,第三行對應表4。
整體代碼如下:
import os
os.environ["OPENAI_API_KEY"]="YOUR_OPEN_AI_KEY"
import subprocess
import uuid
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma
from langchain_core.documents import Document
from langchain_openai import OpenAIEmbeddings
from langchain_core.runnables import RunnablePassthrough
def june_run_nougat(file_path, output_dir):
# Run Nougat and store results as Mathpix Markdown
cmd=["nougat", file_path, "-o", output_dir, "-m", "0.1.0-base", "--no-skipping"]
res=subprocess.run(cmd)
if res.returncode !=0:
print("Error when running nougat.")
return res.returncode
else:
print("Operation Completed!")
return 0
def june_get_tables_from_mmd(mmd_path):
f=open(mmd_path)
lines=f.readlines()
res=[]
tmp=[]
flag=""
for line in lines:
if line=="\\begin{table}\n":
flag="BEGINTABLE"
elif line=="\\end{table}\n":
flag="ENDTABLE"
if flag=="BEGINTABLE":
tmp.append(line)
elif flag=="ENDTABLE":
tmp.append(line)
flag="CAPTION"
elif flag=="CAPTION":
tmp.append(line)
flag="MARKDOWN"
print('-' * 100)
print(''.join(tmp))
res.append(''.join(tmp))
tmp=[]
return res
file_path="YOUR_PDF_PATH"
output_dir="YOUR_OUTPUT_DIR_PATH"
if june_run_nougat(file_path, output_dir)==1:
import sys
sys.exit(1)
mmd_path=output_dir + '/' + os.path.splitext(file_path)[0].split('/')[-1] + ".mmd"
tables=june_get_tables_from_mmd(mmd_path)
# Prompt
prompt_text="""You are an assistant tasked with summarizing tables and text. \
Give a concise summary of the table or text. The table is formatted in LaTeX, and its caption is in plain text format: {element} """
prompt=ChatPromptTemplate.from_template(prompt_text)
# Summary chain
model=ChatOpenAI(temperature=0, model="gpt-3.5-turbo")
summarize_chain={"element": lambda x: x} | prompt | model | StrOutputParser()
# Get table summaries
table_summaries=summarize_chain.batch(tables, {"max_concurrency": 5})
print(table_summaries)
# The vectorstore to use to index the child chunks
vectorstore=Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
# The storage layer for the parent documents
store=InMemoryStore()
id_key="doc_id"
# The retriever (empty to start)
retriever=MultiVectorRetriever(
vectorstore=vectorstore,
docstore=store,
id_key=id_key,
search_kwargs={"k": 1} # Solving Number of requested results 4 is greater than number of elements in index..., updating n_results=1
)
# Add tables
table_ids=[str(uuid.uuid4()) for _ in tables]
summary_tables=[
Document(page_content=s, metadata={id_key: table_ids[i]})
for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, tables)))
# Prompt template
template="""Answer the question based only on the following context, which can include text and tables, there is a table in LaTeX format and a table caption in plain text format:
{context}
Question: {question}
"""
prompt=ChatPromptTemplate.from_template(template)
# LLM
model=ChatOpenAI(temperature=0, model="gpt-3.5-turbo")
# Simple RAG pipeline
chain=(
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
print(chain.invoke("when layer type is Self-Attention, what is the Complexity per Layer?")) # Query about table 1
print(chain.invoke("Which parser performs worst for BLEU EN-DE")) # Query about table 2
print(chain.invoke("Which parser performs best for WSJ 23 F1")) # Query about table 4
本文討論了RAG流程中表處理的關鍵技術和現有解決方案,并提出了解決方案及其實現。
我們在本文中使用 nougat 來解析表格。但是,如果有更快、更有效的解析工具可用,我們會考慮替換 nougat。我們對待工具的態度是先有正確的想法,然后找到工具來實現,而不是依賴于某種工具。
在本文中,我們將所有表格內容輸入到LLM中。然而,在實際場景中,我們應該考慮表超出LLM上下文長度的情況。我們可以通過使用有效的分塊方法來解決這個問題。
人負責的項目主要采用阿里云數據庫 MySQL,最近頻繁出現慢 SQL 告警,執行時間最長的竟然高達 5 分鐘。
圖片來自 Pexels
導出日志后分析,主要原因竟然是沒有命中索引和沒有分頁處理。其實這是非常低級的錯誤,我不禁后背一涼,團隊成員的技術水平亟待提高啊。
改造這些 SQL 的過程中,總結了一些經驗分享給大家,如果有錯誤歡迎批評指正。
MySQL 性能
①最大數據量
拋開數據量和并發數,談性能都是耍流氓。MySQL 沒有限制單表最大記錄數,它取決于操作系統對文件大小的限制。
《阿里巴巴 Java 開發手冊》提出單表行數超過 500 萬行或者單表容量超過 2GB,才推薦分庫分表。
性能由綜合因素決定,拋開業務復雜度,影響程度依次是硬件配置、MySQL 配置、數據表設計、索引優化。500 萬這個值僅供參考,并非鐵律。
我曾經操作過超過 4 億行數據的單表,分頁查詢最新的 20 條記錄耗時 0.6 秒,SQL 語句大致是:
select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20
prePageMinId 是上一頁數據記錄的最小 ID。雖然當時查詢速度還湊合,隨著數據不斷增長,有朝一日必定不堪重負。
分庫分表是個周期長而風險高的大活兒,應該盡可能在當前結構上優化,比如升級硬件、遷移歷史數據等等,實在沒轍了再分。對分庫分表感興趣的同學可以閱讀分庫分表的基本思想。
②最大并發數
并發數是指同一時刻數據庫能處理多少個請求,由 max_connections 和 max_user_connections 決定。
max_connections 是指 MySQL 實例的最大連接數,上限值是 16384,max_user_connections 是指每個數據庫用戶的最大連接數。
MySQL 會為每個連接提供緩沖區,意味著消耗更多的內存。如果連接數設置太高硬件吃不消,太低又不能充分利用硬件。
一般要求兩者比值超過 10%,計算方法如下:
max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3%
查看最大連接數與響應最大連接數:
show variables like '%max_connections%';
show variables like '%max_user_connections%';
在配置文件 my.cnf 中修改最大連接數:
[mysqld]
max_connections=100
max_used_connections=20
③查詢耗時 0.5 秒
建議將單次查詢耗時控制在 0.5 秒以內,0.5 秒是個經驗值,源于用戶體驗的 3 秒原則。如果用戶的操作 3 秒內沒有響應,將會厭煩甚至退出。
響應時間=客戶端 UI 渲染耗時+網絡請求耗時+應用程序處理耗時+查詢數據庫耗時,0.5 秒就是留給數據庫 1/6 的處理時間。
④實施原則
相比 NoSQL 數據庫,MySQL 是個嬌氣脆弱的家伙。它就像體育課上的女同學,一點糾紛就和同學鬧別扭(擴容難),跑兩步就氣喘吁吁(容量小并發低),常常身體不適要請假(SQL 約束太多)。
如今大家都會搞點分布式,應用程序擴容比數據庫要容易得多,所以實施原則是數據庫少干活,應用程序多干活:
數據表設計
①數據類型
數據類型的選擇原則,更簡單或者占用空間更小:
相比 datetime,timestamp 占用更少的空間,以 UTC 的格式儲存自動轉換時區。
②避免空值
MySQL 中字段為 NULL 時依然占用空間,會使索引、索引統計更加復雜。從 NULL 值更新到非 NULL 無法做到原地更新,容易發生索引分裂影響性能。
因此盡可能將 NULL 值用有意義的值代替,也能避免 SQL 語句里面包含 is not null 的判斷。
③Text 類型優化
由于 Text 字段儲存大量數據,表容量會很早漲上去,影響其他字段的查詢性能。建議抽取出來放在子表里,用業務主鍵關聯。
索引優化
索引分類如下:
索引優化原則:
select login_name, nick_name from member where login_name = ?
login_name, nick_name 兩個字段建立組合索引,比 login_name 簡單索引要更快。
SQL 優化
①分批處理
博主小時候看到魚塘挖開小口子放水,水面有各種漂浮物。浮萍和樹葉總能順利通過出水口,而樹枝會擋住其他物體通過,有時還會卡住,需要人工清理。
MySQL 就是魚塘,最大并發數和網絡帶寬就是出水口,用戶 SQL 就是漂浮物。
不帶分頁參數的查詢或者影響大量數據的 update 和 delete 操作,都是樹枝,我們要把它打散分批處理,下面舉例說明。
業務描述:更新用戶所有已過期的優惠券為不可用狀態。
SQL 語句:
update status=0 FROM `coupon` WHERE expire_date <= #{currentDate} and status=1;
如果大量優惠券需要更新為不可用狀態,執行這條 SQL 可能會堵死其他 SQL,分批處理偽代碼如下:
int pageNo=1;
int PAGE_SIZE=100;
while(true) {
List<Integer> batchIdList=queryList('select id FROM `coupon` WHERE expire_date <=#{currentDate} and status=1 limit #{(pageNo-1) * PAGE_SIZE},#{PAGE_SIZE}');
if (CollectionUtils.isEmpty(batchIdList)) {
return;
}
update('update status=0 FROM `coupon` where status=1 and id in #{batchIdList}')
pageNo ++;
}
②操作符 <> 優化
通常 <> 操作符無法使用索引,舉例如下,查詢金額不為 100 元的訂單:
select id from orders where amount != 100;
如果金額為 100 的訂單極少,這種數據分布嚴重不均的情況下,有可能使用索引。
鑒于這種不確定性,采用 union 聚合搜索結果,改寫方法如下:
(select id from orders where amount > 100)
union all
(select id from orders where amount < 100 and amount > 0)
③OR 優化
在 Innodb 引擎下 OR 無法使用組合索引,比如:
select id,product_name from orders where mobile_no = '13421800407' or user_id = 100;
OR 無法命中 mobile_no + user_id 的組合索引,可采用 union,如下所示:
(select id,product_name from orders where mobile_no='13421800407')
union
(select id,product_name from orders where user_id=100);
此時 id 和 product_name 字段都有索引,查詢才最高效。
④IN 優化
IN 適合主表大子表小,EXIST 適合主表小子表大。由于查詢優化器的不斷升級,很多場景這兩者性能差不多一樣了。
嘗試改為 Join 查詢,舉例如下:
select id from orders where user_id in (select id from user where level = 'VIP');
采用 Join 如下所示:
select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';
⑤不做列運算
通常在查詢條件列運算會導致索引失效,如下所示,查詢當日訂單:
select id from order where date_format(create_time,'%Y-%m-%d') = '2019-07-01';
date_format 函數會導致這個查詢無法使用索引,改寫后:
select id from order where create_time between '2019-07-01 00:00:00' and '2019-07-01 23:59:59';
⑥避免Select All
如果不查詢表中所有的列,避免使用 SELECT *,它會進行全表掃描,不能有效利用索引。
⑦Like 優化
Like 用于模糊查詢,舉個例子(field 已建立索引):
SELECT column FROM table WHERE field like '%keyword%';
這個查詢未命中索引,換成下面的寫法:
SELECT column FROM table WHERE field like 'keyword%';
去除了前面的 % 查詢將會命中索引,但是產品經理一定要前后模糊匹配呢?全文索引 fulltext 可以嘗試一下,但 Elasticsearch 才是終極武器。
⑧Join 優化
Join 的實現是采用 Nested Loop Join 算法,就是通過驅動表的結果集作為基礎數據,通過該結數據作為過濾條件到下一個表中循環查詢數據,然后合并結果。
如果有多個 Join,則將前面的結果集作為循環數據,再次到后一個表中查詢數據。
驅動表和被驅動表盡可能增加查詢條件,滿足 ON 的條件而少用 Where,用小結果集驅動大結果集。
被驅動表的 Join 字段上加上索引,無法建立索引的時候,設置足夠的 Join Buffer Size。
禁止 Join 連接三個以上的表,嘗試增加冗余字段。
⑨Limit 優化
Limit 用于分頁查詢時越往后翻性能越差,解決的原則:縮小掃描范圍,如下所示:
select * from orders order by id desc limit 100000,10
耗時0.4秒
select * from orders order by id desc limit 1000000,10
耗時5.2秒
先篩選出 ID 縮小查詢范圍,寫法如下:
select * from orders where id > (select id from orders order by id desc limit 1000000, 1) order by id desc limit 0,10
耗時0.5秒
如果查詢條件僅有主鍵 ID,寫法如下:
select id from orders where id between 1000000 and 1000010 order by id desc
耗時0.3秒
如果以上方案依然很慢呢?只好用游標了,感興趣的朋友閱讀 JDBC 使用游標實現分頁查詢的方法。
其他數據庫
作為一名后端開發人員,務必精通作為存儲核心的 MySQL 或 SQL Server,也要積極關注 NoSQL 數據庫,他們已經足夠成熟并被廣泛采用,能解決特定場景下的性能瓶頸。
作者:編碼磚家
編輯:陶家龍
出處:www.cnblogs.com/xiaoyangjia/p/11267191.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。