[关闭]
@zzy0471 2020-07-30T02:01:34.000000Z 字数 5568 阅读 694

大叔学ML第五:逻辑回归

大叔学ML


基本形式

逻辑回归是最常用的分类模型,在线性回归基础之上扩展而来,是一种广义线性回归。下面举例说明什么是逻辑回归:假设我们有样本如下(是我编程生成的数据):

image.png-9.7kB

我们要做的是找到一个决策边界,把两类样本给分开,当有新数据进来时,就判断它在决策边界的哪一边。设边界线为线性函数

取0时的直线,如下图:

image.png-12.4kB

我们的目标就是根据已知的样本来确定取值

上图中,处在边界线左边的为负例(带入(1)式结果小于0),边界线右边的为正例(带入(1)式结果大于0)。从概率的角度考虑:把一个点代入(1)式,如果为正,且越大越离边界线远,它是正例的概率就越大,直到接近1;相反,把一个点代入(1)式,如果为负,且越小离边界线越远,它是正例的概率就越小,直到接近0;此外,把一个点代入(1)式,如果结果正好等于0,那么它在边界线上,为正例和为负例的概率都是0.5。

为了用数学的方式精确表达上面的概率论述,前人找到一个好用的函数:


这个函数叫做sigmoid(sigmoid:S形状的)函数(下文用S(z)或s(z)表示sigmoid函数),样子如下:

image.png-11kB

时,函数值为0.5,当时,函数取值;当时,函数取值,如果我们把欲判断点代入决策边界(1)式后得到的结果作为sigmoid函数的输入,那么输出就可以表示该点是正例的概率,简直完美。其实S型的函数应该还有别的,为什么前人独爱这个呢?那是因为,这个函求导比较简单,用链式法则可以非常容易算出其导数式为:

simgiod函数求导过程:


第一步用了链式法则。

代价函数

逻辑回归的代价函数可由极大似然估计法得出。我们暂且不管极大似然估计法的证明,直观地理解非常容易:如果你是一个班级的新老师,发现有个孩子考了95.5分,你肯定会认为这个孩子很可能是学霸,虽然学霸有时也会考低分,学渣有时也考高分,但是发生概率很小。更一般的叙述是:有若干事件A、B和C,已知其发生概率为分别为,如果我们观察到A、B和C已经发生了,那么我们就认为是个尽可能大的值,如果我们要求,那么应该是使得最大的那个值。如果A、B和C互相独立,我们所求的就是使得最大化的

已知:

,写到一起:
,根据(4)式写出极大似然函数:
,设代价函数
,最大化即最小化。求对数是为了方便计算,把乘法转换为加法,把幂运算转换为乘法,单调性不变;前面的负号是为了把求最大值问题转换为求最小值问题,习惯上,应用梯度下降法时都是求最小值,不然叫“梯度上升了法”了,手动滑稽。当然,使用梯度下降法的前提条件是函数是凸的,大叔懒得证明了,这个函数就是个凸函数,不管你们信不信,我反正是信了。进一步对上式化解得:

用梯度下降法求

将(5)式对求偏导:

如果读者对矩阵计算非常熟悉的话,应该可以看出,上式可以写成矩阵形式,这样计算更方便:

求导的过程如下,多次应用链式法则即可:


- 步骤1用到到了公式:
- 步骤2用到式(3)

有了偏导式后就可以编程了:

  1. import matplotlib.pyplot as plt
  2. import numpy as np
  3. def sigmoid(z):
  4. return 1 / (1 + np.e**(-z))
  5. def draw_samples(X, Y, sample_count):
  6. ''' 绘制正负例. '''
  7. positiveX1 = []
  8. positiveX2 = []
  9. negativeX1 = []
  10. negativeX2 = []
  11. for i in range(sample_count):
  12. if Y[i, 0] == 1:
  13. positiveX1.append(X[i, 0])
  14. positiveX2.append(X[i, 1])
  15. else:
  16. negativeX1.append(X[i, 0])
  17. negativeX2.append(X[i, 1])
  18. plt.scatter(positiveX1, positiveX2, marker='+')
  19. plt.scatter(negativeX1, negativeX2, marker='.')
  20. def draw_border(theta):
  21. '''绘制边界线'''
  22. X = []
  23. Y = []
  24. for x in range(-2, 12):
  25. X.append(x)
  26. Y.append(-theta[0] / theta[2] - theta[1] / theta[2] * x )
  27. plt.plot(X, Y)
  28. def create_samples(samples_count):
  29. ''' 生成样本数据'''
  30. X = np.empty((samples_count, 2))
  31. Y = np.empty((samples_count, 1))
  32. for i in range(samples_count):
  33. x1 = np.random.randint(0, 10)
  34. x2 = np.random.randint(0, 10)
  35. y = 0
  36. if x1 + x2 - 10 > 0:
  37. y =1
  38. X[i, 0] = x1
  39. X[i, 1] = x2
  40. Y[i, 0] = y
  41. noise = np.random.normal(0, 0.1, (samples_count, 2))
  42. X += noise
  43. return X, Y
  44. def grad(X, Y, samples_count, theta):
  45. ''' 求代价函数在theta处的梯度. '''
  46. T = sigmoid(np.dot(X, theta))
  47. g = np.dot(X.T, (T - Y)) / samples_count
  48. return g
  49. def descend(X, Y, samples_count, theta = np.array([[1],[1],[1]]), step = 0.01, threshold = 0.05):
  50. ''' 梯度下降.
  51. Args:
  52. X: 样本
  53. Y:样本标记
  54. theta:初始值
  55. step:步长
  56. threshold:阈值
  57. Returns:
  58. theta:求出来的最优theta
  59. '''
  60. g = grad(X, Y, samples_count, theta)
  61. norm = np.linalg.norm(g)
  62. while norm > threshold:
  63. g = grad(X, Y, samples_count, theta)
  64. norm = np.linalg.norm(g)
  65. theta = theta - g * step
  66. print(norm)
  67. return theta
  68. samples_count = 100
  69. X, Y = create_samples(samples_count)
  70. MatrixOnes = np.ones((100, 1))
  71. X_with_noes = np.hstack((MatrixOnes, X)) # 添加等于1的x0
  72. theta = descend(X_with_noes, Y, samples_count)
  73. plt.xlabel('$x_1$')
  74. plt.ylabel('$x_2$')
  75. draw_samples(X, Y, samples_count)
  76. draw_border(theta.flatten())
  77. plt.show()

运行结果:
image.png-14.9kB

上面的代码中,我在样本矩阵中增加了一列为1的元素,这样做是为了计算方便,使得样本矩阵的列数等于欲求的行数,满足矩阵乘法的要求,加上这一列对结果没有影响,请参考大叔学ML第二:线性回归

扩展

祝元旦快乐!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注