no image
[전처리] 서울시 구별 CCTV와 인구 관계 분석
1. 서울시 자치구별 CCTV 현황 데이터 가져오기 - http://data.seoul.go.kr/ 열린데이터광장 메인 데이터분류,데이터검색,데이터활용 data.seoul.go.kr - CCTV 검색 - xlsx 파일을 다운로드(파일 이름을 cctv로 수정) # 데이터 읽기 import matplotlib.pyplot as plt import pandas as pd import numpy as np from pandas import Series, DataFrame import platform from matplotlib import font_manager, rc #데이터 읽어오기 cctv = pd.read_excel('./data/cctv.xlsx') print(cctv.head()) cctv.info(..
2024.03.20
no image
[Python] 딥러닝 _ Keras
1. Keras 모든 종류의 신경망을 손쉽게 만들고 훈련, 평가, 실행할 수 있는 고수준 딥러닝 API API 문서는 https://keras.io 거의 모든 딥러닝 라이브러리에서 사용 가능 2. Keras 의 Dense - 완전 연결 층을 만들기 위한 클래스 완전 연결 층 : 이전 층의 모든 연산을 받아들이는 층 import tensorflow as tf from tensorflow import keras 2-1) 생성할 때 파라미터 unit: 뉴런의 개수 activation: 활성화 함수로 기본값은 None 이고 sigmoid, softmax(다중 분류 문제에 사용), tanh(하이퍼볼릭 탄젠트 함수), relu 등을 설정할 수 있다. input_shape 는 입력 층(첫번째 층)의 경우 입력되는 데..
2024.03.20
no image
[딥러닝] Keras _ 패션 이미지 분류
Keras 의 내장 데이터 세트 - 종류 boston housing: 보스톤 집값으로 회귀에 사용 cifar10: 이미지 분류 데이터로 종류가 10가지 cifar100: 이미지 분류 데이터로 종류가 100가지 mnist: 손 글씨 데이터 fashion mnist: 의류 데이터로 클래스가 10가지 imdb: 영화 데이터로 분류에 활용 reuters: 뉴스 토픽 - 자연어 분류 - load_data 라는 함수를 호출하면 훈련 데이터 튜플 과 테스트 데이터 튜플로 데이터를 리턴 - 각각의 튜플은 피처 와 레이블로 나뉘어져 있음 1. 데이터 가져오기 fashion_mnist = keras.datasets.fashion_mnist (X_train_full, y_train_full), (X_test, y_test)..
2024.03.20
no image
[딥러닝] Keras 이항분류 _ 레드와 화이트와인 분류
데이터 url red: http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv white: http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality- white.csv - 구분자: ; - 피처 fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality 1. 데이터 가져오기 import pandas as pd red..
2024.03.20
no image
[Python] 딥러닝 _ Tensorflow
1. 딥러닝 여러 비선형 변환 기법의 조합을 통해 높은 수준의 추상화를 시도하는 머신러닝 알고리즘의 집합 연속된 층(Layer)에서 점진적으로 의미있는 표현을 배우는 방식 기존의 머신러닝 방법은 1~2가지의 데이터 표현을 학습하는 얕은 학습을 수행하지만 딥러닝은 수백개 이상의 층 이용 데이터로부터 표현을 학습하는 수학 모델 층을 통과할 때마다 새로운 데이터 표현을 만들어가면서 학습 1-1) 작동 원리 층에서 입력 데이터가 처리되는 내용은 일련의 숫자로 이루어진 층의 가중치에 저장이 되는데 이는 그 층의 가중치를 parameter로 갖는 함수로 표현 이 가중치를 알아내려면 데이터를 관찰해야 하고 신경망의 출력이 기대하는 것보다 얼마나 벗어났는지 측정 딥러닝은 기본적으로 지도학습 지도학습은 오차를 줄여나가는..
2024.03.20
no image
[Python] 선형회귀 실습 _ 보스톤 주택 가격에 대한 선형 회귀
- 데이터: http://lib.stat.cmu.edu/datasets/boston - 데이터에 대한 설명 CRIM per capita crime rate by town ZN proportion of residential land zoned for lots over 25,000 sq.ft. INDUS proportion of non-retail business acres per town CHAS Charles River dummy variable (= 1 if tract bounds river; 0 otherwise) NOX nitric oxides concentration (parts per 10 million) RM average number of rooms per dwelling AGE propor..
2024.03.18
no image
[Python] 연관 분석
1. 문장의 유사도 측정 1-1) 코사인 유사도 문장을 벡터로 만들어서 거리 측정 벡터의 크기보다는 벡터의 방향성이 얼마나 유사한지에 기반 두 벡터 사이의 사잇각을 구해서 얼마나 유사한지 수치로 적용 유사한 벡터들은 방향이 같고 관련성이 없는 벡터들은 방향이 일치하지도 않고 반대 방향도 아닌 경우 1-2) 코사인 유사도 API sklearn.feature_extration.text 패키지를 이용해서 전처리 sklearn.metrics.pairwise 패키지의 cosin_similarity 함수를 이용해서 측정 def cos_similarity(v1, v2): dot_product = np.dot(v1, v2) l2_norm = (np.sqrt(sum(np.square(v1))) * np.sqrt(sum(..
2024.03.18
no image
[Python] 연관분석 실습 _ 아이템 기반 추천 시스템
- movies.csv: 영화 정보 데이터 - ratings.csv: 평점정보 데이터 1. 데이터 읽어오기 movies = pd.read_csv('./python_machine_learning-main/data/movielens/movies.csv') ratings = pd.read_csv('./python_machine_learning-main/data/movielens/ratings.csv') print(movies.shape) print(ratings.shape) (9742, 3) (100836, 4) # ratings에서 timestamp 제거 ratings = ratings[['userId', 'movieId', 'rating']] ratings_matrix.head(3) - 추천 시스템 데이터..
2024.03.18

1. 서울시 자치구별 CCTV 현황 데이터 가져오기

- http://data.seoul.go.kr/

 

열린데이터광장 메인

데이터분류,데이터검색,데이터활용

data.seoul.go.kr

- CCTV 검색

- xlsx 파일을 다운로드(파일 이름을 cctv로 수정)

 

 

# 데이터 읽기

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from pandas import Series, DataFrame
import platform
from matplotlib import font_manager, rc

#데이터 읽어오기
cctv = pd.read_excel('./data/cctv.xlsx')
print(cctv.head())
cctv.info()


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 25 entries, 0 to 24
Data columns (total 10 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   기관명       25 non-null     object 
 1   소계        25 non-null     int64  
 2   2011년 이전  23 non-null     float64
 3   2012년     23 non-null     float64
 4   2013년     23 non-null     float64
 5   2014년     25 non-null     int64  
 6   2015년     25 non-null     int64  
 7   2016년     25 non-null     int64  
 8   2017년     25 non-null     int64  
 9   2018년     25 non-null     int64  
dtypes: float64(3), int64(6), object(1)
memory usage: 2.1+ KB

 

 

 

 

 

2. 서울시 자치구별 인구 현황 데이터 가져오기

- http://data.seoul.go.kr/

- 자치구별 인구 검색

- pop.txt

 

 

# 데이터 읽기

pop = pd.read_csv('./data/pop.txt',  encoding='utf-8', skiprows=2, delimiter='\t', thousands=',')
print(pop.head())
print()
pop.info()



<class 'pandas.core.frame.DataFrame'>
RangeIndex: 26 entries, 0 to 25
Data columns (total 14 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   기간        26 non-null     object 
 1   자치구       26 non-null     object 
 2   세대        26 non-null     int64  
 3   계         26 non-null     int64  
 4   남자        26 non-null     int64  
 5   여자        26 non-null     int64  
 6   계.1       26 non-null     int64  
 7   남자.1      26 non-null     int64  
 8   여자.1      26 non-null     int64  
 9   계.2       26 non-null     int64  
 10  남자.2      26 non-null     int64  
 11  여자.2      26 non-null     int64  
 12  세대당인구     26 non-null     float64
 13  65세이상고령자  26 non-null     int64  
dtypes: float64(1), int64(11), object(2)
memory usage: 3.0+ KB

 

 

 

 

 

3. 데이터 전처리

# 컬럼 이름 변경

cctv.rename(columns={cctv.columns[0] : '구별'}, inplace=True)
print(cctv.head())
print()

gu = []
for x in cctv['구별']:
    gu.append(x.replace(' ', ''))
cctv['구별'] = gu

pop.rename(columns={pop.columns[1] : '구별'}, inplace=True)
print(pop.head())




 

 

# 필터링

#pop에서 컬럼 추출
pop = pop[['기간', '구별', '계', '남자', '여자']]

#pop의 첫번째 행은 합계
#첫번째 행 제거
pop.drop([0], inplace=True)

#여성인구 비율을 알아보기 위해서 새로운 열 생성
pop['여성비율'] = pop['여자']/pop['계']*100
pop

 

 

# 병합

#구별 컬럼을 이용해서 2개의 frame을 합치기
df = pd.merge(cctv, pop, on='구별')

 

 

# 불필요한 컬럼 제거

del df['2011년 이전']
del df['2012년']
del df['2013년']
del df['2014년']
del df['2015년']
del df['2016년']
del df['2017년']
del df['기간']

 

 

# 인덱스 재설정

df.set_index('구별', inplace=True)
df

 

 

 

 

 

 

4. 시각화

font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
df['소계'].plot(kind='barh', grid=True, figsize=(10,10))
plt.show()
df['소계'].sort_values().plot(kind='barh', grid=True, figsize=(5,5))
plt.show()
df['cctv비율'] = df['소계']/df['계'] * 100
df['cctv비율'].sort_values().plot(kind='barh', grid=True, figsize=(5,5))
plt.show()

 

 

# 시각화 - 산포도

plt.figure(figsize=(6,6))
plt.scatter(df['계'], df['소계'], s=50)
plt.xlabel('인구수')
plt.ylabel('CCTV개수')
plt.grid()
plt.show()

 

 

# 시각화 - 기울기와 y절편 구해서 라인 그리기

fp1 = np.polyfit(df['계'], df['소계'], 1)
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)

plt.figure(figsize=(5,5))
plt.scatter(df['계'], df['소계'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g')
plt.xlabel('인구수')
plt.ylabel('CCTV')
plt.grid()
plt.show()

 

 

#오차 표시

fp1 = np.polyfit(df['계'], df['소계'], 1)
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)
df['오차'] = np.abs(df['소계'] - f1(df['계']))
plt.figure(figsize=(14,10))
plt.scatter(df['계'], df['소계'], c=df['오차'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g')

for n in range(24):
    plt.text(df['계'][n]*1.02, df['소계'][n]*0.98,
             df.index[n], fontsize=12)

plt.xlabel('인구수')
plt.ylabel('인구당비율')
plt.colorbar()
plt.grid()
plt.show()

[Python] 딥러닝 _ Keras

0ㅑ채
|2024. 3. 20. 12:04

1. Keras

  • 모든 종류의 신경망을 손쉽게 만들고 훈련, 평가, 실행할 수 있는 고수준 딥러닝 API
  • API 문서는 https://keras.io
  • 거의 모든 딥러닝 라이브러리에서 사용 가능

 

 

 

2. Keras 의 Dense

- 완전 연결 층을 만들기 위한 클래스

  • 완전 연결 층 : 이전 층의 모든 연산을 받아들이는 층
import tensorflow as tf
from tensorflow import keras

 

 

2-1) 생성할 때 파라미터

  • unit: 뉴런의 개수
  • activation: 활성화 함수로 기본값은 None 이고 sigmoid, softmax(다중 분류 문제에 사용), tanh(하이퍼볼릭 탄젠트 함수), relu 등을 설정할 수 있다.
  • input_shape 는 입력 층(첫번째 층)의 경우 입력되는 데이터의 크기를 지정해야 하는 매개변수

 

- 샘플 데이터 생성

X = np.arange(1, 6)
y = 3 * X + 2
print(X)
print(y)
[1 2 3 4 5]
[ 5  8 11 14 17]

 

 

 

2-2) Tensorflow의 Keras 모델 생성 방법

- Sequential API

- Functional API

- SubClassing

 

 

2-3) Sequential API 활용

  • 층을 이어 붙이듯 시퀀스에 맞게 일렬로 연결하는 방식
  • 입력 레이어부터 출력 레이어까지 순서를 갖는 형태
  • 입력 레이어가 첫번째 레이어가 되는데 입력 데이터가 이 레이어 투입되고 순서대로 각 층을 하나씩 통과하면서 딥러닝 연산을 수행
  • 이해하기가 가장 쉬운 방법이지만 2개 이상의 다중 입력이나 다중 출력을 갖는 복잡한 구조를 만들 지 못함

 

- 모델 구조

  •  list 이용
    model = tf.keras.Sequential([
                  tf.keras.layers.Dense(10),
                  tf.keras.layers.Dense(5),
                  tf.keras.layers.Dense(1)
    ])
  • add 함수 이용
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Dense(10))
    model.add(tf.keras.layers.Dense(5))
    model.add(tf.keras.layers.Dense(1))

 

- 입력 데이터 형태 지정

  • 첫번째 층의 input_shape 에 튜플이나 리스트 형태로 설정
    • shape 가 150, 4 인 경우 (150, 4), [150, 4] 도 가능하고 (4, ), [4] 도 가능
    • 첫번째 숫자가 데이터의 개수이고 나머지 부분이 데이터의 shape 가 되므로 데이터의 개수를 생략하더라도 shape 가 설정되면 데이터의 개수는 유추할 수 있기 때문

 

- 단순 선형 회귀 모델 생성

  • Layer가 1개면 가능
  • 입력 데이터는 피처가 1개
  • 출력도 하나의 숫자

    첫번째 층은 입력 구조가 필수
    마지막 층의 뉴런의 개수는 출력하는 데이터의 피처 개수
model = tf.keras.Sequential([
    tf.keras.layers.Dense(1, input_shape=(1,))
])

 

- Sequential 모델은 모델의 구조를 확인하는 것이 가능 - summary()

#모델의 구조 확인
model.summary()
  • 각 층에 대한 정보 리턴

 

- 컴파일

  • 컴파일 과정에서는 Optimizer, Loss Function(손실 함수로 일반적으로 평가 지표를 작성), Metrics(평가 지표를 작성하는데 여기는 list가 가능) 를 지정
  • 작성을 할 때는 긴문자열, 짧은 문자열, 클래스 인스턴스 3가지 방법이 가능한데 문자열로 설정하는 경우는 세부적인 파라미터 조정이 안되고 클래스 인스턴스를 이용하면 세부 파라미터 조정이 가능

# 모델 컴파일

model.compile(optimizer='sgd', loss='mean_squared_error',
             metrics=['mean_squared_error', 'mean_absolute_error']) #긴 문자열 사용

model.compile(optimizer='sgd', loss='mse',
             metrics=['mse', 'mae']) #짧은 문자열 사용

model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.005),
             loss = tf.keras.losses.MeanSquaredError(),
             metrics = [tf.keras.metrics.MeanSquaredError(), 
                        tf.keras.metrics.MeanAbsoluteError])

 

# 훈련

  • 훈련을 하면 가중치를 업데이트
  • 훈련 데이터 세트의 입력(X) 과 정답에 해당하는 출력(y) 그리고 반복 훈련 횟수에 해당하는 epoch 를 설정하는데 기본적으로 훈련을 할 때 마다 손실 과 평가 지표를 출력하는데 verbose=0 을 추가하면 중간 과정이 생략 
  • validation_data 에 검증 데이터를 설정하면 검증 데이터에 대한 손실 과 평가 지표도 같이 반환
  • fit 함수가 리턴하는 객체는 epoch 별 손실 과 평가지표를 dict로 저장하고 있다.
history = model.fit(X, y, epochs=1000, verbose=0)

#훈련 과정에서 발생한 손실 과 평가지표를 시각화
plt.plot(history.history['loss'], label='loss')
plt.legend()
plt.show()

 

- 검증 및 예측

  • 검증은 evaluate 메서드에 훈련 데이터를 입력하면 됩니다.
model.evaluate(X, y)

 

- 예측

  • predict(ndarray 나 tensor로 대입
model.predict(np.array([10]))

 

 

 

2-4) Classification 분류 

  • 데이터가 어느 범주(Category)에 해당하는지 판단하는 문제
  • 회귀가 알고리즘의 퍼포먼스를 확인하기 위해서는 잔차 제곱의 합(SSE) 나 잔차 제곱의 평균(MSE) 등을 사용하고 이 값들은 일반적으로 실수
  • 분류에서는 같은 목적으로 예측이 정답을 얼마나 맞혔는지에 대한 정확도를 측정
    • 정확도는 보통 퍼센트로 나타내고 이 수치는 직관적이기 때문에 머신러닝 알고리즘의 벤치마크 역할을 분류를 가지고 판단
  • ImageNet 이라는 데이터베이스를 이용해서 이미지의 범주를 분류하는 대회가 있는데 2012년 CNN이 등장하면서 2017 년에 거의 100%를 달성
  • 이항 분류
    • 정답의 범주가 두 개인 분류 문제

 

# 레드와 화이트와인 분류: 이항 분류

2024.03.20 - [Python/Python 실전편] - [딥러닝] Keras 이항분류 _ 레드와 화이트와인 분류

 

 

 

# 패션 이미지 분류

2024.03.20 - [Python/Python 실전편] - [딥러닝] Keras _ 패션 이미지 분류

 

[딥러닝] Keras _ 패션 이미지 분류

Keras 의 내장 데이터 세트 - 종류 boston housing: 보스톤 집값으로 회귀에 사용 cifar10: 이미지 분류 데이터로 종류가 10가지 cifar100: 이미지 분류 데이터로 종류가 100가지 mnist: 손 글씨 데이터 fashion mni

yachae4910.tistory.com

 

 

 

2-5) subclassing

  • Sequential API 나 Functional API는 선언적 방식인데 사용할 층 과 연결 방식을 정의한 후 모델에 데이터를 주입해서 훈련이나 추론을 하는 방식
  • 선언적 방식은 장점이 많은데 모델을 저장하거나 복사 또는 공유하기 쉬우며 모델의 구조를 출력하거나 분석하기도 좋고 프레임워크가 크기를 짐작하고 타입을 확인해서 에러를 일직 발견할 수 있고 디버깅하기도 쉬움
    • 정적이라는 단점이 있음
    • 수정하지 못함
  • subclassing 은 기존의 클래스를 상속받아서 수정해서 사용하는 것
  • Model 클래스를 상속받고 __init__ 메서드에서 필요한 층을 만들고 call 메서드 안에 수행하려는 연산을 기술하고 출력 층을 리턴하도록 작성
  • subclassing 을 하고자 하면 원 클래스에 메서드들의 기능을 확인할 필요가 있습니다.
    • 필요한 메서드를 오버라이딩 해서 사용

 

- 샘플

class WideAndDeepModel(keras.models.Model):
	def __init__(self, 필요한 매개변수, **kwargs):
		super().__init__(**kwargs)
 		self.hidden1 = keras.layers.Dense(units, activation)
		self.hidden2 = keras.layers.Dense(units, activation)
		self.hidden3 = keras.layers.Dense(units, activation)
		self.output = keras.layers.Dense(units)
		
	def call(self, inputs):
		hidden1 = self.hidden1(input_B)
		..
		return self.output


model = WideAndDeepModel(매개변수)

 

 

 

 

2-6) 모델 저장과 복원

  • 모델을 만들고 훈련 한 후 모델.save 함수를 호출하면 모델을 훈련한 상태로 저장할 수 있다.
  • load_model 함수를 이용해서 복원이 가능
  • 복원한 모델을 이용해서 예측을 할 수 도 있고 훈련을 추가로 할 수 있습니다.
    • 복원한 모델을 가지고 추가 훈련을 해서 사용하는 것을 사전 훈련된 모델을 이용한다고 함

 

 

 

 

 

 

 

3. Keras NN

3-1) Sequential API 이용 회귀

# 데이터 가져오기

  • 외부에서 데이터를 가져오면 자료형을 확인
  • 디셔너리라면 모든 key를 확인 - keys()
  • 클래스라면 모든 속성을 확인 - dir
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()

print(type(housing))
print(dir(housing))
<class 'sklearn.utils._bunch.Bunch'>
['DESCR', 'data', 'feature_names', 'frame', 'target', 'target_names']

 

 

# 데이터 분할 - 훈련 / 훈련 시 검증 / 테스트

from sklearn.model_selection import train_test_split

X_train_full, X_test, y_train_full, y_test = train_test_split(housing.data, housing.target, random_state=42)

X_train, X_valid, y_train, y_valid = train_test_split(X_train_full, y_train_full, random_state=42)
 

 

 

# 정규화

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

X_train = scaler.fit_transform(X_train)
X_valid = scaler.fit_transform(X_valid)
X_test = scaler.fit_transform(X_test)

print(X_train.shape)
(11610, 8)

 

 

# 회귀 모델 만들기

  • Sequential API를 이용해서 회귀용 MLP을 구축, 훈련, 평가, 예측하는 방법은 분류에서 했던 것 과 매우 비슷하지만
  • 차이점은 출력 층이 활성화 함수가 없는 하나의 뉴런을 가져야 한다는 것, 손실 함수가 평균 제곱 오차나 평균 절대값 오차로 변경을 해야 함
import tensorflow as tf
from tensorflow import keras

#회귀 모델 만들기
#input_shape 설정할 때 데이터의 개수는 생략
model = keras.models.Sequential([
    keras.layers.Dense(30, activation="relu", input_shape=X_train.shape[1:]),
    keras.layers.Dense(15, activation="relu"),
    keras.layers.Dense(units=1)
])

model.summary()
Model: "sequential"

 

 

# 모델 컴파일 및 훈련

model.compile(loss="mean_squared_error", 
              optimizer=keras.optimizers.SGD(learning_rate=0.001))
history = model.fit(X_train, y_train, epochs=20,
                   validation_data = (X_valid, y_valid))


Epoch 20/20
363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 0.3844 - val_loss: 0.4524

 

 

# 모델 평가

print(model.evaluate(X_test, y_test))

 

 

# 예측

X_new = X_test[:3]
y_pred = model.predict(X_new)
print(y_pred)

 

 

- Sequential API

  • 층을 연결하고 모든 데이터가 순차적으로 층을 통과하면서 출력을 만드는 방식

 

 

 

3-2) Functional API를 활용한 회귀

- Functional API

  • 일반적 MLP(Sequential API)는 네트워크에 있는 층 전체에 모든 데이터를 통과시키는데 이렇게 하면 간단한 패턴이 연속적인 변환으로 인해서 왜곡 될 수 있음
  • 입력의 일부 또는 전체를 출력 층에 바로 연결하는 방식
    • MLP가 전체를 통과하기도 하고 일부분만 통과하기도 하기 때문에 복잡한 패턴 과 단순한 패턴 모두를 학습해서 더 좋은 성과를 내기도 합니다.
  • 함수형 API를 사용하게 되면 입력 데이터를 설정
    • 층 간의 결합도 직접 설정

 

- 함수형 API를 이용해서 모델 만들기

#모델 만들기
input_ = keras.layers.Input(shape=X_train.shape[1:])

#input_ 층의 출력을 받아서 수행하는 층
hidden1 = keras.layers.Dense(30, activation="relu")(input_)
hidden2 = keras.layers.Dense(15, activation="relu")(hidden1)

#2개의 층 합치기
concat = keras.layers.concatenate([input_, hidden2])

#출력 층 생성
output = keras.layers.Dense(1)(concat)

model = keras.models.Model(inputs=[input_], outputs=[output])
  • 함수형 API의 모델을 만들 때는 input 과 output에 list를 대입한다.
  • 여러 개의 입력 과 여러 개의 출력 사용이 가능하다.
  • 객체 탐지 와 같은 문제를 해결할 때는 출력이 1개가 아니라 여러 개인 경우가 많다.
  • 이런 문제는 Sequential API로는 해결할 수 없다.

 

# 컴파일과 훈련

model.compile(loss="mean_squared_error", 
              optimizer=keras.optimizers.SGD(learning_rate=0.001))
history = model.fit(X_train, y_train, epochs=20,
                   validation_data = (X_valid, y_valid))


Epoch 20/20
363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.4188 - val_loss: 0.4654

 

 

- 입력 경로 여러개 생성

#여러 경로의 input 사용
input_A = keras.layers.Input(shape=[5])
input_B = keras.layers.Input(shape=[6])

hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(15, activation="relu")(hidden1)

#input_A는 하나의 hidden 층도 통과하지 않은 데이터
# hidden2는 2개의 hidden 층을

#통과한 데이터
concat = keras.layers.concatenate([input_A, hidden2])
output = keras.layers.Dense(1)(concat)

#입력을 2가지를 사용 - 데이터의 다양성을 추가해서 학습
#모든 입력이 hidden layer를 통과하게 되면 깊이가 깊어질 때 데이터의 왜곡 발생 가능
model = keras.models.Model(inputs=[input_A, input_B], outputs=[output])

 

# 모델의 input 데이터 수정해서 입력 데이터 수정

  • 첫번째 입력은 앞의 5개를 사용하고 두번째 입력은 뒤의 6개를 이용 - 전체 데이터 사용
X_train_A, X_train_B = X_train[:, :5], X_train[:, 2:]
X_valid_A, X_valid_B = X_valid[:, :5], X_valid[:, 2:]
X_test_A, X_test_B = X_test[:, :5], X_test[:, 2:]
model.compile(loss="mse", optimizer=keras.optimizers.SGD(learning_rate=0.001))
history = model.fit((X_train_A, X_train_B), y_train,
                   epochs=20, validation_data=((X_valid_A, X_valid_B), y_valid))
mse_test = model.evaluate((X_test_A, X_test_B), y_test)


363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.4468 - val_loss: 0.4863
162/162 ━━━━━━━━━━━━━━━━━━━━ 0s 944us/step - loss: 0.4481

 

 

- 출력을 여러개 생성

필요한 이유: 

  • 그림에 있는 주요 물체를 분류하고 위치를 알아야 하는 경우가 있는데 이 경우는 분류 와 회귀를 동시에 수행해야 하는 경우
  • 다중 분류 작업을 하고자 하는 경우
  • 보조 출력을 사용하고자 할 때
    • 서로 다른 입력을 받아서 딥러닝을 수행한 후 일정 비율을 적용해서 반영

다른 입력을 받아서 출력을 만든 후 0.9:0.1 비율로 반영해서 출력 만들기

  • 손실 함수를 수행할 때 각 출력의 비중을 다르게 반영하도록 하기
  • 여러 경로의 input 사용
input_A = keras.layers.Input(shape=[5])
input_B = keras.layers.Input(shape=[6])

hidden1 = keras.layers.Dense(30, activation="relu")(input_B)
hidden2 = keras.layers.Dense(15, activation="relu")(hidden1)

concat = keras.layers.concatenate([input_A, hidden2])

output = keras.layers.Dense(1)(concat)
aux_output = keras.layers.Dense(1)(hidden2)

model = keras.models.Model(inputs=[input_A, input_B],
                          outputs=[output, aux_output])
#출력이 2개가 된 경우 손실을 적용할 때 비율을 설정하는 것이 가능
#이 경우는 첫번째 출력의 손실을 90% 반영하고 두번째 출력의 손실을 10% 반영
model.compile(loss="mse", optimizer=keras.optimizers.SGD(learning_rate=0.001),
             loss_weights=[0.9, 0.1])

history = model.fit((X_train_A, X_train_B), y_train,
                   epochs=20, validation_data=((X_valid_A, X_valid_B), y_valid))


Epoch 20/20
363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.3993 - val_loss: 0.4345

 

X_new_A, X_new_B = X_test_A[:3], X_test_B[:3]
#출력이 2개 이므로 각 데이터마다 2개의 값을 리턴
y_pred_main, y_pred_aux = model.predict([X_new_A, X_new_B])
print(y_pred_main)
print(y_pred_aux)
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 76ms/step
[[0.4174796]
 [1.8939716]
 [3.5359378]]
[[-0.1951855 ]
 [-0.06544766]
 [-0.6592884 ]]

 

 

 

3-3) SubClassing을 이용하는 방법

  • Model 클래스를 상속받아서 사용하는 방법
  • __init__ 메서드를 재정의해서 필요한 층을 생성
  • call 메서드를 재정의해서 출력층을 리턴

- 장점

  • 모델을 생성할 때 필요한 매개변수를 직접 설정할 수 있기 때문에 동적인 모델을 만들 수 있다.

- 이전과 동일한 모델을 subclassing을 이용해서 구현

  • 상위 클래스를 직접 만들지 않은 경우는 __init__ 메서드에서
  • 상위 클래스의 __init__을 호출해야 합니다.
class WideAndDeepModel(keras.models.Model):
    def __init__(self, units=30, activation="relu", **kwargs):
        #상위 클래스의 초기화 메서드 호출
        super().__init__(**kwargs)
        
        #층을 생성
        self.hidden1 = keras.layers.Dense(units, activation=activation)
        self.hidden2 = keras.layers.Dense(units, activation=activation)
        
        self.main_output = keras.layers.Dense(1)
        self.aux_output = keras.layers.Dense(1)
        
    #두번째 매개변수가 input     
    def call(self, inputs):
        input_A, input_B = inputs
        hidden1 = self.hidden1(input_B)
        hidden2 = self.hidden2(hidden1)
        
        concat = keras.layers.concatenate([input_A, hidden2])
        
        main_output = self.main_output(concat)
        aux_output = self.aux_output(hidden2)
        return main_output, aux_output

 

model = WideAndDeepModel(30, activation="relu")
model.compile(loss="mse", optimizer=keras.optimizers.SGD(learning_rate=0.001),
             loss_weights=[0.9, 0.1])

history = model.fit((X_train_A, X_train_B), y_train,
                   epochs=20, validation_data=((X_valid_A, X_valid_B), y_valid))


Epoch 20/20
363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step - loss: 0.3751 - val_loss: 0.4181

 

 

 

 

3-4) CallBack

- 어떤 사건이 발생했을 때 특정 작업을 수행하도록 하는 것

 

- 조기종료

  • epochs 을 크게 지정하면 훈련을 많이 수행하기 때문에 성능이 좋아질 가능성이 높은데 늘리게 되면 훈련 시간이 오래 걸린다.
  • 일정 에포크 동안 검증 세트에 대한 점수가 향상되지 않으면 훈련을 멈추도록 할 수 있다.
  • keras.callbacks.EarlyStopping 클래스의 인스턴스를 만들 때 patience 매개변수에 원하는 에포크 지정해서 만들고 모델이 fit 메서드를 호출할 때 callbacks 파라미터에 list 형태로 대입하면 됨
#5번의 epoch 동안 점수가 좋아지지 않으면 조기 종료
early_stopping_cb = keras.callbacks.EarlyStopping(patience=5)

history = model.fit((X_train_A, X_train_B), y_train,
                   epochs=100, validation_data=((X_valid_A, X_valid_B), y_valid),
                   callbacks=[early_stopping_cb])



Epoch 45/100
363/363 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - loss: 0.3407 - val_loss: 0.3985

 

 

- 학습률 스케줄러

  • 학습률을 동적으로 변화시켜서 수행하는 것이 가능
  • LearningRateScheduler 라는 클래스를 이용하는데 이 때는 에포크 와 학습율을 매개변수로 갖는 함수를 만들어서 인스턴스를 생성할 때 대입
#5번의 epoch 동안 점수가 좋아지지 않으면 조기 종료
early_stopping_cb = keras.callbacks.EarlyStopping(patience=5)

#5번의 epoch 동안은 기존 학습률을 유지하고 그 이후에는 학습률을 감소시키는 함수
def scheduler(epoch, lr):
    if epoch < 5:
        return lr
    else:
        lr = lr - 0.0001
        return lr
#학습률을 동적으로 변화시키는 체크포인트    
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(scheduler)

history = model.fit((X_train_A, X_train_B), y_train,
                   epochs=100, validation_data=((X_valid_A, X_valid_B), y_valid),
                   callbacks=[early_stopping_cb, lr_scheduler])

 

 

- 모델 저장과 복원

  • 딥러닝은 fit 함수를 호출한 후 다음에 다시 fit을 호출하면 이전에 훈련한 이후 부터 다시 훈련하는 것이 가능
  • 기존 모델을 저장하고 이를 읽어들여서 재훈련이 가능

 

# 모델 저장

  • save 함수를 이용해서 저장
  • 모델을 저장할 때 loss 는 반드시 full name으로 작성
  • 버전에 따라서는 확장자를 제한하는 경우도 있다.
    • 최신 버전에서 이 부분이 적용
model = keras.models.Sequential([
    keras.layers.Dense(30, activation="relu", input_shape=[8]),
    keras.layers.Dense(15, activation="relu"),
    keras.layers.Dense(1)
])

model.compile(loss="mse", optimizer=keras.optimizers.SGD(learning_rate=0.001))
history=model.fit(X_train, y_train, epochs=10, validation_data=(X_valid, y_valid))
mse_test = model.evaluate(X_test, y_test)
#모델 저장
model.save("my_model.keras")

 

 

- 모델 가져오기

  • load 함수 이용

# 모델 복원

m = keras.models.load_model("my_model.keras")

#복원 된 모델과 기존의 모델이 같은 결과를 만들어 냅니다.
print(m.predict(X_new))
print(model.predict(X_new))
  •  복원된 모델과 기존의 모델이 같은 결과를 만들어냄

 

 

- 모델의 가중치를 저장하고 복원

  • save_weights 와 load_weights를 이용
model.save_weights("my_model.weights.h5")
model.load_weights("my_model.weights.h5")

 

 

- 모델을 저장하는 체크 포인트 사용

  • ModelCheckPoint 라는 클래스를 이용해서 모델의 이름을 설정
  • 체크 포인트를 만들 때 이름만 설정하면 마지막 모델이 저장
    • 마지막 모델은 최상의 모델이 아닐 수 있다.
    • save_best_only=True를 설정하면 최상의 모델을 저장한다.
checkpoint_cb = keras.callbacks.ModelCheckpoint('my_model.keras',
                                               save_best_only=True)
history = model.fit(X_train, y_train, epochs=10,
                   validation_data=(X_valid, y_valid),
                   callbacks=[checkpoint_cb])

 

 

 

3-5) 사용자 정의 콜백

- Callback 클래스를 상속받아서 원하는 콜백을 생성할 수 있는 기능

- on_train_begin, on_train_end, on_epoch_begin, on_epoch_end, on_batch_bigin, on_batch_end 이 메서드들은 훈련전후 그리고 한 번의 에포크 전후 그리고 배치 전후에 작업을 수행시키고자 하는 경우에 사용

  • on_test 로 시작하는 메서드를 오버라이딩 하면 검증 단계에서 작업을 수행

 

 

 

3-6) 신경망의 하이퍼 파라미터 튜닝

- 신경망의 유연성은 단점이 되기도 하는데 조정할 하이퍼 파라미터가 많음

- 복잡한 네트워크 구조에서 뿐 만 아니라 간단한 다층 퍼셉트론에서도 층의 개수, 층마다 존재하는 뉴런의 개수, 각 층에서 사용하는 활성화 함수, 가중치 초기화 전략 등 많은 것을 바꿀 수 있는데 어떤 하이퍼 파라미터 조합이 주어진 문제에 대해서 최적인지 확인

- 이전 머신러닝 모델들은 GridSearchCV 나 RandomizedSearchCV를 이용해서 하이퍼 파라미터 공간을 탐색할 수 있었는데 딥러닝에서는 이 작업을 할려면 Keras 모델을 sklearn 추정기 처럼 보이도록 바꾸기

 

 

- 신경망의 하이퍼 파라미터 튜닝

keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)

 

 # 딥러닝 모델을 만드는 함수

def build_model(n_hidden=1, n_neurons=30, learning_rate=0.003, input_shape=[8]):
    model = keras.models.Sequential()
    #입력 레이어 추가
    model.add(keras.layers.InputLayer(input_shape=input_shape))
    #n_hidden 만큼 히든 층 추가
    for layer in range(n_hidden):
        model.add(keras.layers.Dense(n_neurons, activation="relu"))
    #출력 층 추가
    model.add(keras.layers.Dense(1))
    #최적화 함수 생성
    optimizer = keras.optimizers.SGD(learning_rate = learning_rate)
    model.compile(loss="mean_squared_error", optimizer=optimizer, metrics=['mse'])
    return model

 

# 하이퍼 파라미터 튜닝을 위해 scikit-learn 추정기로 딥러닝 모델 생성

!pip install scikeras
from scikeras.wrappers import KerasRegressor

keras_reg = KerasRegressor(build_model())


from sklearn.model_selection import RandomizedSearchCV

param_distribs = {
    "epochs":[100, 200, 300, 400, 500, 600]
}

#랜더마이즈드 cv를 생성
rnd_search_cv = RandomizedSearchCV(keras_reg, param_distribs, n_iter=10, cv=3,
                                  verbose=2)
#하이퍼파라미터 튜닝
rnd_search_cv.fit(X_train, y_train, epochs=100,
                 validation_data=(X_valid, y_valid),
                 callbacks=[keras.callbacks.EarlyStopping(patience=10)])

 

 

 

- 은닉 층 개수

  • 이론적으로는 은닉 층이 하나인 다층 퍼셉트론이라도 뉴런 개수가 충분하다면 아주 복잡한 함수도 모델링할 수 있지만 심층 신경망이 얕은 신경망보다 파라미터 효율성이 좋다. 
  • 심층 신경망은 복잡한 함수를 모델링하는데 얕은 신경망보다 적은 수의 뉴런을 사용하기 때문에 동일한 양의 훈련 데이터에서 더 높은 성능을 낼 수 있다.
  • 심층 신경망을 만들면 아래쪽 은닉 층은 저수준의 구조를 모델링하고 
    중간 은닉 층은 저수준의 구조를 연결해서 중간 수준의 구조를 모델링하고 
    가장 위쪽 은닉 층 과 출력 층은 중간 수준의 구조를 연결해서 고수준의 구조를 모델링 
  •  MNIST 데이터 세트에서 하나의 은닉층에 수백개의 뉴런을 사용한 경우 정확도가 97% 정도 나왔는데 동일한 수의 뉴런을 사용하고 은닉층을 2개로 늘렸을 때 98% 정도의 정확도
  • 복잡한 문제들은 훈련 세트에 과대 적합이 될 때 까지 점진적으로 은닉 층의 개수를 늘릴 수 있는데 대규모 이미지 분류나 음성 인식 분야에서는 수십개에서 수백개의 은닉층을 가지게 되는데 이런 경우는 훈련 데이터가 아주 많이 필요함
    • 일반적으로 이런 네트워크를 처음부터 훈련하는 경우는 거의 없음
    • 비슷한 작업에서 가장 뛰어난 성능을 낸 미리 훈련된 네트워크 일부를 재사용

 

- 은닉 층의 뉴런 개수

  • 입력 층 과 출력 층의 뉴런의 개수는 수정할 수 없음
  • 은닉 층의 뉴런의 개수는 일반적으로 각 층의 뉴런의 개수를 줄여가면서 깔대기처럼 구성하는데 저수준의 많은 특성이 고수준의 적은 특성으로 합쳐질 수 있기 때문
  • 뉴런의 개수는 일단 많이 가지고 시작을 하다가 조기 종료 나 규제를 이용해서 조정

 

- 학습률

  • 좋은 학습률을 찾는 방법은 아주 작은 학습률(0.00001) 에서 시작해서 점진적으로 큰 학습률(10) 까지 수백 번 반복하여 모델을 훈련
    • 보통의 경우는 0.00001 ~ 10 까지 exp(log (10의 6승 / 500)) 을 곱해가면서 조정

 

- Optimiazer

  • SGD 보다는 최근에 등장한 Optimizer 사용을 권장

 

- Batch_Size

  • 32 이하의 크기를 사용하는 권장하지만 최근에는 학습률을 작은 값에서 큰 값으로 변경해가면서 수행하는 경우는 192 정도의 크기도 괜찮다.
  • 배치 크기가 커지면 훈련 시간을 단축할 수 있음
  • 학습률을 변경하면서 수행할 때 큰 배치 크기를 사용하고 훈련이 불안정해지면 작은 배치 크기를 선택

 

- 활성화 함수

  • 일반적으로 ReLU 가 좋은 값

 

- 반복 횟수

  • 대부분 크게 설정하고 조기 종료를 설정

 

 

3-7) 딥러닝에서의 문제점과 해결책

- 그라디언트 소실 과 폭주 문제

  • 역전파 알고리즘: 
    출력 층에서 입력 층으로 오차 그라디언트를 전파하면서 진행되는데 알고리즘이 신경망의 모든 파라미터에 대한 오차 함수의 그라디언트를 계산하면 경사 하강법 단계에서 이 그라디언트를 사용해서 파라미터를 수정
  • vanishing gradient(그라디언트 소실) :
    알고리즘이 하위 층으로 진행될수록 그라디언트는 점점 작아지는 경우가 많은데 경사 하강법이 하위 층의 연결 가중치를 변경하지 않은채로 둔다면 훈련이 좋은 솔루션을 찾지 못하게 되는 현상.
  • exploding gradient(폭주):
    그라디언트가 점점 더 커져서 여러 층이 비정상적으로 큰 가중치를 가지되면 알고리즘은 발산한다고 하는데  RNN에서 주로 나타남
  • 원인
    • 활성화 함수로 로지스틱 시그모이드 함수를 사용하고 가중치 초기화를 할 때 평균이 0이고 표준 편차가 1인 정규 분포에서 추출한 값으로 한 것
    • 로지스틱 시그모이드 함수는 평균이 0이 아니고 0.5 이기 때문에 이 문제가 발생한다고 보고 하이퍼볼릭 탄젠트 함수(평균이 0)를 사용

 

- 글로럿과 He 초기화

  • 딥러닝 초창기에는 가중치 초기화를 할 때 평균이 0이고 표준 편차가 1인 정규 분포에서 추출한 값을 가지고 사용
  • 글러럿 초기화 또는 세이비어 초기화
    • 평균이 0이고 분산이 1/fan(avg) 인 정규 분포 나 제곱근(3/fan(avg)) 한 값에 -1 과 1을 곱한 값의 균등 분포에서 초기화
      fan-in: 입력의 개수
      fan-out: 출력의 개수
      fan(avg) = (fan-in + fan-out)/2
    • 훈련 속도도 향상
    • fan(avg) 대신에 fan-in을 사용하면 르쿤 초기화라고 함
  • He 초기화
    • ReLU와 그 변종들
    • 2/fan-in 인 정규분포를 가지고 초기화
  • keras 에서 지원하는 목록: https://www.tensorflow.org/api_docs/python/tf/keras/initializers
  • 기본값은 Glorot 초기화 인데 다른 방법을 사용하고자 하면 kernel_initializer="초기화 방법" 을 지정하거나 클래스의 인스턴스로 지정
  • 지원 가능한 목록
    • print([name for name in dir(keras.initializers) if not name.startswith("_")])
  • 사용
    • keras.layers.Dense(10, activation="relu", kernel_initializer = "he_normal")

 

- 수렴하지 않는 활성화 함수

  • 딥러닝 모델을 만들 때 잘못된 활성화 함수를 선택하면 그라디언트 소실이나 폭주 문제가 발생할 수 있음
  • 초창기에는 sigmoid 함수가 최선이라고 생각을 했지만 이후에 다양한 활성화 함수가 등장
  • 많이 사용되는 것은 relu, softmax, tanh 등ㅍ
  • ReLU
    • 특정 양수 값에 수렴하지 않는다는 장점이 있음
    • 계산이 빠름
    • 학습률을 너무 높게 설정하면 가중치의 합이 음수가 되서 뉴런이 죽어 버림
    • 뉴런이 죽는 문제를 해결하기 위해서 max(알파*x, x)를 적용하는 방식이 있는데 이것이 LeakyReLU
    • 활성화 함수로 적용해도 되지만 층을 만들어서 추가해도 됨
    • 최근의 Tensorflow에서 제공하는 Keras API는 문자열로 설정하는 부분을 인스턴스를 생성해서 적용하는 부분으로 변경되고 있다.
  • LeakyReLU를 적용한 패션 MNIST 분류

# 활성화 함수의 초기화 변경

(X_train_full, y_train_full), (X_test, y_test)  = keras.datasets.fashion_mnist.load_data()
X_train_full = X_train_full /  255.0
X_test = X_test / 255.0

X_valid, X_train = X_train_full[:5000], X_train_full[5000:]
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
tf.random.set_seed(42)
np.random.seed(42)

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(250, kernel_initializer="he_normal"),
    keras.layers.LeakyReLU(),
    keras.layers.Dense(100, kernel_initializer="he_normal"),
    keras.layers.LeakyReLU(),
    keras.layers.Dense(50,kernel_initializer="he_normal"),
    keras.layers.LeakyReLU(),
    keras.layers.Dense(10, activation="softmax"),
])

model.compile(loss="sparse_categorical_crossentropy",
             optimizer=keras.optimizers.SGD(learning_rate=0.001),
             metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=10,
                   validation_data=(X_valid, y_valid))
print(model.evaluate(X_test, y_test))
313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8340 - loss: 0.4829
[0.4901731014251709, 0.8281000256538391]

 

 

- 알파를 무작위로 선택하고 테스트 할 때는 평균을 사용하는 방식인 RReLU 방식도 제안

- 알파 값을 훈련하는 동안 역전파에 의해서 변경하는 PReLU 도 등장

  • 대규모 이미지 세트에서는 매우 잘 동작을 하는데 소규모 데이터에서는 과대 적합이 발생하는 경우가 있음

- PReLU를 적용해서 비교

#PReLU 사용
tf.random.set_seed(42)
np.random.seed(42)

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(250, kernel_initializer="he_normal"),
    keras.layers.PReLU(),
    keras.layers.Dense(100, kernel_initializer="he_normal"),
    keras.layers.PReLU(),
    keras.layers.Dense(50,kernel_initializer="he_normal"),
    keras.layers.PReLU(),
    keras.layers.Dense(10, activation="softmax"),
])

model.compile(loss="sparse_categorical_crossentropy",
             optimizer=keras.optimizers.SGD(learning_rate=0.001),
             metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=10,
                   validation_data=(X_valid, y_valid))
print(model.evaluate(X_test, y_test))

 

 

- ELU 함수

  • ReLU 와 유사한데 0보다 작을 때 알파(exp(데이터) - 1) 로 계산
  • Dense 층에 activation 매개변수에 "elu"로 바로 적용 
  • 훈련 속도는 좋은데 검증 속도가 느림

 

#PReLU 사용

tf.random.set_seed(42)
np.random.seed(42)

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(250, kernel_initializer="he_normal", activation="relu"),
    keras.layers.Dense(100, kernel_initializer="he_normal", activation="relu"),
    keras.layers.Dense(50,kernel_initializer="he_normal", activation="relu"),
    keras.layers.Dense(10, activation="softmax"),
])

model.compile(loss="sparse_categorical_crossentropy",
             optimizer=keras.optimizers.SGD(learning_rate=0.001),
             metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=10,
                   validation_data=(X_valid, y_valid))
print(model.evaluate(X_test, y_test))

 

 

- SELU

  • Scaled ELU
  • 출력을 평균이 0이고 표준 편차가 1이 되도록 만들어주는 활성화 함수
  • 다른 활성화 함수보다 뛰어난 성능을 발휘
  • 제약조건
    • 입력 특성이 반드시 표준화 되어 이어야 함(평균이 0이고 표준편차가 1)
    • 가중치 초기화는 르쿤 초기화가 되어 있어야 함
    • Sequential API로 생성된 모델이어야 함
  • 단점
    • 훈련 시간이 오래 걸림
  • activation 매개변수에 selu를 설정

- 컴퓨팅 자원이 허락한다면 여러가지 초기화 방법을 적용해서 훈련을 하면 의미있는 결과를 얻어 낼 수 있다.

 

 

- 배치 정규화

  • 활성화 함수를 변경하고 초기화 기법을 변경을 해서 그라디언트 소실 문제 나 폭주 문제를 어느 정도 해결할 수 있는데 훈련을 많이 하게 되면 완전히 해결했다고 보기는 어려움
  • 활성화 함수에 대입하기 전 이나 후에 데이터를 원점에 맞추고 정규화 한 다음 2개의 새로운 파라미터를 추가해서 값의 스케일을 조정하고 이동시킴
  • 새로 만들어진 2개의 파라미터는 하나는 스케일 조정에 사용하고 하나는 원점 이동에 사용
  • 배치 정규화 층을 신경망의 첫번째 층으로 사용하면 훈련 세트를 표준화 할 필요가 없음
  • 이 역할을 BatchNoramlization 이라는 층을 이용해서 수행
model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    
    keras.layers.BatchNormalization(),
    keras.layers.Dense(250, activation="relu"),
    
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100, activation="relu"),
    
    keras.layers.BatchNormalization(),
    keras.layers.Dense(50, activation="relu"),
    
    keras.layers.BatchNormalization(),
    keras.layers.Dense(10, activation="softmax"),
])

model.compile(loss="sparse_categorical_crossentropy",
             optimizer=keras.optimizers.SGD(learning_rate=0.001),
             metrics=['accuracy'])
history = model.fit(X_train, y_train, epochs=10,
                   validation_data=(X_valid, y_valid))
print(model.evaluate(X_test, y_test))
  • 배치 정규화를 이용하면 일부 뉴런이 죽는 현상을 거의 해소하기 때문에 성능이 좋아질 가능성이 높다.

 

 

- 그라디언트 클리핑

  • 역전파 될 때 일정 임계값을 넘어서는 못하게 그라디언트를 잘라 내는 것
  • RNN 에서는 배치 정규화를 사용하기가 어려움
    • 이 경우에는 Optimizer를 만들 때 clipvalue 나 clipnorm 매개변수를 이용해서 Gradient 값을 제한하는 방법으로 유사한 효과를 나타낸다.
    • 배치 정규화가 -1 ~ 1 사이의 값으로 정규화하므로 임계값은 1.0을 사용한다.
    • 그라디언트의 값이 -1 보다 작거나 1보다 크면 -1 이나 1로 수정해서 기울기가 소실되는 것을 방지

 

 

- 사전 훈련된 층 재사용

  • 전이학습 (Transfer Learning)  : 큰 규모의 NN을 처음부터 훈련시키는 것은 많은 자원을 소모하게 되는데 이런 경우 해결하려는 것과 비슷한 유형의 문제를 처리한 신경망이 있는지 확인해보고 그 신경망의 하위층을 재사용하는 것이 효율적
  • 전이 학습을 이용하게 되면 훈련 속도를 개선할 수 있고 훈련 데이터의 양도 줄어들게 된다.
  • 동일한 이미지 분류 모델이라면 하위 층에서 유사하게 점이나 선 들의 모형을 추출하는 작업을 할 가능성이 높으므로 하위 층을 공유해도 유사한 성능을 발휘하게 된다. 
  • 출력 층은 하고자 하는 마지막 작업이 서로 다를 가능성이 높고(ImageNet 의 이미지를 분류하는 모델은 20,000 개의 카테고리를 가지고 있고 우리는 개와 고양이를 분류하는 이진 분류의 경우) 상위 층은 거의 출력 과 유사한 형태가 만들어 진 것이므로 역시 재사용할 가능성은 낮다.
  • 샌달 과 셔츠를 제외한 모든 이미지를 가지고 분류 모델을 만들고 이를 이용해서 샌달과 셔츠 이미지 중 200개 만 가진 작은 훈련 세트를 훈련해서 정확도를 확인

 

#데이터 셋 분할

def split_dataset(X, y):
    y_5_or_6 = (y == 5) | (y == 6)
    #5 나 6이 아닌 데이터
    y_A = y[~y_5_or_6]
    y_A[y_A > 6] -= 2 #6보다 큰 레이블은 2를 빼서 연속된 레이블로 만들기
    
    y_B = (y[y_5_or_6] == 6).astype(np.float32)
    return ((X[~y_5_or_6], y_A), (X[y_5_or_6], y_B))
(X_train_A, y_train_A), (X_train_B, y_train_B) = split_dataset(X_train, y_train)
(X_valid_A, y_valid_A), (X_valid_B, y_valid_B) = split_dataset(X_valid, y_valid)
(X_test_A, y_test_A), (X_test_B, y_test_B) = split_dataset(X_test, y_test)

X_train_B = X_train_B[:200]
y_train_B = y_train_B[:200]
X_train_A.shape #훈련 데이터 전체
(43986, 28, 28)
X_train_B.shape #훈련 데이터 중 200개
(200, 28, 28)
y_train_A[:50] #8가지 모양
array([4, 0, 5, 7, 7, 7, 4, 4, 3, 4, 0, 1, 6, 3, 4, 3, 2, 6, 5, 3, 4, 5,
       1, 3, 4, 2, 0, 6, 7, 1, 3, 7, 0, 3, 7, 4, 2, 7, 0, 6, 3, 3, 2, 2,
       0, 6, 3, 1, 7, 1], dtype=uint8)
y_train_B[:50] #2가지 모양
array([1., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0., 1., 1., 0., 0., 0., 0.,
       0., 0., 1., 1., 0., 0., 1., 1., 0., 1., 1., 1., 1., 1., 0., 1., 0.,
       1., 1., 1., 0., 1., 0., 0., 0., 1., 1., 1., 0., 1., 0., 0., 0.],
      dtype=float32)

 

- 많은 양의 데이터로 모델 훈련

  • 이 작업은 실제로는 우리는 검색하고 모델을 load 하면 됨
tf.random.set_seed(42)
np.random.seed(42)

#모델 생성
model_A = keras.models.Sequential()

#입력 층
model_A.add(keras.layers.Flatten(input_shape=[28, 28]))

#히든 층
for n_hidden in (300, 100, 50, 50, 50):
    model_A.add(keras.layers.Dense(n_hidden, activation="selu"))

#출력 층
model_A.add(keras.layers.Dense(8, activation="softmax"))


model_A.compile(loss = "sparse_categorical_crossentropy",
               optimizer=keras.optimizers.SGD(learning_rate=0.001),
               metrics=['accuracy'])

history = model_A.fit(X_train_A, y_train_A, epochs=20,
                     validation_data=(X_valid_A, y_valid_A))

 

# 모델 저장

model_A.save("my_model_A.keras")
#실제 해결을 하고자 하는 모델
model_B = keras.models.Sequential()
#입력 층
model_B.add(keras.layers.Flatten(input_shape=[28, 28]))
#히든 층
for n_hidden in (300, 100, 50, 50, 50):
    model_B.add(keras.layers.Dense(n_hidden, activation="selu"))
#출력 층: 2개를 분류하는 것은 2가지 방법이 있음
#1일 확률을 구하는 것 과 0 과 1일 확률을 구하는 것
model_B.add(keras.layers.Dense(1, activation="sigmoid"))
model_B.compile(loss = "binary_crossentropy",
               optimizer=keras.optimizers.SGD(learning_rate=0.001),
               metrics=['accuracy'])
history = model_B.fit(X_train_B, y_train_B, epochs=20,
                     validation_data=(X_valid_B, y_valid_B))

 

- 기존 모델인 model_A를 이용해서 해결

#기존 모델 가져오기
model_A = keras.models.load_model("my_model_A.keras")

#기존 모델에서 출력 층을 제외한 레이어를 가져오기
model_B_on_A = keras.models.Sequential(model_A.layers[:-1])

#출력 층 추가
model_B_on_A.add(keras.layers.Dense(1, activation='sigmoid'))

#모든 레이어가 다시 훈련하지 않도록 설정
for layer in model_B_on_A.layers[:-1]:
    layer.trainable = False
    
model_B_on_A.compile(loss = "binary_crossentropy",
               optimizer=keras.optimizers.SGD(learning_rate=0.001),
               metrics=['accuracy'])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=20,
                     validation_data=(X_valid_B, y_valid_B))

 

# 모든 레이어가 다시 훈련하도록 설정

#기존 모델에서 출력 층을 제외한 레이어를 가져오기
model_B_on_A = keras.models.Sequential(model_A.layers[:-1])
#출력 층 추가
model_B_on_A.add(keras.layers.Dense(1, activation='sigmoid'))

#모든 레이어가 다시 훈련하도록 설정
for layer in model_B_on_A.layers[:-1]:
    layer.trainable = True
    
model_B_on_A.compile(loss = "binary_crossentropy",
               optimizer=keras.optimizers.SGD(learning_rate=0.001),
               metrics=['accuracy'])
history = model_B_on_A.fit(X_train_B, y_train_B, epochs=20,
                     validation_data=(X_valid_B, y_valid_B))

 

 

# 일반화 오차 비교

print(model_B.evaluate(X_test_B, y_test_B))
print(model_B_on_A.evaluate(X_test_B, y_test_B))

 

 

 

- 고속 옵티마이저 사용

  • 모멘텀 최적화: SGD를 사용할 때 momentum 이라는 파라미터에 값을 설정하면 성능이 좋아짐
  • 모멘텀 최적화에서 훈련 속도를 향상시키고자 할 때는 네스테로프 가속 경사를 이용하게 되는데 이 경우에는 use_nestrov=True를 추가
  • Adagrad, RMSProp, Adam, Nadam 등의 옵티마이저가 추가

 

- 완전 연결층에 규제를 가하는 방법

  • l1 이나 l2 규제를 추가
  • kernel_regularizer 파라미터에 keras.regularizers.l1 이나 l2(값)

 

- Dropout

  • 입력 뉴런은 그대로 사용하고 출력 뉴런은 임시적으로 Drop 될 확률을 설정
    • 이전 레이어에서 전달되는 데이터를 일부분 제거하는 방식
  • 확률은 일반적으로 10 ~ 50% 사이로 설정하는데 RNN에서는 20~30 정도로 설정하고 CNN에서는 40~50% 로 설정
  • 이를 사용하고자 할 때는 Dropout 이라는 클래스에 rate로 확률을 설정
    • 입력 층 앞에는 넣으면 안되고 입력층 다음부터 추가해서 사용

'Python' 카테고리의 다른 글

[Python] 딥러닝 _ Tensorflow  (0) 2024.03.20
[Python] 연관 분석  (0) 2024.03.18
[Python] 연관분석 실습 _ 네이버 지식인 크롤링  (4) 2024.03.15
[Python] 감성 분석 실습  (3) 2024.03.14
[Python] 차원 축소  (0) 2024.03.12

Keras 의 내장 데이터 세트

- 종류

  • boston housing: 보스톤 집값으로 회귀에 사용
  • cifar10: 이미지 분류 데이터로 종류가 10가지
  • cifar100: 이미지 분류 데이터로 종류가 100가지
  • mnist: 손 글씨 데이터
  • fashion mnist: 의류 데이터로 클래스가 10가지
  • imdb: 영화 데이터로 분류에 활용
  • reuters: 뉴스 토픽 - 자연어 분류

- load_data 라는 함수를 호출하면 훈련 데이터 튜플 과 테스트 데이터 튜플로 데이터를 리턴
- 각각의 튜플은 피처 와 레이블로 나뉘어져 있음

 

 

 

1. 데이터 가져오기

fashion_mnist = keras.datasets.fashion_mnist
(X_train_full, y_train_full), (X_test, y_test) = fashion_mnist.load_data()

print(X_train_full.shape) #28 * 28 에 해당하는 이미지 6만개
print(X_test.shape)
print(y_test.shape)
(60000, 28, 28)

(10000, 28, 28)

(10000,)

 

X_train_full[0] #0부터 255까지의 값으로 되어 있음
  • 딥러닝은 0 ~ 1 사이의 실수인 경우 학습을 더 잘하는 것으로 알려져 있음
  • 값을 변경하지 않아도 학습은 가능하지만 되도록이면 0 ~ 1 사이의 숫자로 변환해주는 것이 좋습니다.

 

 

 

2. 정규화 및 훈련/검증 데이터 분할

X_valid, X_train = X_train_full[:5000] / 255. , X_train_full[5000:] / 255.
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_test = X_test/255.

 

 

# 이미지 1개 출력

plt.imshow(X_valid[0], cmap='binary')

 

 

# 각 클래스의 실제 레이블을 배열로 생성

class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
              "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
print(y_train[0])
print(class_names[y_train[0]])
plt.imshow(X_train[0], cmap='binary')
4
Coat


 

 

 

 

 

3. Flatten Layer

  • 데이터의 차원을 1차원으로 변경해주는 Layer
  • Dense Layer 는 입력받는 데이터의 차원이 1차원만 가능
    • Dense Layer는 입력 데이터의 차원을 1차원으로 변경해서 사용해야 합니다.
    • 이미지 데이터의 경우는 변환을 해서 사용해야 합니다.
  • Flatten Layer를 자동으로 1차원으로 변경을 수행합니다.
print(np.array([[1, 2], [3, 4]]).flatten())
[1 2 3 4]

 

 

 

- 활성화 함수

- ReLU: max(0, x)
- Leaky ReLU: max(x, 0.1x) - 이론적으로 기울기 소실이 일어날 수 있지만 실제로는 거의 일어나지 않음

 

 

 

 

 

4. 모델 생성

model = keras.models.Sequential()
  • Dense는 데이터의 차원을 1차원으로 입력을 받아야 합니다.
  • 이미지 데이터의 경우는 가로 * 세로 또는 가로 * 세로 * 채널의수 로 입력
  • Flatten 은 원본 이미지의 차원을 주면 1차원으로 수정을 합니다.
model.add(keras.layers.Flatten(input_shape=[28, 28])) #Input Layer
model.add(keras.layers.Dense(300, activation="relu")) #Hidden Layer
model.add(keras.layers.Dense(200, activation="relu")) #Hidden Layer
model.add(keras.layers.Dense(100, activation="relu")) #Hidden Layer
  • Output Layer의 units 는 출력의 개수가 되어야 합니다.
  • 분류 문제는 활성화 함수로 softmax 가 잘 동작합니다.
  • 출력을 하나로 만들고자 하면 sigmoid를 사용하면 됩니다.
model.add(keras.layers.Dense(10, activation="softmax")) #Output Layer

 

 

 

 

 

5. 모델 구조 확인

model.summary()

 

 

- 손실 함수

  • 출력층의 활성화 함수가 sigmoid: binary_crossentropy
  • 출력층의 활성화 함수가 softmax: 
    • categorical_crossentropy(원핫인코딩 된 경우)
    • sparse_categorical_crossentropy(원핫인코딩 안된 경우)

 

 

- 옵티마이저

  • 손실을 낮추기 위해서 신경망의 가중치 와 학습률 등의 신경망의 속성을 변경하는데 사용되는 최적화 방식
  • 종류
    • SGD(확률적 경사하강법)
    • RMSprop
    • Adam: 가장 많이 사용되는 알고리즘으로 좋은 성능을 내는 것으로 알려져 있음
    • Adadelta
    • Adagrad
    • Nadam
    • Ftrl

 

- 평가 지표

  • 분류: auc, precision, recall, accuracy
  • 회귀: mse, mae, rmse

 

 

 

 

6. 모델 complie

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

# 모델 훈련
history = model.fit(X_train, y_train, epochs=30,
                   validation_data = (X_valid, y_valid))

 

 

# 일반화 오차 - 평가

model.evaluate(X_test, y_test)

 

 

# 예측

  • predict: 각 클래스에 대한 기댓값
  • predict_classes: 가장 높은 클래스가 리턴
X_new = X_test[:3]
y_proba = model.predict(X_new)
print(y_proba.round(3))

 

y_pred = np.argmax(model.predict(X_new), axis=-1)
print(y_pred)
print(np.array(class_names)[y_pred])

데이터 url

    red: http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv
    white: http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-

 

 

white.csv

- 구분자: ;

- 피처
    fixed acidity
    volatile acidity
    citric acid
    residual sugar
    chlorides
    free sulfur dioxide
    total sulfur dioxide
    density
    pH
    sulphates
    alcohol
    quality

 

 

 

1. 데이터 가져오기

import pandas as pd
red = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv',
                 sep=";")
red.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1599 entries, 0 to 1598
Data columns (total 12 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   fixed acidity         1599 non-null   float64
 1   volatile acidity      1599 non-null   float64
 2   citric acid           1599 non-null   float64
 3   residual sugar        1599 non-null   float64
 4   chlorides             1599 non-null   float64
 5   free sulfur dioxide   1599 non-null   float64
 6   total sulfur dioxide  1599 non-null   float64
 7   density               1599 non-null   float64
 8   pH                    1599 non-null   float64
 9   sulphates             1599 non-null   float64
 10  alcohol               1599 non-null   float64
 11  quality               1599 non-null   int64  
dtypes: float64(11), int64(1)
memory usage: 150.0 KB

 

white = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv',
                 sep=";")
white.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4898 entries, 0 to 4897
Data columns (total 12 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   fixed acidity         4898 non-null   float64
 1   volatile acidity      4898 non-null   float64
 2   citric acid           4898 non-null   float64
 3   residual sugar        4898 non-null   float64
 4   chlorides             4898 non-null   float64
 5   free sulfur dioxide   4898 non-null   float64
 6   total sulfur dioxide  4898 non-null   float64
 7   density               4898 non-null   float64
 8   pH                    4898 non-null   float64
 9   sulphates             4898 non-null   float64
 10  alcohol               4898 non-null   float64
 11  quality               4898 non-null   int64  
dtypes: float64(11), int64(1)
memory usage: 459.3 KB

 

 

 

2. 기술통계량 확인

wine.describe()


 

 

 

 

3. 정규화

- 데이터의 값을 0 ~ 1로 정규화

wine_norm = (wine - wine.min()) / (wine.max() - wine.min())
wine_norm.describe()

 

 

 

 

4. 데이터 셔플

#데이터를 랜덤하게 섞기
wine_shuffle = wine_norm.sample(frac=1)
wine_np = wine_shuffle.to_numpy()
print(wine_np[:5])
[[0.26446281 0.06       0.44578313 0.18711656 0.05980066 0.07986111
  0.2764977  0.13668787 0.41860465 0.08988764 0.60869565 0.83333333
  1.        ]
 [0.32231405 0.37333333 0.12650602 0.02453988 0.11295681 0.10763889
  0.29262673 0.16367843 0.42635659 0.12921348 0.27536232 0.33333333
  0.        ]
 [0.24793388 0.20666667 0.20481928 0.10429448 0.01827243 0.12847222
  0.29262673 0.09658762 0.35658915 0.12359551 0.57971014 0.66666667
  1.        ]
 [0.20661157 0.13333333 0.28313253 0.16257669 0.05149502 0.20833333
  0.4078341  0.1698477  0.31007752 0.16292135 0.2173913  0.5
  1.        ]
 [0.29752066 0.18       0.12048193 0.20398773 0.07475083 0.21527778
  0.51382488 0.22691344 0.30232558 0.15730337 0.13043478 0.5
  1.        ]]

 

 

 

 

5. 훈련 데이터와 테스트 데이터 만들기

#80%에 해당하는 인덱스 구하기
train_idx = int(len(wine_np) * 0.8)
#80% 기준으로 훈련 데이터 와 테스트 데이터 분리
train_X, train_Y = wine_np[:train_idx, :-1], wine_np[:train_idx, -1] 
test_X, test_Y = wine_np[train_idx:, :-1], wine_np[train_idx:, -1] 

#타겟을 원핫인코딩을 수행
train_Y = tf.keras.utils.to_categorical(train_Y, num_classes=2)
test_Y = tf.keras.utils.to_categorical(test_Y, num_classes=2)
print(train_Y[0])
[0. 1.]
  • 일반 머신러닝 알고리즘에서는 타겟을 원핫인코딩 하지 않음

 

 

 

6. 분류 모델 생성

- 마지막 층의 units 은 출력하는 데이터의 개수로 회귀는 1 분류는 2 이상
- 분류 문제의 activation 은 softmax를 많이 사용

model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=48, activation='relu', input_shape=(12,)),
    tf.keras.layers.Dense(units=24, activation='relu'),
    tf.keras.layers.Dense(units=12, activation='relu'),
    tf.keras.layers.Dense(units=2, activation='softmax')
])

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.07),
             loss='categorical_crossentropy',
             metrics=['accuracy'])
model.summary()
  • softmax 함수는 자연 로그의 밑인 e의 지수를 사용해 계산한 뒤 모두 더한 값으로 나누는데 이렇게 나온 결과는 총합이 1.0 인 확률값
  • softmax 는 분류 나 RNN 에서 다음 토큰 예측 등 결괏값으로 확률이 필요한 분야에서 사용
  • 이 경우는 [0.97, 0.03]  으로 나오는데 앞이 red 와인일 확률이고 1이 white 와인일 확률
  • 시그모이드 처럼 곡선 함수

 

#소프트맥스 함수

#소프트맥스 함수
import math

x = np.arange(-2, 2, 0.01)
e_x = math.e ** x

plt.plot(x, e_x)
plt.show()
  • entropy: 정보이론에서 정보량을 나타내기 위해 사용하는 단위
  • 확률의 역수에 로그를 취한 값
  • -log확률
  • 확률이 높은 사건일 수 록 정보량이 적다고 판단하기 때문
  • 엔트로피의 기댓값은 엔트로피에 확률을 곱해준 값
  • 엔트로피가 높은 것은 높은 불확실성을 나타냄
  • 분류문제에서 불확실성의 정도는 낮추는 방향으로 학습을 진행
  • 분류 문제에서는 어느 한쪽의 확률이 높은 쪽으로 학습을 진행

 

 

 

7. 훈련

  • 32개씩 가지고 학습
  • 25% 검증 데이터로 만들어서 확인
  • 총 25번 진행
history = model.fit(train_X, train_Y, epochs=25,
                   batch_size=32, validation_split=0.25)

 

 

 

 

8. 모델 평가

model.evaluate(test_X, test_Y)
  • 이진 분류를 할 때 타겟은 2개의 속성으로 만들어져야 하고 출력 층에서 unit의 개수는 2개이고 손실 함수로 categorical_crossentropy를 사용하고 출력 층의 activation 함수는 softmax를 사용
  • 다중 클래스 분류는 출력 층에서 unit 개수만 변경하면 됩니다.

 

 

 

9. 다중 클래스 분류

#타겟으로 사용할 만한 특성 확인

print(wine['quality'].describe())
count    6497.000000
mean        5.818378
std         0.873255
min         3.000000
25%         5.000000
50%         6.000000
75%         6.000000
max         9.000000
Name: quality, dtype: float64

 

 

#샘플의 타겟 비율이 5:1 정도가 넘어가면 샘플링 비율을 조정

print(wine['quality'].value_counts())
quality
6    2836
5    2138
7    1079
4     216
8     193
3      30
9       5
Name: count, dtype: int64

 

 

#6을 기준으로 6보다 작으면 0 6이면 1 7이상이면 2로 구간화

wine.loc[wine['quality'] <= 5, 'new_quality'] = 0
wine.loc[wine['quality'] == 6, 'new_quality'] = 1
wine.loc[wine['quality'] >= 7, 'new_quality'] = 2

print(wine['new_quality'].value_counts())
new_quality
1.0    2836
0.0    2384
2.0    1277
Name: count, dtype: int64

 

 

#불필요한 데이터 제거

del wine['quality']

 

 

#정규화

  • 딥러닝을 할 때는 이 작업을 하지 않아도 되는데 대신에  epoch 를 늘려주던지 아니면 layer을 더 많이 쌓아 해결
wine_backup = wine.copy()

wine_norm = (wine - wine.min()) / (wine.max() - wine.min())
wine_norm['new_quality'] = wine_backup['new_quality']

 

#셔플

  • 데이터를 읽을 때 순서대로 데이터를 읽었기 때문에 셔플을 하지 않으면 샘플링할 때 어느 한 쪽 데이터가 과표집(많이 표집) 될 수 있다.
  • 여론조사는 컨벤션 효과라고 하는 것 때문에 과표집 문제가 많이 발생
wine_shuffle = wine_norm.sample(frac=1)
print(type(wine_shuffle))
wine_np = wine_shuffle.to_numpy()
<class 'pandas.core.frame.DataFrame'>

 

train_idx = int(len(wine_np) * 0.8)
train_X, train_Y = wine_np[:train_idx, :-1], wine_np[:train_idx, -1] 
test_X, test_Y = wine_np[train_idx:, :-1], wine_np[train_idx:, -1]
  • 딥러닝을 이용해서 분류를 할 때는 타겟을 원핫인코딩 하는 경우가 많다.
  • 분류를 할 때는 activation 을 softmax로 설정하는 경우가 많은데 softmax는 각 클래스에 대한 기대값을 확률의 형태로 나타낸다.
  • 출력이 여러 개의 값으로 구성된다.
  • 가장 기대값이 높은 인덱스에 1을 설정하고 나머지는 0으로 설정
train_Y = tf.keras.utils.to_categorical(train_Y, num_classes=3)
test_Y = tf.keras.utils.to_categorical(test_Y, num_classes=3)

 

 

# 분류 모델 생성

  • 마지막 층의 units 은 출력하는 데이터의 개수로 회귀는 1 분류는 2 이상
  • 분류 문제의 activation 은 softmax를 많이 사용
  • 분류를 할 때 3가지 모양을 가져야 하므로 마지막 층의 units 만 3으로 수정
model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=48, activation='relu', input_shape=(12,)),
    tf.keras.layers.Dense(units=24, activation='relu'),
    tf.keras.layers.Dense(units=12, activation='relu'),
    tf.keras.layers.Dense(units=6, activation='relu'),
    tf.keras.layers.Dense(units=3, activation='softmax')
])


model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.003),
             loss='categorical_crossentropy',
             metrics=['accuracy'])
model.summary()
model.fit(train_X, train_Y, epochs=25, batch_size=32, 
         validation_split=0.25)
model.evaluate(test_X, test_Y)

 

 

 

1. 딥러닝

  • 여러 비선형 변환 기법의 조합을 통해 높은 수준의 추상화를 시도하는 머신러닝 알고리즘의 집합
  • 연속된 층(Layer)에서 점진적으로 의미있는 표현을 배우는 방식
  • 기존의 머신러닝 방법은 1~2가지의 데이터 표현을 학습하는 얕은 학습을 수행하지만 딥러닝은 수백개 이상의 층 이용
  • 데이터로부터 표현을 학습하는 수학 모델
  • 층을 통과할 때마다 새로운 데이터 표현을 만들어가면서 학습

 

 

 

1-1) 작동 원리

  • 층에서 입력 데이터가 처리되는 내용은 일련의 숫자로 이루어진 층의 가중치에 저장이 되는데 이는 그 층의 가중치를 parameter로 갖는 함수로 표현

  • 이 가중치를 알아내려면 데이터를 관찰해야 하고 신경망의 출력이 기대하는 것보다 얼마나 벗어났는지 측정
    • 딥러닝은 기본적으로 지도학습
    • 지도학습은 오차를 줄여나가는 작업이므로 오차를 측정하는 함수가 존재해야 하는데 이를 손실함수라고 한다.(Loss Function)
  • 딥러닝은 손실 함수의 값이 감소하는 방향으로 가중치 값을 수정해나가는 것
    • 초창기에는 네트워크의 가중치를 랜덤하게 할당하고 랜덤한 변환을 연속적으로 수행하는 방식을 사용했는데 이 방식은 수행을 많이 하게 되면 오히려 손실 점수가 높아지게 된다.

 

 

작동원리

 

 

1-2) 특징

  • 딥러닝이 확산된 가장 큰 요인은 많은 문제에서 기존의 머신러닝 알고리즘보다 성능이 우수하기 때문
  • 특성 공학을 완전히 자동화 함
    • 기존의 머신러닝 알고리즘들은 대부분 알고리즘이 이해할 수 있도록 데이터를 변화하는 작업을 직접 수행
    • 딥러닝은 데이터의 좋은 표현을 스스로 만들어낸다. 
  • 딥러닝이 학습할 때의 특징
    • 층을 거치면서 점진적으로 더 복잡한 표현을 만들어냄
    • 점진적인 표현을 만드는 작업을 순차적으로 하는게 아니고 공동으로 학습

 

 

1-3) 최근 경향

  • 2010년대 후반까지는 Kaggle에서 GBM과 Deep Learning을 사용한 모델이 우승을 차지했는데 GBM은 구조적인 데이터에 사용하고 Deep Learning은 이미지 분류와 지각에 관한 문제에 사용
    • GBM 모델 중에서도 XGBoost가 주로 이용
  • 패턴 인식 분야에서는 최근 거의 무조건 Deep Learning 사용
  • 딥러닝을 많이 사용하게 된 이유는 하드웨어의 발전과 데이터의 증가

 

 

1-4) 장점

  • 성능이 우수
  • 특성 공학이 자동
  • 구조화되지 않은 데이터 학습 능력이 뛰어남

 

 

1-5) 제한

  • 학습 데이터가 많아야 함
  • 네트워크가 만들어 낸 특성을 해석하기가 어려움
  • 컴퓨팅 자원이 많이 필요
  • 데이터의 크기가 기가 바이트를 넘지 않는다면 딥러닝과 머신러닝은 별 차이가 없다.

 

 

1-6) 딥러닝 패키지

- Torch

  • C로 구현된 라이브러리로 페이스북에서 Torch로 만들 PyTorch를 내놓으면서 유명
  • 연구용으로 많이 사용 

- Theano

  • Numpy 배열과 관련성이 높은 패키지로 계산을 많이 하는 연구에 이용
  • 구글의 오픈소스인 Tensorflow가 Theano에서 영감을 얻은 라이브러리
  • 프로덕션 구현이 강력 - PC, Android, Web용 라이브러리 제공

- CuDNN

  • CUDA Deep Neural Network의 약자로 GPU 구현을 위한 라이브러리 제공

 

 

 

 

2. 인공 신경망 (ANN - Artificial Neural Network)

  • 근원: 지능적인 기계를 만드는 법에 대한 영감을 얻으려면 뇌 구조를 살펴보는 것이 합리적이라고 판단
    • 인공 신경망은 뇌에 있는 생물학적 뉴런의 네트워크에서 영감을 받은 머신러닝 모델이지만 새를 보고 비행기에 대한 영감을 얻었다고 해서 비행기 날개를 새처럼 펄럭 거릴 필요는 없는데 인공 신경망도 생물학적 뉴런에서 점점멀어지고 있음
  • 일부에서는 neuron이라는 표현 대신에 unit이라고 지칭
  • 1943년에 워런 매컬러의 논문에서 처음 소개

 

 

2-1) 뉴런을 이용한 논리 연산

- 가장 처음 만든 모델은 하나 이상의 이진 입력과 이진 출력 하나를 가짐

  • 이 모델을 가지고 not, or, and 연산 수행

 

 

2-2) Perceptron

  • 입력과 출력이 이진이 아닌 숫자이고 각각의 입력 연결은 가중치와 연관되어 있음
  • 입력의 가중치 합을 계산한 뒤 계산된 합에 함수(계단 함수 - step function)를 적용해서 결과를 출력 
  • 가장 많이 사용되는 계단 함수: heaviside와 sign function
    • heaviside는 가중치의 합이 0보다 작으면 0,  0보다 크거나 같으면 1을 리턴하는 함수
    • sign function은 가중치의 합이 0보다 작을 때 -1,  0일 때 0,  0보다 클 때 1을 리턴하는 함수
  • 이러한 perceptron이 여러개 모여서 하나의 Layer를 구성하게 되고 이전 층의 모든 뉴런과 연결된 것을 Fully Connected Layer(완전연결층) 또는 Dense Layer(밀집층)
  • 입력은 입력 뉴런이라고 하는 특별한 뉴런에 주입이 되는데 이러한 입력 뉴런으로 구성된 레이러를 Input Layer라고 부른다.
    • 이 레이어가 다른 점은 편향 특성이 더해지는데 (x0 = 1) 이 편향 특성은 항상 1을 출력하는 특별한 종류의 뉴런이라고 해서 편향 뉴런(Bias Neuron)이라고 표현
  • Perceptron은 선형 분류 모형의 형태를 갖게 됨
    • 논리 연산 중 XOR 문제를 해결하지 못함

 

 

2-3) MLP

  • Perceptron이 지닌 한계점을 극복하기 위해서 여러개의 Layer를 쌓아 올린 MLP(Multi Layer Perceptron)이 등장
  • Perceptron은 기본적으로 Input과 Output Layer로만 구성되지만 MLP는 이 중간에 Hidden Layer를 추가한 형태
    • Hidden Layer는 여러개의 Perceptron이 모여있는 구조
    • Hidden Layer를 여러개 쌓으면 깊어지기 때문에 이를 Deep Learning이라고 부름
  • Input에서 Weight을 계산하고 Hidden Layer를 거쳐서 Output을 만들어내는데 이 과정을 Feed Forward라고 한다.

 

 

2-4) Activation Function

  • 어떤 신호를 받아서 이를 적절히 처리해서 출력해주는 함수
  • Input과 Weight를 받아서 연산을 수행해주는 함수
  • 신경망은 비선형 Activation Function을 선호
  • 선형 함수: Sign Function, Heaviside Step

- Sigmoid(로그함수 - 1/(1 + e의 -x승))

  • Sigmoid 함수는 입력 값이 0 이하이면 0.5 이하의 값을 출력하고 0 이상이면 0.5 이상의 값을 출력하는데 입력 값에 대해서 0과 1 사이의 값으로 Scailing 해주는 개념의 함수
  • Sign Function과 Heaviside는 선형이고 Sigmoid는 비선형
  • 대부분의 경우 비선형이 선형보다 우수한 성능을 발휘하기 때문에 신경망은 비선형을 선호
  • Sigmoid 함수는 복잡한 문제를 해결할 수는 있지만 Back Propagation 과정 중 Gradient Vanishing(소멸) 현상이 발생할 수 있음
  • 모델이 깊어질수록 이러한 현상이 자주 발생

 

- softmax 함수

  • Sigmoid를 일반화시킨 함수로 다중 클래스 분류에 가장 적합

 

- ReLU (Rectified Linear Unit)

  • 전체 입력이 0보다 크면 출력은 순입력(가중치를 곱하고 편향을 더한 값)과 같고 전체 입력이 0보다 작거나 같으면 0을 출력
  • y = max(0, 가중치*입력 + 편향)
  • ReLU는 입력이 양수이고 기울기가 0이어도 일정한 기울기를 가지게 됨
  • 양수만 보면 선형이지만 0이나 0보다 작을 때 0의 값을 갖도록 해서 비선형을 만들어 냄
  • 음수일 때도 기울기를 가지도록 만들어진 함수가 있는데 이 함수는 PReLU

 

- Hyperbolic Tangent : tanh

  • 출력이 0인 지점에서 기울기를 가짐

 

 

2-5) Learning Rate 학습률

  • 경사 하강법에서 가중치를 업데이트 할 때 간격
  • 숫자가 커지면 최적의 지점을 찾지 할 수 있고 너무 작은 값을 선택하면 학습 속도가 느려질 수 있음
  • 신경망에서는 0.01 아래의 값을 사용하는 것을 권장

 

 

2-6) Back Propagation 역전파

  • Input 에서 Output 까지 계산을 수행하고 그 결과와 실제 답과의 오차를 구해서 오차가 작아지는 형태로 가중치를 조정해야 하는데, 가중치를 조정할 때 뒤에 있는 가중치를 먼저 업데이트 한다고 해서 역전파 알고리즘이라고 부름
  • 한번 Feed Forward 로 작업을 수행하고 Back Propagation을 수행하면 1번의 epoch 라고 함
  • 알고리즘을 수행할 때 모든 데이터를 가지고 한꺼번에 하지는 않고 일반적으로 mini batch 라고 부르는 작은 단위로 실행

 

 

 

 

3. TensorFlow

  • 구글이 만든 Deep Learning에 초점을 맞춘 라이브러리
  • GPU 지원
  • 분산 컴퓨팅을 지원: 여러 대의 컴퓨터에서 동시에 학습 가능하고 예측도 가능
  • 플랫폼에 중립적인 포맷으로 내보낼 수 있음
    • 리눅스를 사용하는 파이썬 환경에서 Tensorflow 모델을 훈련시키고 이 모델을 안드로이드에서 실행할 수 있음

 

 

3-1)  API 

  • 거의 모든 운영체제를 지원하고 Tensorflow Lite를 이용하면 안드로이드 와 iOS 같은 모바일 운영체제에서도 실행되고 Python API를 많이 이용하지만 C++, Java, Go(golang), Swift API도 제공됨
  • Tensorflow Hub에서 사전에 훈련된 신경망을 쉽게 다운로드 받아서 사용할 수 있음
  • https://www.tensorflow.org/resources

 

 

3-2) 연산

  • 가장 저 수준의 연산은 C++ 코드로 만들어져 있음
  • 연산들이 여러 종류의 커널에서 수행: CPU, GPU, TPU 등
    • TPU는 Deep Learning 연산을 위해 특별하게 설계된 ASIC Chip

 

 

3-3) 설치

pip install tensorflow
  • numpy 버전과 tensorflow 버전이 서로 충돌할 때가 있다.

버전확인

import tensorflow as tf
print(tf.__version__)

 

 

 

 

4. Tensorflow 사용

4-1) Tensor 생성

  • numpy의 ndarray와 유사한 방식으로 생성
  • immutable 데이터(변경 불가)는 tf.constant로 생성
tf.constant(42) # 스칼라
tf.constant([[1., 2., 3.], [4., 5., 6.]]) # 행렬
t = tf.constant([[1., 2., 3.], [4., 5., 6.]])
t
<tf.Tensor: shape=(2, 3), dtype=float32, numpy=
array([[1., 2., 3.],
          [4., 5., 6.]], dtype=float32)>
print(t[:, 1])  #1번째 열: 하나의 열이라서 1차원 배열
print()
print(t[:, 1:])  #1번째 열부터 모든 열
print()
print(t[..., 1, tf.newaxis])
tf.Tensor([2. 5.], shape=(2,), dtype=float32)

tf.Tensor(
[[2. 3.]
 [5. 6.]], shape=(2, 2), dtype=float32)

tf.Tensor(
[[2.]
 [5.]], shape=(2, 1), dtype=float32)
  • 1차원 배열이 2차원 배열이 된다.

 

 

4-2) 연산

  • numpy 의 ndarray 와 거의 동일한 방식으로 연산을 수행하고 함수 나 메서드의 이름도 거의 일치
print(t + 10)
print()

print(tf.square(t))
print()

print(t @ tf.transpose(t))
tf.Tensor(
[[11. 12. 13.]
 [14. 15. 16.]], shape=(2, 3), dtype=float32)

tf.Tensor(
[[ 1.  4.  9.]
 [16. 25. 36.]], shape=(2, 3), dtype=float32)

tf.Tensor(
[[14. 32.]
 [32. 77.]], shape=(2, 2), dtype=float32)

 

 

- tensorflow나 numpy에서 배열을 가지고 산술연산을 하는 경우 실제로는 함수 호출

  • python 에서는 __이름__ 형태로 메서드를 만드는 경우가 있는데 이러한 함수들은 Magic Function 이라고 부르는데 사용을 할 때 메서드 이름을 호출하는 것이 아니라 다른 연산자를 이용한다.
  • 이렇게 연산자의 기능을 변경하는 것을 연산자 오버로딩
    • 객체 지향에서만 가능합니다.
  • + 는 숫자 두 개를 더하는 기능을 가지고 있는데 tensorflow 나 numpy 에서는 __add__를 정의해서 이 메서드를 호출하도록 한다.

 

- 이름이 다른 함수도 있음

  • np.mean -> tf.reduce_mean

 

- tf.float32 는 제한된 정밀도를 갖기 때문에 연산을 할 때 마다 결과가 달라질 수 있음

 

- 동일한 기능을 하는 함수나 클래스를 Keras 가 별도로 소유하고 있는 경우도 있음

  • Keras는 Tensorflow 의 저수준 API(Core 에 가까운 쪽)

 

 

4-3) Tensor와 numpy의 ndarray

  • 2개의 데이터 타입은 호환이 되서 서로 변경이 가능
  • Tensor를 ndarray 로 변환하고자 하는 경우는 numpy() 메서드를 호출해도 되고 numpy 의 array 함수에 Tensor를 대입해도 된다.
    • ndarray를 가지고 Tensor를 만들고자 할 때는 contant 나 variable 같은 함수에 대입
ar = np.array([2, 3, 4])

print(tf.constant(ar))
tf.Tensor([2 3 4], shape=(3,), dtype=int32)
ar = np.array([2, 3, 4])

print(tf.constant(ar))
[2 3 4]
[2 3 4]

 

 

4-4) 타입 변환

  • Data Type이 성능을 크게 감소시킬 수 있음
  • Tensorflow 는 타입을 절대로 자동 변환하지 않는다.
    • 데이터 타입이 다르면 연산이 수행되지 않음
  • 타입이 다른데 연산을 수행하고자 하는 경우에는 tf.cast 함수를 이용해서 자료형을 변경해야 함
  • Tensorflow 가 형 변환을 자동으로 하지 않는 이유는 사용자가 알지 못하는 작업은 수행을 하면 안된다는 원칙 때문
#ndarray는 이렇게 자료형이 다른 경우 자동으로 자료형을 변경해서 연산을 수행
print(np.array([1, 2, 3]) + np.array([1.6, 2.3, 3.4]))
[2.6 4.3 6.4]
#자료형이 달라서 에러가 발생
print(tf.constant(2.0) + tf.constant(40))
InvalidArgumentError: cannot compute AddV2 as input #1(zero-based) was expected to be a float tensor but is a int32 tensor [Op:AddV2] name: 
t1 = tf.constant(40) 
#형 변환 한 후 연산 수행
print(tf.constant(2.0) + tf.cast(t1, tf.float32))
tf.Tensor(42.0, shape=(), dtype=float32)

 

 

4-5) 변수

  • 일반적인 데이터는 직접 변경을 하지 않기 때문에 constant 로 생성해서 사용하면 되는데 역전파 알고리즘을 수행하게 되면 가중치가 업데이트된다고 했는데 가중치는 수정되어야 하기 때문에 constant로 생성되면 안됨
  • 수정되어야 하는 데이터는 tf.Variable 을 이용해서 생성
    • 데이터를 수정하고자 하는 경우는 assign 함수를 이용
    • assign_add 나 assign _sub를 이용해서 일정한 값을 더하거나 빼서 수정할 수 있음
  • 배열의 일부분을 수정할 때는 assign 함수나 scatter_update() 나 scatter_nd_update ()를 이용
#변수 생성
v = tf.Variable([[1, 2, 3], [4, 5, 6]])
print(v)
print(id(v))

#이 경우는 데이터를 수정한 것이 아니고 
#기존 데이터를 복제해서 연산을 수행한 후 그 결과를 가리키도록 한 것
#참조하는 위치가 변경됨
v = v * 2
print(v)
print(id(v))

#내부 데이터 수정
v = tf.Variable([[1, 2, 3], [4, 5, 6]])
print(v)
print(id(v))

#이 경우는 id 변경없이 데이터를 수정 
v.assign(2 * v)
print(v)
print(id(v))
<tf.Variable 'Variable:0' shape=(2, 3) dtype=int32, numpy=
array([[1, 2, 3],
       [4, 5, 6]])>
2832592070160
tf.Tensor(
[[ 2  4  6]
 [ 8 10 12]], shape=(2, 3), dtype=int32)
2832590246432
<tf.Variable 'Variable:0' shape=(2, 3) dtype=int32, numpy=
array([[1, 2, 3],
       [4, 5, 6]])>
2832592070992
<tf.Variable 'Variable:0' shape=(2, 3) dtype=int32, numpy=
array([[ 2,  4,  6],
       [ 8, 10, 12]])>
2832592070992

 

 

4-6) Data구조

- 종류

  • SparseTensor: 0이 많은 Tensor를 효율적으로 나타내는데 tf.sparse 패키지에서 이 Tensor에 대한 연산을 제공 
  • TensorArray: Tensor의 list
  • RaggedTensor: list 의 list
  • stringtensor: 문자열 텐서인데 실제로는 바이트 문자열로 저장됨
  • set
  • queue

 

 

4-7) Tensorflow 함수

  • 일반 함수는 python 의 연산 방식에 따라 동작하지만 Tensorflow 함수를 만들게 되면 Tensorflow 프레임워크가 자신의 연산 방식으로 변경해서 수행을 하기 때문에 속도가 빨라질 가능성이 높음

- 생성방법

  • 함수를 정의한 후 tf.function 이라는 decorator를 추가해도 되고 tf.function 함수에 함수를 대입해서 리턴받아도 됨
  • 함수를 변경하는 과정에서 Tensorflow 함수로 변경이 불가능하면 일반 함수로 변환

 

 

4-8) 난수 생성

  • tf.random 모듈을 이용해서 난수를 생성

 

 

 

5. 뉴런 생성

  • 뉴런을 추상화하면 Perceptron 

 

5-1) 입력 -> 뉴런 -> 출력

  • 이런 여러 개의 뉴런이 모이면 Layer 라고 하며 이런 Layer의 집합이 신경망

 

 

5-2) 뉴런의 구성

  • 입력, 가중치, 활성화 함수, 출력으로 구성
  • 입력과 가중치와 출력은 일반적으로 정수나 실수
  • 활성화 함수는 뉴런의 출력 값을 정하는 함수
  • 간단한 형태의 뉴런은 입력에 가중치를 곱한 뒤 활성화 함수를 취하면 출력을 얻어 낼 수 있음
  • 뉴런은 가중치를 처음에는 랜덤하게 초기화를 해서 시작하고 학습 과정에서 점차 일정한 값으로 수렴
    • 학습 할 때 변하는 것은 가중치
  • 활성화 함수는 시그모이드나 ReLU 를 주로 이용
    • 최근에는 주로 ReLU를 주로 이용
  • 뉴런을 직접 생성
#시그모이드 함수
import math
def sigmoid(x):
    return 1 / (1 + math.exp(-x))

#입력
x = 1
#출력
y = 0

#한번 학습
w = tf.random.normal([1], 0, 1) #가중치
output = sigmoid(x * w)
print(output)
0.5174928205334988
#학습률을 0.01 로 설정해서 경사하강법을 수행

#학습 횟수 - epoch
for i in range(1000):
    #출력을 생성
    output = sigmoid(x * w)
    #출력 오차를 계산
    error = y - output
    
    #가중치 업데이트
    w = w + x * 0.1 * error
    #횟수 와 오차 그리고 출력값을 확인
    if i % 100 == 0:
        print(i, error, output)
0 -0.000905242523688215 0.000905242523688215
100 -0.0008971286421305708 0.0008971286421305708
200 -0.0008891556201675512 0.0008891556201675512
300 -0.0008813285493673459 0.0008813285493673459
400 -0.0008736348299158559 0.0008736348299158559
500 -0.0008660738198464301 0.0008660738198464301
600 -0.0008586448693105883 0.0008586448693105883
700 -0.000851337993012676 0.000851337993012676
800 -0.0008441600044856342 0.0008441600044856342
900 -0.0008370967242979266 0.0008370967242979266
  • 입력 데이터가 0 이고 가중치를 업데이트하는 식이 w + x * 0.1 * error  라서 x 가 0이 되면 가중치를 업데이트 할 수 없음
  • 단순하게 시그모이드 함수를 적용하면 이 경우 최적점에 도달할 수 없고 계속 0.5 만 출력
  • 입력 데이터 0에서 기울기가 없어지는 현상 - 기울기 소실 문제(Gradient Vanishing)
  • 이 경우에는 시그모이드 함수 대신에 0에서 기울기를 갖는 ReLU를 사용하거나 시그모이드 함수에 데이터를 대입할 때 편향을 추가해서 해결
#입력
x = 0
#출력
y = 1

#학습률을 0.01 로 설정해서 경사하강법을 수행

#학습 횟수 - epoch
for i in range(1000):
    #출력을 생성
    output = sigmoid(x * w)
    #출력 오차를 계산
    error = y - output
    
    #가중치 업데이트
    w = w + x * 0.1 * error
    #횟수 와 오차 그리고 출력값을 확인
    if i % 100 == 0:
        print(i, error, output)
0 0.5 0.5
100 0.5 0.5
200 0.5 0.5
300 0.5 0.5
400 0.5 0.5
500 0.5 0.5
600 0.5 0.5
700 0.5 0.5
800 0.5 0.5
900 0.5 0.5

 

 

 

5-3) AND를 구현

- AND 연산

    0 0 -> 0
    0 1 -> 0
    1 0 -> 0
    1 1 -> 1

  • 입력 피처는 2개이고 출력은 1개,  가중치는 2개가 되어야 한다.
    가중치는 입력 피처마다 설정됨
#입력과 출력 데이터 생성
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [0], [0], [1]])

#가중치 초기화
w = tf.random.normal([2], 0, 1) #입력 피처가 2개이므로 가중치도 2개
b = tf.random.normal([1], 0, 1) #편향

#학습
for i in range(3000):
    #샘플이 여러 개이므로 에러는 모든 샘플의 에러 합계로 계산
    #에러 합계를 저장할 변수
    error_sum = 0
    #4는 입력 데이터의 개수
    for j in range(4):
        #출력
        output = sigmoid(np.sum(X[j] * w)+ 1*b)
        #오차
        error = y[j][0] - output
        #가중치 업데이트 - 학습률은 0.1
        w = w + X[j] * 0.1 * error
        #편향 업데이트
        b = b + 1 * 0.1 * error
        #에러 값을 추가
        error_sum += error
    #100번 훈련을 할 때 마다 에러의 합계를 출력     
    if i % 100 == 99:
        print(i, error_sum)
99 -0.17735192112485976
199 -0.11300731067658454
299 -0.08377195476035765
399 -0.06660772360599848
499 -0.05523404708400695
599 -0.047131021104291515
699 -0.0410676336021214
799 -0.036362474376702675
899 -0.03260789231152378
999 -0.02954500410502825
1099 -0.026999358822200475
1199 -0.024851178654502953
1299 -0.023015025341699566
1399 -0.02142777432262047
1499 -0.020043216619398396
1599 -0.018823937643251798
1699 -0.017743068185467267
1799 -0.01677645680136907
1899 -0.015911195444538048
1999 -0.01512755414717172
2099 -0.014419652551368074
2199 -0.013772095486375625
2299 -0.013179941576847973
2399 -0.01263622946061431
2499 -0.012136535236976859
2599 -0.011673507060527888
2699 -0.011244035558989098
2799 -0.010844459250993976
2899 -0.010471849788373774
2999 -0.01012400105746833
#예측한 값 확인
for i in range(4):
    print('X:', X[i], "y:", y[i], sigmoid(np.sum(X[i]*w) + b))
X: [0 0] y: [0] 7.0765703582980925e-06
X: [0 1] y: [0] 0.01685800433498835
X: [1 0] y: [0] 0.016892931634238755
X: [1 1] y: [1] 0.9765455899723378

 

 

5-4) XOR 문제

#입력과 출력 데이터 생성 - 같으면 0 다르면 1
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])

#가중치 초기화
w = tf.random.normal([2], 0, 1) #입력 피처가 2개이므로 가중치도 2개
b = tf.random.normal([1], 0, 1) #편향

#학습
for i in range(3000):
    #샘플이 여러 개이므로 에러는 모든 샘플의 에러 합계로 계산
    #에러 합계를 저장할 변수
    error_sum = 0
    #4는 입력 데이터의 개수
    for j in range(4):
        #출력
        output = sigmoid(np.sum(X[j] * w)+ 1*b)
        #오차
        error = y[j][0] - output
        #가중치 업데이트 - 학습률은 0.1
        w = w + X[j] * 0.1 * error
        #편향 업데이트
        b = b + 1 * 0.1 * error
        #에러 값을 추가
        error_sum += error
    #100번 훈련을 할 때 마다 에러의 합계를 출력     
    if i % 100 == 99:
        print(i, error_sum)
99 0.016761352459349288
199 0.0033813217285643127
299 0.000681716543979527
399 0.00013744418256744773
499 2.769994422202604e-05
599 5.5935759204484015e-06
699 1.125229269538508e-06
799 2.149941442652903e-07
899 1.9544921903147383e-08
999 4.746623760709667e-08
1099 4.746623760709667e-08
1199 4.746623760709667e-08
1299 4.746623760709667e-08
1399 4.746623760709667e-08
1499 4.746623760709667e-08
1599 4.746623760709667e-08
1699 4.746623760709667e-08
1799 4.746623760709667e-08
1899 4.746623760709667e-08
1999 4.746623760709667e-08
2099 4.746623760709667e-08
2199 4.746623760709667e-08
2299 4.746623760709667e-08
2399 4.746623760709667e-08
2499 4.746623760709667e-08
2599 4.746623760709667e-08
2699 4.746623760709667e-08
2799 4.746623760709667e-08
2899 4.746623760709667e-08
2999 4.746623760709667e-08

 

# 일정 epoch 이후에 에러값이 변경되지 않음

#예측한 값 확인
for i in range(4):
    print('X:', X[i], "y:", y[i], sigmoid(np.sum(X[i]*w) + b))
X: [0 0] y: [0] 0.5128175691057347
X: [0 1] y: [1] 0.49999997485429043
X: [1 0] y: [1] 0.4871823676059484
X: [1 1] y: [0] 0.47438160794816525
  • 4개의 출력 결과가 비슷
#가중치 와 편향을 출력
print('가중치:', w)
print("편향:", b)
가중치: tf.Tensor([-0.10256328 -0.05128161], shape=(2,), dtype=float32)
편향: tf.Tensor([0.05128151], shape=(1,), dtype=float32)
  • 첫 번째 가중치가 두번째 가중치보다 2배 정도 더 크다.
  • 첫번째 데이터의 영향력이 커져서 첫번째 데이터에 의해서 값이 결정될 가능성이 높다.
  • XOR 문제는 하나의 선으로는 해결할 수 없는 문제
  • 이런 경우는 여러 개의 선을 그어서 해결 - Layer를 여러 개 만들어야 한다.

 

 

 

 

6. MLP

6-1) XOR 문제 해결

import numpy as np
#입력과 출력 데이터 생성 - 같으면 0 다르면 1
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([[0], [1], [1], [0]])

#2개의 완전 연결층을 가진 모델을 생성
#units 는 뉴런의 개수이고 activation은 출력을 계산해주는 함수
#input_shape는 맨 처음 입력 층에서 피처의 모양
model = tf.keras.Sequential([
    tf.keras.layers.Dense(units=2, activation='sigmoid', input_shape=(2, )),
    tf.keras.layers.Dense(units=1, activation='sigmoid')
])

#lr 부분이 에러가 발생하면 learning_rate 로 수정하시면 됩니다.
model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.1), loss='mse')

#모델을 확인
model.summary()

 

# 훈련

#batch_size는 한 번에 연산되는 데이터의 크기(Mini Batch)
history = model.fit(X, y, epochs=10000, batch_size=1)

 

# 결과 확인

result = model.predict(X)
print(result)

 

# 측정치 변화량 (손실의 변화량)

import matplotlib.pyplot as plt
plt.plot(history.history['loss'])

 

 

 

 

 

 

7. Tensorflow Data API

  • Machine Learning을 하는 경우 대규모 데이터 세트를 사용해야 하는 경우가 있는데 이 경우 Data를 Load 하고 Preprocessing 을 수행하는 작업은 번거로운 작업이며 데이터를 어떻게 사용할 것인가 하는 것도 어려운 작업(Multi Threading, Queue, Batch, Prefetch 등) 중 하나

- Tensorflow Data API

  • dataset 객체를 만들고 Data를 읽어올 위치와 변환 방법을 지정하면 이러한 작업을 자동으로 처리 
  • Data API는 텍스트 파일(csv, tsv 등), 고정 길이를 가진 이진 파일, Tensorflow의 TFRecord 포맷을 사용하는 이진 파일에서 데이터를 읽을 수 있다.
    • 관계형 데이터베이스나 Google Big Query 와 같은 NoSQL 데이터베이스에서 데이터를 읽어올 수 있다.
  • Tensorflow에서는 One Hot Encoding 이나 BoW Encoding, embedding 등의 작업을 수행해주는 다양한 Preprocessing 층도 제공

 

 

7-1) 프로젝트

- TF 변환

- TF Dataset(TFDS)

  • dataset을 다운로드할 수 있는 편리한 함수 제공
  • 이미지 넷과 같은 대용량 dataset이 포함되어 있음
dataset = tf.data.Dataset.range(10) #0~9 까지의 데이터를 가지고 데이터셋을 구성
print(dataset)
<_RangeDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>

 

# 대다수의 데이터 셋은 이터레이터 구현

for item in dataset:
    print(item)
tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(4, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64)
tf.Tensor(6, shape=(), dtype=int64)
tf.Tensor(7, shape=(), dtype=int64)
tf.Tensor(8, shape=(), dtype=int64)
tf.Tensor(9, shape=(), dtype=int64)
  • 데이터에 마지막이 batch 개수에 맞지 않는 경우 drop_remainder=True를 설정하면 마지막 배치는 사용하지 않음
dataset = dataset.repeat(5).batch(5)
for item in dataset:
    print(item)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)
tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int64)
tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int64)

 

 

# 데이터 변환

dataset1 = dataset.map(lambda x : x * 2)
for item in dataset1:
    print(item)
tf.Tensor([0 2 4 6 8], shape=(5,), dtype=int64)
tf.Tensor([10 12 14 16 18], shape=(5,), dtype=int64)
tf.Tensor([0 2 4 6 8], shape=(5,), dtype=int64)
tf.Tensor([10 12 14 16 18], shape=(5,), dtype=int64)
tf.Tensor([0 2 4 6 8], shape=(5,), dtype=int64)
tf.Tensor([10 12 14 16 18], shape=(5,), dtype=int64)
tf.Tensor([0 2 4 6 8], shape=(5,), dtype=int64)
tf.Tensor([10 12 14 16 18], shape=(5,), dtype=int64)
tf.Tensor([0 2 4 6 8], shape=(5,), dtype=int64)
tf.Tensor([10 12 14 16 18], shape=(5,), dtype=int64)

 

 

# 원하는 데이터만 추출

dataset = tf.data.Dataset.range(10)
dataset = dataset.filter(lambda x : x % 2 == 1)
for item in dataset:
    print(item)
tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(3, shape=(), dtype=int64)
tf.Tensor(5, shape=(), dtype=int64)
tf.Tensor(7, shape=(), dtype=int64)
tf.Tensor(9, shape=(), dtype=int64)
dataset = tf.data.Dataset.range(10)
dataset = dataset.take(3)
for item in dataset:
    print(item)
tf.Tensor(0, shape=(), dtype=int64)
tf.Tensor(1, shape=(), dtype=int64)
tf.Tensor(2, shape=(), dtype=int64)

 

 

# 데이터 shuffling

  • 경사하강법은 훈련 세트에 있는 데이터들이 독립적이고 동일한 분포를 가졌을 때 최고의 성능을 발휘
  • 셔플링을 할 때 주의할 점은 랜덤 시드를 부여해서 shuffling 되는 순서를 동일하게 만들어야 한다.
dataset = tf.data.Dataset.range(10).repeat(3)
dataset = dataset.shuffle(buffer_size=3, seed=42).batch(7)
for item in dataset:
    print(item)
tf.Tensor([0 3 4 2 1 5 8], shape=(7,), dtype=int64)
tf.Tensor([6 9 7 2 3 1 4], shape=(7,), dtype=int64)
tf.Tensor([6 0 7 9 0 1 2], shape=(7,), dtype=int64)
tf.Tensor([8 4 5 5 3 8 9], shape=(7,), dtype=int64)
tf.Tensor([7 6], shape=(2,), dtype=int64)

 

 

 

- https://tensorflow.org/datasets 에 가면 널리 사용되는 dataset을 다운로드 할 수 있음

  • tfds.load 함수를 호출하면 Data를 다운로드하고 dataset 의 디셔너리로 Data를 리턴

 

- minist 데이터 가져오기

#!pip install tensorflow_datasets

import tensorflow_datasets as tfds

datasets = tfds.load(name="mnist")

#dict 형태로 데이터를 가져옵니다.
print(type(datasets))

#모든 키 확인
print(datasets.keys())

print(type(datasets['train']))
#하나의 데이터도 dict 로 되어 있음
for item in datasets['train']:
    #print(type(item))
    #print(item.keys())
    #print(item['label'])
    plt.imshow(item['image'])
    break

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 딥러닝 _ Keras  (0) 2024.03.20
[Python] 연관 분석  (0) 2024.03.18
[Python] 연관분석 실습 _ 네이버 지식인 크롤링  (4) 2024.03.15
[Python] 감성 분석 실습  (3) 2024.03.14
[Python] 차원 축소  (0) 2024.03.12

- 데이터: http://lib.stat.cmu.edu/datasets/boston
- 데이터에 대한 설명

  • CRIM     per capita crime rate by town
  • ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
  • INDUS    proportion of non-retail business acres per town
  • CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
  • NOX      nitric oxides concentration (parts per 10 million)
  • RM       average number of rooms per dwelling
  • AGE      proportion of owner-occupied units built prior to 1940
  • DIS      weighted distances to five Boston employment centres
  • RAD      index of accessibility to radial highways
  • TAX      full-value property-tax rate per $10,000
  • PTRATIO  pupil-teacher ratio by town
  • B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
  • LSTAT    % lower status of the population
  • MEDV     Median value of owner-occupied homes in $1000's

 MEDV 컬럼이 타겟이고 나머지가 피처

 

 

1. 데이터 가져오기

data_url = "http://lib.stat.cmu.edu/datasets/boston"

raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
print(raw_df.head())
          0      1      2    3      4      5     6       7    8      9     10
0    0.00632  18.00   2.31  0.0  0.538  6.575  65.2  4.0900  1.0  296.0  15.3
1  396.90000   4.98  24.00  NaN    NaN    NaN   NaN     NaN  NaN    NaN   NaN
2    0.02731   0.00   7.07  0.0  0.469  6.421  78.9  4.9671  2.0  242.0  17.8
3  396.90000   9.14  21.60  NaN    NaN    NaN   NaN     NaN  NaN    NaN   NaN
4    0.02729   0.00   7.07  0.0  0.469  7.185  61.1  4.9671  2.0  242.0  17.8
  • 공백 문자 단위로 데이터를 분할해서 읽을 공백 문자가 \s 이고
    +를 추가한 이유는 맨 앞에 공백이 있으면 제거하기 위해서
    뒤의 3개의 데이터도 하나의 행에 포함이 되어야 하는데 뒷줄로 넘어감

 

# 홀수 행에 짝수 행에서 앞의 2개의 열의 데이터를 붙이고 타겟 만들기

#피처 만들기
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])

#타겟 만들기
target = raw_df.values[1::2, 2]

...

 

 

 

2. DataFrame 생성

  • 피처의 이름을 출력하거나 데이터를 전처리할 때 피처의 이름을 사용하기 위함
bostonDF = pd.DataFrame(data, columns=["CRIM", "ZN", "INDUS", "CHAS", "NOX",
                                      "RM", "AGE", "DIS", "RAD", "TAX", "PTRATIO",
                                      "B", "LSTAT"])
bostonDF["PRICE"] = target
bostonDF.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 506 entries, 0 to 505
Data columns (total 14 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   CRIM     506 non-null    float64
 1   ZN       506 non-null    float64
 2   INDUS    506 non-null    float64
 3   CHAS     506 non-null    float64
 4   NOX      506 non-null    float64
 5   RM       506 non-null    float64
 6   AGE      506 non-null    float64
 7   DIS      506 non-null    float64
 8   RAD      506 non-null    float64
 9   TAX      506 non-null    float64
 10  PTRATIO  506 non-null    float64
 11  B        506 non-null    float64
 12  LSTAT    506 non-null    float64
 13  PRICE    506 non-null    float64
dtypes: float64(14)
memory usage: 55.5 KB

 

 

 

3. 타겟과 피처 사이의 선형 관계 파악

  • 산포도를 이용(여러 개의 컬럼 사이의 산포도를 만들 때는 seaborn 의 pairplot 이나 regplot을 이용)
    • 예전에는 숫자가 아닌 컬럼이 있으면 제외하고 출력을 했는데
    • 최신 API에서는 숫자 컬럼에 대해서만 동작하기 때문에 숫자가 아닌 컬럼이 있으면 에러
    • 피처가 많은 경우 불필요한 산포도가 너무 많이 그려져서 알아보기가 어려움
sns.pairplot(bostonDF, height=2.5)
plt.tight_layout()
plt.show()

 

 

#타겟 과의 관계를 파악하고자 하는 feature 리스트를 생성

lm_features = ["RM", "ZN", "INDUS", "NOX", "AGE", "PTRATIO", "LSTAT", "RAD"]

#한 줄에 4개씩 8개의 그래프 영역을 생성
fig, axe = plt.subplots(figsize=(16, 8), ncols=4, nrows=2)
#python에서 list를 순회할 때 인덱스를 같이 사용하는 방법
for i, feature in enumerate(lm_features):
    row = int(i / 4)
    col = i % 4
    sns.regplot(x=feature, y="PRICE", data=bostonDF, ax=axe[row][col])

 

 

# 타겟과 피처의 상관 계수 확인

#상관 계수 구하기
cols = lm_features.copy()
cols.append("PRICE")
cm = np.corrcoef(bostonDF[cols].values.T)
print(cm)
[[ 1.          0.31199059 -0.39167585 -0.30218819 -0.24026493 -0.35550149
  -0.61380827 -0.20984667  0.69535995]
 [ 0.31199059  1.         -0.53382819 -0.51660371 -0.56953734 -0.39167855
  -0.41299457 -0.31194783  0.36044534]
 [-0.39167585 -0.53382819  1.          0.76365145  0.64477851  0.38324756
   0.60379972  0.59512927 -0.48372516]
 [-0.30218819 -0.51660371  0.76365145  1.          0.7314701   0.18893268
   0.59087892  0.61144056 -0.42732077]
 [-0.24026493 -0.56953734  0.64477851  0.7314701   1.          0.26151501
   0.60233853  0.45602245 -0.37695457]
 [-0.35550149 -0.39167855  0.38324756  0.18893268  0.26151501  1.
   0.37404432  0.46474118 -0.50778669]
 [-0.61380827 -0.41299457  0.60379972  0.59087892  0.60233853  0.37404432
   1.          0.48867633 -0.73766273]
 [-0.20984667 -0.31194783  0.59512927  0.61144056  0.45602245  0.46474118
   0.48867633  1.         -0.38162623]
 [ 0.69535995  0.36044534 -0.48372516 -0.42732077 -0.37695457 -0.50778669
  -0.73766273 -0.38162623  1.        ]]
plt.figure(figsize=(15, 7))
sns.set(font_scale=1.5) #화면 크기에 따라 변경 - 폰트 크기

hm = sns.heatmap(cm, cbar=True, annot=True, square=True, fmt='.2f',
                annot_kws={'size':15}, yticklabels=cols, xticklabels=cols)
plt.show()
  • ticklabels는 보여질 문자열
  • annot_kws 는 글자 크기
  • fmt는 포맷으로 숫자의 표시 형식
  • annot는 값 출력 여부

 

 

- 데이터 탐색 결과

  • RM 과 LSTAT 의 PRICE 영향도가 높음
  • RM은 양의 상관 관계를 가지고 LSTAT는 음의 상관 관계를 가짐
  • 단변량(피처가 1개) 선형 회귀를 수행한다면 2개의 피처 중 하나를 이용하는 것이 효율적

 

 

scipy 를 이용한 RM 과 PRICE에 대한 단변량 선형 회귀

from scipy import stats

result = stats.linregress(bostonDF['RM'], bostonDF['PRICE'])

#하나의 속성 출력
print(result.pvalue)

#예측 - 방이 4개 인 경우
print("방이 4개인 경우:", result.slope * 4 + result.intercept)
2.487228871008377e-74
방이 4개인 경우: 1.7378151482826851
  • scipy는 머신러닝을 수행하지만 머신러닝만을 위한 패키지는 아님
  • 피처를 설정할 때 2차원 배열이 아니어도 됨
  • scipy 나 seaborn 의 데이터는 pandas의 자료구조가 가능

 

 

sklearn을 이용한 단변량 선형 회귀

from sklearn.linear_model import LinearRegression

#모델 생성 - 행렬 분해를 이용하는 선형 회귀 모델
slr = LinearRegression()

#타겟과 피처 생성
X = bostonDF[['LSTAT']].values #sklearn 에서는 피처가 2차원 배열
y = bostonDF['PRICE'].values #타겟은 일반적으로 1차원 배열

#훈련
slr.fit(X, y)
  • 통계 패키지는 훈련의 결과를 리턴
  • 머신러닝 패키지는 훈련의 결과를 내부에 저장
  • 다음 데이터를 예측할 때 통계 패키지는 수식을 직접 만들어야 함
  • 머신러닝 패키지는 수식을 만들지 않고 새로운 데이터만 predict에게 제공
  • API를 확인할 때 통계 패키지는 리턴하는 데이터를 확인해야 하고
  • 머신러닝 패키지는 모델을 확인해야 함
#기울기 와 절편 출력
print("기울기:", slr.coef_[0])
print("절편:", slr.intercept_)

#예측
print(slr.coef_[0] * 4 + slr.intercept_)
print(slr.predict([[4]]))
기울기: -0.9500493537579905
절편: 34.5538408793831
30.75364346435114
[30.75364346]

 

 

 

 

 

 

 

 

 

 

[Python] 연관 분석

0ㅑ채
|2024. 3. 18. 12:10

1. 문장의 유사도 측정

1-1) 코사인 유사도

  • 문장을 벡터로 만들어서 거리 측정
    • 벡터의 크기보다는 벡터의 방향성이 얼마나 유사한지에 기반
    • 두 벡터 사이의 사잇각을 구해서 얼마나 유사한지 수치로 적용
  • 유사한 벡터들은 방향이 같고 관련성이 없는 벡터들은 방향이 일치하지도 않고 반대 방향도 아닌 경우

 

1-2) 코사인 유사도 API

  • sklearn.feature_extration.text 패키지를 이용해서 전처리
  • sklearn.metrics.pairwise 패키지의 cosin_similarity 함수를 이용해서 측정
def cos_similarity(v1, v2):
    dot_product = np.dot(v1, v2)
    l2_norm = (np.sqrt(sum(np.square(v1))) * np.sqrt(sum(np.square(v2))))
    similarity = dot_product / l1_norm
    
    return similarity

 

#피처 벡터화

doc_list = ['I love you', 'I like you', 'I love movie']

from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vect_simple = TfidfVectorizer()
feature_vect_simple = tfidf_vect_simple.fit_transform(doc_list)
print(feature_vect_simple)
  (0, 3) 0.7071067811865476
  (0, 1) 0.7071067811865476
  (1, 0) 0.7959605415681652
  (1, 3) 0.6053485081062916
  (2, 2) 0.7959605415681652
  (2, 1) 0.6053485081062916
  • shape : (3, 4)    3개의 문장 4개의 단어
    • 'I'는 자동으로 불용어 처리함
    • love you, like you, love movie 만 존재

# TFidfVectorizer로 transform()한 결과는 Sparse Matrix이므로 Dense Matrix로 변환

feature_vect_dense = feature_vect_simple.todense()
print(feature_vect_dense)

 

# 밀집 행렬로 변환

  • 희소행렬은  1인 것만의 데이터를 가져오기 떄문에 거리 계산이 불가하다. 그래서 밀집행렬 변환은 필수 작업
# TFidfVectorizer로 transform()한 결과는 Sparse Matrix이므로 Dense Matrix로 변환. 
feature_vect_dense = feature_vect_simple.todense()

#첫번째 문장과 두번째 문장의 feature vector  추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)
vect3 = np.array(feature_vect_dense[2]).reshape(-1,)

print(cos_similarity(vect1, vect2))
print(cos_similarity(vect1, vect3))
print(cos_similarity(vect2, vect3))
0.4280460350631186
0.4280460350631186
0.0
  • 유사도는 1이 가장 가깝고 0이 가장 멀다.

# API 활용

  • 거리를 측정할 벡터 2개를 대입
from sklearn.metrics.pairwise import cosine_similarity

similarity_simple_pair = cosine_similarity(feature_vect_simple[0] , feature_vect_simple)
print(similarity_simple_pair)
[[1.         0.42804604          0.42804604]]
  • feature_vect_simple 전체와의 거리
  • feature_vect_simple[n]  n번째 문장과의 거리
  • 희소행렬을 대입하면 밀집행렬로 변환해서 유사도 측정

 

 

1-3) 문서 군집에서 유사도 값이 큰 것끼리 묶기

# 한글 문장의 유사도 측정

### 훈련 데이터 만들기
from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer(min_df = 1)
contents = ['우리 김치찌개 먹으러 가자!',
                   '나는 고기김치찌개 좋아해.',
                    '계란찜도 좋아합니다.'
                   '먹고 나서 잠깐 산책할까?',
                   '산책하면서 라우브 신곡 듣고 싶어.']

#좋아해-좋아합니다. 다른 말로 인식하니 어근추출 해주기
from konlpy.tag import Okt
okt = Okt()
contents_tokens = [okt.morphs(row) for row in contents]
print(contents_tokens)
[['우리', '김치찌개', '먹으러', '가자', '!'], 
['나', '는', '고기', '김치찌개', '좋아해', '.'], 
['계란찜', '도', '좋아합니다', '.', '먹고', '나서', '잠깐', '산책', '할까', '?'], 
['산책', '하면서', '라우', '브', '신곡', '듣고', '싶어', '.']]

 

# 형태소 분석 후 문장 변환

contents_for_vectorize = []

for content in contents_tokens:
    sentence = ''
    for word in content:
        sentence = sentence + ' ' + word
        
    contents_for_vectorize.append(sentence)
    
print(contents_for_vectorize)
[' 우리 김치찌개 먹으러 가자 !', ' 나 는 고기 김치찌개 좋아해 .', ' 계란찜 도 좋아합니다 . 먹고 나서 잠깐 산책 할까 ?', ' 산책 하면서 라우 브 신곡 듣고 싶어 .']
tfidf_vect = TfidfVectorizer()
feature_vect = tfidf_vect.fit_transform(contents_for_vectorize)
print(tfidf_vect.get_feature_names_out())
print(feature_vect.toarray().transpose())
['가자' '계란찜' '고기' '김치찌개' '나서' '듣고' '라우' '먹고' '먹으러' '산책' '신곡' '싶어' '우리' '잠깐'
 '좋아합니다' '좋아해' '하면서' '할까']

new_post = ['우리 김치찌개 먹자']

new_post_tokens = [okt.morphs(row) for row in new_post]
new_post_for_vectorize = []

for content in new_post_tokens:
    sentence=''
    for word in content:
        sentence = sentence + ' ' + word
    new_post_for_vectorize.append(sentence)
print(new_post_for_vectorize)
new_post_vec = tfidf_vect.transform(new_post_for_vectorize)
print(new_post_vec)
[' 우리 김치찌개 먹자']
  (0, 12) 0.7852882757103967
  (0, 3) 0.6191302964899972
cos_similarity_value = cosine_similarity(new_post_vec, feature_vect)
print(cos_similarity_value)
[[0.66914631 0.30147576 0.         0.        ]]
  •   '우리 김치찌개 먹으러 가자 !', ' 나는 고기김치찌개 좋아해 .', 두 문장에서 유사도가 나옴!
#가장 유사한 인덱스
print(np.argmax(cos_similarity_value))
0
result = ['사과', '배', '한라봉', '천혜향', '감귤']
text = result[np.argmax(cos_similarity_value)]
print(text)
사과
  • 원래 챗봇은 {질문 : 답, 질문 : 답 .. } 형태로 해서 유사한 답을 반응하는 방식

 

 

 

2. Word2Vec

  • 단어나 문장을 가지고 다음 단어를 예측하는 것

 

2-1) Continuous Bag of Words

  • 여러개의 단어를 가지고 다음 단어를 예측하는 것
    • 안녕하세요 반갑습니다 나는 오늘 응봉산을 오를 예정입니다.
    • 위의 문장을 학습한 후 (나는 오늘 응봉산에)  ->  '오를' 을 예측

 

2-2) Skip-Gram

  • 특정 단어를 가지고 다음 단어나 문장을 예측하는 것 
  • window size가 있어서 현재 위치에서 몇 개를 예측할 것인지 설정
    • 안녕하세요 반갑습니다 나는 오늘 응봉산을 오를 예정입니다.
    • window size가 1이면
      • 나는  ->  오늘
      • 나는  ->  반갑습니다
    • window size가 2라면
      • 나는  ->  오늘
      • 나는  ->  반갑습니다.
      • 나는  ->  신사역에
      • 나는  ->  안녕하세요

 

2-3) API

  • gensim 패키지Word2Vec 클래스로 구현
  • similarity 함수를 이용해서 2개의 단어의 유사도를 측정해주고 most_similar 함수를 이용해서 가장 유사한 단어를 리턴,
    • positive 인수와 negative 인수를 이용해서 단어간 관계도 찾을 수 있다.

 

2-4) 네이버 지식인 검색 결과 가장 유사한 단어 찾기

2024.03.15 - [Python] - [Python] 네이버 지식인 크롤링 _ 연관분석 실습

 

[Python] 네이버 지식인 크롤링 _ 연관분석 실습

1. 데이터 가져오기 # 고양이 질문 데이터 from bs4 import BeautifulSoup #HTML 파싱 import urllib #검색어 인코딩 import time #슬립 사용 import requests #HTML 가져오기 from tqdm import tqdm_notebook #크롤링 결과 저장할

yachae4910.tistory.com

 

 

 

3. 연관 규칙 분석

  • 원본 데이터에서 대상들의 연관된 규칙을 찾는 무방향성 데이터마이닝 기법
  • 하나의 거래나 사건에 포함된 항목 간의 관련성을 파악해서 둘 이상의 항목들로 구성된 연관성 규칙을 찾는 탐색적인 방법
  • 트랜잭션을 대상으로 트랜잭션 내의 연관성을 분석해서 상품 거래의 규칙이나 패턴을 찾아 상품 간의 연관성을 도출해내는 분석 방법

-  장점

  • 조건 반응 표현식의 결과를 이해하기 쉽다. 
  • 편리한 분석 데이터 형태

- 단점

  • 계산 과정에서 연산 많이 수행

- 순차분석

  • 연관 규칙 분석은 무방향성이라서 A를 산 사람이 B를 살 확률을 확인하는 것
  • 순차분석은 A를 사고 B를 살 확률
  • 시간의 개념이 있다.

 

3-1) 과정

  • 거래 내역 데이터를 가지고 트랜잭션 객체 생성
  • 품목과 트랜잭션 ID를 관찰
  • 지지도, 신뢰도, 향상도를 이용한 연관 규칙 발견
  • 시각화
  • 업무 적용

 

3-2) 평가지표

- Support(지지도)

  • 전체 거래 건수 중에서 X와 Y가 모두 포함된 건수의 비
    • X와 Y 모두 포함한 거래 건수 / 전체 거래 건수
  • 전체 품목에서 관련 품목의 거래 확률
  • 지지도가 낮다는 것은 해당 규칙이 자주 발생하지 않는다는 의미

 

- Confidence(신뢰도)

  • X를 구매한 거래 중에서 Y를 포함하는 거래의 건수 비
  • X와 Y 모두 포함한 거래 건수 / X를 포함하는 거래 건수

 

- Lift(향상도)

  • 신뢰도를 지지도로 나눈 값
  • 두 항목의 독립성 여부를 판단하는 수치
  • 1이면 서로 독립, 1보다 작으면 음의 상관관계, 1보다 크면 양의 상관관계 의미
    값이 클수록 연관성이 높은 것

 

 

3-3) API

- apyori 패키지 apriri 모듈

  • min_support
  • min_confidence
  • min_lift

- 일반 텍스트 데이터에 적용 가능

 

 

3-4) network 패키지

  • 그래프를 다루기 위한 패키지
  • 그래프를 만드는 클래스
    • Graph:  방향이 없음
    • DiGraph: 방향성 그래프 
    • node와 edge로 구성

2024.03.15 - [Python/Python 실전편] - [Python] 연관분석 실습 _ 손흥민

 

[Python] 연관분석 실습 _ 손흥민

설치 !pip install apyori 1. 데이터 읽어오기 df = pd.read_csv("./python_machine_learning-main/data/tweet_temp.csv") df.head() #한글 정제 함수 import re # 한글만 추출 def text_cleaning(text): hangul = re.compile('[^ ㄱ-ㅣ가-힣]+') #

yachae4910.tistory.com

 

 

 

 

4. 추천 시스템

4-1) 추천 시스템 알고리즘

- 콘텐츠 기반 필터링

- 협업 필터링

  • 최근접 이웃 협업 필터링
  • 잠재 요인 협업 필터링

초창기에는 콘텐츠 기반 필터링이나 최근접 이웃 협업 필터링을 주로 이용햇는데 넷플릭스의 추천 시스템이 행렬 분해 기법을 이용한 잠재 요인 협업 필터링을 사용하면서 최근에는 잠재 요인 협업 필터링을 주로 이용하고 두가지 알고리즘을 혼합해서 사용

 

 

4-2) 콘텐츠 기반 필터링

  • 사용자가 특정한 아이템을 선호하는 경우 그 아이템과 비슷한 콘텐츠를 가진 다른 아이템을 추천하는 방식
  • 어떤 영화를 보고 추천하거나 높은 평점을 제시한 경우 그 영화의 장르나 배우, 감독 등을 파악해서 유사한 다른 영화 추천

2024.03.15 - [Python/Python 실전편] - [Python] 연관분석 실습 _ 영화 콘텐츠 기반 필터링 추천 시스템

 

[Python] 연관분석 실습 _ 영화 콘텐츠 기반 필터링 추천 시스템

데이터 https://www.kaggle.com/tmdb/tmdb-movie-metadata 1. 데이터 읽어오기 movies =pd.read_csv('./python_machine_learning-main/data/tmdb_5000_movies.csv') print(movies.shape) movies.head() # 데이터 생성 movies_df = movies[['id','title', 'genr

yachae4910.tistory.com

 

 

4-3) 협업 필터링

  • 사용자가 아이템에 매긴 평점 정보나 상품 구매 이력같은 사용자 행동 양식만을 기반으로 추천
  • 사용자-아이템 평점 매트릭스와 같은 축적된 사용자 행동 데이터를 기반으로 사용자가 아직 평가하지 않은 아이템을 예측 평가하는 것
  ITEM 1 ITEM 2 ITEM 3 ITEM 4
USER 1 3   3  
USER 2 4 2   3
USER 3   1 2 2

 

  • USER  1은 USER2 와 USER3의 선택에서 겹치는게 있다. (ITEM1, ITEM3)
    => ITEM 4 추천

 

- 최근접 이웃 협업 필터링

  • 사용자 기반: 나와 유사한 고객의 다음 상품도 구매하도록 만듦
    • 사용자 A는 B와 다크나이트, 인터스텔라, 엣지오브투모로우에서 비슷한 평점을 줬기 때문에 
    • 사용자 A에게 프로메테우스를 추천해준다.
    • 단점: 로그인해서 평점을 축적해야 한다.

  • 아이템 기반: 이 상품을 선택한 다른 고객들은 다음 상품도 구매
    • 로그인 하지 않아도 '이 책을 구입하신 분들이 함께 산 책' 등을 추천해줄 수 있다. 
    • 그래서 많은 기업들이 선호한다. 쿠팡, 당근 등

 

- 잠재 요인 협업 필터링

  • 사용자-아이템 평점 매트릭스 속에 숨어있는 잠재 요인을 추출해서 추천 예측을 할 수 있게 하는 기법
  • 최근접 이웃 협업 필터링을 실무에서 사용하기 어려운 이유는 사용자와 유사한 아이템을 구매한 사용자를 찾는 것은 쉽지 않다. 
    • 쇼핑몰이나 넷플릭스 같은 OTT 기업의 데이터는 굉장히 많아서 내가 산 상품을 전부 산, 또는 내가 본 영화를 전부 본 유저를 찾기는 쉽지 않다. 

  • 원본 데이터와 100% 같지는 않다. 
  • 원본 행렬을 가지고 주성분분석을 해서 작은 단위의 밀집 행렬 3개를 만들고 (SV, MNF 등)
  • 다시 내적(product)하면 값이 채워짐. 

 

2024.03.18 - [Python/Python 실전편] - [Python] 연관분석 실습 _ 아이템 기반 추천 시스템

 

[Python] 연관분석 실습 _ 아이템 기반 추천 시스템

- movies.csv: 영화 정보 데이터 - ratings.csv: 평점정보 데이터 1. 데이터 읽어오기 movies = pd.read_csv('./python_machine_learning-main/data/movielens/movies.csv') ratings = pd.read_csv('./python_machine_learning-main/data/movielens/rati

yachae4910.tistory.com

 

 

 

 

5. 행렬 분해

- 하나의 행렬을 특정한 구조를 가진 2개 이상의 행렬의 곱으로 표현하는 것

- 선형 방정식으 해를 구하거나 행렬 계산을 효율적으로 수행하기 위해서 사용

데이터 전처리에서는 None 값을 채우는 용도로 사용하기도 함

 

 

5-1) 알고리즘

- SVD(Singular Value Decompsition) : 특잇값 분해

- NMF(Non-Negative Matrix Fatorization) : 음수 미포함 행렬 분해

- SGD(Stochastic Gradient Descent) : 경사하강법

- ALS(Alterating Least Square)

 

 

5-2) SVD

- N * M 크기의 행렬 A를 3개의 행렬 곱으로 나타내는 것

  • A(M * N) = U(M * N) 시그마(M * N) V(N * N)의 역행렬

 

- 제약조건

  • U와 V는 정방행렬 ( 행과 열의 개수가 같은 행렬 )
  • U와 V는 직교행렬 ( 자신의 역행렬과 곱을 하면 단위 행렬이 나옴 )
from numpy.linalg import svd
A = np.array([[3, -1], [1, 3], [1, 1]]) 
U, S, VT = svd(A)

print(U) #행이 3개이므로 3*3 행렬
print(VT) #열이 2개이므로 2*2 행렬
print(S) #2개의 값
[[-4.08248290e-01  8.94427191e-01 -1.82574186e-01]
 [-8.16496581e-01 -4.47213595e-01 -3.65148372e-01]
 [-4.08248290e-01 -1.94289029e-16  9.12870929e-01]]

[3.46410162 3.16227766]

[[-0.70710678 -0.70710678]
 [ 0.70710678 -0.70710678]]

 

 

# 3개 행렬의 곱

  • U @ S @ V는 안됨
  • 사이즈가 다르기 때문
temp = np.diag(S, 0)
print(temp)
[[3.46410162 0.        ]
 [0.         3.16227766]]
  • 2*2가 만들어진다.
  • 그래서 옆으로 하나 더 밀어야 함
temp = np.diag(S, 1)[:, 1:]

U @ temp @ VT
array([[ 3., -1.],
          [ 1.,  3.],
          [ 1.,  1.]])

 

 

 

5-3) NMF 

- 음수 미포함 행렬 분해

  • 음수를 포함하지 않는 행렬 V를 음수 포함하지 않는 행렬 W와 H의 곱으로 분해

X = WH

  • W 는 X와 동일한 모양으로 생성
  • H는 열의 개수만큼의 행을 가지게 되고 데이터의 demension만큼의 열을 소유

 

- W는 가중치 행렬 (Weight Matrix)라고 하고 H는 특성 행렬(Feature Matrix)이라고 함

- W는 카테고리 특성의 관계를 나타내는 것이고 특성 행렬은 원래 특성에 대비한 새로운 특성에 대한 관계를 나타냄

- 차원 축소나 새로운 차원을 생성할 때도 이용

 

 

 

5-4) 경사 하강법

  • GPU를 사용하는 것이 가능하고 결측치에 상관없이 사용 가능
  • 이전 2가지 방법은 결측치가 있으면 0으로 채워서 수행해야 함

 

 

 

5-5) ALS (Alternating Least Square)

  • 경사 하강법은 2개의 동시에 변경하면서 최적의 값을 찾아가지만 ALS는 하나의 행렬은 고정하고 다른 하나의 행렬만 변경하면 최적의 값을 찾아가는 방식
  • 결측치가 있으면 안되기 때문에 결측치가 있으면 0으로 채워서 사용해야 함

 

 

5-6) 사용

  • 행렬 분해에는 SVD가 자주 사용되지만 사용자-평점데이터를 가지고 행렬 분해를 하고자 하는 경우에는 None 값이 너무 많기 때문에 SGD나 ALS를 주로 이용
  • 행렬분해를 이용해서 시청하지 않은 또는 구매하지 않은 상품에 대한 점수를 예측하는 방식을 잠재요인 협업 추천이라고 함
    • 넷플릭스가 영화 추천에 사용하면서 유명해진 방식
  • 행렬 분해 알고리즘을 이용해서 추천 시스템을 구축해주는 패키지 중의 하나로 scikit-surprise가 있고, 그 이외에 딥러닝을 이용하는 방식도 있다.
  • scikit-surprise 패키지는 Dataset 클래스를 이요해서만 데이터 로딩이 가능하고 데이터가 반드시 userid, itemid, rating의 형태를 갖는 row로 구성되어 있어야 함
    • 일반 파일로 만들어진 데이터가 있다면 row 형태를 맞추고 surprise.Reader라는 클래스를 이용해서 Dataset의 형태로 변경해야 가능
  • 요즘은 이 방식들을 잘 사용하지 않고 lightFM 같은 딥러닝 모델들을 이용하는 경우가 많다.
    • 추천 시스템을 만들고자 하는 경우는 여러 모델을 사용해보는 것이 좋다.
    • 일반적으로 추천 시스템은 평가를 하기가 어렵기 때문에 여러 모델을 사용해서 추천 목록을 만드는 것이 좋다.

 

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 딥러닝 _ Keras  (0) 2024.03.20
[Python] 딥러닝 _ Tensorflow  (0) 2024.03.20
[Python] 연관분석 실습 _ 네이버 지식인 크롤링  (4) 2024.03.15
[Python] 감성 분석 실습  (3) 2024.03.14
[Python] 차원 축소  (0) 2024.03.12

- movies.csv: 영화 정보 데이터

- ratings.csv: 평점정보 데이터

 

1. 데이터 읽어오기

movies = pd.read_csv('./python_machine_learning-main/data/movielens/movies.csv')
ratings = pd.read_csv('./python_machine_learning-main/data/movielens/ratings.csv')
print(movies.shape)
print(ratings.shape)
(9742, 3)
(100836, 4)



 

 

# ratings에서 timestamp 제거

ratings = ratings[['userId', 'movieId', 'rating']]
ratings_matrix.head(3)

 

 

 

- 추천 시스템 데이터 형태

  • user id별로 상품 id가 펼쳐지거나, 상품 id별로 user id가 펼쳐져야 한다.
  • 둘중 하나를 인덱스로 보내야 함 

# user id를 인덱스로 해서 movie id 별로 평점을 확인할 수 있도록 변경

ratings_matrix = ratings.pivot_table('rating', index='userId', columns='movieId')
  • 컬럼에 영화 제목이 아니라 movie id가 출력됨
  • movie id를 영화 제목으로 변경

 

# movies와 ratings를 movieid 컬럼을 기준으로 join(merge)

# title 컬럼을 얻기 이해 movies 와 조인 수행
rating_movies = pd.merge(ratings, movies, on='movieId')
rating_movies.head(3)

 

 

# pivot

# columns='title' 로 title 컬럼으로 pivot 수행. 
ratings_matrix = rating_movies.pivot_table('rating', index='userId', columns='title')

# NaN 값을 모두 0 으로 변환
ratings_matrix = ratings_matrix.fillna(0)
ratings_matrix.head(3)
  • 행렬을 펼쳐낼 때 유저별로 유사도를 계산할지, 영화별로 유사도를 계산할지에 따라서 펼쳐내는 방향이 달라진다.
  • 유사도를 계산할 항목이 인덱스로 존재하면 됨

 

 

 

2. 영화간 유사도 산출

# 아이템 기반으로 유사도 측정. 아이템인 영화제목이 index가 되도록 설정

ratings_matrix_T = ratings_matrix.transpose()
ratings_matrix_T.head(3)

 

 

# 코사인 유사도

#영화를 기준으로 코사인 유사도 산출
from sklearn.metrics.pairwise import cosine_similarity

item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)
print(item_sim)
[[1.         0.         0.         ... 0.32732684 0.         0.        ]
 [0.         1.         0.70710678 ... 0.         0.         0.        ]
 [0.         0.70710678 1.         ... 0.         0.         0.        ]
 ...
 [0.32732684 0.         0.         ... 1.         0.         0.        ]
 [0.         0.         0.         ... 0.         1.         0.        ]
 [0.         0.         0.         ... 0.         0.         1.        ]]

 

 

# 영화 제목 붙이기

# cosine_similarity() 로 반환된 넘파이 행렬을 영화명을 매핑하여 DataFrame으로 변환
item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns,
                          columns=ratings_matrix.columns)
print(item_sim_df.shape)
item_sim_df.head(3)

 

 

# Godfather, The(1972)와 가장 유사한 영화 5개 추출

item_sim_df["Godfather, The (1972)"].sort_values(ascending=False)[:6]
title
Godfather, The (1972)                        1.000000
Godfather: Part II, The (1974)               0.821773
Goodfellas (1990)                            0.664841
One Flew Over the Cuckoo's Nest (1975)       0.620536
Star Wars: Episode IV - A New Hope (1977)    0.595317
Fargo (1996)                                 0.588614
Name: Godfather, The (1972), dtype: float64

 

 

 

 

3. 아이템 기반 최근접 이웃 협업 필터링으로 개인화된 영화 추천

  • 아이템 기반의 영화 유사도 데이터는 모든 사용자의 평점을 기준으로 영화의 유사도를 생성했기 때문에 영화를 추천할 수는 있지만 개인의 취향을 전혀 반영하지 않음
  • 개인화된 영화 추천은 유저가 아직 관람하지 않은 영화를 추천해야 함
  • 아직 관람하지 않은 영화에 대해서 아이템 유사도와 기존에 관람한 영화의 평점데이터를 기반으로 모든 영화의 평점을 예측하고 그중에서 높은 예측 평점을 가진 영화를 추천

- 계산식: 사용자가 본 영화에 대한 실제 평점과 다른 모든 영화와의 코사인 유사도를 내적 곱 하고 그 값을 전체 합으로 나눔

 

 

# 사용자 별로 평점을 예측해주는 함수

def predict_rating(ratings_arr, item_sim_arr ):
    ratings_pred = ratings_arr.dot(item_sim_arr)/ np.array([np.abs(item_sim_arr).sum(axis=1)])
    return ratings_pred

 

 

#개인화된 예측 평점 확인

ratings_pred = predict_rating(ratings_matrix.values , item_sim_df.values)
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,
                                   columns = ratings_matrix.columns)
ratings_pred_matrix.head(3)

 

 

# 평가

from sklearn.metrics import mean_squared_error

# 사용자가 평점을 부여한 (0이 아닌) 영화에 대해서만 예측 성능 평가 MSE 를 구함. 
def get_mse(pred, actual):
    pred = pred[actual.nonzero()].flatten()
    actual = actual[actual.nonzero()].flatten()
    return mean_squared_error(pred, actual)

print('아이템 기반 모든 인접 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))
아이템 기반 모든 인접 이웃 MSE:  9.895354759094706
  • MSE가 너무 높다. 
  • 평점이 5.0인데, 제곱근인 3정도 차이가 난다는 건 추천 시스템으로 사용하기 어렵다는 뜻

 

 

 

4. 추천 수정

  • 현재는 모든 영화와의 유사도를 이용해서 평점을 예측했는데 모든 영화보다는 유저가 본 영화 중 유사도가 가장 높은 영화 몇개를 이용해서 예측하는 것이 더 나을 가능성이 높다.
# 유사도를 계산할 영화의 개수를 배개변수로 추가
def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
    # 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화
    pred = np.zeros(ratings_arr.shape)

    # 사용자-아이템 평점 행렬의 열 크기만큼 Loop 수행 
    for col in range(ratings_arr.shape[1]):
        # 유사도 행렬에서 유사도가 큰 순으로 n개 데이터 행렬의 index 반환
        top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]
        # 개인화된 예측 평점을 계산
        for row in range(ratings_arr.shape[0]):
            pred[row, col] = item_sim_arr[col, :][top_n_items].dot(ratings_arr[row, :][top_n_items].T) 
            pred[row, col] /= np.sum(np.abs(item_sim_arr[col, :][top_n_items]))        
    return pred

 

# 수정 후 성능

ratings_pred = predict_rating_topsim(ratings_matrix.values , item_sim_df.values, n=20)
print('아이템 기반 인접 TOP-20 이웃 MSE: ', get_mse(ratings_pred, ratings_matrix.values ))


# 계산된 예측 평점 데이터는 DataFrame으로 재생성
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index= ratings_matrix.index,
                                   columns = ratings_matrix.columns)
아이템 기반 인접 TOP-20 이웃 MSE:  3.695009387428144
  • 아이템이나 유저를 가지고 유사도를 측정할 때 모든 데이터를 사용하는 것보다는 유사도가 높은 데이터 몇개를 이용해서 예측을 하는 것이 성능이 좋은 경우가 많다.
#9번째 유저에 영화 추천
user_rating_id = ratings_matrix.loc[9, :]
user_rating_id[ user_rating_id > 0].sort_values(ascending=False)[:10]
title
Adaptation (2002)                                                                 5.0
Citizen Kane (1941)                                                               5.0
Raiders of the Lost Ark (Indiana Jones and the Raiders of the Lost Ark) (1981)    5.0
Producers, The (1968)                                                             5.0
Lord of the Rings: The Two Towers, The (2002)                      5.0
Lord of the Rings: The Fellowship of the Ring, The (2001)       5.0
Back to the Future (1985)                                                         5.0
Austin Powers in Goldmember (2002)                                   5.0
Minority Report (2002)                                                            4.0
Witness (1985)                                                                    4.0
Name: 9, dtype: float64

 

 

# 유저가 보지 않은 영화 목록을 리턴하는 함수

def get_unseen_movies(ratings_matrix, userId):
    # userId로 입력받은 사용자의 모든 영화정보 추출하여 Series로 반환함. 
    # 반환된 user_rating 은 영화명(title)을 index로 가지는 Series 객체임. 
    user_rating = ratings_matrix.loc[userId,:]
    
    # user_rating이 0보다 크면 기존에 관람한 영화임. 대상 index를 추출하여 list 객체로 만듬
    already_seen = user_rating[ user_rating > 0].index.tolist()
    
    # 모든 영화명을 list 객체로 만듬. 
    movies_list = ratings_matrix.columns.tolist()
    
    # list comprehension으로 already_seen에 해당하는 movie는 movies_list에서 제외함. 
    unseen_list = [ movie for movie in movies_list if movie not in already_seen]
    
    return unseen_list

 

 

# 유저가 보지 않은 영화 목록에서 예측 평점이 높은 영화 제목을 리턴하는 함수

def recomm_movie_by_userid(pred_df, userId, unseen_list, top_n=10):
    # 예측 평점 DataFrame에서 사용자id index와 unseen_list로 들어온 영화명 컬럼을 추출하여
    # 가장 예측 평점이 높은 순으로 정렬함. 
    recomm_movies = pred_df.loc[userId, unseen_list].sort_values(ascending=False)[:top_n]
    return recomm_movies

 

# 유저가 보지 않은 영화 목록 추출

# 사용자가 관람하지 않는 영화명 추출   
unseen_list = get_unseen_movies(ratings_matrix, 9)

# 아이템 기반의 인접 이웃 협업 필터링으로 영화 추천 
recomm_movies = recomm_movie_by_userid(ratings_pred_matrix, 9, unseen_list, top_n=10)

# 평점 데이타를 DataFrame으로 생성. 
recomm_movies = pd.DataFrame(data=recomm_movies.values,index=recomm_movies.index,columns=['pred_score'])
recomm_movies

 

 

 

 

5. 작업 과정 정리

- 각 영화 간의 유사도 측정

 

- 유저가 본 영화의 평점을 기반으로 해서 유사도가 높은 20개의 영화를 추출하고, 그 영화들의 평점으로 않은 영화의 평점을 예측