一、什么是逻辑回归
与线性回归不同,逻辑回归解决的是分类问题。比如,下图中有很多的点:
我们事先给一些已知点打上标签,标记出哪个点是黄色的点,哪个点是蓝色的点。然后我们再给出另一个点的坐标,逻辑回归会给出该点有多大概率是黄色的。即,逻辑回归输出的是概率。而前面学习的线性回归输出的则是一个线性增长的值。
那怎么得到这个概率呢?这就要用到前面提到过的激活函数。激活函数主要是处理非线性的变化,比如,之前提到的相亲的例子,有的人对于相亲对象是否有钱这件事情上的看法是,当对方有钱到一定程度后,钱再增加,好感度也不会有什么增加了。另外,因为输出的是一个概率,所以应该把输出的值压缩到[0,1]区间内。
操作步骤:
1)加载一个二分类数据集
2)定义模型结构:带有激活函数的单个神经元
3)训练模型并进行预测
二、加载二分类数据集
1)使用预先准备好的脚本来生成二分类数据集。
在真实的工作中,搞机器学习和深度学习,80%的时间都是在处理数据的。
下面我们来生成一个二分类数据集。对于怎么生成二分类数据集,不是本文的重点,因此,我们只是给出脚本。
logistic-regression/data.js
export function getData(numSamples) {
let points = [];
function getGauss(cx, cy, label) {
for(let i = 0; i < numSamples / 2; i++) {
let x = normalRandom(cx);
let y = normalRandom(cy);
points.push({ x, y, label });
}
}
getGauss(2, 2, 1);
getGauss(-2, -2, 0);
return points;
}
function normalRandom(mean = 0, variance = 1) {
let v1, v2, s;
do {
v1 = 2 * Math.random() - 1;
v2 = 2 * Math.random() - 1;
s = v1 * v1 + v2 * v2;
} while ( s > 1);
let result = Math.sqrt(-2 * Math.log(s) / s) * v1;
return mean + Math.sqrt(variance) * result;
}
logistic-regression/index.js
import * as tfvis from '@tensorflow/tfjs-vis';
import { getData } from './data.js';
window.onload = () => {
const data = getData(400); // 获取400个点
console.log(data);
}
经过parcel src/logistic*/*html打包,我们得到如下log输出:
其中,label表示的是该点是蓝色的概率。那么相反地,该点是黄色的概率就是1减去这个概率。
2)可视化二分类数据集
tfvis.render.scatterplot(
{ name: '逻辑回归训练数据' },
{
values: [
data.filter(p => p.label === 1),
data.filter(p => p.label === 0),
] // 这里的数据跟之前的有些不同,这是一个二维数组
}
);
效果如下:
三、定义模型结构
操作步骤:
1)初始化一个神经网络模型
2)为神经网络模型添加层
3)设计层的神经元个数、inputShape、激活函数
补充:
点积:
如果a向量为[a1, a2, ..., an],b向量为[b1, b2, ..., bn],那么二者的点积为 a1b1 + a2b2 + ... + anbn。
代码如下:
const model = tf.sequential();
model.add(tf.layers.dense({
units: 1, // 一个神经元
inputShape: [2], // 特征长度为2(有两个特征)的1维数组
activation: 'sigmoid' // 激活函数。之所以选择它的原因是它能保证输出结果在[0,1]之间
}))
之所以选择sigmoid作为激活函数,是因为它能保证输出结果在[0,1]之间。见下图所示:
四、训练模型并可视化训练过程
操作步骤:
1)将二分类训练数据转化为Tensor(将特征数量为2的数据转换为Tensor)
2)训练模型
3)使用tfvis可视化训练过程
import * as tfvis from '@tensorflow/tfjs-vis';
import * as tf from '@tensorflow/tfjs';
import { getData } from './data.js';
window.onload = async () => {
const data = getData(400); // 获取400个点
console.log(data);
tfvis.render.scatterplot(
{ name: '逻辑回归训练数据' },
{
values: [
data.filter(p => p.label === 1),
data.filter(p => p.label === 0),
] // 这里的数据跟之前的有些不同,这是一个二维数组
}
);
const model = tf.sequential();
model.add(tf.layers.dense({
units: 1, // 一个神经元
inputShape: [2], // 特征长度为2(有两个特征)的1维数组
activation: 'sigmoid' // 激活函数。之所以选择它的原因是它能保证输出结果在[0,1]之间
}));
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, // 因为整个训练数据是400个,这里设置了batchSize等于40,那就相当于10个batch就完成一轮训练,即10个batch构成一个epoch。
epochs: 50,
callbacks: tfvis.show.fitCallbacks(
{ name: '训练过程' },
['loss']
)
});
}
因为整个训练数据是400个,这里设置了batchSize等于40,那就相当于10个batch就完成一轮训练,即10个batch构成一个epoch。
效果如下:
五、进行预测
操作步骤:
1)编写前端界面输入待预测数据
之前都是在代码写死了预测数据。这样导致的问题是,每次想修改待预测数据就需要再训练一次。
<form action="" onsubmit="predict(this); return false;">
x: <input type="text" name="x">
y: <input type="text" name="y">
<button type="submit">预测</button>
</form>
之所以要return false是为了防止提交之后页面跳转。
2)使用训练好的模型进行预测
3)将输出的Tensor转为普通数据进行显示
代码如下:
src/logistic-regression/index.js
import * as tfvis from '@tensorflow/tfjs-vis';
import * as tf from '@tensorflow/tfjs';
import { getData } from './data.js';
window.onload = async () => {
const data = getData(400); // 获取400个点
console.log(data);
tfvis.render.scatterplot(
{ name: '逻辑回归训练数据' },
{
values: [
data.filter(p => p.label === 1),
data.filter(p => p.label === 0),
] // 这里的数据跟之前的有些不同,这是一个二维数组
}
);
const model = tf.sequential();
model.add(tf.layers.dense({
units: 1, // 一个神经元
inputShape: [2], // 特征长度为2(有两个特征)的1维数组
activation: 'sigmoid' // 激活函数。之所以选择它的原因是它能保证输出结果在[0,1]之间
}));
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, // 因为整个训练数据是400个,这里设置了batchSize等于40,那就相当于10个batch就完成一轮训练,即10个batch构成一个epoch。
epochs: 50,
callbacks: tfvis.show.fitCallbacks(
{ name: '训练过程' },
['loss']
)
});
window.predict = (form) => {
// 预测
const pred = model.predict(tf.tensor([[+form.x.value, +form.y.value]]));
alert(`预测结果:${pred.dataSync()[0]}`)
}
}
输入x:2,y:2,得到预测结果:0.9999983310699463。
输入x:-2,y:-2,得到预测结果:0.000006647646841884125