查看全课大纲(第 76 / 82 节)
- 1.函数与数据泛化
- 2.向量:从几何直觉到高维空间
- 3.矩阵与张量
- 4.特征值与特征向量:矩阵变换的不动方向
- 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
- 6.概率与统计:机器学习的数学基础
- 7.独立性与随机变量:贝叶斯公式与二项分布
- 8.期望、方差与协方差:随机变量的数字画像
- 9.联合概率:当一条轴不够用的时候
- 10.概率分布(一):从伯努利到伽马函数
- 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
- 12.机器学习的模型:找输入与输出的关系
- 13.机器学习的应用
- 14.过拟合
- 15.机器学习的分类
- 16.有监督学习:从房价预测说起
- 17.损失函数
- 18.梯度下降(上):更新规则与学习速率
- 19.梯度下降(2)
- 20.牛顿方法
- 21.KNN简介
- 22.距离测算
- 23.朴素贝叶斯
- 24.朴素贝叶斯的实际应用
- 25.信息熵:决策树构建的度量基础
- 26.信息增益与决策树的构建
- 27.ID3算法
- 28.决策树节点的划分
- 29.CART树
- 30.Bagging
- 31.Boosting:自适应提升算法
- 32.Gradient Boosting
- 33.线性模型:回归与分类的同一副骨架
- 34.最小二乘:把最优参数直接解出来
- 35.最小二乘法的几何与概率视角
- 36.极大似然:给最小二乘一个概率出身
- 37.逻辑回归(1):把线性模型接到分类上
- 38.逻辑回归(2):损失函数与多分类
- 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
- 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
- 41.优化方法(1):坐标下降与前向选择
- 42.优化方法(2):前向策略与最小角回归
- 43.拉格朗日乘子法(1):从无约束到等式约束
- 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
- 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
- 46.最大间隔:把分类问题变成优化问题
- 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
- 48.支持向量:边界上的少数派
- 49.SVM 计算过程:一笔到底算完,再走进核方法
- 50.贝叶斯公式:概率即信念
- 51.隐含变量和概率模型:给看不见的原因建模
- 52.EM 算法:期望与最大化的迭代
- 53.琴生不等式:EM 的数学钥匙
- 54.三个硬币问题:EM 的复盘与衔接
- 55.高斯混合模型:用隐变量解释双峰数据
- 56.隐马尔可夫模型
- 57.隐马尔可夫模型(2):前向算法与动态混合模型
- 58.隐马尔可夫模型在 NLP 中的运用
- 59.EM 算法:把「为什么收敛」推到底
- 60.HMM 在 NLP 中的运用:从分词到机器翻译
- 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
- 62.高维下的高斯模型
- 63.主题模型:从词袋到隐含主题
- 64.Dirichlet 分布(1)
- 65.Dirichlet 分布(2)
- 66.统计学习内容回顾
- 67.K-means 算法
- 68.PCA 降维
- 69.SVD 降维
- 70.从聚类到流形
- 71.局部线性嵌入
- 72.随机邻域嵌入:SNE 与 t-SNE
- 73.无参模型:让数据自己说话
- 74.高斯过程:函数空间里的正态分布
- 75.高斯过程(2):噪声、正则化与核的线性组合
- 76.神经网络简介:从感知器到自编码器
- 77.反向传播:把误差一层层传回去
- 78.卷积神经网络(CNN):让特征自己学出来
- 79.循环神经网络(RNN):给网络装上记忆
- 80.深度学习回顾:静态一类,动态一类
- 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
- 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传
神经网络简介:从感知器到自编码器
约 15 分钟
小象实战讲义 · 机器学习算法精讲
前面十八章里我们学的模型——线性模型、逻辑回归、决策树、SVM、各种概率模型——都属于「传统」机器学习的地盘。从这一章开始,进入今天最重要的主角:神经网络。本节先做三件事:第一,把人工神经网络和生物大脑的关系说清楚;第二,看明白单个神经元其实就是我们学过的逻辑回归,而前馈网络不过是一堆逻辑回归排成层、用矩阵运算批量跑;第三,认识一个特殊的结构——自编码器,它既能压缩、也能加密。最后我们停在损失函数的门口:误差怎么一层层「传回来」,正是下一节反向传播要回答的问题。
💡 核心导读
- 正名:我们现在做的神经网络,准确称呼是人工神经网络(Artificial Neural Networks)——它只是「类似于」大脑的数学模型,并不是真正的大脑;但它已从单独的计算领域,成长为机器学习里最重要的一环。
- 神经元 = 逻辑回归:输入向量乘权重求和、加偏置、过 sigmoid——一个神经元做的计算,正是第八章学过的逻辑回归。
- 前馈网络:一层层「线性组合 + 非线性激活」堆叠——,;整体一定是非线性函数,核心构成却是线性的计算方式。
- 自编码器:输入输出都是 维向量——把六维压到三维再重构,是压缩;输入 student、输出 35、72、89、47、47,是加密。
- 下一节钩子:构造损失函数时一般希望它是「连续」的——这样里边的误差才能不断地传递回来。
从感知器说起:借「大脑」之名
这一章讲 neural networks(神经网络)。讲师开场先摆出两个长得像的英文词:perception 与 perceptron。前者是「我们自己的认知」,后者是最早做出来的一个神经网络的工具或模型,中文叫感知器——一个词是人的认知,一个是照着它造出来的机器模型,差得其实很远。
最早的憧憬很自然:我们的大脑由很多神经构成,人们希望通过数学模型解释基本神经运作的规律,了解甚至「再造」人的神经思维和方式,通过它了解整个智能背后的奥秘。最早做神经网络模型的时候,人们确实充满了这种憧憬。
但后来逐渐发现:这套模型,也许和我们的神经相去甚远——不过这并不影响我们用它去做其他不同方向的研究。所以第一件要强调的事是:我们现在做的神经网络,应该叫 artificial neural networks,即人工神经网络,只不过大家一般都简称「神经网络」。两者之间一定要明确区分:这种模型只是类似于大脑,并不是真正大脑的情况。
还有一段定位史值得记住。神经网络最开始可以认为是单独的一种不同的计算,有人管它叫一个领域;但渐渐地,它已经默认归到今天所说的机器学习领域里,成为机器学习里面最重要的一环——而它引申出的各种 Deep Learning(深度学习)方法,也正是今天研究的重点(第二十章我们再回头看深度学习的版图)。
神经元:一个逻辑回归单元
从最基本的第一件事开始:一个神经元。跟以前所有的 machine learning 问题一样,给定一个输入,是一个 维的向量,。神经元内部先做线性组合:每个输入 乘上对应的权重 ,全部加起来,再加上一个偏置项 。
这个偏置怎么表述?别忘了我们正常的线性函数都是 的形式(第八章线性模型,8.1 节)。处理办法是老技巧:放一个永远等于 1 的输入 ,让它连到这一层各神经元的权重就是 ——「这个我们已经在最开始讲过了」(4.1 节引入的 ,4.4 节房价例里它就是 里的 )。
线性组合之后,再经过一个 sigmoid 函数,出来就得到最后的结果。sigmoid 我们是老朋友了(1.1 节第一次提过它;8.5 节逻辑回归的正片用的就是它):
它把定义域从负无穷到正无穷,压缩到 0 与 1 之间。
现在问大家一个问题,讲师当年也是这么问课堂的:这个简单的神经元模型,是否似曾相识?——正是我们曾经学过的逻辑回归(8.5 节):用这种方式进行计算,映射到最终的一个结果。也就是说,一个神经元,就是一个逻辑回归。
前馈网络:把逻辑回归排成层
单个神经元会算了,多个的情况怎么办?想象一个现在最传统的前馈神经网络:第 层里有 个节点,第 层有 个节点,每一个节点之间都有一个权重,记作 。
这个下标要专门讲一下,讲师特意停下来强调「大家要区分一下」: 表示的是第 层里边的第 个节点,到第 层的第 个节点的权重——方向是从 到 、从上一层到下一层,指的就是连接它们的「这条线」。权重本身构成一个大矩阵,所以所有的线性组合都可以通过矩阵相乘的方式来实现。
单个节点:z 是激活之前的结果
对于第 层的第 个节点,它收到的线性组合是:
其中 是上一层第 个节点的输出。注意这个 的身份:它是通过这个节点的 activation function(激活函数)之前的结果——上一层所有的输出信息,乘上 、加上偏置,汇到这个节点上来,先攒成 ,还没有过激活函数。
整层向量化:矩阵形式
一个节点会算了,一层呢?第 层一共有 个节点,每个节点都有自己的线性组合。把「每个 乘上输入」的复杂公式变成一个矩阵的相乘,就得到整层的计算:
逐元素地作用在 的每个分量上。这样,把这一层所有节点的输出 算出来,它又变成下一层的输入——不断地进行前馈的传递和传播,就构成了一个从前到后的复杂神经网络。
讲师把这整件事用最简单的描述串了一遍:我们要做的,就是 这个已知的线性组合,乘上一个 (非线性激活函数),变成下一层——稍稍区分一下,这里是 和 ;这一层的输出当成下一层的输入(讲师说「第二层的输出可以当为第三层的权重」时当场改口——是当输入),再乘上 、加上 、再过一个 ,变成第二层的输出;再往下做一步就是乘 、加 、前面再加一个输出——三层,最后是 :
给这件事定个性:整个神经网络所做的,是先进行线性组合,加一个非线性激活函数(一般是 sigmoid);把输出的结果再做线性组合,再经过一个 sigmoid;再把得到的结果做线性组合,再加一个 sigmoid。所以,第一,它一定是一个非线性函数,因为它经过了多层的激活函数;但它核心构成,是一个线性的计算方式。看似复杂,实际上大家可以想想——就是这么一个简单的过程。
换个角度:一堆逻辑回归的集合
学到这里再回头看:一个多层的、有多个节点的神经网络,它做的事情,就是把每一个节点——每一个节点不就是一个 logistic regression 嘛——把多个逻辑回归做一个集合,同时满足某些需求和条件,这就是一个前馈的神经网络。课件里有一页把这个一句话钉死了:
A neural network = running several logistic regressions at the same time. The LRs are then feed into another logistic regression function. (一个神经网络 = 同时跑好几个逻辑回归;这些逻辑回归的输出,再喂进另一个逻辑回归函数。)
一个诚实的旁白:它真的在解释大脑吗
讲师在这里停下来讲了一段很实在的话。学到这一步,如果你还在纠结「神经网络是不是在试图解释大脑的工作原理」,你会发现:按这个理解,也许我们大脑就像很多个线性拟合一样——听起来也是怪怪的。也许我们到今天,一直都没有搞清楚大脑的神经到底是怎么工作的;我们猜想它感觉起来要比这复杂一些。就算真是同样的道理,我们也没有特别强烈的、从生理学拿到的证据,说大脑的神经是类似于这样工作的。
但回过头来,无论怎么样,我们做这件事,是希望用这种数学模型去解决生活中遇到的很多问题——就类似于其他的 machine learning,一样的道理。
代码:亲手跑一遍前向传播
公式看完了,跑一遍。下面是一个 3 输入、2 隐藏、1 输出的小网络(讲义设定的小例子——讲师板书讲的是结构与流程,数字是讲义配的),把每一层的 和 都打印出来:
import math
def sigmoid(z):
"""Sigmoid 激活函数: f(z) = 1 / (1 + e^{-z})"""
return 1.0 / (1.0 + math.exp(-z))
# 一个 3 输入 -> 2 隐藏 -> 1 输出 的小网络(讲义设定的小例子)
# 第一层权重 W1 (2x3): 第 j 个输入到第 i 个隐藏神经元
W1 = [[0.4, -0.2, 0.6],
[0.1, 0.5, -0.3]]
b1 = [0.1, -0.2]
# 第二层权重 W2 (1x2): 第 j 个隐藏神经元到输出神经元
W2 = [[0.7, -0.4]]
b2 = [0.2]
x = [1.0, 0.5, -0.5]
# --- 第 1 层到第 2 层: 线性组合 z = W a + b ---
z1 = []
for i in range(2):
z1.append(W1[i][0] * x[0] + W1[i][1] * x[1] + W1[i][2] * x[2] + b1[i])
a1 = [sigmoid(z) for z in z1] # 经过激活函数, 变成这层的输出
# --- 第 2 层到第 3 层(输出层) ---
z2 = [W2[0][0] * a1[0] + W2[0][1] * a1[1] + b2[0]]
y = sigmoid(z2[0])
print("输入 x =", x)
print(f"隐藏层 线性组合 z1 = [{z1[0]:.4f}, {z1[1]:.4f}] <- 激活函数之前的结果")
print(f"隐藏层 输出 a1 = [{a1[0]:.4f}, {a1[1]:.4f}] <- sigmoid 之后, 变成下一层的输入")
print(f"输出层 线性组合 z2 = [{z2[0]:.4f}]")
print(f"网络最终输出 y = {y:.4f}")输入 x = [1.0, 0.5, -0.5]
隐藏层 线性组合 z1 = [0.1000, 0.3000] <- 激活函数之前的结果
隐藏层 输出 a1 = [0.5250, 0.5744] <- sigmoid 之后, 变成下一层的输入
输出层 线性组合 z2 = [0.3377]
网络最终输出 y = 0.5836逐行对着读。 那一行完全可以直接手算验证:第一个隐藏神经元收到的是 ,第二个是 ——这就是「上一层所有信息乘上 再加偏置,汇到节点上」的原始形态。 是 sigmoid 之后的输出: 过了 sigmoid 变 , 变 ——都落进了 0 与 1 之间。然后这两个数变成下一层的输入:输出神经元拿它们做线性组合得 ,再过一次 sigmoid,得到网络最终输出 。整个过程没有一步是黑盒:三层函数嵌套,每一层都是「乘权重、加偏置、过 sigmoid」。特别看输出神经元干的事——拿上一层的两个数做线性组合、过 sigmoid 出一个 0 到 1 的数——一个不折不扣的逻辑回归(8.5 节)。
自编码器:输入什么,就输出什么
到这里,神经网络的基本盘已经有了。讲师接着介绍一个比较重要的神经网络:Autoencoder,自编码器——课件里给它的定义是「a neural network used to learn efficient coding」,一个用来学习高效编码的神经网络。
先看以前的神经网络输出什么。以前的神经网络是一个向量输入,经过很多层、通过不同的隐藏计算,最后得到一个输出——这个输出 往往属于一个数,。讲师说到这里还当场纠正了自己:「是一个常——不要长,这是一个——Sorry,是一个实数;就是一个向量,或者只是一个数而已。」
但实际上,神经网络的功能远比这强大:我们可以构造出「输入一个向量,输出另一个向量」的网络,里边的权重、参数的调整,完全可以通过给定的一对一对的训练数据来达到。
压缩:六维进、三维转、六维出
看右边这个自编码器的例子(讲师板书结构):它的输入是六维的,从 到 (还要加上一个偏置,「所以这个永远是一,大家可以不用纠结它」);它的输出也是六维的。我们要做的事情是:输入 1、2、3、4、5、6,输出的结果也应该是 1、2、3、4、5、6——也就是说,输入什么向量,训练数据里它的输出就应该是 exactly、完全相同的这个向量。
训练了半天,这些权重在干什么?前一段权重(我们管它叫 )通过「线性组合加非线性激活函数」的映射,把六维空间的向量映射到一个三维空间——隐含节点是三个。回过头来,这三个隐含节点里的数值要重构:类似地也是「线性加激活函数」的方式,把原先的六维数据,通过这个三维重新构造出来。
大家想,这件事在做什么?不就是在做压缩嘛——把一个六维空间的数据压缩到一个三维空间,同时还要有一种方法,把六维空间的数通过三维空间的组合重新重构出来。
加密:student 进、数字出
这还是一个特别好的加密的方式。刚才说输入 123456、输出也是 123456,两个向量完全相同的时候,这个叫压缩;那假设输入的是 123456,输出来的是 10011003 这样的不同的数,或者输出的是 a、b、c、d、e、f——那我们做的就是把这些数字加密成字母。或者简单说,把字母输入 student,出来的是 35、72、89、47、47 等等——这种对应关系,它们中间的关系就是一种加密。所以自编码器还可以做很多类似于这样不同的事情。
它与以前学过的机器学习有一个非常不同的地方,值得单独点出来:它的关键好处是只需要给定 、 一对一对的数据,但这个 属于 维,同时 也属于 维——以前我们多半是 ,不说 。输入输出之间存在某种变换关系,我们就可以通过权重调整来得到它。
代码:六维压三维,再重构
把上面那个「6 进 3 转 6 出」的结构跑起来。下面代码里编码用 (输入到隐层)、解码用 (隐层到输出)两组参数——正是讲师点名的两组权重。我们让三个隐含节点分别只看一对输入的「对比」(讲义设定):第一个只看 、第二个只看 、第三个只看 :
import math
def sigmoid(z):
return 1.0 / (1.0 + math.exp(-z))
# 自编码器: 6 维输入 -> 3 个隐含节点 -> 6 维输出(讲义设定的小例子)
# W: 输入到隐层的权重 (3x6); U: 隐层到输出的权重 (6x3)
W = [[ 2, -2, 0, 0, 0, 0],
[ 0, 0, 2, -2, 0, 0],
[ 0, 0, 0, 0, 2, -2]]
b = [-0.5, -0.5, -0.5]
U = [[ 2, 0, 0], [-2, 0, 0],
[ 0, 2, 0], [ 0, -2, 0],
[ 0, 0, 2], [ 0, 0, -2]]
c = [-0.9, -0.9, -0.9, -0.9, -0.9, -0.9]
def autoencoder(x):
"""前向传播: 编码 6 -> 3, 解码 3 -> 6"""
h = [sigmoid(sum(W[i][j] * x[j] for j in range(6)) + b[i]) for i in range(3)]
x_hat = [sigmoid(sum(U[k][i] * h[i] for i in range(3)) + c[k]) for k in range(6)]
return h, x_hat
for label, x in [("x_A", [0.9, 0.1, 0.8, 0.2, 0.7, 0.3]),
("x_B", [0.2, 0.8, 0.3, 0.7, 0.1, 0.9])]:
h, x_hat = autoencoder(x)
mse = sum((xi - xh) ** 2 for xi, xh in zip(x, x_hat)) / 6
print(f"输入 {label} = {x}")
print(f" 三维隐含表示 h = [{h[0]:.4f}, {h[1]:.4f}, {h[2]:.4f}]")
print(f" 重构输出 x_hat = [{', '.join(f'{v:.4f}' for v in x_hat)}]")
print(f" 重构均方误差 MSE = {mse:.4f}")输入 x_A = [0.9, 0.1, 0.8, 0.2, 0.7, 0.3]
三维隐含表示 h = [0.7503, 0.6682, 0.5744]
重构输出 x_hat = [0.6458, 0.0831, 0.6074, 0.0965, 0.5619, 0.1142]
重构均方误差 MSE = 0.0277
输入 x_B = [0.2, 0.8, 0.3, 0.7, 0.1, 0.9]
三维隐含表示 h = [0.1545, 0.2142, 0.1091]
重构输出 x_hat = [0.3564, 0.2299, 0.3842, 0.2094, 0.3359, 0.2463]
重构均方误差 MSE = 0.1800先读 那两行。 的三对输入都是「前大后小」(0.9 大过 0.1、0.8 大过 0.2、0.7 大过 0.3),三个隐含节点全部偏高(0.7503、0.6682、0.5744); 每一对都反过来, 全部掉到低位(0.1545、0.2142、0.1091)。六维压到三维,留下的是什么,由权重 决定——我们设定它保留「成对对比」,它就只记对比。这也顺带说明了为什么权重需要「学」:想让隐含表示保留数据里真正重要的东西,随手拍脑袋是不行的。
再看重构:x_hat 并不等于原输入—— 的均方误差 0.0277, 的 0.1800。原因很直白:这组 和 是讲义随手设定的,没有经过任何训练。怎么系统地调整这两组参数,让重构误差降下来?这就需要把「误差」变成一个可以优化的目标——损失函数,并且让误差能够「传回来」指导每一层权重的调整。这正是下一节反向传播的正片。
损失函数与「误差传回来」
无论是普通的前馈神经网络(输出一个数或一个向量),还是自编码器,总之我们需要做的事情都是:通过调整里边的参数,来达到最好的结果。
如果输出最后是一个数,我们可以构造出 减去网络的输出——讲师原话:「或者不叫 了,因为这是个神经网络了,我们就管它叫 吧」——把两者的差别做平方、做加和,这不就是我们构造的一个损失函数嘛(4.2 节的老三样):
而对于 autoencoder,假设输入不同的数的时候,可以把输出归一化之后求 cross entropy(交叉熵)——8.5 节逻辑回归用过的那一款——或者去定义「重构的两者之间的距离到底怎么计算」。有了这个距离,就不断地调整里边的 (输入到隐层)和 (隐层到输出)这两组参数,使它们之间的损失函数最小。
最后是讲师留给下一节的钩子,也是本节真正的收尾:构造损失函数的时候,一般希望大家构造一个连续的损失函数——这样它后面传递的时候,里边的误差才能不断地传递回来。误差怎么一层层传回来?下一节:反向传播(Back Propagation)。
📝 动手练一练
- 压缩还是加密:同样是「6 维输入 → 3 维隐含 → 6 维输出」的自编码器—— (a) 当训练数据是「输入 、输出 」这样的自身配对时,它学到的能力叫什么? (b) 当训练数据是「输入 student、输出 35、72、89、47、47」这样的配对时呢? (c) 它跟这门课以前学的任务相比,在数据形态上最本质的不同是什么?
👉 点击查看参考答案
(a) 压缩——六维数据经由三维隐含层中转,再重构回六维;(b) 加密——学的是两组 维向量之间的对应(映射)关系,中间的关系就是「密文」;(c) 自编码器的 且 ,输入输出同维;以前的任务多半是 ,输出只是一个数。
- 单神经元手算:输入 ,权重 ,偏置 。 (a) 手算线性组合 与输出 (提示: 会算出一个很好看的数); (b) 把 从 改成 再算一次,输出变成多少? (c) 说说偏置 在神经元里扮演的角色——它和 4.4 节线性模型里的哪个老朋友是同一个身份?
👉 点击查看参考答案
(a) ,所以 ——正好落在 sigmoid 的正中点上。 (b) ,,输出被大幅推向 1。 (c) 偏置是不看输入、由节点自己决定的「底子」:它把整个线性组合整体平移——正是 4.4 节 所承担的 ( 里的截距 )在神经元里的同一个身份。 从 抬到 、 整体抬高 3,输出就从 0.5 跳到 0.95——这就是「底子」的力量。
本章小结
本节三步走。第一步,正名:从最早的感知器(perceptron)讲起——人工神经网络受生物大脑启发而生,但与真实神经机制「相去甚远」;它只是类似于大脑的数学模型,却已从单独的计算领域成长为机器学习里最重要的一环,其引申的深度学习是今天的研究重点。第二步,结构:单个神经元 = 逻辑回归(线性组合 + 偏置 + sigmoid,8.5 节);前馈网络把逻辑回归排成层,整层向量化为 、,一层层前馈传递——整体一定是非线性函数(多层激活),核心构成却是线性的计算方式;一句话:一堆逻辑回归同时跑,输出再喂给下一个逻辑回归。第三步,自编码器:输入输出都是 维——六维压到三维再重构是压缩;输入 student 输出 35、72、89、47、47 是加密;这与以前 的任务划出了分界线。收在门口:损失函数(平方和 / 交叉熵)要构造得连续——这样误差才能不断传递回来。怎么传?下一节反向传播见。
📋 行动清单
- 口述「神经元 = 逻辑回归」:不看笔记,向同学讲清单个神经元的计算与 8.5 节逻辑回归的一一对应关系。
- 维度检查:对于 ,若第 层有 5 个节点、第 层有 3 个节点, 是几行几列的矩阵?(3 行 5 列——行数是本层节点数,列数是上一层节点数。)
- 跑改代码:把自编码器代码块的输入换成你自己的六维向量(每个分量取 0 到 1 之间),观察 与重构输出的变化方向。
- 带着问题进下一节:为什么损失函数要「连续」?「误差不断传递回来」到底怎么传?下一节《反向传播》揭晓。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第19章课件:神经网络(BP/CNN/RNN)