intro
-
빈도수 기반 표현은 단어 간 의미차를 표현할 수 없다.
단어-단어 거리를 계산해서 차이를 얘기하는 것이 어렵다. 유사도를 비교할 수는 있겠지만 논리적인 의미는 없다.
단지 컴퓨터에서 처리하기 적합한 자료 구조일 뿐이다. -
단어 자체의 의미를 다차원 공간에서 벡터화 필요
- 단어들 사이의 유사도 측정 가능
- 단어들 간의 평균 및 연산을 통해 추론 가능
-
벡터의 연산 ex
- king - man + woman = queen
- 한국 - 서울 + 도쿄 = 일본
-
장점 : dimension도 엄청나게 줄일 수 있고, 단어 간 연산도 가능하다.
Continuous Bag of Words (CBoW)
-
주변에 있는 단어들로 중간에 있는 단어들을 예측하는 방법이다.
- The fat cat (sat) on the mat.
{'The', 'fat', 'cat', 'on', 'the', 'mat'} 으로부터(context word) sat(center word)을 예측
- The fat cat (sat) on the mat.
-
윈도우 슬라이딩을 통해 데이터 셋 생성
- (필터와 똑같은 방식)
- 한 문장을 가지고 (중심단어+주변단어)를 필터처럼 하여 sliding
neural net language model
input layer: V(vocabulary 벡터가 여러 개(주변 단어 개수만큼)
V 하나는 1값이 하나, 나머지 다 0인 벡터
projection layer: y벡터에는 예측값들
output layer
- 신경망 구조를 가지나, activation 개념은 없다
- 차원 축소했다가 원 차원으로 차원 확대 (기존 신경망과의 차이점)
- 학습 알고리즘으로 구할 수 있다고 가정하면, softmax로 W matrix를 구하는 것이 목적.
- V:vocabulary 수, M:훨씬 작은 차원
- 뒤는 학습과정일 뿐이고, prediction하는게 아니다. W만 사용하는 것.
- (주의) W'는 transpose가 아니고 별도의 벡터를 의미한다.
word2vec Basic
word2vec은 CBOW 방식과 Skip-Gram 방식의 단어 임베딩을 구현한 C++ 라이브러리로 기본적인 임베딩 모형에 subsampling, negative sampling 등의 기법을 추가하여 학습 속도를 향상하였다. 파이썬에서는 gensim이라는 패키지에 Word2Vec
이라는 클래스를 임포트 할 수 있다.
Word2Vec 클래스를 이용하여 단어와 벡터를 생성할 수 있다.
from gensim.models import word2vec
sentences = [
['this', 'is', 'a', 'good', 'product'],
['it', 'is', 'a', 'excellent', 'product'],
['it', 'is', 'a', 'bad', 'product'],
['that', 'is', 'the', 'worst', 'product']
]
# 문장을 이용하여 단어와 벡터를 생성한다.
#size=M size, window=중심단어+주변단어 갯수, 단어가 최소 한 번 이상 반복되는 것들만 고려하겠다.
model = Word2Vec(sentences, size=20, window=3, min_count=1)
# 단어벡터를 구한다.
word_vectors = model.wv
print(word_vectors)
print(word_vectors["this"]) # W. 20차원. (위에서 size=20)
print(word_vectors["this"].shape) #(20,)
#result
<gensim.models.keyedvectors.Word2VecKeyedVectors object at 0x0000021E5CFF17B8>
[ 0.02357259 0.00649772 -0.00507605 0.00189502 0.02495623 -0.02366254
0.02455258 0.02163283 -0.00767915 -0.0008018 0.00795123 0.02067606
-0.00107131 -0.0188961 0.00398155 -0.00649158 0.02439296 -0.02208698
-0.00722194 0.0039971 ]
고정된 차원이 만들어졌기 때문에, DNN이든 CNN이든 붙여도 상관 없다.
- 생성된 단어 보기
vocabs = word_vectors.vocab.keys()
print(vocabs)
#dict_keys(['this', 'is', 'a', 'good', 'product', 'it', 'excellent', 'bad', 'that', 'the', 'worst'])
어떤 단어들이 생성되어있는지 볼 수 있다. min_count=1이기 떄문에 모든 단어들이 출력된다.
- 모든 단어벡터 출력
word_vectors_list = [word_vectors[v] for v in vocabs]
print(word_vectors_list)
- 두 단어의 유사도 출력하는 함수
model.similarity(w1='it', w2='this')
distance와 similarity는 반대 개념. similarity ~ cos(theta)
- finding the most similar words
model.most_similar('it', topn=2)
model.most_similar('it')
#[('worst', 0.3926549553871155),
('the', 0.148879736661911),
('bad', 0.1412423998117447),
('this', 0.12053025513887405),
('good', 0.12036717683076859),
('a', 0.088790163397789),
('product', 0.0051093995571136475),
('that', -0.1780233383178711),
('excellent', -0.28302836418151855),
('is', -0.28809720277786255)]
단어벡터들로 평균 벡터를 구할 수 있고, 이것으로 거리를 구해서 분류가 가능하다.
Read data
- read data
#파일 읽기 함수. 첫줄(헤더)를 제외하고 한 줄씩 읽어서 data 에 담아서 리턴 한다.
def read_data(filename):
with open(filename, encoding='utf-8', mode='r') as f:
data = [line.split('\t') for line in f.read().splitlines()]
data = data[1:] # header 제외
return data
ratings_train = read_data('nlp/ratings_train.txt')
- 형태소 분석
okt = Okt()
def tokenize(doc):
return ['/'.join(t) for t in okt.pos(doc, norm=True, stem=True)]
#형태소를 붙여주는 이유는 문맥에 따라서 다른 단어일 경우 때문에.
- 인풋 데이터 가공
#파일 중에서 영화 리뷰 데이터만 담기
docs = []
for row in ratings_train:
docs.append(row[1])
data = [tokenize(d) for d in docs]
문장 단위로 input을 가지고, 여기서 주변 단어와 중심 단어를 만들어야 한다.
문장 단위로 데이터를 주면 알아서 sliding하면서 데이터를 만들어 간다.
(word2vec 가 parsing까지 있는 건 아니고)
그 문장은 단어들의 리스트로 구성되어 있다.
문장 x 단어들의 2차원 형태로 input 넣어줘야 한다.
- 모델 생성
#모델 생성
w2v_model = word2vec.Word2Vec(data, size=100, window=3, min_count=2)
w2v_model.save('nlp/naver.model')
vocabs = w2v_model.wv.vocab.keys()
print(vocabs)
#dict_keys(['아/Exclamation', '더빙/Noun', '../Punctuation', '진짜/Noun', '짜증나다/Adjective', ...
벡터 연산
- 벡터 연산을 활용하여 the most similar word 찾기
print(w2v_model.wv.most_similar(positive=tokenize(u'남자 여배우'),
negative=tokenize(u'배우'), topn=5))
#[('여자/Noun', 0.8360661268234253), ('여자애/Noun', 0.7506942749023438), ('꼬마/Noun', 0.7407692670822144), ('아빠/Noun', 0.7093860507011414), ('고양이/Noun', 0.7076500058174133)]
남자 + 여배우 - 배우 = 여자
- the most similar word
w2v_model.wv.most_similar(positive=tokenize(u'정우성'))
#result
[('니콜슨/Noun', 0.904967188835144),
('박유천/Noun', 0.9010274410247803),
('김명민/Noun', 0.8953330516815186),
('배종옥/Noun', 0.8873140811920166),
('김상경/Noun', 0.8826473355293274),
('허준호/Noun', 0.8817464113235474),
('류승룡/Noun', 0.8814874887466431),
('조인성/Noun', 0.8805835247039795),
('숀펜/Noun', 0.8799116611480713),
('전도연/Noun', 0.8792793154716492)]
배우 이름을 넣으니 배우 이름들이 나온다. 연관성 있는 형태소들을 반환한다.
w2v_model.wv.most_similar(positive=tokenize(u'약하다'))
#result
[('부족하다/Adjective', 0.8432176113128662),
('산만하다/Adjective', 0.8223651647567749),
('억지스럽다/Adjective', 0.8083056807518005),
('부자연스럽다/Adjective', 0.8035701513290405),
('부실하다/Adjective', 0.7877820730209351),
('빈약하다/Adjective', 0.78594970703125),
('딸리다/Verb', 0.7788472175598145),
('밋밋하다/Adjective', 0.7785338163375854),
('떨어지다/Verb', 0.7781445980072021),
('빠르다/Adjective', 0.7769982218742371)]
부정적인 단어는 부정적인 단어들이 연관되어 있다.
cf) u'문자열'
은 unicode
b'문자열'
은 byte data
f'문자열'
은 위와는 다르지만, 안에 있는 문자열을 어떻게 포맷팅 할 것인지에 있어서 공통점을 갖는다.
차원 축소
vocabs = w2v_model.wv.vocab.keys()
print(len(vocabs)) #27477
vocab = list(w2v_model.wv.vocab) #list 아닌 걸 list로
X = w2v_model[vocab] #shape:(27477, 100)
tsne = TSNE(n_components=2) #통계적으로 유의한(variance가 최소가 되도록) 2개의 차원만 남김
X_tsne = tsne.fit_transform(X[:300,:])
#result
vocab[:10] #['아/Exclamation', '더빙/Noun', '../Punctuation', '진짜/Noun', '짜증나다/Adjective', '목소리/Noun', '흠/Noun', '.../Punctuation', '포스터/Noun', '보고/Noun']
X
#[[ 6.01853311e-01 3.56701761e-01 1.54575324e+00 ... -6.51699185e-01
5.92311621e-01 -4.35075574e-02]
[ 4.58722949e-01 -1.02985553e-01 -4.73003089e-01 ... 1.05785141e-02
-4.45425063e-01 -6.58629909e-02]
[ 9.63209644e-02 5.49817741e-01 -4.26770300e-01 ... 1.31252438e-01
-2.03027636e-01 -1.35950390e-02]
...
X_tsne.shape #(300, 2)
X_tsne
#[[-7.67433643e+00 -2.47308397e+00]
[ 3.68729472e+00 -3.71609855e+00]
[ 4.65554428e+00 -1.36403294e+01]
[-1.03487587e+01 8.00144958e+00]
...
시각화 - scattering
from matplotlib import font_manager, rc
import matplotlib as mpl
#한글 세팅
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
#'-'가 깨져서 나오니 정상적으로 나오게 하려면
mpl.rcParams['axes.unicode_minus'] = False
plt.scatter(X_tsne[:,0], X_tsne[:,1], c='red') #x값, y값
words = vocab[:10]
for i, word in enumerate(words):
plt.text(X_tsne[i,0], X_tsne[i,1 ], word, fontsize=8)
plt.savefig('nlp/out.png', dpi=200)
practice
두 keyword 각각의 유사 단어들을 그룹핑해서 출력
group1 = w2v_model.wv.most_similar(tokens('엄마'), topn=30)
group1 = [w for ( w, s ) in group1 ]
X1 = w2v_model[group1]
group2 = w2v_model.wv.most_similar(tokens('정우성'), topn=30)
group2 = [w for ( w, s ) in group2 ]
X2 = w2v_model[group2]
X_tsne =tsne.fit_transform(np.vstack([X1, X2]))
label = np.hstack([0*np.ones(30), np.ones(30) ]) #앞 30개는 0, 뒤 30개는 1인 numpy object
c = [ 'red' if l == 0 else 'blue' for l in label ]
plt.scatter(X_tsne[:,0], X_tsne[:,1], color=c)
실행시킬 때마다 결과가 다르게 나온다. -> 알고리즘의 특성
입력 데이터가 바뀐 건 아닌데, 2차원으로 축소하는 과정에서 매 시행마다 바뀐다. randomly charactor 있음 -> initial point의 문제.
PCA (Principa Component Analysis) 또한 차원을 축소하는
데이터의 분포가 주어졌을 때 차원을 줄이는 방법이다.
'Python > NLP 자연어처리' 카테고리의 다른 글
형태소 분석 (0) | 2020.02.10 |
---|---|
단어 빈도수 기반 자연어 처리 (0) | 2020.02.10 |
자연어 처리 소개와 char 코드 기반 자연어 처리 (0) | 2020.02.10 |