<jupyter_start><jupyter_text>Word Embedding- 단어 임베딩을 계산하는 튜토리얼입니다.- **참고문헌** : https://github.com/DerekChia/word2vec_numpy  말뭉치 읽어들이기형태소 분석은 이미 수행되어 있다고 가정합니다<jupyter_code>text = "1 8 3 9 년 바그너 는 괴테 의 파우스트 을 처음 읽 고 그 내용 에 마음 이 끌려 이 를 소재 로 해서 하나 의 교향곡 을 쓰 려는 뜻 을 갖 는다 . 이 시기 바그너 는 1 8 3 8 년 에 빛 독촉 으로 산전수전 을 다 걲은 상황 이 라 좌절 과 실망 에 가득 했으며 메피스토펠레스 를 만나 는 파우스트 의 심경 에 공감 했 다고 한다 . 또한 파리 에서 아 브네 크 의 지휘 로 파리 음악원 관현악단 이 연주 하 는 베토벤 의 교향곡 9 번 을 듣 고 깊 은 감명 을 받 았 는데 , 이것 이 이듬해 1 월 에 파우스트 의 서곡 으로 쓰여진 이 작품 에 조금 이 라도 영향 을 끼쳤 으리라는 것 은 의심 할 여지 가 없 다 . 여기 의 라단조 조성 의 경우 에 도 그 의 전기 에 적혀 있 는 것 처럼 단순 한 정신 적 피로 나 실의 가 반영 된 것 이 아니 라 베토벤 의 합창 교향곡 조성 의 영향 을 받 은 것 을 볼 수 있 다 . 그렇게 교향곡 작곡 을 1 8 3 9 년 부터 4 0 년 에 걸쳐 파리 에서 착수 했으나 1 악장 을 쓴 뒤 에 중단 했 다 . 또한 작품 의 완성 과 동시 에 그 는 이 서곡 ( 1 악장 ) 을 파리 음악원 의 연주회 에서 연주 할 파트 보 까지 준비 하 였으나 , 실제로 는 이루 어 지지 는 않 았 다 . 결국 초연 은 4 년 반 이 지난 후 에 드레스덴 에서 연주 되 었 고 재연 도 이루 어 졌 지만 , 이후 에 그대로 방치 되 고 말 았 다 . 그 사이 에 그 는 리엔치 와 방황 하 는 네덜란드인 을 완성 하 고 탄호이저 에 도 착수 하 는 등 분주 한 시간 을 보냈 는데 , 그런 바쁜 생활 이 이 곡 을 잊 게 한 것 이 아닌가 하 는 의견 도 있 다 . 바그너 는 괴테 의 파우스트 를 읽 고 무엇 을 쓰 고자 했 는가 ? 교향곡 바그너 는 교향곡 작곡 을 어디 까지 쓴 뒤 에 중단 했 는가 ? 1 악장 바그너 가 파우스트 서곡 을 쓸 때 어떤 곡 의 영향 을 받 았 는가 ? 베토벤 의 교향곡 9 번 1 8 3 9 년 바그너 가 교향곡 의 소재 로 쓰 려고 했 던 책 은 ? 파우스트 파우스트 서곡 의 라단조 조성 이 영향 을 받 은 베토벤 의 곡 은 ? 합창 교향곡 바그너 가 파우스트 를 처음 으로 읽 은 년 도 는 ? 1 8 3 9 바그너 가 처음 교향곡 작곡 을 한 장소 는 ? 파리 바그너 의 1 악장 의 초연 은 어디 서 연주 되 었 는가 ? 드레스 덴 한편 1 8 4 0 년 부터 바그너 와 알 고 지내 던 리스트 가 잊혀져 있 던 1 악장 을 부활 시켜 1 8 5 2 년 에 바이마르 에서 연주 했 다 . 이것 을 계기 로 바그너 도 이 작품 에 다시 관심 을 갖 게 되 었 고 , 그 해 9 월 에 는 총보 의 반환 을 요구 하 여 이 를 서곡 으로 간추린 다음 수정 을 했 고 브라이트 코프 흐 & 헤르 텔 출판사 에서 출판 할 개정판 도 준비 했 다 . 1 8 5 3 년 5 월 에 는 리스트 가 이 작품 이 수정 되 었 다는 것 을 인정 했 지만 , 끝내 바그너 의 출판 계획 은 무산 되 고 말 았 다 . 이후 1 8 5 5 년 에 리스트 가 자신 의 작품 파우스트 교향곡 을 거의 완성 하 여 그 사실 을 바그너 에게 알렸 고 , 바그너 는 다시 개정 된 총보 를 리스트 에게 보내 고 브라이트 코프 흐 & 헤르 텔 출판사 에 는 2 0 루이 의 금 을 받 고 팔 았 다 . 또한 그 의 작품 을 “ 하나하나 의 음표 가 시인 의 피 로 쓰여졌 다 ” 며 극찬 했 던 한스 폰 뷜로 가 그것 을 피아노 독주 용 으로 편곡 했 는데 , 리스트 는 그것 을 약간 변형 되 었 을 뿐 이 라고 지적 했 다 . 이 서곡 의 총보 첫머리 에 는 파우스트 1 부 의 내용 중 한 구절 을 인용 하 고 있 다 . 바그너 의 작품 을 시인 의 피 로 쓰여졌 다고 극찬 한 것 은 누구 인가 ? 한스 폰 뷜로 잊혀져 있 는 파우스트 서곡 1 악장 을 부활 시킨 것 은 누구 인가 ? 리스트 바그너 는 다시 개정 된 총보 를 얼마 를 받 고 팔 았 는가 ? 2 0 루이 의 금 파우스트 교향곡 을 부활 시킨 사람 은 ? 리스트 파우스트 교향곡 을 피아노 독주 용 으로 편곡 한 사람 은 ? 한스 폰 뷜로 1 악장 을 부활 시켜 연주 한 사람 은 ? 리스트 파우스트 교향곡 에 감탄 하 여 피아노 곡 으로 편곡 한 사람 은 ? 한스 폰 뷜로 리스트 가 바그너 와 알 게 된 연도 는 ? 1 8 4 0 년 이 작품 은 라단조 , Sehr gehalten ( 아주 신중 하 게 ) , 4 / 4 박자 의 부드러운 서주 로 서주 로 시작 되 는데 , 여기 에 는 주요 주제 , 동기 의 대부분 이 암시 , 예고 되 어 있 다 . 첫 부분 의 저음 주제 는 주요 주제 ( 고뇌 와 갈망 동기 , 청춘 의 사랑 동기 ) 를 암시 하 고 있 으며 , 제 1 바이올린 으로 더욱 명확 하 게 나타난다 . 또한 그것 을 이어받 는 동기 도 중요 한 역할 을 한다 . 여기 에 새로운 소재 가 더 해진 뒤 에 새로운 주제 도 연주 된다 . 주요부 는 Sehr bewegt ( 아주 격동 적 으로 ) , 2 / 2 박자 의 자유 로운 소나타 형식 으로 매우 드라마틱 한 구상 과 유기 적 인 구성 을 하 고 있 다 . 여기 에 는 지금 까지 의 주제 나 소재 외 에 도 오보에 에 의한 선율 과 제 2 주제 를 떠올리 게 하 는 부차 적 인 주제 가 더 해 지 는데 , 중간부 에서 는 약보 3 이 중심 이 되 고 제 2 주제 는 축소 된 재현부 에서 D 장조 로 재현 된다 . 마지막 에 는 주요 주제 를 회상 하 면서 조용히 마친다 . 서주 에 는 무엇 이 암시 되 어 있 는가 ? 주제 , 동기 첫 부분 에 는 어떤 악기 를 사용 해 더욱 명확 하 게 나타내 는가 ? 제 1 바이올린 주요부 는 어떤 형식 으로 되 어 있 는가 ? 소나타 형식 첫 부분 의 주요 주제 를 암시 하 는 주제 는 ? 저음 주제 제 2 주제 의 축소 된 재현부 의 조성 은 ? D 장조 곡 이 시작 할 때 의 박자 는 ? 4 / 4 박자 이 곡 의 주요 주제 는 ? 고뇌 와 갈망 동기 , 청춘 의 사랑 동기 제 2 주제 에선 무슨 장조 로 재현 되 는가 ? D 장조"
corpus = [[word for word in text.split()]]
corpus<jupyter_output><empty_output><jupyter_text>하이퍼 파라메터 설정- `window size` : 타겟단어 앞뒤로 몇 개의 단어를 볼지- `dimension` : word2vec 임베딩의 차원 수- `epochs` : 말뭉치를 몇 번 학습할지- `learning_rate` : 학습률<jupyter_code>settings = {
    'window_size': 2,
    'dimension': 10,
    'epochs': 1000,
    'learning_rate': 0.05
}
settings<jupyter_output><empty_output><jupyter_text>Vocabulary 만들기단어 임베딩 구축 대상 어휘 목록을 만듭니다.<jupyter_code>from collections import defaultdict
word_counts = defaultdict(int)
for row in corpus:
    for word in row:
        word_counts[word] += 1
word_counts
vocab_size = len(word_counts.keys())
vocab = list(word_counts.keys())
vocab_size
vocab<jupyter_output><empty_output><jupyter_text>컴퓨터는 단어를 이해하지 못하므로, 인덱스로 바꿔줘야 합니다. (1) 단어를 넣으면 인덱스 값을 반환하는 dictionary `word_to_index`, (2) 인덱스 값을 넣으면 단어를 반환하는 dictionary `index_to_word`를 만듭시다<jupyter_code>word_to_index = dict((word, i) for i, word in enumerate(vocab))
index_to_word = dict((i, word) for i, word in enumerate(vocab))
word_to_index
index_to_word<jupyter_output><empty_output><jupyter_text>One-hot-representation 만들기단어를 넣으면 원핫 벡터를 만들어주는 함수를 만들어 봅시다.<jupyter_code>import numpy as np
def word2onehot(word):
    word_vec = np.zeros(vocab_size)
    word_index = word_to_index[word]
    word_vec[word_index] = 1
    return word_vec
word2onehot("바그너")<jupyter_output><empty_output><jupyter_text>Skip-Gram 학습데이터 만들기Skip-Gram은 `타겟 단어(target word)`로 `주변 단어(context word)`를 맞추는 과정에서 학습하는 모델입니다. `(타겟 단어, 주변 단어)` 쌍을 만들어 봅시다.<jupyter_code>def generate_skipgram_training_data():
    training_data = []
    for sentence in corpus:
        sent_len = len(sentence)
        for i, word in enumerate(sentence):
            w_target = sentence[i]
            for j in range(i - settings['window_size'], i + settings['window_size'] + 1):
                if j != i and j <= sent_len - 1 and j >= 0:
                    w_context = sentence[j]
                    training_data.append([w_target, w_context])
    return training_data
train_data = generate_skipgram_training_data()
train_data<jupyter_output><empty_output><jupyter_text>parameter initializationSkip-Gram 모델의 trained parameter를 초기화합니다. $\mathbf{U}$는 타겟 단어, $\mathbf{V}$는 주변 단어와 관련한 행렬입니다<jupyter_code>np.random.seed(7)
parameters = {}
parameters["U"] = np.random.randn(vocab_size, settings["dimension"]) * 0.01
parameters["V"] = np.random.randn(settings["dimension"], vocab_size) * 0.01
parameters["U"]
parameters["V"]<jupyter_output><empty_output><jupyter_text>파라메터 업데이트 과정을 추적하기 위해 초기 행렬 값을 `old_param`에 복사해 둡니다.<jupyter_code>import copy
old_param = {'U': copy.deepcopy(parameters["U"]), 'V': copy.deepcopy(parameters["V"])}<jupyter_output><empty_output><jupyter_text>Forward ComputationSkip-Gram은 타깃 단어($t$)로 문맥 단어($c$)를 맞추는 과정에서 학습됩니다.𝐔 는 타겟 단어, 𝐕는 주변 단어와 관련한 행렬입니다.$\mathbf{u_{t}} = \text{onehot}_{t} \cdot \mathbf{U} \\\text{logit} = \mathbf{u_{t}} \cdot \mathbf{V} \\P(c|t) = \text{softmax(logit)}$<jupyter_code>def forward_computation(target_word_onehot_vectors, parameters):
    # target_word_onehot_vectors shape : [batch_size, vocab_size]
    # U shape : [vocab_size, settings["dimension"]]
    # target_word_vectors shape : [batch_size, settings["dimension"]]
    target_word_vectors = np.dot(target_word_onehot_vectors, parameters["U"])
    # logits shape : [batch_size, vocab_size]
    logits = np.dot(target_word_vectors, parameters["V"])
    # 0.0001 : underflow 방지
    softmax_probs = softmax(logits)
    #softmax_probs = np.divide(np.exp(logits), np.sum(np.exp(logits), axis=0, keepdims=True) + 0.001)
    if len(target_word_vectors.shape) == 1:
        target_word_vectors = np.expand_dims(target_word_vectors, axis=0)
    # 나중에 backprop을 하기 위한 데이터 캐시
    caches = {"target_word_vectors": target_word_vectors}
    return softmax_probs, caches<jupyter_output><empty_output><jupyter_text>softmax 함수 정의$\exp$는 1보다 크기 때문에 지수승을 거듭하면 지나치게 큰 수가 나옴이에 입력 벡터 $x$의 최대값을 $x$에서 빼줌이렇게 하더라도 $x$ 벡터 요소들의 대소관계는 바뀌지 않음<jupyter_code>np.exp(2000)
def softmax(x, axis=1):
    x = x - x.max(axis=axis, keepdims=True)
    y = np.exp(x)
    return y / y.sum(axis=axis, keepdims=True)<jupyter_output><empty_output><jupyter_text>Loss Computation<jupyter_code>def cross_entropy(target, softmax_prob):
    """
    targe: shape (batch_size, vocab_size)
    softmax_prob: output out of softmax. shape: (batch_size, vocab_size)
    """
    batch_size = softmax_prob.shape[0]
    cost = -(1 / batch_size) * np.sum(np.sum(target * np.log(softmax_prob + 0.001), axis=0, keepdims=True), axis=1)
    return cost<jupyter_output><empty_output><jupyter_text>Back Propagationhttps://ratsgo.github.io/deep%20learning/2017/10/02/softmax/<jupyter_code>def softmax_backward(target, softmax_prob):
    """
    target: labels onehot vector of training data. shape: (batch_size, vocab_size)
    softmax_prob: output out of softmax. shape: (batch_size, vocab_size)
    """
    return softmax_prob - target
def dense_backward(dL_dlogits, caches):
    V = parameters['V']
    batch_size = dL_dlogits.shape[0]
    target_word_vectors = caches['target_word_vectors']
    dL_dtarget = (1 / batch_size) * np.dot(dL_dlogits, V.T)
    dL_dV = np.dot(dL_dlogits.T, target_word_vectors)
    return dL_dtarget, dL_dV<jupyter_output><empty_output><jupyter_text>parameter update$\mathbf{u}_{t}$는 $\mathbf{U}$ 행렬에서 타겟 단어(t)에 해당하는 행 벡터를 참조(look)한 것따라서 $U$ 행렬의 t에 해당하는 행벡터만 업데이트함<jupyter_code>def update_parameters(parameters, dL_dtarget, dL_dV,
                      learning_rate, target_words_idexes):
    parameters['V'] -= learning_rate * dL_dV.T
    if type(target_words_idexes) == list:
        for target_word_idx, target_word_update in zip(target_words_idexes, dL_dtarget):
            parameters['U'][[target_word_idx], :] -= learning_rate * target_word_update
    else:
        parameters['U'][[target_words_idexes], :] -= learning_rate * dL_dtarget<jupyter_output><empty_output><jupyter_text>계산 예시<jupyter_code>target_word, context_word = train_data[100]
target_word
context_word
target_word_onehot_vector = word2onehot(target_word)
softmax_prob, caches = forward_computation([target_word_onehot_vector], parameters)
softmax_prob
np.sum(softmax_prob)
caches
context_word_onehot_vector = word2onehot(context_word)
loss = cross_entropy([context_word_onehot_vector], softmax_prob)
loss
dL_dlogits = softmax_backward([context_word_onehot_vector], softmax_prob)
dL_dlogits.shape
dL_dtarget, dL_dV = dense_backward(dL_dlogits, caches)
dL_dtarget.shape
dL_dV.shape
dL_dtarget
target_word
target_word_idx = word_to_index[target_word]
update_parameters(parameters, dL_dtarget, dL_dV, 1000, target_word_idx)
parameters["U"]
old_param["U"]
parameters["U"][target_word_idx, :]
old_param["U"][target_word_idx, :]
parameters["V"]
old_param["V"]<jupyter_output><empty_output><jupyter_text>train<jupyter_code>parameters = {}
parameters["U"] = np.random.randn(vocab_size, settings["dimension"]) * 0.01
parameters["V"] = np.random.randn(settings["dimension"], vocab_size) * 0.01
target_word_onehot_vectors = [word2onehot(target_word) for target_word, context_word in train_data]
context_word_onehot_vectors = [word2onehot(context_word) for target_word, context_word in train_data]
target_words_idexes = [word_to_index[target_word] for target_word, context_word in train_data]
for i in range(settings["epochs"]):
    softmax_prob, caches = forward_computation(target_word_onehot_vectors, parameters)
    loss = cross_entropy(context_word_onehot_vectors, softmax_prob)
    dL_dlogits = softmax_backward(context_word_onehot_vectors, softmax_prob)
    dL_dtarget, dL_dV = dense_backward(dL_dlogits, caches)
    update_parameters(parameters, dL_dtarget, dL_dV, settings['learning_rate'], target_words_idexes)
    if i % 100 == 0:
        print(loss)
def most_similar(query_word, topn=3, embeddings=parameters["U"]):
    # embeddings shape : [vocab_size, dimension]
    embedding_norms = np.linalg.norm(embeddings, axis=1)
    query_vector = embeddings[word_to_index[query_word]] / np.linalg.norm(embeddings[word_to_index[query_word]])
    cosine_simiarities = []
    for word_idx, (word_vector, norm) in enumerate(zip(embeddings, np.linalg.norm(embeddings, axis=1))):
        cosine_similarity = np.dot(query_vector, word_vector / norm)
        cosine_simiarities.append([index_to_word[word_idx], cosine_similarity])
    return sorted(cosine_simiarities, key=lambda x: x[1], reverse=True)[1:topn+1]
most_similar("파우스트", embeddings=parameters["U"])
most_similar("파우스트", embeddings=parameters["V"].T)
most_similar("파우스트", embeddings=parameters["U"] + parameters["V"].T)
most_similar("파우스트", embeddings=np.concatenate((parameters["U"], parameters["V"].T), axis=1))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0994949494949495, 'fraction_numerical': 0.014057239057239056, 'mean_word_length': 3.716554188169909, 'pattern_counts': {'":': 1, '<': 62, '<?xml version=': 0, '>': 62, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '216969', 'license': 'MIT', 'n_tokens_mistral': 7130, 'n_tokens_neox': 8237, 'n_words': 1724, 'path': 'python/nlp/.ipynb_checkpoints/1. word embedding-checkpoint.ipynb', 'repo_name': 'love-adela/snippets', 'size': '11880'}