AI/자연어처리(NLP)

[Stanford 강의] Assignment1

CSE 2025. 11. 26. 23:22

지금 듣고 있는 스탠포드 대학교의 자연어처리 수업은 단순히 유투브로 수업만 볼 수 있는 것이 아니고 과제도 직접 해 볼 수 있다.

 

 

Stanford CS 224N | Natural Language Processing with Deep Learning

Note: In the 2023–24 academic year, CS224N will be taught in both Winter and Spring 2024. --> Natural language processing (NLP) is a crucial part of artificial intelligence (AI), modeling how people share information. In recent years, deep learning appro

web.stanford.edu

과제와 관련된 파일들은 이 사이트에서 화면을 쭉 내리면 Events에 Assignment1에서 다운이 가능하다.

 

첫번째 과제는 주피터 노트북에서 수행하는 과제였다.

과제를 열어보면 기본적으로 데이터셋의 일부를 받아오는 코드가 이미 있고,

여러 모듈 임포트 문도 다 써놓아 추가적인 작업이 필요하지 않았다.


Part 1

part 1은 직접 co-occurrence 기반 방식을 직접 단계별로 구현해보는 것이다.

 

아래 코드가 데이터셋을 받아오는 코드이다.

def read_corpus():
    files = imdb_dataset["train"]["text"][:NUM_SAMPLES]
    return [[START_TOKEN] + [re.sub(r'[^\w]', '', w.lower()) for w in f.split(" ")] + [END_TOKEN] for f in files]

실제로 아래처럼 실행해보면

imdb_corpus = read_corpus()
pprint.pprint(imdb_corpus[:3], compact=True, width=100)
print("corpus size: ", len(imdb_corpus[0]))

각 문장 당 하나의 리스트로, 원소는 각 단어로 나오는것을 알 수 있다.

여기까지는 기본적으로 제공되는 코드이고 이제 첫 번째 문제가 나온다.

1.1 

첫번째 문제는 위의 2차원 리스트인 corpus를 입력으로 받아, 중복된 단어를 제거한 1차원 리스트를 정렬하여 출력하는 것이다.

def distinct_words(corpus):
    corpus_words = []
    n_corpus_words = -1
    
    # ------------------
    # Write your implementation here.
    flattened_list = [y for x in corpus for y in x]
    temp_list=set(flattened_list)
    corpus_words = list(temp_list)
    corpus_words.sort()
    n_corpus_words = len(corpus_words)
    # ------------------

    return corpus_words, n_corpus_words

나는 먼저 2차원 리스트를 1차원으로 평탄화 시켜주고,
set으로 변환 후 다시 리스트로 변환하여 중복값을 없애주었다.
set자체에 중복값을 처리해주는 기능이 있기 때문이다.
그 이후 sort하여 문제에서 요구하는 출력을 맞춰주었다.

내 구현이 맞는지 쉽게 확인할 수 있는 작은 규모의 테스트케이스도 있다.

# ---------------------
# Run this sanity check
# Note that this not an exhaustive check for correctness.
# ---------------------

# Define toy corpus
test_corpus = ["{} All that glitters isn't gold {}".format(START_TOKEN, END_TOKEN).split(" "), "{} All's well that ends well {}".format(START_TOKEN, END_TOKEN).split(" ")]
test_corpus_words, num_corpus_words = distinct_words(test_corpus)

# Correct answers
ans_test_corpus_words = sorted([START_TOKEN, "All", "ends", "that", "gold", "All's", "glitters", "isn't", "well", END_TOKEN])
ans_num_corpus_words = len(ans_test_corpus_words)

# Test correct number of words
assert(num_corpus_words == ans_num_corpus_words), "Incorrect number of distinct words. Correct: {}. Yours: {}".format(ans_num_corpus_words, num_corpus_words)

# Test correct words
assert (test_corpus_words == ans_test_corpus_words), "Incorrect corpus_words.\nCorrect: {}\nYours:   {}".format(str(ans_test_corpus_words), str(test_corpus_words))

# Print Success
print ("-" * 80)
print("Passed All Tests!")
print ("-" * 80)

 

1.2

두 번째 문제이다.

co-occurrence행렬을 만드는 것이 목표이다.

def compute_co_occurrence_matrix(corpus, window_size=4):
    words, n_words = distinct_words(corpus)
    M = None
    word2ind = {}
    
    # ------------------
    # Write your implementation here.

    for i in range(len(words)):
        word2ind[words[i]] = i #값넣어주기
        
    
    M = np.zeros((n_words,n_words))
    for sentence in corpus:
        for i, center_word in enumerate(sentence): #index와 word 같이 내놓음
            window_start_index = max(0, i-window_size)
            window_end_index = min(len(sentence), i+window_size+1)
            
            for j in range(window_start_index,window_end_index):
                if i==j:
                    continue
                in_window_word = sentence[j]
                M[word2ind[center_word],word2ind[in_window_word]]+=1
                
    # ------------------

    return M, word2ind

간단한 for문을 이용해 word2ind라는 딕셔너리에 단어를 key로, 위의 distinct word 에서 index를 value로 넣어주었다.
이 단어의 개수를 사이즈로 가지는 0으로 초기화된 정방행렬 M을 선언했다.
이후 corpus를 순회하며 이 행렬을 채우는 방식으로 코딩했다.
corpus는 2차원 리스트이다.
한 문장안의 단어들을 원소로 가진 리스트들이 더 큰 리스트 안에 담겨있다.

각 문장에 대해 co-occurrence행렬을 채워야하므로 먼저 window의 시작과 끝을 숫자로 나타내었다.
단순히 중심단어에서 ±window size 만큼 하면 문장의 양 끝에서는 범위에러가 날 수 있다.

이중 for문으로 각 문장과, 그 안의 중심단어에 대해 순회하면서, 윈도우 안의 단어가 본인이 아닐때 행렬의 값을 1추가하는 방식으로 구현했다.

sentence에서 단어를 하나씩 순회할 때, index도 같이 가져오도록 enumerate를 사용했다.

 

1.3

위에서 만든 M행렬을 dense하게 만드는 과정이다.

SVD를 사용한다.

SVD를 직접 구현할 필요없이 있는 함수를 가져다가 쓰면된다.

def reduce_to_k_dim(M, k=2):
    n_iters = 10    # Use this parameter in your call to `TruncatedSVD`
    M_reduced = None
    print("Running Truncated SVD over %i words..." % (M.shape[0]))
    
    # ------------------
    # Write your implementation here.
    svd = TruncatedSVD(n_components=k, n_iter=10, random_state=0)
    M_reduced = svd.fit_transform(M)  # M_reduced.shape = (N, k)

    
    # ------------------

    print("Done.")
    return M_reduced

 

1.4

이제 위에서 차원을 줄인 벡터를 시각화해보는 것이다.

def plot_embeddings(M_reduced, word2ind, words):

    # ------------------
    # Write your implementation here.
    plt.figure(figsize=(10,10))
    
    for i in words:
        index = word2ind[i]
        x,y = M_reduced[index]
        plt.scatter(x, y, marker='o', color='red')
        plt.text(x + 0.01, y + 0.01, i, fontsize=12)
        
    
    # ------------------

 

1.5

이 문제는 구현은 아니고 위에서 한 시각화를 보고 분석을 해보는 것이다.

 


Part 2

part 2는 GloVe에 대해 분석해보는 내용이다.

코딩을 해야할 것은 거의 없고, 여러 질문에 대한 답을 하는 문제들이다.

 

 

1. 어떤 다의어에 대해 이와 가장 가까운 벡터들을 살펴봤을 때,

그 뜻이 다양하게 나오지 않고 하나의 뜻만 나오는 문제

 

2. 사람의 직관과 다른 유사어,반의어 판단

 

3. 단어 유추에서 이상한 결과들

 

4. 모델이 인종,종교,성별 등에 대해 가진 편향

 

등에 대해 생각해볼 수 있다.