
벌써 3주차를 하게 되네요. 하면서 느낀 점은 이거를 실무에서 쓰려면 공부를 많이 많이.. 해야 할 것 같아요.
이틀 전에 공부한 회귀 중에 릿지? 랏쏘? 이런게 있는거 같은데 벌써 까먹은거면 보면..
차라리 허깅페이스를 통해서 이미 학습된 모델을 잘 이용하는게 좋을 것 같다는 생각도 듭니다..
딥러닝이 궁금해서 하긴 하는데 차트며.. 공식이며.. 생각보다 어렵고 이거를 이해하고 활용해서 내가 필요에 의해 쓸 수 있을까?? 라는 생각이 드네요.
이 시간에 알고리즘이나 AWS, 아키텍처 같은 공부를 하는게 더 낫지 않을까 라는 생각도 들지만..
뭐.. 그래도 잘 따라가면 얻는게 없지는 않을 것 같습니다. 조금이라도 아는게 있으면 조금 더 관심이 갈 수 있으니까요. 아마..?
깊게 고민하기 보다는 가볍게 실습을 위주로 하며, 아~ 그렇구나.. 하는 생각으로 가볍게 읽어봐야겠어요.
느낀 점#
로지스틱 회귀는 쓸 데가 있어 보입니다.. 아래 내용 정리를 하면서 사용자 특성(성별, 나이, 이용 시간)을 활용해 웹 사이트의 또는 유튜브 광고 클릭율을
예측하는데 쓰이는 것 같아요.
실제로 넷플릭스, 아마존, 구글 같은 회사들이 사용자 정보를 바탕으로 사용자 행동을 예측하고 개인화 서비스를 제공한다고 합니다.
그렇다고 합니다.
추가로 알고리즘 팀과 협업할 때 아리송한 언어들을 공부하니까 조금 도움이 되는 것 같습니다. :D..
기본 숙제(필수)#
Ch.04(04-1) 2번 문제 풀고, 풀이 과정 설명하기
문제: 로지스틱 회귀가 이진 분류에서 확률을 출력하기 위해 사용하는 함수는?
시그모이드 함수, 소프트맥스 함수
시그모이드 함수는 이진 분류에서 사용하며, 소프트맥스 함수는 다중 분류에서 사용합니다.
일반적으로 클래스 2개인 경우는 시그모이드, 클래스가 3개 이상인 경우는 소프트맥스 함수를 사용하는게 좋습니다.
소프트 맥스는 1에 수렴하는 확률을 표시하기 때문에 사용자 입장에서 더 이해하기 쉽습니다.
내용 정리#
04-1 로지스틱 회귀#
로지텍 럭키 박스처럼 상자안에 확률적으로 도미 또는 빙어가 들어가 있습니다.
럭키백의 확률#
길이, 높이, 두께, 대각성 길이, 무게를 사용하여 도미 또는 빙어를 맞춰봅니다.
k-최근접 이웃의 확률을 통해서 위의 데이터로 맞춰봅니다. 먼저 샘플 데이터를 가져옵니다.
1
2
3
4
| import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
fish.head()
|
index | Species | Weight | Length | Diagonal | Height | Width |
---|
0 | Bream | 242.0 | 25.4 | 30.0 | 11.52 | 4.02 |
1 | Bream | 290.0 | 26.3 | 31.2 | 12.48 | 4.3056 |
2 | Bream | 340.0 | 26.5 | 31.1 | 12.3778 | 4.6961 |
3 | Bream | 363.0 | 29.0 | 33.5 | 12.73 | 4.4555 |
4 | Bream | 430.0 | 29.0 | 34.0 | 12.444 | 5.134 |
판다스를 이용해 샘플 데이터를 읽어옵니다. 위와 같이 샘플 데이터를 볼 수 있습니다. 실제로는 해당 링크에 가면 더 많은 데이터를 볼 수 있습니다.
1
| print(pd.unique(fish['Species']))
|
실제로 스페셜 열의 유니크 값을 출력해보면 [‘Bream’ ‘Roach’ ‘Whitefish’ ‘Parkki’ ‘Perch’ ‘Pike’ ‘Smelt’] 이라는 항목들이 있는 것을 볼 수 있습니다.
to_numpy()
메서드로 넘파이 배열로 바꾸어 fish_input에 5개의 특성을 나오도록 입력 데이터를 생성합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
print(fish_input[:10])
[[242. 25.4 30. 11.52 4.02 ]
[290. 26.3 31.2 12.48 4.3056]
[340. 26.5 31.1 12.3778 4.6961]
[363. 29. 33.5 12.73 4.4555]
[430. 29. 34. 12.444 5.134 ]
[450. 29.7 34.7 13.6024 4.9274]
[500. 29.7 34.5 14.1795 5.2785]
[390. 30. 35. 12.67 4.69 ]
[450. 30. 35.1 14.0049 4.8438]
[500. 30.7 36.2 14.2266 4.9594]]
|
필요한 데이터를 준비합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
from sklearn.model_selection import train_test_split
# 훈련 세트와 테스트 세트로 나누기
train_input, test_input, train_target, test_target = train_test_split(
fish_input, fish_target, random_state=42)
from sklearn.preprocessing import StandardScaler
# StandardScaler 클래스를 사용해 훈련 세트와 테스트 세트를 표준화 전처리
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
|
위의 데이터로 훈련 세트와 테스트 세트의 점수를 확인합니다.
1
2
3
4
5
6
7
| from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier(n_neighbors=3)
kn.fit(train_scaled, train_target)
print(kn.score(train_scaled, train_target)) # 0.8907563025210085
print(kn.score(test_scaled, test_target)) # 0.85
|
기존의 Species 의 타깃 데이터에 2개 이상의 클래스가 포함된 문제를 다중 분류라고 부릅니다.
1
2
3
4
5
6
7
8
9
10
| import numpy as np
proba = kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4))
[[0. 0. 1. 0. 0. 0. 0. ]
[0. 0. 0. 0. 0. 1. 0. ]
[0. 0. 0. 1. 0. 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]
[0. 0. 0.6667 0. 0.3333 0. 0. ]]
|
위와 같이 predict_proba
메서드를 이용하여 classes_ 속성과 동일하게 알파벳 순서로 타깃의 확률에 대한 정보를 확인할 수 있습니다.
로지스틱 회귀#
로지스틱 회귀는 이름은 회귀이지만 분류 모델입니다.
핵심 개념
- 선형 회귀와 달리 0과 1 사이의 확률값을 출력
- 시그모이드 함수를 사용해서 선형 결합을 확률로 변환
- 주로 이진 분류(예: 스팸/정상 메일, 합격/불합격)에 사용
웹 서비스의 클릭률 예측, 의료 진단, 마케팅 타겟팅 등에 널리 사용된다고 합니다..
시그모이드 그래프를 그려보면 아래와 같습니다.
1
2
3
4
5
6
7
8
9
10
| import numpy as np
import matplotlib.pyplot as plt
z = np.arange(-5, 5, 0.1)
phi = 1 / (1 + np.exp(-z))
plt.plot(z, phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()
|

로지스틱 회귀로 이진 분류 수행하기 위해서는 불리언 인덱싱을 이용하여 판단할 수 있습니다.
아래와 같이 True인 항목만 출력되는 것을 확인할 수 있습니다.
1
2
| char_arr = np.array(['A', 'B', 'C', 'D', 'E'])
print(char_arr[[True, False, True, False, False]]) # ['A' 'C']
|
위와 같이 도미(Bream)와 빙어(Smelt) 행만 골라서 도미와 빙어 데이터를 고릅니다.
이를 이용해 로지스틱 회귀를 이용해 샘플데이터가 빙어인지 도미인지 확인해봅니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| bream_smelt_indexes = (train_target == 'Bream') | (train_target == 'Smelt')
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_target[bream_smelt_indexes]
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)
# 5개의 샘플의 도미 빙어 판단
print(lr.predict(train_bream_smelt[:5]))
# ['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']
# 5개의 샘플의 확률 판단(알파벳 순서이므로 도미가 먼저)
print(lr.predict_proba(train_bream_smelt[:5]))
[[0.99760007 0.00239993]
[0.02737325 0.97262675]
[0.99486386 0.00513614]
[0.98585047 0.01414953]
[0.99767419 0.00232581]]
|
위와 같이 샘플 5개 중 4개가 도미로 예측했습니다.
아래는 로지스틱 회귀가 학습한 계수를 확인할 수 있습니다.
공식같은게 있는데.. 이걸 돌리면 됩니다. 아래 주석을 참조하여 로지스틱 회귀의 작동 원리??(점점 멘붕..)를 살펴봅니다.
1
2
3
4
5
6
7
8
9
10
11
12
| print(lr.coef_, lr.intercept_)
# [[-0.40451732 -0.57582787 -0.66248158 -1.01329614 -0.73123131]] [-2.16172774]
# 위 각 속성에 맞게 곱한 다음.. 마지막에 마지막 꺼 빼기..를 하는게 아래의 decision_function 함수의 역할..!
decisions = lr.decision_function(train_bream_smelt[:5])
print(decisions)
# [-6.02991358 3.57043428 -5.26630496 -4.24382314 -6.06135688]
# 위의 값(z)을 시그모이드 함수? 아래 라이브러리를 통해 expit하면 시그모이드 값을 알 수 있음..
from scipy.special import expit
print(expit(decisions))
# [0.00239993 0.97262675 0.00513614 0.01414953 0.00232581]
|
결론만 놓고 보면 위의 expit을 통해서 가장 위의 5개의 샘플 중에 도미는 0에 가까워지고 빙어는 1에 가까워집니다.
로지스틱 회귀로 다중 분류 수행하기#
LogisticRegression 클래스는 기본적으로 반복적인 알고리즘을 사용합니다. max_iter 매개변수에서 반복 횟수를 지정(기본 100)합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| lr = LogisticRegression(C=20, max_iter=1000)
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) # 0.9327731092436975
print(lr.score(test_scaled, test_target)) # 0.925
print(lr.predict(test_scaled[:5]))
# ['Perch' 'Smelt' 'Pike' 'Roach' 'Perch']
proba = lr.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=3))
print(lr.classes_)
# 5개의 샘플에 대해 7개의 물고기? 종의 확률 값
['Bream' 'Parkki' 'Perch' 'Pike' 'Roach' 'Smelt' 'Whitefish']
[[0. 0.014 0.842 0. 0.135 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.934 0.015 0.016 0. ]
[0.011 0.034 0.305 0.006 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
|
소프트맥스 함수: 여러 개의 선형 방적식의 출력값을 0~1 사이로 압축하고 전체 합이 1이 되도록 만듬. 이를 위해 지수 함수를 사용하기 때문에
정규화된 지수 함수라고도 부름
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))
[[ -6.51 1.04 5.17 -2.76 3.34 0.35 -0.63]
[-10.88 1.94 4.78 -2.42 2.99 7.84 -4.25]
[ -4.34 -6.24 3.17 6.48 2.36 2.43 -3.87]
[ -0.69 0.45 2.64 -1.21 3.26 -5.7 1.26]
[ -6.4 -1.99 5.82 -0.13 3.5 -0.09 -0.7 ]]
from scipy.special import softmax
proba = softmax(decision, axis=1)
print(np.round(proba, decimals=3))
# 행의 값을 더하면 1에 수렴..
[[0. 0.014 0.842 0. 0.135 0.007 0.003]
[0. 0.003 0.044 0. 0.007 0.946 0. ]
[0. 0. 0.034 0.934 0.015 0.016 0. ]
[0.011 0.034 0.305 0.006 0.567 0. 0.076]
[0. 0. 0.904 0.002 0.089 0.002 0.001]]
|
로지스틱 회귀는 값을 0 ~ 1 사이로 압축하기 때문에 우리는 이 값을 0 ~100% 사이로 이해할 수 있습니다.
04-2 확률적 경사 하강법#
점진적인 학습#
훈련한 모델을 버리지 않고 조금 씩 훈련을 쌓는 방식을 점진적인 학습이라고 부릅니다.
대표적으로 점진적 학습 알고리즘은 확률적 경사 하강법 입니다.
확률적 경사 하강법#
확률적 경사 하강법(Stochastic Gradient Descent, SGD)은 머신러닝에서 모델을 학습시킬 때 사용하는 최적화 알고리즘입니다.
기본 개념
일반적인 경사 하강법은 전체 데이터셋을 사용해 기울기를 계산하지만, SGD는 한 번에 하나의 데이터 포인트만 사용해서 기울기를 계산하고 가중치를 업데이트합니다.
위와 같이 한 번 모두 사용하는 과정을 에포크 라고 부릅니다.

위와 같은 그림으로 이해하면 좋을 것 같습니다.
손실 함수#
손실 함수는 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준입니다.
로지스틱 손실 함수#
손실 함수
손실 함수는 예측과 정답(타깃)을 곱한 다음 음수로 바꿔서 계산합니다.
로그 함수는 0에 가까울수록 아주 큰 음수가 되기 때문에 손실을 아주 크게 만들어 모델에 큰 영향을 줍니다.
이진 분류는 로지스틱 손실 함수를 사용하고 다중 분류는 크로스엔트로피 손실 함수를 사용합니다.
SGDClassifier#
SGDClassifier의 객체를 만들 때 2개의 매개변수를 지정합니다.
loss는 손실 함수의 종류를 지정합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| import pandas as pd
fish = pd.read_csv('https://bit.ly/fish_csv_data')
# 종(물고기) 종류를 제외한 나머지 5개는 입력 데이터로 사용 종(물고기) 열은 타깃 데이터
fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
fish_input, fish_target, random_state=42)
# 훈련 세트에서 학습한 통계 값으로 테스트 세트도 변환 해야함
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
from sklearn.linear_model import SGDClassifier
# 10회 반복(10 에포크)
sc = SGDClassifier(loss='log_loss', max_iter=10, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.773109243697479
print(sc.score(test_scaled, test_target)) # 0.775
|
위의 값에서 10에포크를 하니 0.77 정도의 정확도가 나왔는데 50번을 돌리면
0.8739495798319328
0.8
이 정도의 정확도가 나옵니다.
1
2
3
4
| sc.partial_fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.7983193277310925
print(sc.score(test_scaled, test_target)) # 0.775
|
partial_fit
함수를 사용하여 추가로 훈련하니 조금 더 정확도가 오릅니다.
에포크와 과대/과소적합#
에포크를 진행하면 훈련 세트 점수를 오르지만 어느 순간 테스트 세트 점수가 감소하기 시작합니다.
위 지점이 과대적합되기 시작하는 곳입니다. 과대적합이 시작하기 전에 훈련을 멈추는 것을 조기 종료 라고 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| import numpy as np
sc = SGDClassifier(loss='log_loss', random_state=42)
train_score = []
test_score = []
classes = np.unique(train_target)
# 반복하면서 훈련 및 테스트 세트에 점수 기록
for _ in range(0, 300):
sc.partial_fit(train_scaled, train_target, classes=classes)
train_score.append(sc.score(train_scaled, train_target))
test_score.append(sc.score(test_scaled, test_target))
import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
|

위 모델은 100에포크 정도가 적절한 반복 횟수로 보입니다.
1
2
3
4
5
6
| # tol 매개변수가 None이면 100만큼 무조건 반복
sc = SGDClassifier(loss='log_loss', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.957983193277311
print(sc.score(test_scaled, test_target)) # 0.925
|
SGDClassifier는 tol 매개변수가 없으면 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춥니다.
그렇지만 tol을 지정하지 않고 학습하는 경우 에포크 만큼 돌지 않기 때문에 속도가 빠르지만 정확도가 더 떨어지는(0.85 쯤) 현상이 있습니다.
힌지 손실을 사용하여 훈련할 수 있습니다. 힌지 손실은 서포트 벡터 머신이라 불리며 또 다른 머신러닝 알고리즘을 위한 손실 함수입니다.
1
2
3
4
5
| sc = SGDClassifier(loss='hinge', max_iter=100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)
print(sc.score(train_scaled, train_target)) # 0.9495798319327731
print(sc.score(test_scaled, test_target)) # 0.925
|
점진적 학습을 위한 확률적 경사 하강법#
지금까지 회귀와 분류에 널리 사용되는 알고리즘을 배웠습니다.
최근접 이웃, 선형 회귀, 릿지, 라쏘, 로지스틱 회귀, 확률적 경사 하강법 등..
위 알고리즘들은 실전에서 널리 사용되는 뛰어난 기법이지만 최고는 아니랍니다? 응..? 그럼 왜..
다음 장에서 신경망 알고리즘을 제외하고 머신러닝에서 가장 뛰어난 성능을 내는 알고리즘을 학습합니다.