Skip to content
Snippets Groups Projects
Commit d4b72c96 authored by Victor Zimmermann's avatar Victor Zimmermann
Browse files

Add comments, fixed some bugs, other minor updates.

parent 2de73dc7
No related branches found
No related tags found
No related merge requests found
...@@ -12,6 +12,7 @@ from multiprocessing import Pool ...@@ -12,6 +12,7 @@ from multiprocessing import Pool
nlp = spacy.load('en') # standard english nlp nlp = spacy.load('en') # standard english nlp
#counts occurences of nodes and cooccurrences
def frequencies(corpus_path, target): def frequencies(corpus_path, target):
stop_words = set(stopwords.words('english') + config.stop_words) stop_words = set(stopwords.words('english') + config.stop_words)
...@@ -20,16 +21,16 @@ def frequencies(corpus_path, target): ...@@ -20,16 +21,16 @@ def frequencies(corpus_path, target):
max_nodes = config.max_nodes max_nodes = config.max_nodes
max_edges = config.max_edges max_edges = config.max_edges
node_freq = dict() node_freq = dict() #counts (potential) nodes
edge_freq = dict() edge_freq = dict() #counts (potential) edges
files = [corpus_path + f for f in os.listdir(corpus_path)] files = [corpus_path + f for f in os.listdir(corpus_path)] #file names of corpus files
s_target = target.replace('_', ' ') #target word with spaces s_target = target.replace('_', ' ') #target word with spaces
i = 0 i = 0 #for update print statements
for f in files: for f in files:
if i % int(len(files)/10) == 0: if i % int(len(files)/10) == 0: #prints update after every 10th of the corpus is parsed
file_ratio = i/len(files[:]) file_ratio = i/len(files[:])
max_node_ratio = len(node_freq)/max_nodes max_node_ratio = len(node_freq)/max_nodes
...@@ -37,48 +38,56 @@ def frequencies(corpus_path, target): ...@@ -37,48 +38,56 @@ def frequencies(corpus_path, target):
ratios = [file_ratio, max_node_ratio, max_edge_ratio] ratios = [file_ratio, max_node_ratio, max_edge_ratio]
#uses the ratio closest to 100%.
percentage = int((max(ratios))*100) percentage = int((max(ratios))*100)
print('[a] ~{:02d}%\tNodes: {}\tEdges: {}.'.format(percentage, len(node_freq), len(edge_freq)), target) print('[a] ~{:02d}%\tNodes: {}\tEdges: {}.'.format(percentage, len(node_freq), len(edge_freq)), target)
#checks maximum node values
if len(node_freq) > max_nodes: if len(node_freq) > max_nodes:
return node_freq, edge_freq return node_freq, edge_freq
#checks maximum edge values
if len(edge_freq) > max_edges: if len(edge_freq) > max_edges:
return node_freq, edge_freq return node_freq, edge_freq
with open(f, 'r') as lines: with open(f, 'r') as lines: #parses single file
try: try:
for line in lines: for line in lines: #parses single paragraph
line = line.lower() line = line.lower()
if s_target in line: if s_target in line: #greedy pre selection, not perfect
tokens = set() tokens = set() #set of node candidates
doc = nlp(line.replace(s_target, target)) doc = nlp(line.replace(s_target, target)) #nlp processing
if target in [t.text for t in doc]: if target in [t.text for t in doc]: #better selection
for tok in doc: for tok in doc:
text = tok.text text = tok.text #string value
tag = tok.tag_ tag = tok.tag_ #pos tag
#doesn't add target word to nodes
if text == target: if text == target:
pass pass
#doesn't add stop words to nodes
elif text in stop_words: elif text in stop_words:
pass pass
#only adds tokens with allowed tags to nodes
elif tag in allowed_tags: elif tag in allowed_tags:
tokens.add(tok.text) tokens.add(tok.text)
#if there are enough (good) tokens in paragraph
if len(tokens) >= min_context_size: if len(tokens) >= min_context_size:
for token in tokens: for token in tokens:
#updates counts for nodes
if token in node_freq: if token in node_freq:
node_freq[token] += 1 node_freq[token] += 1
else: else:
...@@ -86,11 +95,13 @@ def frequencies(corpus_path, target): ...@@ -86,11 +95,13 @@ def frequencies(corpus_path, target):
for edge in {(x,y) for x in tokens for y in tokens if x < y}: for edge in {(x,y) for x in tokens for y in tokens if x < y}:
#updates counts for edges
if edge in edge_freq: if edge in edge_freq:
edge_freq[edge] += 1 edge_freq[edge] += 1
else: else:
edge_freq[edge] = 1 edge_freq[edge] = 1
#if a file is corrupted (can't always be catched with if-else)
except UnicodeDecodeError: except UnicodeDecodeError:
pass pass
...@@ -98,10 +109,13 @@ def frequencies(corpus_path, target): ...@@ -98,10 +109,13 @@ def frequencies(corpus_path, target):
i += 1 i += 1
#update print
print('[a] 100%\tNodes: {}\tEdges: {}.'.format(len(node_freq), len(edge_freq)), target) print('[a] 100%\tNodes: {}\tEdges: {}.'.format(len(node_freq), len(edge_freq)), target)
return node_freq, edge_freq return node_freq, edge_freq
#build graph from frequency dictionaries
def build_graph(node_freq, edge_freq): def build_graph(node_freq, edge_freq):
min_node_freq = config.min_node_freq min_node_freq = config.min_node_freq
...@@ -110,11 +124,13 @@ def build_graph(node_freq, edge_freq): ...@@ -110,11 +124,13 @@ def build_graph(node_freq, edge_freq):
G = nx.Graph() G = nx.Graph()
#node : node frequency
for key, value in node_freq.items(): for key, value in node_freq.items():
if value >= min_node_freq: if value >= min_node_freq:
G.add_node(key) G.add_node(key)
#edge : edge frequency
for key, value in edge_freq.items(): for key, value in edge_freq.items():
if value < min_edge_freq: if value < min_edge_freq:
...@@ -130,33 +146,37 @@ def build_graph(node_freq, edge_freq): ...@@ -130,33 +146,37 @@ def build_graph(node_freq, edge_freq):
return G return G
#Identifies senses by choosing nodes with high degrees
def root_hubs(graph, edge_freq, min_neighbors=4, theshold=0.8): def root_hubs(graph, edge_freq, min_neighbors=4, theshold=0.8):
min_neighbors = config.min_neighbors min_neighbors = config.min_neighbors
threshold = config.threshold threshold = config.threshold
G = deepcopy(graph) G = deepcopy(graph)
V = sorted(G.nodes, key=lambda key: G.degree[key], reverse=True) # -1 to sort descending (...3 -> 2 -> 1...) V = sorted(G.nodes, key=lambda key: G.degree[key], reverse=True) # sorts according to degree
H = list() H = list() #output list
while V: while V:
v = V[0] v = V[0] #best hub candidate
if G.degree[v] >= min_neighbors: if G.degree[v] >= min_neighbors:
mfn = sorted(G.adj[v], key=lambda key: edge_freq[v,key] if v < key else edge_freq[key, v], reverse=True)[:min_neighbors] #mfn: most frequent neighbors mfn = sorted(G.adj[v], key=lambda key: edge_freq[v,key] if v < key else edge_freq[key, v], reverse=True)[:min_neighbors] #most frequent neighbors
if np.mean([G.edges[v,n]['weight'] for n in mfn]) < theshold: if np.mean([G.edges[v,n]['weight'] for n in mfn]) < theshold: #if the median weight of the most frequent neighbors is under threshold
H.append(v) H.append(v)
#removes neighbors of new hub as hub candidates
for nbr in deepcopy(G).adj[v]: for nbr in deepcopy(G).adj[v]:
G.remove_node(nbr) G.remove_node(nbr)
#removes hub candidate
G.remove_node(v) G.remove_node(v)
#reorderd potential hubs after deletions
V = sorted(G.nodes, key=lambda key: G.degree[key], reverse=True) V = sorted(G.nodes, key=lambda key: G.degree[key], reverse=True)
else: else:
...@@ -170,7 +190,7 @@ def root_hubs(graph, edge_freq, min_neighbors=4, theshold=0.8): ...@@ -170,7 +190,7 @@ def root_hubs(graph, edge_freq, min_neighbors=4, theshold=0.8):
def components(graph, hubs, target): def components(graph, hubs, target):
G = deepcopy(graph) G = deepcopy(graph)
H = hubs H = hubs #root hubs
t = target t = target
#G.add_node(t) #G.add_node(t)
...@@ -179,6 +199,7 @@ def components(graph, hubs, target): ...@@ -179,6 +199,7 @@ def components(graph, hubs, target):
T = nx.minimum_spanning_tree(G) T = nx.minimum_spanning_tree(G)
#removes singletons
for node in deepcopy(T).nodes: for node in deepcopy(T).nodes:
if len(T.adj[node]) == 0: if len(T.adj[node]) == 0:
T.remove_node(node) T.remove_node(node)
...@@ -186,17 +207,22 @@ def components(graph, hubs, target): ...@@ -186,17 +207,22 @@ def components(graph, hubs, target):
return T return T
#Calculates score for a given path in a minimum spanning tree
def score(graph, from_node, to_node): def score(graph, from_node, to_node):
#if correct tree
if nx.has_path(graph, from_node, to_node): if nx.has_path(graph, from_node, to_node):
# calculates shortest path (approximation for path with lowest total weight)
path = nx.shortest_path(graph, from_node, to_node, 'weight') path = nx.shortest_path(graph, from_node, to_node, 'weight')
total_weight = 0 total_weight = 0
#adds weights of every sub-path
for i in range(1, len(path)): for i in range(1, len(path)):
sub_from, sub_to = path[i-1], path[i] sub_from, sub_to = path[i-1], path[i]
total_weight += graph[sub_from][sub_to]['weight'] total_weight += graph[sub_from][sub_to]['weight']
#the further the path, the lower the score
return 1/(1+total_weight) return 1/(1+total_weight)
else: else:
...@@ -204,47 +230,52 @@ def score(graph, from_node, to_node): ...@@ -204,47 +230,52 @@ def score(graph, from_node, to_node):
return 0 return 0
# Basically Word Sense Disambiguation, matches context to sense
def disambiguate(mst, hubs, contexts, target=""): def disambiguate(mst, hubs, contexts, target=""):
target = target.replace('_', ' ') target = target.replace('_', ' ')
T = mst T = mst #minimum spanning tree
H = hubs H = hubs #root hubs
C = [c.lower().strip().replace(target, '') for c in contexts] C = [c.lower().strip().replace(target, '') for c in contexts] #cleaned up contexts
score_dict = dict() score_dict = dict() #memoisation for scores
result = list() result = list() #output of function
for c in C: for c in C:
idx = C.index(c) + 1 idx = C.index(c) + 1 #index based on position in list
#if no sense is found for a target word, we should assume that there only is one sense #if no sense is found for a target word, we should assume that there only is one sense
if len(H) == 0: if len(H) == 0:
result.append((1, idx)) result.append((1, idx, 0))
else: else:
doc = nlp(c) doc = nlp(c) #parsed context
texts = [tok.text for tok in doc] texts = [tok.text for tok in doc] #tokens
scores = np.zeros(len(H)) #initialise with zeros for every sense scores = np.zeros(len(H)) #initialise with zeros for every sense
for text in texts: for text in texts:
if text in T.nodes: if text in T.nodes: #if word wasn't filtered out
new_scores = list() new_scores = list() #scores to be added to total scores
for h in H: for h in H: #for each hub
if (text, h) in score_dict:
if (text, h) in score_dict: #memoisation
new_scores.append(score_dict[(text,h)]) new_scores.append(score_dict[(text,h)])
else: else:
new_score = score(T, text, h) new_score = score(T, text, h)
new_scores.append(new_score) new_scores.append(new_score)
score_dict[(text,h)] = new_scores score_dict[(text,h)] = new_score #memoisation
scores = np.add(scores, new_scores) scores = scores + np.array(new_scores)
else: else:
...@@ -257,22 +288,32 @@ def disambiguate(mst, hubs, contexts, target=""): ...@@ -257,22 +288,32 @@ def disambiguate(mst, hubs, contexts, target=""):
else: else:
result.append((np.argmax(scores)+1, idx)) #applies sense with the highest score to context
max_score = np.max(scores)
argmax_score = np.argmax(scores)
#clusters begin at 1
result.append((argmax_score + 1, idx))
return result return result
# our main function, here the main stepps for word sense induction are called
def WSI(topic_id, topic_name, results): def WSI(topic_id, topic_name, results):
#buffer for useful information
out_buffer = '\n' out_buffer = '\n'
#paths for input (corpus) and output(directory)
corpus_path = config.corpus corpus_path = config.corpus
output_path = config.output output_path = config.output
#removes trailing new_lines
old_target = topic_name.strip() #original target old_target = topic_name.strip() #original target
out_buffer += ("[A] Word sense induction for '"+old_target+"':\n") out_buffer += ("[A] Word sense induction for '"+old_target+"':\n")
if old_target[:4] == 'the_' and old_target.count('_') >= 2: #hard coded 'the'-protection #in topics longer than two words, the leading 'the' can generally be removed without changing the sense
if old_target[:4] == 'the_' and old_target.count('_') >= 2:
target = old_target[4:] target = old_target[4:]
...@@ -280,37 +321,46 @@ def WSI(topic_id, topic_name, results): ...@@ -280,37 +321,46 @@ def WSI(topic_id, topic_name, results):
target = old_target target = old_target
#writes headline for output files
f = open(output_path+target+'.absinth', 'w') f = open(output_path+target+'.absinth', 'w')
f.write('subTopicID\tresultID\n') f.write('subTopicID\tresultID\n')
#counts occurences of single words, as well as cooccurrences, saves it in dictionary
print('[a]', 'Counting nodes and edges.', old_target) print('[a]', 'Counting nodes and edges.', old_target)
node_freq, edge_freq = frequencies(corpus_path, target) node_freq, edge_freq = frequencies(corpus_path, target)
out_buffer += '[A] Nodes: {}\tEdges:{}\n'.format(str(len(node_freq)), str(len(edge_freq))) out_buffer += '[A] Nodes: {}\tEdges: {}\n'.format(str(len(node_freq)), str(len(edge_freq)))
#builds graph from these dictionaries, also applies multiple filters
print('[a]', 'Building graph.', old_target) print('[a]', 'Building graph.', old_target)
G = build_graph(node_freq, edge_freq) G = build_graph(node_freq, edge_freq)
#finds root hubs (senses) within the graph + more filters for these
print('[a]', 'Collecting root hubs.', old_target) print('[a]', 'Collecting root hubs.', old_target)
H = root_hubs(G, edge_freq) H = root_hubs(G, edge_freq)
out_buffer += '[A] Root hubs:\n' out_buffer += '[A] Root hubs:\n'
i = 1 #adds sense inventory to buffer with some common neighbors for context
i = 1 #sense index
for h in H: for h in H:
mfn = sorted(G.adj[h], key=lambda x: edge_freq[h,x] if h < x else edge_freq[x, h], reverse=True)[:6] mfn = sorted(G.adj[h], key=lambda x: edge_freq[h,x] if h < x else edge_freq[x, h], reverse=True)[:6]
out_buffer += (' {}. {}: {}\n'.format(i, h, mfn)) out_buffer += (' {}. {}: {}\n'.format(i, h, mfn))
i += 1 i += 1
#performs minimum_spanning_tree algorithm on graph
print('[a]', 'Building minimum spanning tree.', old_target) print('[a]', 'Building minimum spanning tree.', old_target)
T = components(G, H, target) T = components(G, H, target)
#matches senses to clusters
print('[a]', 'Disambiguating results.', old_target) print('[a]', 'Disambiguating results.', old_target)
D = disambiguate(T, H, results[topic_id], target) D = disambiguate(T, H, results[topic_id], target)
out_buffer += ('[A] Mapping: '+ str(D) + '\n') out_buffer += ('[A] Mapping: '+ str(D) + '\n')
#prints buffer
print('[a]', 'Writing to file.', old_target) print('[a]', 'Writing to file.', old_target)
print(out_buffer) print(out_buffer)
#writes clustering to file
for d in D: for d in D:
f.write(topic_id+'.'+str(d[0])+'\t'+topic_id+'.'+str(d[1])+'\n') f.write(topic_id+'.'+str(d[0])+'\t'+topic_id+'.'+str(d[1])+'\n')
...@@ -320,8 +370,13 @@ def WSI(topic_id, topic_name, results): ...@@ -320,8 +370,13 @@ def WSI(topic_id, topic_name, results):
if __name__ == '__main__': if __name__ == '__main__':
data_path = config.dataset # If absinth.py is run in test environment
if '-t' in sys.argv:
data_path = config.test
else:
data_path = config.dataset
# results.txt includes the queries for a given target word
results = dict() results = dict()
with open(data_path+'results.txt', 'r') as results_file: with open(data_path+'results.txt', 'r') as results_file:
...@@ -329,14 +384,15 @@ if __name__ == '__main__': ...@@ -329,14 +384,15 @@ if __name__ == '__main__':
for line in results_file.readlines()[1:]: for line in results_file.readlines()[1:]:
l = line.split('\t') l = line.split('\t')
id1, _ = l[0].split('.') id1, _ = l[0].split('.') #the second part of the id is ignored, as it is identical to the list index
if id1 not in results: if id1 not in results:
results[id1]=list() results[id1]=list()
results[id1].append(" ".join(l[2:])) results[id1].append(" ".join(l[2:])) # here I join title and snippet, the URL is ignored
# topics.txt is a list of target words
topics = dict() topics = dict()
with open(data_path+'topics.txt', 'r') as topics_file: with open(data_path+'topics.txt', 'r') as topics_file:
...@@ -346,7 +402,10 @@ if __name__ == '__main__': ...@@ -346,7 +402,10 @@ if __name__ == '__main__':
l = line.split('\t') l = line.split('\t')
topics[l[0]] = l[1] topics[l[0]] = l[1]
# multiprocessing
with Pool(4) as pool: with Pool(4) as pool:
# calls WSI() for for topics at a time
pool.starmap(WSI, [(key, value, results) for key,value in topics.items()]) pool.starmap(WSI, [(key, value, results) for key,value in topics.items()])
#for key, value in topics.items(): #for key, value in topics.items():
# WSI(key, value, results) # WSI(key, value, results)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment