Deep learning with JavaScript: - Go语言中文社区

Deep learning with JavaScript:


PART2:A gentle introduction to TensorFlow.js

在本章中,以一个简单的下载时间预测问题为动力示例,我们将介绍完整的机器学习模型的主要组成部分。 我们将从实用的角度介绍张量,建模和优化,以便您可以直观地了解张量,它们如何工作以及如何正确使用它们。

1 使用TensorFlow.js预测下载时间

 首先,我们将访问我们的培训数据。 在机器学习中,数据可以从磁盘读取、在网络上下载、生成或简单地硬编码。在这个例子中我们采用最后一种方法是因为它很方便,并且我们只处理一小部分数据量。其次,我们将数据转换为张量,以便将它们输入到我们的模型中。 下一步是创建一个模型,就像我们在第1章中看到的那样,该模型类似于设计适当的可训练函数:将输入数据映射到我们要预测的事物的函数。在这种情况下,输入数据和预测目标都是数字。 一旦我们的模型和数据可用,我们将对模型进行训练,并随时监控其报告的指标。 最后,我们将使用经过训练的模型对尚未见到的数据进行预测,并评估模型的准确性。我们将通过复制和粘贴可运行代码片段以及有关理论和工具的解释来完成每个阶段。本文中的代码将以两种格式显示。 第一种格式,代码清单,提供了在参考代码存储库中可以找到的结构代码。 每清单中有标题和数字。 例如,清单2.1包含非常简短的HTML您可以将逐字复制到文件上的摘要(例如/tmp/tmp.html)您的计算机,然后在Web浏览器中打开文件:///tmp/tmp.html,它本身不会做很多事情。代码的第二种格式是控制台交互。

2.模型

其中,kernel和Bias是可调的,创造模型时,它们是随机产生的。所以我们需要在训练过程中,找到最适合我们这个工程的k和b,那么我们需要两个条件:

1.一个可以评价现在权重表现情况方法;

2.一个更新权重的方法,以优化权重。

那么,就引出了模型编译步骤:

1.损失函数(loss function):通过误差来评价目前模型的表现好坏,越小,表现越好,如果我们的模型训练很长时间,并且损失没有减少,这可能意味着我们的模型没有学会拟合数据; 

2.优化器(optimizer):神经网络通过它来更新权值,这是基于数据和损失函数的。

举例(loss function):

平均绝对误差:meanAbsoluteError = average( absolute(model_Output - targets) )

For example, given
modelOutput = [1.1, 2.2, 3.3, 3.6]
targets = [1.0, 2.0, 3.0, 4.0]
then,
meanAbsoluteError = average([|1.1 - 1.0|, |2.2 - 2.0|,
|3.3 - 3.0|, |3.6 - 4.0|])
= average([0.1, 0.2, 0.3, 0.4])
= 0.25

举例(opimizer):

随机梯度下降:stochastic gradient descent-sgd.简而言之,这意味着我们将使用微积分来确定应对权重进行哪些调整,以减少损失; 然后我们将进行这些调整并重复该过程。

3.训练

(async function() {
await model.fit(trainTensors.sizeMB,
trainTensors.timeSec,
{epochs: 10});
})();
其中,fit()方法是用数据训练模型,epochs是遍历数据的次数,10表示遍历样本10次(迭代10次 每次优化一个权重)。这个过程是很耗时间的,所以我们用异步方法/await不让这个过程阻塞主线程。这种方法和Javascript里面的asynch fetch相似。
 
模型的evalidate()方法计算应用于提供的示例特征和目标的损失函数。 它与fit()方法类似,因为它可以计算相同的损失,但是valuate()不会更新模型的权重。 我们使用valuate()来估计测试数据上模型的质量,从而对模型在将来的应用程序中的性能有所了解.
model.evaluate(testTensors.sizeMB, testTensors.timeSec).print();
Tensor
0.31778740882873535
 
在这里,我们看到测试数据中的平均损耗约为0.318。 鉴于默认情况下,模型是从随机初始状态训练而来的,您将获得不同的值。换句话说,该模型的平均绝对误差(MAE)仅超过0.3秒。也就是预计下载时间距离真实下载时间最多0.3s左右。
这个好吗? 它比仅估计常数好吗?我们可以选择的一个好的常数是平均延迟。 让我们看看使用TensorFlow.js对张量的数学运算的支持会产生什么样的错误。首先,我们将计算平均下载时间,该时间是根据我们的培训集计算得出的:
> const avgDelaySec = tf.mean(trainData.timeSec); //根据训练数据中的下载时间算一个下载延迟平均值
> avgDelaySec.print();
Tensor
0.2950500249862671 //平均下载时间为0.29s
接下来,让我们手动计算meanAbsoluteError。 MAE只是我们的预测与实际值相差多少的平均值。 我们将使用tf.sub()来计算测试目标与(恒定)预测之间的差异,并使用tf.abs()来获取绝对值(因为有时我们太低而有时又太高), 然后使用tf.mean取平均值:
tf.mean(tf.abs(tf.sub(testData.timeSec, 0.295))).print();
Tensor
0.22020000219345093
请记住,在训练过程中,内核和偏差的值会逐步更新。 在这种情况下,每个时期都是一个步骤。 如果仅针对少数时期(步骤)训练模型,则参数值可能没有机会接近最佳值。 让我们再训练模型几个周期,然后再次评估:
> model.fit(trainTensors.sizeMB,
trainTensors.timeSec,
{epochs: 200}); //迭代训练了200次 也就是参数优化了200次
> model.evaluate(testTensors.sizeMB, testTensors.timeSec).print();
Tensor
0.04879039153456688

好多了! 看来我们以前不太适合,这意味着我们的模型尚未充分适应训练数据。 现在,我们的估计平均在0.05秒以内。 比天真地猜测平均值要精确四倍。 在这本书中,我们将提供有关如何避免欠拟合不足以及更隐蔽的过拟合问题的指南,在该问题中,模型对训练数据的调整过多,而对于未曾见过的数据则无法很好地推广!

预测:

> const smallFileMB = 1;
> const bigFileMB = 100;
> const hugeFileMB = 10000;
> model.predict(tf.tensor2d([[smallFileMB], [bigFileMB],
[hugeFileMB]])).print();
Tensor
[[0.1373825 ],
[7.2438402 ],
[717.8896484]]

预测结果如上图所示,下载100MB的文件大约需要7s。请注意,我们的训练数据中没有接近此大小的示例。通常,将数据外推到训练数据之外是非常冒险的,但是存在一个简单的问题,它可能是准确的。 。 。 只要我们不会在内存缓冲区,输入输出连接等方面遇到新的麻烦。 如果我们可以在此范围内收集更多的培训数据,那就更好了。

我们还看到我们需要将输入变量包装到适当形状的张量中。 在清单2.3中,我们将inputShape定义为[1],因此模型希望每个示例都具有该形状。 fit()和predict()一次都可以处理多个示例。 为了提供n个样本,我们将它们堆叠在一起成为单个输入张量,因此该张量必须具有[n,1]的形状。 如果我们忘记了,而是为模型提供了错误形状的张量,那么我们将出现形状错误,例如以下代码:

model.predict(tf.tensor1d([smallFileMB, bigFileMB, hugeFileMB])).print();
Uncaught Error: Error when checking : expected dense_Dense1_input to have 2
dimension(s), but got array with shape [3]

这里这样相当于输入了三个独立的变量,与intputShape[1]矛盾,所以我们用tensor2d将之转换为一个整体为[20,1]的输入,则符合输入,模型会将里面的元素一个一个拿出来处理。

const model = tf.sequential([tf.layers.dense({inputShape: [1], units: 1})]);
model.compile({optimizer: 'sgd', loss: 'meanAbsoluteError'});
(async () => await model.fit(trainTensors.sizeMB,
trainTensors.timeSec,
{epochs: 10}))();
model.evaluate(testTensors.sizeMB, testTensors.timeSec);
model.predict(tf.tensor2d([[7.8]])).print();
以上是构建一个模型,并训练这个模型,并用这个模型预测的全过程。
那训练模型时的细节有哪些呢?
1.梯度下降(gradient-descent):
output = kernel * input + bias
首先,k、b权重包含了密度层从训练数据中学到的信息;不同的权重会有不同的损失函数结果,这形成了损失面:
“X”的地方代表了最佳参数选择,此时loss最小。该损失表面具有良好的碗形,碗的底部的全局最小值表示最佳参数设置。 但是,总的来说,深度学习模型所面临的损失要比这种模型复杂得多。 它将具有两个以上的维,并且可能具有许多局部最小值—即,点低于附近的任何点,但不是最低的整体。
我们模型的随机初始化从一个随机参数设置开始,类似于该地图上的一个随机位置,由此我们可以计算出初始损失;接下来,我们根据反馈信号逐步调整参数;这种逐步调整参数的过程称之为训练——也是机器学习中的学习,过程如下图:
1.绘制一批训练样本x和相应的目标y_true(打上标签 有监督训练)。 批处理只是将许多输入示例放在一起作为张量。 批次中的示例数称为批次大小。 在实际的深度学习中,通常将其设置为2的幂,例如128或256。将示例分批处理,以利用GPU的并行处理能力并使梯度的计算值更稳定。
2.在x上运行网络(称为正向传递的步骤),以获得预测y_pred。
3.计算批次上的网络损失,这是y_true和y_pred之间不匹配的度量。 回想一下,当调用model.compile()时指定了损失函数(当时是MAE)。
4.更新网络中的所有权重(参数),以稍微减少此批次的损失的方式。 各个权重的详细更新由优化器管理,这是我们在model.compile()调用期间指定的另一个选项(sgd)。const optimizer = tf.train.sgd(0.0005);(这里可能就要用到梯度优化方法来选择参数更新的方向了)
其中,步骤四最为复杂,我们不知道哪些权重要增加哪些要减少,而且不知道具体要改变多少。我们可以简单地猜测和检查,仅接受实际上减少损失的更新。 这样的算法也许可以解决像这样的简单问题,但是速度非常慢。但我们需要优化的权重有很多时,这种方法往往是不现实的,我们随机寻找一个减小损失的方向可能性是极小的。更好的方法是利用网络中使用的所有操作都是可微分的这一事实,并根据网络参数计算损耗的梯度。怎么理解梯度?
 
一个方向,如果将权重在该方向上移动一点,您将在所有可能的方向中最快地增加损失函数。
 
即使这个定义不是太过专业,但仍有很多需要解开的地方,所以让我们尝试分解:
1.首先,梯度是一个向量。 它具有与权重相同数量的元素。 它代表所有重量值选择空间中的一个方向。 如果模型的权重由两个数字组成(如我们的简单线性回归网络中的情况),则梯度为2D向量。 深度学习模型通常具有数千或数百万个维度,这些模型的梯度是具有数千或数百万个元素的向量(方向)。代表着im个权重参数的值可能的m个改变方向。(比如在损失函数图中,第一次在图的左下角,那么梯度方向应该就是往右和往上,这样才能靠近最佳的参数点)
2.其次,梯度取决于当前的重量值。 换句话说,不同的权重值将产生不同的梯度。 从图2.5可以清楚地看出,下降最快的方向取决于您在损耗面上的位置。 在左边,我们必须向右走。 在底部附近,我们必须上升,依此类推。
3.最后,梯度的数学定义指定了损失函数沿其增加的方向。 当然,在训练神经网络时,我们希望减少损失。 这就是为什么我们必须沿与梯度相反的方向移动权重的原因。*****重要
移动权重方向与梯度方向相反——该训练过程恰当地称为梯度下降。 记得在清单2.4中,当我们使用配置优化器指定模型优化器时:'sgd'吗? 随机梯度下降的梯度下降部分现在应该清楚了。 “随机”部分仅表示我们在每个梯度下降步骤中从训练数据中抽取随机样本以提高效率,而不是在每个步骤中都使用每个训练数据样本。 随机梯度下降只是对梯度下降的一种修改,以提高计算效率(sgd-随机抽取数据进行梯度下降 gradient descent)。如下图:
 

图中显示了算法是怎么通过梯度下降而调整参数达到最佳参数的。以及不同的epoches数的损失函数的区别。但是,当我们以后遇到更复杂的模型时,请记住,梯度下降的本质保持不变:只是迭代地降低复杂的高维曲面的坡度,希望我们最终会到达一个非常低的位置 。

但我们还不知道怎么设置10、50、100、200这样的学习率和步长来完成训练:如果我们使用的学习率太小而步长太小,则在合理的时间内我们将无法达到最佳参数。 相反,如果我们使用太大的学习率,因此太大的步伐,我们将完全跳过最小值,甚至可能损失比我们离开的地方更高的损失。 这将导致我们的模型参数在最优范围内剧烈振荡,而不是以一种简单的方式快速接近它。如下图是学习率太高了(const optimizer = tf.train.sgd(0.5);),造成的步长太长:

反向传播算法(BP)-计算梯度下降的方向:

y’ = v * x,loss = square(y’ - y) = square(v * x - y) 如图:
 

算法原则:假设其他所有内容(在这种情况下为x和y)保持不变,则将v增大单位量,我们将获得多少损失值变化,该量称为损耗相对于v的梯度。

具体分析v梯度下降的细节(BP):

1.在标记为损失的边缘处,我们从1的梯度值开始。这很简单,“损失的单位增加对应于损失本身的单位增加”。

2.在标记为e3的边缘,我们计算相对于e3处电流值的单位变化的损耗梯度。 因为介入运算是一个平方,并且从基本演算中我们知道(e3)2相对于e3的导数(单变量情况下的梯度)为2 * e3,所以得出的梯度值为2 * -5 = -10。 将值-10乘以之前的梯度(即1),以获得边缘e3上的梯度:-10。 这是如果e3增加1所导致的损失增加量(意思就e3增加1,损失就增加-10

)。正如您可能已经观察到的,我们使用的规则是从一条边的损失梯度到一条边的损失梯度。 下一条边缘是将前一个渐变与当前节点上本地计算的渐变相乘。 该规则有时称为连锁规则。

3. 在边缘e2,我们计算了e3相对于e2的梯度。 因为这是一个简单的添加操作,所以梯度只是1,而不考虑其他输入值(-y)。 把这个1和边缘e3的梯度,我们得到边缘e2的梯度,即-10。

4. 在边缘e1,我们计算了e2相对于e1的梯度。 这里的操作是x和v之间的乘法,即x*v。因此,e2相对于e1的梯度(即,t to v)is x,or2. 将2的值乘以边缘e2上的梯度,得到最终梯度:2*-10=-20

就这样往后推算,得到v的最终梯度-20,也就是相对于v增加一个单位,损失就增加-20;相关于走一步的方向找到了,现在需要将它乘步长也就是学习率(0.01):-(-20) * 0.01 = 0.2 超这个方向的相反方向(-(-20))走了0.2,也就是更新权值:

v = 0 + 0.2 = 0.2->如您所见,因为我们有x = 2和y = 5,并且要拟合的函数是y’= v * x,所以v的最佳值为5/2 = 2.5。 经过一步训练,v的值从0变为0.2。 换句话说,权重v稍微接近所需值。 在随后的训练步骤中它将越来越近(忽略训练数据中的任何噪声),这将基于先前描述的相同的反向传播算法。就这样继续BP,那么v逐渐靠近2.5(重复上述过程).

前面的示例特意简化了,以便易于理解。 尽管该示例捕获了反向传播的本质,但在实际的神经网络训练中发生的反向传播在以下方面与之不同:

1. 而不是提供一个简单的训练示例(x=2和y=5,在我们的情况下),通常同时提供一批许多输入示例。 用来推导梯度的损失值是一个 所有单独示例的损失值的算术平均值。(损失是多个样本的算术平均值)

2.通常,要更新的变量具有更多元素。 因此,通常涉及矩阵演算,而不是像我们刚才那样做一个简单的单变量导数。

3.通常不必涉及只为一个变量计算梯度的方法,而可以涉及多个变量。 图2.10显示了一个示例,它是一个稍微复杂的线性模型,其中有两个要优化的变量。 除k外,模型还有一个偏差项:y'= k * x + b。 这里,有两个要计算的梯度,一个用于k,一个用于b。 反向传播的两条路径都从损失开始。 它们共有一些共同的边缘,并形成树状结构。

多输入特征DL:

1.数据集:波士顿房屋价格数据集是1970年代后期在马萨诸塞州波士顿及其周围地区收集的500个简单房地产记录的集合;

2.特征表如下:

3.利用GitHub上的开源项目:https://github.com/tensorflow/tfjs-examples.git

 

4.其中有三个重要的文件:HTML、index.js、.json文件。HTML里面是UI框架和拉起.js脚本的标签。JavaScript代码通常将模块化为多个文件,以提高可读性和样式。 在这个波士顿住宅项目中,用于更新视觉元素的代码位于ui.js中,而用于下载数据的代码位于data.js中。 两者都是通过index.js的import语句引用的。我们将使用的第三种重要文件类型是元数据包.json文件,这是npm包管理器的要求。

index.html-根HTML文件,它提供DOM根和对JavaScript脚本的调用;

index.js-根JavaScript文件,用于加载数据,定义模型和训练循环并指定UI元素;

data.js下载和访问Boston-housing数据集所需的结构的实现;

ui.js-用于将UI元素连接到动作的UI挂钩的实现;画图;

normalization.js-数字例程,例如,从数据中减去平均值;

package.json-标准npm软件包定义,描述了构建和运行此演示所需的依赖关系(例如TensorFlow.js!)

引入数据:

// Initialize a BostonHousingDataset object defined in data.js.
const bostonData = new BostonHousingDataset();
const tensors = {};
// Convert the loaded csv data, of type number[][] into 2d tensors.
export const arraysToTensors = () => {
tensors.rawTrainFeatures = tf.tensor2d(bostonData.trainFeatures) ;
tensors.trainTarget = tf.tensor2d(bostonData.trainTarget) ;
tensors.rawTestFeatures = tf.tensor2d(bostonData.testFeatures) ;
tensors.testTarget = tf.tensor2d(bostonData.testTarget) ;
}
// Trigger the data to load asynchronously once the page has loaded.
get (1 number)
let tensors;
document.addEventListener('DOMContentLoaded', async () => {
await bostonData.loadData();
arraysToTensors();
}, false);
export const computeBaseline = () => {
const avgPrice = tf.mean(tensors.trainTarget);
console.log(`Average price: ${avgPrice.dataSync()[0]}`);
const baseline =
tf.mean(tf.pow(tf.sub(
tensors.testTarget, avgPrice), 2));
console.log(
`Baseline loss: ${baseline.dataSync()[0]}`);
};   
.dataSync()是将数据从GPU转换到CPU。
数据规范化:如果一些特征的缩放与其他特征非常不同,那么某些权重将比其他权重更敏感。 在一个方向上的一个很小的移动会使输出的变化更大 通用电气向不同的方向移动。 这会造成不稳定,使模型难以拟合。 为了抵消这一点,我们将首先规范我们的数据。 这意味着我们将缩放我们的特征,使它们具有零均值和单位标准差。 这种类型的归一化是常见的,也可以称为标准变换或z分归一化。 这样做的算法很简单-我们首先计算每个特征的平均值,并将其从原始值中减去,以便该特征的平均值为零。 我们再计算这个壮举 我们的标准差与平均值减去,并做一个除法。 在伪码里:
normalizedFeature = (feature - mean(feature)) / std(feature)
 例如,当特征为[10,20,30,40]时,归一化版本约为[-1.3,-0.4,0.4,1.3],它的平均值显然为零;
以下代码处于工程文件中的normalization.js中:
/**
* Calculates the mean and standard deviation of each column of an array.
*
* @param {Tensor2d} data Dataset from which to calculate the mean and
* std of each column independently.
*
* @returns {Object} Contains the mean and std of each vector
* column as 1d tensors.
*/
export function determineMeanAndStddev(data) {
const dataMean = data.mean(0);
const diffFromMean = data.sub(dataMean);
const squaredDiffFromMean = diffFromMean.square();
const variance = squaredDiffFromMean.mean(0);
const std = variance.sqrt();
return {mean, std};
}
该函数确定Mean和Stddev作为输入数据,这是一个秩-2张量。 按照惯例,第一个维度是样本维度:每个索引对应一个独立的、唯一的样本。 第二个维度是特征维度:它的12个元素对应于12个输入特征(如CRIM、ZN、INDUS等)。 因为我们要计算每个特征的平均值 莱伊:
 当我们沿轴0取平均值时,我们在样本方向上取平均值。 结果是一个秩-1张量,只剩下特征轴。就得到了不同样本的各种特征值各自的均值。 在使用轴时要小心,因为这是一个常见的错误来源
> dataMean.shape
[12]
> dataMean.print();
[3.3603415, 10.6891899, 11.2934837, 0.0600601, 0.5571442, 6.2656188,
68.2264328, 3.7099338, 9.6336336, 409.2792969, 18.4480476, 12.5154343]
上述结果为12个特征的平均值向量;
const diffFromMean = data.sub(dataMean);
将数据减去平均值,进行中心化,但是,在这种情况下,TensorFlow使用广播来扩展第二张量的形状,实际上是将其重复333次,完全按照用户的意图进行操作而不会使它们拼写出来。
然后,广播将自动从a轴到n-1轴发生。例如,以下示例通过广播对两个不同形状的随机张量应用基于元素的最大运算:
x = tf.randomUniform([64, 3, 11, 9]);
y = tf.randomUniform([11, 9]);
z = tf.maximum(x, y);
x is a random tensor with
shape [64, 3, 11 , 9].
y is a random tensor with shape [ 11 , 9].
The output z has shape [64, 3, 11 , 9] like x.
这不得不用到很多tfjs相关的API( https://js.tensorflow.org/api/latest/#sub
 
所以,上述计算svd(标准差)的方式可简便为:
const svd=data.sub(data.mean(0)).square().mean().sqrt();
得到每个特征中心化后的标准差
/**
* Given expected mean and standard deviation, normalizes a dataset by
* subtracting the mean and dividing by the standard deviation.
*
* @param {Tensor2d} data: Data to normalize.
* Shape: [numSamples, numFeatures].
* @param {Tensor1d} mean: Expected mean of the data. Shape [numFeatures].
* @param {Tensor1d} std: Expected std of the data. Shape [numFeatures]
*
* @returns {Tensor2d}: Tensor the same shape as data, but each column
* normalized to have zero mean and unit standard deviation.
 传感器的形状与数据相同,但每列*归一化为零均值和单位标准差
*/
export function normalizeTensor(data, dataMean, dataStd) {
return data.sub(dataMean).div(dataStd); //减去均值后除以std  使其均值为0  标准差为单位1   防止数据波动太大对模型建立的影响
baseline =
tf.mean(tf.pow(tf.sub(
tensors.testTarget, avgPrice), 2));

}
线性回归模型建立:

1. 我们的数据是标准化的,我们已经做了尽职调查数据工作来计算一个合理的基线-下一步是建立和拟合一个模型,看看我们是否能超过基线。

baseline =

tf.mean(tf.pow(tf.sub(

tensors.testTarget, avgPrice), 2));(计算的是MSE)

2.在下例代码中,我们定义了线性回归模型,就像在2.1节中所做的一样(来自index.js)。 代码非常相似。 我们从下载时间预测模型中看到的唯一区别在于inputShape配置,该配置现在接受长度为12而不是1的向量。单个密集层仍具有单位:1,表示输出一个数字:

export const linearRegressionModel = () => {     =()=>{}
const model = tf.sequential();
model.add(tf.layers.dense(
{inputShape: [bostonData.numFeatures], units: 1})); //这里的inputShape为特指数12
return model;
};

定义损失函数和学习率构造模型:

const LEANING_RATE=0.01.

model.compile({

optimizer:tf.train.sgd(LEANING_RATE),//学习率设置为0.01步长

loss:'meanSquraredError'//MSE   算梯度

});

训练模型:

await model.fit(tensors.trainFeatures,tensors.trainTaiget,

{

batchsize:BATCH_SIZE//一次采用多少个数据

epochs:NUM_EPOCHS,//迭代多少次

callbacks:{   //反馈调用  有

onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd , onBatchBegin , and
onBatchEnd几种
onEpochEnd:async (epoch,logs)=>{
await ui.updateStatus(
`Epoch ${epoch + 1} of ${NUM_EPOCHS} completed.`
trainLoss=logs.loss;
await ui.plotData(epoch,trainLoss);
}
}
)};
 验证是一个机器学习概念,值得一点解释. 验证数据与培训数据和测试数据分开.验证数据用于什么? 机器学习工程师将看到验证数据的结果,并使用该结果更改模型的某些配置,以改进模型。 这些配置的例子包括模型中的层数、层的大小、优化器的类型和训练期间使用的学习速率等等。但是,如果此循环执行了足够的次数,那么我们实际上是在验证数据上进行调整。 如果我们使用相同的验证数据来评估模型的最终准确性,则从模型已经看到数据的意义上说,最终评估的结果将不再是可推广的。评估结果不能保证能反映出模型将来在看不见的数据上的表现。 这是将验证与测试数据分开的目的。 我们的想法是使我们的模型适合训练数据,并根据对验证数据的评估来调整其超参数。 当我们全部完成并满意过程后,我们将仅根据测试数据对模型进行一次评估,以获得最终的,可概括的性能评估。 让我们总结一下培训、验证和测试集是什么,以及如何在TensorFlow.js中使用它们:
1. 训练数据-为了用梯度下降来拟合模型权重-在TensorFlow.js中的使用:通常,训练数据使用主要参数(x和y)调用Model.fit(x,y,config)
2. 验证数据-用于选择模型结构和超参数。 模型.FIT()有两种方法指定验证数据,两者都作为配置参数。 如果用户具有用于验证的显式数据,则可以将其指定为config.validationData. 如果您希望框架将部分训练数据拆分并将其用作验证数据,请指定要在config.validationSplit中使用的部分。 该框架将注意不使用验证数据来训练模型,因此没有重叠.
3. 对于模型性能的最终无偏估计. 评估数据通过将其作为x和y参数传递给Model.valuate(x,y,config)而暴露在系统中。
下面是编程实例:
let trainLoss;
let valLoss;
await model.fit(tensors.trainFeatures,tensors.trainTarget.{
                  batchSize:BATCH_SIZE,
                  epochs:NUM_EPOCHS,
                  validationSplit:0.2
                callbacks:{
                 onEochEnd:async(epoch,logs)=>{      
               await ui.updateStatus(
              `Epoch ${epoch + 1} of ${NUM_EPOCHS} complete;
                 trainLoss=logs.loss;
                  valLoss=logs.val_loss;
                 await ui.plotData(epoch,trainLoss,valLoss);
}
}
});
ValidationSplit:0.2字段指示model.fit()机械选择训练数据的最后20%作为验证数据。 该数据将不会用于训练(它不会影响梯度下降)。 将这个模型训练到200个时代大约需要11秒在浏览器上的现代笔记本电脑。 我们现在可以在我们的测试集上评估模型,看看它是否比基线更好。 下一个 列表显示了如何使用模型。评估()在我们保留的测试数据上收集模型的性能,然后调用我们的自定义UI例程更新视图:
await ui.updateStatus('Running on test data...');
const result = model.evaluate(
tensors.testFeatures, tensors.testTarget, {batchSize: BATCH_SIZE});
const testLoss = result.dataSync()[0];
await ui.updateStatus(
`Final train-set loss: ${trainLoss.toFixed(4)}n` +
`Final validation-set loss: ${valLoss.toFixed(4)}n` +
`Test-set loss: ${testLoss.toFixed(4)}`);
 在这里,model.value()返回一个标量(召回,一个秩-0张量),其中包含在测试集上计算的损失。 由于梯度下降的随机性,你可能会得到不同的结果 但以下结果是典型的:
Final train-set loss: 21.9864
Final validation-set loss: 31.1396
Test-set loss: 25.3206
Baseline loss: 85.58
从中我们可以看出,我们对错误的最终无偏估计约为25.3,这比我们的85.6天真基线要好得多。 回想一下我们的错误正在计算中使用meanSquaredError。 取平方根,我们看到基线估计值通常偏离9.2以上,而线性模型仅偏离约5.0。 相当很大的进步! 如果我们是世界上唯一有权访问此信息的人,那么我们可以成为1978年波士顿最好的房地产投资者! 除非以某种方式有人能够建立更加准确的估计。 。 。如果您的好奇心超前并单击了Train Neural Network Regressor,您已经知道可以进行更好的估算。 在下一章中,我们将介绍非线性深度模型,以说明如何实现这种壮举。

理解模型内容

1.理解权重的意义:

output = kernel · features + bias

核和偏倚的值都是在拟合模型时学习的。 与第2.1.3节中学习的标量线性函数相反,此处的功能和内核都是向量,“·”号表示内积,即向量的标量乘法的一般化。 内积,也称为点积,仅是匹配元素的乘积之和。 清单2.16中的伪代码更精确地定义了内部乘积。 对于表2.1所列的每个单独的特征元素,如“犯罪率”和“一氧化氮浓度”,内核中有一个相关的学习数。

function innerProduct(a, b) {
output = 0;
for (let i = 0 ; i < a.length ; i++) {
output += a[i] * b[i];
}
return output;
}//定义a和b的内积、点乘
 例如,如果模型了解到内核[i]是正的,这意味着如果特征[i]值更大,则输出将更大。 反之亦然,如果模型得知内核[j]为负值,那么。 一个很小的学习值表明模型认为相关的特征对预测几乎没有影响,而一个很大的学习值表明 该模型将重点放在特征上,特征值的微小变化将对预测产生较大的影响。请注意,只有在特征已经归一化的情况下,才可以以这种方式比较幅度,就像我们对波士顿住房数据集所做的那样。下面是五个权重的例子:
 我们可以看到,对于我们预期会对房地产价格产生负面影响的特征,例如当地居民辍学的比率和距离。学习的权重是积极的,因为我们期望与价格直接相关,如酒店的房间数量。
 从模型中提取内部权重:
 学习模型的模块化结构使提取相关权重变得容易;我们可以直接访问它们,但为了获得t,需要达到一些API级别 他的原始价值观
 重要的是要记住,由于值可能在GPU上,而且设备间通信成本很高,所以请求这些值是异步的。 这很容易,因为这个模型中只有一个层,所以我们可以在model.layers[0]上得到它的句柄。 既然我们有了这个层,我们就可以用getWeights()访问内部权重,这是旋转一个权重数组:
> model.layers[0].getWeights()[0]  //获得该模型的层和该层的权重 有kernel 和bias 我们需要kernel 所以 取【0】
let trainLoss;
let valLoss;
await model.fit(tensors.trainFeatures, tensors.trainTarget, {
batchSize: BATCH_SIZE,
epochs: NUM_EPOCHS,
validationSplit: 0.2,
callbacks: {
onEpochEnd: async (epoch, logs) => {
await ui.updateStatus(
`Epoch ${epoch + 1} of ${NUM_EPOCHS} completed.`);
trainLoss = logs.loss;
valLoss = logs.val_loss;
await ui.plotData(epoch, trainLoss, valLoss);
model.layers[0].getWeights()[0].data().then(kernelAsArr => {
// console.log(kernelAsArr);
const weightsList = describeKerenelElements(kernelAsArr);
ui.updateWeightDescription(weightsList);
});
}
}
});
 由于GPUCPU通信的异步性质,数据()是异步的,并返回张量值的承诺,而不是实际值.在清单2.17中,传递给promise的then()方法的回调将张量值绑定到一个名为kernelAsArr的变量。 如果未注释console.log()语句,则每个时期一次将如下所示的语句(列出内核的值)记录到控制台中:
 
> Float32Array(12) [-0.44015952944755554, 0.8829045295715332,
0.11802537739276886, 0.9555914402008057, -1.6466193199157715,
3.386948347091675, -0.36070501804351807, -3.0381457805633545,
1.4347705841064453, -1.3844640254974365, -1.4223048686981201,
-3.795234441757202]
 然而,由于特征是等价的,如果权重被逆转,模型将输出完全相同的值。
建立神经网络非线性的直觉:首先,有一个额外的神经元层,这是隐藏层。 第二,隐藏层包含一个非线性激活函数(由代码中的激活:‘Sigmoid’指定),whic。 h由图3.1B面板中的方框表示.
 乙状结肠函数是一个“挤压”非线性,因为它“挤压”从无限到无限的所有实值到一个小得多的范围(0到1,在这种情况下)。
 
 假设带有偏置的矩阵乘法和加法的结果是由以下随机值数组组成的二维张量:
[[1.0], [0.5], …, [0.0]],然后,通过分别调用50个元素中的每个元素上的乙状结肠(S)函数,给出[S(1.0)]、[S(0.5)]、...、[S(0.0)]=[[0.731]、[0.622]、...、[0.0],以获得致密层的最终输出。]] 除了乙状结肠函数外,在深度学习中还经常使用其他几种可微非线性函数。 包括relu和双曲正切(或tanh)..

 为什么非线性提高了我们模型的精度??

作为另一个完全合理的方案,只有犯罪率在一定范围内,房价才能随邻里犯罪率以负数变化。 像上一章中开发的那样,纯线性模型无法准确地对此类型的关系进行建模,而S形非线性则更适合于对此建模关系。 但是,通过将线性激活替换为像乙状结肠这样的非线性激活,我们是否失去了学习数据中可能存在的任何线性关系的能力? 幸运的是,答案是否定的。 这是 因为乙状结肠函数的一部分(靠近中心的部分)相当接近直线。因此,将非线性激活中加入非线性激活会导致其所能学习的输入输出关系的宽度的净增益。 此外,非线性函数不同于线性函数,因为级联非线性函数导致更丰富的非线性函数集。 这里,级联是指将一个函数的输出作为输入传递给另一个函数。 假设有两个线性函数:

f(x) = k1 * x + b1
and
g(x) = k2 * x + b2
 将这两个函数相加等于定义一个新函数h:h(X)=g(f(X)=k2*(k1*xb1)b2=(k2*k1)*x(k2*b1b2). 可以看出,h仍然是线性函数.. 它只是有一个不同的核(斜率)和不同的偏置(截距)与f1和f2。 斜率现在是(k2*k1),偏置现在是(k2*b1)。 b2)。 将任意数量的线性函数级联,总是会产生线性函数.

 

 
 
 
 

 

 

 

 
 
 

 

 

 

 

 

 

 

 

 

 

 

 

Calculating baseline loss of guessing the mean pric

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

.

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_45895329/article/details/105408438
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-04-10 17:23:56
  • 阅读 ( 1259 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢