안녕하세요. 보랏입니다.
어제 잠시 약속이 있어 어제 복습 내용을 올리게 되었습니다.
바로 복습 시작하고 오늘 오후에 던파 분석 내용도 올리도록 하겠습니다.
바로 복습 시작하겠습니다.
1. 머신러닝 - 릿지, 랏쏘, 엘라스틱 넷 회귀 알고리즘
- 모델의 과대적합을 방지하는 좋은 방법은 모델에 규제를 가하는 방법
- 다항회귀모델의 다항식 차수를 감소시키는 등 자유도를 줄이는 방법
- 회귀 계수의 크기를 제어해 과적합을 개선하려면 아래 그림과 깉이 함수를 최소화하는 것이 필요
- alpha : 학습 데이터 적합 정도와 회귀 계수 값의 크기 제어를 수행하는 튜닝 파라미터
- alpha가 0(또는 매우 작은 값) 이면 비용 함수 식은 기존과 동일한 Min(RSS(W)+0) 이 됨
- alpha가 무한대(또는 매우 큰 값) 이면 비용함수 식은 RSS(W)에 비해 alpha * ||W||22 값이 너무 커지므로 W값을 작게 만들어야 cost가 최소화됨
- 즉, alpha를 0에서부터 지속적으로 값을 증가시키면 회귀 계수 값의 크기를 감소시킬 수 있음
- 이처럼 비용 함수에 alpha값으로 패널티를 부여해 회귀 계수 값의 크기를 감소시켜 과적합을 개선하는 방시을 규제라고 함
- 규제는 L2방식과 L1방식으로 구분
- L2규제 : alpha*||W||22와 같이 W의 제곱에 대해 패널티를 부여하는 방식 -- 릿지 회귀
- L1규제 : alpha*||W||1와 같이 W의 절댓값에 대해 패널티를 부여 -- 라쏘 회귀
릿지 회귀(Ridge Regression) - 보스턴 주택가격 dataset을 사용하여 Ridge클래스로 예측 및 성능 평가
- 사이킷런은 Ridge클래스를 통해 릿지 회귀를 구현
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from scipy import stats
from sklearn.datasets import load_boston
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
%matplotlib inline
# boston 데이타셋 로드
boston = load_boston()
# boston 데이타셋 DataFrame 변환
bostonDF = pd.DataFrame(boston.data , columns = boston.feature_names)
# boston dataset의 target array는 주택 가격임. 이를 PRICE 컬럼으로 DataFrame에 추가함.
bostonDF['PRICE'] = boston.target
print('Boston 데이타셋 크기 :',bostonDF.shape)
y_target = bostonDF['PRICE']
X_data = bostonDF.drop(['PRICE'],axis=1,inplace=False)
ridge = Ridge(alpha = 10)
neg_mse_scores = cross_val_score(ridge, X_data, y_target, scoring="neg_mean_squared_error", cv = 5)
rmse_scores = np.sqrt(-1 * neg_mse_scores)
avg_rmse = np.mean(rmse_scores)
print(' 5 folds 의 개별 Negative MSE scores: ', np.round(neg_mse_scores, 3))
print(' 5 folds 의 개별 RMSE scores : ', np.round(rmse_scores,3))
print(' 5 folds 의 평균 RMSE : {0:.3f} '.format(avg_rmse))
- 릿지가 5배 폴드 세트의 평균 RMSE가 5.518로 선형회귀의 RMSE 평균인 5.829보다 예측능력이 높음
- 이제 alpha값을 0, 0.1, 1, 10, 100으로 변화시키면서 RMSE와 회귀 계수 값의 변화를 확인
# Ridge에 사용될 alpha 파라미터의 값들을 정의
alphas = [0, 0.1, 1, 10, 100]
# 각 alpha에 따른 회귀 계수 값을 시각화하기 위해 5개의 열로 된 맷플롯립 축 생성
fig, axs = plt.subplots(figsize=(18,6), nrows=1, ncols=5)
# 각 alpha에 따른 회귀 계수 값을 데이터로 저장하기 위한 DataFrame 생성
coeff_df = pd.DataFrame()
# alphas list 값을 iteration하면서 alpha에 따른 평균 rmse 구하고, 회귀 계수 값 시각화 및 데이터 저장. pos는 axis의 위치 지정
for pos, alpha in enumerate(alphas):
ridge = Ridge(alpha = alpha)
ridge.fit(X_data, y_target)
#cross_val_score를 이용하여 5 fold의 평균 RMSE 계산
neg_mse_scores = cross_val_score(ridge, X_data, y_target, scoring="neg_mean_squared_error", cv = 5)
avg_rmse = np.mean(np.sqrt(-1 * neg_mse_scores))
print('alpha {0} 일 때 5 folds 의 평균 RMSE : {1:.3f} '.format(alpha,avg_rmse))
# alpha에 따른 피처별 회귀 계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가.
coeff = pd.Series(data=ridge.coef_, index=X_data.columns )
colname='alpha:'+str(alpha)
coeff_df[colname] = coeff
# 막대 그래프로 각 alpha 값에서의 회귀 계수를 시각화. 회귀 계수값이 높은 순으로 표현
coeff = coeff.sort_values(ascending=False)
axs[pos].set_title(colname)
axs[pos].set_xlim(-3,6)
sns.barplot(x=coeff.values, y=coeff.index, ax=axs[pos])
# for 문 바깥에서 맷플롯립의 show 호출 및 alpha에 따른 피처별 회귀 계수를 DataFrame으로 표시
plt.show()
# alpha 값에 따른 컬럼별 회귀계수 출력
sort_column = 'alpha:'+str(alphas[0])
coeff_df.sort_values(by=sort_column, ascending=False)
- alpha 값을 계속 증가시키면 회귀 계수 값이 작아지며, NOX피처의 경우 회귀 계수가 크게 작아짐
라쏘회귀 - 보스턴 주택가격 dataset을 사용하여 Lasso클래스로 예측 및 성능 평가
- W의 절댓값에 패널티를 부여하는 L1규제를 선형 회귀에 적용한 모델
- L1규제는 불필요한 회귀 계수를 급격하게 감소시켜 0으로 만들고 제거
- L1규제는 적절한 feature만 회귀에 포함시키는 선택 기능 보유
from sklearn.linear_model import Lasso, ElasticNet
# alpha값에 따른 회귀 모델의 폴드 평균 RMSE를 출력하고 회귀 계수값들을 DataFrame으로 반환
def get_linear_reg_eval(model_name, params=None, X_data_n=None, y_target_n=None,
verbose=True, return_coeff=True):
coeff_df = pd.DataFrame()
if verbose : print('####### ', model_name , '#######')
for param in params:
if model_name =='Ridge': model = Ridge(alpha=param)
elif model_name =='Lasso': model = Lasso(alpha=param)
elif model_name =='ElasticNet': model = ElasticNet(alpha=param, l1_ratio=0.7)
neg_mse_scores = cross_val_score(model, X_data_n,
y_target_n, scoring="neg_mean_squared_error", cv = 5)
avg_rmse = np.mean(np.sqrt(-1 * neg_mse_scores))
print('alpha {0}일 때 5 폴드 세트의 평균 RMSE: {1:.3f} '.format(param, avg_rmse))
# cross_val_score는 evaluation metric만 반환하므로 모델을 다시 학습하여 회귀 계수 추출
model.fit(X_data_n , y_target_n)
if return_coeff:
# alpha에 따른 피처별 회귀 계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가.
coeff = pd.Series(data=model.coef_ , index=X_data_n.columns )
colname='alpha:'+str(param)
coeff_df[colname] = coeff
return coeff_df
# 라쏘에 사용될 alpha 파라미터의 값들을 정의하고 get_linear_reg_eval() 함수 호출
lasso_alphas = [ 0.07, 0.1, 0.5, 1, 3]
coeff_lasso_df =get_linear_reg_eval('Lasso', params=lasso_alphas, X_data_n=X_data, y_target_n=y_target)
# 반환된 coeff_lasso_df를 첫번째 컬럼순으로 내림차순 정렬하여 회귀계수 DataFrame출력
sort_column = 'alpha:'+str(lasso_alphas[0])
coeff_lasso_df.sort_values(by=sort_column, ascending=False)
- alpha값이 증가함에 따라 일부 feature의 회귀 계수는 0으로 변경되며, 이렇게 회귀 계수가 0인 feature가 회귀 식에서 제외되면서 L1규제를 이용하여 feature 선택이 가능
엘라스틱넷 회귀
- L2규제와 L1규제를 결합한 회귀모델
- 라쏘 회귀는 서로 상관관계가 높은 feature중에서 중요 feature만을 선택하고 다른 feature들은 모든 회귀 계수를 0으로 만들어 alpha값에 따라 회귀 계수의 값이 급격히 변동
- 엘라스틱넷 회귀는 이러한 점을 완화하기 위해 L2규제를 라쏘 회귀에 추가하였으나 수행시간이 상대적으로 오래 걸림
- 사이킷런은 ElasticNet클래스를 통해 엘라스틱넷 규제를 구현하며 , 주요 생성 파라미터는 alpha와 l1_ratio
- alpha = aL1+bL2로 정의되며 여기서 a+b가 alpha 값
- l1_ratio = a/(a+b) -- 0이면 a가 0이 되므로 L2규제, l2_ratio가 1이면 b가 0이므로 L1규제
# 엘라스틱넷에 사용될 alpha 파라미터의 값들을 정의하고 get_linear_reg_eval() 함수 호출
# l1_ratio는 0.7로 고정
elastic_alphas = [ 0.07, 0.1, 0.5, 1, 3]
coeff_elastic_df =get_linear_reg_eval('ElasticNet', params=elastic_alphas,
X_data_n=X_data, y_target_n=y_target)
# 반환된 coeff_elastic_df를 첫번째 컬럼순으로 내림차순 정렬하여 회귀계수 DataFrame출력
sort_column = 'alpha:'+str(elastic_alphas[0])
coeff_elastic_df.sort_values(by=sort_column, ascending=False)
- l1_ratio는 0.7로 고정하고 alpha가 0.5일 때 RMSE가 5.468로 가장 좋은 예측 성능을 보이고, 이전의 라쏘보다는 상대적으로 0이 되는 값이 적다는 것을 알 수 있다.
2. django 로그인 & 로그아웃 페이지 생성
로그인 페이지 생성
- 네비게이션 바에 로그인 버튼 생성 (navbar.html)
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<a class="navbar-brand" href="{% url 'pybo:index' %}">Pybo</a>
<button class="navbar-toggler ml-auto" type="button" data-toggle="collapse" data-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse flex-grow-0" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item ">
<a class="nav-link" href="{% url 'common:login' %}">로그인</a>
</li>
</ul>
</div>
</nav>
- common폴더 urls.py 아래코드 입력
from django.contrib.auth import views as auth_views
from django.urls import path
from . import views
app_name = 'common'
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='common/login.html'), name='login'),
]
- 로그인 템플릿 생성 -> templates 폴더 아래 login.html 생성
{% extends "base.html" %}
{% block content %}
<div class="container my-3">
<form method="post" class="post-form" action="{% url 'common:login' %}">
{% csrf_token %}
{% include "form_errors.html" %}
<div class="form-group">
<label for="username">사용자ID</label>
<input type="text" class="form-control" name="username" id="username"
value="{{ form.username.value|default_if_none:'' }}">
</div>
<div class="form-group">
<label for="password">비밀번호</label>
<input type="password" class="form-control" name="password" id="password"
value="{{ form.password.value|default_if_none:'' }}">
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
</div>
{% endblock %}
- 에러 발생 홈페이지 생성 -> templates 폴더 아래 form_errors.html 생성
- 필드오류 (field.errors) : 사용자가 입력한 필드 값에 대한 오류로 값이 누락되었거나 필드의 형식이 일치하지 않는 경우에 발생하는 오류
- 넌필드오류(form.non_field_errors) : 필드의 값과는 상관없이 다른 이유로 발생하는 오류
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %} <!-- 필드 오류를 출력한다. -->
<div class="alert alert-danger">
<strong>{{ field.label }}</strong>
{{ error }}
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %} <!-- 넌필드 오류를 출력한다. -->
<div class="alert alert-danger">
<strong>{{ error }}</strong>
</div>
{% endfor %}
{% endif %}
- 로그인이 성공하면 django.contrib.auth 패키지는 디폴트로 /accounts/profile/ 이라는 url로 이동하여 오류 발생
- /accounts/profile/ URL은 현재 pybo에 구성한 URL구조와 맞지 않으므로 로그인 성공 시 / 페이지로 이동할 수 있도록 config/setting.py 수정 필요
- / 페이지는 기본 URL인 http://localhost:8000/
# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'
- config/urls.py 파일에 / 페이지에 대응하는 URL매칭 규칙 추가
from django.contrib import admin
from django.urls import path, include
from pybo import views
urlpatterns = [
path('admin/', admin.site.urls),
path('pybo/', include('pybo.urls')),
path('common/', include('common.urls')),
path('', views.index, name='index'), # '/' 에 해당되는 path
]
로그아웃 페이지 생성
- 로그인 후에는 로그인 링크를 로그아웃 링크로 변경
- navbar.html 템플릿 파일에서 로그인 링크 부분 수정
<li class="nav-item">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'common:logout' %}">{{ user.username }} (로그아웃)</a>
{% else %}
<a class="nav-link" href="{% url 'common:login' %}">로그인</a>
{% endif %}
</li>
- {% if user.is_authenticated %} 은 현재 사용자가 로그인 되었는지를 판별
- 로그인이 되어 있으면 "로그아웃" 링크를 표시하고 로그인이 되어 있지 않다면 "로그인" 링크를 표시
- 템플릿에서 django.contrib.auth 기능으로 User객체 사용
- AbstractUser 클래스 주요 속성 및 멤버 함수
- .is_authenticated : 로그인 여부 (로그인한 상태라면 true, 로그아웃 상태라면 false)
- .is_anonymous : 로그아웃 여부 - is_authenticated의 반대 경우 (로그인한 상태라면 false, 로그아웃 상태라면 true)
- .set_password : 지정 암호를 암호화해서 password필드에 저장
- .check_password : 암호 비교
- .set_unusable_password() : 로그인 불가 암호로 세팅
- .has_unusable_password() : 로그인 불가 암호 설정여부
- user.username - 사용자명 (사용자 ID)
- user.is_superuser - 사용자가 슈퍼유저인지 여부
- User 모델 클래스 획득 방법
- get_user_mode 모델 클래스 참조 방법
- settings.AUTH_USER_MODEL 모델 클래스 참조 방법
## get_user_mode 모델 클래스 참조 방법
from django.contrib.auth import get_user_model
User = get_user_model()
User.objects.all()
## settings.AUTH_USER_MODEL 모델 클래스 참조 방법
from django.conf import settings
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
- 로그아웃 링크가 추가되었으므로 common/urls.py에 매핑
from django.urls import path
from django.contrib.auth import views as auth_views
app_name = 'common'
urlpatterns = [
path('login/', auth_views.LoginView.as_view(template_name='common/login.html'), name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
- 로그아웃 후 redirect 위치 생성 필요 --> config/urls.py 파일에 / 페이지에 대응하는 URL매칭 규칙 추가
# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'
# 로그아웃시 이동하는 URL
LOGOUT_REDIRECT_URL = '/'
회원가입 기능 구현
- 로그인/로그아웃 우측에 회원가입을 위한 링크 추가 (회원가입은 로그아웃 상태에서만 표시하도록 설정)
- templates폴더 navbar.html 템플릿에 해당 코드 추가
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
(... 생략 ...)
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'common:logout' %}">{{ user.username }} (로그아웃)</a>
{% else %}
<a class="nav-link" href="{% url 'common:login' %}">로그인</a>
{% endif %}
</li>
<li> #여기부터
{% if not user.is_authenticated %}
<a class="nav-link" href="{% url 'common:signup' %}">회원가입</a>
{% endif %}
</li> #여기까지 추가
</ul>
</div>
</div>
</nav>
- navbar.html 템플릿에 {% url 'common:signup' %} 태그를 추가했으므로 여기에 대응하는 url매칭 규칙 추가
- common/urls.py파일에 회원가입을 위한 url매칭 규칙 추가
- 계정 생성 시 사용할 Userform을 common/forms.py파일에 작성
- UserForm은 django.contrib.auth.forms 모듈의 UserCreationFrom 클래스를 상속하여 생성
- email 속성 추가
- UserCreationForm의 is_vaild함수는 폼에 위의 속성 3개가 모두 입력되었는지, 비밀번호1, 2가 같은지, 생성규칙이 맞는지 등을 검사하는 로직 보유
- common/views.py 파일에 회원가입을 위한 signup함수 정의
- post 요청인 경우에는 화면에서 입력한 데이터로 사용자를 생성하고 get요청인 경우에는 회원가입 화면을 보여줌
- form.cleaned_data.get 함수는 폼의 입력값을 개별적으로 얻고 싶은 경우에 사용
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from common.forms import UserForm
def signup(request):
if request.method == "POST":
form = UserForm(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password) # 사용자 인증
login(request, user) # 로그인
return redirect('index')
else:
form = UserForm()
return render(request, 'common/signup.html', {'form': form})
- 회원가입 화면을 구성하는 signip.html 템플릿을 생성 필요
{% extends "base.html" %}
{% block content %}
<div class="container my-3">
<form method="post" action="{% url 'common:signup' %}">
{% csrf_token %}
{% include "form_errors.html" %}
<div class="mb-3">
<label for="username">사용자 이름</label>
<input type="text" class="form-control" name="username" id="username"
value="{{ form.username.value|default_if_none:'' }}">
</div>
<div class="mb-3">
<label for="password1">비밀번호</label>
<input type="password" class="form-control" name="password1" id="password1"
value="{{ form.password1.value|default_if_none:'' }}">
</div>
<div class="mb-3">
<label for="password2">비밀번호 확인</label>
<input type="password" class="form-control" name="password2" id="password2"
value="{{ form.password2.value|default_if_none:'' }}">
</div>
<div class="mb-3">
<label for="email">이메일</label>
<input type="text" class="form-control" name="email" id="email"
value="{{ form.email.value|default_if_none:'' }}">
</div>
<button type="submit" class="btn btn-primary">생성하기</button>
</form>
</div>
{% endblock %}