声明:版权所有,转载请联系作者并注明出处: 风雪夜归子 - CSDN博客
由于知乎不支持markdown格式,所以有公式的地方都使用了截图,若影响阅读效果,可移步我的Blog:风雪夜归子 - CSDN博客,文章会同步更新!
逻辑回归
上一篇文章介绍了线性回归、岭回归、lasso回归和多项式回归模型。这些模型都是广义线性回归模型的具体形式,广义线性回归是一种灵活的框架,比普通线性回归要求更少的假设。这一章,我们讨论广义线性回归模型的具体形式的另一种形式,逻辑回归(logistic regression)。
逻辑回归模型在工业界是工程师用的非常多的模型了,比如在CTR预测等项目中被大量使用,之所以用的非常普遍,是因为逻辑回归拥有简单、解释性好、计算速度快等优点。
和前面讨论的模型不同,逻辑回归是用来做分类任务的。分类任务的目标是找一个函数,把观测值匹配到相关的类和标签上。学习算法必须用成对的特征向量和对应的标签来估计匹配函数的参数,从而实现更好的分类效果。在二元分类(binary classification)中,分类算法必须把一个实例分为两个类别。二元分类案例包括,预测患者是否患有某种疾病,音频中是否含有人声,杜克大学男子篮球队在NCAA比赛中第一场的输赢。
普通的线性回归假设响应变量呈正态分布,也称为高斯分布(Gaussian distribution )或钟形曲线(bell curve)。正态分布数据是对称的,且均值,中位数和众数(mode)是一样的。很多自然现象都服从正态分布。比如,人类的身高就服从正态分布,姚明那样的高度极少,在99%之外了。
在某些问题里,响应变量不是正态分布的。比如,掷一个硬币获取正反两面的概率分布是伯努力分布(Bernoulli distribution),又称两点分布或者0-1分布。表示一个事件发生的概率是P,不发生的概率1−P,概率在[0, 1]之间。线性回归假设自变量(解释变量)值的变化会引起因变量(响应变量)值的变化,如果响应变量的值是概率,这条假设就不满足了。广义线性回归去掉了这条假设,用一个联连函数(link function)来描述解释变量与响应变量的关系。实际上,在线性回归模型里面,我们已经用了联连函数。普通线性回归作为广义线性回归的特例使用的是恒等联连函数(identity link function),将解释变量的通过线性组合的方式来联接服从正态分布的响应变量。如果响应变量不服从正态分布,就要用另外一种联连函数了。
from __future__ import print_function, division
import sys
import os
import math
from sklearn import datasets
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def shuffle_data(X, y, seed=None):
if seed:
np.random.seed(seed)
idx = np.arange(X.shape[0])
np.random.shuffle(idx)
return X[idx], y[idx]
# 正规化数据集 X
def normalize(X, axis=-1, p=2):
lp_norm = np.atleast_1d(np.linalg.norm(X, p, axis))
lp_norm[lp_norm == 0] = 1
return X / np.expand_dims(lp_norm, axis)
# 标准化数据集 X
def standardize(X):
X_std = np.zeros(X.shape)
mean = X.mean(axis=0)
std = X.std(axis=0)
# 做除法运算时请永远记住分母不能等于0的情形
# X_std = (X - X.mean(axis=0)) / X.std(axis=0)
for col in range(np.shape(X)[1]):
if std[col]:
X_std[:, col] = (X_std[:, col] - mean[col]) / std[col]
return X_std
# 划分数据集为训练集和测试集
def train_test_split(X, y, test_size=0.2, shuffle=True, seed=None):
if shuffle:
X, y = shuffle_data(X, y, seed)
n_train_samples = int(X.shape[0] * (1-test_size))
x_train, x_test = X[:n_train_samples], X[n_train_samples:]
y_train, y_test = y[:n_train_samples], y[n_train_samples:]
return x_train, x_test, y_train, y_test
# 将一个向量转换成对角阵,其中对角阵上的元素就是向量中元素
def vec2diagonal(vec):
vec_length = len(vec)
diagonal = np.zeros((vec_length, vec_length))
for i in range(vec_length):
diagonal[i][i] = vec[i]
return diagonal
def accuracy(y, y_pred):
y = y.reshape(y.shape[0], -1)
y_pred = y_pred.reshape(y_pred.shape[0], -1)
return np.sum(y == y_pred)/len(y)
class Sigmoid:
def function(self, x):
return 1/(1 + np.exp(-x))
def derivative(self, x):
return self.function(x) * (1 - self.function(x))
class LogisticRegression():
"""逻辑回归分类模型.Parameters:-----------learning_rate: float学习率."""
def __init__(self, learning_rate=.1):
self.w = None
self.learning_rate = learning_rate
self.sigmoid = Sigmoid()
def fit(self, X, y, n_iterations=4000):
# 在第一列添加偏置列,全部初始化为1
X = np.insert(X, 0, 1, axis=1)
X = X.reshape(X.shape[0], -1)
y = y.reshape(y.shape[0], -1)
n_samples, n_features = np.shape(X)
# 参数初始化 [-1/n_features, 1/n_features]
limit = 1 / math.sqrt(n_features)
self.w = np.random.uniform(-limit, limit, (n_features, 1))
for i in range(n_iterations):
# 通过初始化的参数w计算预测值
y_pred = self.sigmoid.function(X.dot(self.w))
# 梯度下降更新参数w.
self.w -= self.learning_rate * X.T.dot(-(y - y_pred) *
self.sigmoid.function(X.dot(self.w)) *
(1 - self.sigmoid.function(X.dot(self.w))))
def predict(self, X):
# 训练模型的时候我们添加了偏置,预测的时候也需要添加偏置
X = X.reshape(X.shape[0], -1)
X = np.insert(X, 0, 1, axis=1)
# 预测
y_pred = np.round(self.sigmoid.function(X.dot(self.w))).astype(int)
return y_pred
def main():
# Load dataset
data = datasets.load_iris()
X = normalize(data.data[data.target != 0])
y = data.target[data.target != 0]
y[y == 1] = 0
y[y == 2] = 1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, seed=1)
clf = LogisticRegression()
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
accu = accuracy(y_test, y_pred)
print ("Accuracy:", accu)
plt.figure(figsize=(12, 8))
plt.scatter(X[y==0][:,0], X[y==0][:,1])
plt.scatter(X[y==1][:,0], X[y==1][:,1])
plt.show()
if __name__ == "__main__":
main()
Accuracy: 0.939393939394