Logistic回归的原理是用逻辑函数把线性回归的结果(-∞,+∞)
映射到(0,1)
,下面介绍线性回归函数和逻辑函数。
线性回归函数
线性回归函数的数学表达式:
y = θ 0 + θ 1 x 1 + θ 2 x 2 , + … + θ n x n = θ T x y=\theta _{0} +\theta _{1}x _{1}+\theta _{2}x _{2},+…+\theta _{n}x _{n} = \theta^{T} x y=θ0+θ1x1+θ2x2,+…+θnxn=θTx
其中xi
是自变量,y
是因变量,y
的值域为(-∞,+∞)
, θ 0 \theta _{0} θ0是常数项, θ i \theta _{i} θi是待求系数,不同的权重 θ i \theta _{i} θi反映了自变量对因变量不同的贡献程度。
对于一元一次方程: y=a+bx
,这种只包括一个自变量和一个因变量的回归分析称为一元线性回归分析。
对于二元一次方程: y= a+b1x1+b2x2
,三元一次方程: y = a+b1x1+b2x2+b3x3
,这种回归分析中包括两个或两个以上自变量的回归分析,称为多元线性回归分析。
不管是一元线性回归分析还是多元线性回归分析,都是线性回归分析。
Sigmoid函数
函数表达式:
g ( z ) = 1 1 + e − z g(z)=\frac{1}{1+e^{-z} } g(z)=1+e−z1
函数图像:
从图像中可以看出,当z
趋于-∞
,g(z)
趋于0,当z
趋于+∞
,g(z)
趋于1,且函数的值阈为(0,1)
。原理是当z
趋于-∞
, e − z e^{-z} e−z趋于∞,g(z)
趋于0,当z趋于+∞
, e − z e^{-z} e−z趋于0,g(z)
趋于1。
同时可以发现当z趋于5时,g(z)的值已经到0.99附近,z越大,g(z)越趋于1。所以这个函数很好描述我们日常生活中,平时碰到的概率。我们可能认为明天下雨的概率为0.3,天晴的概率为0.7。抛一枚硬币正面的概率为0.5,反面的概率也为0.5。概率也是介于0到1之间的一些数,很自然我们可以把sigmod函数的值域和概率联系起来。
梯度下降过程类似一个人下山过程:
从上面的分析知,用梯度下降法求解参数最重要的是找到下降最快的方向和确定要走的步长。
那么什么是函数下降最快的方向?
如果学过一元函数的导数,应该知道导数的几何意义是某点切线的斜率。除此之外导数还可以表示函数在该点的变化率,导数越大,表示函数在该点的变化越大。
从图中可以发现p2点的斜率大于p1点的斜率,即p2点的导数大于p1点的导数。对于多维向量
x = ( x 1 , x 2 , . . . , x n ) x= (x_{1},x_{2},...,x_{n} ) x=(x1,x2,...,xn)
它的导数叫做梯度(偏导数),当求某个变量的导数时,把其它变量视为常量,对整个函数求导,也就是分别对于它的每个分量求导数,即
x ′ = ( x 1 ′ , x 2 ′ , . . . , x n ′ ) x^{'} = (x_{1}^{'},x_{2}^{'},...,x_{n}^{'} ) x′=(x1′,x2′,...,xn′)
对于函数的某个特定点,它的梯度就表示从该点出发,函数值变化最为迅猛的方向。至此梯度下降法求解参数的方向已经找到,那就是函数的梯度方向。
data_arr
为数据特征,label_arr
是每个数据对应标签 :def load_data_set(fileName):
data_arr = []
label_arr = []
f = open(fileName, 'r')
for line in f.readlines():
line_arr = line.strip().split()
data_arr.append([np.float(line_arr[0]),
np.float(line_arr[1]),
np.float(line_arr[2]),
np.float(line_arr[3]),
np.float(line_arr[4]),
np.float(line_arr[5]),
np.float(line_arr[6]),
np.float(line_arr[7])])
label_arr.append(int(line_arr[8]))
data_arr = maxminnorm(data_arr)
return data_arr, label_arr
data_arr, class_labels = load_data_set()
在预处理时要注意如果有的数据太大,要将数据归一化,如果不归一化会导致数据溢出。根据sigmoid函数
g ( z ) = 1 1 + e − z g(z)=\frac{1}{1+e^{-z} } g(z)=1+e−z1当z过大时, e − z e^{-z} e−z数值是很大的,所以要将器归一化处理,根据归一化公式: x i = x i − m i n ( x ) m a x ( x ) − m i n ( x ) x_{i}=\frac{x_{i} -min(x)}{max(x)-min(x)} xi=max(x)−min(x)xi−min(x)根据公式可以将数据全部保证在0-1之间,并且不丢失数据代表的特征。带入到sigmoid函数后,就不会有数据溢出的异常了。
def maxminnorm(array):
array = np.array(array)
maxcols=array.max(axis=0)
mincols=array.min(axis=0)
data_shape = array.shape
data_rows = data_shape[0]
data_cols = data_shape[1]
t=np.empty((data_rows,data_cols))
for i in range(data_cols):
t[:,i]=(array[:,i]-mincols[i])/(maxcols[i]-mincols[i])
return t
param data_arr
,当然你传入一个二维的ndarray也行。class_labels
是类别标签,它是一个的行向量。为了便于矩阵计算,需要将该行向量转换为列向量,做法是将原向量转置,再将它赋值给label_mat。def grad_ascent(data_arr, class_labels):
data_mat = np.mat(data_arr)
# 变成矩阵之后进行转置
label_mat = np.mat(class_labels).transpose()
# m->数据量,样本数 n->特征数
m, n = np.shape(data_mat)
# 学习率,learning rate
alpha = 0.001
# 最大迭代次数
max_cycles = 500
# 生成一个长度和特征数相同的矩阵
# weights 代表回归系数, 此处的 ones((n,1)) 创建一个长度和特征数相同的矩阵,其中的数全部都是 1
weights = np.ones((n, 1))
for k in range(max_cycles):
h = sigmoid(data_mat * weights)
error = label_mat - h
weights = weights + alpha * data_mat.transpose() * error
return weights
def stoc_grad_ascent0(data_mat, class_labels):
m, n = np.shape(data_mat)
alpha = 0.01
weights = np.ones(n)
for i in range(m):
# sum(data_mat[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn,
# 此处求出的 h 是一个具体的数值,而不是一个矩阵
h = sigmoid(sum(data_mat[i] * weights))
error = class_labels[i] - h
weights = weights + alpha * error * data_mat[i]
return weights
def stoc_grad_ascent1(data_mat, class_labels, num_iter=150):
m, n = np.shape(data_mat)
weights = np.ones(n)
for j in range(num_iter):
# 这里必须要用list,不然后面的del没法使用
data_index = list(range(m))
for i in range(m):
# i和j的不断增大,导致alpha的值不断减少,但是不为0
alpha = 4 / (1.0 + j + i) + 0.01
# 随机产生一个 0~len()之间的一个值
# random.uniform(x, y) 方法将随机生成下一个实数,它在[x,y]范围内,x是这个范围内的最小值,y是这个范围内的最大值。
rand_index = int(np.random.uniform(0, len(data_index)))
h = sigmoid(np.sum(data_mat[data_index[rand_index]] * weights))
error = class_labels[data_index[rand_index]] - h
weights = weights + alpha * error * data_mat[data_index[rand_index]]
del(data_index[rand_index])
return weights
def classify_vector(in_x, weights):
# print(np.sum(in_x * weights))
prob = sigmoid(np.sum(in_x * weights))
if prob > 0.5:
return 1.0
return 0.0
def colic_test():
f_train = open('data/loandata.txt', 'r')
f_test = open('data/loandata_test.txt', 'r')
training_set = []
training_labels = []
# trainingSet 中存储训练数据集的特征,trainingLabels 存储训练数据集的样本对应的分类标签
for line in f_train.readlines():
curr_line = line.strip().split('\t')
if len(curr_line) == 1:
continue # 这里如果就一个空的元素,则跳过本次循环
line_arr = [float(curr_line[i]) for i in range(8)]
training_set.append(line_arr)
training_labels.append(float(curr_line[8]))
# 使用 改进后的 随机梯度下降算法 求得在此数据集上的最佳回归系数 trainWeights
train_weights0 = grad_ascent(np.array(training_set), training_labels)
train_weights1 = stoc_grad_ascent0(np.array(training_set), training_labels)
train_weights2 = stoc_grad_ascent1(np.array(training_set), training_labels)
error_count0 = 0
error_count1 = 0
error_count2 = 0
num_test_vec = 0.0
# 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率
for line in f_test.readlines():
num_test_vec += 1
curr_line = line.strip().split('\t')
if len(curr_line) == 1:
continue # 这里如果就一个空的元素,则跳过本次循环
line_arr = [float(curr_line[i]) for i in range(8)]
if int(classify_vector(np.array(line_arr), train_weights0)) != int(curr_line[8]):
error_count0 += 1
if int(classify_vector(np.array(line_arr), train_weights1)) != int(curr_line[8]):
error_count1 += 1
if int(classify_vector(np.array(line_arr), train_weights2)) != int(curr_line[8]):
error_count2 += 1
right_rate = 1 - (error_count0 / num_test_vec)
print('梯度下降法正确率:{}'.format(right_rate))
right_rate = 1 - (error_count1 / num_test_vec)
print('随机梯度下降法正确率:{}'.format(right_rate))
right_rate = 1 - (error_count2 / num_test_vec)
print('改进随机梯度下降法正确率:{}'.format(right_rate))
return