【课程】吴恩达机器学习课程(二)

基于吴恩达机器学习 - Machine Learning Specialization 课程 的笔记第二部分;神经网络与支持向量机;

Course 2 : Advanced Learning Algorithms

In the second course of the Machine Learning Specialization, you will:

  • Build and train a neural network with TensorFlow to perform multi-class classification
  • Apply best practices for machine learning development so that your models generalize to data and tasks in the real world
  • Build and use decision trees and tree ensemble methods, including random forests and boosted trees

Week 4 : Neural Networks : Representation

8. Neural Networks

8.1 Non-linear Hypotheses

无论是线性回归还是逻辑回归都有这样一个缺点,即当特征太多时,计算的负荷会非常大。

假设我们希望训练一个模型来识别视觉对象(例如识别一张图片上是否是一辆汽车),我们采用的都是50x50像素的小图片,并且我们将所有的像素视为特征,则会有 2500个特征,如果我们要进一步将两两特征组合构成一个多项式模型,则会有约$2500^{2}/2$个(接近3百万个)特征。普通的逻辑回归模型,不能有效地处理这么多的特征,这时候我们需要神经网络。

8.2 Neurons and the Brain

神经网络兴起于二十世纪八九十年代,应用得非常广泛。但由于各种原因,在90年代的后期应用减少了。而其中主要的原因是:神经网络是的计算量有些偏大。然而由于近些年计算机的运行速度变快,才足以真正运行起大规模的神经网络,这门技术又再次火爆了起来。正是由于这个原因和其他一些我们后面会讨论到的技术因素,如今的神经网络对于许多应用来说是最先进的技术。

8.3 Model Representation

为了构建神经网络模型,我们需要首先思考大脑中的神经网络是怎样的?每一个神经元都可以被认为是一个处理单元/神经核(processing unit/Nucleus),它含有许多输入/树突(input/Dendrite),并且有一个输出/轴突(output/Axon)。神经网络是大量神经元相互链接并通过电脉冲来交流的一个网络。

神经网络模型建立在很多神经元之上,每一个神经元又是一个个学习模型。这些神经元(也叫激活单元,activation unit)采纳一些特征作为输出,并且根据本身的模型提供一个输出在神经网络中,参数又可被成为权重(weight)。

神经网络模型是许多逻辑单元按照不同层级组织起来的网络,每一层的输出变量都是下一层的输入变量。下图为一个3层的神经网络,第一层成为输入层(Input Layer),最后一层称为输出层(Output Layer),中间一层成为隐藏层(Hidden Layers)。我们可以为每一层都增加一个偏差单位(bias unit)$x_0,a_0$

下面引入一些标记法来帮助描述模型:

  • $a_{i}^{(j)}$ 代表第$j$ 层的第 $i$ 个激活单元。
  • $\theta ^{(j)}$代表从第 $j$ 层映射到第$ j+1$ 层时的权重的矩阵。

对于上图所示的模型,激活单元和输出分别表达为

如果第 $j$ 层有 $s_j$ 个神经元,$j+1$ 层有 $s_{j+1}$ 个神经元,权重 $\Theta^{(j)}$ 的维度就是 $s_{j+1}\times(s_j+1)$

为了方便理解,我们令

  • $z^{(j+1)}=\Theta^{(j)} \times a^{(j)}$

  • $a^{(j+1)}=g\left(z^{(j+1)}\right)$

我们把这样从左到右的算法称为前向传播算法( FORWARD PROPAGATION )

8.4 Examples and Intuitions

神经网络中,单层神经元(无中间层)的计算可用来表示逻辑运算,比如逻辑与(AND)、逻辑或(OR)。

【Example AND】

【Example OR】

【Example Not $x_1$】

逻辑异或(XOR),逻辑异或非(XNOR)则需要两层神经元来计算。

【Example XNOR】

第一层做 AND 判断,第二层做 OR 判断

8.5 识别手写数字练习-逻辑回归与神经网络前向传播

1
2
3
4
5
6
7
8
9
10
# 数据
import numpy as np
import pandas as pd
# 画图
import matplotlib
import matplotlib.pyplot as plt
# 机器学习
import scipy.optimize as opt
from scipy.io import loadmat
from sklearn.metrics import classification_report
训练集

一共5000个训练数据,每个数据是一个表示20*20的灰度图像即400维,总的矩阵为5000*400

1
2
3
4
5
6
7
8
9
10
11
def load_data(path, transpose=True):
data = loadmat(path)
X = data['X'] # (5000, 400)
y = data['y'] # (5000, 1)
y = y.reshape(y.shape[0]) # 从 (5000, 1) 到 (5000, )
if transpose:
X = np.array([im.reshape((20,20)).T.reshape(400) for im in X])
# 对 X 进行转置
return X, y

raw_x, raw_y = load_data('ex3data1.mat')
画图
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
def plot_an_image(image):
fig, ax = plt.subplots(figsize=(1, 1))
ax.matshow(image.reshape((20,20)), cmap=matplotlib.cm.binary)
plt.xticks(np.array([]))
plt.yticks(np.array([]))
plt.show()

pick_one = np.random.randint(0, 5000)
plot_an_image(raw_x[pick_one, :])
print('this should be {}'.format(raw_y[pick_one]))

def plot_100_image(X):
sz = int(np.sqrt(X.shape[1])) # size
sample_idx = np.random.choice(np.arange(X.shape[0]), 100)
sample_images = X[sample_idx, :]

fig, axs = plt.subplots(nrows=10, ncols=10, sharey=True, sharex=True,
figsize=(8,8))
for r in range(10):
for c in range(10):
axs[r, c].matshow(sample_images[10 * r + c].reshape((sz, sz)),
cmap=matplotlib.cm.binary)
plt.xticks(np.array([]))
plt.yticks(np.array([]))
plt.show()
数据准备

1
2
3
4
5
6
X = np.insert(raw_x, 0, np.ones(raw_x.shape[0]), axis=1) # 偏差单位

y = []
for k in range(1, 11):
y.append([1 if i==k else 0 for i in raw_y])
y = np.array([y[-1]] + y[:-1]) # 把 10 放回首位
逻辑回归代价函数
1
2
3
4
5
6
7
8
9
10
11
def sigmoid(z):
return 1 / (1 + np.exp(-z))

def cost(theta, X, y):
first = y * np.log(sigmoid(X @ theta.T))
second = (1 - y) * np.log(1 - sigmoid(X @ theta.T))
return -np.mean(first + second)

def regularized_cost(theta, X, y, l):
reg = l / (2 * len(X)) * (theta[1:] ** 2).sum()
return cost(theta, X, y) + reg
逻辑回归梯度函数
1
2
3
4
5
6
def gradient(theta, X, y, l):
error = sigmoid(X@theta.T) - y
grad = X.T @ error / len(X)
reg = theta * l / len(X)
reg[0] = 0
return grad + reg
1
2
3
4
def logistic_regression(X, y, l=1):
theta = np.zeros(X.shape[1])
res = opt.minimize(fun = regularized_cost, x0=theta, args=(X, y, l), method='TNC', jac=gradient, options={'disp': True})
return res.x
逻辑回归预测分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def predict(theta, X):
prob = sigmoid(X @ theta)
return [1 if i >= 0.5 else 0 for i in prob]

# 训练1维

theta_0 = logistic_regression(X, y[0])
y_pred = predict(theta_0, X)
print('Accurary = {}'.format(np.mean(y[0] == y_pred)))

# 训练k维

theta_k = np.array([logistic_regression(X, y[k]) for k in range(10)])
# theta_k.shape = (10, 401)
prob_matrix = sigmoid(X @ theta_k.T)
np.set_printoptions(suppress=True) # 表示小数不需要以科学计数法的形式输出

y_pred = np.argmax(prob_matrix, axis=1) # 返回每行最大的列索引
y_pred = np.array([10 if i == 0 else i for i in y_pred])
神经网络向前传播

练习题中已经给出了权重,所以我们只需要通过向前传播来预测

1
2
3
4
5
6
7
8
9
10
def load_weight(path):
data = loadmat(path)
return data['Theta1'], data['Theta2']

theta1, theta2 = load_weight('ex3weights.mat') # (25, 401), (10, 26)

X, y = load_data('ex3data1.mat', transpose=False)
X = np.insert(X, 0, np.ones(X.shape[0]), axis=1)

# 此处需要重新导入,因为练习中模型的输出 [0~9] 对应的是现实中的 [1~10],并没有像我们前面一样更换位置。

可以看出模型分为 输入层 中间层 和输出层,第一个阶段从 400 个变量转变为 25 个单元, 第二个阶段从 25+1 个变量转变为 10 个单元, 也是输出矩阵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 输入层

a1 = X
z2 = a1 @ theta1.T
z2 = np.insert(z2, 0, np.ones(z2.shape[0]), axis=1)

# 第二层

a2 = sigmoid(z2)
z3 = a2 @ theta2.T

# 输出层

a3 = sigmoid(z3)

# 预测

y_pred = np.argmax(a3, axis=1)+1
classification_report(y, y_pred)

Week 5 : Neural Networks : Learning

9. Cost Function and Backpropagation

9.1 Cost Function

首先引入一些便于稍后讨论的新标记方法:

  • 训练样本 $m$
  • 输入 $x$ 和输出信号 $y$
  • $L$ 表示神经网络层数
  • $S_l$表示每层的neuron个数 ($S_L$表示输出层神经元个数)

将神经网络的分类定义为两种情况:

  • 二类分类:$S_L=1, y=0\, or\, 1$表示哪一类;
  • $K$类分类:$S_L=k, y_i = 1$表示分到第$i$类;$(k>3)$

逻辑回归问题中我们的代价函数为:

在逻辑回归中,我们只有一个输出变量,又称标量(scalar),也只有一个因变量$y$,但是在神经网络中,我们可以有很多输出变量,我们的$h_\theta(x)$是一个维度为$K$的向量,并且我们训练集中的因变量也是同样维度的一个向量,因此我们的代价函数会比逻辑回归更加复杂一些

定义 $\left(h_{\Theta}(x)\right)_{i}=i^{t h} \text { output }$

正则化的一项是排除了每一层$\theta_0$后,每一层的$\theta$ 矩阵的和

9.2 Backpropagation Algorithm

详细推导见 参考文章,或本博客内 另一篇笔记

首先,我们定义 $\delta$ 来表示误差,则:$\delta_j^{(L)}=a_j^{(L)}-y_j$

我们利用这个误差值来计算前一层的误差:$\delta^{(i)}=\left(\Theta^{(i)}\right)^{T}\delta^{(i+1)}.\ast g’\left(z^{(i)}\right)$

注意误差与导数之间是点乘

其中 $g’(z^{(i)})$是 $S$ 形函数的导数: $g’(z^{(i)})=a^{(i)}\ast(1-a^{(i)})$

我们有了所有的误差的表达式后,便可以计算代价函数的偏导数了,假设$λ=0$,即我们不做任何正则化处理时有:

9.3 Backpropagation Intuition

Formally,$\delta^{(l)}_{j}=\frac{\partial}{\partial z^{(l)}_{j}}\text{cost}$

其中

因此可求出

因此 $\delta^{(l)}_{j}$ 又相当于是第 $l$ 层的第 $j$ 单元中得到的激活项的“误差”,即”正确“的 $a^{(l)}_{j}$ 与计算得到的 $a^{(l)}_{j}$ 的差

【TensorFlow】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

model = Sequential(
[
Dense(3, activation='sigmoid', name = 'layer1'),
Dense(1, activation='sigmoid', name = 'layer2') # activation可以换成softmax
]
)

model.compile(
loss = tf.keras.losses.BinaryCrossentropy(), # 同时此处需要换成 SparseCategoricalCrossentropy
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01),
)

model.fit(
Xt,Yt,
epochs=10,
)

9.4 Gradient Checking

当我们对一个较为复杂的模型(例如神经网络)使用梯度下降算法时,可能会存在一些不容易察觉的错误,意味着,虽然代价看上去在不断减小,但最终的结果可能并不是最优解。

为了避免这样的问题,我们采取一种叫做梯度的数值检验(Numerical Gradient Checking)方法。这种方法的思想是通过估计梯度值来检验我们计算的导数值是否真的是我们要求的。

对梯度的估计采用的方法是在代价函数上沿着切线的方向选择离两个非常近的点然后计算两个点的平均值用以估计梯度。即对于某个特定的 $\theta$,我们计算出在 $\theta$-$\varepsilon $ 处和 $\theta$+$\varepsilon $ 的代价值($\varepsilon $是一个非常小的值,通常选取 0.001),然后求两个代价的平均,用以估计在 $\theta$ 处的代价值。

然后我们对通过反向传播方法计算出的偏导数进行检验。根据上面的算法,计算出的偏导数存储在矩阵 $D_{ij}^{(l)}$ 中。检验时,我们要将该矩阵展开成为向量,同时我们也将 $\theta$ 矩阵展开为向量,我们针对每一个 $\theta$ 都计算一个近似的梯度值,将这些值存储于一个近似梯度矩阵中,最终将得出的这个矩阵同 $D_{ij}^{(l)}$ 进行比较。

9.5 Random Initialization

任何优化算法都需要一些初始的参数。到目前为止我们都是初始所有参数为0,这样的初始方法对于逻辑回归来说是可行的,但是对于神经网络来说是不可行的。如果我们令所有的初始参数都为0,这将意味着我们第二层的所有激活单元都会有相同的值。因此,我们通常使用初始参数为正负ε之间的随机值

9.6 Putting It Together

小结一下使用神经网络时的步骤:

网络结构:

  • 首先要做的事是选择网络结构,即决定选择多少层以及决定每层分别有多少个单元。
    • 第一层的单元数即我们训练集的特征数量。
    • 最后一层的单元数是我们训练集的结果的类的数量。
    • 如果隐藏层数大于1,确保每个隐藏层的单元个数相同,通常情况下隐藏层单元的个数越多越好。
    • 我们真正要决定的是隐藏层的层数和每个中间层的单元数。

训练神经网络:

  1. 参数的随机初始化

  2. 利用正向传播方法计算所有的$h_{\theta}(x)$

  3. 编写计算代价函数 $J$ 的代码

  4. 利用反向传播方法计算所有偏导数

  5. 利用数值检验方法检验这些偏导数

  6. 使用优化算法来最小化代价函数

9.7 识别手写数字练习-神经网络逆向传播

数据读取与画图和前面步骤类似,接着是将 [1~10] 的数变为 10 维向量的格式。

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
def expend_y(y):
'''
expend 5000*1 -> 5000*10
y=2 -> y=[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
'''

res = []
for i in y:
tmp = np.zeros(10)
tmp[i-1] = 1
res.append(tmp)
return np.array(res)

'''
与expand_y(y)结果一致
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse=False)
y_onehot = encoder.fit_transform(y)
y_onehot.shape
'''

y = expend_y(y)

def serialize(a, b):
return np.concatenate((np.ravel(a), np.ravel(b)))
# 将两个 theta 合并

def deserialize(seq):
return seq[ : 25*401].reshape(25, 401), seq[25*401 : ].reshape(10, 26)
#将两个 theta 分开
cost funciton
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
def sigmoid(z):
return 1 / (1 + np.exp(-z))

def feed_forward(theta, X):
t1, t2 = deserialize(theta) # t1:(25, 401) t2:(10, 26)
a1 = X # 5000*401

z2 = a1 @ t1.T # 5000*25
a2 = np.insert(sigmoid(z2), 0, np.ones(z2.shape[0]), axis=1) # 5000*26

z3 = a2 @ t2.T # 5000*10
h = sigmoid(z3) # 5000*10
return a1, z2, a2, z3, h

def cost(theta, X, y):
h = feed_forward(theta, X)[-1]
tmp = -y * np.log(h) - (1-y) * np.log(1-h)
return tmp.sum() / y.shape[0]

def regularized_cost(theta, X, y, l=1):
t1, t2 = deserialize(theta)
m = X.shape[0]

reg1 = np.power(t1[:, 1:], 2).sum() / (2 * m)
reg2 = np.power(t2[:, 1:], 2).sum() / (2 * m)

return cost(theta, X, y) + reg1 + reg2
Back propagation
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
def sigmoid_gradient(z):
return sigmoid(z) * (1 - sigmoid(z))
# sigmoid 函数求导

def gradient(theta, X, y):
t1, t2 = deserialize(theta)
m = X.shape[0]

delta1 = np.zeros(t1.shape) # 25*401
delta2 = np.zeros(t2.shape) # 10*26

a1, z2, a2, z3, h = feed_forward(theta, X)

for i in range(m):
a1i = a1[i] # 1*401
z2i = z2[i] # 1*25
a2i = a2[i] # 1*26

hi = h[i] # 1*10
yi = y[i] # 1*10
d3i = hi - yi # 1*10,输出层的误差

z2i = np.insert(z2i, 0, np.ones(1))
d2i = t2.T @ d3i * sigmoid_gradient(z2i) # 1*26 隐藏层的误差

# careful with np vector transpose
delta2 += np.matrix(d3i).T @ np.matrix(a2i)
delta1 += np.matrix(d2i[1:]).T @ np.matrix(a1i)

return serialize(delta1, delta2)

d1, d2 = deserialize(gradient(theta, X, y))

def regularized_gradient(theta, X, y, l=1):
m = X.shape[0]
delta1, delta2 = deserialize(gradient(theta, X, y))
delta1 /= m
delta2 /= m

t1, t2 = deserialize(theta)
t1[:, 0] = 0
t2[:, 0] = 0

delta1 += l / m * t1
delta2 += l / m * t2

return serialize(delta1, delta2)
梯度检查
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
32
33
34
def expand_array(arr):
'''
replicate array into matrix

[1, 2, 3]

[[1, 2, 3],
[1, 2, 3],
[1, 2, 3]]

'''

return np.array(np.matrix(np.ones(arr.shape[0])).T @ np.matrix(arr))


def gradient_checking(theta, X, y, epsilon, regularized=False):
m = len(theta)
def a_numeric_grad(plus, minus, regularized=False):
if regularized:
return (regularized_cost(plus, X, y) - regularized_cost(minus, X, y)) / (epsilon*2)
else:
return (cost(plus, X, y) - cost(minus, X, y)) / (epsilon*2)

theta_matrix = expand_array(theta)
epsilon_matrix = np.identity(m) * epsilon # identity单位矩阵
plus_matrix = theta_matrix + epsilon_matrix
minus_matrix = theta_matrix - epsilon_matrix

approx_grad = np.array([a_numeric_grad(plus_matrix[i], minus_matrix[i], regularized)
for i in range(m)])
analytic_grad = regularized_gradient(theta, X, y) if regularized else gradient(theta, X, y)
diff = np.linalg.norm(approx_grad - analytic_grad) / np.linalg.norm(approx_grad + analytic_grad)

print('If your backpropagation implementation is correct,\nthe relative difference will be smaller than 10e-9 (assume epsilon=0.0001).\nRelative Difference: {}\n'.format(diff))
进行训练
1
2
3
4
5
6
7
8
9
10
11
12
13
def random_init(size):
return np.random.uniform(-0.12, 0.12, size)

def nn_training(theta, X, y):
init_theta = random_init(len(theta))
res = opt.minimize(fun=regularized_cost, x0 = init_theta,
args=(X, y, 1), method='TNC',
jac=regularized_gradient,
options={'maxiter': 400})
return res

res = nn_training(theta, X, y)
final_theta = res.x
显示隐藏层
1
2
3
4
5
6
7
8
9
10
11
def plot_hidden_layer(theta):
t1, t2 = deserialize(theta)
hidden_layer = t1[:, 1:]
fig, ax_array = plt.subplots(nrows=5, ncols=5, sharey=True, sharex=True, figsize=(5,5))
for r in range(5):
for c in range(5):
ax_array[r, c].matshow(hidden_layer[5*r+c].reshape((20,20)),
cmap=matplotlib.cm.binary)
plt.xticks(np.array([]))
plt.yticks(np.array([]))
plt.show()

Week 6 : Advice for Applying Machine Learning

10. Evaluating a Learning Algorithm

10.1 Deciding What to Try Next

使用预测房价的学习例子,假如你已经完成了正则化线性回归,也就是最小化代价函数$J$的值,在你得到学习参数以后,将假设函数放到一组新的房屋样本上进行测试,你发现在预测房价时产生了巨大的误差,现在你的问题是要想改进这个算法,接下来应该怎么办?

  • 获得更多的训练样本——通常是有效的,但代价较大,下面的方法也可能有效,可考虑先采用下面的几种方法。
  • 尝试减少特征的数量

  • 尝试获得更多的特征

  • 尝试增加多项式特征

  • 尝试减少正则化程度$\lambda$

  • 尝试增加正则化程度$\lambda$

我们不应该随机选择上面的某种方法来改进我们的算法,而是运用一些机器学习诊断法来帮助我们知道上面哪些方法对我们的算法是有效的。

10.2 Evaluating a Hypothesis

检验算法是否过拟合,我们可以将数据分成训练集和测试集,通常用70%的数据作为训练集,用剩下30%的数据作为测试集。很重要的一点是训练集和测试集均要含有各种类型的数据,通常我们要对数据进行“洗牌”,然后再分成训练集和测试集。使用训练集得到函数系数后对测试集进行预测,然后与真实值对比计算误差。

10.3 Model Selection and Train_Validation_Test Sets

交叉验证集 :使用60%的数据作为训练集,使用 20%的数据作为交叉验证集,使用20%的数据作为测试集

模型选择的方法为:

  1. 使用训练集训练出10个模型

  2. 用10个模型分别对交叉验证集计算得出交叉验证误差(代价函数的值)

  3. 选取代价函数值最小的模型

  4. 用步骤3中选出的模型对测试集计算得出推广误差(代价函数的值)

对于神经网络中的隐藏层的层数的选择,通常从一层开始逐渐增加层数,为了更好地作选择,可以把数据分为训练集、交叉验证集和测试集,针对不同隐藏层层数的神经网络训练神经网络,然后选择交叉验证集代价最小的神经网络。

10.4 Diagnosing Bias vs. Variance

当你运行一个学习算法时,如果这个算法的表现不理想,那么多半是出现两种情况:要么是偏差比较大,要么是方差比较大。换句话说,出现的情况要么是欠拟合,要么是过拟合问题。

对于训练集,当 $d$ (degree of polynomial) 较小时,模型拟合程度更低,误差较大;随着 $d$ 的增长,拟合程度提高,误差减小。

对于交叉验证集,当 $d$ 较小时,模型拟合程度低,误差较大;但是随着 $d$ 的增长,误差呈现先减小后增大的趋势,转折点是我们的模型开始过拟合训练数据集的时候。

  • 训练集误差和交叉验证集误差近似时:偏差/欠拟合

  • 交叉验证集误差远大于训练集误差时:方差/过拟合

10.5 Regularization and Bias_Variance

在我们在训练模型的过程中,一般会使用一些正则化方法来防止过拟合。但是我们可能会正则化的程度太高或太小了,即我们在选择λ的值时也需要思考与刚才选择多项式模型次数类似的问题。

我们选择一系列的想要测试的 $\lambda$ 值,通常是 0-10之间的呈现2倍关系的值(如:$0,0.01,0.02,0.04,0.08,0.15,0.32,0.64,1.28,2.56,5.12,10$ 共12个)。 我们同样把数据分为训练集、交叉验证集和测试集。

选择$\lambda$的方法为:

  1. 使用训练集训练出12个不同程度正则化的模型
  2. 用12个模型分别对交叉验证集计算的出交叉验证误差
  3. 选择得出交叉验证误差最小的模型
  4. 运用步骤3中选出模型对测试集计算得出推广误差,我们也可以同时将训练集和交叉验证集模型的代价函数误差与λ的值绘制在一张图表上
  • 当 $\lambda$ 较小时,训练集误差较小(过拟合)而交叉验证集误差较大
  • 随着 $\lambda$ 的增加,训练集误差不断增加(欠拟合),而交叉验证集误差则是先减小后增加

10.6 Learning Curves

我经常使用学习曲线来判断某一个学习算法是否处于偏差、方差问题。学习曲线是学习算法的一个很好的合理检验sanity check)。学习曲线是将训练集误差和交叉验证集误差和训练集样本数量($m$)的函数进行图表绘制。

  • 在高偏差/欠拟合的情况下,增加数据到训练集不一定能有帮助。

  • 在高方差/过拟合的情况下,增加更多数据到训练集可能可以提高算法效果。

10.7 Deciding What to Do Next Revisited

回顾 10.1 中提出的六种可选的下一步,让我们来看一看我们在什么情况下应该怎样选择:

  1. 获得更多的训练样本——解决高方差

  2. 尝试减少特征的数量——解决高方差

  3. 尝试获得更多的特征——解决高偏差

  4. 尝试增加多项式特征——解决高偏差

  5. 尝试减少正则化程度λ——解决高偏差

  6. 尝试增加正则化程度λ——解决高方差

11. Machine Learning System Design

11.1 Error Analysis

构建一个学习算法的推荐方法为:

  1. 从一个简单的能快速实现的算法开始,实现该算法并用交叉验证集数据测试这个算法
  2. 绘制学习曲线,决定是增加更多数据,或者添加更多特征,还是其他选择
  3. 进行误差分析:人工检查交叉验证集中我们算法中产生预测误差的样本,看看这些样本是否有某种系统化的趋势

注意,在交叉验证向量上来做误差分析

11.2 Error Metrics for Skewed Classes

在前面的课程中提到了误差分析,以及设定误差度量值的重要性。那就是,设定某个实数来评估你的学习算法,并衡量它的表现,有了算法的评估和误差度量值。有一件重要的事情会对于你的学习算法造成非常微妙的影响,这件重要的事情就是偏斜类skewed classes)的问题。类偏斜情况表现为我们的训练集中有非常多的同一种类的样本,只有很少或没有其他类的样本。这时我们有两个指标可以评判,分别是 精准度Precision)和召回率Recall

我们将算法预测的结果分成四种情况:

  1. 正确肯定True Positive,TP):预测为真,实际为真
  2. 正确否定True Negative,TN):预测为假,实际为假
  3. 错误肯定False Positive,FP):预测为真,实际为假
  4. 错误否定False Negative,FN):预测为假,实际为真

【Python 代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
correct = [1 if a^b == 0 else 0 for (a,b) in zip(predictions, Y)]
accuracy = (sum(correct) / len(correct))
print('accuracy = {0:.0f}%'.format(accuracy*100))

# a^b : a和b中不同时存在的元素

# or

from sklearn.metrics import classification_report

print(classification_report(Y, predictions))

# precision查准率,recall召回率,f1-score调和平均数

11.3 Trading Off Precision and Recall

如果我们希望只在非常确信的情况下预测为真,即我们希望更高的查准率,我们可以使用比0.5更大的阈值,如0.7,0.9。相反如果我们希望提高查全率,我们可以使用比0.5更小的阈值,如0.3。

而选择一个比较优秀的阈值可以通过计算F1 值F1 Score)来判断

Week 7 : Support Vector Machines

12. Large Margin Classification

12.1 Optimization Objective

支持向量机(Support Vector Machine) 代价函数与逻辑回归相似,却更加强大

回顾之前逻辑回归的代价函数

这个式子括号中的函数可以分为两部分

当 $y=1$ 时只左边部分的值不为 0 ,当 $y=0$ 时则右边起作用,因此可得到以下两图。

我们从这里开始建立支持向量机,新的代价函数将会以图中虚线的形式表现出,它是一条同逻辑回归非常相似的直线。这里采用这种表示方式可以在之后带来计算上的优势。

而对于代价函数本身,我们也有些许更变,

其中 $cost_1,cost_2$ 是上图虚线采用的函数,同时我们去掉了 $\frac1m$ 改用乘以一个常量 $C$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sklearn.svm

svc1 = sklearn.svm.LinearSVC(C=1, loss='hinge', max_iter=20000)
svc1.fit(data[['X1', 'X2']], data['y'])
svc1.score(data[['X1', 'X2']], data['y'])

# The confidence score for a sample is the signed distance of that sample to the hyperplane.
data['SVM1 Confidence'] = svc1.decision_function(data[['X1', 'X2']])

fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(data['X1'], data['X2'], s=50, c=data['SVM1 Confidence'], cmap='seismic') # 按照距离给定颜色
ax.set_title('SVM(C=1) Decision Confidence')
ax.set_xlabel('X1')
ax.set_ylabel('X2')

# 决策边界, 使用等高线表示
x1 = np.arange(0, 4.5, 0.01)
x2 = np.arange(0, 5, 0.01)
x1, x2 = np.meshgrid(x1, x2)
y_pred = np.array([svc1.predict(np.vstack((a, b)).T) for (a, b) in zip(x1, x2)])
plt.contour(x1, x2, y_pred, colors='g', linewidths=.5)

plt.show()

12.2 Large Margin Intuition

人们有时将支持向量机看作是大间距分类器。这个可以从代价函数中的 $cost_1,cost_2$ 可以看出。

当我们有一个样本 $y=1$,只有在$z>=1$时 (不仅仅是大于 0),代价函数 $cost_1(z)$ 才等于0。这种做法相当于提高了函数的阈值,鼓励做出更精准的判断。

如果 $C$ 非常大,则最小化代价函数的时候,我们将会很希望找到一个使第一项为0的最优解。

因此代价函数也可以写成以下格式的优化问题

从图中来看,分类器可以有很多种解 (紫,绿,黑),但可以看出黑色线是最好的结果,因为它使得两个类有比较大的间距,而紫线和绿线离训练样本就非常近,在分离样本的时候就会比黑线表现差。因此,这个距离叫做支持向量机的间距,而这是支持向量机具有鲁棒性的原因,因为它努力用一个最大间距来分离样本。

12.3 Mathematics Behind Large Margin Classification

首先,我们可以通过 之前的笔记 复习一下关于向量内积的知识。

回顾之前的优化问题,如果假设 $n=2$ ,$\theta_0=0$,其中

因此支持向量机做的全部事情,就是极小化参数向量$\theta$范数的平方,或者说长度的平方

图示中绿线为决策边界,而黑线就是向量$\theta$ ,$θ^Tx^{(i)}$ 相当于 $x^{(i)}$ 在向量$\theta$上的投影乘以向量 $\theta$ 的范数。我们可以将此转换为 $p^{(i)}\cdot{\left| \theta \right|}$。

为了让$\left| \theta \right|$尽量小并且满足约束条件,我们需要使$p^{(i)}$尽量大,即$x^{(i)}$ 在向量$\theta$上的投影尽量大,即$x^{(i)}$远离决策边界。

12.4 Kernels

我们之前讨论过可以使用高级数的多项式模型来解决无法用直线进行分隔的分类问题,而除此之外,我们还可以用核函数计算出新的特征然后进行训练。

给定一个训练样本$x$,我们利用$x$的各个特征与我们预先选定的地标(landmarks) $l^{(1)},l^{(2)},l^{(3)}$的近似程度来选取新的特征$f_1,f_2,f_3$。

上例中的 $\operatorname{similarity}(x,l^{(1)})$ 就是核函数,准确来说,这是一个高斯核函数(Gaussian Kernel)。 我们通常直接写成 $k(x,l^{(1)})$

  • 如果一个训练样本$x$与地标$l$之间的距离近似于0,则新特征 $f$近似于$e^{-0}=1$
  • 如果一个训练样本$x$与地标$l$之间距离较远,则新特征$f$近似于$e^{-(一个较大的数)}=0$

因此,当对新特征进行回归的时候,当系数都为正,越是靠近地标的值就越接近1

在高斯核函数之外我们还有其他一些选择,如:

  • 多项式核函数(Polynomial Kernel

  • 字符串核函数(String kernel

  • 卡方核函数( chi-square kernel

  • 直方图交集核函数(histogram intersection kernel

  • 等…

这些核函数的目标也都是根据训练集和地标之间的距离来构建新特征,这些核函数需要满足Mercer’s定理,才能被支持向量机的优化软件正确处理。

在具体实施过程中,当我们将核函数与SVM结合时,我们还需要对代价函数最后的正则化项进行些微调整,在计算$\sum_{j=1}^{n=m}\theta _{j}^{2}=\theta^{T}\theta $时,我们用$\theta^TM\theta$代替$\theta^T\theta$,其中$M$是根据我们选择的核函数而不同的一个矩阵。这样做的原因是为了简化计算。

理论上讲,我们也可以在逻辑回归中使用核函数,但是上面使用 $M$来简化计算的方法不适用与逻辑回归,因此计算将非常耗费时间。

【参数$C$和$\sigma$的影响】
  • $C=1/\lambda$
  • $C$ 较大时,相当于$\lambda$较小,可能会导致过拟合,高方差;
  • $C$ 较小时,相当于$\lambda$较大,可能会导致低拟合,高偏差;
  • $\sigma$较大时,可能会导致低方差,高偏差;
  • $\sigma$较小时,可能会导致低偏差,高方差。

下面是一些普遍使用的准则:

$n$为特征数,$m$为训练样本数。

  • 如果相较于$m$而言,$n$要大许多,即训练集数据量不够支持我们训练一个复杂的非线性模型,我们选用逻辑回归模型或者不带核函数的支持向量机。
  • 如果$n$较小,而且$m$大小中等,例如$n$在 1-1000 之间,而$m$在10-10000之间,使用高斯核函数的支持向量机。
  • 如果$n$较小,而$m$较大,例如$n$在1-1000之间,而$m$大于50000,则使用支持向量机会非常慢,解决方案是创造、增加更多的特征,然后使用逻辑回归或不带核函数的支持向量机。

值得一提的是,神经网络在以上三种情况下都可能会有较好的表现,但是训练神经网络可能非常慢,选择支持向量机的原因主要在于它的代价函数是凸函数,不存在局部最小值。


【课程】吴恩达机器学习课程(二)
http://achlier.github.io/2022/08/05/吴恩达机器学习课程_2/
Author
Hailey
Posted on
August 5, 2022
Licensed under