tensorflowJS入门以及数据可视化 美国金县的房价预测

本文是作者使用TensorFlow.js进行机器学习的实践记录,主要涉及线性回归模型的构建和美国金县房屋销售数据的预测。首先介绍了tensorflowJS和数据可视化库tfjs-vis的引入,接着详细阐述了如何加载csv数据、绘制散点图、实现数据的最小-最大-归一化以及模型训练过程。文章提供了完整的代码示例,包括训练模型的步骤和训练过程中loss变化的可视化。
摘要由CSDN通过智能技术生成

前言

tensorflowJS是一个基于javascript的机器学习库,由tensorflow官方团队移植到javascript并进行维护。可以在web和node环境下运行。为了方便数据可视化,将在html中进行编程。本文完成后会提供在线体验网页。

本文为博主的入门实操记录,对于理论性的内容不会提到太多,想更好的了解机器学习原理,强烈建议先看台湾大学李宏毅教授关于机器学习的课程p2~p3, 里面会提到一些机器学习的基础概念并且讲的非常好!

(强推)李宏毅2021春机器学习课程

本文跟随tensorflowJS视频教程,使用美国金县房屋销售数据进行线性回归训练,对房价进行预测。提供本文样本数据下载,以及相关的参考资料连接。

跟随视频:
使用 TensorFlow.js 在 JavaScript 中进行机器学习

api文档:
tensorflowJS API
数据可视化 tfjs-vis API

美国金县房屋销售数据:
kc_house_data.csv

关于机器学习的理解仅为博主个人观点,可能存在偏差,欢迎交流指正。

1 项目初始化

1)创建文件夹命名为 csdn范例
2)进入文件夹,并创建 tensorflowJS.html 文件,基于最终要实现的功能进行简单的页面设计。

在这里插入图片描述
3)将数据集 kc_house_data.csv 放入文件夹中。

2 tensorflwJS 和 数据可视化库的引入

tensorflowJS.html 文件中的 head 标签中引入 tensorflowJS 以及数据可视化库 tfjs-vis

<!-- 通过'script'标签 加载 tensorflowJS 以及数据可视化插件 tfvis -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis"></script>

3 加载csv文件,映射房屋面积与房价,并数据可视化

调用 tf 即可获取 tensorflowJS 的单例对象
tf.data.csv() 方法可通过传入url字符串加载csv文件。由于浏览器限制无法加载本地文件,只能通过超链接获取。所以先通过node环境的 http-serve 模块建立一个小型的服务器。

ps. node环境下在url基础上加上前缀 "file://" 即可

3.1 小型服务器实现

1)通过node的npm包管理下载 http-server 模块

npm i http-server -g

2)打开终端(或cmd,powershell)进入项目目录,输入命令行启动小型服务器

http-server -c-1

3)启动后可看到控制台提供的ip地址,选择其中一个通过浏览器打开,可以看到项目的目录,这时候就可以通过ip地址访问 html 和获取 csv 文件了。

!!!一定要通过ip地址访问html文件,否者在请求csv文件时会存在跨域问题!!!

3.2初始化变量定义

在html中创建 <script> 标签来执行javascript代码。先初始化一些变量,获取按钮dom,和用于log的div的dom

let
    // 数据
        dataset = null, // 初始数据集
        pointDataset = null, // 面积与房价 x~y 点集
        featureArray = null, // 因素集
        labelArray = null, // 结果集
        featureTensor = null, // 因素张量(tensor)集
        labelTensor = null, // 结果张量(tensor)集
        normalizedFeature = null, // 最小-最大-归一化 后的因素数据集
        normalizedLabel = null, // 最小-最大-归一化 后的结果数据集
        trainingFeatureTensor = null, // 用于训练的因素张量
        testingFeatureTensor = null, // 用于测试的因素张量
        trainingLabelTensor = null, // 用于训练的结果张量
        testingLabelTensor = null, // 用于测试的结果张量
        model = null, // 模型
        // 信息打印窗口获取
        traningInfo = document.getElementById("traningInfo"),
        predictInfo = document.getElementById("predictInfo"),
        // 按钮获取
        loadBtn = document.getElementById("loadBtn"),
        trainingBtn = document.getElementById("trainingBtn"),
        predictBtn = document.getElementById("predictBtn"),
        toggleVisorBtn = document.getElementById("toggleVisorBtn");

3.3 准备画散点图的函数

调用 tfvis 即可获取数据可视化库的单例对象
tfvis.render.scatterplot() 调用该函数画散点图

// 散点图 tfvis.render.scatterplot() 的 params:表名、点集和该点的含义、最后 x坐标名,y坐标名
    async function plot(pointsArray, featureName) {
        tfvis.render.scatterplot({
            name: `${featureName} vs House Price`
        }, {
            values: [pointsArray],
            series: ['original']
        }, {
            xLabel: featureName,
            yLabel: "price"
        })
    }

3.4 加载csv文件并完成数据可视化

为加载按钮编写响应函数 load() ,通过该函数:
1)加载 .csv 文件。
2)映射房屋面积与房价生成点集。
3)打乱点集顺序以防点集自身的一些规律影响模型的准确度。
4)画散点图。

tf.data.csv() 调用该函数加载 .csv 文件。返回 tf.data.CSVDataset 对象
tf.data.CSVDataset.toArray() 调用该函数,异步返回数据集的数组形式。返回 Array 对象
tf.util.shuffle() 调用该函数打乱一个数组对象顺序。返回 Array 对象。

// 加载函数
    async function load() {
        // 禁用一些按钮
        loadBtn.setAttribute("disabled", "disabled");
        trainingBtn.setAttribute("disabled", "disabled");
        predictBtn.setAttribute("disabled", "disabled");
        loadBtn.classList.add('disabled');
        trainingBtn.classList.add('disabled');
        predictBtn.classList.add('disabled');

        // 加载文件
        traningInfo.innerHTML = `正在加载 kc_house_data.csv ... ...\n`;
        dataset = tf.data.csv("http://192.168.158.1:8080/kc_house_data.csv");
        traningInfo.innerHTML += `kc_house_data.csv 加载完成!\n`;

        traningInfo.innerHTML += `可视化数据集中 ... ...\n`;

        // 映射房屋面积与价格,生成点集
        pointset = dataset.map(record => ({
            x: record.sqft_living, // 房屋面积
            y: record.price, // 房屋价格
        }))

        // 随机排列数据
        pointsArray = await pointsDataset.toArray();
        if (pointsArray.length % 2 !== 0) { // 保证数据是偶数个  方便后面拆分数据集
            pointsArray.pop();
        }
        tf.util.shuffle(pointsArray);

        // 画散点图 查看面积为x,价格为y 的散点分布情况
        plot(pointsArray, "square feet");

        traningInfo.innerHTML += `数据可视化完成!\n`;

        // 启用一些按钮
        loadBtn.removeAttribute("disabled");
        loadBtn.classList.remove('disabled');
        trainingBtn.removeAttribute("disabled");
        trainingBtn.classList.remove('disabled');
    }
    // 绑定加载函数到按钮
    loadBtn.onclick = load;

测试:正常加载,并且可以在网页右侧看到散点图

在这里插入图片描述

4 可视化面板展开隐藏功能实现

tfvis.visor().toggle() 调用以切换面板显示或隐藏

// 可视化面板显示隐藏函数
    function toggleVisor() {
        tfvis.visor().toggle();
    }
    // 绑定面板显示隐藏按钮点击事件
    toggleVisorBtn.onclick = toggleVisor;

5 训练模型

5.1 “最小-最大-归一化” 以及 “逆向最小-最大-归一化”的函数编写

最小最大归一化是将一组数据的特征保留的同时,将数的最小最大范围缩小到0~1。
例如 :
[10, 20, 30] 中,将最小的 10 归一化后为下边界 0,最大数 30 归一化后为上边界 1,而 20 根据在数组中的特征,记为 0.5。
即归一化后数组为: [0, 0.5, 1]

x 为数组中的一个数,min 为数组中的最小数, max 为数组中的最大数。x’ 为归一化后的数,x’的计算公式为:
x’ = (x - min) / (max - min)

下面两个函数中 tensor 为张量对象,下面是一些会用到的该对象的简单计算函数
.min() 取张量中的最小值
.max() 取张量中的最大值
.sub() 减去一个数
.add() 加上一个数
.div() ➗一个数
.mul() 乘以一个数

5.1.1 归一化函数

// 👇归一化函数👇  将张量中的每个值x换成一个0~1中能代表x的值a   (x-min)/(max-min)
    function normalize(tensor) {
        const min = tensor.min();
        const max = tensor.max();
        return {
            tensor: tensor.sub(min).div(max.sub(min)),
            min,
            max,
        }
    }

5.1.2 逆向归一化函数

// 逆向归一化 通过归一化后的值a和最大值max最小值min 返回原始的x
    function deNormalize(tensor, min, max) {
        return tensor.mul(max.sub(min)).add(min);
    }

5.2 训练模型

1)将点集分割为 因素张量 和 结果张量。

2)对张量进行 最小最大归一化,方便计算。

3)对 因素张量 和 结果张量 分别分割为 训练张量 和 测试张量,分别用来训练模型和检测模型。

4)定义模型
----1、指定为线性模型。
----2、创建一层全连接层神经网络,配置该神经网络中有一个神经元,使用偏差 => 更精确拟合方程,设置激活方程为线性方程。
----3、配置loss方程为平方差 => loss方程是评价model好坏的标准,既当前模型的神经元计算的结果与真实值越接近,loss越低,模型越好,调整神经元可以改变loss。配置优化方案为梯度下降,步长为0.1 => 将不同配置的神经元所得到的loss画成一个函数图像的话,loss应为一条曲线,梯度下降是计算当前神经元下loss曲线的斜率,然后根据斜率调整神经元使得loss去往更低的点。步长是每次调整的大小。

5)通过 tfvis 获取每次迭代后数据可视化的回调函数 onEpochEnd

6)训练模型。
----1、指定迭代次数为20次 => 既模型会进行20代优化,可以配置每代优化的次数,这里没有配置,具体可看该函数的api。
----2、配置验证集为训练集的20% => 模型训练时可使用训练集中的数据对当前模型进行loss验证,该验证只是评估当前模型的好坏,对神经元的调整不产生影响。
----3、每次迭代结束调用可视化回调函数 onEpochEnd 来显示数据。

tf.tensor2d() 返回一个二维张量对象
tf.split() 分割成指定数量的张量
tf.sequential() 创建一个顺序模型
tf.Sequential.add() 在神经层最顶端添加一层神经网络
tf.layers.dense() 定义一层全连接层神经网络
tf.LayersModel.compple() 编译模型,配置loss和优化器
tf.Sequential.fit() 按配置训练模型

// 训练函数
    async function run() {
        // 禁用 加载按钮 和 训练按钮
        loadBtn.setAttribute("disabled", "disabled");
        loadBtn.classList.add('disabled');
        trainingBtn.setAttribute("disabled", "disabled");
        trainingBtn.classList.add('disabled');

        traningInfo.innerHTML += `初始化 因素张量 和 结果张量 中 ... ...\n`;
        // 获取因素和结果的张量
        featureTensor = tf.tensor2d(pointsArray.map(p => p.x), [pointsArray.length, 1]);
        labelTensor = tf.tensor2d(pointsArray.map(p => p.y), [pointsArray.length, 1]);

        // 将张量 最小-最大-归一化
        normalizedFeature = normalize(featureTensor);
        normalizedLabel = normalize(labelTensor);


        traningInfo.innerHTML += `分割训练集和测试集 中 ... ...\n`;
        // 分割数据集 分别用于训练和测试
        [trainingFeatureTensor, testingFeatureTensor] = tf.split(normalizedFeature.tensor, 2);
        [trainingLabelTensor, testingLabelTensor] = tf.split(normalizedFeature.tensor, 2);


        traningInfo.innerHTML += `创建model 中 ... ...\n`;
        // 创建顺序model
        model = tf.sequential();

        // 添加一层全连接层  *ps神经网络
        model.add(tf.layers.dense({
            units: 1,   // 1个神经元,因为模型比较简单,1个就够了
            useBias: true,  // 使用偏差
            activation: "linear",  // 激活方程:线性 这里有很多选项,可以在api中查看,本例比较简单,所以选择线性
            inputDim: 1,    // 输入的因素个数,只有面积,所以填 1
        }))

        // 编译model,配置优化器 定义loss
        model.compile({
            loss: "meanSquaredError", // 用平方差来评价当前模型好坏
            optimizer: tf.train.sgd(0.1), // 使用梯度下降进行优化 步长为 0.1
        })

        // 获取可视化回调函数
        const {
            onEpochEnd
        } = tfvis.show.fitCallbacks({
            name: "Training Performance",
        }, ['loss']);


        traningInfo.innerHTML += `训练model 中 ... ...\n`;
        // 训练模型
        await model.fit(trainingFeatureTensor, trainingLabelTensor, {
            epochs: 20, // 迭代20次
            validationSplit: 0.2, // 使用训练集的20%进行loss验证
            callbacks: {
                onEpochEnd, // 每次迭代结束,调用可视化回调函数
            }
        })


        traningInfo.innerHTML += `训练完成!!!\n`;
        // 禁用/启用一些按钮
        trainingBtn.setAttribute("disabled", "disabled");
        trainingBtn.classList.add('disabled');
        loadBtn.removeAttribute("disabled");
        loadBtn.classList.remove('disabled');
        predictBtn.removeAttribute("disabled");
        predictBtn.classList.remove('disabled');
    }
    // 将run()函数绑定到训练按钮上
    trainingBtn.onclick = run;

测试:功能正常,在可视化面板中可以看到训练中loss的变化曲线。

loss变化图

6 测试模型

6.1 添加按钮及样式

在这里插入图片描述

6.2 测试功能实现

未完待续。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值