<jupyter_start><jupyter_text>---  Latent Semantic Analysis 실습 2 (예제) 다음과 같이 document와 query가 주어졌다.$d_1$ : "Machine learning is super fun"$d_2$ : "Python is super, super cool"$d_3$ : "Statistics is cool, too"$d_4$ : "Data science is fun"$d_5$ : "Python is great for machine learning"$d_6$ : "I like football"    $d_7$ : "Football is great to watch"$query$ : "machine learning python"<jupyter_code># Matrix를 생성하기 위하여, index의 고정이 필요하고, 이를 위해서 순서를 정확히 보장하기 위해 OrderDict을 사용한다.
from collections import OrderedDict

# Corpus를 생성한다.
corpus = OrderedDict(
    {
        'cool':0, 
        'data':1,
        'football':2,
        'fun':3,
        'great':4,
        'learning':5,
        'like':6,
        'machine':7,
        'python':8,
        'science':9,
        'statistics':10,
        'super':11,
        'watch':12
    }
)
corpus
d1 = "Machine learning is super fun"
d2 = "Python is super, super cool"
d3 = "Statistics is cool, too"
d4 = "Data science is fun"
d5 = "Python is great for machine learning"
d6 = "I like football"
d7 = "Football is great to watch"

docs = [d1, d2, d3, d4, d5, d6, d7]
docs<jupyter_output><empty_output><jupyter_text>1. Document-Word matrix 생성  1.1. 전처리 (Python 진영에서는 전문적인 형태소분석기 konlpy를 사용하면 된다.)<jupyter_code># 각 문서를 빈 여백을 기준으로 나눈다.
d1_tokens = d1.split(' ')
d1_tokens
# 대소문자의 구분을 없앤다.
d1_tokens = map(str.lower, d1_tokens)  # 또는 [s.lower for s in d1_tokens]를 사용
d1_tokens = list(d1_tokens)
d1_tokens
import re
# '-'를 제외한 구두점만 제거한다. (아주 간단하게)
d1_tokens = [re.sub(r'[^a-zA-Z0-9-]', '', token) for token in d1_tokens]
d1_tokens<jupyter_output><empty_output><jupyter_text>---(Advanced)<jupyter_code># 구두점을 제거하는 방법은 다음과 같이 하면 된다.
import string
print(string.punctuation)

s = 'test-string,wow.good!'
translator=str.maketrans('','',string.punctuation)
s.translate(translator)
# a-z and A-Z and 0-9 and -를 제외한(^) 단어는 empty string과 교체한다.
re.sub(r'[^a-zA-Z0-9-]', '','asd-')<jupyter_output><empty_output><jupyter_text>---<jupyter_code># 함수로 정의한다.
def parse_document(doc):
    tokens = doc.split(' ')
    tokens = map(str.lower, tokens)
    tokens = list(tokens)
    tokens = [re.sub(r'[^a-zA-Z0-9-]', '', token) for token in tokens]
    
    return tokens
# 사실 이 과정에서, 과거형은 현재형으로, 축약형 that's는 that is로 변경해주는 등
# "형태소 분석" 과정이 필요하나 그것은 NLP 전처리 과정에 속함으로 우선은 여기까지만 작업한다.
d2_tokens = parse_document(d2)
d3_tokens = parse_document(d3)
d4_tokens = parse_document(d4)
d5_tokens = parse_document(d5)
d6_tokens = parse_document(d6)
d7_tokens = parse_document(d7)

print(d1_tokens)
print(d2_tokens)
print(d3_tokens)
print(d4_tokens)
print(d5_tokens)
print(d6_tokens)
print(d7_tokens)<jupyter_output>['machine', 'learning', 'is', 'super', 'fun']
['python', 'is', 'super', 'super', 'cool']
['statistics', 'is', 'cool', 'too']
['data', 'science', 'is', 'fun']
['python', 'is', 'great', 'for', 'machine', 'learning']
['i', 'like', 'football']
['football', 'is', 'great', 'to', 'watch']<jupyter_text>1.2. Document vector를 생성한다.<jupyter_code># 먼저 corpus내의 iterator 내용을 확인하자.
for v, i in corpus.items():
    print(v, i)<jupyter_output>cool 0
data 1
football 2
fun 3
great 4
learning 5
like 6
machine 7
python 8
science 9
statistics 10
super 11
watch 12<jupyter_text>Document를 구성하는 vector의 길이는 corpus의 길이와 같다.<jupyter_code>corpus.values()<jupyter_output><empty_output><jupyter_text>- 첫번째 문장 `Machine learning is super fun`은,- 전처리 후 `'machine', 'learning', 'is', 'super', 'fun'` tokens가 되며,- 전체 corpus을 entry로 가진 vector `(cool:없음) (data:없음) (football:없음) fun (great:없음) learning (like:없음) machine (python:없음) (science:없음) (statistics:없음) super (watch:없음)`에 대응되고,- 최종적으로 document vector `0 0 0 1 0 1 0 1 0 0 0 1 0`에 대응된다.<jupyter_code>corpus.items()
d1_tokens
# d1 vector를 생성해보자.
d1_vec = []
for v, _ in corpus.items():
    if v in d1_tokens:
        d1_vec.append(1)
    else:
        d1_vec.append(0)
d1_vec
# Document vector를 생성하는 코드를 함수로 바꿔보자.
def get_doc_vector(tokens):
    doc_vec = [1 if v in tokens else 0 for v, _ in corpus.items()]
    return doc_vec
d2_vec = get_doc_vector(d2_tokens)
d3_vec = get_doc_vector(d3_tokens)
d4_vec = get_doc_vector(d4_tokens)
d5_vec = get_doc_vector(d5_tokens)
d6_vec = get_doc_vector(d6_tokens)
d7_vec = get_doc_vector(d7_tokens)

print(d1_vec)
print(d2_vec)
print(d3_vec)
print(d4_vec)
print(d5_vec)
print(d6_vec)
print(d7_vec)<jupyter_output>[0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1]<jupyter_text>1.3. Document-Word matrix를 생성한다.<jupyter_code># 최종 matrix Word-Document matrix
A = np.array([d1_vec, d2_vec, d3_vec, d4_vec, d5_vec, d6_vec, d7_vec])
A<jupyter_output><empty_output><jupyter_text>2. Doc-Doc matrix 와 Word-Word matrix를 생성한다.  2.1. Doc-Doc matrix $AA^T$ :  문서 $i$와 문서 $j$가 $b$개의 공통 단어를 가지고 있으면 $B[i, j]=b$<jupyter_code>doc_mat = np.dot(A, A.T)
doc_mat<jupyter_output><empty_output><jupyter_text>2.2. Word-Word matrix $A^TA$ : 단어 $i$와 단어 $j$가 문서 $c$개 에서 함께 발생했으면 $C[i,j]=c$<jupyter_code>word_mat = np.dot(A.T, A)
word_mat<jupyter_output><empty_output><jupyter_text>3. SVD(Singular Value Decomposition)  $$A=U \Sigma V^T$$<jupyter_code>U, sig, V_T = np.linalg.svd(A)<jupyter_output><empty_output><jupyter_text>3.1. $U$의 컬럼은 $AA^T$의 고유벡터들이다.  즉 Doc-Doc matrix 데이터를 생성하는 축들이다.<jupyter_code>U<jupyter_output><empty_output><jupyter_text>> $AA^T$ 의 eigenvector (컬럼들)<jupyter_code># np.linalg.eig(np.dot(A, A.T))<jupyter_output><empty_output><jupyter_text>3.2. $V$의 컬럼은 $AA^T$의 고유벡터들이다. 즉 Word-Word matrix 데이터를 생성하는 축들이다.<jupyter_code># V의 각 컬럼은 A^T*A의 eigenvector 이다.
V_T.T<jupyter_output><empty_output><jupyter_text>> $A^TA$의 eigenvector (컬럼들)<jupyter_code># np.linalg.eig(np.dot(A.T, A))<jupyter_output><empty_output><jupyter_text>4. Word vector와 Document vector 생성하기 Word vector와 document vector를 생성할 때, 선택하는 singular value의 수가 중요하다. 2차원으로 보여주기 위해 $k=2$로 표현하자.  4.1. Words Vectors<jupyter_code>word_eigen_vec = V_T.T[:, :2]
# Word-Word matrix의 eigenvectors
word_eigen_vec
# Singular value
sig<jupyter_output><empty_output><jupyter_text>$$\text{word vector} = \Sigma V^T$$<jupyter_code>np.diag(sig[:2])
word_eigen_vec.T
word_vec = np.dot(np.diag(sig[:2]), word_eigen_vec.T)
# Sigular value가 weight로 사용된 word vector
word_vec.T
word_vec
corpus
# Plotting 을 위해 다음과 같이 x축 y축을 구분한다.
# x 축
w_x = word_vec[0]
# y 축
w_y = word_vec[1]
# Words를 plotting에 나타내기 위해서
words = corpus.keys()
words
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib as mpl

mpl.rcParams['figure.figsize'] = (8, 8)

fig, ax = plt.subplots()

# Scatter
ax.scatter(w_x, w_y, s=100)

# Scatter에 라벨링을 한다.
for i, txt in enumerate(words):
    ax.annotate(txt, (w_x[i], w_y[i]), fontsize=20)  # free와 live가 겹쳐지는 위치<jupyter_output><empty_output><jupyter_text>4.2. Document Vectors<jupyter_code>doc_eigen_vec = U[:, :2]
# Doc-Doc matrix의 eigenvectors
doc_eigen_vec
# Singular value
sig
# Sigular value가 weight로 사용된 doc vector
doc_vec = np.dot(doc_eigen_vec, np.diag(sig[:2]))
doc_vec
# Plotting을 위해서 다음과 같이 나눈다.
doc_x = doc_vec[:, 0]
doc_y = doc_vec[:, 1]
fig, ax = plt.subplots()

# Scatter
ax.scatter(doc_x, doc_y, marker='*', s=100)

# Scatter에 라벨링을 한다.
for i, txt in enumerate(['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7']):
    ax.annotate(txt, (doc_x[i], doc_y[i]), fontsize=20)
# Document와 word를 함께 나타내보겠다.
fig, ax = plt.subplots()
ax.scatter(w_x, w_y, s=100)
for i, txt in enumerate(words):
    ax.annotate(txt, (w_x[i], w_y[i]), fontsize=20)
    
ax.scatter(doc_x, doc_y, marker='*', s=100)
for i, txt in enumerate(['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7']):
    ax.annotate(txt, (doc_x[i], doc_y[i]), fontsize=20)<jupyter_output><empty_output><jupyter_text>5. Query, document distance 구하기  5.1. Query<jupyter_code># Query : die, dagger
q1 = word_vec[:, 7]
print('machine', q1)

q2 = word_vec[:, 8]
print('python', q2)

q3 = word_vec[:, 5]
print('learning', q3)
query = (q1 + q2 + q3) / 3
print('machine python learning :', query)<jupyter_output>machine python learning : [-1.17875156 -0.00828871]<jupyter_text>5.2. Query와 document의 거리 : Word embedding 공간에서는 cosine similarity를 사용$$\text{Cosine Similarity} = \frac{d_i q}{|d_i| |q|}$$<jupyter_code>def get_consine_similarity(doc, query):
    dot_prod = np.dot(doc, query)
    len_doc = np.sqrt(sum(doc ** 2))
    len_query = np.sqrt(sum(query ** 2))
    
    return dot_prod / (len_doc * len_query)
result = {}
for idx, doc in zip(docs, doc_vec):
    result[get_consine_similarity(doc, query)] = idx
docs
result<jupyter_output><empty_output><jupyter_text>5.3. 최종 결과<jupyter_code>import operator

# 최종결과를 sorting하라!
sorted(result.items(), key=operator.itemgetter(0), reverse=True)
fig, ax = plt.subplots()
ax.scatter(w_x, w_y, s=100)
for i, txt in enumerate(words):
    ax.annotate(txt, (w_x[i], w_y[i]), fontsize=20)
    
ax.scatter(doc_x, doc_y, marker='*', s=100)
for i, txt in enumerate(['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7']):
    ax.annotate(txt, (doc_x[i], doc_y[i]), fontsize=20)
    
ax.scatter(query[0], query[1], marker='v', s=200, color='red')
ax.annotate('query', (query[0], query[1]), fontsize=20)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.16493945674702737, 'fraction_numerical': 0.040362168648412786, 'mean_word_length': 4.876923076923077, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '21', 'dir': 'jupyter-structured-clean-dedup', 'id': '210261', 'license': 'MIT', 'n_tokens_mistral': 4689, 'n_tokens_neox': 4821, 'n_words': 989, 'path': 'machine-learning-lecture-notes-master/Lecture06_Sub_LSA_2.ipynb', 'repo_name': 'm1016m/NN', 'size': '9167'}