Ext-Abs-StructuredSum / utils /preprocess.py
fuhsiao's picture
update
dfcc660
from sklearn.feature_extraction.text import TfidfVectorizer
from sentence_transformers import SentenceTransformer, util
import pandas as pd
import numpy as np
import gradio as gr
import torch
import spacy
import re
nlp = spacy.load("en_core_sci_sm")
# ----------------------------------------------
# Step 1. 讀取檔案 轉換 句子單位 JSON
# ----------------------------------------------
def read_text_to_json(path):
paper = {}
with open(path, 'r', encoding='utf-8') as txt:
key = None
for line in txt:
line = line.strip()
if line.startswith('@Paper') or line.startswith('@Section'):
key = line.split()[1]
paper[key] = []
elif key and line:
paper[key].append(line)
return paper
def is_valid_format(paper):
for key in ['title', 'I', 'M', 'R', 'D']:
if key not in paper or len(paper[key])==0:
return False
return True
def remove_parentheses_with_useless_tokens(text):
return re.sub(r'\s*\(\s*(?:table|fig|http|www)[^()]*\)', '', text, flags = re.I) # re.I 不區分大小寫
def segment_sentences(section, pos_para = False):
sents = []
sents_break = [".", "?", "!"]
start = para_i = pre_para_i = 0
conn = False
for para in section:
para = remove_parentheses_with_useless_tokens(para).strip() # 避免末端空白判斷為 token 而無法 sents_break
doc = nlp(para)
for sent in doc.sents:
if any(t in sents_break for t in sent[-1].text): # 部分句尾詞如 3h. 無法分詞, 因此包含 sents_break 即可
para_i +=1
text = "".join(t.text_with_ws for t in doc[start:sent.end]) # 原始字串
tokenize_text = " ".join(t.text for t in doc[start:sent.end]) # 分詞字串
sentence = {"text":text, "tokenize_text":tokenize_text, "pos":pre_para_i+para_i} # 建立句子物件
if pos_para: sentence['pos_para'] = para_i # pos 句子位置, pos_para 句子於每段位置
sents.append(sentence)
start = sent.end
conn = False
else:
start = start if conn else sent.start # sent.end 非斷句字符 紀錄此句 start, 直到斷句前不更改 start 位置
conn = True
pre_para_i += para_i
start = para_i = 0
return sents
def convert_to_sentence_json(paper):
sentJson = {
'title': paper['title'],
'body': {}
}
for key in ['I', 'M', 'R', 'D']:
sentJson['body'][key] = segment_sentences(paper[key], True)
return sentJson
# ----------------------------------------------
# Step 2. 句子單位 進行 特徵萃取
# ----------------------------------------------
# 句子列表
def sent_lst(sents):
return [sent['text'] for sent in sents]
# 移除停用詞及標點
def clean_token(doc):
return [token for token in doc if not (token.is_stop or token.is_punct)]
# 段落之總句數
def add_num_sents_para(sents):
reset = True
for index, sent in reversed(list(enumerate(sents))):
if reset: ptr = sent['pos_para']
reset = True if sent['pos_para'] == 1 else False
sents[index]['ns_para'] = ptr
return sents
# 位置重要性
def position_imp(cur, ns):
imp = 1 if cur == 1 else (ns-cur)/ns
return imp
# 標題詞列表
def title_wlst(txt):
doc = nlp(txt)
wlst = [token.text.lower() for token in clean_token(doc)]
return list(set(wlst))
# 句子之標題詞數量
def title_word_count(doc, wlst):
titleLen = len(wlst)
score = 0 if titleLen == 0 else len([token for token in doc if token.text.lower() in wlst])/titleLen
return score
# 標記詞性之數量
def pos_token(doc, pos_type):
return len([token for token in doc if token.pos_ == pos_type])
# 自定分詞器
def custom_toknizer(txt):
doc = nlp(txt)
words = [token.lemma_.lower() for token in doc if not (token.is_stop or token.is_punct or token.is_digit)]
return words
# 詞頻-逆向句子頻率
def Tfisf(lst):
tf = TfidfVectorizer(tokenizer=custom_toknizer, lowercase=False)
tfisf_matrix = tf.fit_transform(lst)
word_count = (tfisf_matrix!=0).sum(1)
with np.errstate(divide='ignore', invalid='ignore'):
mean_score = np.where(word_count == 0, 0, np.divide(tfisf_matrix.sum(1), word_count)).flatten()
return mean_score
# 餘弦相似度
def similarity(lst, ptm):
model = SentenceTransformer(ptm)
embeddings = model.encode(lst, convert_to_tensor=True)
cosine = util.cos_sim(embeddings, embeddings)
cosine = cosine.sum(1)-1
cosine = torch.divide(cosine, torch.max(cosine)).numpy() # .cpu().numpy()
return cosine
# 特徵萃取
def feature_extraction(title, section, sents):
lst = sent_lst(sents)
tfisf = Tfisf(lst)
cosine = similarity(lst, "sentence-transformers/all-MiniLM-L6-v2")
# Number of sentences
ns = len(sents)
sents = add_num_sents_para(sents)
# Extracting the features of each sentences
arr = np.empty((0,9))
for index, sent in enumerate(sents):
doc = nlp(sent["text"])
doc = clean_token(doc)
F1 = len(doc) # Sentence Length (undone) -> len / longest sentence len
F2 = position_imp(sent["pos"], ns) # Sentence Position
F3 = position_imp(sent["pos_para"], sent["ns_para"]) # Sentence Position (in paragraph)
F4 = title_word_count(doc, title) # Title Word
F5 = 0 if F1 == 0 else pos_token(doc, "PROPN")/F1 # Proper Noun
F6 = 0 if F1 == 0 else pos_token(doc, "NUM")/F1 # Numerical Token
F7 = tfisf[index] # Term Frequency-Inverse Sentence Frequency
F10 = cosine[index] # Cosine Similarity
feat = np.array([[section, F1, F2, F3, F4, F5, F6, F7, F10]])
arr = np.append(arr, feat, axis=0)
# F1 (done)
maxLen = np.amax(arr[:,1])
arr[:,1] = arr[:,1]/maxLen
return arr
# 設置欄位類型
def set_dtypes(df):
df = df.astype({'section': 'int8', 'F1': 'float32', 'F2': 'float32',
'F3': 'float32', 'F4': 'float32', 'F5': 'float32',
'F6': 'float32', 'F7': 'float32', 'F10': 'float32'})
return df
# 文章 IMRD - 句子特徵
def feature_from_imrd(body, title):
paper = np.empty((0,9))
for index, key in enumerate(['I', 'M', 'R', 'D'], start = 1):
paper = np.append(paper, feature_extraction(title, index, body[key]), axis = 0)
df = pd.DataFrame(paper, columns = ['section','F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F10'])
return set_dtypes(df)
def extract_sentence_features(sentJson):
title = title_wlst(sentJson['title'][0])
sentFeat = feature_from_imrd(sentJson['body'], title)
return sentFeat