본문 바로가기
AI

임베딩

by 코낄2 2024. 1. 24.

1. 자연어의 특성

자연어를 기계가 이해할 수 있는 형태로 변환하기 위해서는 토큰화 작업을 통해 단어 사전을 생성하고, 이를 기계가 이해할 수 있는 언어로 표현해야 합니다.

 

1-1. 단어의 유사성과 모호성

사람은 주변 정보에 따라 숨겨진 의미를 파악하고 이해할 수 있으나, 기계는 학습의 부재 또는 잘못된 데이터로 의미를 파악하지 못하는 경우가 있습니다. 한 가지 형태의 단어에 여러 의미가 포함되어 생기는 중의성, 혹은 유사성과 모호성은 자연어 처리에서 매우 중요합니다.

  • 동형어(동형성): 형태는 같지만 의미가 다른 단어 (예: "배" - 과일, 배 - 선박)
  • 다의어(다의성): 하나의 형태가 여러 의미를 갖는 단어 (예: "머리" - 몸의 일부, 생물의 머리)
  • 동의어(동의성): 서로 다른 형태의 단어들이 동일한 의미를 가지는 단어 (예: "나이"와 "연세")
  • 상의어(상위어)와 하위어(하위어): 개념적 상위와 하위를 나타내는 단어들 (예: "동물" - 상위어, "고양이" - 하위어)

1-2. 언어의 모호성 해소

자연어 처리에서의 중의성 문제를 해소하기 위해 단어 중의성 해소(WSD) 알고리즘이 사용됩니다.

  • 지식 기반 단어 중의성 해소:
    • 지식 기반 알고리즘은 컴퓨터가 읽을 수 있는 사전이나 어휘집을 활용하여 단어의 의미를 추론합니다.
    • 데이터를 직접 선별하므로 노이즈가 적지만, 구축에 많은 리소스가 필요하며 데이터 편향이 발생할 수 있습니다.
    • 예시로는 WordNet이 영어 자연어 처리에서 활용되는 대표적인 지식 기반 리소스입니다.
  • 지도 학습 기반 단어 중의성 해소:
    • 지도 학습은 정답이 있는 데이터로 모델을 훈련시키는 방법입니다.
    • WSD에서는 단어의 세부 의미가 부착된 코퍼스를 사용하여 학습하며, 이를 통해 학습하지 않은 새로운 문장에서 의미를 판별합니다.
    • 높은 성능을 위해서는 높은 품질의 레이블이 필요하며, 충분한 데이터가 있을 경우 일반화된 환경에서 좋은 성능을 나타냅니다.
  • 비지도 학습 기반 단어 중의성 해소:
    • 비지도 학습은 정답이 없는 데이터로 학습하는 방법으로, 주로 WSI(Word Sense Induction) 작업과 연관됩니다.
    • 단어의 의미를 사전적인 의미에 연결하지 않고, 맥락을 기반으로 의미를 군집화하는 방식입니다.
    • 대규모 자연어 코퍼스에서 자동으로 학습 가능하나, 성능을 내는데 어려움이 있습니다.

2. 임베딩 구축 방법

2-1. 임베딩이란

임베딩은 자연어 처리 작업에서 자연어를 수치화하는 과정을 의미합니다. 이는 특징 추출을 통해 단어를 벡터로 표현하는 것이며, 토큰화 작업을 통해 구축한 단어 사전을 통해 이를 수행합니다.

 

2-2. 임베딩의 역할

  • 정보 함축: 임베딩은 자연어의 의미적 정보를 포함하고 중요한 특징을 추출하여 벡터로 압축합니다. 이로써 단어 벡터 간의 사칙 연산이 가능해져 단어 간의 의미적, 문법적 관계를 도출할 수 있습니다. 또한, 단어 유추 평가 및 자연어 간 유사도 계산에 활용됩니다. https://word2vec.kr/search/
 

Korean Word2Vec

ABOUT 이곳은 단어의 효율적인 의미 추정 기법(Word2Vec 알고리즘)을 우리말에 적용해 본 실험 공간입니다. Word2Vec 알고리즘은 인공 신경망을 생성해 각각의 한국어 형태소를 1,000차원의 벡터 스페이

word2vec.kr

  • 자연어를 벡터로 표현하면 코사인 유사도를 활용하여 두 벡터 간 유사도를 계산할 수 있습니다. 코사인 유사도는 -1이상 1이하의 값을 가지며, 값이 1에 가까울수록 유사도가 높다고 판단합니다.
  • 전이학습 : 이미 만들어진 임베딩을 다른 작업의 학습을 위한 입력값으로 쓸 수 있습니다. 품질 좋은 임베딩을 사용할수록 자연어처리 작업의 학습 속도와 성능이 향상됩니다. 매번 새로운 것을 학습하는 scratch부터 시작하면 오랜 시간이 걸립니다.
    • 파인 튜닝 (Fine-tuning): 전이 학습에 의해 얻은 임베딩을 초기화로 사용하여, 새로운 작업을 학습하는 과정에서 모델의 성능을 더욱 향상시키는 기술입니다. 새로운 데이터에 맞게 모델을 미세 조정하여 성능을 최적화합니다.

2-3. 단어 출현 빈도에 기반한 임베딩 구축 방법

  • 원 핫 인코딩
    • 자연어를 0과 1로 구별하는 인코딩 방법으로, 단어의 인덱스에 1, 나머지에 0을 할당하는 벡터 표현입니다.
    • 희소 행렬로 표현되어 효율성이 떨어지고 단어 유사도를 반영하지 못합니다.
  • Bag of Words (BoW)
    • 단어들의 '출현 빈도'에 집중하는 방법으로, 각 단어에 고유한 정수 인덱스를 부여하여 단어 집합을 생성하고 각 인덱스의 위치에 단어 토큰의 등장 횟수를 기록한 벡터를 만듭니다.
    • 단어 단위의 압축 방식이기 때문에 희소 문제와 단어 순서를 반영하지 못합니다.
  • TF-IDF (Term Frequency-Inverse Document Frequency)
    • 단어의 빈도와 역문서 빈도를 고려하여 단어의 중요도를 가중치로 부여하는 표현 방법입니다.
    • 문서의 유사도를 구하는 작업, 검색 시스템에서 검색 결과의 중요도를 정하는 작업, 문서 내에서 특정 단어의 중요도를 구하는 작업 등에서 효과적으로 쓰일 수 있습니다.
    • 여전히 단어의 빈도로 판단하는 방식이기 때문에 맥락적 유사도를 반영하지 못하는 한계가 있습니다.

2-4. 단어의 순서

  • 통계 기반 언어 모델
    • 단어가 n개 주어졌을 때 n개의 단어가 동시에 나타날 확률을 반환하는 모델입니다.
    • 단어 시퀀스를 확률적으로 표현하여 언어의 구조를 파악합니다.
  • 딥러닝 기반 언어 모델
    • 딥러닝을 활용하여 입력과 출력 사이의 관계를 유연하게 정의하며 확률 모델로 동작할 수 있게 합니다.
    • MLM (Masked Language Modeling; 문장 중간에 마스크를 씌워서 어떤 단어가 올지 예측하는 과정에서 학습)과 Next Token Prediction(단어 시퀀스를 가지고 다음 단어를 예측하는 과정에서 학습)은 대표적인 예로, 각각 BERT와 GPT 등이 이에 해당합니다.

3. 텍스트 유사도

텍스트 유사도는 두 자연어 텍스트가 얼마나 유사한지를 나타내는 방법을 의미하며, 주관적인 척도이기 때문에 정량화하는 방법이 중요합니다.

3-1. 유클리디안 거리 기반 유사도

  • 두 점 사이의 거리를 측정하는 유클리디안 거리 공식을 사용하여 문서의 유사도를 측정합니다.
  • 거리가 가까울수록 유사도가 높다고 판단합니다.
  • 자연어 처리뿐만 아니라 다양한 알고리즘 분야에서 범용적으로 사용되는 거리 측정 기법입니다.

3-2. 맨하탄 거리 기반 유사도

  • 맨하탄 거리 공식을 사용하여 문서의 유사도를 측정합니다.
  • 유클리드 거리와 유사하지만 각 차원의 차를 곱하는 대신에 절대값을 바로 합산합니다.
  • 유클리드 거리 공식보다 값이 크거나 같으며, 다차원 공간에서 특별한 상황이 아니면 잘 사용되지 않습니다.

3-3. 코사인 유사도

  • 두 벡터의 코사인 각도를 이용하여 유사도를 측정합니다.
  • 완전히 동일한 방향일 때 1의 값을 가지며, 90도 각을 이룰 때 0, 반대 방향일 때 -1의 값을 가집니다.
  • 값이 1에 가까울수록 유사도가 높아지며, 자연어 처리에서 유사도 계산에 적합합니다.

3-4. 자카드 유사도

  • 두 문장을 각각 단어의 집합으로 만든 뒤, 두 집합을 통해 유사도를 측정합니다.
  • 공통된 단어의 개수를 두 집합의 합집합으로 나누어 계산합니다.
  • 값이 0과 1 사이로, 1에 가까울수록 유사도가 높아집니다.

4. 유사도 측정 실습

sen_1 = '오늘 점심에 배가 너무 고파서 밥을 너무 많이 먹었다.'
sen_2 = '오늘 점심에 배가 고파서 밥을 많이 먹었다.'
sen_3 = '오늘 배가 너무 고파서 점심에 밥을 너무 많이 먹었다.'
sen_4 = '오늘 점심에 배가 고파서 버스를 많이 먹었다.'
sen_5 = '어제 저녁에 밥을 너무 많이 먹었더니 배가 부르다.'
sen_6 = '이따가 오후 7시에 출발하는 비행기가 3시간 연착 되었다고 하네요.'

training_documents= [sen_1, sen_2, sen_3, sen_4, sen_5, sen_6]
# Bag of Words 기반 문서-단어 행렬을 활용한 문장 간 유사도 측정
from sklearn.feature_extraction.text import CountVectorizer
vertorizer = CountVectorizer()
vertorizer.fit(training_documents)
word_idx = vertorizer.vocabulary_
word_idx
{'오늘': 15,
 '점심에': 19,
 '배가': 9,
 '너무': 3,
 '고파서': 2,
 '밥을': 8,
 '많이': 5,
 '먹었다': 6,
 '버스를': 10,
 '어제': 13,
 '저녁에': 18,
 '먹었더니': 7,
 '부르다': 11,
 '이따가': 17,
 '오후': 16,
 '7시에': 1,
 '출발하는': 20,
 '비행기가': 12,
 '3시간': 0,
 '연착': 14,
 '되었다고': 4,
 '하네요': 21}
# word_idx를 idx 순서대로 정렬
for key, idx in sorted(word_idx.items()):
    print(f'{key}:{idx}')
3시간:0
7시에:1
고파서:2
너무:3
되었다고:4
많이:5
먹었다:6
먹었더니:7
밥을:8
배가:9
버스를:10
부르다:11
비행기가:12
어제:13
연착:14
오늘:15
오후:16
이따가:17
저녁에:18
점심에:19
출발하는:20
하네요:21
# word_idx에 따라 dataframe을 생성
# 컬럼: key, 인덱스: 문장idx, 값: 빈도수
import pandas as pd

result = []
vocab = list(word_idx.keys())
vocab
['오늘',
 '점심에',
 '배가',
 '너무',
 '고파서',
 '밥을',
 '많이',
 '먹었다',
 '버스를',
 '어제',
 '저녁에',
 '먹었더니',
 '부르다',
 '이따가',
 '오후',
 '7시에',
 '출발하는',
 '비행기가',
 '3시간',
 '연착',
 '되었다고',
 '하네요']
for i in range(len(training_documents)):
    result.append([])
    d = training_documents[i]
    for j in range(len(vocab)):
        target = vocab[j]
        result[-1].append(d.count(target))

tf = pd.DataFrame(result, columns=vocab)

  • 유사도를 측정할 문장들을 문장-단어 행렬 기반 임베딩으로 변환
vector_sen_1 = vertorizer.transform([sen_1]).toarray()[0]
vector_sen_2 = vertorizer.transform([sen_2]).toarray()[0]
vector_sen_3 = vertorizer.transform([sen_3]).toarray()[0]
vector_sen_4 = vertorizer.transform([sen_4]).toarray()[0]
vector_sen_5 = vertorizer.transform([sen_5]).toarray()[0]
vector_sen_6 = vertorizer.transform([sen_6]).toarray()[0]

print(vector_sen_1) // [0 0 1 2 0 1 1 0 1 1 0 0 0 0 0 1 0 0 0 1 0 0]
print(vector_sen_2) // [0 0 1 0 0 1 1 0 1 1 0 0 0 0 0 1 0 0 0 1 0 0]
print(vector_sen_3) // [0 0 1 2 0 1 1 0 1 1 0 0 0 0 0 1 0 0 0 1 0 0]
print(vector_sen_4) // [0 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 0 0 0 1 0 0]
print(vector_sen_5) // [0 0 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 1 0 0 0]
print(vector_sen_6) // [1 1 0 0 1 0 0 0 0 0 0 0 1 0 1 0 1 1 0 0 1 1]

 

# 코사인 기반 유사도 계산

sen_1 = '오늘 점심에 배가 너무 고파서 밥을 너무 많이 먹었다.'
sen_2 = '오늘 점심에 배가 고파서 밥을 많이 먹었다'
sen_3 = '오늘 배가 너무 고파서 점심에 밥을 너무 많이 먹었다'
sen_4 = '오늘 점심에 배가 고파서 버스를 많이 먹었다.'
sen_5 = '어제 저녁에 밥을 너무 많이 먹었더니 배가 부르다'
sen_6 = '이따가 오후 7시에 출발하는 비행기가 3시간 연착 되었다고 하네요'

 

  • sen_1, sen_2: 의미가 유사한 문장 간 유사도 계산(조사 생략)
  • sen_1, sen_3: 의미가 유사한 문장 간 유사도 계산(순서 변경)
  • sen_2, sen_4: 문장 내 단어를 임의의 단어로 치환한 문장과 원 문장 간 유사도 계산
  • sen_1, sen_5: 의미는 다르지만 비슷한 주제를 가지는 문장 간 유사도 계산
  • sen_1, sen_6: 의미가 서로 다른 문장 간 유사도 계산
import numpy as np
from numpy import dot
from numpy.linalg import norm

def cos_sim(A,B):
    return dot(A,B)/(norm(A)*norm(B))
print(f'sen_1, sen_2: {cos_sim(vector_sen_1, vector_sen_2)}') // sen_1, sen_2: 0.7977240352174656
print(f'sen_1, sen_3: {cos_sim(vector_sen_1, vector_sen_3)}') // sen_1, sen_3: 1.0
print(f'sen_2, sen_4: {cos_sim(vector_sen_2, vector_sen_4)}') // sen_2, sen_4: 0.857142857142857
print(f'sen_1, sen_5: {cos_sim(vector_sen_1, vector_sen_5)}') // sen_1, sen_5: 0.5330017908890261
print(f'sen_1, sen_6: {cos_sim(vector_sen_1, vector_sen_6)}') // sen_1, sen_6: 0.0
  • TF-IDF기반 문서-단어 행렬을 활용한 문장 간 유사도 측정
# vertor로 변환해줌
from sklearn.feature_extraction.text import TfidfVectorizer

tfidfv = TfidfVectorizer().fit(training_documents)
for key, idx in sorted(tfidfv.vocabulary_.items()):
    print(f'{key}:{idx}')
3시간:0
7시에:1
고파서:2
너무:3
되었다고:4
많이:5
먹었다:6
먹었더니:7
밥을:8
배가:9
버스를:10
부르다:11
비행기가:12
어제:13
연착:14
오늘:15
오후:16
이따가:17
저녁에:18
점심에:19
출발하는:20
하네요:21
tf_idf = tfidfv.transform(training_documents).toarray()
print(tf_idf) # 중요도 출력
[[0.         0.         0.28941449 0.67547293 0.         0.24993256
  0.28941449 0.         0.28941449 0.24993256 0.         0.
  0.         0.         0.         0.28941449 0.         0.
  0.         0.28941449 0.         0.        ]
 [0.         0.         0.39248775 0.         0.         0.33894457
  0.39248775 0.         0.39248775 0.33894457 0.         0.
  0.         0.         0.         0.39248775 0.         0.
  0.         0.39248775 0.         0.        ]
 [0.         0.         0.28941449 0.67547293 0.         0.24993256
  0.28941449 0.         0.28941449 0.24993256 0.         0.
  0.         0.         0.         0.28941449 0.         0.
  0.         0.28941449 0.         0.        ]
 [0.         0.         0.34642121 0.         0.         0.29916243
  0.34642121 0.         0.         0.29916243 0.58392899 0.
  0.         0.         0.         0.34642121 0.         0.
  0.         0.34642121 0.         0.        ]
 [0.         0.         0.         0.29913919 0.         0.22136971
  0.         0.43208699 0.25633956 0.22136971 0.         0.43208699
  0.         0.43208699 0.         0.         0.         0.
  0.43208699 0.         0.         0.        ]
 [0.33333333 0.33333333 0.         0.         0.33333333 0.
  0.         0.         0.         0.         0.         0.
  0.33333333 0.         0.33333333 0.         0.33333333 0.33333333
  0.         0.         0.33333333 0.33333333]]
# TF-IDF 행렬에서 얻어지는 유사도의 값을 0~1로 스케일링하기 위해 L1정규화를 진행
def l1_normalize(v):
    norm = np.sum(v)
    return v /norm
    
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix_l1 = tfidf_vectorizer.fit_transform(training_documents)
tfidf_norm_l1 = l1_normalize(tfidf_matrix_l1)

tf_sen_1 = tfidf_norm_l1[0:1]
tf_sen_2 = tfidf_norm_l1[1:2]
tf_sen_3 = tfidf_norm_l1[2:3]
tf_sen_4 = tfidf_norm_l1[3:4]
tf_sen_5 = tfidf_norm_l1[4:5]
tf_sen_6 = tfidf_norm_l1[5:6]

tf_sen_1.toarray()
array([[0.        , 0.        , 0.01788756, 0.04174829, 0.        ,
        0.01544734, 0.01788756, 0.        , 0.01788756, 0.01544734,
        0.        , 0.        , 0.        , 0.        , 0.        ,
        0.01788756, 0.        , 0.        , 0.        , 0.01788756,
        0.        , 0.        ]])
  • 유클리디안 거리 기반 유사도 측정
from sklearn.metrics.pairwise import euclidean_distances
euclidean_distances(tf_sen_1, tf_sen_2) // array([[0.04479254]])
def euclidean_distances_value(vec_1, vec_2):
    return round(euclidean_distances(vec_1, vec_2)[0][0],3)
    
print(f'sen_1, sen2: {euclidean_distances_value(tf_sen_1, tf_sen_2)}') // sen_1, sen2: 0.045
print(f'sen_1, sen3: {euclidean_distances_value(tf_sen_1, tf_sen_3)}') // sen_1, sen3: 0.0
print(f'sen_2, sen4: {euclidean_distances_value(tf_sen_2, tf_sen_4)}') // sen_2, sen4: 0.044
print(f'sen_1, sen5: {euclidean_distances_value(tf_sen_1, tf_sen_5)}') // sen_1, sen5: 0.068
print(f'sen_1, sen6: {euclidean_distances_value(tf_sen_1, tf_sen_6)}') // sen_1, sen6: 0.087
  • 코사인 유사도 측정
def cosine_similarity_value(vec_1, vec_2):
    return round(cosine_similarity(vec_1,vec_2)[0][0], 2)

print(f'sen_1, sen2: {cosine_similarity_value(tf_sen_1, tf_sen_2)}') // sen_1, sen2: 0.74
print(f'sen_1, sen3: {cosine_similarity_value(tf_sen_1, tf_sen_3)}') // sen_1, sen3: 1.0
print(f'sen_2, sen4: {cosine_similarity_value(tf_sen_2, tf_sen_4)}') // sen_2, sen4: 0.75
print(f'sen_1, sen5: {cosine_similarity_value(tf_sen_1, tf_sen_5)}') // sen_1, sen5: 0.39
print(f'sen_1, sen6: {cosine_similarity_value(tf_sen_1, tf_sen_6)}') // sen_1, sen6: 0.0

# 기존에 직접 계산한 결과
# sen_1, sen_2: 0.7977240352174656
# sen_1, sen_3: 1.0
# sen_2, sen_4: 0.857142857142857
# sen_1, sen_5: 0.5330017908890261
# sen_1, sen_6: 0.0

 

언어 모델을 활용한 문장 간 유사도 측정

# 설치
!pip install transformers
from transformers import AutoModel, AutoTokenizer, BertTokenizer

MODEL_NAME = 'bert-base-multilingual-cased'
model = AutoModel.from_pretrained(MODEL_NAME)
# AutoModel :모델을 다운, 설치, 전이학습도 사용하게 해줌

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
bert_sen_1 = tokenizer(sen_1, return_tensors = 'pt') # pt : 파이토치 텐서
bert_sen_2 = tokenizer(sen_2, return_tensors = 'pt')
bert_sen_3 = tokenizer(sen_3, return_tensors = 'pt')
bert_sen_4 = tokenizer(sen_4, return_tensors = 'pt')
bert_sen_5 = tokenizer(sen_5, return_tensors = 'pt')
bert_sen_6 = tokenizer(sen_6, return_tensors = 'pt')

# bert_sen_1
# input_ids : 값은 bert모델 단어사전에서 매치시킨 단어의 index값

{'input_ids': tensor([[   101,   9580, 118762,   9668,  71013,  10530,   9330,  11287,   9004,
          32537,   8888,  46150,  12424,   9327,  10622,   9004,  32537,  47058,
           9266,  17706,    119,    102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}

 

  • BERT 모델은 주로 두 가지 종류의 출력을 제공합니다. 하나는 각 토큰의 출력(last_hidden_states)이고, 다른 하나는 풀링된 전체 시퀀스에 대한 출력(pooler_output)입니다. pooler_output은 주로 전체 시퀀스를 대표하는 하나의 벡터로 요약된 표현을 제공합니다.
  • pooler_output : BERT 모델에서 나오는 마지막 레이어의 출력. =입력 텍스트에 대한 최종 문맥
sen_1_outputs = model(**bert_sen_1)
sen_1_pooler_output = sen_1_outputs.pooler_output

sen_2_outputs = model(**bert_sen_2)
sen_2_pooler_output = sen_2_outputs.pooler_output

sen_3_outputs = model(**bert_sen_3)
sen_3_pooler_output = sen_3_outputs.pooler_output

sen_4_outputs = model(**bert_sen_4)
sen_4_pooler_output = sen_4_outputs.pooler_output

sen_5_outputs = model(**bert_sen_5)
sen_5_pooler_output = sen_5_outputs.pooler_output

sen_6_outputs = model(**bert_sen_6)
sen_6_pooler_output = sen_6_outputs.pooler_output
from torch import nn

cos_sim = nn.CosineSimilarity(dim=1, eps=1e-6) 
# eps : 분모가 0이 되는걸 방지하기 위해 아주 작은 소수를 기본으로 넣어줌
print(f'sen_1, sen2: {cos_sim(sen_1_pooler_output, sen_2_pooler_output)}')
// sen_1, sen2: tensor([0.9901], grad_fn=<SumBackward1>)
print(f'sen_1, sen3: {cos_sim(sen_1_pooler_output, sen_3_pooler_output)}')
// sen_1, sen3: tensor([0.9972], grad_fn=<SumBackward1>)
print(f'sen_2, sen4: {cos_sim(sen_2_pooler_output, sen_4_pooler_output)}')
// sen_2, sen4: tensor([0.9906], grad_fn=<SumBackward1>)
print(f'sen_1, sen5: {cos_sim(sen_1_pooler_output, sen_5_pooler_output)}')
// sen_1, sen5: tensor([0.9744], grad_fn=<SumBackward1>)
print(f'sen_1, sen6: {cos_sim(sen_1_pooler_output, sen_6_pooler_output)}')
// sen_1, sen6: tensor([0.9533], grad_fn=<SumBackward1>)

 

'AI' 카테고리의 다른 글

[논문 리뷰]attention 매커니즘  (0) 2024.01.31
워드 임베딩 시각화  (0) 2024.01.31
데이터 전처리 실습  (0) 2024.01.19
자연어 처리 진행 순서  (0) 2024.01.19
자연어 처리 개요  (0) 2024.01.16