tensorflow.js示例笔记 - predict-download-time

预测下载时间。

<!DOCTYPE html>
<html>
    <head>
        <title>predict-download-time</title>
        <style>
            canvas {
                border: 1px solid #d3d3d3;
            }
        </style>
        <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    </head>
    <body>
    </body>
    <script>
        window.onload = async function (){
            const trainData = {
                sizeMB: [
                    0.080, 9.000, 0.001, 0.100, 8.000, 5.000, 0.100, 6.000, 0.050, 0.500,
                    0.002, 2.000, 0.005, 10.00, 0.010, 7.000, 6.000, 5.000, 1.000, 1.000
                ],
                timeSec: [
                    0.135, 0.739, 0.067, 0.126, 0.646, 0.435, 0.069, 0.497, 0.068, 0.116,
                    0.070, 0.289, 0.076, 0.744, 0.083, 0.560, 0.480, 0.399, 0.153, 0.149
                ]
            };

            const testData = {
                sizeMB: [
                    5.000, 0.200, 0.001, 9.000, 0.002, 0.020, 0.008, 4.000, 0.001, 1.000,
                    0.005, 0.080, 0.800, 0.200, 0.050, 7.000, 0.005, 0.002, 8.000, 0.008
                ],
                timeSec: [
                    0.425, 0.098, 0.052, 0.686, 0.066, 0.078, 0.070, 0.375, 0.058, 0.136,
                    0.052, 0.063, 0.183, 0.087, 0.066, 0.558, 0.066, 0.068, 0.610, 0.057
                ]
            };

            // 创建一个二维张量。第一个参数为张量的值,第二个参数为张量的形状即shape。
            // 但这里的二维张量并不表示二维空间,而是指二秩张量或二轴张量。通常几维就等
            // 于几轴、几秩序,有关术语如下:
            //
            // 形状:张量的每个轴的长度(元素数量)。
            // 秩:张量的轴数。标量的秩为0,向量的秩为1,矩阵的秩为2。
            // 轴或维度:张量的一个特殊维度。
            // 大小:张量的总项数,即形状矢量元素的乘积。
            //
            // 0维张量:标量(非数组)
            // 1维张量:矢量(一元数组)
            // 2维张量:矩阵(二元数组)
            // 3维张量:矩阵数组(rgb三通道就可以表示为这个)
            // 4维张量:矩阵数组的数组(从这里开始相当于从0维张量重新计算,但每个元素已经是是3维张量了)
            // 5维张量:矩阵数组的数组的数组
            // ...
            // N维张量
            const trainTensors = {
                sizeMB: tf.tensor2d(trainData.sizeMB, [20, 1]),   // 输入数据,单特征。
                timeSec: tf.tensor2d(trainData.timeSec, [20, 1])  // 标签。
            };

            // 创建测试用张量。
            const testTensors = {
                sizeMB: tf.tensor2d(testData.sizeMB, [20, 1]),
                timeSec: tf.tensor2d(testData.timeSec, [20, 1])
            };
            // 除了通过上述传2个参数的方式创建张量,还可以直接传多维数组来创建张量,即只需要传一个参数
            // 就行,比如:
            // tf.tensor2d([[1, 2], [3, 4]]);
            // 等同于:
            // tf.tensor2d([1, 2, 3, 4], [2, 2]);
            // 其中[2, 2]表示张量的形状,即有2个轴(数组元素个数),每个轴的元素数量为2。

            // ***轴一般按照从全局到局部的顺序进行排序:首先是批次轴,随后是空间维度周周,最后是每个位
            // 置的特征。这样,在内存中,特征向量就会位于连续的区域。

            // 创建一个线性回归模型。
            const model = tf.sequential();

            // 给模型添加一个稠密层,我们在这里的输入期望为只有一个值的一维张量,操作方式也支持在上面的
            // 创建模型的代码处以传参的方式添加。
            // 对于线性回归模型的单层稠密层模型来说,kernel(内核、斜率)和bias(偏差)为可调的两个参数,
            // 下面内容会详细介绍。
            // keras中的dense稠密层又称为full connected全连接的层。它表示每一个结点都与上一层的所
            // 有结点相连,用来把前边提取到的特征综合起来。
            // 层有关的配置都是针对keras的配置,要深入研究的话,可以查看keras和神经网络的教程。
            // ***对于tfjs来说,它并没有像tf一样直接引用keras,而是实现了类似于keras的API,因此我们
            // 也可以将其看为是引入了Keras,对于一些高级API的概念性理解上,可以这么认为。
            model.add(tf.layers.dense({
                inputShape: [1],
                // units代表该层的输出维度或神经元个数, units解释为神经元个数为了方便计算参数量,解释
                // 为输出维度为了方便计算维度。
                units: 1
            }));

            // 配置模型训练选项。
            model.compile({
                // 优化器用于网络根据数据和损失函数更新其系数的算法。这里指定为随机梯度下降算法。
                // ***梯度下降是深度学习背后的最基本的算法结构,其实现为反向传播。
                optimizer: 'sgd',
                // 损失函数用于误差测量(平均绝对误差)。这是如何在训练数据上测量网络的性能。损失越少越好,
                // 当我们训练时,我们应该能够计算出随着时间的推移所造成的损失,并看到误差的下降。如果我们
                // 的模型训练了很长一段时间,损失没有减少,这可能意味着我们的模型没有习得去拟合数据。
                loss: 'meanAbsoluteError'
            });
            // 对数据集以固定迭代数量的周期来训练模型,这一过程中,模型会对数据进行拟合。
            await model.fit(
                trainTensors.sizeMB,  // 输入。
                trainTensors.timeSec, // 输出。
                {epochs: 10}  // 训练的迭代周期为10次。(先定为该值,后面会跟随注释的说明来更改)
                // {epochs: 200}  // 训练的迭代周期为200次。
            );

            // 评估模型。evaluate与fit类似,区别在于评估模型不会去更新模型的系数,不会改变模型,
            // 评估就是计算平均损耗(误差),提供特征(输入)和目标数据(输出)来计算损耗,用于了解该模型在预测上的表现。
            // meanAbsoluteError损耗函数,用于计算预测与目标之间的距离,取其绝对值(使它们全部为正),
            // 然后返回这些值的平均值。
            model.evaluate(testTensors.sizeMB, testTensors.timeSec).print(); // epochs为10的时候,误差在0.7-2.8之间。
            // 由于默认情况下模型是从随机初始状态训练而来的,梯度下降也将有随机性,因此每次执行将获得不同的值。

            // 手动计算测试数据集的误差(平均绝对误差),得到与实际值相差多少的平均值,值约为0.22。
            const avgDelaySec = tf.mean(trainData.timeSec); // 0.295。
            tf.mean(tf.abs(tf.sub(testData.timeSec, avgDelaySec))).print(); // 0.2202250212430954。

            // 从这个评估的输出我们可以发现,评估模型的输出结果很糟糕。而与评估值下面的我们手动计算的
            // 误差来做对比,发现我们手动计算的误差其实更低。0.7-2.8之间,可是远大于0.22了。换句话说,
            // 这就意味着我们模型的准确性比我们手动计算的准确性还差。说明模型尚未学习到数据的真实结构,
            // 模型和数据并未充分拟合(欠拟合)。

            // 稠密层模型在训练时,就是一个简单的线性方程:output = kernel * input + bias,
            // 在训练过程中output和input由我们提供的数据集来决定,相对来说是固定的。而kernel和bias
            // 参数则是在一开始被赋值为较小的随机值,我们称之为随机初始化。在训练过程中参数kernel和bias
            // 偏差值会逐步更新,也就是通过梯度下降的方式来进行优化。但太少的训练周期(epochs: 10)很
            // 难使参数接近最佳值,因此我们继续加大训练迭代周期,解决欠拟合问题,然后再次评估修改迭代周
            // 期后的模型。因此对调用fit方法的传入参数做以下修改,这也称之为调整超参数:
            // await model.fit(
                // ...
                // {epochs: 200}  //  训练的迭代周期为200次。
            // );
            // 然后我们再通过评估模型得到的平均误差绝大多数时候都在0.055以内,这比我们手动计算的平均误差0.295
            // 还要精确5倍以上。
            // 从实际的梯度下降曲线(模型损耗随训练迭代周期变化)来看,当迭代周期从50起时,已基本处于良好拟合的位置。

            // ***注意:模型的数据拟合程度不是越大越好,超过了合理的范围,可能会造成过度拟合(过拟合),
            // 相当于模型已经开始通过死记硬背的方式去学习训练数据,这样也会造成较大的误差。如何避免
            // 过拟合和更隐蔽的过拟合是深度学习中一直需要面对的问题。

            // 然后我们准备一组预测数据,来检验下我们的模型的真实效果。
            // ***注意:这里需要将epochs重设为200.
            const smallFileMB = 1;
            const bigFileMB = 100;
            const hugeFileMB = 10000;
            const predictSizeMBTensor = tf.tensor2d([[smallFileMB], [bigFileMB], [hugeFileMB]]);
            model.predict(predictSizeMBTensor).print();
            // 我们得到的预测结果如下(多次predict的结果也不是不同的,这里取平均的一个):
            // [[0.1342551], [6.4955053], [642.6205444]]
            // 1MB = > 0.1342551
            // 100MB => 6.4955053
            // 10000MB => 642.6205444
            // 对于10000MB来讲,我们的训练数据中,没有任何接近此打下的示例,因此对该数据的预测是非常冒险的。

            // 好了,这里总结一下,仅通过以下短短几行代码,我们就能进行训练、评估模型,也能通过模型做预测。
            // 库和高级API的存在极大低降低了入门深度学习的难度。
            // 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();
        };
    </script>
</html>

 

posted @ 2024-05-21 13:29  james·von  阅读(8)  评论(0编辑  收藏  举报