回归思想:假设现有一些数据点,用一条直线对这些点进行拟合(该线称为最佳拟合线),这个拟合过程就称为回归
利用logistic回归进行分类的思想:根据现有的数据对分类边界建立回归公式,以此进行分类
(1) 收集数据:采用任意方法收集数据。
(2) 准备数据:由于需要进行距离计算,因此要求数据类型为数值型。另外,结构化数据格式则最佳。
(3) 分析数据:采用任意方法对数据进行分析。
(4) 训练算法:大部分时间将用于训练,训练的目的是为了找到最佳的分类回归系数。
(5) 测试算法:一旦训练步骤完成,分类将会很快。
(6) 使用算法:首先,我们需要输入一些数据,并将其转换成对应的结构化数值; 接着,基于训练好的回归系数就可以对这些数值进行简单的回归计算,判定它们属于哪个类别;在这之后,我们就可以在输出的类别上做一些其他分析工作。
3.1、Sigmoid函数看成是一个映射函数或者非线性变换函数,通过变换是的当前的一个值转换成概率,由值转换成概率,就将回归问题转为分类问题 。
3.2 、Sigmoid函数的具体计算公式及图像:
为了实现Logistic回归分类,我们可以在每个特征上都乘以一个回归系数,然后把 所有的结果值相加,将这个总和代入Sigmoid函数中,进而得到一个范围在0~1之间的数值。任何大于0.5的数据被分入1类,小于0.5即被归入0类。所以,Logistic回归也可以被看成是一种概率估计。
3.3 总结线性回归与logistic回归
sigmoid函数让我们确定了分类函数,而函数输入z的由公式得:z=w0x0+w1x1+...+wnxn,记为z = wTx+b 。
其中Sigmoid函数的输入值为z、向量x是分类器的输入数据,向量w是找到的最佳系数,b为常数。
因此我们的任务就变成如何确认最佳参数。
4.1、训练算法--梯度上升法
- 梯度上升法基于的思想是:要找到某函数的 最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为∇,则函数f(x,y)的梯度由 下式表示:
这个梯度意味着要沿x的方向移动 ,沿y的方向移动 。其中,函数f(x,y) 必须要在待计算的点上有定义并且可微。
- 如图,梯度上升算法到达每个点后都会重新估计移动的方向。从P0开始,计算完该点的梯度,函数就根据梯度移动到下一点P1。在P1点,梯度再次被重新计算,并沿新的梯度方向移动到P2。如此循环迭代,直到满足停止条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。
梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记做α。用向 量来表示的话,梯度上升算法的迭代公式如下:。 该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或算 法达到某个可以允许的误差范围。
- 梯度下降算法
它与这里的梯度上升算法是一样的,只是公式中的加法需要变成减法。因此,对应的公式可以写成:,梯度上升算法用来求函数的最大值,而梯度下降算法用来求函数的最小值。
4.2 训练算法--随机梯度上升法
梯度上升算法在每次更新回归系数时都需要遍历整个数据集,该方法在处理100个左右的数 据集时尚可,但如果有数十亿样本和成千上万的特征,那么该方法的计算复杂度就太高了。一种 改进方法是一次仅用一个样本点来更新回归系数,该方法称为随机梯度上升算法。由于可以在新 样本到来时对分类器进行增量式更新,因而随机梯度上升算法是一个在线学习算法。与“在线学习”相对应,一次处理所有数据被称作是“批处理”。
4.3 小结
- 梯度上升算法:每次更新回归系数所有样本都参与。
优点:分类准确,获取全局最优解
缺点:当样本比较多时,训练速度特别慢
适用场合:样本较少的数据集
- 随机梯度下降法:每次更新回归系数只有一个样本参与。
优点:训练速度很快
缺点:准确率会降低,并不是朝着整体最优方向进行,容易获取到局部最优解
适用场合:样本非常多的数据集
5.1 数据集准备 (0,1二分类问题)
有115个样本点,每个点包含两个数值型特征:X1和X2。在此数据集上,通过使用梯度上升法找到最佳回归系数,也就是拟合出Logistic回归模型的最佳参数。
5.2 打开文件按行读取数据集并绘制图像
def loadDataSet():
dataMat = [] # 数据列表
labelMat = [] # 标签列表
fr = open('testSet.txt') # 打开文件
for line in fr.readlines(): # 遍历每行,读取数据
lineArr = line.strip().split() # 去除回车符
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) # 把此行的数据添加到数据列表里面
labelMat.append(int(lineArr[2])) # 添加标签进列表
return dataMat, labelMat
# 绘制数据集
def showData():
dataMat, labelMat = loadDataSet() # 加载数据集,标签
dataArr = np.array(dataMat) # 转换成umPy的数组
n = np.shape(dataArr)[0] # 获取数据总数
xcord1 = []; ycord1 = [] # 存放正样本
xcord2 = []; ycord2 = [] # 存放负样本
for i in range(n): # 依据数据集的标签来对数据进行分类
if int(labelMat[i]) == 1: # 数据的标签为1,表示为正样本
xcord1.append(dataArr[i, 1]); ycord1.append(dataArr[i, 2])
else: # 否则,若数据的标签不为1,表示为负样本
xcord2.append(dataArr[i, 1]); ycord2.append(dataArr[i, 2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=15, c='red') # 绘制正样本
ax.scatter(xcord2, ycord2, s=15, c='green', marker='s') # 绘制负样本
plt.title('DateSet') # 标题
plt.xlabel('X1'); plt.ylabel('X2') # x,y轴的标签
plt.show()
5.3 使用梯度上升找最佳参数并绘制决策边界
# sigmoid函数
def sigmoid(inX):
return 1.0 / (1 + np.exp(-inX))
# 梯度上升算法
def gradAscent(dataMatIn, classLabels): # dataMatIn数据集、classLabels数据标签
dataMatrix = np.mat(dataMatIn) # 转换为NumPy矩阵
labelMat = np.mat(classLabels).transpose() # 转换为NumPy矩阵,并且矩阵转置
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
alpha = 0.001 # 目标移动的步长
maxCycles = 500 # 迭代次数
weights = np.ones((n, 1)) # 权重初始化为1
for k in range(maxCycles): # 重复矩阵运算
h = sigmoid(dataMatrix * weights) # 矩阵相乘,计算sigmoid函数
error = (labelMat - h) # 计算误差
weights = weights + alpha * dataMatrix.transpose() * error # 矩阵相乘,更新权重
return weights
# 获得回归系数
dataMat, labelMat = loadDataSet()
weigths = gradAscent(dataMat, labelMat)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
# 绘制数据集和Logistic回归最佳拟合直线
def plotBestFit(weights):
dataMat, labelMat = loadDataSet() # 加载数据集,标签
dataArr = np.array(dataMat) # 转换成umPy的数组
n = np.shape(dataArr)[0] # 获取数据总数
xcord1 = []; ycord1 = [] # 存放正样本
xcord2 = []; ycord2 = [] # 存放负样本
for i in range(n): # 依据数据集的标签来对数据进行分类
if int(labelMat[i]) == 1: # 数据的标签为1,表示为正样本
xcord1.append(dataArr[i, 1]); ycord1.append(dataArr[i, 2])
else: # 否则,若数据的标签不为1,表示为负样本
xcord2.append(dataArr[i, 1]); ycord2.append(dataArr[i, 2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s') # 绘制正样本
ax.scatter(xcord2, ycord2, s=30, c='green') # 绘制负样本
x = np.arange(-3.0, 3.0, 0.1) # x区间
y = (-weights[0] - weights[1] * x) / weights[2] # 最佳拟合直线
ax.plot(x, y)
plt.title('BestFit') # 标题
plt.xlabel('X1'); plt.ylabel('X2') # x,y轴的标签
plt.show()
# 运行画出图
dataMat, labelMat = loadDataSet()
weigths = gradAscent(dataMat, labelMat)
plotBestFit(weigths.getA())
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
5.4 优化算法改进--使用随机梯度上升找最佳参数并绘制决策边界
# 随机梯度上升算法
def stocGradAscent0(dataMatrix, classLabels): # dataMatIn数据集、classLabels数据标签
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
alpha = 0.01 # 目标移动的步长
weights = np.ones(n) # 所以初始化为1
for i in range(m): # 重复矩阵运算
h = sigmoid(sum(dataMatrix[i] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[i] - h # 计算误差
weights = weights + alpha * error * dataMatrix[i] # 矩阵相乘,更新权重
return weights
# 运行测试代码
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent0(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
随机梯度上升算法在上述数据集上的执行结果,最佳拟合直线并非最佳分类线,可以看出拟合曲线出现了很大的偏差。
5.5 优化算法改进--使用改进随机梯度上升找最佳参数并绘制决策边界
# 改进的随机梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150): # dataMatIn数据集、classLabels数据标签、numIter迭代次数
m, n = np.shape(dataMatrix) # 获取数据集矩阵的大小,m为行数,n为列数
weights = np.ones(n) # 所以初始化为1
for j in range(numIter):
dataIndex = list(range(m)) # 创建数据下标列表
for i in range(m):
alpha = 4 / (1.0 + j + i) + 0.0001 # apha目标移动的步长,每次迭代调整
randIndex = int(random.uniform(0, len(dataIndex))) # 随机选取更新样本
h = sigmoid(sum(dataMatrix[randIndex] * weights)) # 矩阵相乘,计算sigmoid函数
error = classLabels[randIndex] - h # 计算误差
weights = weights + alpha * error * dataMatrix[randIndex] # 矩阵相乘,更新权重
del (dataIndex[randIndex]) # 删除已使用过的样本
return weights
# 测试
dataMat, labelMat = loadDataSet()
weigths = stocGradAscent1(np.array(dataMat), labelMat)
plotBestFit(weigths)
print("w0: %f, w1: %f, W2: %f" % (weigths[0], weigths[1], weigths[2]))
使用 Logistic 回归来预测患有疝病的马的存活问题。
horseColicTraining.txt
horseColicTest.txt
def colicTest1():
# 读取测试集和训练集,并对数据进行格式化处理
frTrain = open('horseColicTraining.txt') # 读取训练集文件
frTest = open('horseColicTest.txt') # 读取测试集文件
trainingSet = [] # 创建数据列表
trainingLabels = [] # 创建标签列表
for line in frTrain.readlines(): # 按行读取
currLine = line.strip().split('\t') # 分隔
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
# 使用改进的随即上升梯度训练
trainWeights = gradAscent(np.array(trainingSet), trainingLabels)
errorCount = 0 # 错误数
numTestVec = 0.0
for line in frTest.readlines(): # 遍历每行数据
numTestVec += 1.0 # 测试集数量加1
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
if int(classifyVector(np.array(lineArr), trainWeights)) != float(currLine[21]):
errorCount += 1 # 预测结果与真值不一致,错误数加1
errorRate = (float(errorCount) / numTestVec) # 计算错误率
print("测试的错误率为: %f" % errorRate)
return errorRate
# 求结果的平均值
def multiTest():
numTests = 10
errorSum = 0.0
for k in range(numTests):
errorSum += colicTest1()
print("在 %d 迭代之后, 平均错误率为: %f" % (numTests, errorSum / float(numTests)))
multiTest()
在本次实验中,对于数据集没有处理。由于testSet.txt放入文件集存入是的格式有问题,导致代码无法运行,后去去掉多余的空格就可以了。logistic回归问题就是概率分类问题,从线性回归到logistic回归(拟合出一条分类的直线)。知道了logistic回归问题与分类之间的联系,一步一步地了解logistic回归是如何实现以及原理,以及算法训练的改进。这些探索的过程都让我收获很多。