python全栈-人工智能基础-机器学习
文章目录
- [梯度下降法Gradient Descent](#梯度下降法Gradient Descent)
- [归一化 正则化 升维](#归一化 正则化 升维)
-
- 归一化normalization
- 正则化regularization
- [升维polynomial regression](#升维polynomial regression)
- 逻辑回归
- Softmax回归
- [音乐分类器 -Sotfmax](#音乐分类器 -Sotfmax)
- SVM支持向量机
- 决策树
- 集成学习-随机森林RF
- Adaboost-串行
- GBDT
- XGBoost
梯度下降法Gradient Descent
不是一个具体的算法,而是一个辅助算法,用来辅助其他算法求最优解的
一个通用的辅助算法
-
学习率的设置 对于一些深度学习的算法,往往会自动设置学习率,对于机器学习的算法,仍然需要手动设置学习率
- 学习率就是寻找最优解的步骤,太大可能来回震荡,太小会增加迭代次数
- 一般是0.1,0.01,0.001
- 最理想的学习率确实是需要自动调的,比如一开始迈大步找最优解,锁定区间之后,在缩小区间,临近的时候,再缩小步长,类似高数求最小区间
-
全局最优解
- 假设cost与损失函数的曲线是一个w型的,最优解可能是第一个谷底,也可能是第二个谷底。当然是最低的谷底是最优解
- 如果使用梯度下降法,有极大的概率会选择第一个谷底,无论什么情况,因为遇到第一个谷底的时候,就可以左右震荡找最优解了,但往往第一个谷底是局部最优解。这个模型是能用的。但是肯定比不过全局最优解。就需要调大学习率尽可能让它跨到第二个谷底,找全局最优解。
- 调大学习率跳出局部最优解,找全局最优解
三种梯度下降法的优缺点
- 全量梯度下降(批量梯度下降)BGD
使用整个训练集,可以基本保证每次都接近最优解
又因为用的是全部的训练集,导致学习时间延长
- 随机梯度下降SGD
是随机从数据集里面获取样本进行学习,因为样本的数量比整个数据集小的多,学习速度快,避免了全量梯度下降法在训练的时候,对数据的重复训练
由于是使用样本,不可避免的抽到不太好的样本数据,会对机器学习带来波动
正是因为使用了样本数据,由于波动,可能会跳出局部最优解,到达全局最优解
迭代次数增多,学习速度变快
使用随机索引,在xy里面取出一对数据
为了避免,有些数据,如两端数据,是极小概率被随机到,可以使用
numpy.random.shuffle(),这个方法可以随机重组数组numpy.random.shuffle()会随机打乱数组中元素的顺序。这意味着它直接修改原数组,而不是返回一个新的打乱后的数组。
- 小批量梯度下降MiniBGD
在BGD和SGD之间的折中方法,相对来说,又快又准
一般选择50~256个样本进行学习
比SGD稳,比BGD快
用的最多了
使用随机索引,从数据集里面取出一批数据,在SGD的基础上,一次取一批,一个区间的数据。准确来说是随机区间
轮次和批次
- 轮次epoch 对训练学习多少轮
- 批次batch 数据量过多,一轮也太多,就要在一轮学习里面分批,分批次学习
一轮是对整个训练集完成一次学习,一批是一轮里面的一部分学习
x_train,y_train,x_test,y_test
就是把数据集里面的xy,分为两部分,x的训练集和测试集,y的训练集和测试集
代码解析
pyimport numpy as np # 创建数据集X,y np.random.seed(1) X = np.random.rand(100, 1) y = 4 + 3*X + np.random.randn(100, 1) #np.random.randn(100, 1) 是噪音,现实生活中的人为影响 X_b = np.c_[np.ones((100, 1)), X] # 第一列的1是为了θ₀(截距参数)服务的! # 1 → 对应参数 θ₀ # x → 对应参数 θ₁ # 创建超参数 n_iterations = 10000 t0, t1 = 5, 500 # 定义一个函数来调整学习率 def learning_rate_schedule(t): return t0/(t+t1) # 1,初始化θ, W0...Wn,标准正太分布创建W theta = np.random.randn(2, 1) # 4,判断是否收敛,一般不会去设定阈值,而是直接采用设置相对大的迭代次数保证可以收敛 for i in range(n_iterations): # 2,求梯度,计算gradient gradients = X_b.T.dot(X_b.dot(theta)-y) # 3,应用梯度下降法的公式去调整θ值 θt+1=θt-η*gradient learning_rate = learning_rate_schedule(i) # 学习率 theta = theta - learning_rate * gradients print(theta)这个代码是在模拟一个线性回归问题,目的是让算法学会从数据中找到隐藏的线性关系。
这里设计了一个已知的线性关系:
- 真实的线性函数是:
y = 4 + 3x4是截距(bias/偏置项)3是斜率(权重)np.random.randn(100, 1)是噪声,模拟现实世界的随机误差为什么要这样设计?
因为我们想测试梯度下降算法是否能从带噪声的数据中重新发现这个线性关系!
设计逻辑
py步骤1:创建已知答案的问题 # 我们知道真实答案是:θ₀=4, θ₁=3 y = 4 + 3*X + noise 步骤2:让算法从零开始猜测 # 随机初始化参数,算法不知道真实答案 theta = np.random.randn(2, 1) # 可能是 [[-0.5], [2.1]] 步骤3:通过梯度下降学习 # 算法会逐渐调整theta,直到接近真实值[4, 3] for i in range(n_iterations): # 计算梯度并更新参数 步骤4:验证学习效果 print(theta) # 应该接近 [[4], [3]] 在实际应用中: X 可能是房屋面积 y 可能是房价 我们不知道真实的线性关系 梯度下降帮我们从数据中发现这种关系
归一化 正则化 升维
归一化normalization
- 最大值最小值归一化
缺点:如果有离群值,会带偏整个样本数据 比如有一个特别高的最大值
找数据的min和max,然后,以min为0,max为1,把所有数据归到0和1之间
比如[1,2,3,5,5]
通过公式计算: (x - x_min)/(x_max - x_min)
最小值是1,最大值是5.
两端分别是01,对于2来说:(2-1)/(5-1)=0.25,对于3来说, (3 - 1)/(5 - 1)=0.5
- 标准归一化
以均值代替最小值,标准差代替最大值和最小值的差值 可以减小离群值带来的影响
执行标准归一化之后,不一定在0-1之间,可以在区间外边
正则化regularization
防止过拟合
- under fit 拟合不到位,连凑活用都不行
- over fit 拟合过度,训练集准确率升高,但是测试集准确率变低了。太专注训练集了,对于训练集来说完美了,但是不通用了
- just right 刚刚好
增加模型鲁棒性robust,鲁棒是Robust音译,就是强壮。在特殊情况不死机不崩溃就是鲁棒性,
L1和L2是为了解决模型过拟合的问题L1L2也是给模型添加正则化细数而L1L2是根据模型的theta和W进行计算的是为了增加模型的泛化能力
举例:
5x+4y+3=0
0.5x+0.4y+0.3=0
对于第二个公式来说,偏差小于第一个公式 比如x=100,x=110. 对于y来说,第二个式子偏差小一点
把xy前面的系数叫w,w越小偏差越小越不明显,这是容错率,但是w太小的时候,就无法保证准确率,太小就不能正常使用了,找的就是这个平衡
在容错率和正确率之间找平衡
在归一化的基础上再加一个惩罚项,防止过拟合
- Ridge岭回归 MSE
py
from sklearn.linear_model import Ridge
X = 2*np.random.rand(100, 1)
y = 4 + 3*X + np.random.randn(100, 1)
ridge_reg=Ridge(alpha=0.4, solver='sag') # alpha是泛化超参数,越小越准确,正则化系数
fidge_reg.fit(X,y) # 数据导入
print(fidge_reg.predict([n])) # n是期望
print(fidge_reg.intercept_) # 第一个未知数
print(fidge_reg.coef_) # 第二个未知数
- SGDRegressor梯度下降+岭回归
py
import numpy as np
from sklearn.linear_model import SGDRegressor
X = 2*np.random.rand(100, 1)
y = 4 + 3*X + np.random.randn(100, 1)
sgd_reg = SGDRegressor(penalty='l2', max_iter=10000) # penalty是使用L1 , L2哪一个lasso方法
sgd_reg.fit(X, y.reshape(-1,))
print(sgd_reg.predict([[1.5]]))
print(sgd_reg.intercept_)
print(sgd_reg.coef_)
- ElasticNet L1和L2 同时使用
py
import numpy as np
from sklearn.linear_model import ElasticNet
from sklearn.linear_model import SGDRegressor # 意思是sklearn.linear_model这个模块里面也有SGDRegressor方法
X = 2*np.random.randn(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)
# elastic_reg = ElasticNet(alpha=0.04, l1_ratio=0.15) 这是使用ElasticNet
# elastic_reg.fit(X, y)
# print(elastic_reg.predict([[1.5]]))
sgd_reg = SGDRegressor(penalty='elasticnet', max_iter=1000) 这是使用SGDRegressor
sgd_reg.fit(X, y.ravel())
print(sgd_reg.predict([[1.5]]))
升维polynomial regression
为了解决维度过少导致模型预测不准确的问题,增加维度用的,也就是增加特征
- 多项式回归是升维的一种方法 是数据预处理的算法,在sklearn.preprocessing模块里面,是一种预处理方法
- 线性算法的运行速度很快
- 当数据xy是非线性关系的时候,比如分类,就只能使用非线性算法,回归树神经网络等等拟合非线性数据 还有就是升维把数据变成线性关系
pyimport numpy as np import matplotlib.pyplot as plt from sklearn.preprocessing import PolynomialFeatures # 本节的主角,数据预处理 from sklearn.linear_model import LinearRegression # 一个算法模型,但是只能求x的一次项的系数 from sklearn.metrics import mean_squared_error # 就是MSE评估模型 np.random.seed(42) m = 100 X = 6*np.random.rand(m, 1) - 3 y = 0.5*X**2 + X + 2 + np.random.randn(m, 1) # W0=2,W1=1,W2=0.5 对应的三个系数+噪音 X**2是x的平方 plt.plot(X, y, 'b.') # 数据集的拆分 X_train = X[:80] y_train = y[:80] X_test = X[80:] y_test = X[80:] d = {1: 'g-', 2: 'r+', 10: 'y*'} # 对三次维度的拟合结果绘制不同的线 for i in d: poly_features = PolynomialFeatures(degree=i, include_bias=True) # 升维工具 X_poly_train = poly_features.fit_transform(X_train) # 把X升维 X_poly_test = poly_features.fit_transform(X_test) print(X_train[0]) print(X_poly_train[0]) print(X_train.shape) print(X_poly_train.shape) lin_reg = LinearRegression(fit_intercept=False) # 创建线性回归模型 lin_reg.fit(X_poly_train, y_train) # 用升维后的数据训练 print(lin_reg.intercept_, lin_reg.coef_) # 看看是否随着degree的增加升维,是否过拟合了 这边是测试集数据 y_train_predict = lin_reg.predict(X_poly_train) # 预测 y_test_predict = lin_reg.predict(X_poly_test) plt.plot(X_poly_train[:, 1], y_train_predict, d[i]) print(mean_squared_error(y_train, y_train_predict)) print(mean_squared_error(y_test, y_test_predict)) plt.show()
fit()就是训练过程,predict()就是预测
逻辑回归
逻辑回归不是回归算法,而是分类算法
逻辑回归是基于多元线性回归的算法,一种线性分类器
基于决策树的算法,基于神经网络的算法才是非线性算法
SVM本质也是线性的,但是可以升维变成非线性的算法
- sigmoid functionS型曲线
图像大概是:y = 1/(tanX) + 0.5
就是一个奇函数,关于原点对称,但是现在通过+0.5,调整了一下图像整体上移。在x趋近于正无穷的时候,无限接近1,X趋近负无穷的时候,y无限趋近于0.现在是关于(0,0.5)对称。
原理:y=1/(1+e**-z)
代码:1.0/(1.0 + math.exp(-z))
Sigmoid作用
逻辑回归是多元线性回归,但是把结果缩放在0~1之间
- 指数族分布
高斯分布,二项分布,伯努利分布,多项分布,泊松分布,指数分布,beta分布,拉普拉斯分布,gamma分布
只要是y服从指数族分布,就可以使用广义线性回归建模
- 最大似然估计的思想(MLE)
根据若干已知的xy,找到一组w,使得在x已知的情况下,预测y的最大概率可能取值
- 逻辑回归损失函数,读入数据计算最优解,实现逻辑回归预测,实现逻辑回归损失函数
pyfrom sklearn.datasets import load_breast_cancer from sklearn.linear_model import LogisticRegression import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from sklearn.preprocessing import scale # 归一化模块 data = load_breast_cancer() # 这是获取数据集的地方,模块里面自带的,关于癌症的预测,里面是关于肿瘤的特征 # scale是 X, y = scale(data['data'][:, :2]), data['target'] # 这边x是肿瘤的特征(取了所有行两列的数据,也就是两个特征),y是 是否患癌 # 设置模型超参数 # 求出两个维度对应的数据在逻辑回归算法下的最优解 lr = LogisticRegression(fit_intercept=False) #fit_intercept是是否计算W0,false是不用。也就是截距项 # 有了截距项不好分析,不利于画图演示,目前是想看损失函数,看看对于模型来说,使用几个维度预测的准 lr.fit(X, y) # 给模型传入数据 # 分别把两个维度所对应的参数 W1 和 W2 取出来 这里是已经计算完了 theta1 = lr.coef_[0, 0] theta2 = lr.coef_[0, 1] # 因为是两个特征,计算出两个参数,是两行一列的矩阵,这个算法自动把矩阵转置为一行两列的矩阵 print(theta1, theta2) # 已知 W1 和 W2 的情况下,传进来数据的 X,返回数据的 y_predict def p_theta_function(features, w1, w2): # features 是某一条的样本数据,[X1,X2] z = w1*features[0] + w2*features[1] # z就是y,z = W1*X1 + W2*X2 return 1 / (1 + np.exp(-z)) # 逻辑回归公式 # 传入一份已知数据的 X,y,如果已知 W1 和 W2 的情况下,计算对应这份数据的 Loss损失 def loss_function(samples_features, samples_labels, w1, w2): # 逻辑回归的损失函数 result = 0 # 遍历数据集中的每一条样本,并且计算每条样本的损失,加到 result 身上得到整体的数据集损失 for features, label in zip(samples_features, samples_labels): # 这是计算一条样本的 y_predict p_result = p_theta_function(features, w1, w2) loss_result = -1*label*np.log(p_result)-(1-label)*np.log(1-p_result) result += loss_result # 累加损失数 return result # 后面都是画图证明theta12的最优解属实 # 扩大theta的范围 theta1_space = np.linspace(theta1-0.6, theta1+0.6, 50) theta2_space = np.linspace(theta2-0.6, theta2+0.6, 50) result1_ = np.array([loss_function(X, y, i, theta2) for i in theta1_space]) result2_ = np.array([loss_function(X, y, theta1, i) for i in theta2_space]) fig1 = plt.figure(figsize=(8, 6)) # 看的是theta1的最优解 plt.subplot(2, 2, 1) # 分区域绘图,这个是2*2里面的左上角 plt.plot(theta1_space, result1_) plt.subplot(2, 2, 2)# 看的是theta2的最优解 plt.plot(theta2_space, result2_) # 绘制等高线,看theta1和2范围扩大0.6的时候,损失的结果,是一个三维图的二维投影 plt.subplot(2, 2, 3) theta1_grid, theta2_grid = np.meshgrid(theta1_space, theta2_space) loss_grid = loss_function(X, y, theta1_grid, theta2_grid) plt.contour(theta1_grid, theta2_grid, loss_grid) plt.subplot(2, 2, 4) # 刚刚的等高线不够密集,这里是增加了等高线的条数,可以更清晰的找到最优解 plt.contour(theta1_grid, theta2_grid, loss_grid, 30) fig2 = plt.figure() ax = Axes3D(fig2) # 构建三维画布 ax.plot_surface(theta1_grid, theta2_grid, loss_grid) # 对应的就是W1,W2,Loss plt.show()就是先计算出两个theta,然后把这两个theta的值左右各加上0.6的范围,
逻辑回归是做分类任务的,而且只能是二分类。就是01这种,不是是1就是0.
为了拓展这个逻辑回归算法。让它进行多分类,就是二分类之上的作用,可以使用叠加的方式,分批分类。比如有ABC三种类型,第一次分类把A看作一类,bc是非A那一类,由此可以从ABC里面比较准确的分出A来
按照这个理念,所有可以解决二分类的算法都可以解决多分类问题
Softmax回归
这个叫回归的算法也是进行分类的算法,是一个多分类算法,是假设多项分布,也是二项分布的扩展
伯努利分布,可以使用Logistic回归建模
多项式分布是指数族分布的一种
从广义线性回归的η推导出来的Softmax回归
当设置Softmax损失函数k=2的时候,就是逻辑回归损失函数
- 应用:当遇到多分类的需求在多个二分类和多分类之间高如何选择(主要是看需求
如果遇到的场景是分类之间互相独立的话,比如猫狗车。特别明显,就用多分类softmax
如果是分类之间没有清晰的界限,男人女人这种。在外表看来的时候,可能会是其中一个。使用多个二分类模型,这样每一个二分类模型都会给出一个分类的概率值。 设置一个阈值,就有可能一个东西有多个分类,对应分类的概率,可以方便后续进行人为的二次筛选。
音乐分类器 -Sotfmax
- 原理展示
py
import time
from scipy import fft
from scipy.io import wavfile
from matplotlib.pyplot import specgram
import matplotlib.pyplot as plt
import numpy as np
sample_rate, X = wavfile.read(r".\genres\blues\converted\blues.00000.au.wav")
print(sample_rate, X.shape) # 22050 (661794,) X.shape 是 numpy 数组的一个属性,它告诉你数组的维度和每个维度的大小。是最快确认数据结构的方法
plt.figure(figsize=(10, 4), dpi=80) # 创建新画布的函数,figsize画布的尺寸大小,单位是英寸(inch),不是像素;dpi=80 表示每英寸有80个像素点
plt.xlabel("time")
plt.ylabel("frequency")
plt.grid(True, linestyle='-', color='0.75') # 第一个参数是是否开启网格辅助画图,然后是线的类型
specgram(X, Fs=sample_rate, xextent=(0, 30)) # specgram() 是用来绘制频谱图(声谱图/Spectrogram)的函数,可以同时显示音频的时间、频率、强度三个维度。 X: 音频数据数组(波形数据);Fs=sample_rate用来正确计算频率轴;xextent=(0, 30):设置时间轴的显示范围,表示显示从0秒到30秒,不管音频实际多长,横轴都显示0-30秒。xextent固定了横轴的刻度,不会随着实际的音频长度重新分配刻度
plt.show()
def plotSpec(g, n):
sample_rate, X = wavfile.read(fr".\genres\{g}\converted\{g}.{n}.au.wav")
specgram(X, Fs=sample_rate, xextent=(0, 30))
plt.title(g+"-"+n[-1])
plt.figure(num=None, figsize=(18, 9), dpi=80, facecolor='w', edgecolor='k')
plt.subplot(6, 3, 1); plotSpec("classical", '00001')
plt.subplot(6, 3, 2); plotSpec("classical", '00002')
plt.subplot(6, 3, 3); plotSpec("classical", '00003')
plt.subplot(6, 3, 4); plotSpec("jazz", '00001')
plt.subplot(6, 3, 5); plotSpec("jazz", '00002')
plt.subplot(6, 3, 6); plotSpec("jazz", '00003')
plt.subplot(6, 3, 7); plotSpec("country", '00001')
plt.subplot(6, 3, 8); plotSpec("country", '00002')
plt.subplot(6, 3, 9); plotSpec("country", '00003')
plt.subplot(6, 3, 10); plotSpec("pop", '00001')
plt.subplot(6, 3, 11); plotSpec("pop", '00002')
plt.subplot(6, 3, 12); plotSpec("pop", '00003')
plt.subplot(6, 3, 13); plotSpec("rock", '00001')
plt.subplot(6, 3, 14); plotSpec("rock", '00002')
plt.subplot(6, 3, 15); plotSpec("rock", '00003')
plt.subplot(6, 3, 16); plotSpec("metal", '00001')
plt.subplot(6, 3, 17); plotSpec("metal", '00002')
plt.subplot(6, 3, 18); plotSpec("metal", '00003')
plt.tight_layout(pad=0.4, w_pad=0, h_pad=1.0) # plt.tight_layout() 是用来自动调整子图布局的函数,让多个子图之间不重叠、间距合适。pad=0.4:整体图表与画布边缘的填充距离;w_pad=0:子图之间的水平间距(宽度方向);h_pad=1.0:子图之间的垂直间距(高度方向)
plt.show()
# 上面都是为了展示音乐分类器的原理,下面才是这个分类器的完整内容
- 数据预处理
py
from scipy import fft
from scipy.io import wavfile
import numpy as np
# 准备音乐数据,把音乐文件一个个的去使用傅里叶变换,并且把傅里叶变换之后的结果落地保存
# 提取特征 也就是数据的预处理
def create_fft(g, n): # 传入类型和序号
rad = r"。\genres\\"+g+"/converted/"+g+'.'+str(n).zfill(5)+'.au.wav'
sample_rate, X = wavfile.read(rad) # 获取音频数据
fft_features = abs(fft.fft(X)[:1000]) # 使用傅里叶处理,再用abs取绝对值 ,1000是索引,不是频率值 FFT结果是复数
sad = r".\genres\\"+g+'.'+str(n).zfill(5)+".fft"
np.save(sad, fft_features) # 最后把处理好的数据保存为fft
# # 音乐分析通常只关心 0-8000 Hz
# useful_freq_range = abs(fft_result[:800]) # 大概8kHz以下
#
# # 语音识别通常只关心 0-4000 Hz
# speech_freq_range = abs(fft_result[:400]) # 大概4kHz以下
# 如果是标准音频(44.1kHz),前1000个其实已经包含了很高的频率范围,足够音乐分析用了!
genre_list = ['classical', 'jazz', 'country', 'pop', 'rock', 'metal' , 'blues' ,'disco' ,'hiphop' ,'reggae']
for g in genre_list:
for n in range(100):
create_fft(g, n) # 批量处理所有的音频数据
为什么要用FFT做特征?
传统方式的问题:
# 直接用音频波形训练
X_train = [音频1波形, 音频2波形, ...]
# 每个音频几十万个数据点
# 问题:数据量巨大,且时域特征对分类帮助不大
FFT特征的优势:
# 用频域特征训练
X_train = [音频1频谱, 音频2频谱, ...] # 每个音频只有1000个特征
# 优势:数据压缩,且频谱特征更适合区分音乐类型
实际效果对比:
古典音乐的频谱特征:
- 低频(大提琴):强度高
- 中频(小提琴):丰富
- 高频:相对平缓
摇滚音乐的频谱特征:
- 低频(鼓、贝斯):非常强
- 中频(吉他):尖峰多
- 高频(镲片):爆发性强
机器学习模型就是通过学习这些频域"指纹"来区分不同音乐类型的!
FFT本质上是把**"这首歌听起来怎么样"转换成了"这首歌的频率成分是什么"**,后者更适合计算机理解和分类。
频域 就是从"频率角度"看问题,而不是从"时间角度"看。
最简单的理解:
时域 = 看"什么时候发生了什么"
频域 = 看"有哪些频率成分"就是看某些频率出现的次数来判断是什么类型的音乐
- 训练模型
py
import numpy as np
from sklearn.linear_model import LogisticRegression
import pickle
from pprint import pprint
from scipy.io import wavfile
from scipy import fft
genre_list = ['classical', 'jazz', 'country', 'pop', 'rock', 'metal' , 'blues' ,'disco' ,'hiphop' ,'reggae']
# 读取傅里叶变换之后的数据集,将其做成机器学习所需要的X和y
X = []
y = []
for g in genre_list:
for n in range(100):
rad = r".\genres\trainset\\"+g+"."+str(n).zfill(5)+".fft.npy"
fft_features = np.load(rad)
X.append(fft_features)
print(f'正在处理类型:{g}的第{n}条数据')
y.append(genre_list.index(g))
# 处理数据转为numpy可以识别的数组
X = np.array(X)
y = np.array(y)
print('正在训练模型')
# 训练模型并且保存模型
model = LogisticRegression()
model.fit(X, y)
print('训练完成!')
output = open('model.pkl', 'wb')
pickle.dump(model, output)
output.close()
在训练完成之前有个报错,是模型迭代次数的问题,如果人为设置迭代次数,10000容易过拟合,100/1000就不够拟合。反正怎么弄都收敛不好,但是这个模型还是可以正在训练出来,有报错就有报错忽略即可
- 检验模型
py
import numpy as np
import pickle
import librosa
from scipy import fft
# 应该和训练时保持一致:
genre_list = ['classical', 'jazz', 'country', 'pop', 'rock', 'metal' , 'blues' ,'disco' ,'hiphop' ,'reggae']
genre_dict = {
'classical': '古典音乐',
'jazz': '爵士乐',
'country': '乡村音乐',
'pop': '流行音乐',
'rock': '摇滚乐',
'metal': '金属乐',
'blues': '蓝调音乐',
'disco': '迪斯科',
'hiphop': '嘻哈音乐',
'reggae': '雷鬼音乐'
}
print('开始读取模型。。。')
pkl_file = open('model.pkl', 'rb')
model_loaded = pickle.load(pkl_file)
pkl_file.close()
music_name = "Stayin' Alive - Bee Gees.mp3"
print('处理要预测的音乐文件:' + music_name)
# 关键:使用 mono=True 强制转换为单声道
X, sample_rate = librosa.load(
r".\genres\\" + music_name, sr=22050,
mono=True)
print('正在fft格式化')
# 提取FFT特征
test_fft_features = abs(fft.fft(X))[:1000]
print('fft格式化完成!开始输出结果。。。')
# 检查模型是否有 predict_proba 方法(用于获取概率)
if hasattr(model_loaded, 'predict_proba'):
# 使用 predict_proba 获取概率分布
probabilities = model_loaded.predict_proba([test_fft_features])[0]
predicted_class = np.argmax(probabilities)
predicted_genre = genre_list[predicted_class]
print(f"预测结果: {genre_dict[predicted_genre]} ({predicted_genre})")
print("\n概率分布(前5名):")
# 创建概率排序
sorted_probabilities = sorted(zip(genre_list, probabilities), key=lambda x: x[1], reverse=True)
for i, (genre, prob) in enumerate(sorted_probabilities[:5], 1):
print(f"{i}. {genre_dict[genre]}: {prob:.2%}")
else:
# 如果没有 predict_proba 方法,使用原来的方式
prediction = model_loaded.predict([test_fft_features])
print(f"预测结果形状: {np.array(prediction).shape}")
print(f"预测结果内容: {prediction}")
# 处理一维数组的情况
if isinstance(prediction, (list, np.ndarray)) and len(prediction) == 1:
predicted_class = prediction[0]
else:
predicted_class = prediction
predicted_genre = genre_list[predicted_class]
print(f"预测结果: {genre_dict[predicted_genre]} ({predicted_genre})")
print("注意:当前模型不支持输出概率分布")
这个是一个最基本的机器学习的入门项目,预测结果很不好,但是能用。这里面机器学习最重要的四个步骤,他都比较缺少,首先第一个是样本数量只有一百个,对于真正的机器学习,需要成千上万,成千都算少,得上万几十万几千万个数据,这是最基本的这个样本数量都没有保证。然后就是还有这个算法的选择上这只是使用了一个softmax回归进行了一个分类也不好.还有就是特征也太少了,仅仅使用了fft这一个特征进行判断.然后举例的话就是通过一个人的身高判断一个人的品行,这是一个特别大的这个肯定会有特别大的误差吧.还有就是参数根本就没有调参的优化.
SVM支持向量机
svm是讲的一个分类算法,当然了这个SVM支持向量机也是一个分类算法,它的前身是感知器算法,而感知器的话是一个1958年就提出来了一个学习模型,但是在当年没有制造出来,然后感知器模型的前提:数据是线性可分的。
感知器算法是古老的分类算法之一。原理比较简单,但是这个模型的分类泛化能力特别弱。感知器是SVM神经网络和神经深度学习等算法的基础。
感知器的思想是在任意空间中寻找一个超平面,然后把所有的二元类别分开
- 如何区分线性可分与线性不可分?
在坐标系上,我们用一条直线,可以把两个类别的点分开这就是线性可分;如果我们只能使用曲线,把这两个类分开,那就是线性不可分
SVM也是通过寻找超平面用于解决二分类问题的分类算法和感知机算法是一样的都是寻找超平面的
SVM的损失函数与感知机和逻辑回归都不一样
感知机是通过判错的点来寻找超平面,逻辑回归是通过最大似然寻找超平面,SVM是通过支持向量寻找超平面。所以损失函数都不一样。感知机和逻辑回归都是直接最小损失函数得来的Theta或者是叫W权重和b
SVM有两种求解方法,一种是直接最小化损失函数(合页损失函数hinge loss)得到的Theta,另一种是先寻找支持向量再找到支持向量超平面
逻辑回归的损失函数是log loss
模型的泛化能力弱导致它就无法很好的适应这个新的数据集就是训练集没有办法很好地适应这个训练集对训练集进行分类啊预测等等各种问题
在感知机模型中一般是可以找到多个可以分类的这个超平面,把这个两个数据类分开,然后他就提出了一个概念,就是优化这个超平面让所有的点尽可能的离超平面远一点,离的越远的话那新加入来的数据也就是测试集数据,就有更大的概率会被归到正确的分类里面,他还提出了一个理念,就是说离超平面足够远的点基本上都是被正确分类的。所以说研究这个足够远的点是没有意义的。我们要关心的是离超平面很近的点,因为离超平面很近的点,就有可能会被分错。那么我们需要讨论的就是如何把这些离超平面很近的点进行这个正确的分类,还有就是如何设计这个超平面增加它的容错能力也就是泛化能力。
这边提供了一个方法,如何寻找这个最佳的超平面。已知这个平面呢就是一个在三维空间内就是一个纸片,他的高度就是一条线,几乎忽略不计。我们都没有人会讨论这个高度,然而这个高度就是影响这个泛化能力的关键,于是我们给这个超平面增加高度,比如说就是让这个平面有了体积。希望这个平面足够的高然后呢刚刚好可以分开所有的这个点进行一个分类。足够高的时候就达到这个高度的最大值的时候,再取这个平面的这个中心值,这个中心值就是最好的这个分界线,也就是最好的超平面。当高度最大值的时候,就是这个超平面对两边这个点,进行最好的一个分割,也就是这个超平面是最好的这个泛化能力最好的这个超平面,也是容错能力最高的超平面
SVM 尝试找到一个决策边界,距离两个类别最近的样本最远!
SVM比之前的逻辑回归进行分类多了一个二次优化问题,之前的逻辑回归只是找一个分界线,他不会管这个分界线有多么好,只要找到了让这个损失函数最小就完了,他就没有考虑边界的这个合理的情况,他可能这个边界离某一个分类很近,离某一个分类很远。那在新加入一个训练集的时候或者测试集的时候就会把这个数据错误的分到别的分类里面,而SVM就是为了解决这个问题,进行二次优化,在找到边界之后,在选择最好的边界
svm分类
SVM 支持向量机
- 线性可分支持向量机
硬间隔最大化 - 线性支持向量机
软间隔最大化 - 非线性支持向量机
升维(核函数)
离超平面最近的那些点叫做支持向量,所以说这个SVM支持向量机,主要研究的是离超平面最近的那些点,因为可能是一个也可能是多个,毕竟我们是把那个超平面的一个高度增加了,所以说应该是至少两个,才能决定这一个超平面的高度
硬间隔SVM的话,是强制把两个类别的点进行一个超平面的分割,但是在大部分大多数情况下或者是有一部分情况下,是线性不可分的,因为有这个噪声点,比如说有那么一两个点是特殊值,偶尔那么一两个点离这个它本来所在的一个分类,离的特别特别的远,那么对于硬间隔,就比较吃力了硬间隔没有办法很好的去分割它,就是变成了线性不可分,就是因为这些噪声点而导致的。所以说就需要使用软间隔SVM。
为了解决硬间隔的问题我们引入了软间隔的概念,软间隔SVM比硬间隔多了一个松弛条件,就是把约束条件放宽一点,允许那么一两个噪声点在这个分割线之外,就是允许那么一两个点可以被分错位置,使这个超平面的宽度尽可能的增加,超平面的宽度越大泛化能力就越好,对后面的测试集的效果就会更好
- 维度爆炸
就是说我们在处理非线性数据的时候,想要使用这种线性的方法线性分类方法,只能是对非线性数据进行一个升维,通过升维,来找这个超平面或者是分界线,但是也并不是所有的数据都可以升维,,如果说我们的维度只有两个,升维之后他就是五个,就是有五个维度,五个参数吧,我们要求五个这种Theta。如果本来是三维的一个非线性数据,我们在处理的时候就会变成19个数据,。就是19个维度的这个数据,这种几何式增长,但是我们一般在处理数据特征的时候是由几十个几百个这种维度,再升维那么维度直接爆炸
解决这个问题引入了一个新的定义叫核函数,核函数的话是专门用来解决升维数据的这个问题,也是为了解决围度爆炸这个问题.常用的核函数有四个,一个是线性核函数,多项式核函数,高斯核函数,sigmoid核函数。
这个四个核函数在使用的时候就是一个超参数而已。
-
参数:
-
kernel 核函数 默认是rbf高斯,linear线性回归,poly多项式回归,sigmoid
-
probability 布尔值,默认是false,就是不需要返回概率值。如果需要返回概率值,会使模型的判断减速
-
tol 阈值
-
class_weight 权重,当数据集的正例负例样本数量相差很多的时候,就需要这个权重参数了。把少的样本权重提高。
-
max_iter 迭代次数
-
decison_function_shape 分类方式可填ovo,ovr,默认是ovr。ovr是使用多个二分类实现多分类,ovo是直接使用多分类
- one versus one(ovo)比如abc三种类别,单独取出ab进行分类,然后是bc,ac分别进行二分类
- one versus the rest(ovr)还是abc三种,但是a/bc, b/ac, c/ab 是把另外两类看成一大类
-
SVM 算法小结
SVM 算法是一个很优秀的算法,在集成学习和神经网络之类的算法没有表现出优越性能之前,SVM 算法基本占据了分类模型的统治地位。目前在大数据时代的大样本背景下,SVM由于其在大样本时超级大的计算量,热度有所下降,但仍然是一个常用的机器学习算法。
-
优点
- 解决高维特征的分类问题和回归问题很有效,在特征维度大于样本数时依然有很好的效果。
- 仅仅使用一部分支持向量来做超平面的决策,无需依赖全部数据。
- 有大量的核函数可以使用,从而可以很灵活的来解决各种非线性的分类回归问题。
- 样本量不是海量数据的时候,分类准确率高,泛化能力强。
-
缺点
- 如果特征维度远远大于样本数,则 SVM 表现一般。
- SVM 在样本量非常大,核函数映射维度非常高时,计算量过大,不太适合使用。
- 非线性问题的核函数的选择没有通用标准,难以选择一个合适的核函数。
- SVM 对缺失数据敏感。
- SVM 对比逻辑回归
1、LR 采用 log 损失,SVM 采用合页损失。
2、LR 对异常值敏感,SVM 对异常值不敏感。
3、在训练集较小时,SVM 较适用,而 LR 需要较多的样本。
4、LR 模型找到的那个超平面,是尽量让所有点都远离它,而 SVM 寻找的那个超平面,是只让最靠近中间分割线的那些点尽量远离,即只用到那些支持向量的样本。
5、对非线性问题的处理方式不同,LR 主要靠特征构造,必须组合交叉特征,特征离散化。SVM 也可以这样,还可以通过 kernel。
6、SVM 更多的属于非参数模型,而 logistic regression 是参数模型,本质不同。其区别就可以参考参数模型和非参模型的区别
案例-人脸图像识别
py""" =================================================== Faces recognition example using eigenfaces and SVMs =================================================== The dataset used in this example is a preprocessed excerpt of the "Labeled Faces in the Wild", aka LFW_: http://vis-www.cs.umass.edu/lfw/lfw-funneled.tgz (233MB) .. _LFW: http://vis-www.cs.umass.edu/lfw/ Expected results for the top 5 most represented people in the dataset: ================== ============ ======= ========== ======= precision recall f1-score support ================== ============ ======= ========== ======= Ariel Sharon 0.67 0.92 0.77 13 Colin Powell 0.75 0.78 0.76 60 Donald Rumsfeld 0.78 0.67 0.72 27 George W Bush 0.86 0.86 0.86 146 Gerhard Schroeder 0.76 0.76 0.76 25 Hugo Chavez 0.67 0.67 0.67 15 Tony Blair 0.81 0.69 0.75 36 avg / total 0.80 0.80 0.80 322 ================== ============ ======= ========== ======= """ from __future__ import print_function from time import time import logging import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split # 拆分数据集的,分为训练集和测试集 from sklearn.model_selection import GridSearchCV from sklearn.datasets import fetch_lfw_people # 获取数据集,人脸识别,人脸分类 from sklearn.metrics import classification_report from sklearn.metrics import confusion_matrix from sklearn.decomposition import PCA from sklearn.svm import SVC # svm分类算法 # Display progress logs on stdout logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') # ############################################################################# # Download the data, if not already on disk and load it as numpy arrays lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4) # 读取人脸数据,参数min_faces_per_person限制一个人的图片只有大于70张的时候,才会添加到数据集里面去 # introspect the images arrays to find the shapes (for plotting) n_samples, h, w = lfw_people.images.shape # 输出图片的形状,高宽 # for machine learning we use the 2 data directly (as relative pixel # positions info is ignored by this model) X = lfw_people.data n_features = X.shape[1] # 特征数量 # the label to predict is the id of the person y = lfw_people.target target_names = lfw_people.target_names n_classes = target_names.shape[0] print("Total dataset size:") print("n_samples: %d" % n_samples) print("n_features: %d" % n_features) print("n_classes: %d" % n_classes) # ############################################################################# # Split into a training set and a test set using a stratified k fold # split into a training and testing set X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42) # test_size是测试集的和总数据集的比例这里是1/4的数据作为测试集 print(X_train.shape) print(X_test.shape) # ############################################################################# # Compute a PCA (eigenfaces) on the face dataset (treated as unlabeled # dataset): unsupervised feature extraction / dimensionality reduction # 这部分是对数据进行了降维的操作 n_components = 150 # 指定降维后的维度数量 print("Extracting the top %d eigenfaces from %d faces" % (n_components, X_train.shape[0])) t0 = time() pca = PCA(n_components=n_components, svd_solver='randomized', whiten=True).fit(X_train) # PCA是主成分分析,降维的Priciple Components print("done in %0.3fs" % (time() - t0)) print(pca.components_.shape) eigenfaces = pca.components_.reshape((n_components, h, w)) print("Projecting the input data on the eigenfaces orthonormal basis") t0 = time() X_train_pca = pca.transform(X_train) # 对训练集x降维,然后重新赋值 X_test_pca = pca.transform(X_test) # 这边是测试集x print(X_train_pca.shape) print(X_test_pca.shape) print("done in %0.3fs" % (time() - t0)) # ############################################################################# # Train a SVM classification model print("Fitting the classifier to the training set") t0 = time() param_grid = {'C': [1e3, 5e3, 1e4, 5e4, 1e5], 'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], } clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, cv=5) # 这边调用了svm算法,使用了高斯分布,正负例样本权重都是1 GridSearch是栅格搜索,CV是交叉验证,param_grid是svm里面的超参,一共是两个参数,c是5个,gamma是6个,一共30种组合。交叉验证自动去选择最好的参数组合 cv=5 是一轮5次 一种参数组合跑5次。总数就是30*5=150次训练 # svc方法是本次测试的主角,可以填写4个参数,kernel,class_weight,C,gamma 但是这样写就只能使用一种参数的类别 # GridSearchCV方法 可以自动把30种参数全部训练一遍,取最好的组合 clf = clf.fit(X_train_pca, y_train) # 输入训练集数据 print("done in %0.3fs" % (time() - t0)) print("Best estimator found by grid search:") print(clf.best_estimator_) # ############################################################################# # Quantitative evaluation of the model quality on the test set print("Predicting people's names on the test set") t0 = time() y_pred = clf.predict(X_test_pca) # 输入测试集数据,进行预测 print("done in %0.3fs" % (time() - t0)) print(classification_report(y_test, y_pred, target_names=target_names)) # 评估模型指标 print(confusion_matrix(y_test, y_pred, labels=range(n_classes))) # ############################################################################# # Qualitative evaluation of the predictions using matplotlib 这边都是绘图 def plot_gallery(images, titles, h, w, n_row=3, n_col=4): '''Helper function to plot a gallery of portraits''' plt.figure(figsize=(1.8 * n_col, 2.4 * n_row)) plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35) for i in range(n_row * n_col): plt.subplot(n_row, n_col, i + 1) plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray) plt.title(titles[i], size=12) plt.xticks(()) plt.yticks(()) # plot the result of the prediction on a portion of the test set def title(y_pred, y_test, target_names, i): pred_name = target_names[y_pred[i]].rsplit(' ', 1)[-1] true_name = target_names[y_test[i]].rsplit(' ', 1)[-1] return 'predicted: %s\ntrue: %s' % (pred_name, true_name) prediction_titles = [title(y_pred, y_test, target_names, i) for i in range(y_pred.shape[0])] plot_gallery(X_test, prediction_titles, h, w) # plot the gallery of the most significative eigenfaces eigenface_titles = ["eigenface %d" % i for i in range(eigenfaces.shape[0])] plot_gallery(eigenfaces, eigenface_titles, h, w) plt.show()
案例2-识别垃圾邮件
py
# -*- coding: utf-8 -*-
import os
import numpy as np
from collections import Counter
from sklearn.naive_bayes import MultinomialNB, GaussianNB, BernoulliNB
from sklearn.svm import SVC, NuSVC, LinearSVC
from sklearn.metrics import confusion_matrix
def make_Dictionary(train_dir): # 读取邮件数据
emails = [os.path.join(train_dir, f) for f in os.listdir(train_dir)]
all_words = []
for mail in emails: # 从每个邮件里面拆分成单个单词,保存在all_words列表里面
with open(mail) as m:
for i, line in enumerate(m):
if i == 2:
words = line.split()
all_words += words
dictionary = Counter(all_words) # Counter统计all_words的词频
list_to_remove = dictionary.keys()
for item in list(list_to_remove):
if item.isalpha() is False:
del dictionary[item]
elif len(item) == 1:
del dictionary[item]
dictionary = dictionary.most_common(3000) # 统计最多出现的前3000词
return dictionary
def extract_features(mail_dir): # 提取特征,把所有邮件变成一个二维数组
files = [os.path.join(mail_dir, fi) for fi in os.listdir(mail_dir)]
features_matrix = np.zeros((len(files), 3000)) # 把邮件里面的词做一个统计,这个是一个(邮件书*3000)的矩阵,默认都是0,某个单词在某邮件里面出现过,那么这个邮件对应的单词列就是1,也就是提取特征
docID = 0
for fil in files:
with open(fil) as fi:
for i, line in enumerate(fi):
if i == 2:
words = line.split()
for word in words: # 遍历邮件
wordID = 0
for i, d in enumerate(dictionary): # 判断词汇有没有出现
if d[0] == word:
wordID = i
features_matrix[docID, wordID] = words.count(word)
docID = docID + 1
return features_matrix
# Create a dictionary of words with its frequency
train_dir = 'train-mails'
dictionary = make_Dictionary(train_dir)
# Prepare feature vectors per training mail and its labels
train_labels = np.zeros(702) #总的邮件数量
train_labels[351:701] = 1 # 后半部分都是垃圾邮件,就是要预测的邮件类型,垃圾邮件是1,正常邮件是0
train_matrix = extract_features(train_dir)
# Training SVM and Naive bayes classifier and its variants 就是说用svm和朴素贝叶斯两个模型建模
model1 = SVC()
model2 = MultinomialNB() # NB是Naive bayes 朴素贝叶斯 用于文本分类,后面细讲
model1.fit(train_matrix, train_labels) # 传入训练集
model2.fit(train_matrix, train_labels)
# Test the unseen mails for Spam
test_dir = 'test-mails'
test_matrix = extract_features(test_dir) # 开始处理测试集数据
test_labels = np.zeros(260)
test_labels[130:260] = 1
result1 = model1.predict(test_matrix) # 预测测试集数据
result2 = model2.predict(test_matrix)
print(confusion_matrix(test_labels, result1)) # confusion_matrix混淆矩阵,就是把预测结果和真实结果做一个矩阵对比数据,一般是2*2的 因为是二分类,所以是2*2,三分类就是3*3了,也是对角线是判对的
# 预测x 预测Y
#真实x TN FP 可以看出,对角线的数据才是正确数据的数量,越大越好
#真实Y FN TP
print(confusion_matrix(test_labels, result2))
# 预测结果 朴素贝叶斯要比svm效果好 因为朴素贝叶斯就是进行文本分类的,属于是专业对口了
# [[129 1]
# [ 24 106]]
# [[129 1]
# [ 9 121]]
这个案例,大部分都在想办法把数据转换为一个矩阵。关于svm的就两行代码,一个模型实例化,一个传入训练集和测试集数据方法,就出来预测结果了
决策树
决策树是一个比较古老的算法, 它会像二叉树一样,根据X里面的特征进行预测y的值,但是决策树是一个启发式贪婪式的算法,他只顾眼前并不会考虑整体,他只会说去一直调整这个树的结构让他去得到最好的结果,他并不但是他并不关心整体的这个效果,而解决这个问题的是XGboost算法,会以最后的这个损失函数为终点,尽可能的减小损失.就是他始终是想要比如说预测二手车他始终是想要把预测出一个正确的二手车价格, XGboost是根据整体的,这个比如说有十个选项,那么他会挨个尝试一次,然后取最好的效果,取最好的那一个选项. 而决策树的话他只会取最好的那一个选项,什么意思呢. 决策树它有十个选项,十个选项里面有一个最好的,但是它下面呢还有一步.他就不会考虑了,他只考虑一个 . 就是决策树只考虑一步, XGboost会考虑整体全部的这个步骤.
决策树可以做分类, 也可以做回归, 是一种有监督机器学习的算法
特点
- 可以处理非线性的问题
- 可解释性强 没有θ
- 模型简单,模型预测效率高 if else
- 不容易显示的使用函数表达,不可微 XGBoost
这个算法可以处理非线性的问题,那么我们在做数据预处理的时候,就可以没必要把数据特征进行一个矩阵的转换了。
这个模型是直接就是根据某些维度进行这个预测,然后呢他也会选出最重要的那些维度进行这个模型的预测, 对人们的使用更加直观
减少或者降低决策树的过拟合问题,,需要调整这个决策树的深度,就可以解决这个问题
决策树的生成说白了就是数据不断分裂的递归过程,每一次分裂,尽可能让类别一样的数据在树的一边,当树的叶子节点的数据都是一类的时候,则停止分裂。
- 常用分裂条件
- Gini系数/基尼指数CART 在0~1之间,越接近0越好
- 信息增益ID3 越小越好
- 信息增益率C4.5 前三种是解决分类问题的,这三个加起来就是看分割后的叶子节点的纯度,也就是分裂后的内部百分比
- MSE 解决回归问题的
需要把所有的维度都测试一次,找最好的几个维度来作为叶子节点
信息量=熵=不确定性 也就是叶子节点的纯度
通俗来讲,叶子节点的纯度,因为这是一个决策树,是它具有两个结果,我们定义为正例和负例,决策树的话它是每次分裂一部分值,就是把它分裂成两种情况,比如说1和0,他分在左边子节点1的数量占这个叶子结点总数的99%,零是1%;而右边那个叶子节点,1占1%,零占99%。那么我们就认为下面左边这个叶子节点,可以说它就是1,他这个是确定性很大如果是100%。那就是肯定是1了,那都不需要怀疑的,而熵也就是指的不确定性,不确定性就是1和0的比例相近,50%的可能性会是1,50%是0,或者是60%是1,40%是0,这种不确定性比较大是这个意思。
剪枝pruning
解决过拟合的问题
有两种剪枝方法:
-
前剪枝/预剪枝 用的挺多的
给切分节点的条件设置的很严苛,导致决策树很短小
- 参数 sklearn里面集成学习的DecisionTree里面的一个类似决策树预剪枝的方法
- criterion='gini' 分裂条件
- splitter = 'beat' 尝试所有的维度,只能选择best最好的维度分裂
- max_depth=None 允许决策树的最大深度/层数 可以低于最大深度
- min_samples_split=2 默认是2,当一个叶子节点想要再分裂的时候,必须有2个子节点,如果只有少于2就不允许分裂
- min_samples_leeaf=1 默认是1,假如是2,在分裂的时候,如果子节点里面的样本数量低于2,比如只有1个样本,那么这个父节点不允许分裂
- min_weight_fraction_leaf=0 样本数量和根节点的样本数的比值是否大于这个值,如果大于才允许分裂
- max_leaf_nodes=None 最大的节点个数
- min_impurity_decrease=0 父节点与两个子节点不纯度的差值必须大于这个值,不大于不允许分裂
- min_impurity_split=None 最小不纯度,小于这个值不允许分裂
- class_weight=None 权重,可以传一个数组用于设置正负例的权重,在计算gini系数的时候会用到
- presort=Flase 预排序,有些维度可以排序,比如资产等等,可以方便后续分裂的时候机器判断的复杂度,加快机器学习的速度
- 后剪枝 用的很少 计算复杂度很大
- 决策树完全构建好后,然后才开始裁剪。
常见三种后剪枝方法:
(1) REP---错误率降低剪枝
(2) PEP---悲观剪枝(C4.5)
(3) CCP---代价复杂度剪枝(CART) 2和3是方法里面自带的优化步骤,即便不设置超参,也会自动使用
决策树优缺点
没有梯度下降,theta,w1w2等等。用不到正则化,归一化等等
优点
- 决策过程接近人的思维习惯。
- 模型容易解释,比线性模型具有更好的解释性。 就是能解释样本如何分类的
- 能清楚地使用图形化描述模型。
- 处理定型特征比较容易。
缺点
- 一般来说,决策树学习方法的准确率不如其他的模型。(和集成学习相比
- 不支持在线学习。当有新样本来的时候,需要重建决策树。 (在线学习:指的是可以随时添加训练集数据。因为决策树在构建的时候需要计算总体的纯度,而新增数据会破坏原有的纯度。
- 容易产生过拟合现象。
ID3和C4.5
C4.5 是ID3的改进版。ID3会优先选择取值多的属性,会导致可能一直使用用户id进行分裂,因为用户id的值都不同,全是不同的取值。但是明显是不合适的。也是因为ID3根据信息增益来分裂。值多的属性具有更大的信息增益
c4.5专门来解决这个问题,使用了信息增益率,衡量分裂数据的广度和均匀性。
ID3 只能处理离散数据,C4.5 还能对连续属性进行处理
C4.5 其他优点
1)在树的构造过程中可以进行剪枝,缓解过拟合;2)能够对连续属性进行离散化处理(二分法);3)能够对缺失值进行处理;
缺点:构造树的过程需要对数据集进行多次顺序扫描和排序,导致算法低效;
刚才我们提到 信息增益对可取值数目较多的属性有所偏好;而信息增益率对可取值数目较少的属性有所偏好!OK,两者结合一下就好了!
解决方法:先从候选属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。而不是大家常说的直接选择信息增益率最高的属性! 这样做的目的是为了提速
就是c4.5做了一个后剪枝(悲观剪枝
绘制决策树模型的工具graphvis
https://graphviz.gitlab.io/_pages/Download/Download_windows.html
需要自己配置到环境变量里面去
sklearn里面有一个api接口可以生成决策树模型的dot文件,再配合这个dot工具可以生成png图片
dot文件是使用文本的形式去描述一颗树,graphviss 根据文件绘制图片
生成dot文件之后,在终端里面先进入dot.exe所在的目录,然后使用 dot -Tpng 刚刚生成的dot文件的绝对路径 -o
这个终端命令有两个参数Tpng和o,o是把生成的图片放在和dot文件的目录里面
案例-决策树分类鸢尾花数据集
py
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier # 决策树分类算法
from sklearn.tree import export_graphviz # 生成dot文件
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score # acc评估模型的方法,给一个正确率
import matplotlib.pyplot as plt
import matplotlib as mpl
# 获取数据
iris = load_iris()
data = pd.DataFrame(iris.data)
data.columns = iris.feature_names
data['Species'] = load_iris().target # 新增了一列进行分类说明
print(data)
# 切分数据
x = data.iloc[:, 2:4] # 花瓣长度和宽度
y = data.iloc[:, -1]
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25, random_state=42)
# 训练模型
tree_clf = DecisionTreeClassifier(max_depth=8, criterion='gini')
tree_clf.fit(x_train, y_train)
export_graphviz(
tree_clf, # 传入刚刚的训练模型
out_file="./iris_tree.dot", # 指定dot的保存位置
feature_names=iris.feature_names[2:4], # 对应x的名字
class_names=iris.target_names, # 对应y的名字
rounded=True, # 创建的图片里面的节点边框是不是有弧度的
filled=True # 给节点填充颜色
)
# 对测试集进行评估
y_test_hat = tree_clf.predict(x_test)
print("acc score:", accuracy_score(y_test, y_test_hat))
print(tree_clf.feature_importances) # 会返回维度的重要程度,越接近1,越重要
# 对新测试样本进行预测
print(tree_clf.predict_proba([[5, 1.5]])) # 输出概率值
print(tree_clf.predict([[5, 1.5]])) # 输出分类号
在绘制决策树的时候, 可以使用for循环设置树的深度,当模型训练好之后对模型进行评估,比如说我们评估一个错误率,然后再根据决策树的深度和错误率绘制一个折线图,就可以看出决策树应该设置为什么样的深度就够用了。
决策树也可以进行回归预测DecisionTreeRegressor,当他预测一个曲线的时候,比如说一个正弦函数。他想要预测一个正弦函数的这个回归模型,那么这个决策树在绘制的时候会以很多线段而进行拟合这个图形。树的深度越深线段越多。也就是说那个可以选择点就越多,点越多的话就越接近原来的正弦函数。
集成学习-随机森林RF
随机森林是多棵决策树,当有新来的样本之后,所有的决策树给出一个答案,再接着统计所有决策树答案的比例,再根据比例少数服从多数,给出一个最终的确定答案。
集成学习里面最经典的这个理解,假如有20个人给了我一个主意关于投资股票问我怎么做决定。
如果选择资历最深的朋友一个意见当作自己的意见那就是最好的单颗决策树 ,也就是上面的决策树。
如果说是所有的朋友投票,少数服从多数就是随机森林。
如果说是资历资质比较好的就给他多几票,资质比较差的少给几票这就是adaboost
遇见那种运气贼差的买什么股票就跌什么股票的朋友该怎么办,这就是属于使用技巧,反着用
这种多个模型投票的模型,叫聚合模型
随机森林里面的随机,指的是样本随机,对应的决策树就是随机的。就是总的数据集不变,在拆分训练集和测试集的时候,使用随机分配的方式,获取到随机样本
如何生成随机模型,就是随机森林里面的随机的模型。
- 首先我们对数数据有一定的操作方式,这个模型的数据,如果我们给同样的整体数据,但是给一个模型不同的超参数,就可以得到不同超参数的随机模型
- 还可以是我们给相同的超参数,这个随机模型不同的列,行相同,创建不同的模型,这是数据不同
- 考虑列相同行不相同就是随机的这个行数据
- 同样的超参数,同样的数据,但是数据的权重不一样,我们可以给某一列增加数据权重这样可以也可以获得不同的模型 adaboost
- Bagging
对训练集进行随机抽样,把抽样结果训练模型,是并行训练,独立训练每一个模型。----random fprest随机森林
- Boosting 提升
也是利用训练集训练出模型,但是他会根据模型的预测结果,调整训练集在利用调整后的训练集训练下一个模型。是一个串行的模式,一般在adaboost,GBDT和Xgboost
优缺点
- 优点: 处理高维度数据,辅助特征选择,并行训练
- 缺点:有噪声的时候,容易过拟合
py
from sklearn.ensemble import RandomForestClassifier # 随机森林方法
from sklearn.model_selection import train_test_split # 切分数据集的
from sklearn.metrics import accuracy_score # 评估模型的
from sklearn.datasets import load_iris # 数据集
iris = load_iris()
X = iris.data[:, :2] # 花萼长度和宽度
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
rnd_clf = RandomForestClassifier(n_estimators=5, max_leaf_nodes=16, n_jobs=1, oob_score=True) # 定义一个随机森林模型 n_estimators是有几颗决策树 max_leaf_nodes每颗树的最大节点数防止过拟合 n_jobs是使用几个线程去训练 oob_score 是否使用随机森林自带的OOB评分
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
print(accuracy_score(y_test, y_pred_rf))
print(rnd_clf.oob_score_)
# Feature Importance 打印每个特征的重要程度
iris = load_iris()
rnd_clf = RandomForestClassifier(n_estimators=500, n_jobs=-1)
rnd_clf.fit(iris["data"], iris['target'])
for name, score in zip(iris['feature_names'], rnd_clf.feature_importances_):
print(name, score) #输出特征名和分数
OOB
out of bag data 没有被抽样到的数据
使用OOB这个概念的主要原因,就是因为我们的数据集很宝贵,然后我们在训练模型的时候会把数据进行拆分,拆分成训练集和测试集,不难发现在这个随机森林里面可能会有部分数据一直没有被用到,比如样本一或者就是数据一吧,可能只会被一个模型用到,而其他的模型不会用到,因为随机森林中有很多个模型,我们就可以让样本一去其他没有用过样本一的这个模型里进行评估,作为测试集。就是样本一对于这个决策树一来说可能是认识的,但是其他的树它是不知道样本一的数据。我们就可以使用整个数据集全部作为随机森林的这个全量的数据作为这个训练集。 但是呢又因为它内部会进行随机数据,就是随机数据进行训练,这个随机森林肯定会有或多或少有的数据没有被其他的那个决策树使用到,因此以此可以用来测试其他的这个模型,所以说这个随机森林可以进行自身的这个评分也就是OOB
Adaboost-串行
自动调整数据权重
- boosting
- 集成学习的思路 结合多个弱学习器,改善单个学习器的泛化能力和鲁棒性
- 多轮迭代全部数据,每一轮产生一个弱分类器,最后预测结果叠加,出一个强分类器的结果
- adaboost,GBDT,Xgboost
理解adaboost的理念
假设有一堆关于水果的图片,我们想要做一个苹果图片这种分类器,就想从一堆水果中识别出苹果来,然后我们这边就可以使用ada boost的思想,首先把这些图片交给第一个模型给它输入一个概念苹果是圆的,他就根据苹果是圆的进行图片的筛选,可以剔除香蕉这种跟圆型不符的图片,但是遇到像橙子这样的水果都是圆的,就没有办法。于是乎我们调整数据的权重给这些错误的图片增加数据权重让第二轮模型主要去调整为什么出错,我们发现苹果应该是红的,橙子这种是的圆形的水果就可以被区分出来。但是又会遇到新的问题,苹果还可能是绿色的绿苹果吗。第二轮模型筛选苹果图片的一个方法,但是又增加了新的错误样本,就会把绿色的苹果也判错,所以第三轮模型会主要针对苹果可以为允许为绿色进行一个调整,这样的话经过三轮下来我们给苹果加上如下的特征:1苹果是圆的,2苹果可以是红色,3苹果可以是绿色。经过这几步下来呃可以初步的做一个苹果的筛选器。
adaboost使用决策桩来训练模型,就是每颗决策树只有一个分裂的机会,防止过拟合,也注定需要多个决策桩进行弱分类器的叠加
给决策树这种限制,防止一颗决策树把全样本的维度全部训练完,也防止过拟合,给其他的决策树一个训练的机会
关于调整数据权重是怎么调的 ,有我个人的理解:就是说我们在第一棵树这个模型建立完之后,对整体的数据集有了一定的判断,然后根据整体数据集的这个正确率和错误率,重新分配数据权重。因为一开始的时候我们是没有正确和错误这个概念的我们只有它那个最终预测的这个y值,所以一开始的时候这个y值如果不是特别离谱的话一般我们给这个正例和负例都是1的权重。我们刚刚的一棵树训练完成之后,根据机器模型预测的结果比如说正例负例有没有判断正确,就是判断正确和判断错误作为这个数据权重。比如说我们20%的样本被判错了,80%叛对了。那我们就给这20%权重增加,给剩下的80%正确的进行权重的这种相减就是减小,然后通过这种调整权重,把他们的错误率和正确率调整为50%也就是一比一。在后边就是下一棵树的训练,再下一棵树重新调整数据集之后,看正确率和错误率,继续调整,循环一直到最好的效果。
在OpenCV里面默认使用Adaboost。
使用OpenCV的时候是导入cv2模块,还需要提前安装opencv
opencv可以使用adaboost训练模型,生成一个xml文件,这个xml就是保存模型的地方
案例-opencv+adaboost正面人脸识别
py
import cv2 # 导入模块
def nothing(x):
pass
cap = cv2.VideoCapture(0) # 开启摄像头
face_cascade = cv2.CascadeClassifier("./data/haarcascade_frontalface_default.xml") # 导入的是一个别人训练好的人脸模型
cv2.namedWindow("Frame")
cv2.createTrackbar("Neighbours", "Frame", 5, 20, nothing)
while True:
_, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
neighbours = cv2.getTrackbarPos("Neighbours", "Frame")
faces = face_cascade.detectMultiScale(gray, 1.3, neighbours) # detectMultiScale检测人脸的方法
for rect in faces: # 识别出人脸之后就会打个框,就是框起来人脸
(x, y, w, h) = rect
frame = cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.imshow("Frame", frame)
key = cv2.waitKey(1)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
GBDT
GD+Boosting+决策树 ---->>梯度提升决策树
- Boosting思想:每棵树修正前面的错误
- 梯度下降:用负梯度指导树的训练
- 可扩展性:损失函数可换,适应不同任务
特征组合->降维
这个并不是gbdt的主要职能但它是属于一个副产品,因为它使用的逻辑回归这个损失函数loss,在决策树里面会对所有的叶子节点进行评分。而这叶子节点的话就是所有的这个特征维度,之前在学习这个单棵决策树的时候就有对这个特征为某些维度的进行评分,我们可以用到,然后当然在这里也可以用到。所以GBBT可以把所有的特征都测试一遍,给出一个整体的评分来看哪些特征的这些重要程度,由此可以把一些不重要的这些特征筛选移除,俗称将为降低维度,降维
GBDT和DT都是贪婪学习,但是DT是单颗决策树没有整体的loss损失函数,而GBDT可以照顾整体的loss。更加适合降维
XGBoost
这个算法的话会有3种的返回值,它是根据应用场景来返回的,如果说是回归模型直接给的是预测值;如果说是分类模型给的是需要经过一些变换的一个概率值;如果是其他模型的话比如像是推荐模型的话就是一个排序的依据,还需要进行二次变换才行
- 优点
自定义loss ,这个loss需要二阶可导
GBDT和XGBoost区别
- 传统的GBDT以CART树作为基学习器,XGBoost还支持线性分类器,这个时候XGBoost相当于L1和L2正则化的逻辑斯蒂回归(分类)或者线性回归(回归);
- 传统的GBDT在优化的时候只用到一阶导数信息,XGBoOSt则对代价函数进行了二阶泰勒展开,得到一阶和二阶导数;
- XGBoOst在代价函数中加入了正则项,用于控制模型的复杂度。从权衡方差偏差来看,它降低了模型的方差,使学习出来的模型更加简单,放置过拟合,这也是XGBoost优于传统GBDT的一个特性;
- shrinkage(缩减),相当于学习速率(XGBoost中的eta)。XGBoost在进行完一次迭代时,会将叶子节点的权值乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。(GBDT也有学习速率);
- 列抽样。XGBoost借鉴了随机森林的做法,支持列抽样,不仅防止过拟合,还能减少计算;,
- 对缺失值的处理。对于特征的值有缺失的样本,XGBoost还可以自动学习出它的分裂方向;
- XGBoost工具支持并行。Boosting不是一种串行的结构吗?怎么并行的?注意XGBoost的并行不是tree粒度的并行,XGBoost也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面t-1次迭代的预测值)。XGBoost的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),XGBoost在训练之前,预先对数据进行了排序,然后保存为block结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行
Xgboost安装
在官网里面下载whl格式的压缩包
使用pip install+绝对路径安装
- 数据预处理
- 缺失值太多,看百分比,如果达到了90%,看重要程度, 很重要,就重新赋值,如何赋值需要根据实际的业务需求。如果不重要直接删除
- 看数据的种类,如果每一个维度,里面的种类很多很杂,没有逻辑可言,比如人名等等直接删除。如果数据里面,有几个大头,比如某几类能占总数居的80%,可以在低一点,就统一给剩下哪些杂的类型,统一命名,具体名称按照兴趣即可。
- 类型太多容易过拟合。尽可能简化。让模型分析的时候更加纯粹,减小噪声污染
- 对于庞大是数据量,一些重要的维度,可以使用绘图的形式,看下数据的位置,比如绘制箱图,看离群值,如果很不合常理,就手动调整,比如使用中位数替换等等
- 字符串尽可能数值化,比如男女这种,两个类型的,就01替换即可。其他维度里面的类型也有这种字符串的,也可以改为数值替换字符串
- 处理完的数据另存一下,方便后续的调用
参数
py
kgb1 =XGBClassifier( # XGBClassifier是之前导入的xgboost方法
learning_rate =0.1,# 学习率
n_estimators=1000, # 迭代次数
max_depth=5, # 最大深度
min_child_weight=1, # 叶子节点样本数最小百分比
gamma=0,
subsample=0.8,# 行数据随机取80%
colsample_bytree=0.8, # 列数据随机取80%
objective='binary:logistic', # 使用二分类和log损失函数
nthread=4, # 4个线程同时使用
scale_pos_weight=1, # 正例的权重
seed=27) # 随机种子
modelfit(xgb1, train, test, predictors)
建模
def modelfit(alg, dtrain, dtest, predictors,useTrainCV=True, cv_folds=5, early_stopping_rounds=50):
这个是模型的建立方法:
- alg 模型参数入口
- dtrain,dtest训练集测试集
- predictors 需要训练的维度
- useTrainCV 是否使用交叉验证,有默认值
- cv_folds 5份交叉验证
- early_stopping_rounds loss损失函数连续小于阈值50次才会停止
交叉验证可以自动更新迭代次数,找到最合适的迭代次数