graph의 similarity는 어떻게 계산할까요.
edit-distance
- “abc”, “abcd”는 얼마나 비슷할까요? 혹은 정량적으로 어떻게 비슷한 정도를 측정할 수 있을까요?
- 간단하게 두 스트링을 비교해보면 캐릭터 하나만 추가되어있는 것을 알 수 있습니다. 즉, ‘하나만 지우면 같아질 것 같은데?’라는 생각이 들죠.
- edit-distance는 이렇게 한쪽에서 다른 쪽으로 갈 때 얼마나 고쳐야 같아지는지를 측정해서 그 유사도를 측정하는 방식을 말합니다. 다음 세 가지 방식중 하나가 필요할 때마다 edit-distance는 하나씩 늘어납니다.
- insertion: 새로운 캐릭터가 삽입되어야 할 때
- deletion: 캐릭터 하나를 지워야 할 때
- substitution: 캐릭터를 변경해야 할 때
- 이렇게 측정할 수 있습니다.
for word
- 단어에 대해서는 아래처럼 쓸 수 있어요. 간단합니다.
from nltk import edit_distance
example_word = 'abc'
target_words = ['abc', 'abcd', 'a bcd', 'lsh', 'lsha']
for w in target_words:
print("edit distance of <{}>: {}".format(w, edit_distance("abc", w)))
edit distance of <abc>: 0
edit distance of <abcd>: 1
edit distance of <a bcd>: 2
edit distance of <lsh>: 3
edit distance of <lsha>: 4
for graph
- graph의 경우는 다행히 이미 networkx에 관련 library가 만들어져 있습니다. 그걸 사용하면 됩니다 하하핫
import networkx as nx
g1 = nx.complete_graph(["n{}".format(i) for i in range(0, 5)])
g2 = nx.complete_graph(["n{}".format(i) for i in range(0, 5)])
for i in range(0, 3):
r_n = np.random.choice(g2.nodes())
print("node removed: {}".format(r_n))
print("edge removed: {}".format([e for e in g2.edges() if r_n in e]))
g2.remove_node(r_n)
print(nx.similarity.graph_edit_distance(g1, g2))
print("="*50)
node removed: n2
edge removed: [('n0', 'n2'), ('n1', 'n2'), ('n2', 'n3'), ('n2', 'n4')]
5.0
==================================================
node removed: n0
edge removed: [('n0', 'n1'), ('n0', 'n3'), ('n0', 'n4')]
9.0
==================================================
node removed: n3
edge removed: [('n1', 'n3'), ('n3', 'n4')]
12.0
==================================================
for tree
- graph에도 다양한 형태가 있습니다. 그중에서 tree 구조에 대해서도 당연히(graph니까) edit-distance를 그릴 수 있습니다.
-
그런데, 이유는 아직 알아보지는 않았지만, 계산속도가 매우 느려요. 진짜 느립니다.
- 아래와 같은 두 그래프를 비교할 때 시간이 1초가 넘게 소요됩니다. 만약 두 tree의 깊이가 훨씬 깊고 넓을 경우에는 진짜, 밤새 해도 안 끝날 때가 있어요.
import matplotlib.pyplot as plt
f, axes = plt.subplots(2, 1)
f.set_size_inches(12, 8)
g1 = nx.balanced_tree(2, 2)
g2 = nx.balanced_tree(1, 6)
nx.draw_networkx(g1, ax=axes[0]), nx.draw_networkx(g2, ax=axes[1])
plt.savefig('../../assets/images/markdown_img/180823_bt_edit.svg')
plt.show()
start_time = time.time()
print("edit distance: {}".format(nx.graph_edit_distance(g1, g2)))
print(time.time() - start_time)
edit distance: 4.0
1.444833755493164
- 이럴때는 좀 간소하지만 optimal하지 않은 edit-distance를 찾으면서 계산할 수도 있습니다.
nx.optimize_graph_edit_distance(g1, g2)
의 경우 연속해서 optimal edit-distance를 찾아주는 iterator를 반환해줍니다. 따라서, 연속해서 읽어들이면 결국은nx.graph_edit_distance(g1, g2)
와 마찬가지로 optimal edit-distance를 찾아주긴 합니다. - 시간이 오래 걸리므로, 간단하게
next()
로 초기 값만 읽어서 비교하는 것도 괜찮을 수 있어요.
for i, v in enumerate(nx.optimize_graph_edit_distance(g1, g2)):
print("{}, edit distancne: {: >4.1f}".format(i, v))
0, edit distancne: 10.0
1, edit distancne: 8.0
2, edit distancne: 6.0
3, edit distancne: 4.0
wrap-up
- 앞서 작성한 바와 같이, 경우에 따라서 edit-distance가 시간이 무척 오래 걸리는 경우가 있습니다. 이유는 제가 나중에 파악해봐야 할 것 같아요. 하고 깃헙에 이슈를 날리면 좋을 것 같습니다.
- 다만 지금 코드를 확인해보니 매우 복잡하군요…..다음에 하기로 한다. 쿠쿠
- 또한 굳이 edit-distance에 집착할 필요도 없어요. 만약 node의 수가 제한되어 있다면, 단순히 edge간의 차집합 크기를 고려해보면 대략 비슷하게 나올 수 있으니까요.
댓글남기기