【超详细】神经网络的可视化解释
《------往期经典推荐------》
一、AI应用软件开发实战专栏【链接】
项目名称 | 项目名称 |
---|---|
1.【人脸识别与管理系统开发】 | 2.【车牌识别与自动收费管理系统开发】 |
3.【手势识别系统开发】 | 4.【人脸面部活体检测系统开发】 |
5.【图片风格快速迁移软件开发】 | 6.【人脸表表情识别系统】 |
7.【YOLOv8多目标识别与自动标注软件开发】 | 8.【基于YOLOv8深度学习的行人跌倒检测系统】 |
9.【基于YOLOv8深度学习的PCB板缺陷检测系统】 | 10.【基于YOLOv8深度学习的生活垃圾分类目标检测系统】 |
11.【基于YOLOv8深度学习的安全帽目标检测系统】 | 12.【基于YOLOv8深度学习的120种犬类检测与识别系统】 |
13.【基于YOLOv8深度学习的路面坑洞检测系统】 | 14.【基于YOLOv8深度学习的火焰烟雾检测系统】 |
15.【基于YOLOv8深度学习的钢材表面缺陷检测系统】 | 16.【基于YOLOv8深度学习的舰船目标分类检测系统】 |
17.【基于YOLOv8深度学习的西红柿成熟度检测系统】 | 18.【基于YOLOv8深度学习的血细胞检测与计数系统】 |
19.【基于YOLOv8深度学习的吸烟/抽烟行为检测系统】 | 20.【基于YOLOv8深度学习的水稻害虫检测与识别系统】 |
21.【基于YOLOv8深度学习的高精度车辆行人检测与计数系统】 | 22.【基于YOLOv8深度学习的路面标志线检测与识别系统】 |
23.【基于YOLOv8深度学习的智能小麦害虫检测识别系统】 | 24.【基于YOLOv8深度学习的智能玉米害虫检测识别系统】 |
25.【基于YOLOv8深度学习的200种鸟类智能检测与识别系统】 | 26.【基于YOLOv8深度学习的45种交通标志智能检测与识别系统】 |
27.【基于YOLOv8深度学习的人脸面部表情识别系统】 | 28.【基于YOLOv8深度学习的苹果叶片病害智能诊断系统】 |
29.【基于YOLOv8深度学习的智能肺炎诊断系统】 | 30.【基于YOLOv8深度学习的葡萄簇目标检测系统】 |
31.【基于YOLOv8深度学习的100种中草药智能识别系统】 | 32.【基于YOLOv8深度学习的102种花卉智能识别系统】 |
33.【基于YOLOv8深度学习的100种蝴蝶智能识别系统】 | 34.【基于YOLOv8深度学习的水稻叶片病害智能诊断系统】 |
35.【基于YOLOv8与ByteTrack的车辆行人多目标检测与追踪系统】 | 36.【基于YOLOv8深度学习的智能草莓病害检测与分割系统】 |
37.【基于YOLOv8深度学习的复杂场景下船舶目标检测系统】 | 38.【基于YOLOv8深度学习的农作物幼苗与杂草检测系统】 |
39.【基于YOLOv8深度学习的智能道路裂缝检测与分析系统】 | 40.【基于YOLOv8深度学习的葡萄病害智能诊断与防治系统】 |
41.【基于YOLOv8深度学习的遥感地理空间物体检测系统】 | 42.【基于YOLOv8深度学习的无人机视角地面物体检测系统】 |
43.【基于YOLOv8深度学习的木薯病害智能诊断与防治系统】 | 44.【基于YOLOv8深度学习的野外火焰烟雾检测系统】 |
45.【基于YOLOv8深度学习的脑肿瘤智能检测系统】 | 46.【基于YOLOv8深度学习的玉米叶片病害智能诊断与防治系统】 |
47.【基于YOLOv8深度学习的橙子病害智能诊断与防治系统】 | 48.【基于深度学习的车辆检测追踪与流量计数系统】 |
49.【基于深度学习的行人检测追踪与双向流量计数系统】 | 50.【基于深度学习的反光衣检测与预警系统】 |
51.【基于深度学习的危险区域人员闯入检测与报警系统】 | 52.【基于深度学习的高密度人脸智能检测与统计系统】 |
53.【基于深度学习的CT扫描图像肾结石智能检测系统】 | 54.【基于深度学习的水果智能检测系统】 |
55.【基于深度学习的水果质量好坏智能检测系统】 | 56.【基于深度学习的蔬菜目标检测与识别系统】 |
57.【基于深度学习的非机动车驾驶员头盔检测系统】 | 58.【太基于深度学习的阳能电池板检测与分析系统】 |
59.【基于深度学习的工业螺栓螺母检测】 | 60.【基于深度学习的金属焊缝缺陷检测系统】 |
61.【基于深度学习的链条缺陷检测与识别系统】 | 62.【基于深度学习的交通信号灯检测识别】 |
63.【基于深度学习的草莓成熟度检测与识别系统】 | 64.【基于深度学习的水下海生物检测识别系统】 |
65.【基于深度学习的道路交通事故检测识别系统】 | 66.【基于深度学习的安检X光危险品检测与识别系统】 |
67.【基于深度学习的农作物类别检测与识别系统】 | 68.【基于深度学习的危险驾驶行为检测识别系统】 |
69.【基于深度学习的维修工具检测识别系统】 | 70.【基于深度学习的维修工具检测识别系统】 |
71.【基于深度学习的建筑墙面损伤检测系统】 | 72.【基于深度学习的煤矿传送带异物检测系统】 |
73.【基于深度学习的老鼠智能检测系统】 | 74.【基于深度学习的水面垃圾智能检测识别系统】 |
75.【基于深度学习的遥感视角船只智能检测系统】 | 76.【基于深度学习的胃肠道息肉智能检测分割与诊断系统】 |
77.【基于深度学习的心脏超声图像间隔壁检测分割与分析系统】 | 78.【基于深度学习的心脏超声图像间隔壁检测分割与分析系统】 |
79.【基于深度学习的果园苹果检测与计数系统】 | 80.【基于深度学习的半导体芯片缺陷检测系统】 |
81.【基于深度学习的糖尿病视网膜病变检测与诊断系统】 | 82.【基于深度学习的运动鞋品牌检测与识别系统】 |
二、机器学习实战专栏【链接】,已更新31期,欢迎关注,持续更新中~~
三、深度学习【Pytorch】专栏【链接】
四、【Stable Diffusion绘画系列】专栏【链接】
五、YOLOv8改进专栏【链接】,持续更新中~~
六、YOLO性能对比专栏【链接】,持续更新中~
《------正文------》
目录
- 引言
- 分类问题
- 单层网络:感知器
- sigmoid函数
- 添加更多功能
- 线性分类器
- 多分类和softmax层
- 多层网络
- 隐藏层
- 维度的游戏
- 非线性函数的必要性
- 神经网络回归问题
- 单层网络
- 多层网络
- 总结
引言
人工神经网络是最强大的,同时也是最复杂的机器学习模型。它们对于传统机器学习算法效果不好的复杂任务特别有效。神经网络的主要优势是它们能够学习数据中复杂的模式和关系,即使数据是高维或非结构化的。
许多文章讨论了神经网络背后的数学原理,如不同的激活函数,前向和反向传播算法,梯度下降和优化方法进行了详细讨论。`在这篇文章中,我们采用了不同的方法,逐层呈现了对神经网络的可视化理解。我们将首先关注单层神经网络在分类和回归问题中的视觉解释,以及它们与其他机器学习模型的相似性。然后我们将讨论隐藏层和非线性激活函数的重要性。所有的可视化都是使用Python创建的。
分类问题
我们从分类问题开始。最简单的分类问题是二进制分类,其中目标只有两个类别或标签。如果目标有两个以上的标签,那么我们有一个多分类问题。
单层网络:感知器
单层神经网络是人工神经网络的最简单形式。这里我们只有一个接收输入数据的输入层和一个产生网络输出的输出层。在这个网络中,输入层不被认为是真正的层,因为它只是传递输入数据。这就是为什么这种架构被称为单层网络。Perceptron是有史以来创建的第一个神经网络,是单层神经网络最简单的例子。
感知器由Frank Rosenblatt于1957年发明。他认为感知器可以模拟大脑的原理,具有学习和决策的能力。最初的感知器被设计用于解决二进制分类问题。
图1显示了感知器的架构。输入数据具有n个特征,用x_1到x_n表示。目标y只有两个标签(y=0和y=1)。
图1
输入层接收数据并将其传递到输出层。输出层中的神经元计算输入特征的加权和。每一个输入特征,x都与权重w相关联。神经元将每个输入乘以其相应的权重,并将结果相加。偏置项w0也被添加到该和中。如果我们用z表示和,则:
激活函数是阶跃函数,定义为:
该激活函数如图2所示。
图2
感知器的输出由y^表示,计算如下:
为了可视化感知器的工作方式,我们使用了一个简单的训练数据集,只有两个特征x1和x2,它是随机定义的,目标y只有两个标签(y=0和y=1)。数据集如图3所示。具体代码如下:
# Listing 1import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
import random
import tensorflow as tf
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.utils import to_categorical
from tensorflow.keras import backendnp.random.seed(3)
n = 30
X1 = np.random.randn(n,2)y1 = np.random.choice((0, 1),size=n)
X1[y1>0,0] -= 4
X1[y1>0,1] += 4
scaler = StandardScaler()
X1 = scaler.fit_transform(X1)plt.figure(figsize=(5, 5))
marker_colors = ['red', 'blue']
target_labels = np.unique(y1)
n = len(target_labels)
for i, label in enumerate(target_labels):plt.scatter(X1[y1==label, 0], X1[y1==label,1], label="y="+str(label),edgecolor="white", color=marker_colors[i])
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
ax = plt.gca()
ax.set_aspect('equal')
plt.xlim([-2.3, 1.8])
plt.ylim([-1.9, 2.2])
plt.show()
图3
本文不详细介绍神经网络的训练过程。我们专注于已经训练好的神经网络的行为。在下面代码中,我们直接使用前面的数据集定义和训练感知器。
# Listing 2class Perceptron(object):def __init__(self, eta=0.01, epochs=50):self.eta = etaself.epochs = epochsdef fit(self, X, y):self.w = np.zeros(1 + X.shape[1])for epoch in range(self.epochs):for xi, target in zip(X, y):error = target - self.predict(xi)self.w[1:] += self.eta * error * xiself.w[0] += self.eta * errorreturn selfdef net_input(self, X):return np.dot(X, self.w[1:]) + self.w[0]def predict(self, X):return np.where(self.net_input(X) >= 0.0, 1, 0)perc = Perceptron(epochs=150, eta=0.05)
perc.fit(X1, y1)
现在我们想看看这个模型如何对我们的训练数据集进行分类。因此,我们定义了一个函数来绘制训练好的神经网络的决策边界。下面代码中定义的这个函数在2D空间上创建一个网格,然后使用一个经过训练的模型来预测网格上所有点的目标。具有不同标签的点的颜色不同。因此,可以使用该函数可视化模型的决策边界。
# Listing 3def plot_boundary(X, y, clf, lims, alpha=1):gx1, gx2 = np.meshgrid(np.arange(lims[0], lims[1],(lims[1]-lims[0])/500.0),np.arange(lims[2], lims[3],(lims[3]-lims[2])/500.0))backgd_colors = ['lightsalmon', 'aqua', 'lightgreen', 'yellow']marker_colors = ['red', 'blue', 'green', 'orange']gx1l = gx1.flatten()gx2l = gx2.flatten()gx = np.vstack((gx1l,gx2l)).Tgyhat = clf.predict(gx)if len(gyhat.shape)==1:gyhat = gyhat.reshape(len(gyhat), 1)if gyhat.shape[1] > 1:gyhat = gyhat.argmax(axis=1)gyhat = gyhat.reshape(gx1.shape)target_labels = np.unique(y)n = len(target_labels)plt.pcolormesh(gx1, gx2, gyhat, cmap=ListedColormap(backgd_colors[:n]))for i, label in enumerate(target_labels):plt.scatter(X[y==label, 0], X[y==label,1],label="y="+str(label),alpha=alpha, edgecolor="white",color=marker_colors[i])
现在,我们使用这个函数来绘制训练数据集的感知器的决策边界。结果如图4所示。
# Listing 4plt.figure(figsize=(5, 5))
# Plot the vector w
plt.quiver([0], [0], perc.w[1], perc.w[2], color=['black'],width=0.008, angles='xy', scale_units='xy',scale=0.4, zorder=5)
# Plot the boundary
plot_boundary(X1, y1, perc, lims=[-2.3, 1.8, -1.9, 2.2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
plt.xlim([-2.3, 1.8])
plt.ylim([-1.9, 2.2])
plt.show()
图4
该图清楚地表明,决策边界是一条直线。我们使用感知器的权重定义向量*w*:
这个向量也在图4中绘制,它垂直于感知器的决策边界(向量很小,所以我们在图中缩放它)。我们现在可以解释这些结果背后的数学原因。
对于具有两个特征的数据集,有:
基于等式1,我们知道z=0的所有数据点的预测标签为1。另一方面,z<0的任何数据点的预测标签将为0。因此,决策边界是z=0的数据点的位置,并且它由以下等式定义:
这是一条直线的方程,这条直线的法向量(垂直于这条直线的向量)是:
这解释了为什么决策边界垂直于向量w。
sigmoid函数
感知器可以预测数据点的标签,但不能提供预测概率。事实上,这个网络无法告诉你它对自己的预测有多自信。我们需要一个不同的激活函数sigmoid来获得预测概率。sigmoid激活函数定义如下:
图5给出了该函数的曲线图。
图5
我们知道事件发生的概率是0到1之间的一个数。由于该图显示了sigmoid函数的范围是(0,1),因此它可以用来表示结果的概率。现在,我们将感知器的激活函数替换为sigmoid函数,以获得图6所示的网络。
图6
在这个网络中,我们用p表示网络的输出,所以我们可以写:
这里p是预测标签为1的概率(y^=1)。为了获得预测目标,我们必须将此概率与默认为0.5的阈值进行比较:
为了可视化这个网络,我们使用之前定义的数据集来训练它。使用keras
库创建这个网络。
# Listing 5np.random.seed(0)
random.seed(0)
tf.random.set_seed(0)model1 = Sequential()
model1.add(Dense(1, activation='sigmoid', input_shape=(2,)))model1.compile(loss = 'binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model1.summary()
这个神经网络的代价函数称为交叉熵。接下来,我们使用定义的数据集来训练这个模型。
# Listing 6history1 = model1.fit(X1, y1, epochs=1500, verbose=0, batch_size=X1.shape[0])
plt.plot(history1.history['accuracy'])
plt.title('Accuracy vs Epochs')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.show()
图7显示了该模型的准确度与训练轮数的关系图。
图7
在训练模型之后,我们可以查看输出层的权重(w和w)。
# Listing 7output_layer_weights = model1.layers[0].get_weights()[0]
model1_w1, model1_w2 = output_layer_weights[0, 0], output_layer_weights[1, 0]
最后,我们画出了这个网络的决策边界。结果如图8所示。
# Listing 8plt.figure(figsize=(5, 5))
# Plot the vector w
output_layer_weights = model1.layers[0].get_weights()[0]
plt.quiver([0], [0], model1_w1,model1_w2, color=['black'],width=0.008, angles='xy', scale_units='xy',scale=1, zorder=5)
# Plot the boundary
plot_boundary(X1, y1, model1, lims=[-2.3, 1.8, -1.9, 2.2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
plt.xlim([-2.3, 1.8])
plt.ylim([-1.9, 2.2])
plt.show()
图8
我们再次看到决策边界是一条直线。我们使用输出层的权重定义向量*w*:
向量*w***垂直于决策边界,就像我们在感知器中看到的那样。让我们解释这些结果背后的数学原因。根据等式2,p=0.5的所有数据点的预测标签为1。另一方面,p<0.5的任何数据点的预测标签将为0。因此,决策边界是p=0.5的所有数据点的位置:
因此,决策边界是由以下等式定义的所有数据点的位置:
如前所述,这是一条直线的方程,这条直线的法向量(垂直于这条直线的向量)是:
添加更多功能
到目前为止,我们只考虑了一个只有两个特征的小数据集。让我们看看当我们有三个特征时会发生什么。下面代码定义了另一个具有3个特性的数据集。该数据集如图9所示。
# Listing 9fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')ax.scatter(X2[y2==0, 0], X2[y2==0,1], X2[y2==0,2],label="y=0", alpha=0.8, color="red")
ax.scatter(X2[y2==1, 0], X2[y2==1,1], X2[y2==1,2],label="y=1", alpha=0.8, color="blue")
ax.legend(loc="upper left", fontsize=12)
ax.set_xlabel("$x_1$", fontsize=18)
ax.set_ylabel("$x_2$", fontsize=18)
ax.set_zlabel("$x_3$", fontsize=15, labelpad=-0.5)
ax.view_init(5, -50)
plt.show()
图9
现在,我们用一个sigmoid神经元创建一个新的网络,并使用这个数据集训练它。
# Listing 10backend.clear_session()
np.random.seed(0)
random.seed(0)
tf.random.set_seed(0)model2 = Sequential()
model2.add(Dense(1, activation='sigmoid', input_shape=(3,)))model2.compile(loss = 'binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history2 = model2.fit(X2, y2, epochs=1500, verbose=0,batch_size=X2.shape[0])
接下来,我们查看训练模型中输出层的权重,并在图10中绘制模型的数据点和决策边界。
# Listing 11model2_w0 = output_layer_biases[0]
model2_w1, model2_w2, model2_w3 = output_layer_weights[0, 0], \output_layer_weights[1, 0], output_layer_weights[2, 0]fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')lims=[-2, 2, -2, 2]
ga1, ga2 = np.meshgrid(np.arange(lims[0], lims[1], (lims[1]-lims[0])/500.0),np.arange(lims[2], lims[3], (lims[3]-lims[2])/500.0))ga1l = ga1.flatten()
ga2l = ga2.flatten()
ga3 = -(model2_w0 + model2_w1*ga1l + model2_w2*ga2l) / model2_w3
ga3 = ga3.reshape(500, 500)
ax.plot_surface(ga1, ga2, ga3, alpha=0.5)
ax.quiver([0], [0], [0], model2_w1, model2_w2, model2_w3,color=['black'], length=0.5, zorder=5)
ax.scatter(X2[y2==0, 0], X2[y2==0,1], X2[y2==0,2],label="y=0", alpha=0.8, color="red")
ax.scatter(X2[y2==1, 0], X2[y2==1,1], X2[y2==1,2],label="y=1", alpha=0.8, color="blue")
ax.legend(loc="upper left", fontsize=12)
ax.set_xlabel("$x_1$", fontsize=16)
ax.set_ylabel("$x_2$", fontsize=16)
ax.set_zlabel("$x_3$", fontsize=15, labelpad=-0.5)
ax.view_init(5, -50)
plt.show()
图10
如图所示,决策边界是垂直于向量的平面
其使用输出层的权重形成。这里,决策边界计算如下:
所以,决策边界就是这个方程的解
这是一个平面的方程,矢量***w(***在方程3中定义)是这个平面的法向矢量。
线性分类器
如果输入数据中有3个以上的特征,会发生什么?我们可以很容易地扩展相同的想法,找到一个感知器或sigmoid神经元的n个特征的网络的决策边界。在这两种情况下,决策边界都是这个方程的解:
这个方程描述了n维空间中垂直于向量的超平面
在2D空间中,超平面变成一维线,而在3D空间中,它变成2D平面。一条直线或一个平面没有曲率,虽然我们不能在更高的维度上想象超平面,但概念是一样的。在n维空间中,超平面是平坦且没有曲率的n-1维子空间。
在机器学习中,线性分类器是一种基于输入特征的线性组合做出决策的分类模型。因此,线性分类器的决策边界是超平面。感知器和sigmoid神经元是线性分类器的两个例子。
值得一提的是,具有交叉熵代价函数的sigmoid神经元相当于逻辑回归模型。下面代码在之前定义的2D数据集上训练逻辑回归模型(来自scikit-learn
库)。该模型的决策边界如图11所示。虽然这是一条直线,但它与图8中的sigmoid神经元所获得的线并不完全相同。
虽然逻辑回归和sigmoid神经元(具有交叉熵代价函数)是等价的模型,但在训练过程中使用不同的方法来找到它们的参数。在神经网络中,随机初始化的梯度下降算法用于训练,然而,逻辑回归模型使用称为lbfgs(有限内存Broyden-Fletcher-Goldfarb-Shanno)的确定性求解器来实现该目的。因此,这两个模型中参数的最终值可能不同,从而改变决策边界线的位置。
# Listing 12# Comparing with a logistic regression model
lr_model = LogisticRegression().fit(X1, y1)plt.figure(figsize=(5, 5))
# Plot the boundary
plot_boundary(X1, y1, lr_model, lims=[-2.3, 1.8, -1.9, 2.2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
plt.xlim([-2.3, 1.8])
plt.ylim([-1.9, 2.2])
plt.show()
图11
多分类和softmax层
到目前为止,我们的注意力一直集中在二元分类问题上。如果数据集的目标有两个以上的标签,那么我们有一个多分类问题,这样的问题需要一个softmax层。假设一个数据集有n个特征,其目标有C个标签。该数据集可用于训练具有softmax层的单层神经网络,如图12所示。
图12
softmax函数是sigmoid函数对多类分类问题的推广,其中目标具有超过2个标签。输出层中的神经元给出输入特征的线性组合:
softmax层的每个输出计算如下:
在该等式中,p表示预测目标等于第i个标签的概率。最后,预测的标签是具有最高概率的标签:
现在我们创建另一个数据集来可视化softmax层。在这个数据集中,我们有两个特征,目标有3个标签。它被绘制在图13中。
# Listing 13np.random.seed(0)
xt1 = np.random.randn(50, 2) * 0.4 + np.array([2, 1])
xt2 = np.random.randn(50, 2) * 0.7 + np.array([6, 4])
xt3 = np.random.randn(50, 2) * 0.5 + np.array([2, 6])y3 = np.array(50*[1]+50*[2]+50*[3])
X3 = np.vstack((xt1, xt2, xt3))
scaler = StandardScaler()
X3 = scaler.fit_transform(X3)plt.figure(figsize=(6, 6))
plt.scatter(X3[y3==1, 0], X3[y3==1,1], label="y=1", alpha=0.7, color="red")
plt.scatter(X3[y3==2, 0], X3[y3==2,1], label="y=2", alpha=0.7, color="blue")
plt.scatter(X3[y3==3, 0], X3[y3==3,1], label="y=3", alpha=0.7, color="green")
plt.legend(loc="best", fontsize=11)
plt.xlabel("$x_1$", fontsize=16)
plt.ylabel("$x_2$", fontsize=16)
ax = plt.gca()
ax.set_aspect('equal')
plt.show()
图13
接下来,我们创建一个单层神经网络,并使用该数据集对其进行训练。这个网络有一个softmax层。
# Listing 14backend.clear_session()
np.random.seed(0)
random.seed(0)
tf.random.set_seed(0)
y3_categorical = to_categorical(y3-1, num_classes=3)
model3 = Sequential()
model3.add(Dense(3, activation='softmax', input_shape=(2,)))
model3.compile(loss = 'categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
history3 = model3.fit(X3, y3_categorical, epochs=2200,verbose=0, batch_size=X3.shape[0])
接下来,我们查看这个网络的权重和偏差:
# Listing 15output_layer_weights = model3.layers[-1].get_weights()[0]
output_layer_biases = model3.layers[-1].get_weights()[1]model3_w10, model3_w20, model3_w30 = output_layer_biases[0], \
output_layer_biases[1], output_layer_biases[2]model3_w1 = output_layer_weights[:, 0]
model3_w2 = output_layer_weights[:, 1]
model3_w3 = output_layer_weights[:, 2]
最后,我们可以绘制这个模型的决策边界。
# Listing 16plt.figure(figsize=(5, 5))
plt.quiver([1.7], [0.7], model3_w3[0]-model3_w2[0],model3_w3[1]-model3_w2[1], color=['black'],width=0.008, angles='xy', scale_units='xy',scale=1, zorder=5)
plt.quiver([-0.5], [-2.2], model3_w2[0]-model3_w1[0],model3_w2[1]-model3_w1[1], color=['black'],width=0.008, angles='xy', scale_units='xy',scale=1, zorder=5)
plt.quiver([-1.8], [-1.7], model3_w3[0]-model3_w1[0],model3_w3[1]-model3_w1[1], color=['black'],width=0.008, angles='xy', scale_units='xy',scale=1, zorder=5)
plt.text(0.25, 1.85, "$\mathregular{w_3-w_2}$", color="black",fontsize=12, weight="bold", style="italic")
plt.text(1.2, -1.1, "$\mathregular{w_2-w_1}$", color="black",fontsize=12, weight="bold", style="italic")
plt.text(-1.5, -0.5, "$\mathregular{w_3-w_1}$", color="black",fontsize=12, weight="bold", style="italic")
plot_boundary(X3, y3, model3,lims=[-2.2, 2.4, -2.5, 2.1],alpha= 0.7)
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
plt.xlim([-2.2, 2.4])
plt.ylim([-2.5, 2.1])
plt.show()
图14
如图14所示,softmax创建了3个决策边界,每个边界都是一条直线。例如,标签1和2之间的决策边界是标签1和2具有相等预测概率的点的位置。因此,我们可以这样写:
通过简化最后一个方程,我们得到:
这又是一条直线的方程。如果我们定义向量wi为:
这条线的法向量可以写为:
因此,决策边界垂直于w2-w1。类似地,可以表明其他决策边界都是直线,并且标签i和j之间的线垂直于向量*w*_j。
一般地,如果我们在训练数据集中有n个特征,则决策边界将是n维空间中的超平面。这里,标签i和j的超平面垂直于向量*w*_j,其中
具有softmax激活的单层神经网络是线性分类器到更高维度的推广。它继续使用超平面来预测目标的标签,但预测所有标签需要多个超平面。
到目前为止,所有显示的数据集都是线性可分的,这意味着我们可以使用超平面来分离具有不同标签的数据点。实际上,数据集很少是线性可分的。在下面的部分中,我们将看看分类非线性可分数据集的困难。
多层网络
下面代码创建了一个不能线性分离的数据集。该数据集如图15所示。
# Listing 17np.random.seed(0)
n = 1550
Xt1 = np.random.uniform(low=[0, 0], high=[4, 4], size=(n,2))
drop = (Xt1[:, 0] < 3) & (Xt1[:, 1] < 3)
Xt1 = Xt1[~drop]
yt1= np.ones(len(Xt1))Xt2 = np.random.uniform(low=[0, 0], high=[4, 4], size=(n,2))
drop = (Xt2[:, 0] > 2.3) | (Xt2[:, 1] > 2.3)Xt2 = Xt2[~drop]
yt2= np.zeros(len(Xt2))X4 = np.concatenate([Xt1, Xt2])
y4 = np.concatenate([yt1, yt2])scaler = StandardScaler()
X4 = scaler.fit_transform(X4)colors = ['red', 'blue']
plt.figure(figsize=(6, 6))
for i in np.unique(y4):plt.scatter(X4[y4==i, 0], X4[y4==i, 1], label = "y="+str(i),color=colors[int(i)], edgecolor="white", s=50)plt.xlim([-1.9, 1.9])
plt.ylim([-1.9, 1.9])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='upper right', fontsize=11, framealpha=1)
plt.show()
图15
这个数据集有两个特征和一个二进制目标。首先,我们尝试用它来训练一个sigmoid神经元。
# Listing 18backend.clear_session()
np.random.seed(2)
random.seed(2)
tf.random.set_seed(2)model4 = Sequential()
model4.add(Dense(1, activation='sigmoid', input_shape=(2,)))
model4.compile(loss = 'binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history4 = model4.fit(X4, y4, epochs=4000, verbose=0,batch_size=X4.shape[0])
在训练网络之后,我们可以绘制决策边界。图16显示了该图。
# Listing 19plt.figure(figsize=(5,5))
plot_boundary(X4, y4, model5, lims=[-2, 2, -2, 2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='upper right', fontsize=11, framealpha=1)
plt.xlim([-1.9, 1.9])
plt.ylim([-1.9, 1.9])
plt.show()
图16
正如预期的那样,决策边界是一条直线。然而,在这个数据集中,直线不能将具有不同标签的数据点分开,因为数据集不是线性可分的。我们只能使用该模型分离一小部分数据点,导致预测精度较低。
隐藏层
我们了解到单层神经网络充当线性分类器。因此,在进入输出层之前,我们必须首先将原始数据集转换为线性可分数据集。这正是多层网络中隐藏层的作用。输入层接收来自原始数据集的要素。然后,这些特征被转移到一个或多个隐藏层,这些隐藏层试图将它们变成线性可分离的特征。最后,新的特征被传输到输出层,输出层充当线性分类器。
多层网络的性能取决于隐藏层线性化输入数据集的能力。如果隐藏层无法将原始数据集转换为线性可分离的数据集(或至少接近线性可分离的数据集),则输出层将无法提供准确的分类。
让我们创建一个多层网络,可以使用以前的数据集进行训练。下面代码定义了一个带有一个隐藏层的神经网络,如图17所示。
# Listing 20backend.clear_session()
np.random.seed(2)
random.seed(2)
tf.random.set_seed(2)input_layer = Input(shape=(2,))
hidden_layer = Dense(3, activation='relu')(input_layer)
output_layer = Dense(1, activation='sigmoid')(hidden_layer)
model5 = Model(inputs=input_layer, outputs=output_layer)model5.compile(loss = 'binary_crossentropy', optimizer='adam',metrics=['accuracy'])
图17
输入层有2个神经元,因为数据集只有两个特征。隐藏层有3个神经元,每个神经元都有一个ReLU(Rectified Linear Unit)激活函数。该非线性激活函数定义如下:
图18显示了ReLU的图。
图18
最后,我们在输出层中有一个S形神经元。现在,我们使用我们的数据集训练这个模型,并绘制决策边界。
# Listing 21history5 = model5.fit(X4, y4, epochs=2200, verbose=0,batch_size=X4.shape[0])plt.figure(figsize=(5,5))
plot_boundary(X4, y4, model5, lims=[-2, 2, -2, 2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='upper right', fontsize=11, framealpha=1)
plt.xlim([-1.9, 1.9])
plt.ylim([-1.9, 1.9])
plt.show()
图19
该模型可以正确地分离标签为0和1的数据点,但决策边界不再是一条直线。模型如何实现这一点?让我们来看看隐藏层和输出层的输出。下面代码绘制了隐藏层的输出(图20)。请注意,我们在隐藏层中有三个神经元,它们的输出用一个A1,一个A2和一个A3来表示。因此,我们需要在3D空间中绘制它们。在这种情况下,输出层的决策边界是分离隐藏空间的数据点的平面。
# Listing 22hidden_layer_model = Model(inputs=model5.input,outputs=model5.layers[1].output)
hidden_layer_output = hidden_layer_model.predict(X4)
output_layer_weights = model5.layers[-1].get_weights()[0]
output_layer_biases = model5.layers[-1].get_weights()[1]w0 = output_layer_biases[0]w1, w2, w3= output_layer_weights[0, 0], \output_layer_weights[1, 0], output_layer_weights[2, 0]fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')
# Plot the bounday
lims=[0, 4, 0, 4]
ga1, ga2 = np.meshgrid(np.arange(lims[0], lims[1], (lims[1]-lims[0])/500.0),np.arange(lims[2], lims[3], (lims[3]-lims[2])/500.0))ga1l = ga1.flatten()
ga2l = ga2.flatten()
ga3 = (0.5 - (w0 + w1*ga1l + w2*ga2l)) / w3
ga3 = ga3.reshape(500, 500)
ax.plot_surface(ga1, ga2, ga3, alpha=0.5)marker_colors = ['red', 'blue']
target_labels = np.unique(y4)
n = len(target_labels)
for i, label in enumerate(target_labels):ax.scatter(hidden_layer_output[y4==label, 0],hidden_layer_output[y4==label, 1],hidden_layer_output[y4==label, 2],label="y="+str(label),color=marker_colors[i])ax.view_init(0, 25)
ax.set_xlabel('$a_1$', fontsize=14)
ax.set_ylabel('$a_2$', fontsize=14)
ax.set_zlabel('$a_3$', fontsize=14)
ax.legend(loc="best")
plt.show()
图20
原始数据集是二维和非线性可分离的。因此,隐藏层将其转换为现在可线性分离的3D数据集。然后,由输出层创建的平面很容易对其进行分类。
因此,我们得出结论,图19中所示的非线性决策边界就像一个错觉,我们在输出层仍然有一个线性分类器。然而,当平面映射到原始2D数据集时,它显示为非线性决策边界(图21)。
图21
维度的游戏
当一个数据点通过神经网络的每一层时,该层中的神经元数量决定了它的维数。这里每个神经元编码一个维度。由于原始数据集是2D的,因此我们需要在输入层中使用两个神经元。隐藏层有三个神经元,因此它将2D数据点转换为3D数据点。额外的维度以某种方式展开输入数据集,并帮助将其转换为线性可分离的数据集。最后,输出层只是3D空间中的线性分类器。
多层网络的性能取决于隐藏层线性化输入数据集的能力。在这个例子中定义的神经网络的隐藏层可以将原始数据集转换为线性可分离的数据集。但实际上,这并不总是可能的。大致线性可分的数据集有时是隐藏层可以产生的最佳结果。因此,某些数据点可能会被输出层错误标记。然而,只要模型的总体准确度足以满足实际应用,这是可以接受的。
此外,通常具有多个隐藏层的神经网络。在这种情况下,隐藏层联合收割机最终会创建一个线性可分离的数据集。
非线性函数的必要性
在隐藏层中使用非线性激活函数(如ReLU)至关重要。我们可以用一个例子来解释非线性激活函数的重要性。让我们用一个线性激活函数来替换前面神经网络中的ReLU激活函数。线性激活函数定义如下:
图22显示了该激活函数的曲线图。
图22
现在让我们使用一个线性激活函数作为图17中的先验神经网络的隐藏层。这个重新设计的神经网络如图23所示。
图23
下面定义了神经网络,并用前面的数据集训练它。决策边界如图24所示。
# Listing 23backend.clear_session()
np.random.seed(2)
random.seed(2)
tf.random.set_seed(2)input_layer = Input(shape=(2,))
hidden_layer_linear = Dense(3, activation='linear')(input_layer)
output_layer = Dense(1, activation='sigmoid')(hidden_layer_linear)
model6 = Model(inputs=input_layer, outputs=output_layer)model6.compile(loss = 'binary_crossentropy',optimizer='adam', metrics=['accuracy'])history6 = model6.fit(X4, y4, epochs=1000, verbose=0,batch_size=X4.shape[0])plt.figure(figsize=(5,5))
plot_boundary(X4, y4, model6, lims=[-2, 2, -2, 2])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='upper right', fontsize=11, framealpha=1)
plt.xlim([-1.9, 1.9])
plt.ylim([-1.9, 1.9])
plt.show()
图24
我们看到决策边界仍然是一条直线。这意味着隐藏层无法线性化数据集。让我们解释一下原因。由于我们使用线性激活函数,隐藏层的输出如下:
这些方程可以用矢量形式表示:
这意味着,在一个向量空间中的每个数据点都在一个平行于向量的平面上:
下面用向量v1和v2绘制隐藏层的输出。该图显示在图25的右侧。
# Listing 24fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')
# Plot the bounday
lims=[-3, 4, -3, 4]
ga1, ga2 = np.meshgrid(np.arange(lims[0], lims[1], (lims[1]-lims[0])/500.0),np.arange(lims[2], lims[3], (lims[3]-lims[2])/500.0))ga1l = ga1.flatten()
ga2l = ga2.flatten()
ga3 = (0.5 - (w0 + w1*ga1l + w2*ga2l)) / w3
ga3 = ga3.reshape(500, 500)
ax.plot_surface(ga1, ga2, ga3, alpha=0)marker_colors = ['red', 'blue']
target_labels = np.unique(y4)
n = len(target_labels)
for i, label in enumerate(target_labels):ax.scatter(hidden_layer_output[y4==label, 0],hidden_layer_output[y4==label, 1],hidden_layer_output[y4==label, 2],label="y="+str(label),color=marker_colors[i], alpha=0.15)ax.quiver([0], [0], [0], hidden_layer_weights[0,0],hidden_layer_weights[0,1], hidden_layer_weights[0,2],color=['black'], length=1.1, zorder=15)
ax.quiver([0], [0], [0], hidden_layer_weights[1,0],hidden_layer_weights[1,1], hidden_layer_weights[1,2],color=['black'], length=1.1, zorder=15)ax.view_init(30, 100)
ax.set_xlabel('$a_1$', fontsize=14)
ax.set_ylabel('$a_2$', fontsize=14)
ax.set_zlabel('$a_3$', fontsize=14)
ax.legend(loc="best")
plt.show()
图25
空间中的数据点显然是三维的,然而,它们的数学维数是2,因为它们都位于2D平面上。虽然隐藏层有3个神经元,但它不能生成真实的3D数据集。它只能在3D空间中旋转原始数据集,并沿向量v1和v2沿着拉伸。然而,这些操作不会破坏原始数据集的结构,并且转换后的数据集仍然是非线性可分的。因此,由输出层创建的平面无法正确分类数据点。当这个平面映射回2D空间时,它显示为一条直线(图26)。
图26
总之,隐藏层中的神经元数量不是定义转换数据集的数学维度的唯一因素。如果没有非线性激活函数,原始数据集的数学维度不会改变,隐藏层无法达到其目的。
神经网络回归问题
在本节中,我们将看到神经网络如何解决回归问题。在回归问题中,数据集的目标是连续变量。我们首先创建这样一个数据集的示例,并将其绘制在图27中。
# Listing 25np.random.seed(0)
num_points = 100
X5 = np.linspace(0,1, num_points)
y5 = -(X5-0.5)**2 + 0.25fig = plt.figure(figsize=(5, 5))
plt.scatter(X5, y5)
plt.xlabel('x', fontsize=14)
plt.ylabel('y', fontsize=14)
plt.show()
图27
单层网络
我们首先尝试单层神经网络。这里输出层有一个带有线性激活函数的神经元。该神经网络如图28所示。
图28
它的输出可以写为:
现在,如果我们对此使用均方误差(MSE)损失函数,它就会变得像线性回归模型。下面代码使用前面的数据集来训练这样一个网络。由于数据集只有一个特征,神经网络最终只有一个神经元(图29)。
图29
# Listing 26backend.clear_session()
np.random.seed(0)
random.seed(0)
tf.random.set_seed(0)model6 = Sequential()
model6.add(Dense(1, activation='linear', input_shape=(1,)))
model6.compile(optimizer='adam', loss='mse', metrics=['mse'])
history7 = model6.fit(X5, y5, epochs=500, verbose=0,batch_size=X5.shape[0])
训练模型后,我们可以绘制其预测与原始数据点的关系图。
# Listing 27X5_test = np.linspace(0,1, 1000)
yhat1 = model6.predict(X5_test)fig = plt.figure(figsize=(5, 5))
plt.scatter(X5, y5, label="Train data")
plt.plot(X5_test, yhat1, color="red", label="Prediction")
plt.xlabel('x', fontsize=14)
plt.ylabel('y', fontsize=14)
plt.legend(loc="best", fontsize=11)
plt.show()
图30
因此,我们得出结论,具有线性激活函数和MSE损失函数的单层神经网络的行为类似于线性回归模型。
多层网络
为了学习非线性数据集,我们需要添加隐藏层。图31示出了这样的网络的示例。这里我们有一个带有线性激活函数的隐藏层。
图31
然而,这个神经网络也像线性回归模型一样。为了解释原因,我们首先写出隐藏层的输出:
现在,我们可以计算神经网络的输出:
这意味着使用MSE损失函数,神经网络仍然表现得像线性模型。为了避免这个问题,我们需要在隐藏层中使用非线性激活函数。
在下一个示例中,我们将隐藏层的激活函数替换为ReLU,如图32所示。这里,隐藏层有10个神经元。
图32
下面代码实现并训练这个神经网络。
# Listing 28backend.clear_session()
np.random.seed(15)
random.seed(15)
tf.random.set_seed(15)input_layer = Input(shape=(1,))
x = Dense(10, activation='relu')(input_layer)
output_layer = Dense(1, activation='linear')(x)
model7 = Model(inputs=input_layer, outputs=output_layer)model7.compile(optimizer='adam', loss='mse', metrics=['mse'])history8 = model7.fit(X5, y5, epochs=1500, verbose=0,batch_size=X5.shape[0])hidden_layer_model = Model(inputs=model7.input,outputs=model7.layers[1].output)
hidden_layer_output = hidden_layer_model.predict(X5_test)
output_layer_weights = model7.layers[-1].get_weights()[0]
output_layer_biases = model7.layers[-1].get_weights()[1]
经过训练,我们最终可以绘制出这个神经网络的预测。
# Listing 29X5_test = np.linspace(0,1, 1000)
yhat2 = model7.predict(X5_test)fig = plt.figure(figsize=(5, 5))
plt.scatter(X5, y5, label="Train data", alpha=0.7)
plt.plot(X5_test, yhat2, color="red", label="Prediction")
plt.xlabel('x', fontsize=14)
plt.ylabel('y', fontsize=14)
plt.legend(loc="best", fontsize=11)
plt.show()
图33
我们看到网络现在可以生成非线性预测。让我们来看看隐藏层。下一个清单绘制了隐藏层的输出。图34中的一个示例。输出神经元首先将每个a乘以其对应的权重(w ^[1]a)。最后,它计算以下总和
这是神经网络的预测。图34中绘制了所有这些项。
# Listing 30fig, axs = plt.subplots(10, 4, figsize=(18, 24))
plt.subplots_adjust(wspace=0.55, hspace=0.2)for i in range(10):axs[i, 0].plot(X5_test, hidden_layer_output[:, i], color="black")axs[i, 1].plot(X5_test, hidden_layer_output[:, i]*output_layer_weights[i],color="black")axs[i, 0].set_ylabel(r'$a_{%d}$' % (i+1), fontsize=21)axs[i, 1].set_ylabel(r'$w^{[1]}_{%d}a_{%d}$' % (i+1, i+1), fontsize=21)axs[i, 2].axis('off')axs[i, 3].axis('off')
axs[i, 0].set_xlabel("x", fontsize=21)
axs[i, 1].set_xlabel("x", fontsize=21)axs[4, 2].axis('on')
axs[6, 2].axis('on')
axs[4, 2].plot(X5_test, [output_layer_biases]*len(X5_test))
axs[6, 2].plot(X5_test,(hidden_layer_output*output_layer_weights.T).sum(axis=1))
axs[6, 2].set_xlabel("x", fontsize=21)
axs[4, 2].set_ylabel("$w^{[1]}_0$", fontsize=21)
axs[4, 2].set_xlabel("x", fontsize=21)
axs[6, 2].set_ylabel("Sum", fontsize=21)
axs[5, 3].axis('on')
axs[5, 3].scatter(X5, y5, alpha=0.3)
axs[5, 3].plot(X5_test, yhat2, color="red")
axs[5, 3].set_xlabel("x", fontsize=21)
axs[5, 3].set_ylabel("$\hat{y}$", fontsize=21)
plt.show()
图34
在我们的神经网络中,隐藏层中的每个神经元都有一个ReLU激活函数。我们在图18中显示了ReLU激活函数的图。它由两条在原点相交的直线组成。左边的一个是水平的,而另一个的斜率为1。隐藏层中每个神经元的权重和偏置会修改ReLU的形状。它可以更改交点的位置、这些线的顺序以及非水平线的斜率。之后,输出层的权重也可以改变非水平线的斜率。图35中示出了这种改变的示例。
图35
然后将修改后的ReLU函数组合以近似数据集目标的形状,如图36所示。每个修改后的ReLU函数都有一个简单的结构,但是当它们组合在一起时,可以近似任何连续函数。最后,输出层的偏置被添加到ReLU函数的总和中,以垂直调整它们。
图36
通用逼近定理指出,具有一个包含足够大量神经元的隐藏层的前馈神经网络可以以任何期望的精度逼近输入子集上的任何连续函数,只要激活函数是非常数,有界和连续的。为了在实践中证明这一点,我们使用了上述同一个神经网络,但这次在隐藏层中使用了400个神经元。图37显示了该神经网络的预测。你可以看到,向隐藏层添加更多的神经元可以显著提高神经网络逼近目标的能力。
图37
总结
在本文中,我们介绍了对神经网络的直观理解以及每一层在做出最终预测时所扮演的角色。我们从感知机开始,展示了单层网络的局限性。我们看到,在分类问题中,单层神经网络相当于线性分类器,而在回归问题中,行为就像线性回归模式。解释了隐层和非线性激活函数的作用。在分类问题中,隐藏层试图线性化非线性可分离的数据集。在回归问题中,隐藏层中神经元的输出就像非线性构建块,它们被加在一起以做出最终的预测。
好了,这篇文章就介绍到这里,喜欢的小伙伴感谢给点个赞和关注,更多精彩内容持续更新~~
关于本篇文章大家有任何建议或意见,欢迎在评论区留言交流!
相关文章:
【超详细】神经网络的可视化解释
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
LVS+Keepalived 高可用集群搭建
一、高可用集群: 1.什么是高可用集群: 高可用集群(High Availability Cluster)是以减少服务中断时间为目地的服务器集群技术它通过保护用户的业务程序对外不间断提供的服务,把因软件、硬件、人为造成的故障对业务的影响…...
使用git管理uniapp项目
1.本地管理 1. 在项目根目录中新建 .gitignore 忽略文件,并配置如下: # 忽略 node_modules 目录 /node_modules /unpackage/dist 2. 打开终端,切换到项目根目录中,运行如下的命令,初始化本地 Git 仓库࿱…...
回调函数的用法
回调函数的基本用法 回调函数是一种被作为参数传递给另一个函数的函数,接收回调函数作为参数的函数在合适的时候会调用这个回调函数。回调函数为代码提供了更高的灵活性和可扩展性,下面为你详细介绍回调函数的基本用法。 基本概念 回调函数的核心在于函…...
样式垂直居中,谁才是王者
样式垂直居中,谁才是王者 面试官 常问如何让元素垂直居中,这其实是个经典的前端问题。 实现垂直居中的方法多种多样,从传统的表格布局到现代的Flexbox、Grid布局,再到绝对定位配合transform,甚至是line-height技巧&am…...
五、Three.js顶点UV坐标、纹理贴图
一部分来自1. 创建纹理贴图 | Three.js中文网 ,一部分是自己的总结。 一、创建纹理贴图 注意:把一张图片贴在模型上就是纹理贴图 1、纹理加载器TextureLoader 注意:将图片加载到加载器中 通过纹理贴图加载器TextureLoader的load()方法加…...
Linux Kernel Connection Tracking Table
在 Linux 内核中,连接跟踪表(Connection Tracking Table,简称 conntrack)是一个用于跟踪网络连接状态的核心组件。它主要由 Netfilter 框架管理,广泛应用于防火墙、NAT(网络地址转换)和负载均衡…...
NavVis VLX三维扫描:高层建筑数字化的革新力量【沪敖3D】
在三维激光扫描领域,楼梯结构因其复杂的空间形态和连续垂直移动的实际需求,一直是技术难点之一。利用NavVis VLX穿戴式移动扫描系统成功完成一栋34层建筑的高效扫描,其中楼梯部分的数据一遍成形且无任何分层或形变。本文将深入分析该项目的技…...
JVM生产环境问题定位与解决实战(二):JConsole、VisualVM到MAT的高级应用
生产问题定位指南:几款必备的可视化工具 引言 在上一篇文章中,详细的介绍了JDK自带的一系列命令行工具,,如jps、jmap、jstat、jstack以及jcmd等,这些工具为排查和诊断Java虚拟机(JVM)问题提供…...
【深入理解JWT】从认证授权到网关安全
最近的项目学习中,在进行登陆模块的用户信息验证这一部分又用到了JWT的一些概念和相关知识,特在此写了这篇文章、方便各位笔者理解JWT相关概念 目录 先来理解JWT是什么? 区分有状态认证和无状态认证 有状态认证 VS 无状态认证 JWT令牌的…...
esp工程报错:something went wrong when trying to build the project esp-idf 一种解决办法
最近上手了正点原子esp32s3板子,环境采用的是vscodeesp-idf插件。导入了正点原子的demo测试,每次都报这个错误无法建造。也不是网上说的ninja error,不是中文路径的问题。 在终端中查看,发现是缺少了git。(我这里没有…...
基于MATLAB红外弱小目标检测MPCM算法复现
摘要:本文详细介绍了一种基于人类视觉系统特性的红外弱小目标检测算法——Multiscale patch-based contrast measure (MPCM)。该算法通过增强目标与背景的对比度,有效检测红外图像中的弱小目标,并在MATLAB环境中进行了复现与实验验证。 关键…...
java基础面试篇
目录 1.概念 1.1说一下Java的特点 1.2Java为什么是跨平台的? 1.3 JVM、JDK、JRE三者关系? 1.4为什么Java解释和编译都有? 1.5 jvm是什么? 1.6 编译型语言和解释型语言的区别? 1.7 Python和Java区别是什么&#…...
Java Map实现类面试题
Java Map实现类面试题 HashMap Q1: HashMap的实现原理是什么? HashMap基于哈希表实现,使用数组链表红黑树(Java 8)的数据结构。 public class HashMapPrincipleExample {// 模拟HashMap的基本结构public class SimpleHashMap&…...
Vue2+Three.js加载并展示一个三维模型(提供Gitee源码)
目录 一、案例截图 二、安装Three.js 三、代码实现 四、Gitee源码 一、案例截图 二、安装Three.js npm install three 三、代码实现 模型资源我是放在public文件夹下面的: 完整代码: <template><div><div ref"container&qu…...
Spark内存并行计算框架
spark核心概念 spark集群架构 spark集群安装部署 spark-shell的使用 通过IDEA开发spark程序 1. Spark是什么 Apache Spark™ is a unified analytics engine for large-scale data processingspark是针对于大规模数据处理的统一分析引擎 spark是在Hadoop基础上的改进&…...
DeepSeek等LLM对网络安全行业的影响
大家好,我是AI拉呱,一个专注于人工智领域与网络安全方面的博主,现任资深算法研究员一职,兼职硕士研究生导师;热爱机器学习和深度学习算法应用,深耕大语言模型微调、量化、私域部署。曾获多次获得AI竞赛大奖,拥有多项发明专利和学术论文。对于AI算法有自己独特见解和经验…...
【QT】QLinearGradient 线性渐变类简单使用教程
目录 0.简介 1)qtDesigner中 2)实际执行 1.功能详述 3.举一反三的样式 0.简介 QLinearGradient 是 Qt 框架中的一个类,用于定义线性渐变效果(通过样式表设置)。它可以用来填充形状、背景或其他图形元素࿰…...
可狱可囚的爬虫系列课程 15:防盗链反爬虫的处理
一、防盗链了解 防盗链是一种技术手段,主要用于防止其他网站通过直接链接的方式使用本网站的资源(如图片、文件等),从而节省带宽和服务器资源。当其他网站尝试直接链接到受保护的资源时,服务器会根据设置的规则判断请求…...
Vue组件:从使用到原理的深度解析
一、什么是Vue组件? 组件是Vue的核心特性之一,它允许开发者将UI拆分为独立可复用的代码片段。每个组件本质上是一个Vue实例,具有自己的: 模板(Template) 数据(Data) 方法…...
SpringBoot接入DeepSeek(硅基流动版)+ 前端页面调试
文章目录 前言正文一、项目环境二、项目代码2.1 pom.xml2.2 DeepSeekController.java2.3 启动类2.4 logback-spring.xml2.5 application.yaml2.6 index.html 三、页面调试3.1 参数提示3.2 开始请求3.3 手动断开 前言 作为一个Java程序员,了解前沿科技技术ÿ…...
Lua的table(表)
Lua表的基本概念 Lua中的表(table)是一种多功能数据结构,可以用作数组、字典、集合等。表是Lua中唯一的数据结构机制,其他数据结构如数组、列表、队列等都可以通过表来实现。 表的实现 Lua的表由两部分组成: 数组部分…...
图片爬取案例
修改前的代码 但是总显示“失败” 原因是 修改之后的代码 import requests import os from urllib.parse import unquote# 原始URL url https://cn.bing.com/images/search?viewdetailV2&ccidTnImuvQ0&id5AE65CE4BE05EE7A79A73EEFA37578E87AE19421&thidOIP.TnI…...
【Python爬虫(90)】以Python爬虫为眼,洞察金融科技监管风云
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取,还涉及数据处理与分析。无论是新手小白还是进阶开发…...
idea + Docker + 阿里镜像服务打包部署
一、下载docker desktop软件 官网下载docker desktop,需要结合wsl使用 启动成功的画面(如果不是这个画面例如一直处理start或者是stop需要重新启动,不行就重启电脑) 打包成功的镜像在这里,如果频繁打包会导致磁盘空间被占满,需…...
C#模拟退火算法
模拟退火算法:寻找最优解的神奇 “退火之旅” 在生活中,我们都见过铁匠打铁。铁匠把烧得通红的铁块不断捶打,然后慢慢冷却,这样打造出来的金属制品才更坚固耐用。模拟退火算法就从这个退火过程中获得灵感,在计算机的数…...
网络安全防御模型
目录 6.1 网络防御概述 一、网络防御的意义 二、被动防御技术和主动防御技术 三、网络安全 纵深防御体系 四、主要防御技术 6.2 防火墙基础 一、防火墙的基本概念 二、防火墙的位置 1.防火墙的物理位置 2.防火墙的逻辑位置 3. 防火墙的不足 三、防火墙技术类型 四…...
APP自动化实战
APP自动化能做什么? 请看示例(实现批量的视频,封面功能复用能力(实现效果参考抖音号:71403700901) APP自动化实战-操作剪映APP PO模式 1. PO模式介绍 PO(Page Object)…...
Unity基础——资源导入
一.资源来源 1.Assert Store(Unity资源官方网站) (1)用于制作游戏的优质资源 | Unity Asset Store (2)或则通过Unity项目打开 2.外部资源 (1)淘宝 (2)找外…...
JMeter性能问题
性能测试中TPS上不去的几种原因 性能测试中TPS上不去的几种原因_tps一直上不去-CSDN博客 网络带宽 连接池 垃圾回收机制 压测脚本 通信连接机制 数据库配置 硬件资源 压测机 业务逻辑 系统架构 CPU过高什么原因 性能问题分析-CPU偏高 - 西瓜汁拌面 - 博客园 US C…...
形式化数学编程在AI医疗中的探索路径分析
一、引言 1.1 研究背景与意义 在数字化时代,形式化数学编程和 AI 形式化医疗作为前沿领域,正逐渐改变着我们的生活和医疗模式。形式化数学编程是一种运用数学逻辑和严格的形式化语言来描述和验证程序的技术,它通过数学的精确性和逻辑性,确保程序的正确性和可靠性。在软件…...
DeepSeek开源周Day1:FlashMLA引爆AI推理性能革命!
项目地址:GitHub - deepseek-ai/FlashMLA 开源日历:2025-02-24起 每日9AM(北京时间)更新,持续五天! 一、开源周震撼启幕 继上周预告后,DeepSeek于北京时间今晨9点准时开源「FlashMLA」,打响开源周五连…...
nginx 配置https
参考文档:nginx 文档 -- nginx官网|nginx下载安装|nginx配置|nginx教程 配置 HTTPS 服务器 HTTPS 服务器优化 SSL 证书链 单个 HTTP/HTTPS 服务器 基于名称的 HTTPS 服务器 具有多个名称 的 SSL 证书 服务器名称指示 兼容性 要配置 HTTPS 服务器,ssl…...
GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用
GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用 目录 GhostBottleneck; InvertedResidual;Squeeze and Excite 是什么,怎么用GhostBottleneckInvertedResidualSqueeze and Excite(SE)GhostBottleneck 概念: GhostBottleneck 是在轻量级神经网…...
Docker启动ES容器打包本地镜像
文章目录 1、安装 Docker2、下载镜像3、查看已下载的镜像4、 保存和加载镜像5、.tar 文件与 Docker 镜像的关系6、如何从 .tar 文件加载 Docker 镜像7、为什么需要 .tar 文件?8、ES 8.x版本无法启动8.1 问题原因8.2 解决方案8.3 提交容器为新镜像 1、安装 Docker 如…...
XXE漏洞:原理、危害与修复方法详解
目录 一、XXE漏洞概述二、XXE漏洞原理三、XXE漏洞危害1. 任意文件读取2. 命令执行3. 拒绝服务攻击(DoS)4. SSRF攻击四、XXE漏洞修复方法1. 禁用外部实体JavaPythonPHP2. 输入验证和过滤3. 安全配置服务器4. 升级解析器版本五、总结一、XXE漏洞概述 XXE(XML External Entity…...
android keystore源码分析
架构 Android Keystore API 和底层 Keymaster HAL 提供了一套基本的但足以满足需求的加密基元,以便使用访问受控且由硬件支持的密钥实现相关协议。 Keymaster HAL 是由原始设备制造商 (OEM) 提供的动态加载库,密钥库服务使用它来提供由硬件支持的加密服…...
状态模式
状态(State)模式属于行为型模式的一种。 状态模式允许对象在其内部状态改变时改变其行为,使其看上去就像改变了自身所属的类一样。 状态模式是为了把一大串if...else...的逻辑给分拆到不同的状态类中,使得将来增加状态比较容易。…...
C++ | 面向对象 | 类
👻类 👾语法格式 class className{Access specifiers: // 访问权限DataType variable; // 变量returnType functions() { } // 方法 };👾访问权限 class className {public:// 公有成员protected:// 受保护成员private:// 私有成员 }…...
鸿蒙-AVPlayer
compileVersion 5.0.2(14) 音频播放 import media from ohos.multimedia.media; import common from ohos.app.ability.common; import { BusinessError } from ohos.base;Entry Component struct AudioPlayer {private avPlayer: media.AVPlayer | nu…...
Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)
一、下载安装 1.Android Studio3.5.2下载地址:Android Studio3.5.2下载地址 其他版本下载地址:其他版本下载地址 2.安装教程(可以多找几个看看) 安装 | 手把手教你Android studio 3.5.2安装(安装教程)_a…...
从.m3u8到.mp4:使用批处理脚本完成视频处理的完整指南
这里介绍一个Windows批处理脚本(Windows Batch Script),主要用于处理 .m3u8 ts 视频文件的下载和合并功能。 以下是程序的主要功能和逻辑流程: 功能概述 参数检查与路径处理: 检查是否传递了文件或文件夹路径作为参数…...
qt5的中文乱码问题,QString、QStringLiteral 为 UTF-16 编码
qt5的中文乱码问题一直没有很明确的处理方案。 今天处理进程间通信时,也遇到了qt5乱码问题,一边是设置的GBK,一边设置的是UTF8,单向通信约定采用UTF8。 发送端保证发的是UTF8字符串,因为UTF8在网络数据包中没有字节序…...
Gurobi 并行计算的一些问题
最近尝试用 gurobi 进行并行计算,即同时用多个 cpu 核计算 gurobi 的 model,但是发现了不少问题。总体来看,gurobi 对并行计算的支持并不是那么好。 gurobi 官方对于并行计算的使用在这个网址,并有下面的大致代码: i…...
Vue3 中如何实现响应式系统中的依赖收集和更新队列的解耦?
一、问题解析:为什么需要解耦? 在响应式系统中,依赖收集(追踪数据与视图的关联关系)和更新队列(批量处理数据变化带来的副作用)是两个核心但职责不同的模块。 Vue3 通过以下设计实现解耦&…...
vue项目中动态添加类名样式不生效问题
一、问题描述 在vue项目中使用:class{tableContent: summary}给元素动态添加了类名tableContent,运行代码后查看类名已经添加成功但样式并未生效。 二、问题产生原因并解决 刚开始把样式写在了<style lang"scss" scoped></style>中&#x…...
供应链管理系统--升鲜宝门店收银系统功能解析,登录、主界面、会员 UI 设计图(一)
供应链管理系统--升鲜宝门店收银系统功能解析,登录、主界面 会员 UI 设计图(一)...
用AI写游戏3——deepseek实现kotlin android studio greedy snake game 贪吃蛇游戏
项目下载 https://download.csdn.net/download/AnalogElectronic/90421306 项目结构 就是通过android studio 建空项目,改下MainActivity.kt的内容就完事了 ctrlshiftalts 看项目结构如下 核心代码 MainActivity.kt package com.example.snakegame1// MainA…...
设计模式的引入
面向对象设计原则 1. 软件设计固有的复杂性2. 面向对象设计原则2.1 引入2.2 依赖倒置原则2.3 开放封闭原则2.4 单一职责原则2.5 Liskov 替换原则( LSP)2.6 接口隔离原则( ISP)2.7 优先使用对象组合,而不是类继承2.8 封…...
P8697 [蓝桥杯 2019 国 C] 最长子序列
P8697 [蓝桥杯 2019 国 C] 最长子序列 题目 分析代码 题目 分析 先分析一波xdm 题意呢就是在s中找有多少个能和t匹配的字符,注意:连续匹配,输出连续的次数 欧克,开始分析,首先,哎~字母!还强调…...