본문 바로가기
카테고리 없음

230413_DB복습

by 보랏 2023. 4. 14.

안녕하세요. 보랏입니다.

 

어제 잠시 약속이 있어 어제 복습 내용을 올리게 되었습니다.

바로 복습 시작하고 오늘 오후에 던파 분석 내용도 올리도록 하겠습니다.

 

바로 복습 시작하겠습니다.

 

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 %}