● 퍼셉트론

1. 초기 머신러닝의 역사

 - Warren McCulloch과 Walter Pits가 뇌의 뉴런 개념을 발표

   (MCP 뉴런 - 신경 세포를 이진 출력을 내는 간단한 논리 회로로 표현)

 - Frank Rosenblatt는 MCP 뉴런 모델을 기반으로 퍼셉트론 학습 개념을 발표

 - 퍼셉트론 규칙에서 자동으로 최적의 가중치를 학습하는 알고리즘 제안

 - 가중치는 뉴런의 출력 신호를 낼지 말지 결정하기 위해 입력 특성에 곱하는 계수

 - 지도학습분류 문제에서 퍼셉트론 알고리즘을 사용하여 새로운 데이터가 한 클래스에 속하는지 아닌지 예측 가능

 

2. 인공 뉴런의 수학적 정의

 - 두 클래스를 1과 -1로 나타냄

 - 입력값 \(x\)와 가중치 벡터 \(w\)의 선형 조합으로 결정함수 \(\phi(z)\) 정의

$$z = w_{1}x_{1} + w_{2}x_{2} + \cdots + w_{m}x_{m}$$

$$w = \begin{bmatrix} w_{1} \\ \vdots  \\ w_{m} \end{bmatrix}, x= \begin{bmatrix} x_{1} \\ \vdots  \\ x_{m} \end{bmatrix}$$

 - 특정 샘플 \(x\)의 최종 입력이 사전 정의된 임계값 \(\theta\)보다 크면 클래스 1, 아니면 클래스 -1로 예측

$$\phi(z) = \begin{cases} 1 & z \geq \theta \\ -1 & 그외 \end{cases}$$

 - \(z \geq \theta\) 식에서 \(\theta\)를 왼쪽으로 넘겨 \(w_{0}=-\theta\)이고, \(x_{0}=1\)인 0번째 가중치를 정의하여 식 간소화

$$z = w_{0}x_{0} + w_{1}x_{1} + \cdots + w_{m}x_{m} = w^{T}x$$

 - 결정함수는 다음과 같이 변경

$$\phi(z) = \begin{cases} 1 & z \geq 0 \\ -1 & 그외 \end{cases}$$

 - \(w_{0} = -\theta\)를 절편이라고 함

퍼셉트론의 결정함수와 결정 경계

 

3. 퍼셉트론 학습 규칙

 - 뇌의 뉴런 하나가 작동하는 방식을 흉내내는 환원주의 접근 방식 사용

  1. 가중치를 0 또는 랜덤한 작은 값으로 초기화
  2. 각 훈련 샘플 \(x^{(i)}\)에서 다음 작업 수행
    1. 출력 값 \(\hat{y}\) 계산
      → 출력값은 계단 함수로 예측한 클래스 레이블
    2. 가중치 업데이트
      \(w_{j}:=w_{j}+\Delta w_{j}\)
      가중치 업데이트 값인 \(\Delta w_{j}=\eta (y^{(i)}-\hat{y}^{(i)})x_{j}^{(i)}\)
      \(\eta\)는 학습률로 0.0~0.1 사이의 값
      \(y^{(i)}\)와 \(\hat{y}^{(i)}\)는 각각 \(i\)번째 훈련 샘플의 정답 레이블,
       예측 레이블

 - 가중치 벡터의 모든 가중치는 동시에 업데이트

 - 모든 가중치가 각자의 업데이트 값 \(\Delta w_{j}\)에 의해 업데이트되기 전에 예측 레이블 \(\hat{y}^{(i)}\)를 다시 계산하지 않음
      → \(\Delta w_{0}=\eta(y^{(i)}-output^{(i)})\)
      → \(\Delta w_{1}=\eta(y^{(i)}-output^{(i)})x_{1}^{(i)}\)
      → \(\Delta w_{2}=\eta(y^{(i)}-output^{(i)})x_{2}^{(i)}\)

 - 퍼셉트론이 클래스 레이블을 정확히 예측하면 가중치 유지(업데이트 값은 0)
      → \(y^{(i)}=-1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(-1-(-1))x_{j}^{(i)}=0\)
      \(y^{(i)}=1, \hat{y}^{(i)}=1,\Delta w_{j}=\eta(1-1)x_{j}^{(i)}=0\)
 - 퍼셉트론이 클래스 레이블을 잘못 예측하면 가중치를 양성 또는 음성 타겟 클래스 방향으로 이동시킴
      \(y^{(i)}=1, \hat{y}^{(i)}=-1, \Delta w_{j}=\eta(1-(-1))x_{j}^{(i)}=\eta(2)x_{j}^{(i)}\)
      \(y^{(i)}=-1, \hat{y}^{(i)}=1, \Delta w_{j}=\eta(-1-1)x_{j}^{(i)}=\eta(-2)x_{j}^{(i)}\)

 - 가중치 업데이트는 \(x_{j}^{(i)}\)값에 비례하여, 이 값을 조절해 결정경계를 더 크게 움직이거나 더 작게 움직일 수 있음

 - 퍼셉트론은 두 클래스가 선형적으로 구분되고 학습률이 충분히 작을 때만 수렴이 보장됨

 - 선형 결정 경계로 나눌 수 없다면 훈련 데이터셋을 반복할 최대 횟수(epoch)를 지정해 분류 허용 오차 지정, 그렇지 않으면 퍼셉트론은 가중치 업데이트를 멈추지 않음

퍼셉트론 알고리즘

 

4. 파이썬으로 퍼셉트론  학습 알고리즘 구현

 - 퍼셉트론 클래스 정의

import numpy as np

class Perceptron(object):
    def __init__(self, eta = 0.01, n_iter = 50, random_state = 1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state

    def fit(self, X, y):
        rgen = np.random.RandomState(self.random_state) # 난수 생성기 객체 생성(격리된 시드 부여 가능, 이전과 동일한 랜덤 시드 재현 가능)
        self.w_ = rgen.normal(loc = 0.0, scale = 0.01,
                              size = 1 + X.shape[1])    # 평균 0, 표준편차 0.01의 정규분포에서 훈련 데이터 개수(X.shape[1]) + 절편(1)만큼의 난수 생성
        self.errors_ = []                               # 훈련 중 error의 감소 확인을 위한 객체

        for _ in range(self.n_iter):
            errors = 0
            for xi, target in zip(X, y):
                update = self.eta * (target - self.predict(xi)) # 가중치 w_j의 변화량
                self.w_[1:] += update * xi                      # 각 훈련 데이터에 가중치값 곱하기
                self.w_[0] += update                            # 가중치값 업데이트
                errors += int(update != 0.0)                    # 업데이트 된 값이 0이 아니면(실제값과 예측값이 다르면) errors에 1 더함
            self.errors_.append(errors)                         # 전체 훈련 데이터에서 예측값이 실제값과 다르게 나온 횟수 추가
        return self
    
    # 최종입력인 Z 계산
    def net_input(self, X):
        return np.dot(X, self.w_[1:]) + self.w_[0]
    
    # 결정함수를 임계값인 0과 비교, 크거나 같으면 1, 작으면 -1로 반환
    def predict(self, X):
        return np.where(self.net_input(X) >= 0, 1, -1)

 - fit 메서드 내의 w_객체에 가중치를 초기화하여 선언
 - 가중치가 0이 아니어야 학습률(eta)가 분류 결과에 영향을 줄 수 있어 0이 아닌 랜덤 값으로 초기화
 - 가중치가 0이면 eta는 가중치 벡터의 방향이 아니라 크기에만 영향을 미침

 - 붓꽃 데이터셋에서 퍼셉트론 훈련
 - Perceptron은 이진분류이므로 setosa와 versicolor에 대해서만 분류 진행
 - 또한, 특성은 첫번째 특성(꽃받침 길이)와 세번째 특성(꽃잎 길이)을 사용

 

import pandas as pd
import matplotlib.pyplot as plt

s = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
df = pd.read_csv(s, header = None, encoding = 'utf-8')

# 처음 100개의 데이터(setosa 50개와 versicolor 50개)의 다섯번째 열인 예측 대상을 y로 선언
y = df.iloc[0:100, 4].values
# setosa이면 -1, versicolor이면 1로 변경
y = np.where(y == 'Iris-setosa', -1, 1)

# 처음 100개의 데이터에서 예측에 사용하고자 하는 특성인 첫번재 열과 세번째 열만 추출
X = df.iloc[0:100, [0, 2]].values

# 산점도로 시각화
plt.scatter(X[:50, 0], X[:50, 1], color = 'red', marker = 'o', label = 'setose')
plt.scatter(X[50:100, 0], X[50:100, 1], color = 'blue', marker = 'x', label = 'versicolor')
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()

# Perceptron 알고리즘 훈련
ppn = Perceptron(eta = 0.1, n_iter = 10)
ppn.fit(X, y)

# 훈련 과정에서 각 반복에서 나온 오차의 개수 시각화
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker = 'o')
plt.xlabel('Epochs')
plt.ylabel('Number of updates')
plt.show()

from matplotlib.colors import ListedColormap

# 결정경계 시각화 함수
def plot_decision_regions(X, y, classifier, resolution = 0.02):
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # 두 특성의 최소값과 최대값에 각각 -1, +1을 한 값을 정의
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    # 위에서 구한 최소값과 최대값을 시작으로 하는 meshgrid 생성(최소값과 최대값 사이에 0.02의 간격으로 데이터 생성)
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    
    # ravel 함수로 meshgrid를 1차원으로 평평하게 펴고, xx1, xx2를 각각 X, y로 Perceptron에 넣어 예측
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    # 예측된 값을 다시 meshgrid 형태로 변경
    Z = Z.reshape(xx1.shape)

    # contourf 함수로 등고선 그래프 그리기(등고선이 두 집단의 경계로 그려짐)
    # 분류된 값이 -1인 것과 1인 것을 각각 빨간색과 파란색으로 구분
    plt.contourf(xx1, xx2, Z, alpha = 0.3, cmap = cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())
    # 훈련 데이터를 빨간색 네모 모양와 파란색 x모양으로 구분
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x = X[y == cl, 0], y = X[y == cl, 1],
                    alpha = 0.8, c = colors[idx], marker = markers[idx], label = cl, edgecolor = 'black')
    
plot_decision_regions(X, y, classifier = ppn)
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc = 'upper left')
plt.show()

 - OvR(One-versus-Resr)기법을 사용하면 이진 분류기로 다중 클래스 문제 해결 가능
       클래스마다 하나의 분류기를 훈련(각 클래스를 양성으로 분류하고 나머지는 음성으로 분류)
       레이블이 없는 새로운 데이터 샘플 분류 시에는 클래스 레이블의 개수와 같은 n개의 분류기 사용
       신뢰도가 가장 높은 클래스 레이블(최종 입력의 절댓값이 가장 큰 클래스)을 분류하려는 샘플의 레이블로 선택

 - 퍼셉트론의 가장 큰 문제는 수렴
       구 클래스가 선형적인 초평면으로 구분될 수 있을 때 수렴하지만, 선형 결정 경계로 완벽하게 구분되지 않으면 최대 epoch를 지정하지 않는한 가중치 업데이트가 멈추지 않음

● 책 '파이썬 머신러닝 완벽 가이드' 참고

● 예시로 '월간 데이콘 쇼츠 - 초전도체 임계온도 예측 AI 헤커톤'의 데이터 사용(train, test, submission 파일 3개 존재)

 

1. 모듈 임포트

import os
# Seed 고정
def seed_everything(seed):
  random.seed(seed)
  os.environ['PYTHONHASHSEED'] = str(seed)
  np.random.seed(seed)
seed_everything(42)
# warning 무시
import warnings
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.preprocessing import StandardScaler, MinMaxScaler

from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.tree import DecisionTreeClassifier, DicisionTreeRegressor
from sklearn.linear_model import LogisticRegression, LinearRegression

from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV

from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, r2_score

 

 

2. 데이터 불러오기

train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
submission = pd.read_csv('sample_submission.csv')

 

 

3. 데이터 탐색

train.head()  # 처음 5개의 데이터 (전체적인 데이터 구조 확인)
train.info()  # 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)
train.describe()  # 숫자형 데이터 요약 (개수, 평균, 표준편차, 최소값, 25%, 50%, 75%, 최대값)
train.isna().sum()  # 각 데이터의 결측값 개수
# 이외에도 이해가 가지 않는 행이나 열만 따로 뽑아서 탐색, 데이터 도메인 지식 등을 찾아보며 데이터에 대한 이해 필요
train = train.drop('ID', axis = 1)  # train 데이터 탐색 결과 단순 식별자 변수인 ID는 필요 없는 것으로 판단, 삭제
feature = train.copy()  # 밑에서 target변수를 제거하고 feature 변수만 남긴 데이터
target = feature.pop('critical_temp') # target 데이터 추출, train에는 feature 데이터만 남음

  - train.head(): 처음 5개의 데이터 (전체적인 데이터 구조 확인)

  - train.info(): 데이터에 대한 개략적인 정보 (행과 열 개수, non-null값 개수, 데이터 타입)

  - train.describe(): 숫자형 데이터 요약 (개수, 평균, 표준편차, 최소값, 25%, 50%, 75%, 최대값)

  - train.isna().sum(): 각 데이터의 결측값 개수

 

 

4. 데이터 시각화

  - 박스플롯으로 이상값 등 데이터 분포 확인

scaler = StandardScaler() # 박스플롯 출력 전 하나의 박스플롯에서 데이터를 한눈에 보기 위해 같은 범위로 임시 스케일링
plt.figure(figsize = (15, 10))
sns.boxplot(scaler.fit_transform(feature))

 

  - 각 feature 데이터의 바이오린 플롯과 히스토그램 출력하여 데이터 분포 확인

for col in train.columns:
  fig, axs = plt.subplots(nrows = 1, ncols = 2, figsize = (10, 4))
  sns.violinplot(x = col, data = train, kde = True, ax = axs[0])
  sns.histplot(x = col, data = train, kde = True, ax = axs[1])

 

  - 히트맵으로 각 feature간 상관관계 파악

plt.figure(figsize = (15, 15))
sns.heatmap(train.corr(), cmap = 'vlag')

 

  - 히트맵으로 결측값 분포 확인(결측값이 있다면 그 부분이 살구색으로 표현됨)

plt.figure(figsize = (15, 15))
sns.heatmap(train.isnull())

 

 

5. 데이터 전처리(결측값 처리)

# 방법 1. 결측값이 있는 행 제거
feature = feature.dropna(axis = 0, how = 'any') # 각 행에서 하나의 값이라도 NaN이면 해당 행 제거
feature = feature.dropna(axis = 0, how = 'all') # 각 행의 모든 값이 NaN이면 해당 행 제거

# 방법 2. 결측값 채우기(특정 값으로 채우기)
feature = feature.fillna(0) # 0으로 채우기
feature = feature.fillna(method = 'pad')  # 바로 앞의 값으로 채우기
feature = feature.fillna(method = 'bfill')  # 바로 뒤의 값으로 채우기
feature = feature.fillna(feature.mean())  # 해당 열의 평균값으로 채우기

# 방법 3. 결측값 채우기(보간법)
feature = feature.interpolate(method = 'time') # 인덱스가 날짜/시간일 때 날짜나 시간에 따라 보간
feature = feature.interpolate(method = 'nearest', limit_direction = 'forward') # 가장 가까운 값으로 보간
feature = feature.interpolate(method = 'linear', limit_direction = 'forward') # 양 끝값에 따라 선형식에 적용하여 보간
feature = feature.interpolate(method = 'polynomial', order = 2) # 양 끝값에 따라 다항식에 적용하여 보간 (order은 다항식 차수)

# 방법 4. 결측값이 있는 열을 해당 열의 결측값이 아닌 행과 다른 feature 변수들로 예측하여 채우기
feature_target = feature.pop('결측값 열') # 다른 행들로부터 결측값을 예측할 목표열 분리
target_index = feature_target[feature_target['결측값 열'] == np.nan].index  # 목표열 중 결측값인 행의 인덱스 추출
feature_train = feature.iloc[~target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스가 아닌 행 추축(train 용)
feature_test = feature.iloc[target_index] # 목표열이 아닌 다른 열에서 결측값이 있는 행의 인덱스인 행 추출(test 용)
feature_target_train = feature_target[~target_index]  # 목표열 중 결측값이 아닌 행 추출(train 용)
rf = RandomForestRegressor()  # 랜덤포레스트로 예측
rf.fit(feature_train, feature_target_train) # 랜덤포레스트에 목표열 중 결측값이 아닌 행을 target 변수, 다른 열의 해당 행들을 feature 변수로 모델 피팅
pred = rf.predict(feature_test) # 피팅된 모델로 목표열에서 결측값인 행 예측
feature.iloc[target_index]['결측값 열'] = pred  # 원래 데이터프레임에서 목표열의 결측값인 값을 위에서 예측한 값으로 대체

 

 

6. 데이터 전처리(이상값 처리)

# IQR으로 이상값 판하여 절단
def outlier(df, col):
  tar = df[col]
  quantile_25 = np.percentile(tar.values, 25)
  quantile_75 = np.percentile(tar.values, 75)
  iqr = quantile_75 - quantile_25
  low = quantile_25 - iqr * 1.5
  high = quantile_75 + iqr * 1.5
  outlier_index = tar[(tar < low) | (tar > high)].index
  del_outlier = df.drop(outlier_index, axis = 0)
  return del_outlier

 

 

7. 데이터 전처리(표준화 및 정규화 = 피처 스케일링)

# 방법 1. StandardScaler
scaler = StandardScaler()
feature[[feature.select_dtypes([float, int]).columns]] = scaler.fit_transform(feature.select_dtypes([float, int]))
test[[test.select_dtypes([float, int]).columns]] = scaler.transform(test.select_dtypes([float, int]))

  -  평균 0, 분산 1

  - SVM, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 정규 분포를 가지고 있다고 가정하고 구현되어 있기 때문에 StandardScaler가 예측 성능 향상에 도움

 

# 방법 2. MinMaxScaler
scaler = MinMaxScaler()
feature[[feature.select_dtypes([float, int]).columns]] = scaler.fit_transform(feature.select_dtypes([float, int]))
test[[test.select_dtypes([float, int]).columns]] = scaler.transform(test.select_dtypes([float, int]))

  - 최소값 0, 최대값 1

  - 음수값이 있다면 최소값 -1, 최대값 1

  - 데이터가 가우시안 정규 분포가 아닐 때 적용해볼 수 있음

 

 

8. 데이터 전처리(인코딩)

# 방법 1. 문자형 변수 라벨 인코딩
encoder = LabelEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object))
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object))

 

# 방법 2. 문자형 변수 원핫 인코딩
encoder = OneHotEncoder()
feature[[feature.select_dtypes(object).columns]] = encoder.fit_transform(feature.select_dtypes(object).toarray())
test[[test.select_dtypes(object).columns]] = encoder.transform(test.select_dtypes(object).toarray())

  - 라벨 인코딩은 문자형 변수 고윳값의 개수에 따라 0부터 n-1까지의 숫자로 변환되어 ML에서 왜곡된 결과를 불러올 수 있음

  - 원핫 인코딩은 이런 문제를 해결하기 위해 고윳값의 개수만큼 새로운 feature 변수를 생성하여 각 행에서 고윳값에 해당하는 feature만 1, 나머지는 0으로 표시

 

 

9. 모델 피팅 및 예측

# feature 변수와 target 변수 분리
X = feature
y = target

# 학습용 데이터(X_train, y_train)와 검증용 데이터(X_test, y_test)로 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)

# 모델 선언(사용할 모델로 선정, 예시로는 RandomForestRegressor 선정)
model = RandomForestRegressor()

# 모델 학습
model.fit(X_train, y_train)

# 학습된 모델로 검증용 데이터 예측
y_pred = rf.predict(X_test)

# 실제 검증용 데이터와 예측한 검증용 데이터 비교하여 점수 출력
print(f'mse: {mean_squared_error(y_test, y_pred)}\nmae: {mean_absolute_error(y_test. y_pred)}\naccuracy: {accuracy_score(y_test, y_pred)}')

 

 

10. 교차 검증

# 방법 1. KFold
def exec_kfold(model, folds = 5):
  kfold = KFold(n_splits = folds)
  scores = []

  for iter, (train_index, test_index) in enumerate(kfold.split(X)):
    X_train, X_test = X.values[train_index], X.values[test_index]
    y_train, y_test = y.values[train_index], y.values[test_index]
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    scores.append(accuracy)
    print(f'교차 검증 {iter} 정확도: {accuracy:.4f}')

  print(f'평균 정확도: {np.mean(scores):.4f}')

exec_kfold(model, folds = 5)
# 방법 2. cross_val_score
scores = cross_val_score(model, X, y, cv = 5)
for iter, accuracy in enumerate(scores):
  print(f'교차 검증 {iter} 정확도: {accuracy:.4f}')
print(f'평균 정확도: {np.mean(scores):.4f}')

  - KFold와 cross_val_score에서 각각 평균 정확도가 다른 이유는 cross_val_score가 StratifiedKFold를 이용해 폴드 세트를 분할하기 때문

 

# 방법 3. GridSearchCV
params = {'max_depth': [2, 3, 5, 10], 'min_samples_split': [2, 3, 5], 'min_samples_leaf': [1, 5, 8]}  # 사용할 모델에 맞는 하이퍼 파라미터 중 탐색하고 싶은 범위 지정
grid_model = GridSearchCV(model, param_grid = params, scoring = 'accuracy', cv = 5, verbose = 3)  # GridSearchCV에 model, 하이퍼 파라미터, 점수, 교차검증 횟수 등을 파라미터로 지정
grid_model.fit(X_train, y_train)  # GridSearchCV 모델에 train 데이터 피팅
print(f'GridSearchCV 최적 하이퍼 파라미터: {grid_model.best_params_}')  # 최적 하이퍼 파라미터
print(f'GridSearchCV 최고 정확도: {grid_model.best_score_:.4f}')  # 최적 하이퍼 파라미터일 때 나온 최고 점수
best_model = grid_model.best_estimator_ # 최적 하이퍼 파라미터로 최고 점수가 나온 모델을 best_model로 저장

y_pred = best_model.predict(X_test) # best_model로 test 데이터 예측
accuracy = accuracy_score(y_test, y_pred) # 점수 계산
print(f'테스트 세트에서의 model 정확도: {accuracy:.4f}')

 

 

11. 기본 분석 이후

  - pycaret, autogluon 등 auto ML을 사용해 최적의 모델 선택

  - 데이터에 대한 깊은 이해를 통해 더 최적의 변수 선택 및 전처리, 차원 축소 및 군집화 등으로 파생변수 생성

11. 행 삭제하기

  • 조건을 넣어 True인 값만 출력하도록 하면 조건에 맞지 않는 값은 삭제된 것과 같은 상태
import pandas as pd

df = pd.read_csv('titanic.csv')
# 데이터프레임에서 Sex열의 값이 male이 아닌 것 즉, female인 것만 출력, male은 삭제되고 female인 것만 출력됨
df[df['Sex'] != 'male'].head(2)

# 행의 인덱스를 사용해 하나의 행을 삭제할 수 있음
# 인덱스가 0인 행만 제외하고 나머지 출력
df[df.index != 0].head(2)

 

 

12. 중복된 행 삭제하기

  • drop_duplicates 사용(매개변수 사용에 주의)
# drop_duplicates를 매개변수 없이 사용하면 완벽하게 같은 행만 삭제되어 예시로 사용하는 타이타닉 데이터에서는 삭제되는 열이 없음
print('원본 데이터프레임 행의 수: {}'.format(len(df)))
print('drop_duplicates 사용한 데이터프레임 행의 수: {}'.format(len(df.drop_duplicates())))

# 출력 결과
원본 데이터프레임 행의 수: 891
drop_duplicates 사용한 데이터프레임 행의 수: 891
# 따라서 중복을 비교할 열을 subset 매개변수에 전달하여야 함
# Sex 변수에서 중복되는 값이 전부 제거되고 제일 처음 male값을 가지는 행과 female 값을 가지는 행만 남아 출력됨
df.drop_duplicates(subset = ['Sex'])

# keep 매개변수에 last를 전달하면 가장 마지막에 남은 male값과 female값만 하나씩 출력됨
df.drop_duplicates(subset = ['Sex'], keep = 'last')

 

 

13. 값에 따라 행을 그룹핑하기

  • groupby('그루핑할 열')['통계 계산을 할 열'].통계계산(mean, sum, count 등)
# 성별로 그루핑하여 각 성별의 나이 평균 계산
df.groupby('Sex')['Age'].mean()

# 출력 결과
Sex
female    27.915709
male      30.726645
Name: Age, dtype: float64

 

  • 첫 번째 열로 그루핑한 다음 두 번째 열로 다시 그룹핑하기
# 성별로 구루핑 하고 각 성별 중 생존 여부에 따라 다시 그루핑
# 이후 총 4개의 그룹 각각의 나이 평균 계산
df.groupby(['Sex', 'Survived'])['Age'].mean()

# 출력 결과
Sex     Survived
female  0           25.046875
        1           28.847716
male    0           31.618056
        1           27.276022
Name: Age, dtype: float64

 

 

14. 시간에 따라 행을 그루핑하기

  • 시간을 인덱스로 하는 예시 데이터프레임 생성
import pandas as pd
import numpy as np

# 2023/06/26부터 30초 간격으로 100,000개의 시간 데이터를 인덱스로 생성
time_index = pd.date_range('06/26/2023', periods = 100000, freq = '30s')

# 위에서 생성한 시간 인덱스를 인덱스로하고, 1~10사이의 숫자 중 랜덤하게 100,000개를 뽑은 것을 각 시간의 값으로 생성
df = pd.DataFrame(index = time_index)
df['Sale_Amount'] = np.random.randint(1, 10, 100000)

df

# 일주일 단위로 행을 그루핑
df.resample('W').sum()

  • 시간 단위
    • 'W': 1주 단위
    • '2W': 2주 단위
    • 'M': 1달 단위
    • 'MS': 1달 단위, 그루핑된 인덱스를 월의 시작 날짜로
  • label = 'left' 옵션: 인덱스를 그루핑된 그룹의 마지막 레이블이 아닌 그 전 그룹의 마지막 레이블로 변환
df.resample('M').sum()
df.resample('M', label = 'left').sum()
df.resample('MS').sum()

'M'
'M', label = 'left'
'MS'

 

 

15. 열 원소 순회하기

  • 데이터프레임의 열을 시퀀스처럼 다룰 수 있음
# 데이터프레임의 ['Name']열에 속한 값들을 하나씩 차례대로 name으로 지정하여 출력(상위 2개의 열에만 예시로 적용)
for name in df['Name'][0:2]:
    print(name.upper())

 

 

16. 모든 열 원소에 함수 적용하기

  • 데이터프레임의 특정 열의 모든 값에 동시에 함수를 적용
# 문자를 대문자로 출력하는 함수 생성
def uppercase(x):
    return x.upper()

# 위에서 생성한 함수를 데이터프레임의 'Name'열에 한번에 적용(상위 2개의 열에만 예시로 적용)
df['Name'].apply(uppercase)[0:2]

 

  • map 함수도 apply 함수와 비슷하지만 map 메서드는 딕셔너리를 입력으로 넣을 수 있고 apply 메서드는 매개변수를 지정할 수 있음
# 딕셔너리 형태로 Survived 열의 1값은 Live, 0값은 Dead로 변경
df['Survived'].map({1 : 'Live', 0 : 'Dead'})[:5]

 

  • apply 메서드에서 age라는 매개변수 전달
# age = 30이라는 매개변수 전달, Age열의 값이 전달받은 age보다 작은지에 대한 boolean 값 return
df['Age'].apply(lambda x, age: x < age, age = 30)[:5]

 

  • applymap 메서드로 전체 데이터프레임에 함수 적용 가능
# 문자값으로 이루어진 열에 대해 최대 길이를 20자로 제한하는 함수
def truncate_string(x):
    if type(x) == str:
        return x[:20]
    else:
        return x

전체 데이터프레임에 적용
df.applymap(truncate_string)[:5]

 

 

17. 그룹에 함수 적용하기

# Sex열로 그루핑하여 male 그룹과 female 그룹의 다른 열들 각각에 lambda 함수 적용
df.groupby('Sex').apply(lambda x: x.count())

 

 

18. 데이터프레임 연결하기

  • concat 함수
data_a = {'id': ['1', '2', '3'],
          'first': ['Alex', 'Amy', 'Allen'],
          'last': ['Anderson', 'Ackerman', 'Ali']}
df_a = pd.DataFrame(data_a, columns = ['id', 'first', 'last'])

data_b = {'id': ['4', '5', '6'],
          'first': ['Billy', 'Brian', 'Bran'],
          'last': ['Bonder', 'Black', 'Balwner']}
df_b = pd.DataFrame(data_b, columns = ['id', 'first', 'last'])

df_a
df_b

df_a
df_b

 

  • concat 함수에 axis = 0으로 하면 행 방향으로 데이터프레임 연결
pd.concat([df_a, df_b], axis = 0)

 

  • concat 함수에 axis = 1로 하면 열 방향으로 데이터프레임 연결
pd.concat([df_a, df_b], axis = 1)

 

 

19. 데이터프레임 병합하기

  • merge 함수
employee_data = {'employee_id': ['1', '2', '3', '4'],
                 'name': ['Amy Jones', 'Allen Keys', 'Alice Bees', 'Tim Horton']}
df_employees = pd.DataFrame(employee_data, columns = ['employee_id', 'name'])

sales_data = {'employee_id': ['3', '4', '5', '6'],
              'total_sales': [23456, 2512, 2345, 1455]}
df_sales = pd.DataFrame(sales_data, columns = ['employee_id', 'total_sales'])

df_employees
df_saeles

df_employees
df_sales

 

  • merge는 기본적으로 내부 조인을 실행(기준으로 하는 열(on = ''으로 지정한 열)이 같은 데이터만 병합하여 출력)
# 두 데이터프레임에서 employee_id가 두 곳 모두에 있는 값은 3, 4이므로 3, 4에 대해서만 다른 데이터가 병합되어 출력됨
pd.merge(df_employees, df_sales, on = 'employee_id')

 

  • how = '' 매개변수를 지정하여 outer, right, left 조인도 실행 가능
  • left, right 조인은 데이터프레임을 매개변수로 주는 순서에 따라 결과가 달라지므로 주의
pd.merge(df_employees, df_sales, on = 'employee_id', how = 'outer')

pd.merge(df_employees, df_sales, on = 'employee_id', how = 'left')

pd.merge(df_employees, df_sales, on = 'employee_id', how = 'right')

 

  • left와 right의 조인 기준열을 각각 지정할 수도 있음
pd.merge(df_employees, df_sales, left_on = 'employee_id', right_on = 'employee_id')

  • 데이터 랭글링은 원본 데이터를 정제하고 사용가능한 형태로 구성하기 위한 변환 과정을 광범위하게 의미하는 비공식 용어
  • 데이터 랭글링은 데이터 전처리의 한 과정
  • 데이터 랭글링에서 사용되는 가장 일반적인 데이터 구조는 데이터프레임

 

1. 데이터프레임 만들기

# 방법 1
import pandas as pd

df = pd.DataFrame()

df['Name'] = ['Jacky Jackson', 'Steven Stevenson']
df['Age'] = [38, 25]
df['Driver'] = [True, False]

df

# 방법 2(numpy 배열을 dataframe으로)
import numpy as np
data = [['Jacky Jackson', 38, True], ['Steven Stevencon', 25, False]]

matrix = np.array(data)
pd.DataFrame(matrix, columns = ['Name', 'Age', 'Driver'])

# or
pd.DataFrame(data, columns = ['Name', 'Age', 'Driver'])

# 방법 3(dictionary를 dataframe으로)
data = {'Name': ['Jacky Jackson', 'Steven Stevenson'],
        'Age': [38, 25],
        'Driver': [True, False]}
pd.DataFrame(data)

# 방법 4(각각의 데이터를 dictionary로, 각 dictionary를 병합)
data = [{'Name': 'Jacky Jackson', 'Age': 25, 'Driver': True},
        {'Name': 'Steven Stevenson', 'Age': 38, 'Driver': False}]
# 인덱스 따로 지정 가능
pd.DataFrame(data, index = ['row1', 'row2'])

 

 

2. 데이터 설명하기

  • 데이터를 적재한 후 확인하는 가장 간단한 방법은 head()를 통해 상위 몇 개의 데이터만 출력해서 확인해보는 것
  • tail()을 사용하면 하위 데이터 출력
df = pd.read_csv('titanic.csv')

# 데이터의 상위 2행만 출력
df.head(2)

 

  • 데이터의 열과 행의 개수 확인
df.shape

# 출력 결과
(891, 12)

 

  • describe()를 사용하여 숫자데이터 열의 통계값 확인
df.describe()

 

 

3. 데이터프레임 탐색하기

  • loc 또는 iloc를 사용해 하나 이상의 행이나 값을 선택
  • loc와 iloc 슬라이싱은 numpy와 달리 마지막 인덱스를 포함
# 첫번째 행 출력
df.iloc[0]

# 출력 결과
PassengerId                          1
Survived                             0
Pclass                               3
Name           Braund, Mr. Owen Harris
Sex                               male
Age                               22.0
SibSp                                1
Parch                                0
Ticket                       A/5 21171
Fare                              7.25
Cabin                              NaN
Embarked                             S
Name: 0, dtype: object
# 콜론(:)을 사용하여 행 슬라이싱
# 첫번째 행부터 네번째 행까지 출력
df.iloc[1:4]

# 슬라이싱의 첫 부분을 공백으로 두면 처음부터 선택
# 슬라이싱의 마지막 부분을 공백으로 두면 마지막까지 선택
df.iloc[:4]

# 데이터프레임에서 행마다 고유한 값을 갖는 특정 열을 인덱스로 설정
# loc를 사용해 고유값으로 해당 행을 슬라이싱 가능
df_name_index = df.set_index(df['Name'])
df_name_index.loc['Braund, Mr. Owen Harris']

# 출력 결과
PassengerId                          1
Survived                             0
Pclass                               3
Name           Braund, Mr. Owen Harris
Sex                               male
Age                               22.0
SibSp                                1
Parch                                0
Ticket                       A/5 21171
Fare                              7.25
Cabin                              NaN
Embarked                             S
Name: Braund, Mr. Owen Harris, dtype: object
# 열을 선택하여 선택한 열만 출력하기도 가능
df[['Age', 'Sex']].head(2)

 

 

4. 조건에 따라 행 선택하기

# 데이터프레임 df[] 안에 조건문인 df['Sex'] == female을 넣어서 True인 행만 출력
df[df['Sex'] == 'female'].head(2)

# &를 사용해 조건 두 개이상도 검색 가능
df[(df['Sex'] == 'female') & (df['Age'] >= 60)].head(2)

 

 

5. 값 치환하기

df['Sex'].replace('female', 'woman').head(2)

# 출력 결과
0     male
1    woman
Name: Sex, dtype: object


# 두 개 이상도 한번에 치환 가능
df['Sex'].replace(['female', 'male'], ['woman', 'man']).head(5)

# 출력 결과
0      man
1    woman
2    woman
3    woman
4      man
Name: Sex, dtype: object


# 한번에 여러 값은 하나의 값으로 치환 가능
df['Sex'].replace(['female', 'male'], 'person').head(5)

# 출력 결과
0    person
1    person
2    person
3    person
4    person
Name: Sex, dtype: object


# 치환할 여러 값을 딕셔너리 형태로 전달 가능
df['Sex'].replace({'female' : 1, 'male' : 0}).head(5)

# 출력 결과
0    0
1    1
2    1
3    1
4    0
Name: Sex, dtype: int64
# 위에서 하나의 열뿐 아니라 데이터프레임 전체의 모든 값을 한번에 치환 가능
df.replace(1, 'one').head(2)

 

 

6. 열 이름 바꾸기

df.rename(columns = {'Pclass' : 'Passenger Class'}).head(2)

 

  • 전체 열 이름을 바꿀 때 열이 많아 각각 입력하기 힘들다면 열 이름은 키로, 값은 비어있는 딕셔너리로 만들어 비어있는 값에 바꿀 열 이름을 넣어서 rename()에 전달하면 편리함
import collections

column_names = collections.defaultdict(str)

for name in df.columns:
    column_names[name]

column_names

# 출력 결과
defaultdict(str,
            {'PassengerId': '',
             'Survived': '',
             'Pclass': '',
             'Name': '',
             'Sex': '',
             'Age': '',
             'SibSp': '',
             'Parch': '',
             'Ticket': '',
             'Fare': '',
             'Cabin': '',
             'Embarked': ''})

 

  • rename(columns = {}) 대신 rename(index = {})으로 인덱스 치환가능
df.rename(index = {0:-1}).head(2)

 

  • 열 이름 소문자로 바꾸기
# 변환 함수 전달
df.rename(str.lower, axis = 'columns').head(2)

 

 

7. 최소값, 최대값, 합, 평균 계산 및 개수 세기

  • 각 통계값에 맞는 메서드 사용
    • 최대값: max()
    • 최소값: min()
    • 평균: mean()
    • 합: sum()
    • 개수: count()
    • 분산: var()
    • 표준편차: std()
    • 첨도: kurt()
    • 왜도: skew()
    • 평균의 표준오차: sem()
    • 최빈값: mode()
    • 중간값: median()
    • 상관계수: corr()
    • 공분산: cov()
print('최대값:', df['Age'].max())
print('최th값:', df['Age'].min())
print('평균:', df['Age'].mean())
print('합:', df['Age'].sum())
print('카운트:', df['Age'].count())

# 출력 결과
최대값: 80.0
최th값: 0.42
평균: 29.69911764705882
합: 21205.17
카운트: 714

 

  • 데이터프레임 전체에도 적용 가능
df.count()

# 출력 결과
PassengerId    891
Survived       891
Pclass         891
Name           891
Sex            891
Age            714
SibSp          891
Parch          891
Ticket         891
Fare           891
Cabin          204
Embarked       889
dtype: int64

 

 

8. 고유값 찾기

  • unique 메서드를 사용해 열에서 고유한 값을 모두 찾음
df['Sex'].unique()

# 출력 결과
array(['male', 'female'], dtype=object)

 

  • nunique 메서드는 unique 값의 개수를 출력
df['Sex'].nunique()

# 출력 결과
2


# nunique는 데이터프레임 전체에 적용 가능
df.nunique()

# 출력 결과
PassengerId    891
Survived         2
Pclass           3
Name           891
Sex              2
Age             88
SibSp            7
Parch            7
Ticket         681
Fare           248
Cabin          147
Embarked         3
dtype: int64

 

  • value_counts() 메서드를 사용해 열에서 고유한 값 각각의 개수 출력
df['Sex'].value_counts()

# 출력 결과
Sex
male      577
female    314
Name: count, dtype: int64

 

 

9. 누락된 값 다루기

  •  isnull()과 notnull() 메서드는 null인지 아닌지에 대한 답을 boolean값(True나 False)으로 나타냄
# 조건으로 isnull()을 주어 True나 False의 값이 나오므로 True인 행만 찾아 출력 가능
df[df['Age'].isnull()].head()

 

  • read_csv() 메서드에서 na_values = [] 옵션을 사용해 null 값으로 선언할 값들은 지정
  • read_csv() 메서드에서 keep_default_na 옵션을 False로 하여 pandas에서 기본적으로 null 값으로 인식하는 값을을 null값이 아니게 만들 수 있음
  • read_csv() 메서드에서 na_filter 옵션을 False로 하면 NaN 변환을 하지 않음

 

 

10. 열 삭제하기

  • drop() 메서드 사용
# 축은 열을 나타내는 1로 지정
df.drop('Age', axis = 1)

 

  • 한 번에 여러개의 열 삭제 가능
df.drop(['Age', 'Sex'], axis = 1)

 

  • 열 이름이 없다면 열의 인덱스 번호를 사용해 삭제 가능
# 인덱스가 1인 열(두번째 열) 삭제
df.drop(df.columns[1], axis = 1)

 

  • del df['Age']를 사용해 열을 삭제할 수 있지만 이는 원보 데이터프레임을 바꿔 원본 데이터의 손상을 유발할 수 있어 추천 X
  • inplace = True 옵션을 사용해도 원본 데이터가 변형되므로 사용 X
  • 또는, 원본 데이터는 놔두고 데이터를 조작할 새로운 데이터프레임을 복사하여 만들면 좋음
  • 머신러닝을 위한 원본 데이터는 로그 파일이나 데이터셋 파일, 데이터베이스, 여러 소스에서 추출
  • CSV, SQL 데이터베이스 등 다양한 소스에서 데이터를 적재하는 방법 학습
  • 실험에 필요한 특성을 가진 모의 데이터 생성 방법 학습
  • 외부 데이터 적재에는 판다스 / 모의 데이터 생성에는 사이킷런 사용

1. 샘플 데이터셋 적재하기

  • 사이킷런의 예제 데이터 사용
# pip install scikit-learn
from sklearn import datasets

# 숫자 데이터셋 적재
digits = datasets.load_digits()

# 특성 행렬
features = digits.data

# 타깃 벡터 생성
target = digits.target

# 첫번째 샘플 확인
features[0]

# 출력 결과
array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,
       15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,
       12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,
        0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,
       10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])

 

  • 사이킷런 예제 데이터들은 딕셔너리 구조를 가지고 있어 keays와 values를 반환함
digits.keys()

# 출력 결과
dict_keys(['data', 'target', 'frame', 'feature_names', 'target_names', 'images', 'DESCR'])


# DESCR 키는 데이터셋에 대한 설명
print(digits['DESCR'])

# 출력 결과
.. _digits_dataset:

Optical recognition of handwritten digits dataset
--------------------------------------------------

**Data Set Characteristics:**

    :Number of Instances: 1797
    :Number of Attributes: 64
    :Attribute Information: 8x8 image of integer pixels in the range 0..16.
    :Missing Attribute Values: None
    :Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
    :Date: July; 1998

This is a copy of the test set of the UCI ML hand-written digits datasets
https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits

The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.

Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
...
    2005.
  - Claudio Gentile. A New Approximate Maximal Margin Classification
    Algorithm. NIPS. 2000.

 

  • load_데이터셋의 유일한 매개변수인 return_X_y를 True로 하면 특성 X와 타깃 y배열을 반환
import numpy as np

# digits 함수는 특별히 n_class 매개변수를 사용하여 필요한 숫자 개수 지정가능
# 0~4까지 5개의 숫자만 y값에 넣도록 매개변수 설정
X, y = datasets.load_digits(n_class = 5, return_X_y = True)

np.unique(y)
# 출력 결과
array([0, 1, 2, 3, 4])

 

 

2. 모의 데이터셋 만들기

  • 선형 회귀 알고리즘에 사용하려면 make_regression 추천
  • make_regression은 실수 특성 행렬과 실수 타깃 벡터 반환
from sklearn.datasets import make_regression

# n_features는 전체 특성
# n_informative는 타깃 벡터 생성에 사용할 특성
features, target, coefiicients = make_regression(n_samples = 100,
                                                 n_features = 3,
                                                 n_informative = 3,
                                                 n_targets = 1,
                                                 noise = 0.0,
                                                 coef = True,
                                                 random_state = 1)
from sklearn.datasets import make_regression
features, target, coefiicients = make_regression(n_samples = 100,
                                                 n_features = 3,
                                                 n_informative = 3,
                                                 n_targets = 1,
                                                 noise = 0.0,
                                                 coef = True,
                                                 random_state = 1)
print('특성 행렬\n', features[:3])
print('타깃 벡터\n', target[:3])

# 출력 결과
특성 행렬
 [[ 1.29322588 -0.61736206 -0.11044703]
 [-2.793085    0.36633201  1.93752881]
 [ 0.80186103 -0.18656977  0.0465673 ]]
타깃 벡터
 [-10.37865986  25.5124503   19.67705609]

 

  • 분류 알고리즘에 사용하려면 make_classification 추천
  • make_classification은 실수 특성 행렬과 정수 타깃 벡터 반환
from sklearn.datasets import make_classification
# n_redundant는 필요없는 특성의 수, n_informative 특성의 랜덤 선형 결합으로 생성됨
# weights는 차례대로 각각 첫번째 ,두번째 클래스의 비율
features, target = make_classification(n_samples = 100,
                                                     n_features = 3,
                                                     n_informative = 3,
                                                     n_redundant = 0,
                                                     n_classes = 2,
                                                     weights = [.25, .75],
                                                     random_state = 1)
print('특성 행렬\n', features[:3])
print('타깃 벡터\n', target[:3])

# 출력 결과
특성 행렬
 [[ 1.06354768 -1.42632219  1.02163151]
 [ 0.23156977  1.49535261  0.33251578]
 [ 0.15972951  0.83533515 -0.40869554]]
타깃 벡터
 [1 0 0]

 

  • 군집 알고리즘에 사용하려면 make_blobs 추천
  • make_blobs은 실수 특성 행렬과 정수 타깃 벡터 반환
from sklearn.datasets import make_blobs
features, target = make_blobs(n_samples = 100,
                                       n_features = 2,
                                       centers = 3,
                                       cluster_std = 0.5,
                                       shuffle = True,
                                       random_state = 1)
print('특성 행렬\n', features[:3])
print('타깃 벡터\n', target[:3])

# 출력 결과
특성 행렬
 [[ -1.22685609   3.25572052]
 [ -9.57463218  -4.38310652]
 [-10.71976941  -4.20558148]]
타깃 벡터
 [0 1 1]
# 만들어진 군집 데이터 시각화
# pip install matplotlib
import matplotlib.pyplot as plt

plt.scatter(features[:, 0], features[:, 1], c = target)
plt.show()

 

 

3. CSV 파일 적재하기

  • pandas 라이브러리의 read_csv() 사용
  • 매개변수
    • sep = ',': 파일이 사용하는 구분자를 ','로 지정
    • skiprows = range(1, 11): 1행부터 12행까지 건너뛴 후 출력
    • nrows = 1: 한 행 출력
import pandas as pd

dataframe = pd.read_csv('csv 경로')

 

 

4. 엑셀 파일 적재하기

  • pandas 라이브러리의 read_excel() 사용
  • read_excel()을 사용하려면 xlrd 패키지 설치 필요
  • 매개변수
    • sheet_name: 시트 이름 문자열 또는 시트의 위치를 나타내는 정수(0부터 시작되는 인덱스)
    • na_filter: 결측값 탐지, 결측값이 없는 데이터라면 해당 옵션을 제외하는 것이 성능 향상에 도움
    • skip_rows
    • nrows
    • keep_default_na: 공백을 NA로 지정할지 결정
    • na_values = '-': 결측값을 '-'로 표시
# pip install xlrd
import pandas as pd

dataframe = pd.read_excel('excel 경로')

 

 

5. JSON 파일 적재하기

  • pandas 라이브러리의 read_json() 사용
  • 매개변수
    • orient = 'columns'(split, records, index, columns, values 중 하나): JSON 파일의 구성 형식 지정, 어떤 값을 넣어야하는 지 알아내기 위해서는 실험이 필요
      • 'split': {"index" : [인덱스, ...], "columns" : [열, ...], "data" : [값, ...]}
      • 'records': {[열 : 값}, ..., {열 : 값}]
      • 'index': {인덱스 : {열 : 값, ...}, ...}
      • 'values': [값, ...]
      • 'columns': {열: {인덱스 : 값, ...}, ...}
    • json_normalize: 구조화가 덜 된 JSON 데이터를 데이터프레임으로 변환하는 도구
import pandas as pd

dataframe = pd.read_json('json 경로', orient = 'columns')

 

 

6. SQL 데이터베이스로부터 적재하기

  • pandas 라이브러리의 read_sql_query() 사용
  • 먼저, SQLite 데이터베이스 엔진으로 연결하기 위해 create_engine 함수 사용
  • 이후, read_sql_query 함수로 SQL을 사용하여 데이터베이스에 질의, 그 결과를 데이터프레임으로 가져옴
# pip install sqlalchemy
import pandas as pd
from sqlalchemy import create_engine

# sample.db라는 데이터베이스에 연결
database_connection = create_engine('sqlite:///sample.db')

# sample.db에서 data라는 이름의 테이블의 모든 열을 반환하라고 요청
dataframe = pd.read_sql_query('SELECT * FROM data', database_connection)

# 모든 행을 가져올 때는 질의문 없이 read_sql_table() 함수도 사용 가능
dataframe = pd.read_sql_table('data', database_connection)

1. 순환 신경망(Recurrent Neural Network, RNN)

  • 루프(loop)를 가진 신경망의 한 종류
  • 시퀀스의 원소를 순회하면서 지금가지 처리한 정보를 상태(state)에 저장

https://aditi-mittal.medium.com/understanding-rnn-and-lstm-f7cdf6dfc14e

 

  - 순환 신경망 레이어(RNN Layer)

  • 입력: (timesteps, input_features)
  • 출력: (timesteps, output_features)
# numpy로 RNN 구조 표현
import numpy as np

timesteps = 100
input_features = 32
output_features = 64

inputs = np.random.random((timesteps, input_features))

state_t = np.zeros((output_features, ))

W = np.random.random((output_features, input_features))
U = np.random.random((output_features, output_features))
b = np.random.random((output_features, ))

sucessive_outputs = []

for input_t in inputs:
    output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
    sucessive_outputs.append(output_t)
    state_t = output_t

final_output_sequence = np.stack(sucessive_outputs, axis = 0)

 

  - 케라스의 순환층

  • SimpleRNN layer
  • 입력: (batch_size, timesteps, input_features)
  • 출력
    • return_sequences로 결정할 수 있음
    • 3D 텐서
      • timesteps의 출력을 모든 전체 sequences를 반환
      • (batch_size, timesteps, output_features)
    • 2D 텐서
      • 입력 sequence에 대한 마지막 출력만 반환
      • (batch_size, output_features)
from tensorflow.keras.layers import SimpleRNN, Embedding
from tensorflow.keras.models import Sequential

model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))  # SimpleRNN 안에 return_sequences = True옵션을 추가하면 전체 sequences를 return시켜줌
model.summary()

# 출력 결과
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding (Embedding)       (None, None, 32)          320000    
                                                                 
 simple_rnn (SimpleRNN)      (None, 32)                2080      
                                                                 
=================================================================
Total params: 322,080
Trainable params: 322,080
Non-trainable params: 0
_________________________________________________________________
  • 네트워크의 표현력을 증가시키기 위해 여러 개의 순환층을 차례대로 쌓는 것이 유용할 때가 있음
    • 이런 설정에서는 중간층들이 전체 출력 sequences를 반환하도록 설정
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences = True))
model.add(SimpleRNN(32, return_sequences = True))
model.add(SimpleRNN(32, return_sequences = True))
model.add(SimpleRNN(32))
model.summary()

# 출력 결과
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_2 (Embedding)     (None, None, 32)          320000    
                                                                 
 simple_rnn_2 (SimpleRNN)    (None, None, 32)          2080      
                                                                 
 simple_rnn_3 (SimpleRNN)    (None, None, 32)          2080      
                                                                 
 simple_rnn_4 (SimpleRNN)    (None, None, 32)          2080      
                                                                 
 simple_rnn_5 (SimpleRNN)    (None, 32)                2080      
                                                                 
=================================================================
Total params: 328,320
Trainable params: 328,320
Non-trainable params: 0
_________________________________________________________________

 

  - LMDB 데이터 적용

  - 데이터 로드

from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing import sequence

num_words = 10000
max_len = 500
batch_size = 32

(input_train, y_train), (input_test, y_test) = imdb.load_data(num_words = num_words)
print(len(input_train))  # 25000
print(len(input_test))   # 25000

input_train = sequence.pad_sequences(input_train, maxlen = max_len)
input_test = sequence.pad_sequences(input_test, maxlen = max_len)
print(input_train.shape) # (25000, 500)
print(input_test.shape)  # (25000, 500)

 

  - 모델 구성

from tensorflow.keras.layers import Dense

model = Sequential()

model.add(Embedding(num_words, 32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation = 'sigmoid'))

model.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['acc'])

model.summary()

# 출력 결과
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_3 (Embedding)     (None, None, 32)          320000    
                                                                 
 simple_rnn_6 (SimpleRNN)    (None, 32)                2080      
                                                                 
 dense (Dense)               (None, 1)                 33        
                                                                 
=================================================================
Total params: 322,113
Trainable params: 322,113
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 학습

history = model.fit(input_train, y_train,
                    epochs = 10,
                    batch_size = 128,
                    validation_split = 0.2)

 

 

  - 시각화

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['acc']
val_acc = history.history['val_acc']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'train loss')
plt.plot(epochs, val_loss, 'r:', label = 'validation loss')
plt.grid()
plt.legend()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'train accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'validation accuracy')
plt.grid()
plt.legend()

model.evaluate(input_test, y_test)

# 출력 결과
loss: 0.6755 - acc: 0.7756
[0.6754735112190247, 0.7755600214004517]
  • 전체 sequences가 아니라 순서대로 500개의 단어만 입력했기 때문에 성능이 낮게 나옴
  • simpleRNN은 긴 sequence를 처리하는데 적합하지 않음

 

 

2. LSTM과 GRU 레이어

  • Simple RNN은 실전에 사용하기엔 너무 단순
  • SimpleRNN은 이론적으로 시간 t에서 이전의 모든 timesteps의 정보를 유지할 수 있지만, 실제로는 긴 시간에 걸친 의존성은 학습할 수 없음
  • 그레디언트 소실 문제(vanishing gradient problem)
    • 이를 방지하기 위해 LSTM, GRU 같은 레이어 등장

 

  - LSTM(Long-Short-Term Memory)

  • 장단기 메모리 알고리즘
  • 나중을 위해 정보를 저장함으로써 오래된 시그널이 점차 소실되는 것을 막아줌

https://colah.github.io/posts/2015-08-Understanding-LSTMs/

 

  - 예제 1) Reyters

  • IMDB와 유사한 데이터셋(텍스트 데이터)
  • 46개의 상호 배타적인 토픽으로 이루어진 데이터셋
    • 다중 분류 문제

  - 데이터셋 로드

from tensorflow.keras.datasets import reuters

num_words = 10000
(x_train, y_train), (x_test, y_test) = reuters.load_data(num_words = num_words)

print(x_train.shape) # (8982,)
print(y_train.shape) # (8982,)
print(x_test.shape)  # (2246,)
print(y_test.shape)  # (2246,)

 

  - 데이터 전처리 및 확인

from tensorflow.keras.preprocessing.sequence import pad_sequences

max_len = 500

pad_x_train = pad_sequences(x_train, maxlen = max_len)
pad_x_test = pad_sequences(x_test, maxlen = max_len)

print(len(pad_x_train[0]))  # 500

pad_x_train[0]

 

  - 모델 구성

  • LSTM 레이어도 SimpleRNN과 같이 return_sequences 인자 사용 가능
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Embedding

model = Sequential()
model.add(Embedding(input_dim = num_words, output_dim = 64))
model.add(LSTM(64, return_sequences = True))
model.add(LSTM(32))
model.add(Dense(46, activation = 'softmax'))

model.compile(optimizer = 'adam',
              loss = 'sparse_categorical_crossentropy',
              metrics = ['acc'])
model.summary()

# 출력 결과
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_1 (Embedding)     (None, None, 64)          640000    
                                                                 
 lstm (LSTM)                 (None, None, 64)          33024     
                                                                 
 lstm_1 (LSTM)               (None, 32)                12416     
                                                                 
 dense (Dense)               (None, 46)                1518      
                                                                 
=================================================================
Total params: 686,958
Trainable params: 686,958
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 학습

history = model.fit(pad_x_train, y_train,
                    epochs = 20,
                    batch_size = 32,
                    validation_split = 0.2)

 

  - 시각화

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['acc']
val_acc = history.history['val_acc']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'train loss')
plt.plot(epochs, val_loss, 'r:', label = 'validation loss')
plt.grid()
plt.legend()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'train accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'validation accuracy')
plt.grid()
plt.legend()

 

  - 모델 평가

model.evaluate(pad_x_test, y_test)

# 출력 결과
loss: 1.6927 - acc: 0.6336
[1.692732810974121, 0.6335707902908325]

 

  - 예제 2) IMDB 데이터셋

  - 데이터 로드

from tensorflow.keras.datasets import imdb
from tensorflow.kears.preprocessing.sequence import pad_sequences

num_words = 10000
max_len = 500
batch_size = 32

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words = num_words)

pad_x_train = sequence.pad_sequences(x_train, maxlen = max_len)
pad_x_test = sequence.pad_sequences(x_test, maxlen = max_len)

 

  - 모델 구성

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Embedding

model = Sequential()
model.add(Embedding(num_words, 32))
model.add(LSTM(32))
model.add(Dense(1, activation = 'sigmoid'))

model.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['acc'])
model.summray()

# 출력 결과
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_3 (Embedding)     (None, None, 32)          320000    
                                                                 
 lstm_3 (LSTM)               (None, 32)                8320      
                                                                 
 dense_2 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 328,353
Trainable params: 328,353
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 학습

history = model.fit(pad_x_train, y_train,
                    epochs = 10,
                    batch_size = 128,
                    validation_split = 0.2)

 

  - 시각화

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['acc']
val_acc = history.history['val_acc']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'train loss')
plt.plot(epochs, val_loss, 'r:', label = 'validation loss')
plt.grid()
plt.legend()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'train accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'validation accuracy')
plt.grid()
plt.legend()

 

  - 모델 평가

model.evaluate(pad_x_test, y_test)

# 출력 결과
loss: 0.9135 - acc: 0.7898
[0.9135046601295471, 0.7898399829864502]
  • LSTM 쓰기전, SimpleRNN을 썻을 때 loss가 0.6755, acc가 0.7756으로 나온 것에 비해 좋은 결과가 나옴

 

 

3. Cosine 함수를 이용한 순환 신경망

# 코사인 시계열 데이터
import numpy as np

np.random.seed(111)
time = np.arange(30 * 12 + 1)
month_time = (time % 30) / 30
time_series = 20 * np.where(month_time < 0.5,
                            np.cos(2 * np.pi * month_time),
                            np.cos(2 * np.pi * month_time) + np.random.random(361))
plt.figure(figsize = (15, 8))
plt.xlabel('Time')
plt.ylabel('Value')
plt.plot(np.arange(0, 30 * 11 + 1),
         time_series[:30 * 11 + 1],
         color = 'blue', alpha = 0.6, label = 'Train Data')
plt.plot(np.arange(30 * 11, 30 * 12 + 1),
         time_series[30 * 11:],
         color = 'orange', label = 'Test Data')
plt.show()

 

  - 데이터 전처리

def make_data(time_series, n):
    x_train_full, y_train_full = list(), list()

    for i in range(len(time_series)):
        x = time_series[i:(i + n)]
        if (i + n) < len(time_series):
            x_train_full.append(x)
            y_train_full.append(time_series[i + n])
        else:
            break
    
    x_train_full, y_train_full = np.array(x_train_full), np.array(y_train_full)

    return x_train_full, y_train_full

n = 10
x_train_full, y_train_full = make_data(time_series, n)

print(x_train_full.shape) # (351, 10)
print(y_train_full.shape) # (351,)


# 뒤에 1씩 추가
x_train_full = x_train_full.reshape(-1, n, 1)
y_train_full = y_train_full.reshape(-1, n, 1)

print(x_train_full.shape) # (351, 10, 1)
print(y_train_full.shape) # (351, 1)

 

  - 테스트 데이터셋 생성

x_train_full = x_train_full.reshape(-1, n, 1)
y_train_full = y_train_full.reshape(-1, n, 1)

print(x_train_full.shape)
print(y_train_full.shape)


# train 데이터와 test 데이터 분리
x_train = x_train_full[:30 * 11]
y_train = y_train_full[:30 * 11]

x_test = x_train_full[30 * 11:]
y_test = y_train_full[30 * 11:]

print(x_train.shape) # (330, 10, 1)
print(y_train.shape) # (330, 1)
print(x_test.shape)  # (21, 10, 1)
print(y_test.shape)  # (21, 10, 1)

 

  - 데이터 확인

sample_series = np.arange(100)
a, b = make_data(sample_series, 10)

print(a[0])  # [0 1 2 3 4 5 6 7 8 9]
print(b[0])  # 10

 

  - 모델 구성

from tensorflow.keras.layers import SimpleRNN, Flatten, Dense
from tensorflow.keras.models import Sequential

def build_model(n):
    model = Sequential()

    model.add(SimpleRNN(units = 32, activation = 'tanh', input_shape = (n, 1)))
    model.add(Dense(1))

    model.compile(optimizer = 'adam',
                  loss = 'mse')
    return model

model = build_model(10)
model.summary()

# 출력 결과
Model: "sequential_4"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 simple_rnn (SimpleRNN)      (None, 32)                1088      
                                                                 
 dense_3 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 1,121
Trainable params: 1,121
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 학습

model.fit(x_train, y_train,
          epochs = 100, batch_size = 12)

 

  - 예측값 그려보기

prediction = model.predict(x_test)

pred_range = np.arange(len(y_train), len(y_train) + len(prediction))

plt.figure(figsize = (12, 5))
plt.xlabel('Time')
plt.ylabel('Value')
plt.plot(pred_range, y_test.flatten(), color = 'orange', label = 'Ground Truth')
plt.plot(pred_range, prediction.flatten(), color = 'blue', label = 'Prediction')
plt.legend()
plt.show()

 

  - 모델 재구성

  • LSTM 사용
from tensorflow.keras.layers import LSTM

def build_model2(n):
    model = Sequential()

    model.add(LSTM(units = 64, return_sequences = True, input_shape = (n, 1)))
    model.add(LSTM(32))
    model.add(Dense(1))

    model.compile(optimizer = 'adam',
                  loss = 'mse')
    return model

model2 = build_model2(10)
model2.summary()

# 출력 결과
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_4 (LSTM)               (None, 10, 64)            16896     
                                                                 
 lstm_5 (LSTM)               (None, 32)                12416     
                                                                 
 dense_5 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 29,345
Trainable params: 29,345
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 재학습 및 예측값 그려보기

model2.fit(x_train, y_train,
           epochs = 100, batch_size = 12)

prediction_2 = model_2.predict(x_test)

pred_range = np.arange(len(y_train), len(y_train) + len(prediction_2))

plt.figure(figsize = (12, 5))
plt.xlabel('Time')
plt.ylabel('Value')
plt.plot(pred_range, y_test.flatten(), color = 'orange', label = 'Ground Truth')
plt.plot(pred_range, prediction.flatten(), color = 'r:', label = 'Model1 Prediction')
plt.plot(pred_range, prediction_2.flatten(), color = 'blue', label = 'Model2 Prediction')
plt.legend()
plt.show()

 

  - 모델 재구성

  • GRU 사용(LSTM보다 더 쉬운 구조)
from tensorflow.keras.layers import GRU

def build_model3(n):
    model = Sequential()

    model.add(GRU(units = 30, return_sequences = True, input_shape = (n, 1)))
    model.add(GRU(30))
    model.add(Dense(1))

    model.compile(optimizer = 'adam',
                  loss = 'mse')
    return model

model_3 = build_model3(10)
model_3.summary()

# 출력 결과
Model: "sequential_7"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 lstm_6 (LSTM)               (None, 10, 64)            16896     
                                                                 
 lstm_7 (LSTM)               (None, 32)                12416     
                                                                 
 dense_6 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 29,345
Trainable params: 29,345
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 재학습 및 예측값 그려보기

model_3.fit(x_train, y_train,
           epochs = 100, batch_size = 12)

prediction_3 = model_3.predict(x_test)

pred_range = np.arange(len(y_train), len(y_train) + len(prediction_3))

plt.figure(figsize = (12, 5))
plt.xlabel('Time')
plt.ylabel('Value')
plt.plot(pred_range, y_test.flatten(), color = 'orange', label = 'Ground Truth')
plt.plot(pred_range, prediction.flatten(), color = 'r:', label = 'Model1 Prediction')
plt.plot(pred_range, prediction_2.flatten(), color = 'blue', label = 'Model2 Prediction')
plt.plot(pred_range, prediction_2.flatten(), color = 'blue', label = 'Model3 Prediction')
plt.legend()
plt.show()

 

  - Conv1D

  • 텍스트 분류나 시계열 예측같은 간단한 문제, 오디오 생성, 기계 번역 등의 문제에서 좋은 성능
  • timestep의 순서에 민감 X
  • 2D Convolution
    • 지역적 특징을 인식
  • 2D Convolution
    • 문맥을 인식

 

  - Conv1D Layer

  • 입력: (batch_size, timesteps, channels)
  • 출력: (batch_size, timesteps, filters)
  • 필터의 사이즈가 커져도 모델이 급격히 증가하지 않기 때문에 다양한 크기를 사용할 수 있음
  • 데이터의 품질이 좋으면 굳이 크기를 달리하여 여러 개를 사용하지 않아도 될 수도 있음

 

  - MaxPooling1D Layer

  • 다운 샘플링 효과
  • 단지 1차원형태로 바뀐 것 뿐

 

  - GlovalMaxPooling Layer

  • 배치 차원을 제외하고 2차원 형태를 1차원 형태로 바꾸어주는 레이어
  • Flatten layer로 대신 사용가능

 

  - IMDB 데이터셋

  - 데이터 로드 및 전처리

from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.layers import Dense, Embedding, Conv1D, MaxPooling1D, GlobalMaxPooling1D

num_words = 10000
max_len = 500
batch_size = 32

(input_train, y_train), (input_test, y_test) = imdb.load_data(num_words = num_words)

print(len(input_train))  # 25000
print(len(input_test))   # 25000

pad_x_train = pad_sequences(input_train, maxlen = max_len)
pad_x_test = pad_sequences(input_test, maxlen = max_len)

print(pad_x_train.shape) # (25000, 500)
print(pad_x_test.shape)  # (25000, 500)

 

  -모델 구성

def build_model():
    model = Sequential()

    model.add(Embedding(input_dim = num_words, output_dim = 32,
                        input_length = max_len))
    model.add(Conv1D(32, 7, activation = 'relu'))
    model.add(MaxPooling1D(7))
    model.add(Conv1D(32, 5, activation = 'relu'))
    model.add(MaxPooling1D(5))
    model.add(GlobalMaxPooling1D())
    model.add(Dense(1, activation = 'sigmoid'))

    model.compile(optimizer = RMSprop(learning_rate = 1e-4),
                  loss ='binary_crossentropy',
                  metrics = ['accuracy'])
    
    return model

model = build_model()
model.summary()

# 출력 결과
Model: "sequential_13"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_5 (Embedding)     (None, 500, 32)           320000    
                                                                 
 conv1d_2 (Conv1D)           (None, 494, 32)           7200      
                                                                 
 max_pooling1d_2 (MaxPooling  (None, 70, 32)           0         
 1D)                                                             
                                                                 
 conv1d_3 (Conv1D)           (None, 66, 32)            5152      
                                                                 
 max_pooling1d_3 (MaxPooling  (None, 13, 32)           0         
 1D)                                                             
                                                                 
 global_max_pooling1d_1 (Glo  (None, 32)               0         
 balMaxPooling1D)                                                
                                                                 
 dense_12 (Dense)            (None, 1)                 33        
                                                                 
=================================================================
Total params: 332,385
Trainable params: 332,385
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 학습

history = model.fit(pad_x_train, y_train,
                    epochs = 30,
                    batch_size = 128,
                    validation_split = 0.2)

 

  - 시각화

import matplotlib.pyplot as plt

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'train loss')
plt.plot(epochs, val_loss, 'r:', label = 'validation loss')
plt.grid()
plt.legend()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'train accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'validation accuracy')
plt.grid()
plt.legend()

model.evaluate(pad_x_test, y_test)

# 출력 결과
loss: 0.3534 - accuracy: 0.8526
[0.35335206985473633, 0.8525999784469604]
  • 과적합이 일어났지만, 다른 optimizer 사용, 규제화를 걸어보는 등 다양하게 시도해볼 수 있음

9. Keras에서 Word2Vec 직접 학습

  - 데이터 준비

from tensorflow.keras.datasets import imdb

(x_train, y_train), (x_test, y_test) = imdb.load_data()
  • 단어 번호와 단어의 관계를 사전으로 만듦
  • 1번은 문장의 시작, 2번은 사전에 없는 단어(OOV)로 미리 지정
word_index = imdb.get_word_index()
index_word = {idx + 3 : word for word, idx in word_index.items()}

index_word[1] = '<START>'
index_word[2] = '<UNKNOWN>'

' '.join(index_word[i] for i in x_train[0])

# 출력 결과
"<START> this film was just brilliant casting location scenery story direction everyone's really
suited the part they played and you could just imagine being there robert redford's is an
amazing actor and now the same being director norman's father came from the same scottish
island as myself so i loved the fact there was a real connection with this film the witty
remarks throughout the film were great it was just brilliant so much that i bought the film
as soon as it was released for retail and would recommend it to everyone to watch and the fly
fishing was amazing really cried at the end it was so sad and you know what they say if you
cry at a film it must have been good and this definitely was also congratulations to the two
little boy's that played the part's of norman and paul they were just brilliant children are
often left out of the praising list i think because the stars that play them all grown up are
such a big profile for the whole film but these children are amazing and should be praised for
what they have done don't you think the whole story was so lovely because it was true and was
someone's life after all that was shared with us all"
num_words = max(index_word) + 1

 

  - 텍스트를 단어 번호로 바꾸기

texts = []
for data in x_train:
    text = ' '.join(index_word[i] for i in data)
    texts.append(text)

len(texts)  # 25000
  • Tokenizer를 사용해 텍스트를 단어로 바꿈
from keras.preprocessing.text import Tokenizer

tok = Tokenizer()
tok.fit_on_texts(texts)

new_data = tok.texts_to_sequences(texts)
new_data[0][:10]

# 출력 결과
[28, 11, 19, 13, 41, 526, 968, 1618, 1381, 63]
# 모든 데이터 문장을 토큰화하고 위의 문장을 그 토큰으로 바꾼뒤 10개만 출력

 

  - 단어쌍 만들기

from tensorflow.keras.preprocessing.sequence import make_sampling_table, skipgrams

# 전제 토큰 개수
VOCAB_SIZE = len(tok.word_index)
print(VOCAB_SIZE)  # 88581
  • 단어를 무작위로 추출하면 자주 나오는 단어가 더 많이 나오게 됨
  • 이를 방지하기위해 단어를 추출할 확률의 균형을 맞춘 샘플링 표를 생성
table = make_sampling_table(VOCAB_SIZE)
  • 두 단어씩 뽑아 좌우 2단어(window_size = 2)안에 들어있는 경우가 있는지 없는지 확인하며 데이터 생성
couples, labels = skipgrams(data, VOCAB_SIZE, window_size = 2, sampling_table = table)
couples[:5]

# 출력 결과
[[16876, 497], [9685, 21], [16876, 21917], [383, 5452], [2098, 13577]]
  • labels에는 윈도우 안에 들어있는 경우가 있으면 1, 없으면 0
labels[:5]

# 출력 결과
[1, 1, 0, 0, 0]
  • 대상 단어는 word_target으로, 맥락 단어는 word_context로 모음
word_target, word_context = zip(*couples)
  • 배열로 바꿈
word_target = np.asarray(word_target, dtype = 'int32')
word_context = np.asarray(word_context, dtype = 'int32')
labels = np.asarray(labels, dtype = 'int32')

word_target.shape    # (288,)
word_context.shape   # (288,)

 

  - skip-gram 모형

  • skip-gram 모형은 함수형 API를 사용해야 함
from tensorflow.keras.layers import Activation, Dot, Embedding, Flatten, Input, Reshape
from tensorflow.keras.models import Model

def build_model():
    input_target = Input(shape = (1, ))
    input_context = Input(shape = (1, ))

    emb = Embedding(input_dim = VOCAB_SIZE, output_dim = 8)
    target = emb(input_target)
    context = emb(input_context)

    dot = Dot(axes = 2)([target, context])
    flatten = Reshape((1, ))(dot)
    output = Activation('sigmoid')(flatten)
    skipgram = Model(inputs = [input_target, input_context], outputs = output)

    return skipgram

model = build_model()
model.summary()

# 출력 결과
Model: "model"
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to                     
==================================================================================================
 input_3 (InputLayer)           [(None, 1)]          0           []                               
                                                                                                  
 input_4 (InputLayer)           [(None, 1)]          0           []                               
                                                                                                  
 embedding_5 (Embedding)        (None, 1, 8)         708648      ['input_3[0][0]',                
                                                                  'input_4[0][0]']                
                                                                                                  
 dot (Dot)                      (None, 1, 1)         0           ['embedding_5[0][0]',            
                                                                  'embedding_5[1][0]']            
                                                                                                  
 reshape (Reshape)              (None, 1)            0           ['dot[0][0]']                    
                                                                                                  
 activation (Activation)        (None, 1)            0           ['reshape[0][0]']                
                                                                                                  
==================================================================================================
Total params: 708,648
Trainable params: 708,648
Non-trainable params: 0
__________________________________________________________________________________________________

 

  - 모델 컴파일 및 학습

from tensorflow.keras.optimizers import Adam

model.compile(optimizer = Adam(),
              loss = 'binary_crossentropy',
              metrics = ['accuracy'])

model.fit([word_target, word_context], labels, epochs = 30)

 

  - 임베딩 레이어 저장 및 로드

emb = model.layers[2]
emb.get_weights()

# 출력 결과
[array([[ 0.01938832,  0.01921825, -0.0462908 , ...,  0.01147114,
         -0.04764376,  0.01121316],
        [-0.01068624, -0.04315212,  0.00839611, ..., -0.02030395,
         -0.02321514, -0.03680412],
        [ 0.00915837,  0.00973357,  0.00904005, ...,  0.01291057,
          0.04295233,  0.0488804 ],
        ...,
        [ 0.01314208,  0.02786795,  0.01130085, ...,  0.03705814,
          0.0427903 ,  0.0109529 ],
        [-0.03585767, -0.04641544, -0.02590518, ..., -0.00451361,
         -0.03019956,  0.01893195],
        [ 0.00769577, -0.02014879, -0.03623866, ..., -0.03457584,
         -0.02138668,  0.02141118]], dtype=float32)]
# 임베딩 레이어 저장
np.save('emb.npy', emb.get_weights()[0])
  • 임베딩 레이어 로드
w = np.load('emb.npy')
  • 임베딩 레이어를 추가할 때 trainable을 False로 하면 추가학습이 이루어 지지 않음
emb_ff = Embedding(input_dim = num_words, output_dim = 8, input_length = 30,
                   weights = [w], trainable = False)

 

 

10. 사전 훈련된 단어 임베딩 사용하기: GloVe 임베딩

  - 원본 IMDB 텍스트 내려받기

import wget
import os
import zipfile

wget.download("http://mng.bz/0tIo")

local_zip = '0tIo'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall()
zip_ref.close()

imdb_dir = "aclImdb"
train_dir = os.path.join(imdb_dir, 'train')

labels = []
texts = []
for label_type in ['neg', 'pos']:
    dir_name = os.path.join(train_dir, label_type)

    for fname in os.listdir(dir_name):
        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname), encoding = 'utf-8')
            texts.append(f.read())
            f.close()

            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)

texts[0]

# 출력 결과
"Story of a man who has unnatural feelings for a pig. Starts out with a opening scene that is
a terrific example of absurd comedy. A formal orchestra audience is turned into an insane,
violent mob by the crazy chantings of it's singers. Unfortunately it stays absurd the WHOLE
time with no general narrative eventually making it just too off putting. Even those from the
era should be turned off. The cryptic dialogue would make Shakespeare seem easy to a third grader.
On a technical level it's better than you might think with some good cinematography by future
great Vilmos Zsigmond. Future stars Sally Kirkland and Frederic Forrest can be seen briefly."

labels[0]  # 0(부정적인 리뷰)

 

  - 데이터 토큰화

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

max_len = 100
training_samples = 200
validation_samples = 10000
max_words = 10000

tokenizer = Tokenizer(num_words = max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print(len(word_index))  # 88582
data = pad_sequences(sequences, maxlen = max_len)
labels = np.asarray(labels)

print(data.shape)    # (25000, 100)
print(labels.shape)  # (25000,)
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples : training_samples + validation_samples]
y_val = labels[training_samples : training_samples + validation_samples]

print(x_train.shape)  # (200, 100)
print(y_train.shape)  # (200,)
print(x_val.shape)    # (10000, 100)
print(y_val.shape)    # (10000,)

 

  - GloVe 단어 임베딩 내려받기

import wget

wget.download("http://nlp.stanford.edu/data/glove.6B.zip")

# 압축풀기
local_zip = 'glove.6B.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall()
zip_ref.close()

 

  - 임베딩 전처리

  • GloVe 파싱
# 데이터를 라인 단위로 불러오기
glove_dir = "glove.6B"
embeddings_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding = 'utf8')
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype = 'float32')
    embeddings_index[word] = coefs

f.close()

print(len(embeddings_index))  # 400000
embedding_dim = 100
embedding_mat = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
    if i < max_words:
        embedding_vector = embeddings_index.get(word)
        if embedding_vector is not None:
            embedding_mat[i] = embedding_vector

embedding_mat

# 출력 결과
array([[ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [-0.038194  , -0.24487001,  0.72812003, ..., -0.1459    ,
         0.82779998,  0.27061999],
       [-0.071953  ,  0.23127   ,  0.023731  , ..., -0.71894997,
         0.86894   ,  0.19539   ],
       ...,
       [ 0.13787   , -0.17727   , -0.62436002, ...,  0.35506001,
         0.33443999,  0.14436001],
       [-0.88968998,  0.55208999, -0.50498998, ..., -0.54351002,
        -0.21874   ,  0.51186001],
       [-0.17381001, -0.037609  ,  0.068837  , ..., -0.097167  ,
         1.08840001,  0.22676   ]])

 

  - 모델 정의

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Flatten, Dense

model = Sequential()

model.add(Embedding(max_words, embedding_dim, input_length = max_len))
model.add(Flatten())
model.add(Dense(32, activation = 'relu'))
model.add(Dense(1, activation = 'sigmoid'))
model.summary()

# 출력 결과
Model: "sequential_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_7 (Embedding)     (None, 100, 100)          1000000   
                                                                 
 flatten_2 (Flatten)         (None, 10000)             0         
                                                                 
 dense_2 (Dense)             (None, 32)                320032    
                                                                 
 dense_3 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 1,320,065
Trainable params: 1,320,065
Non-trainable params: 0
_________________________________________________________________
# 가중치 설정
model.layers[0].set_weights([embedding_mat])

# 학습하지 않고 기존의 가중치값 그대로 사용
model.layers[0].trainable = False
model.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['accuracy'])

history = model.fit(x_train, y_train,
                    epochs = 10,
                    batch_size = 32,
                    validation_data = (x_val, y_val))

# 모델 저장
model.save_weights('pre_trained_glove_model.h5')

 

  - 시각화

loss = history.history['loss']
val_loss = history.history['val_loss']
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'Training Loss')
plt.plot(epochs, val_loss, 'r:', label = 'Validaiton Loss')
plt.legend()
plt.grid()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'Training Accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'Validaiton Accuracy')
plt.legend()
plt.grid()

 

11. 사전 훈련된 단어 임베딩을 사용하지 않고 같은 모델 훈련

model2 = Sequential()

model2.add(Embedding(max_words, embedding_dim, input_length = max_len))
model2.add(Flatten())
model2.add(Dense(32, activation = 'relu'))
model2.add(Dense(1, activation = 'sigmoid'))
model2.summary()

# 출력 결과
Model: "sequential_6"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_8 (Embedding)     (None, 100, 100)          1000000   
                                                                 
 flatten_3 (Flatten)         (None, 10000)             0         
                                                                 
 dense_4 (Dense)             (None, 32)                320032    
                                                                 
 dense_5 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 1,320,065
Trainable params: 1,320,065
Non-trainable params: 0
_________________________________________________________________
model2.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['accuracy'])
history2 = model2.fit(x_train, y_train,
                    epochs = 10,
                    batch_size = 32,
                    validation_data = (x_val, y_val))

loss = history2.history['loss']
val_loss = history2.history['val_loss']
acc = history2.history['accuracy']
val_acc = history2.history['val_accuracy']

epochs = range(1, len(loss) + 1)

plt.plot(epochs, loss, 'b--', label = 'Training Loss')
plt.plot(epochs, val_loss, 'r:', label = 'Validaiton Loss')
plt.legend()
plt.grid()

plt.figure()
plt.plot(epochs, acc, 'b--', label = 'Training Accuracy')
plt.plot(epochs, val_acc, 'r:', label = 'Validaiton Accuracy')
plt.legend()
plt.grid()

 

  - 테스트 데이터 토큰화

test_dir = os.path.join(imdb_dir, 'test')

labels = []
texts = []
for label_type in ['neg', 'pos']:
    dir_name = os.path.join(test_dir, label_type)

    for fname in os.listdir(dir_name):
        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname), encoding = 'utf8')
            texts.append(f.read())
            f.close()

            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)

sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences, maxlen = max_len)
y_test = np.asarray(labels)

print(x_test.shape)  # (25000, 100)
print(y_test.shape)  # (25000,)
model.load_weights('pre_trained_glove_model.h5')
model.evaluate(x_test, y_test)

# 출력 결과
loss: 0.7546 - accuracy: 0.5566
[0.754594087600708, 0.5565599799156189]

1. 용어 설명

  • 토큰(token)
    • 텍스트를 나누는 단위
    • 토큰화(tokenization): 토큰으로 나누는 작업
  • n-gram
    • 문장에서 추출한 N개(또는 그 이하)의 연속된 단어 그룹
    • 같은 개념이 '문자'에도 적용 가능

https://www.sqlservercentral.com/articles/nasty-fast-n-grams-part-1-character-level-unigrams

 

 

2. 문자 수준 원-핫 인코딩

import numpy as np

samples = ['The cat sat on the mat.',
           'The dog ate my homeworks.']

token_index = {}

for sample in samples:
    for word in sample.split():
        if word not in token_index:
            token_index[word] = len(token_index) + 1

max_len = 10
results = np.zeros(shape = (len(samples), max_len,
                            max(token_index.values()) + 1))

# 원-핫 인코딩
for i, sample in enumerate(samples):
    for j, word in list(enumerate(sample.split()))[:max_len]:
        index = token_index.get(word)
        results[i, j, index] = 1.
results

# 출력 결과
array([[[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],  # The
        [0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],  # cat
        [0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],  # sat
        [0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],  # on
        [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],  # the
        [0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],  # mat
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]],

       [[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],  # The
        [0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],  # dog
        [0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],  # ate
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],  # my
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],  # homeworks
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]])

 

 

3. 케라스를 사용한 단어 수준 원-핫 인코딩

  • fit_on_texts()
  • texts_to_sequences()
  • texts_to_matrix()
from tensorflow.keras.preprocessing.text import Tokenizer

samples = ['The cat sat on the mat.',
           'The dog ate my homeworks.']

tokenizer = Tokenizer(num_words = 1000)
tokenizer.fit_on_texts(samples)

sequences = tokenizer.texts_to_sequences(samples)

ohe_results = tokenizer.texts_to_matrix(samples, mode = 'binary')

word_index = tokenizer.word_index
print(len(word_index))

# 출력 결과
9
# 9개의 토큰을 가지고 있음
# 단어의 순서
sequences

# 출력 결과
[[1, 2, 3, 4, 1, 5], [1, 6, 7, 8, 9]]
# 원-핫 인코딩 결과
print(ohe_results.shape)
print(ohe_results)

# 출력 결과
(2, 1000)
[[0. 1. 1. ... 0. 0. 0.]
 [0. 1. 0. ... 0. 0. 0.]]
word_index

# 출력 결과
{'the': 1,
 'cat': 2,
 'sat': 3,
 'on': 4,
 'mat': 5,
 'dog': 6,
 'ate': 7,
 'my': 8,
 'homeworks': 9}
 
 # 단어 인덱스에 따라 sequences의 값이 정해짐

 

  - 토큰화 예제

  • OOV: Out Of Vocabulary
    • 새로운 문장에서 기존에 토큰화한 문장에 존재하지 않으면 OOV로 대체됨
from tensorflow.keras.preprocessing.text import Tokenizer

samples = ["I'm the smartest student.",
           "I'm the best student."]
tokenizer = Tokenizer(num_words = 10, oov_token = '<OOV>')
tokenizer.fit_on_texts(samples)

sequences = tokenizer.texts_to_sequences(samples)

binary_results = tokenizer.texts_to_matrix(samples, mode = 'binary')

print(tokenizer.word_index)

# 출력 결과
# 현재 tokenizer에 대한 word_index
{'<OOV>': 1, "i'm": 2, 'the': 3, 'student': 4, 'smartest': 5, 'best': 6}
binary_results

# 출력 결과
array([[0., 0., 1., 1., 1., 1., 0., 0., 0., 0.],
       [0., 0., 1., 1., 1., 0., 1., 0., 0., 0.]])
  • 테스트
test = ["I'm the fastest student."]
test_seq = tokenizer.texts_to_sequences(test)

print("word index:", tokenizer.word_index)
print("Test Text:", test)
print("Test Seq:", test_seq)

# 출력 결과
word index: {'<OOV>': 1, "i'm": 2, 'the': 3, 'student': 4, 'smartest': 5, 'best': 6}
Test Text: ["I'm the fastest student."]
Test Seq: [[2, 3, 1, 4]]

# fastest는 vocabulary에 없는 oov(out-of-vocabulary) 값이므로 1로 표시됨

 

 

4. 원-핫 단어 벡터와 단어 임베딩

  • 원-핫 단어 벡터
    • 데이터가 희소(sparse)
    • 고차원
  • 단어 임베딩
    • 밀집(dense)
    • 저차원

https://freecontent.manning.com/deep-learning-for-text/

 

 

5. 단어 임베딩

  • 단어 간 벡터 사이의 거리가 가까운, 즉 비슷한 단어들끼리 임베딩
  • 거리 외에 임베딩 공간의 특정 방향도 의미를 가질 수 있음

https://towardsdatascience.com/creating-word-embeddings-coding-the-word2vec-algorithm-in-python-using-deep-learning-b337d0ba17a8

 

  - Embedding Layer

  • 특정 단어를 나타내는 정수 인덱스를 밀집 벡터(dense vector)로 매핑하는 딕셔너리 레이어
  • 입력: (samples, sqquence_length)
  • 출력: (samples, sequences_length, dim)
from tensorflow.keras.layers import Embedding

embedding_layer = Embedding(1000, 64)
embedding_layer

# 출력 결과
<keras.layers.core.embedding.Embedding at 0x265f5b12fa0>
# embedding 객체가 출력됨

 

 

6. 예제: IMDB 데이터

  • 인터넷 영화 데이터베이스(Internet Movie Database)
  • 양극단의 리뷰 5만개로 이루어진 데이터셋
    • 훈련 데이터: 25,000개
    • 테스트 데이터: 25,000개

 

  - modules import

from tensorflow.keras.datasets import imdb
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, Flatten

 

  - 데이터 로드

num_words = 1000
max_len = 20

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words = num_words)

print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

# 출력 결과
(25000,)
(25000,)
(25000,)
(25000,)

 

  - 데이터 확인

  • 긍정: 1
  • 부정: 0
print(x_train[0])
print(y_train[0])

# 출력 결과
# 리뷰 데이터의 sequence와 긍정/부정 결과 출력
[1, 14, 22, 16, 43, 530, 973, 2, 2, 65, 458, 2, 66, 2, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 2, 2, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 2, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2, 2, 16, 480, 66, 2, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 2, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2, 56, 26, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 2, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178, 32]
1

 

  - 참고) IMDB 데이터셋에서 가장 많이 사용된 단어

word_index = {}

for key, val in imdb.get_word_index().items():
    word_index[val] = key

for i in range(1, 6):
    print(word_index[i])

# 출력 결과
the
and
a
of
to

 

  - 데이터 전처리

  • 모든 데이터를 같은 길이로 맞추기
    • pad_sequence()
      • 데이터가 maxlen보다 길면 데이터를 자름
      • 데이터가 길면 padding 설정
        • pre: 데이터 앞에 0으로 채움
        • post: 데이터 뒤에 0으로 채움
  • 모든 데이터(문장 하나하나)가 같은 길이로 맞춰져야 Embedding 레이어 사용가능
from tensorflow.keras.preprocessing.sequence import pad_sequences

pad_x_train = pad_sequences(x_train, maxlen = max_len, padding = 'pre')
pad_x_test = pad_sequences(x_test, maxlen = max_len, padding = 'pre')

print(len(x_train[0]))
print(len(pad_x_train[0]))

# 출력 결과
218
20
# 최대 길이만큼 줄어듬
print(x_train[0])
print(pad_x_train[0])

# 출력 결과
[1, 14, 22, 16, 43, 530, 973, 2, 2, 65, 458, 2, 66, 2, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 2, 2, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 2, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2, 2, 16, 480, 66, 2, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 2, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2, 56, 26, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 2, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178, 32]
[ 65  16  38   2  88  12  16 283   5  16   2 113 103  32  15  16   2  19  178  32]

 

  - 모델 구성

model = Sequential()

model.add(Embedding(input_dim = num_words, output_dim = 32, input_length = max_len))
model.add(Flatten())
model.add(Dense(1, activation = 'sigmoid'))

model.summary()

# 출력 결과
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_1 (Embedding)     (None, 20, 32)            32000     
                                                                 
 flatten (Flatten)           (None, 640)               0         
                                                                 
 dense (Dense)               (None, 1)                 641       
                                                                 
=================================================================
Total params: 32,641
Trainable params: 32,641
Non-trainable params: 0
_________________________________________________________________

 

  - 모델 컴파일 및 학습

model.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['accuracy'])

history = model.fit(pad_x_train, y_train,
                    epochs = 10,
                    batch_size = 32,
                    validation_split = 0.2)

 

  - 시각화

import matplotlib.pyplot as plt

hist_dict = history.history

plt.plot(hist_dict['loss'], 'b--', label = 'Train Loss')
plt.plot(hist_dict['val_loss'], 'r:', label = 'Validation Loss')
plt.legend()
plt.grid()

plt.figure()
plt.plot(hist_dict['accuracy'], 'b--', label = 'Train Accuracy')
plt.plot(hist_dict['val_accuracy'], 'r:', label = 'Validation Accuracy')
plt.legend()
plt.grid()

plt.show()

 

  - 모델 평가

model.evaluate(pad_x_test, y_test)

# 출력 결과
loss: 0.5986 - accuracy: 0.7085
[0.5986294150352478, 0.7085199952125549]

 

  - 단어의 수를 늘린 후 재학습

num_words = 1000
max_len = 500

pad_x_train_2 = pad_sequences(x_train, maxlen = max_len, padding = 'pre')
pad_x_test_2 = pad_sequences(x_test, maxlen = max_len, padding = 'pre')

print(x_train[0])
print(pad_x_train_2[0])

# 출력 결과
[1, 14, 22, 16, 43, 530, 973, 2, 2, 65, 458, 2, 66, 2, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 2, 2, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 2, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2, 2, 16, 480, 66, 2, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 2, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2, 56, 26, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 2, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178, 32]
[  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   1  14  22  16  43 530
 973   2   2  65 458   2  66   2   4 173  36 256   5  25 100  43 838 112
  50 670   2   9  35 480 284   5 150   4 172 112 167   2 336 385  39   4
 172   2   2  17 546  38  13 447   4 192  50  16   6 147   2  19  14  22
   4   2   2 469   4  22  71  87  12  16  43 530  38  76  15  13   2   4
  22  17 515  17  12  16 626  18   2   5  62 386  12   8 316   8 106   5
   4   2   2  16 480  66   2  33   4 130  12  16  38 619   5  25 124  51
  36 135  48  25   2  33   6  22  12 215  28  77  52   5  14 407  16  82
   2   8   4 107 117   2  15 256   4   2   7   2   5 723  36  71  43 530
 476  26 400 317  46   7   4   2   2  13 104  88   4 381  15 297  98  32
   2  56  26 141   6 194   2  18   4 226  22  21 134 476  26 480   5 144
  30   2  18  51  36  28 224  92  25 104   4 226  65  16  38   2  88  12
  16 283   5  16   2 113 103  32  15  16   2  19 178  32]

# 500이라는 최대 길이 맞추고 남은 공간을 0으로 채움, pre이므로 앞쪽에 채움
model = Sequential()

model.add(Embedding(input_dim = num_words, output_dim = 32, input_length = max_len))
model.add(Flatten())
model.add(Dense(1, activation = 'sigmoid'))

model.compile(optimizer = 'rmsprop',
              loss = 'binary_crossentropy',
              metrics = ['accuracy'])

history2 = model.fit(pad_x_train_2, y_train,
                    epochs = 10,
                    batch_size = 32,
                    validation_split = 0.2)

hist_dict_2 = history2.history

plt.plot(hist_dict_2['loss'], 'b--', label = 'Train Loss')
plt.plot(hist_dict_2['val_loss'], 'r:', label = 'Validation Loss')
plt.legend()
plt.grid()

plt.figure()
plt.plot(hist_dict_2['accuracy'], 'b--', label = 'Train Accuracy')
plt.plot(hist_dict_2['val_accuracy'], 'r:', label = 'Validation Accuracy')
plt.legend()
plt.grid()

plt.show()

model.evaluate(pad_x_test_2, y_test)

# 출력 결과
loss: 0.5295 - accuracy: 0.8316
[0.5295160412788391, 0.8316400051116943]

  - 위의 결과도 정확도로 봤을때는 나쁘지 않지만 과적합이 됨

  - 그 이유는

  • 단어 간 관계나 문장 구조 등 의미적 연결 고려 x
  • 시퀀스 전체를 고려한 특성을 학습하는 것은 Embedding 층 위에 RNN층이나 1D 합성곱을 추가하는 것이 좋음

 

 

● 단어 임베딩의 종류

  • LSA
  • Word2Vec
  • Blove
  • FastText
  • etc...

 

 

7. Word2Vec

  • 분류 등과 같이 별도의 레이블 없이 텍스트 자체만 있어도 학습이 가능
  • Word2Vec의 방식(주변 단어의 관계를 이용)
    • CBOW(Continuous Bag-Of-Word)
      • 주변 단어의 임베딩을 더해서 대상 단어를 예측
    • Skip-Gram
      • 대상 단어의 임베딩으로 주변 단어를 예측
      • 일반적으로 CBOW보다 성능이 좋은 편
      • 한번에 여러 단어를 예측해야하기 때문에 비효율적
      • 최근에는 negative sampling이라는 방법 사용

https://www.researchgate.net/figure/CBOW-and-Skip-Gram-neural-architectures_fig14_328160770

 

 

8. 구텐베르크 프로젝트 예제

import requests
import re

 

  - 데이터 다운로드

res = requests.get('https://www.gutenberg.org/files/2591/2591-0.txt')
res

# 출력 결과
<Response [200]>
# 200이면 잘 응답한 것
# 404면 오류 발생한 것

 

  - 데이터 전처리

grimm = res.text[2801:530661]
grimm = re.sub(r'[^a-zA-Z\. ]', ' ', grimm)
sentences = grimm.split('. ')
data = [s.split() for s in sentences]

len(data)  # 3468


data[0]

# 출력 결과
['SECOND',
 'STORY',
 'THE',
 'SALAD',
 'THE',
 'STORY',
 'OF',
 'THE',
 'YOUTH',
 'WHO',
 'WENT',
 'FORTH',
 'TO',
 'LEARN',
 'WHAT',
 'FEAR',
 'WAS',
 'KING',
 'GRISLY',
 'BEARD',
 'IRON',
 'HANS',
 'CAT',
 'SKIN',
 'SNOW',
...
 'tree',
 'which',
 'bore',
 'golden',
 'apples']
# gensim 패키지로부터 Word2Vec을 불러오기
from gensim.models.word2vec import Word2Vec
# sg인자에 0을 넘겨주면 CBOW, 1을 넘겨주면 Skip-gram
# 최소 3번은 등장한 단어, 동시 처리의 수는 4개
model = Word2Vec(data, sg = 1, vector_size = 100, window = 3, min_count = 3, workers = 4)

 

  - 모델 저장 및 로드

# 저장
model.save('word2vec.model')

# 로드
pretrained_model = Word2Vec.load('word2vec.model')

 

  - 단어를 벡터로 변환

  • wv
pretrained_model.wv['princess']

# 출력 결과
array([-0.19268924,  0.17087255, -0.13460916,  0.20450976,  0.03542079,
       -0.31665406,  0.13296   ,  0.54076153, -0.18337499, -0.21417093,
        0.02725333, -0.31845513,  0.01819889,  0.10720193,  0.16601542,
       -0.19728081,  0.05753807, -0.12273175, -0.17903367, -0.22576232,
        0.2438455 ,  0.13664703,  0.18498562, -0.1679803 ,  0.07735273,
       -0.00432668, -0.00775897, -0.08363435, -0.12566872, -0.07055762,
        0.02887373, -0.08917326,  0.17351009, -0.18784055, -0.20769958,
        0.19657052,  0.01372425, -0.074237  , -0.10052767, -0.11275681,
        0.06725535, -0.09701315,  0.02844668,  0.05958825, -0.02586031,
       -0.01711333, -0.11226629, -0.08671231,  0.1945969 ,  0.01690222,
        0.07196116, -0.08172472, -0.05373074, -0.14637838,  0.16281295,
        0.06222549,  0.10643765,  0.07477342, -0.16238536,  0.03527208,
       -0.04292673,  0.04597842,  0.13826323, -0.19217554, -0.25257504,
        0.10983958,  0.03293723,  0.4319519 , -0.21335553,  0.24770555,
       -0.00888118,  0.02231867,  0.17330043, -0.10485211,  0.35415375,
       -0.08000654,  0.01478033, -0.03938808, -0.06453493,  0.02249427,
       -0.21435274, -0.01287377, -0.2137464 ,  0.21174915, -0.1006554 ,
        0.00902446,  0.05607878,  0.16368881,  0.13859129, -0.01395336,
        0.09382439,  0.08065708, -0.056269  ,  0.09765122,  0.188912  ,
        0.1668056 , -0.01361183, -0.14287405, -0.11452819, -0.20357099],
      dtype=float32)

# 'princess'라는 단어를 벡터로 변환한 값

 

  - 유추 또는 유비(analogy)

  • wv.similarity()에 두 단어를 넣어주면 코사인 유사도를 구할 수 있음
pretrained_model.wv.similarity('king', 'prince')

# 출력 결과
0.8212076
  • wv.most_similar()에 단어를 넘겨주면 가장 유사한 단어를 추출할 수 있음
pretrained_model.wv.most_similar('king')

# 출력 결과
[('daughter', 0.9241937398910522),
 ('son', 0.9213796257972717),
 ('woman', 0.9177201390266418),
 ('man', 0.897368848323822),
 ('queen', 0.8747967481613159),
 ('miller', 0.8610494136810303),
 ('old', 0.8595746755599976),
 ('young', 0.8504902124404907),
 ('wolf', 0.8450464010238647),
 ('But', 0.8406485319137573)]
  • wv.most_similar()에 positive와 negetive라는 옵션을 넘길 수 있음
# 'man + princess - woman'을 벡터 계산을 한 값을 출력
# man이고 princess인데 woman이 아닌 단어
pretrained_model.wv.most_similar(positive = ['man', 'princess'], negative = ['woman'])

# 출력 결과
[('bird', 0.9595717787742615),
 ('prince', 0.9491060376167297),
 ('cook', 0.9410891532897949),
 ('bride', 0.9401964545249939),
 ('huntsman', 0.9375050067901611),
 ('mouse', 0.9356588125228882),
 ('cat', 0.9344455003738403),
 ('giant', 0.9341970682144165),
 ('gardener', 0.9327394366264343),
 ('maid', 0.9326624870300293)]

 

  - gensim으로 학습된 단어 임베딩을 Keras에서 불러오기 

from keras.models import Sequential
from keras.layers import Embedding

num_words, emb_dim = pretrained_model.wv.vectors.shape

print(num_words)
print(emb_dim)

# 출력 결과
2446
100

 

  - gensim으로 학습된 단어 임베딩을 Keras의 임베딩 레이어의 가중치로 설정

emb = Embedding(input_dim = num_words, output_dim = emb_dim,
                trainable = False, weights = [pretrained_model.wv.vectors])

model = Sequential()
model.add(emb)

model.summary()

# 출력 결과
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding_3 (Embedding)     (None, None, 100)         244600    
                                                                 
=================================================================
Total params: 244,600
Trainable params: 0
Non-trainable params: 244,600
_________________________________________________________________
# princess에 대한 결과 벡터
i = pretrained_model.wv.index_to_key.index('princess')

model.predict([i])

# 출력 결과
array([[-0.19268924,  0.17087255, -0.13460916,  0.20450976,  0.03542079,
        -0.31665406,  0.13296   ,  0.54076153, -0.18337499, -0.21417093,
         0.02725333, -0.31845513,  0.01819889,  0.10720193,  0.16601542,
        -0.19728081,  0.05753807, -0.12273175, -0.17903367, -0.22576232,
         0.2438455 ,  0.13664703,  0.18498562, -0.1679803 ,  0.07735273,
        -0.00432668, -0.00775897, -0.08363435, -0.12566872, -0.07055762,
         0.02887373, -0.08917326,  0.17351009, -0.18784055, -0.20769958,
         0.19657052,  0.01372425, -0.074237  , -0.10052767, -0.11275681,
         0.06725535, -0.09701315,  0.02844668,  0.05958825, -0.02586031,
        -0.01711333, -0.11226629, -0.08671231,  0.1945969 ,  0.01690222,
         0.07196116, -0.08172472, -0.05373074, -0.14637838,  0.16281295,
         0.06222549,  0.10643765,  0.07477342, -0.16238536,  0.03527208,
        -0.04292673,  0.04597842,  0.13826323, -0.19217554, -0.25257504,
         0.10983958,  0.03293723,  0.4319519 , -0.21335553,  0.24770555,
        -0.00888118,  0.02231867,  0.17330043, -0.10485211,  0.35415375,
        -0.08000654,  0.01478033, -0.03938808, -0.06453493,  0.02249427,
        -0.21435274, -0.01287377, -0.2137464 ,  0.21174915, -0.1006554 ,
         0.00902446,  0.05607878,  0.16368881,  0.13859129, -0.01395336,
         0.09382439,  0.08065708, -0.056269  ,  0.09765122,  0.188912  ,
         0.1668056 , -0.01361183, -0.14287405, -0.11452819, -0.20357099]],
      dtype=float32)

+ Recent posts