线性回归(Liner Regression)
第一篇在这里简单实现线性回归和逻辑回归的梯度下降算法(上)
逻辑回归(Logistic Regression)
首先介绍一下基本的数学公式,并且附上相应的代码。然后对Titanic题目的训练样本进行分析分析,最后运行起来,生成一个结果的CSV文件。
Sigmoid函数和假设函数
Sigmoid函数
Sigmoid函数如上图所示,即将输入 x 转化到 0 ~ 1 的范围之中。这样我们就可以基于Sigmoid函数的输出来判断,如果高于 0.5 则结果为1,而如果低于 0.5 则结果为0。这样我们就能很好的对结果进行二元分类,逻辑回归虽然叫回归,但其本质是一个分类的机器学习算法。在Titanic题目中,如果结果为1则代表该乘客生还,而结果为0则代表该乘客死亡。
接下来上代码,很简单的Python代码,只需要一行就能实现。其中 np 就是 numpy 。
def sigmoid(x):
return 1.0 / ( 1 + np.exp(-x) )
假设函数
假设函数如上图所示。
- 输入向量 x :其维度为 n+1 * 1,即始终为1的x0和其他n个不同的特征值,在Titanic题目中就有船票等级、性别、年龄等等特征。
- 待求参数向量 theta :其维度为 n+1 * 1。
代价函数
由于假设函数中加入了Simoid函数,是一个非线性的函数,如果直接简单的使用线性回归中的代价函数,会导致应用梯度下降算法无法保证收敛的全局最小值,即代价函数成为一个非凸函数。所以将代价函数重新定义,如上图所示,具体的数学原理吴恩达视频中讲的十分清楚,大家可以去看一看。还是体现了代价这个思想,即越预测越偏离实际代价越大,预测越接近实际代价越小。
接下来上代码。
def calc_cost(theta,x,y):
thetaTx = np.dot(x,theta)
m = y.shape[0]
h = sigmoid(thetaTx)
temp = 0
for i in range(y.shape[0]):
if y[i][0] == 0:
temp = temp + np.log(1 - h[i][0])
else :
temp = temp + np.log(h[i][0])
return -1*(1/m)*temp
这里用循环而不是直接简单的套公式,是因为当计算 log 时会出现输入是负数或0的情况,从而导致错误。
梯度计算
这里的代价函数的梯度很有意思,和线性回归的梯度一模一样,尽管代价函数不一样。大家数学不错的可以自己动手算一算。
def calc_gradient(theta,x,y,regular = 0):
if x.shape[0] != y.shape[0]:
print("样本数不统一!")
m = y.shape[0]
thetaTx = np.dot(x,theta)
h = sigmoid(thetaTx)
temp = (1/m)*np.dot(np.transpose(x),h - y)
# 如果正则化参数为0,则直接返回梯度
if regular == 0:
return temp
# 正则化参数不为零,则将梯度加上惩罚项,之后由theta一并减去
temp = temp + ( (regular / m) * theta )
temp[0][0] = temp[0][0] - ( (regular / m) * theta[0][0] )
return temp
这里加入了正则化参数,是为了更好的平衡各个特征,并且并不惩罚 theta0
梯度下降
由于加入了正则项,所以梯度下降算法有一点变化,但并不惩罚 theta0 。
def gradient_descent(x,y,alpha,regular = 0,count_iter = 5000,cost = []):
if x.shape[0] != y.shape[0]:
print("样本数不统一!")
m = y.shape[0]
n = x.shape[1]
# 首先初试化theta
theta = np.ones((n,1))
# 计算当前theta的代价函数的梯度
gradient = calc_gradient(theta,x,y,regular)
# 递归100次输出一次代价函数值
print_i = 0
# 如果梯度无法所有都降低到1e-5时,递归count_iter * 100次则跳出循环
count = 0
while not np.all(np.absolute(gradient) <= 1e-5):
theta = theta - alpha * gradient
gradient = calc_gradient(theta,x,y,regular)
print_i = print_i + 1
temp = calc_cost(theta, x, y)
if print_i >= 100:
print_i = 0
count = count + 1
# cost列表是为了可以分析递归次数和代价函数值直接的关系
cost.append(temp)
print('cost:',temp)
if count >= count_iter:
break
return theta
数据准备
数据来自kaggle上的titanic题目。具体的特征如下:
- PassengerId:乘客ID,训练样本中由891个,测试样本中有418个
- Survived:是否存活,0 = No, 1 = Yes
- Pclass:船票等级,1 = 1st, 2 = 2nd, 3 = 3rd
- Name:乘客姓名
- Sex:性别,male和female
- Age:乘客年龄
- SibSp:该乘客有多少亲属(除开父母)或配偶登船了
- Parch:该乘客有多少个父母或子女登船了
- Ticket:乘客票号
- Fare:票价
- Cabin:乘客船舱号
- Embarked:乘客上船点,只有C、Q和S
在Titanic号撞上冰山后一个乘客能否生存只有关键的几项特征能够帮助我们来判断。
其中Survived特征则是我们的 Y ,也就是实际结果。
接下来分析 X ,可以明确的看出,一个乘客能否生存 PassengerId 、 Name 、 Ticket 都并不重要。
而 Pclass 和 Fare 决定了这个乘客在社会上的地位,所以一定程度的决定了其是否能够存活。
由于人们倾向于让女性和孩子乘上救生船,所以 Sex 和 Age 也一定程度决定了其是否能够存活。
由于父母倾向于优先拯救自己的孩子,而把自己放在第二位,伴侣也是优先为对方考虑,所以 SibSp 和 Parch 也对乘客的存活率有影响。
Embarked 数据比较充分,所以也暂时放入训练样本特征之中,而由于 Cabin 特征数据缺失较多,所以不做考虑。
所以训练样本特征包括: Pclass , Sex , Age , SibSp , Parch , Fare , Embarked 。
由于 Age 字段中数据有缺失,所以对缺失的乘客补充到所有乘客年龄的平均值。
由于 Sex 和 Embarked 字段都是字符串,所以将其数据化。
- Sex:male = 0,female = 1
- Embarked:S = 0 ,C = 1 , Q = 2
分析完毕,接下来上代码,使用的panda对数据进行处理。
import pandas as pd
import numpy as np
import logistic_regresion as lr
import matplotlib.pyplot as plt
# 读取训练样本
titanic = pd.read_csv('train.csv')
# 填充缺失的Age
titanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())
# 将Sex字段标记
titanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0
titanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1
# 填充缺失的Embarked字段,并标记
titanic['Embarked'] = titanic['Embarked'].fillna('S')
titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2
# m个训练样本
m = titanic.shape[0]
# 初试化X0
X0 = np.ones((m,1))
X = np.hstack((X0,titanic[['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']]))
Y = np.asarray(titanic['Survived'])
Y = np.array(Y,ndmin=2)
Y = np.transpose(Y)
X = np.array(X,dtype=np.float64)
接下来开始规定学习率和正则化参数,并开始训练
# 学习率
alpha = 0.001
# 正则化参数
regular = 0.01
# 迭代次数:1000 * 100次
iter_of = 1000
cost = []
theta = lr.gradient_descent(X,Y,alpha,regular,iter_of,cost)
iter_count = range(1,len(cost) + 1)
# 分析迭代次数和代价函数下降的关系
plt.plot(iter_count,cost)
plt.show()
# 存储最后结果生成的theta
np.savetxt("theta.txt",theta)
第一次提交到kaggle上时,由于学习率设置的过大,0.1,所以结果不是很理想,预测为0.7左右,计算的cost也只是刚刚低于1。
而学习率设置为当前0.001后,生成的 theta 如下,预测为0.76左右。计算的cost如下图,低于0.5。
生成结果
import pandas as pd
import numpy as np
import logistic_regresion as lr
# 与训练一样,首先对测试文件中的数据做标记并且填补丢失数据
pd.set_option('display.max_columns', None) #显示所有列
titanic = pd.read_csv('test.csv')
titanic['Age'] = titanic['Age'].fillna(titanic['Age'].median())
titanic.loc[titanic['Sex'] == 'male', 'Sex'] = 0
titanic.loc[titanic['Sex'] == 'female', 'Sex'] = 1
titanic['Embarked'] = titanic['Embarked'].fillna('S')
titanic.loc[titanic['Embarked'] == 'S', 'Embarked'] = 0
titanic.loc[titanic['Embarked'] == 'C', 'Embarked'] = 1
titanic.loc[titanic['Embarked'] == 'Q', 'Embarked'] = 2
m = titanic.shape[0]
# 生成X
X0 = np.ones((m,1))
X = np.hstack((X0,titanic[['Pclass','Sex','Age','SibSp','Parch','Fare','Embarked']]))
X = np.array(X,dtype=np.float64)
# 读取之前训练完成后的theta
theta = np.loadtxt('theta.txt')
# 计算假设函数,对结果进行预测
thetaTx = np.dot(X,theta)
h = lr.sigmoid(thetaTx)
# 预测大于0.5则表示该乘客存活,预测小于0.5则表示该乘客死亡
prediction = []
for x in h:
if x >= 0.5:
prediction.append(1)
else:
prediction.append(0)
# 生成结果CSV文件,提交上Kaggle
ids = range(892,1310)
result = pd.DataFrame({'PassengerId':ids,'Survived':prediction})
result.to_csv('result.csv',index=False,sep=',')
最后生成的结果文件一部分如下图:
提交上Kaggle后的结果。
结尾
虽然提交上Kaggle后结果不咋滴,但也是自己这段时间对机器学习相关的一个学习结果,真正认识到了做一个调参侠是咋样的:)。接下来想做的还有很多,想把BitTorrent协议实现一遍,想更深入的学习一下Dubbo、Hadoop、多线程等等。在暑假结束后自己又马上就要开始研究生生涯了,自己还是有一点小紧张的,毕竟自己还是比较菜,还是什么事情都慢慢的来吧。大家一起加油。