前端机器学习入门 - 声控切歌

486 阅读6分钟

前端机器学习入门

前言

hellow, 好久不见,这次要分享的是机器学习。

事情是这样的,上个月团建去KTV,发现现在的KTV已经可以声控切歌了,虽然语音识别在智能音箱、新能源汽车的车机上应用已经屡见不鲜了,但是连中年男人聚会必去的KTV也有这样的功能了,也许在不久的将来,机器学习也将成为开发的必备技能了,作为前端切图仔,我们是否也能尝试一下前端来实现类似的功能呢?下图是我学习后尝试做的demo。

image.png 那么接下来来看看机器学习到底是个什么玩意,在前端用什么应用场景。

机器学习

什么是机器学习

机器学习就是对计算机一部分数据进行学习,然后对另外一些数据进行预测与判断。 机器学习的核心是“使用算法解析数据,从中学习,然后对新数据做出决定或预测”。也就是说计算机利用以获取的数据得出某一模型,然后利用此模型进行预测的一种方法,这个过程跟人的学习过程有些类似,比如人获取一定的经验,可以对新问题进行预测。

除了机器学习,你可能还听过人工智能、深度学习等词汇,那么机器学习和他们又是什么关系呢?

机器学习是一种实现人工智能的方法,深度学习是一种实现机器学习的技术

Untitled Diagram.png

机器学习分类

机器学习分为监督学习、非监督学习、半监督学习、强化学习等。

  • 监督学习:数据样本有标签。
  • 非监督学习:数据样本无标签。
  • 半监督学习:数据样本有部分(少量)标签。
  • 强化学习:趋向结果则奖励,偏离结果则惩罚。 本文要入门的案例是最常见的监督学习。

机器学习在前端的应用场景

作为一名开发者,我们时常在浏览文章或者短视频的时候会收到来自大数据的亲切问候。

例如:零基础入门python「人工智能」、python「机器学习」训练营...

那么是不是只能python来做机器学习呢? 其实不是的,前端的第一语言是javascript, 相对于后端语言来说,JS在机器学习的生态圈小一些,执行效率会低一些,所以更多时候机器学习采用的都是后端语言。但是前端作为用户输入的第一道门,可以最直接的接受到用户数据,所以也有不少场景适合在前端应用。 例如:

  1. 扫福字识别

  2. 图片分类

  3. 语音识别

tensorflow.js

TensorFlow.js 是一个用于使用 JavaScript 进行机器学习开发的库。使用 JavaScript 开发机器学习模型,并直接在浏览器或 Node.js 中使用机器学习模型。

tensorflow.js官方网站

安装

npm install "@tensorflow/tfjs -S
  

神经网络

人工神经网络(artificial neural network,ANN),简称神经网络(neural network,NN),是一种模仿生物神经网络的结构和功能的数学模型或计算模型。 神经网络是一种运算模型,由大量的节点(或称“神经元”)和之间相互的联接构成。

神经网络通常包含一个输入层、若干个隐藏层和一个输出层。

跟lewis老师学习了机器学习的时候,看到了相亲的案例,也是当下多数程序员要面临的问题,正好符合神经网络的例子。

image.png

神经元

神经网络中最基本的成分是神经元(neuron)模型。每个神经元存储若干权重、偏置和一个激活函数。 神经元的输出为输入乘以权重加上偏置,经过激活函数运算后得到输出。

激活函数

在神经元中,输入的 inputs 通过加权,求和后,还被作用了一个函数,这个函数就是激活函数。引入激活函数是为了增加神经网络模型的非线性。没有激活函数的每层都相当于矩阵相乘。

张量

张量是向量和矩阵向更高维度的泛化。(看不懂?其实我看到这个概念的时候也很懵,简单来说就是一个n维素组)。

 const test1 = tf.tensor([1, 2, 3, 4, 5, 6])
 console.log(test1)
 test1.print()
 //  [1, 2, 3, 4, 5, 6]


 const test2 = tf.tensor([[1,2, 3],[4,5,6]])
 console.log(test2)
 test2.print()
 //  [[1, 2, 3], [4, 5, 6]]

 const shape = [2, 3]
 const test3 = tf.tensor([1, 2, 3, 4, 5, 6], shape)
 console.log(test3)
 test3.print()
 
 // [[1, 2, 3], [4, 5, 6]]

训练与损失

简单来说,训练模型表示通过有标签样本来学习(确定)所有权重和偏差的理想值。在监督式学习中,机器学习算法通过一些损失函数算法建模型:检查多个样本并尝试找出可最大限度地减少损失的模型;这一过程称为经验风险最小化。

image.png 左侧曲线图中的红色箭头比右侧曲线图中的对应红色箭头长得多。显然,相较于左侧曲线图中的蓝线,右侧曲线图中的蓝线代表的是预测效果更好的模型。

线性回归模型

简单的可以理解为理想的模型公式是我们初中学习的 Y=KX +B

image.png

 const xs = [1, 2, 3, 4];
 const ys = [1, 3, 5, 7];

 tfvis.render.scatterplot(
     { name: '线性回归训练集' },
     { values: xs.map((x, i) => ({ x, y: ys[i] })) },
     { xAxisDomain: [0, 5], yAxisDomain: [0, 8] }
 );

 const model = tf.sequential();
 model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
 model.compile({ loss: tf.losses.meanSquaredError, optimizer: tf.train.sgd(0.1) });

 const inputs = tf.tensor(xs);
 const labels = tf.tensor(ys);
 await model.fit(inputs, labels, {
     batchSize: 4,
     epochs: 200,
     callbacks: tfvis.show.fitCallbacks(
         { name: '训练过程' },
         ['loss']
     )
 });

 const output = model.predict(tf.tensor([5]));
     alert(`如果 x 为 5,那么预测 y 为 ${output.dataSync()[0]}`);
 })

逻辑回归模型

逻辑回归(Logistic Regression)是机器学习中的一种分类模型。逻辑回归的本质是假设数据服从这个分布,然后使用极大似然估计做参数的估计。

image.png

  tfvis.render.scatterplot(
     { name: '逻辑回归训练数据' },
     {
         values: [
             data.filter(p => p.label === 1),
             data.filter(p => p.label === 0),
         ]
     }
 );
 model = tf.sequential();
 model.add(tf.layers.dense({
     units: 1,
     inputShape: [2],
     activation: 'sigmoid'
 }));
 model.compile({
     loss: tf.losses.logLoss,
     optimizer: tf.train.adam(0.1)
 });

 const inputs = tf.tensor(data.map(p => [p.x, p.y]));
 const labels = tf.tensor(data.map(p => p.label));

 await model.fit(inputs, labels, {
     batchSize: 40,
     epochs: 20,
     callbacks: tfvis.show.fitCallbacks(
         { name: '训练效果' },
         ['loss']
     )
 });

迁移学习

迁移学习是一种机器学习方法,就是把为任务 A 开发的模型作为初始点,重新使用在为任务 B 开发模型的过程 中。当你想将一个神经网络应用到一个没有充足数据的新领域当中,同时又有一个巨大的预先训练的数据池可以迁移到你的新任务中的时候,迁移学习将是很有用的。 举个栗子:学习骑自行车,再学习摩托车,骑行的方法是类似的,这样学习骑摩托化的成本就降低了。

下面我们来尝试学习tensorflow官方提供的 speech-commands,通过官方提供的语音包,我们可以快速的训练出一个自己的语音模型。

<template>
    <div>
        <div>
            <button v-for="(item,index) in labelList" 
                :key="item.label"
                :disabled="item.disabled"
                @click="onCollect(index)"
            >
                {{item.label}}
            </button>
        </div>
        <button @click="onSave">保存</button>
        <pre >{{count}}</pre>
        <button @click="onTrain">训练</button>
        <br><br>
        监听开关:<input type="checkbox" v-model="checked" @change="onChange" />
    </div>
</template>
<script>
import * as speechCommands from '@tensorflow-models/speech-commands';
import * as tfvis from '@tensorflow/tfjs-vis';
import { ref, onMounted, nextTick } from "vue"
export default {
    setup() {
        const MODEL_PATH = 'http://127.0.0.1:3000';
         let hasTrained = false
        let transferRecognizer;
        const count = ref(0)
        const checked = ref(false);
        const labelList = ref([
            {
                label: '播放',
                disabled: false
            },
            {
                label: '暂停',
                disabled: false
            },
            {
                label: '切歌',
                disabled: false
            }
        ])

        const onCollect = async (index) => {
            labelList.value[index].disabled = true;
            
            const label = labelList.value[index].label;
            await transferRecognizer.collectExample(
                label
            );
            count.value = transferRecognizer.countExamples();

            labelList.value[index].disabled = false;
        }

        const onChange = async() => {
            if (!hasTrained) return
            if (checked.value) {
                await transferRecognizer.listen(result => {
                    const { scores } = result;
                    const labels = transferRecognizer.wordLabels();
                    const index = scores.indexOf(Math.max(...scores));
                    console.log(labels[index]);
                }, {
                    overlapFactor: 0,
                    probabilityThreshold: 0.75
                });
            } else {
                transferRecognizer.stopListening();
            }
        }

        const onTrain = async () => {
            await transferRecognizer.train({
                epochs: 30,
                callback: tfvis.show.fitCallbacks(
                    { name: '训练效果' },
                    ['loss', 'acc'],
                    { callbacks: ['onEpochEnd'] }
                )
            });
            hasTrained = true
        }

        const onSave = () => {
            const arrayBuffer = transferRecognizer.serializeExamples();
            const blob = new Blob([arrayBuffer]);
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = 'data.bin';
            link.click();
        }

        onMounted(async() => {
            const recognizer = speechCommands.create(
                'BROWSER_FFT',
                null,
                MODEL_PATH + '/speech/model.json',
                MODEL_PATH + '/speech/metadata.json'
            );
            await recognizer.ensureModelLoaded();
            transferRecognizer = recognizer.createTransfer('声音数据收集');
        })


        return {
            count,
            checked,
            labelList,

            onSave,
            onTrain,
            onCollect,
            onChange
        }
    }
}
</script>

声控切歌

image.png

<template>
    <div>
        <p>声控</p>
        监听开关:<input type="checkbox" v-model="checked" @change="onCheck" />
        <video ref="videoRef" controls="" :src="music"></video>
    </div>
</template>

<script>
import * as speechCommands from '@tensorflow-models/speech-commands';
import { ref, onMounted, nextTick } from "vue"
export default {
    setup() {
        const checked = ref(false)
        const videoRef = ref()
        let transferRecognizer;
        const MODEL_PATH = 'http://127.0.0.1:3000';


        let index = 0;
        const musicList = [
            'https://gd-filems.dancf.com/saas/xi19e5/0/3d313fd5-1f4a-4612-9349-5e98e22bbae17909.mp3',
            'https://gd-filems.dancf.com/saas/xi19e5/0/b0ca8d0a-64cb-4574-804d-7aa6b0ec25717870.mp3',
            'https://gd-filems.dancf.com/saas/xi19e5/0/054d98f8-ea7b-42e8-8d87-08f35f802ff97923.mp3'
        ]
        const music = ref(musicList[index]);


        const nextMusic = () => {
            if (index < musicList.length - 1) {
                index++;
            } else {
                index = 0;
            }
            console.log('audioRef.value',videoRef.value)
            // videoRef.value.pause();
            music.value = musicList[index];
            nextTick(() => {
                videoRef.value.play();
            })
        }

        const pause = () => {
            videoRef.value.pause();
        }

        const play = () => {
          videoRef.value.play()
        }


        const handle = {
          "切歌": nextMusic,
          "暂停": pause,
          '播放':play
        }

        const onCheck = async() => {
            if (checked.value) {
                 await transferRecognizer.listen(result => {
                    const { scores } = result;
                    const labels = transferRecognizer.wordLabels();
                    const index = scores.indexOf(Math.max(...scores));
                    console.log('labels[index]',labels[index])
                    handle[labels[index]]()
                    console.log("labels",labels)
                    console.log('result', result)
                    // window.play(labels[index]);
                }, {
                    overlapFactor: 0,
                    probabilityThreshold: 0.75
                });
            } else {
                transferRecognizer.stopListening();
            }
        }

        onMounted( async() => {
            const recognizer = speechCommands.create(
                'BROWSER_FFT',
                null,
                MODEL_PATH + '/speech/model.json',
                MODEL_PATH + '/speech/metadata.json',
            );
            await recognizer.ensureModelLoaded();
            transferRecognizer = recognizer.createTransfer('智能KTV');
            const res = await fetch(MODEL_PATH + '/music/data.bin');
            const arrayBuffer = await res.arrayBuffer();
            transferRecognizer.loadExamples(arrayBuffer);
            await transferRecognizer.train({ epochs: 30 });
        })

        return {
            music,
            checked,
            videoRef,

            onCheck
        }
    }
}
</script>

最后

本期教程代码 github.com/betteralong… ,本文只是简单的尝试入门,在tensorflow.js官方网站有更多的案例和相关的API学习。

今天是2021的最后一天了,在最后一天更新了本年的最后一篇文章,期望2022能够坚持学习每月更文。