导图社区 机器学习
吴恩达老师机器学习笔记总结,介绍了线性回归模型中的梯度下降算法及其应用。还概述了梯度下降算法中的关键概念,讨论了特征缩放技术在加速梯度下降速度和效率方面的应用,以及矢量化计算在减少算法代码量并提高执行效率方面的作用。
编辑于2024-06-15 10:12:58机器学习
第一课、线性回归和分类
线性回归模型
逻辑回归模型
第二课、进阶机器学习
神经网络
决策树
第三课、无监督机器学习
线性回归模型
成本函数
常用变量和命名
成本函数公式
成本函数可以理解为在线性模型中,参数w和b在取不同值的情况下,精确预测y需要付出的代价/成本
【代码】NumPy计算成本函数
def compute_cost(x, y, w, b): """ 该函数用于计算某个输入的损失值 :param x: :param y: :param w: :param b: :return: """ m = x.shape[0] cost = 0 for i in range(m): f_wb = w * x[i] + b cost = cost + (f_wb - y[i]) ** 2 cost = cost / (2 * m) return cost
梯度下降
梯度下降是常用的机器学习算法,用于最小化函数参数(w和b)
执行步骤
1、对w和b的值做初步的猜测(选定初始的w和b的参数值),常见情况为各自取0.
2、修改w和b以尝试降低成本函数J的值
3、反复修改,直到成本函数J的值达到或者接近最小值。(在很多情况下,J的最小值可能不止一个)
【代码】使用NumPy实现梯度下降算法
def gradient_descent(x, y, w_in, b_in, alpha, num_iters, cost_function, gradient_function): """ 梯度下降算法 :param x: :param y: :param w_in: :param b_in: :param alpha: :param num_iters: :param cost_function: :param gradient_function: :return: """ J_history = [] p_history = [] b = b_in w = w_in for i in range(num_iters): dj_dw, dj_db = gradient_function(x, y, w, b) # 计算当前梯度。 w = w - alpha * dj_dw # 计算下一轮的参数值 b = b - alpha * dj_db J_history.append(cost_function(x,y,w,b)) # 做历史记录 p_history.append([w,b]) return w, b, J_history, p_history # 返回梯度下降结果。
局部最小值
由于起点不确定,所以梯度下降的路线也是不确定的,尤其是当代价函数的形状并不是标准的碗形的时候,可能其中会存在多个不同的最低点(对应多个低谷),所以从不同位置到达的最小值并不一定是真正的最小值,称为“局部最小值”。
梯度gradient
梯度在数学表示上是成本函数对参数的偏导数,由于线性回归模型中至少有两个参数(w和b),所以通常用成本函数对参数的偏导数表示某一点处的梯度。
【代码】NumPy实现梯度计算
def compute_gradient(x, y, w, b): """ 计算梯度 :param x: :param y: :param w: :param b: :return: """ m = x.shape[0] # 样本数量 dj_dw = 0 dj_db = 0 for i in range(m): f_wb = x[i] * w + b # 计算f_wb的值 dj_dw_i = (f_wb - y[i]) * x[i] # 计算第i个样本的梯度 dj_db_i = f_wb - y[i] # 计算第i个样本的梯度 dj_dw += dj_dw_i # 累加梯度 dj_db += dj_db_i # 累加梯度 dj_dw = dj_dw / m # 梯度平均值 dj_db = dj_db / m # 梯度平均值 return dj_dw, dj_db
梯度下降的数学表达方式
梯度下降的表达式主要是第二步的表达式, 反复执行参数更新,并且在每个步骤计算成本函数值,直到函数值收敛。
公式中偏导数存在的意义:
偏导数是为了能够自动确认参数w和b的移动方向。
学习率α
学习率α会对梯度下降的效果产生巨大影响
α过大,会导致梯度下降算法在局部最低点附近徘徊且无法到达最低点
α过小,会导致梯度下降算法需要移动的次数过多,使得算法执行时间拉长。
梯度下降算法中的偏导数部分会随着不断接近最小值而自动减小,所以在不修改α的情况下,梯度下降的变化率也会逐渐变小。
多维特征
矢量化
矢量化可以减小算法的代码量,并且提高代码的执行效率
矢量化计算主要采用NumPy函数中的`np.dot()`函数实现
特征缩放
特征缩放技术可以加速梯度下降的速度和效率
特征缩放是针对模型中的每个特征/属性,当不同样本的同一属性取值相差过大的时候,可能会导致成本函数形状变得很奇怪
三种特征缩放技术
1、除以最大值
所有特征值除以样本中的最大值
2、均值归一化
计算出该特征的均值μ,每个特征值与μ作差后,除以特征数量
其中,μ是平均值;m是样本数量
3、z-score方法
# z-score特征缩放方法 import numpy as np def zscore_normalize_features(X): # 计算均值μ mu = np.mean(X, axis=0) # μ的形状是(n,) # 计算标准差 sigma = np.std(X, axis=0) # σ的形状是(n,) # 进行特征缩放 x_norm = (X - mu) / sigma return x_norm, mu, sigma
该方法首先计算每个特征的标准差σ和均值μ,然后通过公式计算出每个x_i缩放后的值
其中,μ表示均值。σ表示标准差
特征工程
浮动主题
逻辑回归模型
模型概述
逻辑回归模型主要用于分类算法,比如判断邮件是否是垃圾邮件、肿瘤是良性还是恶性
逻辑回归算法的任务不是绘制函数拟合数据,而是绘制函数区分数据,因此不可以直接套用`f=wx+b`的公式,解决方法是使用了sigmoid函数进行替换/套壳儿。
决策边界
决策边界就是决定当f_wb的取值在哪个范围内的时候,将结果定义为1或者0.
代价函数
逻辑回归模型中,如果使用平方误差成本函数,将会导致成本函数的形状过于诡异,难以寻找最低值,因此,需要用新的成本函数进行替换。
分段函数形式
简化形式
import numpy as np def sigmoid(z): return 1 / (1 + np.exp(-z)) def compute_cost_logistic(X, y, w, b): m = X.shape[0] cost = 0.0 for i in range(m): z_i = np.dot(X[i], w) + b f_wb_i = sigmoid(z_i) cost += -y[i] * np.log(f_wb_i) - (1 - y[i]) * np.log(1 - f_wb_i) cost = cost / m return cost
梯度下降
使用scikit-learn实现逻辑回归
import numpy as np from sklearn.linear_model import LogisticRegression X = np.array([[0.5, 1.5], [1,1], [1.5, 0.5], [3, 0.5], [2, 2], [1, 2.5]]) y = np.array([0, 0, 0, 1, 1, 1]) lr_model = LogisticRegression() lr_model.fit(X, y) y_pred = lr_model.predict(X) # 预测 print("Prediction on training set:", y_pred) print("Accuracy on training set:", lr_model.score(X, y))
逻辑回归模型中,梯度下降的思路和线性回归模型中几乎相同,唯一修改的地方在于成本函数的表达式不同。
【代码】计算梯度
def compute_gradient_logistic(X, y, w, b): m,n = X.shape dj_dw = np.zeros((n,)) #(n,) dj_db = 0. for i in range(m): f_wb_i = sigmoid(np.dot(X[i],w) + b) #(n,)(n,)=scalar err_i = f_wb_i - y[i] #scalar for j in range(n): dj_dw[j] = dj_dw[j] + err_i * X[i,j] #scalar dj_db = dj_db + err_i dj_dw = dj_dw/m #(n,) dj_db = dj_db/m #scalar return dj_db, dj_dw
【代码】梯度下降
def gradient_descent(X, y, w_in, b_in, alpha, num_iters): # An array to store cost J and w's at each iteration primarily for graphing later J_history = [] w = copy.deepcopy(w_in) #avoid modifying global w within function b = b_in for i in range(num_iters): # Calculate the gradient and update the parameters dj_db, dj_dw = compute_gradient_logistic(X, y, w, b) # Update Parameters using w, b, alpha and gradient w = w - alpha * dj_dw b = b - alpha * dj_db # Save cost J at each iteration if i<100000: # prevent resource exhaustion J_history.append( compute_cost_logistic(X, y, w, b) ) # Print cost every at intervals 10 times or as many iterations if < 10 if i% math.ceil(num_iters / 10) == 0: print(f"Iteration {i:4d}: Cost {J_history[-1]} ") return w, b, J_history #return final w,b and J history for graphing
过拟合和欠拟合问题
欠拟合也叫做高偏差, 通常是因为训练模型不能很好的拟合训练数据,导致预测不够准确
1、添加额外的多项式特征
2、增加特征个数
3、减小λ的值(正则化)
过拟合也叫做高方差, 通常因为模型过于专注拟合全部训练数据,导致预测不够准确
1、添加更多训练数据
2、减少特征的个数
3、增大λ的值
正则化
正则化用于解决模型过拟合问题 当一个模型中含有过多的参数时,就试图通过修改对应参数w_j的值,惩罚模型对某些参数的学习。
要进行正则化,需要引入一个新的“正则化参数”,通常用λ表示
线性回归中的正则化表达式
成本函数
梯度下降
逻辑回归中的正则化表达式
成本函数
梯度下降
神经网络
入门
神经网络的结构
一个神经网络模型包含有多个神经层,可粗略认为输入数据为第0层,第一层叫做输入层,最后一层叫做输出层
每个神经层中含有若干神经元/神经单元
每一层的输出作为下一层的输入数据
使用TensorFlow创建神经网络模型
1、创建模型模式与设定神经层类型
2、编译模型
3、训练模型
正向传播算法
子主题
使用NumPy实现/模拟正向传播
矢量化
神经网络模型中也可以采用矢量化的方式提高模型的效率
在神经网络中,每一层的输入数据是一个矩阵,层内的参数也是矩阵的形式,因此需要用到矩阵乘法(叉乘公式)
【代码】NumPy叉乘方法
改进
TensorFlow训练神经网络模型
基本步骤:导包-创建模型-编译模型(这一步主要是指定损失函数loss,以及判断是否使用原始数据)-训练模型(拟合训练数据)
【代码】创建模型对象
import tensorflow as tf # 导入TensorFlow from tensorflow.keras import Sequential # 导入神经网络模型 from tensorflow.keras.layers import Dense # 导入神经层对象 model = Sequential( # 创建一个神经网络模型 [ Dense(units=25, activation="sigmoid"), Dense(units=15, activation="sigmoid"), Dense(units=1, activation="sigmoid"), ] )
【代码】编译模型并且指定损失函数
model.complier()
【代码】拟合模型参数
model.fit()
激活函数
sigmoid函数
该函数只适用于良好总输出状态(0和1)的情况
ReLU函数(recified linear unit)
该函数代表一个经过校正的线性单元,当输入值z小于0的时候,输出值g(z)等于0,反之,当z大于等于0的时候,输出值g(z)等于z
【符号表示】
线性函数(等价于无激活函数)
三种常用的激活函数
sigmoid函数的计算更加复杂,往往计算速度低于ReLU
sigmoid函数计算出的代价函数往往在很多地方梯度不够大,梯度下降效率低于ReLU
选择激活函数
1、当研究结果为1/0的二元分类问题的时候,可以直接采用sigmoid函数
2、如果要解决的是一个回归问题,且损失函数中的y可能取正值,也可能取负值,则选择线性激活函数
3、如果要解决的问题中,y不能取负值,则采用ReLU函数
对于隐藏层,一般情况下使用最广泛的就是ReLU函数
激活函数的重要性
激活函数在数据拟合的过程中起到了至关重要的作用,如果任何情况下都不使用激活函数或者采用线性激活函数,那么,神经网络将无法对数据进行拟合。
Softmax解决多类别分类问题
Softmax是一种用于解决多分类问题的算法,一般被用于最后一个输出层
【基本表达式】
其中,j是特征序号,N是特征的数量
【损失函数】
【注】这里的损失函数采用了分段函数的形式
采用Softmax作为输出层
当输出层需要有多个分类的时候,往往需要在输出层设置多个单元,此时,在输出层的代码就需改修改成Softmax的样子
model = Sequential( [ Dense(25, activation = 'relu'), Dense(15, activation = 'relu'), Dense(4, activation = 'softmax') # < softmax activation here ] ) model.compile( loss=tf.keras.losses.SparseCategoricalCrossentropy(), optimizer=tf.keras.optimizers.Adam(0.001), ) model.fit( X_train,y_train, epochs=10 )
优化的Softmax算法
当计算机计算浮点型数据的时候,会出现一定的误差,为了解决这样的误差,可以在编译的时候设置可选属性:`from_logits=True`,从而在每次传递数值的时候,自动排列原始数据的计算顺序,而不是将计算结果传递到下一层,从而起到优化计算的目的
model.compiler(loss=BinaryCrossEntropy(from_logtis=True))
进行上述修改后,需要将输出层的激活函数改为线性激活函数,而不是Softmax函数,为了补充这一修改的结果,需要对模型的预测结果采用Softmax函数处理。
【这段代码可以参考可选实验室c2w2的解释】 preferred_model = Sequential( [ Dense(25, activation = 'relu'), Dense(15, activation = 'relu'), Dense(4, activation = 'linear') #<-- Note ] ) preferred_model.compile( loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), #<-- Note optimizer=tf.keras.optimizers.Adam(0.001), ) preferred_model.fit( X_train,y_train, epochs=10 ) p_preferred = preferred_model.predict(X_train) sm_preferred = tf.nn.softmax(p_preferred).numpy()
多标签分类问题
多标签分类问题往往与图片关联,常用于判断图片中的某些元素所属的标签
替换梯度下降算法
Adam算法是梯度下降算法的优化,其主要优势在于,对不同参数采用了不同的学习率α,且能够自动调节梯度下降算法中的学习率α
Adam算法的思路是,当发现每一次梯度下降的方向都差不多的时候,就提高α,让梯度下降可以向目标方向更快下降; 当发现梯度下降有来回震荡的趋势(也就是每次下降的方向相差较大)时,就降低α,避免无效的下降。
替换神经层类型
除去密集层Dense以外,卷积层也是一种常用的神经层类型。常用于图片识别的问题中
当进行图片识别的时候,每一个卷积层神经元往往负责图片中的一个部分
当神经网络中存在多个卷积层的时候,就称为卷积神经网络
反向传播
使用sympy计算偏导数
使用反向传播,只需要利用链式法则计算连续偏导数,即可快速得出所求影响,因此反向传播的效率会比正向传播更高。
当采用正向传播的方法计算某一个变量对最终的代价函数J的影响时,必须将改变量进行修改后,依据每一个神经层的计算顺序来逐层计算下一层的变化,直到算到J。当参数量很多或者神经元、神经层数量很多的时候,正向传播的计算效率显然会变得很低
评估
简单评估方法
线性回归模型的性能评估方法
将数据集二八分,得到训练集和测试集,前者用于训练模型,后者用于测试模型
【注】不可以加入正则化项
逻辑回归模型的性能评估方法
同线性回归模型
模型选择方法
将数据集分为训练集、交叉验证集和测试集
训练集用于训练模型,拟合参数
交叉验证集用于检查不同模型的有效性和准确性,协助模型选择
测试集用于选择模型后,对模型进行评估
通过scikit可以实现随机拆分数据集
同样是对于线性回归模型选择,对所有可能的模型分别计算开发误差J_cv,并且取出J_cv最小的一个模型。然后通过测试数据集,就可以估计模型的泛化误差J_test(w,b)。
偏差和方差
模型的三种状态: 高偏差、刚刚好、高偏差
当交叉验证误差较大的时候,判断:
如果训练误差也很大,则是高偏差现象,对应欠拟合现象
如果训练误差较低,则是高方差线性,对应过拟合现象
基准性能水平可以作为判断误差是否过大的标准
1、与人类表现水平对比
2、与竞争算法作对比
3、根据经验进行猜测
学习曲线是一种可视化工具, 用于展示模型的性能随训练数据量的变化而变化的趋势。
当数据集数量增加到足够大的时候,cv误差往往会下降并且接近某一水平
训练误差一般会逐渐提高并收敛到某一个值
高偏差问题的处理方法:
1、添加额外的多项式特征
2、增加特征个数
3、减小λ的值
高方差问题处理方法
1、增加更多训练数据
2、较少特征个数
3、增大λ的值
标注
> 1. 对于训练数据量解决高偏差或者高方差的问题:高偏差指的是模型不能适应训练数据,这个问题是模型本身的参数和特征数目导致的,这种情况下,模型本身就不是解决问题的最佳方案,此时再多的数据也无济于事;高方差指的是对训练数据过度拟合,死死贴住每一个训练数据,这会导致函数曲线变得过于畸形,导致预测准确性降低,此时添加更多的训练数据,就可以在定程度上矫正畸形的模型函数。 > 2. 对于是否增加或者减少特征数量的问题:特征数量越多,函数的图形就(可以)越复杂,为了能够拟合全部训练数据,拥有更多特征的函数就会变得畸形(从而试图适应全部数据),就会导致方差变大。
通过偏差和方差训练神经网络的方法
【流程图】
机器学习开发的迭代循环
选择模型-训练模型-诊断模型
错误分析/误差分析
对于模型在运行中出现的误差进行分析,是十分重要的环节,假设某算法在对交叉验证集进行预测的时候,出现了100个错误,那么接下来就需要对这100个错误进行人工检查,分析这些样本中存在哪些特征是需要算法在后续改动中增加检查权重的,以此作为接下来的工作重心
增加数据
迁移学习 迁移学习类似嫁接的方法,主要是通过在数据量充足的同类型训练集上训练好模型以后,再将训练好的模型根据问题所在的训练集进行修改
方法1:只修改输出层的参数
方法2:训练模型中全部的参数(包括从隔壁训练集中训练好的全部参数
如果数据量比较小,推荐方法一;反之,推荐方法二
迁移学习可行性
对于模型计算过程类似的问题(比如识别动物和识别数字),模型每一层的基本流程和功能是相近的,所以可以在一个问题上训练好以后,再通过另一个数据集对模型进行微调
【注意】迁移学习仅限于同类型问题之间的迁移,对于不同类型的问题,比如语音识别和字符识别之间,不可以进行迁移学习
完整的机器学习项目周期
【流程图】
偏斜数据集的评价标准
假设有一个二元分类问题的结果1的概率是不到1%(比如0.5%),那么当模型预测数据的时候,对是否是1预测将是一个小数,这样的话,就可能会导致每次输出的预测结果都是0%,显然这样的模型存在很大的问题。
一种常见的错误指标是精度和召回率precision and recall。
混淆矩阵
建立一个2×2的矩阵作为混淆矩阵,在顶部的轴上表明实际的类(1或者0)在垂直轴上表明预测的类(1或者0),在这样的表格中,可以列入交叉验证集的实际和预测数据。
理想情况下,需要模型具有高精度和高召回率的算法
高精度意味着,如果算法诊断出患者具有某种疾病,那么患者可能确实患有这种疾病
高回报率意味着如果有患者患有这种罕见疾病,该算法可以正确的识别出他们具有这个疾病
在逻辑回归模型中,提高fx的阈值能够提高精度,但是会导致召回率下降;反过来,降低阈值能够提高召回率,但是会导致精度下降。
F1-score
该指标用于自动组合精度和召回率,以达成二者之间的最佳权衡
F1分数是将精度和召回率相结合计算平均值的方法,但是它更强调二者中更低的值,事实证明,如果精度或者召回率特别低,那么算法就不那么有用,F1分数的计算方式如下图。
F1分数的计算结果也叫做谐波均值,是一种更强调较小值的平均值方法。
决策树
决策树模型
决策树模型的形状类似于一个二叉树或者多叉树
学习过程
1、决定根节点的决策内容
2、关注根节点的左半分支,决定将哪些决策节点放在这边,拆分哪些具体的特征或者功能
3、关注右半分支
4、重复上述步骤
两个关键决策
1、如何选择在每个节点拆分哪个特征?
选择区分度尽可能大的特征
2、什么时候停止进行拆分?
a. 当某节点可以100%判断出一个种类;
b. 当拆分节点将会超过预定的决策树最大深度
根节点的深度视为0
c. 纯净度的提高没有超过某个固定阈值
d. 如果一个节点低于某个阈值的实例数量,也可能需要停止拆分
衡量纯净度
熵函数
以识别图片中是否是猫为例, 假设数据集中猫的比例是p1,令p0=1-p1,那么,熵函数的定义就是:
【公式】
拆分依据:信息增益 拆分前后熵函数值的减小量
当每种决策依据都会导致两个熵较大的分类结果的时候,可以使用加权平均的方式辅助选择
计算方法
1、计算拆分后左侧和右侧子数据集中猫的比例p_l1和p_r1
2、计算左右侧的熵H_l和H_r
3、计算左右侧样本的数量w_l和w_r
4、用根节点的熵减去子节点的加权平均和
公式
【公式】
信息获取标准
决策树学习 1、从根节点的所有示例开始 2、计算所有可能特征的信息增益,并选择信息增益最高的特征 3、根据选择的特征对数据集进行拆分,并创建树的左右分支 4、继续重复分割过程,直到满足停止条件: a. 当一个节点100%是一个类时 b. 当分割节点时,将导致树超过最大深度 c. 额外分割的信息增益小于阈值 d. 当一个节点中的样例数量低于阈值时
单热编码
当某个特征有两个以上的离散值的时候,可以依据这些离散值创建多个分类,此时,在多个分类中总会有一个的值为1,其他的为0。
对所有特征采用单热编码的话,每一个样本的特征就会变成由0和1组成的向量,进而可以用这样的数据集训练神经网络模型
连续数值特征
通过修改决策树,可以使其用于处理连续值,而不是单一的离散值
对于连续值,可以通过设置阈值来进行拆分
在识别猫的案例中,为判断是否是猫而添加一个体重的参数,当体重低于某一个阈值的时候,就可以认为是猫,反过来,则不是。通过多次选择阈值并且计算对应的信息增益,就可以达到利用特征拆分示例的目的。
回归树
如果需求是想要通过猫的耳朵形状、脸型和胡须三个特征,预测猫的体重,那么这个问题就成了一个回归问题。此时,就需要对决策树做一个简单的修改。
在决策树中,常采用信息增益判断拆分标准,在回归树中,常使用方差作为计算依据
【公式】
树集合
单一决策树的缺陷之一就是对数据的微小变化过于敏感,对应的解决方案通常是建立多个决策树,即决策森林
建立合适的决策树集合的方法通常是`替换采样`的方式
替换采样
基本方法:取样后,放回样本集中,在进行下一次取样
在使用替换采样的方式创建树集合的时候,利用替换采样的方法,从全部训练数据中取出一个训练集(其中可能存在重复的训练示例),这样的训练集来自于原本的全部训练数据,但是可能会和原本的全部数据效果相差很大。用这个训练集创建一个决策树即可。
随机森林算法
对于给定的数量为m的训练数据集,假设要创建一个有B棵决策树的集合,就执行一次由1到B的for循环,在每次循环中,采用替换采样的方式创建一个数据集,然后利用这个新的数据集训练一棵决策树。最终得到一个由B棵决策树组成的决策树集合。
【注】B的值设置的更大一些,往往不会损害树集合的功能,但是超出某个阈值后,最终的回报将会降低,所以并不是越大越好
替换采样可能会导致数集合中存在结构相似的决策树,尝试随机化每个节点的特征选择可以优化这个问题
对于每一个节点,当选择一个特征在此节点进行拆分的时候,如果一共有n个特征可选,那么在其中随机选择k个特征,并且允许算法只从这个子集中选择特征。*当n很大的时候,k的选值通常是n的平方根。
简单地描述这种方法的原理就是:带有替换程序的采样会让算法探索许多对数据的微小变化。
XGBoost是目前最好用的构建决策树和决策森林的开原方式之一
XGBoost实在原本的创建替换采样创建方法的基础上改进来的,在原本方法的每次for循环中,挑选示例的时候,是从全部示例中随机挑选的,所有示例在每一次循环中被选中的概率相同。XGBoost的思路是,在这个基础上,不进行等概率的随机选择,而是每次选择之前训练的决策树中判断失误的示例。