|
''' |
|
from https://github.com/amirveyseh/MadDog under CC BY-NC-SA 4.0 |
|
''' |
|
|
|
import string |
|
|
|
if __name__ != "__main__": |
|
import spacy.cli |
|
|
|
spacy.cli.download("en_core_web_sm") |
|
|
|
import spacy |
|
|
|
nlp = spacy.load("en_core_web_sm") |
|
|
|
with open('stopWords.txt') as file: |
|
stop_words = [l.strip() for l in file.readlines()] |
|
|
|
|
|
class Extractor: |
|
def __init__(self): |
|
pass |
|
|
|
def short_extract(self, sentence, threshold, starting_lower_case, ignore_dot=False): |
|
shorts = [] |
|
for i, t in enumerate(sentence): |
|
if ignore_dot: |
|
t = t.replace('.', '') |
|
|
|
if len(t) == 0: |
|
continue |
|
|
|
if not starting_lower_case: |
|
if t[0].isupper() and len([c for c in t if c.isupper()]) / len(t) > threshold and 2 <= len(t) <= 10: |
|
shorts.append(i) |
|
else: |
|
if len([c for c in t if c.isupper()]) / len(t) > threshold and 2 <= len(t) <= 10: |
|
shorts.append(i) |
|
return shorts |
|
|
|
def extract_cand_long(self, sentence, token, ind, ignore_punc=False, add_punc=False, small_window=False): |
|
''' |
|
extract candidate long form of the form "long form (short form)" or "short form (long form)" |
|
|
|
:param sentence: tokenized sentence |
|
:param token: acronym |
|
:param ind: position of the acronym |
|
:return: candidate long form, candidate is on left or right of the short form |
|
''' |
|
if not small_window: |
|
long_cand_length = min([len(token) + 10, len(token) * 3]) |
|
else: |
|
long_cand_length = min([len(token) + 5, len(token) * 2]) |
|
cand_long = [] |
|
cand_long_index = [] |
|
left = True |
|
right_ind = 1 |
|
left_ind = 1 |
|
|
|
if add_punc: |
|
excluded_puncs = ['=', ':'] |
|
else: |
|
excluded_puncs = [] |
|
|
|
if ignore_punc: |
|
while ind + right_ind < len(sentence) and sentence[ind + right_ind] in [p for p in string.punctuation if |
|
p != '(' and p != ')' and p not in excluded_puncs]: |
|
right_ind += 1 |
|
while ind - left_ind > 0 and sentence[ind - left_ind] in [p for p in string.punctuation if |
|
p != '(' and p != ')' and p not in excluded_puncs]: |
|
left_ind -= 1 |
|
|
|
if ind < len(sentence) - 2 - right_ind and ( |
|
sentence[ind + right_ind] == '(' or sentence[ind + right_ind] == '=' or sentence[ |
|
ind + right_ind] in excluded_puncs): |
|
left = False |
|
for j in range(ind + right_ind + 1, min([ind + right_ind + 1 + long_cand_length, len(sentence)])): |
|
if sentence[j] != ')': |
|
cand_long.append(sentence[j]) |
|
cand_long_index.append(j) |
|
else: |
|
break |
|
elif 1 < ind - (left_ind - 1) and ind + right_ind < len(sentence) and ( |
|
(sentence[ind - left_ind] == '(' and sentence[ind + right_ind] == ')') or sentence[ |
|
ind - left_ind] in excluded_puncs): |
|
for k in range(0, long_cand_length): |
|
j = ind - left_ind - 1 - k |
|
if j > -1: |
|
cand_long.insert(0, sentence[j]) |
|
cand_long_index.insert(0, j) |
|
return cand_long, cand_long_index, left |
|
|
|
|
|
def extract_high_recall_cand_long(self, sentence, token, ind, small_window=False, left=False): |
|
''' |
|
Find the candidate long form for a give acronym for high recall extraction |
|
example: The Stopping Trained in America PhDs from Leaving the Economy Act ( or STAPLE Act ) has bee introduced |
|
|
|
:param sentence: |
|
:param token: |
|
:param ind: |
|
:param small_window: |
|
:return: |
|
''' |
|
long_cand_length = min([len(token) + 10, len(token) * 3]) |
|
cand_long = [] |
|
cand_long_index = [] |
|
if not left: |
|
for j in range(ind + 1, min([ind + long_cand_length, len(sentence)])): |
|
cand_long.append(sentence[j]) |
|
cand_long_index.append(j) |
|
else: |
|
for k in range(0, long_cand_length): |
|
j = ind - 1 - k |
|
if j > -1: |
|
cand_long.insert(0, sentence[j]) |
|
cand_long_index.insert(0, j) |
|
return cand_long, cand_long_index, left |
|
|
|
def create_diction(self, sentence, labels, all_acronyms=True, tag='', map_chars=False, diction={}): |
|
''' |
|
convert sequential labels into {short-form: long-form} dictionary |
|
|
|
:param sentence: tokenized sentence |
|
:param labels: labels of form B-short, B-long, I-short, I-long, O |
|
:return: dictionary |
|
''' |
|
shorts = [] |
|
longs = [] |
|
isShort = True |
|
phr = [] |
|
for i in range(len(sentence)): |
|
if labels[i] == 'O' or (isShort and 'long' in labels[i]) or (not isShort and 'short' in labels[i]) or ( |
|
labels[i].startswith('B')): |
|
if len(phr): |
|
if isShort: |
|
shorts.append((phr[0], phr[-1])) |
|
else: |
|
longs.append((phr[0], phr[-1])) |
|
phr = [] |
|
if 'short' in labels[i]: |
|
isShort = True |
|
phr.append(i) |
|
if 'long' in labels[i]: |
|
isShort = False |
|
phr.append(i) |
|
if len(phr): |
|
if isShort: |
|
shorts.append((phr[0], phr[-1])) |
|
else: |
|
longs.append((phr[0], phr[-1])) |
|
acr_long = {} |
|
for long in longs: |
|
best_short = [] |
|
|
|
if long in diction and diction[long] in shorts: |
|
best_short = diction[long] |
|
best_dist = float('inf') |
|
|
|
|
|
if not best_short: |
|
best_short_cands = [] |
|
for short in shorts: |
|
long_form = self.character_match(sentence[short[0]], sentence[long[0]:long[1] + 1], |
|
list(range(long[1] + 1 - long[0])), output_string=True, |
|
is_candidate=False) |
|
if long_form: |
|
best_short_cands.append(short) |
|
if len(best_short_cands) == 1: |
|
best_short = best_short_cands[0] |
|
|
|
|
|
if not best_short and map_chars: |
|
best_short_cands = [] |
|
for short in shorts: |
|
long_form = self.map_chars(sentence[short[0]], sentence[long[0]:long[1] + 1]) |
|
if long_form: |
|
best_short_cands.append(short) |
|
if len(best_short_cands) == 1: |
|
best_short = best_short_cands[0] |
|
|
|
|
|
if not best_short: |
|
best_short_cands = [] |
|
for short in shorts: |
|
is_mapped = self.map_chars_with_capitals(sentence[short[0]], sentence[long[0]:long[1] + 1]) |
|
if is_mapped: |
|
best_short_cands.append(short) |
|
if len(best_short_cands) == 1: |
|
best_short = best_short_cands[0] |
|
|
|
|
|
if not best_short and long[1] < len(sentence) - 2 and sentence[long[1] + 1] == '(' and 'short' in labels[ |
|
long[1] + 2]: |
|
for short in shorts: |
|
if short[0] == long[1] + 2: |
|
best_short = short |
|
break |
|
if not best_short and long[0] > 1 and sentence[long[0] - 1] == '(' and 'short' in labels[long[0] - 2]: |
|
for short in shorts: |
|
if short[1] == long[0] - 2: |
|
best_short = short |
|
break |
|
|
|
if not best_short: |
|
for short in shorts: |
|
if short[0] > long[1]: |
|
dist = short[0] - long[1] |
|
else: |
|
dist = long[0] - short[1] |
|
if dist < best_dist: |
|
best_dist = dist |
|
best_short = short |
|
if best_short: |
|
short_form_info = ' '.join(sentence[best_short[0]:best_short[1] + 1]) |
|
long_form_info = [' '.join(sentence[long[0]:long[1] + 1]), best_short, [long[0], long[1]], tag, 1] |
|
if short_form_info in acr_long: |
|
long_form_info[4] += 1 |
|
acr_long[short_form_info] = long_form_info |
|
if all_acronyms: |
|
for short in shorts: |
|
acr = ' '.join(sentence[short[0]:short[1] + 1]) |
|
if acr not in acr_long: |
|
acr_long[acr] = ['', short, [], tag, 1] |
|
return acr_long |
|
|
|
|
|
def map_chars(self, acronym, long): |
|
''' |
|
This function evaluate the long for based on number of initials overlapping with the acronym and if it is above a threshold it assigns the long form the the acronym |
|
|
|
:param acronym: |
|
:param long: |
|
:return: |
|
''' |
|
capitals = [] |
|
for c in acronym: |
|
if c.isupper(): |
|
capitals.append(c.lower()) |
|
initials = [w[0].lower() for w in long] |
|
ratio = len([c for c in initials if c in capitals]) / len(initials) |
|
if ratio >= 0.6: |
|
return long |
|
else: |
|
return None |
|
|
|
|
|
def map_chars_with_capitals(self, acronym, long): |
|
''' |
|
This function maps the acronym to the long-form which has the same initial capitals as the acronym |
|
|
|
:param acronym: |
|
:param long: |
|
:return: |
|
''' |
|
capitals = [] |
|
for c in acronym: |
|
if c.isupper(): |
|
capitals.append(c.lower()) |
|
long_capital_initials = [] |
|
for w in long: |
|
if w[0].isupper(): |
|
long_capital_initials.append(w[0].lower()) |
|
if len(capitals) == len(long_capital_initials) and all( |
|
capitals[i] == long_capital_initials[i] for i in range(len(capitals))): |
|
return True |
|
else: |
|
return False |
|
|
|
def schwartz_extract(self, sentence, shorts, remove_parentheses, ignore_hyphen=False, ignore_punc=False, |
|
add_punc=False, small_window=False, no_stop_words=False, ignore_righthand=False, |
|
map_chars=False,default_diction=False): |
|
labels = ['O'] * len(sentence) |
|
diction = {} |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
|
|
if ignore_hyphen: |
|
t = t.replace('-', '') |
|
|
|
cand_long, cand_long_index, left = self.extract_cand_long(sentence, t, i, ignore_punc=ignore_punc, |
|
add_punc=add_punc, small_window=small_window) |
|
cand_long = ' '.join(cand_long) |
|
long_form = "" |
|
|
|
if len(cand_long) > 0: |
|
if left: |
|
sIndex = len(t) - 1 |
|
lIndex = len(cand_long) - 1 |
|
while sIndex >= 0: |
|
curChar = t[sIndex].lower() |
|
if curChar.isdigit() or curChar.isalpha(): |
|
while (lIndex >= 0 and cand_long[lIndex].lower() != curChar) or ( |
|
sIndex == 0 and lIndex > 0 and ( |
|
cand_long[lIndex - 1].isdigit() or cand_long[lIndex - 1].isalpha())): |
|
lIndex -= 1 |
|
if lIndex < 0: |
|
break |
|
lIndex -= 1 |
|
sIndex -= 1 |
|
if lIndex >= -1: |
|
try: |
|
lIndex = cand_long.rindex(" ", 0, lIndex + 1) + 1 |
|
except: |
|
lIndex = 0 |
|
if cand_long: |
|
cand_long = cand_long[lIndex:] |
|
long_form = cand_long |
|
else: |
|
sIndex = 0 |
|
lIndex = 0 |
|
if t[0].lower() == cand_long[0].lower() or ignore_righthand: |
|
while sIndex < len(t): |
|
curChar = t[sIndex].lower() |
|
if curChar.isdigit() or curChar.isalpha(): |
|
while (lIndex < len(cand_long) and cand_long[lIndex].lower() != curChar) or ( |
|
ignore_righthand and (sIndex == 0 and lIndex > 0 and ( |
|
cand_long[lIndex - 1].isdigit() or cand_long[lIndex - 1].isalpha()))) or ( |
|
lIndex != 0 and cand_long[lIndex - 1] != ' ' and ' ' in cand_long[ |
|
lIndex:] and |
|
cand_long[cand_long[lIndex:].index(' ') + lIndex + 1].lower() == curChar): |
|
lIndex += 1 |
|
if lIndex >= len(cand_long): |
|
break |
|
if lIndex >= len(cand_long): |
|
break |
|
lIndex += 1 |
|
sIndex += 1 |
|
if lIndex < len(cand_long): |
|
try: |
|
lIndex = cand_long[lIndex:].index(" ") + lIndex + 1 |
|
except: |
|
lIndex = len(cand_long) |
|
if cand_long: |
|
cand_long = cand_long[:lIndex] |
|
long_form = cand_long |
|
|
|
if remove_parentheses: |
|
if '(' in long_form or ')' in long_form: |
|
long_form = '' |
|
|
|
long_form = long_form.split() |
|
if no_stop_words and long_form: |
|
if long_form[0].lower() in stop_words: |
|
long_form = [] |
|
if long_form: |
|
if left: |
|
long_form_index = cand_long_index[-len(long_form):] |
|
else: |
|
long_form_index = cand_long_index[:len(long_form)] |
|
first = True |
|
for j in range(len(sentence)): |
|
if j in long_form_index: |
|
if first: |
|
labels[j] = 'B-long' |
|
first = False |
|
else: |
|
labels[j] = 'I-long' |
|
if default_diction: |
|
diction[(long_form_index[0], long_form_index[-1])] = (i, i) |
|
return self.create_diction(sentence, labels, tag='Schwartz', map_chars=map_chars, diction=diction) |
|
|
|
def bounded_schwartz_extract(self, sentence, shorts, remove_parentheses, ignore_hyphen=False, ignore_punc=False, |
|
add_punc=False, small_window=False, no_stop_words=False, ignore_righthand=False, |
|
map_chars=False, high_recall=False, high_recall_left=False, tag='Bounded Schwartz',default_diction=False): |
|
''' |
|
This function uses the same rule as schwartz but for the format "long form (short form)" will select long forms that the last word in the long form is selected to form the acronym |
|
example: User - guided Social Media Crawling method ( USMC ) that |
|
|
|
:param remove_parentheses: |
|
:param sentence: |
|
:param shorts: |
|
:return: |
|
''' |
|
labels = ['O'] * len(sentence) |
|
diction = {} |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
|
|
if ignore_hyphen: |
|
t = t.replace('-', '') |
|
|
|
if high_recall: |
|
cand_long, cand_long_index, left = self.extract_high_recall_cand_long(sentence, t, i, |
|
small_window=small_window, |
|
left=high_recall_left) |
|
else: |
|
|
|
cand_long, cand_long_index, left = self.extract_cand_long(sentence, t, i, ignore_punc=ignore_punc, |
|
add_punc=add_punc, |
|
small_window=small_window) |
|
cand_long = ' '.join(cand_long) |
|
long_form = "" |
|
|
|
if len(cand_long) > 0: |
|
if left: |
|
sIndex = len(t) - 1 |
|
lIndex = len(cand_long) - 1 |
|
first_ind = len(cand_long) |
|
while sIndex >= 0: |
|
curChar = t[sIndex].lower() |
|
if curChar.isdigit() or curChar.isalpha(): |
|
while (lIndex >= 0 and cand_long[lIndex].lower() != curChar) or ( |
|
sIndex == 0 and lIndex > 0 and ( |
|
cand_long[lIndex - 1].isdigit() or cand_long[lIndex - 1].isalpha())): |
|
lIndex -= 1 |
|
if first_ind == len(cand_long): |
|
first_ind = lIndex |
|
if lIndex < 0: |
|
break |
|
lIndex -= 1 |
|
sIndex -= 1 |
|
if lIndex >= 0 or lIndex == -1 and cand_long[0].lower() == t[0].lower(): |
|
try: |
|
lIndex = cand_long.rindex(" ", 0, lIndex + 1) + 1 |
|
try: |
|
rIndex = cand_long[first_ind:].index(" ") + first_ind |
|
except: |
|
rIndex = len(cand_long) |
|
except: |
|
lIndex = 0 |
|
try: |
|
rIndex = cand_long[first_ind:].index(" ") + first_ind |
|
except: |
|
rIndex = len(cand_long) |
|
if cand_long: |
|
index_map = {} |
|
word_ind = 0 |
|
for ind, c in enumerate(cand_long): |
|
if c == ' ': |
|
word_ind += 1 |
|
index_map[ind] = word_ind |
|
last_word_index = index_map[rIndex - 1] |
|
cand_long = cand_long[lIndex:rIndex] |
|
long_form = cand_long |
|
else: |
|
sIndex = 0 |
|
lIndex = 0 |
|
first_ind = -1 |
|
if t[0].lower() == cand_long[0].lower() or ignore_righthand: |
|
while sIndex < len(t): |
|
curChar = t[sIndex].lower() |
|
if curChar.isdigit() or curChar.isalpha(): |
|
while (lIndex < len(cand_long) and cand_long[lIndex].lower() != curChar) or ( |
|
ignore_righthand and (sIndex == 0 and lIndex > 0 and ( |
|
cand_long[lIndex - 1].isdigit() or cand_long[lIndex - 1].isalpha()))) or ( |
|
lIndex != 0 and cand_long[lIndex - 1] != ' ' and ' ' in cand_long[ |
|
lIndex:] and |
|
cand_long[cand_long[lIndex:].index(' ') + lIndex + 1].lower() == curChar): |
|
lIndex += 1 |
|
if lIndex >= len(cand_long): |
|
break |
|
if first_ind == -1: |
|
first_ind = lIndex |
|
if lIndex >= len(cand_long): |
|
break |
|
lIndex += 1 |
|
sIndex += 1 |
|
if lIndex < len(cand_long) or ( |
|
first_ind < len(cand_long) and lIndex == len(cand_long) and cand_long[-1] == t[-1]): |
|
try: |
|
lIndex = cand_long[lIndex:].index(" ") + lIndex + 1 |
|
except: |
|
lIndex = len(cand_long) |
|
if cand_long: |
|
if not ignore_righthand: |
|
first_ind = 0 |
|
index_map = {} |
|
word_ind = 0 |
|
for ind, c in enumerate(cand_long): |
|
if c == ' ': |
|
word_ind += 1 |
|
index_map[ind] = word_ind |
|
first_word_index = index_map[first_ind] |
|
cand_long = cand_long[first_ind:lIndex] |
|
long_form = cand_long |
|
|
|
if remove_parentheses: |
|
if '(' in long_form or ')' in long_form: |
|
long_form = '' |
|
|
|
long_form = long_form.split() |
|
if no_stop_words and long_form: |
|
if long_form[0].lower() in stop_words: |
|
long_form = [] |
|
if long_form: |
|
if left: |
|
long_form_index = cand_long_index[last_word_index - len(long_form) + 1:last_word_index + 1] |
|
else: |
|
long_form_index = cand_long_index[first_word_index:first_word_index + len(long_form)] |
|
first = True |
|
for j in range(len(sentence)): |
|
if j in long_form_index: |
|
if first: |
|
labels[j] = 'B-long' |
|
first = False |
|
else: |
|
labels[j] = 'I-long' |
|
if default_diction: |
|
diction[(long_form_index[0],long_form_index[-1])] = (i,i) |
|
return self.create_diction(sentence, labels, tag=tag, map_chars=map_chars,diction=diction) |
|
|
|
|
|
def high_recall_schwartz(self, sentence, shorts, remove_parentheses, ignore_hyphen=False, ignore_punc=False, |
|
add_punc=False, small_window=False, no_stop_words=False, ignore_righthand=False, |
|
map_chars=False): |
|
''' |
|
This function use bounded schwartz rules for acronyms which are not necessarily in parentheses |
|
example: The Stopping Trained in America PhDs from Leaving the Economy Act ( or STAPLE Act ) has bee introduced |
|
|
|
:param sentence: |
|
:param shorts: |
|
:param remove_parentheses: |
|
:param ignore_hyphen: |
|
:param ignore_punc: |
|
:param add_punc: |
|
:param small_window: |
|
:param no_stop_words: |
|
:param ignore_righthand: |
|
:param map_chars: |
|
:return: |
|
''' |
|
pairs_left = self.bounded_schwartz_extract(sentence, shorts, remove_parentheses, ignore_hyphen=True, |
|
ignore_punc=ignore_punc, add_punc=add_punc, |
|
small_window=small_window, no_stop_words=no_stop_words, |
|
ignore_righthand=ignore_righthand, map_chars=True, high_recall=True, |
|
high_recall_left=True, tag='High Recall Schwartz') |
|
pairs_right = self.bounded_schwartz_extract(sentence, shorts, remove_parentheses, ignore_hyphen=True, |
|
ignore_punc=ignore_punc, add_punc=add_punc, |
|
small_window=small_window, no_stop_words=no_stop_words, |
|
ignore_righthand=ignore_righthand, map_chars=True, high_recall=True, |
|
high_recall_left=False, tag='High Recall Schwartz') |
|
for acr, lf in pairs_right.items(): |
|
if len(lf[0]) > 0 and (acr not in pairs_left or len(pairs_left[acr][0]) == 0): |
|
pairs_left[acr] = lf |
|
res = {} |
|
for acr, lf in pairs_left.items(): |
|
if acr == ''.join([w[0] for w in lf[0].split() if w[0].isupper()]) or acr.lower() == ''.join( |
|
w[0] for w in lf[0].split() if w not in string.punctuation and w not in stop_words).lower(): |
|
res[acr] = lf |
|
return res |
|
|
|
def character_match(self, acronym, long, long_index, left=False, output_string=False, is_candidate=True): |
|
capitals = [] |
|
long_form = [] |
|
for c in acronym: |
|
if c.isupper(): |
|
capitals.append(c) |
|
|
|
if not is_candidate: |
|
long_capital_initials = [] |
|
for w in long: |
|
if w[0].isupper(): |
|
long_capital_initials.append(w[0]) |
|
|
|
if left: |
|
capitals = capitals[::-1] |
|
long = long[::-1] |
|
long_index = long_index[::-1] |
|
for j, c in enumerate(capitals): |
|
if j >= len(long): |
|
long_form = [] |
|
break |
|
else: |
|
if long[j][0].lower() == c.lower(): |
|
long_form.append(long_index[j]) |
|
else: |
|
long_form = [] |
|
break |
|
|
|
if not is_candidate: |
|
if len(long_capital_initials) != len(long_form) and len(long_capital_initials) > 0: |
|
long_form = [] |
|
|
|
long_form.sort() |
|
if output_string: |
|
if long_form: |
|
return long[long_form[0]:long_form[-1] + 1] |
|
else: |
|
return "" |
|
else: |
|
return long_form |
|
|
|
|
|
def high_recall_character_match(self, sentence, shorts, all_acronyms, ignore_hyphen=False, map_chars=False,default_diction=False): |
|
''' |
|
This function finds the long form of the acronyms that are not surrounded by parentheses in the text using scritc rule of character matching (the initial of the sequence of the words in the candidate long form should form the acronym) |
|
example: annotation software application , Text Annotation Graphs , or TAG , that provides a rich set of ... |
|
|
|
:param sentence: |
|
:param shorts: |
|
:param all_acronyms: |
|
:return: |
|
''' |
|
labels = ['O'] * len(sentence) |
|
diction = {} |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
|
|
if ignore_hyphen: |
|
t = t.replace('-', '') |
|
capitals = [] |
|
for c in t: |
|
if c.isupper(): |
|
capitals.append(c) |
|
cand_long = sentence[max(i - len(capitals) - 10, 0):i] |
|
long_form = '' |
|
long_form_index = [] |
|
for j in range(max(len(cand_long) - len(capitals), 0)): |
|
if ''.join(w[0] for w in cand_long[j:j + len(capitals)]) == t: |
|
long_form = ' '.join(cand_long[j:j + len(capitals)]) |
|
long_form_index = list(range(max(max(i - len(capitals) - 10, 0) + j, 0), |
|
max(max(i - len(capitals) - 10, 0) + j, 0) + len(capitals))) |
|
break |
|
if not long_form: |
|
cand_long = sentence[i + 1:len(capitals) + i + 10] |
|
for j in range(max(len(cand_long) - len(capitals), 0)): |
|
if ''.join(w[0] for w in cand_long[j:j + len(capitals)]) == t: |
|
long_form = ' '.join(cand_long[j:j + len(capitals)]) |
|
long_form_index = list(range(i + 1 + j, i + j + len(capitals) + 1)) |
|
break |
|
long_form = long_form.split() |
|
if long_form: |
|
if long_form[0] in stop_words or long_form[-1] in stop_words: |
|
long_form = [] |
|
if any(lf in string.punctuation for lf in long_form): |
|
long_form = [] |
|
if __name__ != "__main__": |
|
NPs = [np.text for np in nlp(' '.join(sentence)).noun_chunks] |
|
long_form_str = ' '.join(long_form) |
|
if all(long_form_str not in np for np in NPs): |
|
long_form = [] |
|
if long_form: |
|
for j in long_form_index: |
|
labels[j] = 'I-long' |
|
labels[long_form_index[0]] = 'B-long' |
|
if default_diction: |
|
diction[(long_form_index[0], long_form_index[-1])] = (i, i) |
|
return self.create_diction(sentence, labels, all_acronyms=all_acronyms, tag='high recall character match', |
|
map_chars=map_chars,diction=diction) |
|
|
|
def character_match_extract(self, sentence, shorts, all_acronyms, check_all_capitals=False, ignore_hyphen=False, |
|
ignore_punc=False, map_chars=False,default_diction=False): |
|
labels = ['O'] * len(sentence) |
|
diction = {} |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
|
|
if ignore_hyphen: |
|
t = t.replace('-', '') |
|
|
|
if check_all_capitals: |
|
if len(t) != len([c for c in t if c.isupper()]): |
|
continue |
|
|
|
cand_long, cand_long_index, left = self.extract_cand_long(sentence, t, i, ignore_punc=ignore_punc) |
|
long_form = [] |
|
if cand_long: |
|
long_form = self.character_match(t, cand_long, cand_long_index, left, is_candidate=True) |
|
if long_form: |
|
labels[long_form[0]] = 'B-long' |
|
for l in long_form[1:]: |
|
labels[l] = 'I-long' |
|
if default_diction: |
|
diction[(long_form[0], long_form[-1])] = (i, i) |
|
return self.create_diction(sentence, labels, all_acronyms=all_acronyms, tag='character match', |
|
map_chars=map_chars, diction=diction) |
|
|
|
|
|
def filterout_roman_numbers(self, diction): |
|
''' |
|
This function removes roman numbers from the list of extracted acronyms. It removes only numbers from 1 to 20. |
|
:param diction: |
|
:return: |
|
''' |
|
acronyms = set(diction.keys()) |
|
for acr in acronyms: |
|
|
|
|
|
if acr in ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', |
|
'XVI', 'XVII', 'XVIII', 'XIX', 'XX']: |
|
del diction[acr] |
|
return diction |
|
|
|
|
|
def remove_punctuations(self, diction): |
|
''' |
|
Remove head+tailing punctuations |
|
|
|
:param diction: |
|
:return: |
|
''' |
|
|
|
for acr, info in diction.items(): |
|
if len(info[0]) > 0: |
|
if info[0][0] in string.punctuation: |
|
info[0] = info[0][2:] |
|
info[2][0] = info[2][0] + 1 |
|
info[3] = 'remove punctuation' |
|
if len(info[0]) > 0: |
|
if info[0][-1] in string.punctuation: |
|
info[0] = info[0][:-2] |
|
info[2][1] = info[2][1] - 1 |
|
info[3] = 'remove punctuation' |
|
|
|
return diction |
|
|
|
|
|
def initial_capitals_extract(self, sentence, shorts, all_acronyms, ignore_hyphen=False, map_chars=False,default_diction=False): |
|
''' |
|
This function captures long form which their initials is capital and could form the acronym in the format "long form (acronym)" or "(acronym) long form" |
|
example: |
|
|
|
:param sentence: |
|
:param shorts: |
|
:param all_acronyms: |
|
:return: |
|
''' |
|
labels = ['O'] * len(sentence) |
|
diction = {} |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
|
|
if ignore_hyphen: |
|
t = t.replace('-', '') |
|
capitals = [] |
|
for c in t: |
|
if c.isupper(): |
|
capitals.append(c) |
|
cand_long, cand_long_index, left = self.extract_cand_long(sentence, t, i) |
|
capital_initials = [] |
|
capital_initials_index = [] |
|
for j, w in enumerate(cand_long): |
|
lll = labels[i + j - len(cand_long) - 1] |
|
if w[0].isupper() and labels[i + j - len(cand_long) - 1] == 'O': |
|
capital_initials.append(w[0]) |
|
capital_initials_index.append(j) |
|
if ''.join(capital_initials) == t: |
|
long_form = cand_long[capital_initials_index[0]:capital_initials_index[-1] + 1] |
|
long_form_index = cand_long_index[capital_initials_index[0]:capital_initials_index[-1] + 1] |
|
for lfi in long_form_index: |
|
labels[lfi] = 'I-long' |
|
labels[long_form_index[0]] = 'B-long' |
|
if default_diction: |
|
diction[(long_form_index[0], long_form_index[-1])] = (i, i) |
|
return self.create_diction(sentence, labels, all_acronyms=all_acronyms, tag='Capital Initials', |
|
map_chars=map_chars,diction=diction) |
|
|
|
|
|
def hyphen_in_acronym(self, sentence, shorts): |
|
''' |
|
This function merge two acronyms if there is a hyphen between them |
|
example: for C - GAN indicates |
|
|
|
:param sentence: |
|
:param shorts: |
|
:return: |
|
''' |
|
|
|
new_shorts = [] |
|
for short in shorts: |
|
i = short + 1 |
|
next_hyphen = False |
|
while i < len(sentence) and sentence[i] == '-': |
|
next_hyphen = True |
|
i += 1 |
|
j = short - 1 |
|
before_hyphen = False |
|
while j > 0 and sentence[j] == '-': |
|
before_hyphen = True |
|
j -= 1 |
|
|
|
|
|
if i < len(sentence) and sentence[i].isupper() and next_hyphen: |
|
for ind in range(short + 1, i + 1): |
|
new_shorts += [ind] |
|
|
|
|
|
if j > -1 and sentence[j].isupper() and before_hyphen: |
|
for ind in range(j, short): |
|
new_shorts += [ind] |
|
|
|
shorts.extend(new_shorts) |
|
return shorts |
|
|
|
|
|
def merge_hyphened_acronyms(self, sentence, labels=[]): |
|
''' |
|
This function merge hyphened acronyms |
|
example: We show that stochastic gradient Markov chain Monte Carlo ( SG - MCMC ) - a class of |
|
|
|
:param sentence: |
|
:return: |
|
''' |
|
new_sentence = [] |
|
new_labels = [] |
|
merge = False |
|
shorts = self.short_extract(sentence, 0.6, True) |
|
shorts += self.hyphen_in_acronym(sentence, shorts) |
|
|
|
for i, t in enumerate(sentence): |
|
if i in shorts and i - 1 in shorts and i + 1 in shorts and t == '-': |
|
merge = True |
|
if len(new_sentence) > 0: |
|
new_sentence[-1] += '-' |
|
else: |
|
new_sentence += ['-'] |
|
continue |
|
if merge: |
|
if len(new_sentence) > 0: |
|
new_sentence[-1] += t |
|
else: |
|
new_sentence += [t] |
|
else: |
|
new_sentence.append(t) |
|
if labels: |
|
new_labels.append(labels[i]) |
|
merge = False |
|
|
|
return new_sentence, new_labels |
|
|
|
|
|
def add_embedded_acronym(self, diction, shorts, sentence): |
|
''' |
|
This function will add the embeded acronyms into the dictionary |
|
example: we use encoder RNN ( ER ) |
|
|
|
:param diction: |
|
:param shorts: |
|
:return: |
|
''' |
|
short_captured = [] |
|
long_captured = [] |
|
for acr, info in diction.items(): |
|
short_captured.append(info[1][0]) |
|
if info[2]: |
|
long_captured.extend(list(range(info[2][0], info[2][1]))) |
|
for short in shorts: |
|
if short not in short_captured and short in long_captured and sentence[short] not in diction: |
|
diction[sentence[short]] = ['', (short, short), [], 'embedded acronym'] |
|
return diction |
|
|
|
|
|
def extract_templates(self, sentence, shorts, map_chars=False): |
|
''' |
|
Extract acronym and long forms based on templates |
|
example: PM stands for Product Manager |
|
|
|
:param sentence: |
|
:param shorts: |
|
:return: |
|
''' |
|
labels = ['O'] * len(sentence) |
|
for i, t in enumerate(sentence): |
|
if i in shorts: |
|
labels[i] = 'B-short' |
|
capitals = [] |
|
for c in t: |
|
if c.isupper(): |
|
capitals.append(c) |
|
if i < len(sentence) - len(capitals) - 2: |
|
if sentence[i + 1] == 'stands' and sentence[i + 2] == 'for': |
|
if ''.join(w[0] for w in sentence[i + 3:i + 3 + len(capitals)]) == ''.join(capitals): |
|
labels[i + 3:i + 3 + len(capitals)] = ['I-long'] * len(capitals) |
|
labels[i + 3] = 'B-long' |
|
return self.create_diction(sentence, labels, all_acronyms=False, tag='Template', map_chars=map_chars) |
|
|
|
|
|
def update_pair(self, old_pair, new_pair): |
|
for acr, info in new_pair.items(): |
|
if acr not in old_pair: |
|
old_pair[acr] = info |
|
else: |
|
info[4] = max(info[4],old_pair[acr][4]) |
|
old_pair[acr] = info |
|
return old_pair |
|
|
|
def extract(self, sentence, active_rules): |
|
|
|
shorts = self.short_extract(sentence, 0.6, active_rules['starting_lower_case'], |
|
ignore_dot=active_rules['ignore_dot']) |
|
|
|
if active_rules['low_short_threshold']: |
|
shorts += self.short_extract(sentence, 0.50, active_rules['starting_lower_case'], |
|
ignore_dot=active_rules['ignore_dot']) |
|
|
|
|
|
if active_rules['hyphen_in_acronym']: |
|
shorts += self.hyphen_in_acronym(sentence, shorts) |
|
|
|
pairs = {} |
|
if active_rules['schwartz']: |
|
|
|
pairs = self.schwartz_extract(sentence, shorts, active_rules['no_parentheses'], |
|
ignore_punc=active_rules['ignore_punc_in_parentheses'], |
|
add_punc=active_rules['extend_punc'], |
|
small_window=active_rules['small_window'], |
|
no_stop_words=active_rules['no_beginning_stop_word'], |
|
ignore_righthand=active_rules['ignore_right_hand'], |
|
map_chars=active_rules['map_chars'], |
|
default_diction=active_rules['default_diction']) |
|
|
|
if active_rules['bounded_schwartz']: |
|
|
|
bounded_pairs = self.bounded_schwartz_extract(sentence, shorts, active_rules['no_parentheses'], |
|
ignore_punc=active_rules['ignore_punc_in_parentheses'], |
|
add_punc=active_rules['extend_punc'], |
|
small_window=active_rules['small_window'], |
|
no_stop_words=active_rules['no_beginning_stop_word'], |
|
ignore_righthand=active_rules['ignore_right_hand'], |
|
map_chars=active_rules['map_chars'], |
|
default_diction=active_rules['default_diction']) |
|
|
|
pairs = self.update_pair(pairs, bounded_pairs) |
|
|
|
if active_rules['high_recall_schwartz']: |
|
hr_paris = self.high_recall_schwartz(sentence, shorts, active_rules['no_parentheses'], |
|
ignore_punc=active_rules['ignore_punc_in_parentheses'], |
|
add_punc=active_rules['extend_punc'], |
|
small_window=active_rules['small_window'], |
|
no_stop_words=active_rules['no_beginning_stop_word'], |
|
ignore_righthand=active_rules['ignore_right_hand'], |
|
map_chars=active_rules['map_chars'], |
|
default_diction=active_rules['default_diction']) |
|
|
|
pairs = self.update_pair(pairs,hr_paris) |
|
if active_rules['character']: |
|
|
|
|
|
character_pairs = self.character_match_extract(sentence, shorts, not active_rules['schwartz'], |
|
check_all_capitals=active_rules['check_all_capitals'], |
|
ignore_punc=active_rules['ignore_punc_in_parentheses'], |
|
map_chars=active_rules['map_chars'], |
|
default_diction=active_rules['default_diction']) |
|
|
|
pairs = self.update_pair(pairs, character_pairs) |
|
|
|
if active_rules['high_recall_character_match']: |
|
character_pairs = self.high_recall_character_match(sentence, shorts, not active_rules['schwartz'], |
|
map_chars=active_rules['map_chars'],default_diction=active_rules['default_diction']) |
|
acronyms = character_pairs.keys() |
|
for acr in acronyms: |
|
if acr not in pairs or len(pairs[acr][0]) == 0: |
|
pairs[acr] = character_pairs[acr] |
|
|
|
if active_rules['initial_capitals']: |
|
character_pairs = self.initial_capitals_extract(sentence, shorts, not active_rules['schwartz'], |
|
map_chars=active_rules['map_chars'],default_diction=active_rules['default_diction']) |
|
|
|
pairs = self.update_pair(pairs,character_pairs) |
|
|
|
if active_rules['template']: |
|
template_pairs = self.extract_templates(sentence, shorts, map_chars=active_rules['map_chars']) |
|
|
|
pairs = self.update_pair(pairs,template_pairs) |
|
|
|
if active_rules['capture_embedded_acronym']: |
|
pairs = self.add_embedded_acronym(pairs, shorts, sentence) |
|
|
|
if active_rules['roman']: |
|
pairs = self.filterout_roman_numbers(pairs) |
|
|
|
if active_rules['remove_punctuation']: |
|
pairs = self.remove_punctuations(pairs) |
|
return pairs |
|
|
|
failures = [] |
|
sucess = [] |
|
for i in range(len(gold_label)): |
|
gold_diction = self.create_diction(dataset[i]['token'], gold_label[i], tag='gold') |
|
pred_diction = pred_dictions[i] |
|
if gold_diction.keys() != pred_diction.keys() or set(v[0] for v in gold_diction.values()) != set( |
|
v[0] for v in pred_diction.values()): |
|
failures.append([gold_diction, pred_diction, dataset[i]['token'], dataset[i]['id']]) |
|
else: |
|
sucess.append([gold_diction, pred_diction, dataset[i]['token'], dataset[i]['id']]) |
|
failure_ratio = 'Failures: {:.2%}'.format(len(failures) / len(dataset)) + '\n' |
|
print(failure_ratio) |
|
results += failure_ratio |
|
return failures, sucess, results |