{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# **로지스틱 회귀를 이용한 Click Through Rate**\n",
"1. 데이터 전처리 및 One-Hot Encoing\n",
"1. **Logistic 회귀** 동작의 원리\n",
"1. **Gradient descent** 기법, **Statistic Gradient descent** 기법\n",
"1. **Logistic 회기** 분류기 학습 및 예측모델\n",
"1. **L1, L2 정규화**를 이용한 Logistic 회귀\n",
"1. On - Line Learning\n",
"1. **Random Forest** 를 이용한 **feacture selection**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"
\n",
"\n",
"# **1 One - Hot Encoding**\n",
"1. **범주형 feacture** 를 **이진형 수치 feacture** 로 변환\n",
"1. **K개의 값**을 갖는 **범주형** feacture를 **1~k 의** feacture로 매핑시킨다\n",
"1. 변환된 범주형 데이터를 **원본으로** 되돌린다"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## **01 One - Hot Encoding 임베딩 데이터 만들기**\n",
"1. **범주형 feacture** 를 **이진형 수치 feacture** 로 변환\n",
"1. **K개의 값**을 갖는 **범주형** feacture를 **1~k 의** feacture로 매핑시킨다\n",
"1. 변환된 범주형 데이터를 **원본으로** 되돌린다"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[0., 0., 1., 1., 0., 0.],\n",
" [1., 0., 0., 0., 0., 1.],\n",
" [1., 0., 0., 1., 0., 0.],\n",
" [0., 1., 0., 0., 0., 1.],\n",
" [0., 0., 1., 0., 0., 1.],\n",
" [0., 0., 1., 0., 1., 0.],\n",
" [0., 1., 0., 1., 0., 0.]])"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Dict 범주형 데이터를 One-Hot-encoding으로 변환\n",
"from sklearn.feature_extraction import DictVectorizer\n",
"dict_one_hot_encoder = DictVectorizer(sparse=False)\n",
"\n",
"X_dict = [{'interest': 'tech', 'occupation': 'professional'},\n",
" {'interest': 'fashion', 'occupation': 'student'},\n",
" {'interest': 'fashion', 'occupation': 'professional'},\n",
" {'interest': 'sports', 'occupation': 'student'},\n",
" {'interest': 'tech', 'occupation': 'student'},\n",
" {'interest': 'tech', 'occupation': 'retired'},\n",
" {'interest': 'sports', 'occupation': 'professional'}]\n",
"\n",
"X_encoded = dict_one_hot_encoder.fit_transform(X_dict)\n",
"X_encoded"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'interest=fashion': 0,\n",
" 'interest=sports': 1,\n",
" 'interest=tech': 2,\n",
" 'occupation=professional': 3,\n",
" 'occupation=retired': 4,\n",
" 'occupation=student': 5}\n"
]
}
],
"source": [
"# 범주형 Dataset Index 매핑내용 살펴보기\n",
"from pprint import pprint\n",
"pprint(dict_one_hot_encoder.vocabulary_)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## **02 Converting Data by Using Map Data**\n",
"위에서 학습한 **dict_one_hot_encoder** 를 활용하여 데이터를 컨버팅/ 복원"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0. 1. 0. 0. 1. 0.]]\n"
]
}
],
"source": [
"# 위에서 매팽한 table 을 사용하여 새로운 데이터 인코딩\n",
"new_dict = [{'interest': 'sports', 'occupation': 'retired'}]\n",
"new_encoded = dict_one_hot_encoder.transform(new_dict)\n",
"print(new_encoded)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[{'interest=sports': 1.0, 'occupation=retired': 1.0}]\n"
]
}
],
"source": [
"# new_encoded 인코딩 데이터를 원본형태로 되돌린다\n",
"print(dict_one_hot_encoder.inverse_transform(new_encoded))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## **03 Learning New Map Data**\n",
"1. **new_encoded :** 새로운 매핑 데이터 추가하면, 결과적으로 **무시된다**\n",
"1. 두개의 **dict** 데이터 중 **없는건 제외하고 나머지만 Converting** 된다"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0. 0. 0. 0. 1. 0.]\n",
" [0. 0. 1. 0. 0. 0.]]\n"
]
}
],
"source": [
"# 1개의 인덱스에 포함된 2개의 Dict 중, 1개만 converting 된다\n",
"new_dict = [{'interest': 'unknown_interest', 'occupation': 'retired'},\n",
" {'interest': 'tech', 'occupation': 'unseen_occupation'}]\n",
"new_encoded = dict_one_hot_encoder.transform(new_dict)\n",
"print(new_encoded)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## **04 LabelEncoder 를 활용한 One-Hot-Encoding**\n",
"1. **X_int** : One Hot 의 **인덱스값을** 출력한다\n",
"1. 보다 간결하고 식별력이 높다"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[5 1]\n",
" [0 4]\n",
" [0 1]\n",
" [3 4]\n",
" [5 4]\n",
" [5 2]\n",
" [3 1]]\n"
]
}
],
"source": [
"import numpy as np\n",
"X_str = np.array([['tech', 'professional'],\n",
" ['fashion', 'student'],\n",
" ['fashion', 'professional'],\n",
" ['sports', 'student'],\n",
" ['tech', 'student'],\n",
" ['tech', 'retired'],\n",
" ['sports', 'professional']])\n",
"\n",
"from sklearn.preprocessing import LabelEncoder, OneHotEncoder\n",
"label_encoder = LabelEncoder()\n",
"X_int = label_encoder.fit_transform(X_str.ravel()).reshape(*X_str.shape)\n",
"print(X_int)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0. 0. 1. 1. 0. 0.]\n",
" [1. 0. 0. 0. 0. 1.]\n",
" [1. 0. 0. 1. 0. 0.]\n",
" [0. 1. 0. 0. 0. 1.]\n",
" [0. 0. 1. 0. 0. 1.]\n",
" [0. 0. 1. 0. 1. 0.]\n",
" [0. 1. 0. 1. 0. 0.]]\n"
]
}
],
"source": [
"# X_int 를 X_encoded 로 변환\n",
"one_hot_encoder = OneHotEncoder()\n",
"X_encoded = one_hot_encoder.fit_transform(X_int).toarray()\n",
"print(X_encoded)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[0. 0. 0. 0. 1. 0.]\n",
" [0. 0. 1. 0. 0. 0.]\n",
" [0. 0. 0. 0. 0. 0.]]\n"
]
}
],
"source": [
"# Mapping 입력되지 않은 값들은 위와 동일하게 무시된다\n",
"new_str = np.array([['unknown_interest', 'retired'],\n",
" ['tech', 'unseen_occupation'],\n",
" ['unknown_interest', 'unseen_occupation']])\n",
"\n",
"def string_to_dict(columns, data_str):\n",
" data_dict = []\n",
" for sample_str in data_str:\n",
" data_dict.append({column : value for column, value in zip(columns, sample_str)})\n",
" return data_dict\n",
"\n",
"columns = ['interest', 'occupation']\n",
"new_encoded = dict_one_hot_encoder.transform(string_to_dict(columns, new_str))\n",
"print(new_encoded)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"
\n",
"\n",
"# **2 로지스틱 회귀 분류기**\n",
"1. **실수값 데이터는 0~1 사이의 값으로** 변환한다\n",
"1. $y(z) = \\frac{1}{1+exp(-z)}$ 대용량 데이터에 **확장성이 좋은** 알고리즘이다"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## **01 로지스틱 회귀의 동작원리**\n",
"로지스틱 회귀는 나이브 베이즈 분류기처럼 **확률 기반 분류기이다**"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"# 로지스틱 회귀 함수를 정의한다\n",
"import numpy as np\n",
"\n",
"def sigmoid(input):\n",
" return 1.0 / (1 + np.exp(-input))"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"