分类
二分类-单层感知机-iris数据集
数据分析
(数学原理推断都写在笔记本上了,纸质的,应该没有人需要,我也就不放上来了。)
书上的数据都过于简单了,虽然才刚刚开始学基础,但我还是想是一些比较实用的数据,能带给我一些成就感。这次我使用的是 iris 数据集。这也是我第一次尝试通过数学概念直接写代码,不看书,希望自己能成功。
数据中一共有五列,前四列代表着花萼长度、花萼宽度、花瓣长度、花瓣宽度,我们取前两列作分析。为了方便处理数据,我们将 “Iris-setosa” 定义为1, 将 “Iris-versicolor” 定义为 -1 , 暂时不研究第三类 “Iris-virginica” , 取到第 100 行, 先展示一下数据吧。
因为 numpy 的 loadtxt 功能面对复杂的数据集实在不是很好用,我决定要慢慢地接受使用 pandas 了。目前会慢慢过度。
1 2 3 4 5 6 7 8 9 10
| import numpy as np import pandas as pd import matplotlib.pyplot as plt
train = np.array(pd.read_csv('cf-iris.csv').iloc[:100]) train_x = train[:,0:2] train_y = np.where(train[:, 4] == 'Iris-setosa', 1, -1)
plt.plot(train_x[train_y == 1, 0], train_x[train_y == 1, 1], 'o') plt.plot(train_x[train_y == -1, 0], train_x[train_y == -1, 1], 'x')
|

判别函数
我们发现,这个数据是线性可分的,于是尝试用单层感知机进行分类。
\begin{equation}
y(x)=\left\{
\begin{aligned}
-1 \quad \mathbf{x}\mathbf{\omega} \geq 0\\
1 \quad \mathbf{x}\mathbf{\omega} < 0\\
\end{aligned}
\right
.
\end{equation}
1 2 3 4
| w = np.random.rand(2)
def f(x): return 1 if np.dot(w, x) >= 0 else -1
|
更新表达式
\begin{equation}
\mathbf{\omega}=\left\{
\begin{aligned}
\mathbf{\omega}+y^{(i)}x^{(i)} \quad f_\omega(x) \neq y^{(i)}\\
\mathbf{\omega} \quad f_\omega(x) = y^{(i)}\\
\end{aligned}
\right
.
\end{equation}
1 2 3 4 5 6 7 8 9 10
| epoch = 5000 count = 0 for _ in range(epoch): for x, y in zip(train_x, train_y): if f(x) != y: w = w + y*x count += 1
|
效果展示
对 ωx 移项变形得到 x2=−ω2ω1x1
绘图展示:
1 2 3 4 5
| x1 = np.arange(4, 7.5)
plt.plot(train_x[train_y == 1, 0], train_x[train_y == 1, 1], 'o') plt.plot(train_x[train_y == -1, 0], train_x[train_y == -1, 1], 'x') plt.plot(x1, -w[0] / w[1] * x1, linestyle = 'dashed')
|

效果还是挺不错的,但是这里踩了一个坑,就是训练次数如果太小的话会导致看不到效果,因为数据前面的内容全都是一个类别的,最好把训练次数调大一点。
分类-逻辑回归
数据准备
逻辑回归中,y 值更适合用 0, 1 表示,首先调整一下。然后对 x 要进行标准化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| train_y = np.where(train_y == -1, 0, train_y)
mu = train_x.mean() sigma = train_x.std()
def standarize(x): return (x - mu) / sigma
train_z = standarize(train_x)
plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], 'o') plt.plot(train_z[train_y == -1, 0], train_z[train_y == -1, 1], 'x')
def to_matrix(x): x0 = np.ones([x.shape[0], 1]) return np.hstack([x0, x])
X = to_matrix(train_z)
|

Sigmoid 函数与似然函数
本章的函数比较难推理,建议自己写一遍,以后我应该会录视频专门讲一下机器学习数学基础。
sigmoid 函数:
σ(x)=1+e−x1
fθ(x)=σ(θx)
判别函数:
$y = \left{ \begin{array}{ll}
1, &\theta^T x \geq 0 \
0, &\theta^T x < 0
\end{array} \right. $
似然函数:
整体概率用乘积表示,且此时只有两类情况,所以可以全部用 fθ(x) 表示:
L(θ∣X)=i=1∏n(fθ(x)y(i)(1−fθ(x)y(i))1−y(i))
参数更新
求导过程有点复杂,我写在笔记本上了,以后有时间应该会以视频或者图片的方式上传。
θj:=θj−ηi∑n(fθ(x(i))−y(i))xj(i)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| theta = np.random.rand(3)
def f(x): result = -np.dot(x, theta) if not isinstance(result, np.float64): return 1 / (1 + np.exp(result.astype(np.float64))) else: return 1 / (1 + np.exp(result))
def classify(x): return (f(x) >= 0.5).astype(int)
ETA = 1e-3
epoch = 5000
for _ in range(epoch): theta = theta - ETA * np.dot(f(X) - train_y, X)
|
结果展示
x2=−θ2θ0+θ1x1
1 2 3 4 5
| x0 = np.linspace(-0, 1.5, 100) plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], "o") plt.plot(train_z[train_y == 0, 0], train_z[train_y == 0, 1], "x")
plt.plot(x0, -(theta[0] + theta[1] * x0) / theta[2], linestyle="dashed")
|

线性不可分的实现
数据准备
这一次我们使用 50~150 行作为训练数据,并将 Iris-versicolor 设为 1, Iris-virginica 设为 0 ,并用 petal length, petal width 作为分类依据。
这些数据不是很适合线性不可分,还是沿用之前的数据吧,毕竟之前并没有完美的归类。
数据展示:
1 2 3 4 5 6 7
| train = np.array(pd.read_csv('cf-iris.csv').iloc[:100]) train_x = train[:, 0:2] train_y = np.where(train[:, 4] == 'Iris-versicolor', 1, 0) train_z = standarize(train_x)
plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], 'o') plt.plot(train_z[train_y == 0, 0], train_z[train_y == 0, 1], 'x')
|

更新 to_matrix 对应向量 theta
我们使用二次函数来拟合决策边界:
f(x)=θ0+θ1x1+θ2x2+θ3x12
1 2 3 4 5 6
| def to_matrix(x): x0 = np.ones([x.shape[0], 1]) x3 = x[:, 0, np.newaxis] ** 2 return np.hstack([x0, x, x3])
X = to_matrix(train_z)
|
重复学习
1 2 3 4 5 6 7
| theta = np.random.rand(4) ETA = 1e-2 epoch = 2500
for _ in range(epoch): theta = theta - ETA * np.dot(f(X) - train_y, X)
|
数据展示
x2=−θ2θ0+θ1x1+θ3x12
1 2 3 4 5 6
| x1 = np.linspace(-1, 2, 100) x2 = -(theta[0] + theta[1]*x1 + theta[3]*x1**2)/theta[2]
plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], "o") plt.plot(train_z[train_y == 0, 0], train_z[train_y == 0, 1], "x") plt.plot(x1, x2, linestyle='dashed')
|

精度曲线 与 随即梯度下降法
Accuracy=TP+FP+TN+FNTP+TN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| def f(x): result = -np.dot(x, theta) return 1 / (1 + np.exp(np.float64(result)))
theta = np.random.rand(4)
accuracies = []
for _ in range(epoch): p = np.random.permutation(X.shape[0]) for x, y in zip(X[p, :], train_y[p]): theta = theta - ETA * (f(x) - y) * x result = classify(X) == train_y accuracy = len(result[result == True]) / len(result) accuracies.append(accuracy)
fig = plt.figure() ax1 = fig.add_subplot(1, 2, 1)
x1 = np.linspace(-1, 2, 100) x2 = -(theta[0] + theta[1]*x1 + theta[3]*x1**2)/theta[2]
plt.plot(train_z[train_y == 1, 0], train_z[train_y == 1, 1], "o") plt.plot(train_z[train_y == 0, 0], train_z[train_y == 0, 1], "x") plt.plot(x1, x2, linestyle='dashed')
ax2 = fig.add_subplot(1, 2, 2) x1 = np.arange(len(accuracies)) plt.plot(x1, accuracies)
|
