内容简介:扫码直达讨论区线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是一个离散值。我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。softmax回归则适用于分类问题。
3.1线性回归
扫码直达讨论区
线性回归输出是一个连续值,因此适用于回归问题。回归问题在实际中很常见,如预测房屋价格、气温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是一个离散值。我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。softmax回归则适用于分类问题。
由于线性回归和softmax 回归都是单层神经网络,它们涉及的概念和技术同样适用于大多数的深度学习模型。我们首先以线性回归为例,介绍大多数深度学习模型的基本要素和表示方法。
3.1.1线性回归的基本要素
我们以一个简单的房屋价格预测作为例子来解释线性回归的基本要素。这个应用的目标是预测一栋房子的售出价格(元)。我们知道这个价格取决于很多因素,如房屋状况、地段、市场行情等。为了简单起见,这里我们假设价格只取决于房屋状况的两个因素,即面积(平方米)和房龄(年)。接下来我们希望探索价格与这两个因素的具体关系。
1.模型
设房屋的面积为 x 1 ,房龄为 x 2 ,售出价格为 y 。我们需要建立基于输入 x 1 和 x 2 来计算输出 y 的表达式,也就是 模型 (model)。顾名思义,线性回归假设输出与各个输入之间是线性关系:
其中 和 是 权重 (weight), b 是 偏差 (bias),且均为标量。它们是线性回归模型的 参数 (parameter)。模型输出 是线性回归对真实价格 y 的预测或估计。我们通常允许它们之间有一定误差。
2.模型训练
接下来我们需要通过数据来寻找特定的模型参数值,使模型在数据上的误差尽可能小。这个过程叫作 模型训练 (model training)。下面我们介绍模型训练所涉及的3个要素。
3.训练数据
我们通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为 训练数据集 (training data set)或 训练集 (training set),一栋房屋被称为一个 样本 (sample),其真实售出价格叫作 标签 (label),用来预测标签的两个因素叫作 特征 (feature)。特征用来表征样本的特点。
假设我们采集的样本数为 n ,索引为 i 的样本的特征为和,标签为 。对于索引为 i 的房屋,线性回归模型的房屋价格预测表达式为
4.损失函数
在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。它在评估索引为 i 的样本误差的表达式为
其中常数1/2使对平方项求导后的常数系数为 1,这样在形式上稍微简单一些。显然,误差越小表示预测价格与真实价格越相近,且当二者相等时误差为 0。给定训练数据集,这个误差只与模型参数相关,因此我们将它记为以模型参数为参数的函数。在机器学习里,将衡量误差的函数称为 损失函数 (loss function)。这里使用的平方误差函数也称为 平方损失 (square loss)。
通常,我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即
在模型训练中,我们希望找出一组模型参数,记为,来使训练样本平均损失最小:
5.优化算法
当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作 解析解 (analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作 数值解 (numerical solution)。
在求数值解的优化算法中, 小批量随机梯度下降 (mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的 小批量 (mini-batch) ,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。
在训练本节讨论的线性回归模型的过程中,模型的每个参数将作如下迭代:
在上式中, 代表每个小批量中的样本个数( 批量大小 ,batch size), η 称作 学习率 (learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作 超参数 (hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。在少数情况下,超参数也可以通过模型训练学出。本书对此类情况不做讨论。
6.模型预测
模型训练完成后,我们将模型参数 在优化算法停止时的值分别记作 。注意,这里我们得到的并不一定是最小化损失函数的最优解,而是对最优解的一个近似。然后,我们就可以使用学出的线性回归模型 来估算训练数据集以外任意一栋面积(平方米)为 、房龄(年)为 的房屋的价格了。这里的估算也叫作 模型预测 、 模型推断 或 模型测试 。
3.1.2线性回归的表示方法
我们已经阐述了线性回归的模型表达式、训练和预测。下面我们解释线性回归与神经网络的联系,以及线性回归的矢量计算表达式。
1.神经网络图
在深度学习中,我们可以使用 神经网络图 直观地表现模型结构。为了更清晰地展示线性回归作为神经网络的结构,图3-1 使用神经网络图表示本节中介绍的线性回归模型。神经网络图隐去了模型参数权重和偏差。
图3-1线性回归是一个单层神经网络
在图3-1所示的 神经网络 中, 输入 分别为 x 1 和 x 2 ,因此 输入层 的输入个数为 2。 输入个数 也叫特征数或特征向量维度。图3-1中网络的 输出 为 o , 输出层 的输出个数为 1。需要注意的是,我们直接将图3-1 中神经网络的输出 o 作为线性回归的输出,即 。由于输入层并不涉及计算,按照惯例,图3-1 所示的神经网络的 层数 为 1。所以,线性回归是一个 单层神经网络 。输出层中负责计算 o 的单元又叫 神经元 。在线性回归中, o 的计算依赖于 x 1 和 x 2 。也就是说,输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫 全连接层 (fully-connected layer)或 稠密层 ( dense layer)。
2.矢量计算表达式
在模型训练或预测时,我们常常会同时处理多个数据样本并用到 矢量计算 。在介绍线性回归的矢量计算表达式之前,让我们先考虑对两个向量相加的两种方法。
下面先定义两个 1 000 维的向量。
In [1]: nd time a = nd.ones(shape=1000) b = nd.ones(shape=1000)
向量相加的一种方法是,将这两个向量按元素逐一做标量加法。
In [2]: start = time() c = nd.zeros(shape=1000) i range(1000): c[i] = a[i] + b[i] time() - start Out[2]: 0.16967248916625977
向量相加的另一种方法是,将这两个向量直接做矢量加法。
In [3]: start = time() d = a + b time() - start Out[3]: 0.00031185150146484375
结果很明显,后者比前者更省时。因此,我们应该尽可能采用矢量计算,以提升计算效率。
让我们再次回到本节的房价预测问题。如果我们对训练数据集里的3个房屋样本(索引分别为1、2和3)逐一预测价格,将得到
现在,我们将上面3个等式转化成矢量计算。设
对 3 个房屋样本预测价格的矢量计算表达式为 ,其中的加法运算使用了广播机制(参见2.2节)。例如:
In [4]: a = nd.ones(shape=3) b = 10 a + b Out[4]: [11. 11. 11.] <NDArray 3 @cpu(0)>
广义上讲,当数据样本数为 n ,特征数为 d 时,线性回归的 矢量计算表达式 为
其中模型输出 ,批量数据样本特征 ,权重 , 偏差 。相应地,批量数据样本标签 。设模型参数 ,我们可以重写损失函数为
小批量随机梯度下降的迭代步骤将相应地改写为
其中梯度是损失有关3个为标量的模型参数的偏导数组成的向量:
小结
- 和大多数深度学习模型一样,对于线性回归这样一种单层神经网络,它的基本要素包括模型、训练数据、损失函数和优化算法。
- 既可以用神经网络图表示线性回归,又可以用矢量计算表示该模型。
- 应该尽可能采用矢量计算,以提升计算效率。
练习
使用其他包(如 NumPy)或其他编程语言(如 MATLAB),比较相加两个向量的两种方法的运行时间。
3.2线性回归的从零开始实现
扫码直达讨论区
在了解了线性回归的背景知识之后,现在我们可以动手实现它了。尽管强大的深度学习框架可以减少大量重复性工作,但若过于依赖它提供的便利,会导致我们很难深入理解深度学习是如何工作的。因此,本节将介绍如何只利用 NDArray
和 autograd
来实现一个线性回归的训练。
首先,导入本节中实验所需的包或模块,其中的 matplotlib
包 可用于作图,且设置成嵌入显示。
In [1]: %matplotlib inline display pyplot plt autograd, nd
3.2.1生成数据集
我们构造一个简单的人工训练数据集,它可以使我们能够直观比较学到的参数和真实的模型参数的区别。设训练数据集样本数为 1000,输入个数(特征数)为2。给定随机生成的批量样本特征 ,我们使用线性回归模型真实权重 和偏差 ![b=4.2](http://latex.codecogs.com/gif.latex?b=4.2,以及一个随机噪声项 ϵ 来生成标签
其中噪声项 ϵ 服从均值为 0、标准差为 0.01 的正态分布。 噪声 代表了数据集中无意义的干扰。下面,让我们生成数据集。
In [2]: num_inputs = 2 num_examples = 1000 true_w = [2, -3.4] true_b = 4.2 features = nd.random.normal(scale=1, shape=(num_examples, num_inputs)) labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b labels += nd.random.normal(scale=0.01, shape=labels.shape)
注意, features
的每一行是一个长度为 2 的向量,而 labels
的每一行是一个长度为1的向量(标量)。
In [3]: features[0], labels[0] Out[3]: ( [2.2122064 0.7740038] <NDArray 2 @cpu(0)>, [6.000587] <NDArray 1 @cpu(0)>)
通过生成第二个特征 features[:, 1]
和标签 labels
的散点图,可以更直观地观察两者间的线性关系。
In [4]: use_svg_display(): # 用矢量图显示 display.set_matplotlib_formats('svg') set_f igsize(f igsize=(3.5, 2.5)): use_svg_display() # 设置图的尺寸 plt.rcParams['f igure.f igsize'] = f igsize set_f igsize() plt.scatter(features[:, 1].asnumpy(), labels.asnumpy(), 1); # 加分号只显示图
我们将上面的 plt
作图函数以及 use_svg_display
函数和 set_f
igsize
函数定义在 d2lzh
包里。以后在作图时,我们将直接调用 d2lzh.plt
。由于 plt
在 d2lzh
包中是一个全局变量,我们在作图前只需要调用 d2lzh.set_figsize()
即可打印矢量图并设置图的尺寸。
3.2.2读取数据集
在训练模型的时候,我们需要遍历数据集并不断读取小批量数据样本。这里我们定义一个函数:它每次返回 batch_size
(批量大小)个随机样本的特征和标签。
In [5]: # 本函数已保存在d2lzh包中方便以后使用 data_iter(batch_size, features, labels): num_examples = len(features) indices = list(range(num_examples)) random.shuff le(indices) # 样本的读取顺序是随机的 i range(0, num_examples, batch_size): j = nd.array(indices[i: min(i + batch_size, num_examples)]) features.take(j), labels.take(j) # take函数根据索引返回对应元素
让我们读取第一个小批量数据样本并打印。每个批量的特征形状为(10, 2),分别对应批量大小和输入个数;标签形状为批量大小。
In [6]: batch_size = 10 X, y data_iter(batch_size, features, labels): (X, y) [[ 1.0876857 -1.7063738 ] [-0.51129895 0.46543437] [ 0.1533563 -0.735794 ] [ 0.3717077 0.9300072 ] [ 1.0115732 -0.83923554] [ 1.9738784 0.81172043] [-1.771029 -0.45138445] [ 0.7465509 -0.5054337 ] [-0.52480155 0.3005414 ] [ 0.5583534 -0.6039059 ]]
3.2.3初始化模型参数
我们将权重初始化成均值为 0、标准差为 0.01 的正态随机数,偏差则初始化成 0。
In [7]:w=nd.random.normal(scale=0.01,shape=(num_inputs, 1)) b=nd.zeros(shape=(1,))
之后的模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们需要创建它们的梯度。
In [8]:w.attach_grad() b.attach_grad()
3.2.4定义模型
下面是线性回归的矢量计算表达式的实现。我们使用 dot
函数做矩阵乘法。
In [9]: def linreg(X,w,b): # 本函数已保存在d2lzh包中方便以后使用 return nd.dot(X,w) +b
3.2.5定义损失函数
我们使用3.1节描述的平方损失来定义线性回归的损失函数。在实现中,我们需要把真实值 y
变形成预测值 y_hat
的形状。以下函数返回的结果也将和 y_hat
的形状相同。
In [10]: def squared_loss(y_hat,y): # 本函数已保存在d2lzh包中方便以后使用 return (y_hat-y.reshape(y_hat.shape)) ** 2 / 2
3.2.6定义优化算法
以下的 sgd
函数实现了3.1节中介绍的小批量随机梯度下降算法。它通过不断迭代模型参数来优化损失函数。这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。
In [11]: sgd(params, lr, batch_size): # 本函数已保存在d2lzh包中方便以后使用 param params: param[:] = param - lr * param.grad / batch_size
3.2.7训练模型
在训练中,我们将多次迭代模型参数。在每次迭代中,我们根据当前读取的小批量数据样本(特征 X
和标签 y
),通过调用反向函数 backward
计算小批量随机梯度,并调用优化算法 sgd
迭代模型参数。由于我们之前设批量大小 batch_size
为 10,每个小批量的损失 l
的形状为(10, 1)。回忆一下2.3节。由于变量 l
并不是一个标量,运行 l.backward()
将对 l
中元素求和得到新的变量,再求该变量有关模型参数的梯度。
在一个 迭代周期 (epoch)中,我们将完整遍历一遍 data_iter
函数,并对训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。这里的迭代周期个数 num_epochs
和学习率 lr
都是超参数,分别设3和 0.03。在实践中,大多超参数都需要通过反复试错来不断调节。虽然迭代周期数设得越大模型可能越有效,但是训练时间可能过长。我们会在后面第7章中详细介绍学习率对模型的影响。
In [12]: lr = 0.03 num_epochs = 3 net = linreg loss = squared_loss epoch range(num_epochs): # 训练模型一共需要num_epochs个迭代周期 # 在每一个迭代周期中, 会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X # 和y分别是小批量样本的特征和标签 X, y data_iter(batch_size, features, labels): autograd.record(): l = loss(net(X, w, b), y) # l是有关小批量X和y的损失 l.backward() # 小批量的损失对模型参数求梯度 sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数 train_l = loss(net(features, w, b), labels) ('epoch , loss ' % (epoch + 1, train_l.mean().asnumpy())) epoch 1, loss 0.040436 epoch 2, loss 0.000155 epoch 3, loss 0.000050
训练完成后,我们可以比较学到的参数和用来生成训练集的真实参数。它们应该很接近。
In [13]: true_w, w Out[13]: ([2, -3.4], [[ 1.9996936] [-3.3997262]] <NDArray 2x1 @cpu(0)>) In [14]: true_b, b Out[14]: (4.2, [4.199704] <NDArray 1 @cpu(0)>)
小结
- 可以看出,仅使用
NDArray
和autograd
模块就可以很容易地实现一个模型。接下来,本书会在此基础上描述更多深度学习模型,并介绍怎样使用更简洁的代码(见3.3节)来实现它们。
练习
(1)为什么 squared_loss
函数中需要使用 reshape
函数?
(2)尝试使用不同的学习率,观察损失函数值的下降快慢。
(3)如果样本个数不能被批量大小整除, data_iter
函数的行为会有什么变化?
3.3线性回归的简洁实现
扫码直达讨论区
随着深度学习框架的发展,开发深度学习应用变得越来越便利。实践中,我们通常可以用比3.2节更简洁的代码来实现同样的模型。在本节中,我们将介绍如何使用 MXNet 提供的Gluon接口更方便地实现线性回归的训练。
3.3.1生成数据集
我们生成与3.2节中相同的数据集。其中 features
是训练数据特征, labels
是标签。
In [1]: autograd, nd num_inputs = 2 num_examples = 1000 true_w = [2, -3.4] true_b = 4.2 features = nd.random.normal(scale=1, shape=(num_examples, num_inputs)) labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b labels += nd.random.normal(scale=0.01, shape=labels.shape)
3.3.2读取数据集
Gluon 提供了 data
包 来读取数据。由于 data
常用作变量名,我们将导入的 data
模块用添加了 Gluon 首字母的假名 gdata
代替。在每一次迭代中,我们将随机读取包含 10 个数据样本的小批量。
In [2]: data gdata batch_size = 10 # 将训练数据的特征和标签组合 dataset = gdata.ArrayDataset(features, labels) # 随机读取小批量 data_iter = gdata.DataLoader(dataset, batch_size, shuff le=True)
这里 data_iter
的使用与3.2节中的一样。让我们读取并打印第一个小批量数据样本。
In [3]: X, y data_iter: (X, y) [[-1.4011667 -1.108803 ] [-0.4813231 0.5334126 ] [ 0.57794803 0.72061497] [ 1.1208912 1.2570045 ] [-0.2504259 -0.45037505] [ 0.08554042 0.5336134 ] [ 0.6347856 1.5795654 ] [-2.118665 3.3493772 ] [ 1.1353118 0.99125063] [-0.4814555 -0.91107726]]
3.3.3定义模型
在3.2节从零开始的实现中,我们需要定义模型参数,并使用它们一步步描述模型是怎样计算的。当模型结构变得更复杂时,这些步骤将变得更烦琐。其实,Gluon 提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用 Gluon 更简洁地定义线性回归。
首先,导入 nn
模块 。实际上,“nn”是 neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。我们先定义一个模型变量 net
,它是一个 Sequential
实例。在 Gluon 中, Sequential
实例 可以看作是一个串联各个层的容器。在构造模型时,我们在该容器中依次添加层。当给定输入数据时,容器中的每一层将依次计算并将输出作为下一层的输入。
In [4]: from mxnet.gluon import nn net=nn.Sequential()
回顾图3-1 中线性回归在神经网络图中的表示。作为一个单层神经网络,线性回归输出层中的神经元和输入层中各个输入完全连接。因此,线性回归的输出层又叫全连接层。在 Gluon 中,全连接层是一个 Dense
实例 。我们定义该层输出个数为 1。
In [5]:net.add(nn.Dense(1))
值得一提的是,在 Gluon 中我们无须指定每一层输入的形状,例如线性回归的输入个数。当模型得到数据时,例如后面执行 net(X)
时,模型将自动推断出每一层的输入个数。我们将在第4章详细介绍这种机制。Gluon 的这一设计为模型开发带来便利。
3.3.4初始化模型参数
在使用 net
前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。我们从 MXNet 导入 init
模块 。该模块提供了模型参数初始化的各种方法。这里的 init
是 initializer
的缩写形式。我们通过 init.Normal(sigma=0.01)
指定权重参数每个元素将在初始化时随机采样于均值为0、标准差为 0.01 的正态分布。偏差参数默认会初始化为零。
In [6]: from mxnet import init net.initialize(init.Normal(sigma=0.01))
3.3.5定义损失函数
在 Gluon 中, loss
模块 定义了各种损失函数。我们用假名 gloss
代替导入的 loss
模块,并直接使用它提供的平方损失作为模型的损失函数。
In [7]: from mxnet.gluon import lossas gloss loss=gloss.L2Loss() # 平方损失又称L2范数损失
3.3.6定义优化算法
同样,我们也无须实现小批量随机梯度下降。在导入 Gluon 后,我们创建一个 Trainer
实例 ,并指定学习率为 0.03 的小批量随机梯度下降( sgd
)为优化算法。该优化算法将用来迭代 net
实例所有通过 add
函数嵌套的层所包含的全部参数。这些参数可以通过 collect_params
函数获取。
In [8]: from mxnet import gluon trainer=gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': 0.03})
3.3.7训练模型
在使用 Gluon 训练模型时,我们通过调用 Trainer
实例的 step
函数来迭代模型参数。3.2节中我们提到,由于变量 l
是长度为 batch_size
的一维 NDArray
,执行 l.backward()
等价于执行 l.sum().backward()
。按照小批量随机梯度下降的定义,我们在 step
函数中指明批量大小,从而对批量中样本梯度求平均。
In [9]: num_epochs = 3 epoch range(1, num_epochs + 1): X, y data_iter: autograd.record(): l = loss(net(X), y) l.backward() trainer.step(batch_size) l = loss(net(features), labels) ('epoch , loss: ' % (epoch, l.mean().asnumpy())) epoch 1, loss: 0.040309 epoch 2, loss: 0.000153 epoch 3, loss: 0.000050
下面我们分别比较学到的模型参数和真实的模型参数。我们从 net
获得需要的层,并访问其权重( weight
)和偏差( bias
)。学到的模型参数和真实的参数很接近。
In [10]: dense = net[0] true_w, dense.weight.data() Out[10]: ([2, -3.4], [[ 1.9996833 -3.3997345]] <NDArray 1x2 @cpu(0)>) In [11]: true_b, dense.bias.data() Out[11]: (4.2, [4.1996784] <NDArray 1 @cpu(0)>)
小结
- 使用 Gluon 可以更简洁地实现模型。
- 在 Gluon 中,
data
模块提供了有关数据处理的工具,nn
模块定义了大量神经网络的层,loss
模块定义了各种损失函数。 - MXNet 的
initializer
模块提供了模型参数初始化的各种方法。
练习
(1)如果将 l = loss(net(X), y)
替换成 l = loss(net(X), y).mean()
,我们需要将 trainer.step(batch_size)
相应地改成 trainer.step(1)
。这是为什么呢?
(2)查阅 MXNet 文档,看看 gluon.loss
和 init
模块里提供了哪些损失函数和初始化方法。
(3)如何访问 dense.weight
的梯度?
本文摘自最新上架的新书 《动手学深度学习》
- 人工智能机器学习深度学习领域重磅教程图书
- 美亚科学家作品
- 手学深度学习的全新模式,原理与实战紧密结合
目前市面上有关深度学习介绍的书籍大多可分两类,一类侧重方法介绍,另一类侧重实践和深度学习 工具 的介绍。本书同时覆盖方法和实践。本书不仅从数学的角度阐述深度学习的技术与应用,还包含可运行的代码,为读者展示如何在实际中解决问题。为了给读者提供一种交互式的学习体验,本书不但提供免费的教学视频和讨论区,而且提供可运行的Jupyter记事本文件,充分利用Jupyter记事本能将文字、代码、公式和图像统一起来的优势。这样不仅直接将数学公式对应成实际代码,而且可以修改代码、观察结果并及时获取经验,从而带给读者全新的、交互式的深度学习的学习体验。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【火炉炼AI】深度学习002-构建并训练单层神经网络模型
- 优化Docker中的Spring Boot应用:单层镜像方法
- 神经网络 – 序列预测LSTM神经网络落后
- 神经网络历史以及浅析神经网络与感知机
- 【神经网络】11行Python代码实现的神经网络
- 常见的五种神经网络(三):循环神经网络(上篇)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Usability for the Web
Tom Brinck、Darren Gergle、Scott D. Wood / Morgan Kaufmann / 2001-10-15 / USD 65.95
Every stage in the design of a new web site is an opportunity to meet or miss deadlines and budgetary goals. Every stage is an opportunity to boost or undercut the site's usability. Thi......一起来看看 《Usability for the Web》 这本书的介绍吧!