본문 바로가기

Python

[데이터분석] 2주차 강의노트 (3)

 

[데이터분석] 2주차 강의노트 (1)

기본 세팅 - colab에는 한글 세팅이 안 되어 있으므로 코드로 한글 세팅을 해줘야 함 import matplotlib as mpl import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' !apt -qq -y in..

yeahhh.tistory.com

 

[데이터분석] 2주차 강의노트 (2)

* 해당 자료는 스파르타코딩 데이터분석 종합반 수업 강의를 정리한 겁니다. 스파르타에서 제공한 데이터베이스를 사용했습니다. 워드 클라우드 - 입력 : from wordcloud import WordCloud - 워드 클라우

yeahhh.tistory.com

* 해당 자료는 스파르타코딩 데이터분석 종합반 수업 강의를 정리한 겁니다. 스파르타에서 제공한 데이터베이스를 사용했습니다.

 

 

머신러닝 원리

선형회귀

- "공부시간 * a + b = 성적" 형태의 방정식을 가정, 수많은 공부시간과 성적 데이터를 토대로 a,b 값을 추정하는 걸 '학습'이라고 함

- 인공지능 모델을 '구축한다, 학습시킨다' = 대량의 데이터를 토대로 기계한테 a,b 값을 알아내라고 시키는 것

- but, 모든 성적 데이터에 정확하게 들어맞는 직선을 그리는 건 사실상 불가함. 오차를 최대한 줄이는 게 좋은 예측 모델!

- a,b 값을 무작위로 설정했다가 빠르속도로 연산을 해 오차를 비교하면서 최적의 a,b 값을 찾는 걸 '최적화'라고 함

 

머신러닝

- 벡터화(vectorization) : 기계가 이해할 수 있게 단어를 숫자로 치환하는 것. 단어들을 특정한 관계에 따라 3차원 공간에 흩뿌려놓는 것. 특정 단어가 공간에서 어디에 위치하는지를 '벡터'라는 숫자가 알려준다. 이를 토대로 숫자만으로 해당 단어의 의미(상대적 관계)를 이해할 수 있게 됨.

- 모델(model) : 학습시킨 결과

 

텍스트 분류

- 이진 분류(Binary Classification) : 클래스가 2개인 경우 

ex) 스펨 메일 자동 분류

- 다중 클래스 분류(Multiclass Classification) : 클래스가 3개 이상인 경우

ex) 사용자 리뷰로부터 긍/부정을 판단하는 감성 분류, 뉴스 카테고리 분류

 

영화 줄거리를 이용해 장르 분류하기

파일 불러오기

- 훈련 데이터 : 기계에 일정한 내용을 학습시키기 위한 데이터. 각 줄거리의 장르가 어떤 건지 기계에게 알려주기 위한 데이터.

- 테스트 데이터 : 줄거리를 주고 장르를 예측해보라고 주기 위한 데이터. 정확도와 관련. 학습하지 않음

- 텍스트 파일 자체가 ::: 기준으로 번호 / 영화 제목 / 장르 / 줄거리가 구분돼 있으므로 이를 이용해 파일을 불러온다.

import pandas as pd
# sep=":::" 을 이용해서, 열 구분을 ::: 로 해서 불러오라고 말해준다.
train = pd.read_table('train_data.txt', sep=":::", names=['Index', 'Title', 'Genre', 'Content'])
test = pd.read_table('test_data_solution.txt', sep=":::", names=['Index', 'Title', 'Genre', 'Content'])

- train.head(3)으로 세 번째까지 불러와 확인!

- 각 파일의 정보를 보려면 train.info() / test.info()

- 장르 하나하나(분류값)를 class라고도 한다.

 

전처리

- 줄거리(독립 변수=x) 이용해 장르(종속 변수=y) 예측

train['Genre'].unique() # 중복 제외했을 때 해당 열에 존재하는 모든 값 확인

y_train = train['Genre'] # 훈련 데이터의 장르 부분을 y_train 이라는 이름으로 저장
x_train = train['Content'] # 훈련 데이터의 줄거리 부분을 x_train 이라는 이름으로 저장
y_test = test['Genre']
x_test = test['Content']

- 기계는 바로 단어를 이해할 수 없기 때문에 줄거리와 장르 모두 숫자나 벡터 형태로 변형해야 함.

- 이 경우 장르 자체의 의미나 장르 간의 관계가 중요한 게 아니라서 replace 함수 사용해 숫자로 대치시켜도 괜찮

mapping = {' drama ':1, ' thriller ':2, ' adult ':3, ' documentary ':4, ' comedy ':5,
       ' crime ':6, ' reality-tv ':7, ' horror ':8, ' sport ':9, ' animation ':10,
       ' action ':11, ' fantasy ':12, ' short ':13, ' sci-fi ':14, ' music ':15,
       ' adventure ':16, ' talk-show ':17, ' western ':18, ' family ':19, ' mystery ':20,
       ' history ':21, ' news ':22, ' biography ':23, ' romance ':24, ' game-show ':25,
       ' musical ':26, ' war ':27}
y_train = y_train.replace(mapping)
y_test = y_test.replace(mapping)

 

벡터화

- 벡터란 서로 유사도를 구하거나 연산도 가능한 숫자들의 나열

 

1) DTM(Document-Term Matrix)

- 문서 단어 행렬 : 문서를 행으로 하고 각 문서에서 등장하는 각 단어들의 등장 횟수를 행렬로 표현한 것

- 문서들의 유사도를 구할 수 있음

- 한계 : 영어로 dtm을 만들면 정관사 the는 어느 문서든 자주 등장함. the의 빈도수가 높다고 해서 유사하다고 할 수 없음. 이런 중요하지 않은 단어들 혹은 모든 문서에서 자주 등장하는 단어들에 가중치를 낮게 주는 방식이 TF_IDF

 

2) TF-IDF(Term Frequency-Inverse Document Frequency)

- 우선 DTM을 만들고 단어의 빈도와 문서의 빈도 역수를 사용해 DTM 내의 각 단어들마다 중요한 정도를 가중치로 주는 방법

- 문서의 유사도를 구하는 작업, 검색 시스템에서 검색 결과의 중요도를 정하는 작업, 문서 내에서 특정 단어의 중요도를 구하는 작업 등에 쓰임

# d = 문서  /  t = 단어  /  n = 문서의 총 개수

- tf(d,t) : 특정 문서 d에서의 특정 단어 t의 등장 횟수

- df(t) : 특정 단어 t가 등장한 문서의 수

- idf(d,t) : df(t)에 반비례하는 수

- 자연 로그 : 로그의 밑을 자연 상수 e(e=2.718281...)를 사용하는 로그

- IDF 계산을 위해 사용하는 로그의 밑은 TF-IDF를 사용하는 사용자가 임의로 정할 수 있는데, 여기서 로그는 기존의 값에 곱해 값의 크기를 조절하는 상수 역할을 함

- 각종 프로그래밍 언어니 프로그램 패키지로 지원하는 TF-IDF 로그는 대부분 자연 로그를 사용함. 자연 로그는 In으로 표현!

 

DTM, TF-IDF 만들기

- 사이킷 런(scikit-learn) : 머신러닝을 위한 각종 기능을 제공하는 모듈

- 머신러닝의 큰 분류 : 회귀, 분류

- DTM을 만들 때는 사이킷런의 CountVectorizer 사용, TF-IDF 행렬은 TfidfTransformer 사용

- 둘 다 객체를 만든 다음에는 fit_transform() 이라는 함수를 사용해 실행함

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

corpus = [
    'you know I want your love',
    'I like you',
    'what should I do ',    
]

vector = CountVectorizer() # DTM 벡터화를 위한 객체 생성
x_train_dtm = vector.fit_transform(corpus) # 해당 단어들을 벡터화 진행
print(x_train_dtm.toarray()) # 벡터가 어떻게 생겼는지 확인

-> DTM 완성!

[[0 1 0 1 0 1 0 1 1]
 [0 0 1 0 0 0 0 1 0]
 [1 0 0 0 1 0 1 0 0]]

 

- 여기서 TfidfTransformer()을 사용하면 TF-IDF 행렬을 추가적으로 학습할 수 있음

tfidf_transformer = TfidfTransformer() # tfidf 벡터화를 위한 객체 생성
tfidfv = tfidf_transformer.fit_transform(x_train_dtm) # x_train_dtm에 대해서 벡터화 진행
print(tfidfv.toarray()) # 벡터가 어떻게 생겼는지 확인

-> TF-IDF 행렬 완성

[[0.         0.46735098 0.         0.46735098 0.         0.46735098
  0.         0.35543247 0.46735098]
 [0.         0.         0.79596054 0.         0.         0.
  0.         0.60534851 0.        ]
 [0.57735027 0.         0.         0.         0.57735027 0.
  0.57735027 0.         0.        ]]

 

# 벡터화

벡터화 작업

 

머신러닝(예측 모델 구축)

기본 작업

- 훈련용 줄거리 데이터(x_train)에 대해 DTM 생성

- DTM으로부터 TF-IDF 행렬 만들기

- 테스트용 줄거리 데이터(x-test)에 대해서도 DTM과 TF-IDF 생성

 

나이브 베이즈 분류기 설치

- P(A) = A가 일어날 확률

- P(B) = B가 일어날 확률

- P(B|A) = A가 일어나고 B가 일어날 확률 = A -> B

- P(A|B) = B가 일어나고 A가 일어날 확률 = B -> A

- 이때 P(B|A)를 쉽게 구할 수 있는 상황이라면 아래 '베이즈 정리'를 통해 P(A|B)도 쉽게 구할 수 있다.

- '|'를 조건부 확률이라고 함 

- 나이브 베이즈 분류기는 사이킷런의 MultinomialNB() 사용

- 사이킷런이 제공하는 머신러닝 모델들은 공통적으로 fit()라는 함수를 제공

- 훈련 데이터와 해당 훈련 데이터에 대한 레이블을 인자로 사용하면 모델이 학습하게 됨

mod = MultinomialNB()
mod.fit(tfidfv, y_train)

- 테스트 데이터에 대해서 정확도를 측정하기 위해서는 훈련 데이터와 동일한 전처리를 거쳐야 함. 테스트 데이터에 대해서도 TF-IDF 행렬로 변환해주고 predict()라는 함수로 예측값을 얻어 정확도를 측정!

predicted = mod.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

 

 

로지스틱 회귀(Logistic Regression)

- 소프트맥스(softmax) 함수를 사용한 다중 클래스 분류 알고리즘을 지원

- 클래스가 N개일 때, 각 클래스가 정답일 확률을 표현하도록 하는 함수

lr = LogisticRegression(C=10000, penalty='l2') #c와 penalty의 의미는 4주차에서 배울 예정입니다!
lr.fit(tfidfv, y_train)
predicted = lr.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

 

ERROR

- 왤까..? 모듈 문제 같은데, 위로 거슬러 올라가면서 보니까 y_test[3]을 했는데 1이 아니라 drama가 나옴. 전처리가 안 됨

- read가 안 먹히길래 beautifulsoup4 설치하고 requests 임포트 해보니 1로 뜸

- 그러고 다시 차례차례 실행해보니 정상적으로 작동함! 인내심을 가지고 기다리자..

- 알고 보니 l2를 12라고 적었다...ㅎㅎ 

 

 

선형 Support Vector Machine

- 결정 경계(분류를 위한 기준 선)을 정의하는 모델

- 분류하지 않은 새로운 점이 나타나면 경계의 어느 쪽에 속하는지 확인해서 분류 과제를 수행할 수 있게 됨

SVM이 학습이 잘 되었다는 건 F와 같은 경우를 말함

lsvc = LinearSVC(C=1000, penalty='l2', max_iter=500)
lsvc.fit(tfidfv, y_train)
predicted = lsvc.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

** 더 높은 정확도를 얻기 위해서는 자연어처리(NLP)를 더 깊게 공부해야 함. 벡터 임베딩 여러 기법을 시도하고 딥러닝까지 적용해보면 훨씬 더 높은 정확도를 만들어낼 수 있음

 

장르 구분 최종

x_test_dtm = dtmvector.transform(['줄거리~~']) #테스트 데이터를 DTM으로 변환
tfidfv_test = tfidf_transformer.transform(x_test_dtm) #DTM을 TF-IDF 행렬로 변환

predicted = lr.predict(tfidfv_test) #테스트 데이터에 대한 예측
print(predicted)

-> 장르에 해당하는 숫자가 나옴. 각 숫자가 의미하는 바를 대조해보며 확인해보기

 

불용어 제거 활용

- 사이킷런에서 자동적으로 불용어를 제거해줌

- 따라서 countVectorizer 쓸 때 stop_words를 써주면 된다

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

# dtm, tfidf 벡터 생성을 위한 객체 생성
dtmvector = CountVectorizer(stop_words="english") # 영어 스탑워드를 제거해달라는 뜻!
tfidf_transformer = TfidfTransformer()

# x_train에 대해서 dtm, tfidf 벡터 생성
x_train_dtm = dtmvector.fit_transform(x_train)
tfidfv = tfidf_transformer.fit_transform(x_train_dtm)

- 다음 머신러닝 학습 시키기

# 나이브 베이즈 분류기로 학습 진행
mod = MultinomialNB()
mod.fit(tfidfv, y_train)

# x_test에 대해서 dtm, tfidf 벡터 생성
x_test_dtm = dtmvector.transform(x_test) #테스트 데이터를 DTM으로 변환
tfidfv_test = tfidf_transformer.transform(x_test_dtm) #DTM을 TF-IDF 행렬로 변환

predicted = mod.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

- 정확도가 44% 나옴. 불용어 제거하기 전과 큰 차이 없음. 나이브 베이즈 분류기는 원론적인 모델이라 불용어 제거가 영향을 미치지 않은 것!

 

과제

1) 네이버 쇼핑 리뷰 데이터를 이용해 긍/부정 분류 모델 만들기

  • 한글 사용을 위한 세팅 
import matplotlib as mpl
import matplotlib.pyplot as plt
 
%config InlineBackend.figure_format = 'retina'
 
!apt -qq -y install fonts-nanum
 
import matplotlib.font_manager as fm
fontpath = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font = fm.FontProperties(fname=fontpath, size=9)
plt.rc('font', family='NanumBarunGothic') 
mpl.font_manager._rebuild()

# 해당 코드 실행 후 반드시 상단의 런타임 > 런타임 다시 시작을 눌러주세요.
  • 데이터 로드해서 df로 만들기
import pandas as pd
import numpy as np

#판다스를 사용하여 네이버 쇼핑 리뷰 데이터가 존재하는 URL을 입력하고 다운로드
df = pd.read_table('https://raw.githubusercontent.com/bab2min/corpus/master/sentiment/naver_shopping.txt', names=['ratings', 'reviews'])
df

- 링크 바로 달아도 되고, 파일 형태로 넣어도 괜찮다. 파일로 넣으려면 아래처럼 하면 된다.

df = pd.read_table('naver_shopping.txt', names=['ratings', 'reviews'])

 

⭐️ 'A를 이용해 -> B 예측' : A는 벡터화를 진행하고, B는 단순한 번호로 치환하는 인덱스화를 진행 ⭐️

- 이 경우 리뷰가 A, 긍/부정은 B

- 리뷰는 DTM, TF-IDF로 만드는 벡터화, 긍/부정 여부는 이분법이니까 1과 0으로 단순 치환

 

1-1. 긍/부정(B) 인덱스화

- 평점(ratings)이 3보다 크면 긍정(=1), 3과 같거나 작으면 부정(=0) 리뷰로 지정

df['label'] = np.select([df.ratings > 3], [1], default=0)

 

1-2. 훈련 데이터와 테스트 데이터 구분

- train_test_split 함수 활용

- 왼쪽에는 변수를 적어주고

  • x_train (훈련에 사용할 리뷰 데이터) -> A
  • x_test (테스트에 사용할 리뷰 데이터) -> A
  • y_train (훈련에 사용할 긍정/부정 여부) -> B
  • y_test (테스트에 사용할 긍정/부정 여부) -> B

- 오른쪽에는 train_test_split(예측에 사용할 데이터: 리뷰(A), 예측할 데이터: 긍/부정 여부(B), test_size=원하는 테스트 데이터 셋의 크기)

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(df['reviews'], df['label'], test_size = 0.3)

- test_size 크기 0.3은 전체 데이터 중 70%는 훈련 데이터에, 30%는 테스트 데이터에 담는다는 말

 

1-3. 훈련용 데이터 = x_train = 리뷰 = A 벡터화

- 모듈 설치

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

- DTM 만들기

dtmvector = CountVectorizer() # DTM 벡터화를 위한 객체 생성
x_train_dtm = dtmvector.fit_transform(x_train)

- 벡터가 어떻게 생겼나 확인하기

print(x_train_dtm.toarray())

- DTM을 이용해 TF-IDF 벡터 만들기

tfidf_transformer = TfidfTransformer() # tfidf 벡터화를 위한 객체 생성
tfidfv = tfidf_transformer.fit_transform(x_train_dtm) # x_train_dtm에 대해 벡터화 진행

 

1-4. 예측 모델 구축

- 예측 모델에 필요한 모듈 임포트

from sklearn.naive_bayes import MultinomialNB # 다항분포 나이브 베이즈 모델
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score # 정확도 계산
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer

- 모델 학습

# 로지스틱 회귀
lr = LogisticRegression(C=10000, penalty='l2')
lr.fit(tfidfv, y_train)

# 나이브 베이즈 분류
mod = MultinomialNB()
mod.fit(tfidfv, y_train)

# 선형 서포트 벡터 머신
lsvc = LinearSVC(C=1000, penalty='l2', max_iter=500)
lsvc.fit(tfidfv, y_train)

- 테스트 데이터 활용해 정확도 확인

x_test_dtm = dtmvector.transform(x_test) #테스트 데이터를 DTM으로 변환
tfidfv_test = tfidf_transformer.transform(x_test_dtm) #DTM을 TF-IDF 행렬로 변환

predicted = lr.predict(tfidfv_test) #테스트 데이터에 대한 예측
print("정확도:", accuracy_score(y_test, predicted)) #예측값과 실제값 비교

- 로지스 회귀, 나이브 베이즈, 선형 서포트 벡터 머신 각각 정확도 구하기

  • 로지스틱 : 84%
  • 나이브 베이즈 : 88%
  • 선형 서포트 벡터 머신 : 80%

-> 나이브 베이즈가 가장 정확도가 높음! 

 

2) 네이버 쇼핑 리뷰 데이터를 이용해 각각 긍정/부정 리뷰의 워드 클라우드를 만들기

print(df['reviews'].nunique())
df.drop_duplicates(subset=['reviews'], inplace=True)

!pip install konlpy
from konlpy.tag import Okt
tokenizer = Okt()
df['tokenized'] = df['reviews'].apply(tokenizer.nouns)

df['label'] = np.select([df.ratings > 3], [1], default=0)

# hstack은 가로 결합
positive_reviews = np.hstack(df[df['label']==1]['tokenized'].values)
negative_reviews = np.hstack(df[df['label']==0]['tokenized'].values)

# 가장 많이 등장하는 단어와 그 개수를 구할 때는 collectios 모듈의 Counter 클래스 
from collections import Counter

positive_reviews_word_count = Counter(positive_reviews)
negative_reviews_word_count = Counter(negative_reviews)

import numpy as np
from wordcloud import WordCloud

plt.figure(figsize = (15,15))
temp_data1 = ' '.join(positive_reviews)
temp_data2 = ' '.join(negative_reviews)
wc = WordCloud(max_words = 2000 , width = 1600 , height = 800, font_path = fontpath).generate(temp_data)
plt.imshow(wc, interpolation = 'bilinear')

회고

Keep 안 되면 처음부터 다시해보기. 될 때까지 이것저것 만져본다. 어쩌다 얻어 걸리면서 다음에 같은 실수 안 하면 됨! 
Problem 인내심을 가지고,, 처음부터 차례차례 모듈, 라이브러리 설치 까먹지 말자. 안 될 때 처음부터라는건 모듈 설치부터.. 
l2를 12라고 적은 letter 실수 은근 많이 나온다. 꼼꼼하게 살펴 보기!! 
Try 메커니즘을 익혀야 안 막힌다. 한 군데가 막히면 뒤에가 쭉 다 안 된다. 원리를 이해해보자!  아직은 용어가 헷갈림. 모듈, 라이브러리, 패키지, 각 함수의 기능 하나씩 구분할 수 있게 용어를 익히자