这几天看完了逻辑回归相关的课程,听着的时候感觉还算顺利,但是在进行课程练习的过程中还是花费了较长的时间,因为我画出的图形和实际出题题目后面的图形有点不太一样,所以来来回回不断地调整参数。后面才发现和学习速度α以及梯度下降次数有很大的关系。
模型实现
具体的模型推到就不说了,具体推到过程在视频和相关的学习资料中已经说的很清楚了,那么直接通过TypeScript进行实现吧
假设函数
分为了三个函数,为了清晰拆分了三个函数,分别为Hypothesis
,Sigmoid
,Polynomial
// 实现的是1/(1+e^(-z))
async Sigmoid(pow: tf.Tensor<tf.Rank>) {
const e = tf.fill([1, pow.shape[1] || 1], Math.E);
const ones = tf.ones([1, pow.shape[1] || 1]);
return ones.div(ones.add(e.pow(pow.mul(-1))));
}
// 实现的是θT*X
async Polynomial(feature: tf.Tensor<tf.Rank>, theta: tf.Tensor<tf.Rank>) {
const ones = tf.ones([feature.shape[0], 1]);
const data = tf.concat([ones, feature], 1);
const powForE = theta.transpose().matMul(data.transpose());
return powForE;
}
async Hypothesis(feature: tf.Tensor<tf.Rank>, theta: tf.Tensor<tf.Rank>) {
return await this.Sigmoid(await this.Polynomial(feature, theta));
}
代价函数
因为题目中不需要再代价函数中实现lambda
,所以这边就没写
async Cost(
feature: tf.Tensor<tf.Rank>,
theta: tf.Tensor<tf.Rank>,
y: tf.Tensor<tf.Rank>,
lambda: number = 0
) {
const yi = y.transpose();
const h = await this.Hypothesis(feature, theta);
const e = tf.fill([1, h.shape[1] || 1], Math.E);
const ones = tf.ones([1, h.shape[1] || 1]);
const a1 = yi.mul(-1).mul(h.log()); //-yi*log(h(xi))
const a2 = ones.sub(yi).mul(ones.sub(h).log()); //(1-yi)*log(1-h(xi))
let sumData = a1.sub(a2).sum();
return sumData.div(feature.shape[0]).dataSync();
}
梯度函数
这个方法实现的下降一次的方法,加入Lambda
对θ
进行正则化
async Gradient(
feature: tf.Tensor<tf.Rank>,
theta: tf.Tensor<tf.Rank>,
y: tf.Tensor<tf.Rank>,
alpha: number,
lambda: number = 0
) {
const h = await this.Hypothesis(feature, theta);
const ones = tf.ones([feature.shape[0], 1]);
const featureData = tf.concat([ones, feature], 1);
let sumData = h.transpose().sub(y).mul(featureData)
let descent = sumData.sum(0, true).transpose().div(feature.shape[0])
if (lambda > 0) {
const thetaProcess = theta.mul(tf.zeros([1, 1]).concat(tf.ones([theta.shape[0] - 1, 1])));
const data_Regularization = thetaProcess.mul(lambda / feature.shape[0])
descent = descent.add(data_Regularization);
}
const data = theta.sub(descent.mul(alpha));
return data;
}
梯度下降
那么梯度下降就可以对梯度函数进行循环即可
async GradientDescent(
feature: tf.Tensor<tf.Rank>,
alpha: number,
theta: tf.Tensor<tf.Rank>,
yi: tf.Tensor<tf.Rank>,
iterations: number,
lambda: number = 0
) {
let rTheta = theta;
for (let i = 0; i < iterations; i++) {
rTheta = await this.Gradient(feature, rTheta, yi, alpha, lambda);
}
return rTheta;
}
线性的逻辑回归
题目大搞意思就是你手上有历史的学生考分和是否能够顺利毕业之间的关系,需要你推导出模型来通过分数预测学生是否能够顺利毕业。
画点阵图
直接后台读出数据,返回给前端即可
async data1_showData(ctx: Context) {
const data = ReadData("./data/ex2data/ex2data1.txt");
ctx.body = data;
}
如何前端画线就不多说了,画出来的如下图所示
看到上面这个图可以知道,边界线可以用一条直线表示,那么模型可以是:
计算θ=[0,0]时候的损失值
直接调用代价函数就可以了,具体的θ
就传[0,0]
即可
async data1_costValue(ctx: Context) {
const _ex2Service = new ex2Service();
const data = ReadData("./data/ex2data/ex2data1.txt");
const theta = tf.zeros([3, 1]);
const featureData = tf.tensor(data);
const feature = featureData.slice([0, 0], [featureData.shape[0], (featureData.shape[1] || 1) - 1]);
const y = featureData.slice([0, (featureData.shape[1] || 1) - 1], [featureData.shape[0], 1]);
const cost = await _ex2Service.Cost(feature, theta, y);
ctx.body = cost;
}
最终可以看到计算得到的值为: 0.6931471824645996
画边界线
直接调用梯度下降,计算即可拿到下降后的θ
async data1_GradientDescent(ctx: Context) {
const _ex2Service = new ex2Service();
const data = ReadData("./data/ex2data/ex2data1.txt");
const thInit = tf.zeros([3, 1]);
const featureData = tf.tensor(data);
const feature = featureData.slice([0, 0], [featureData.shape[0], (featureData.shape[1] || 1) - 1]);
const y = featureData.slice([0, (featureData.shape[1] || 1) - 1], [featureData.shape[0], 1]);
let theta = await _ex2Service.GradientDescent(feature, 0.01, thInit, y, 200000);
ctx.body = theta.dataSync();
}
在前端根据返回的θ画出边界线即可
非线性的逻辑回归
画点阵图
直接后台读出数据,返回给前端即可
async data2_showData(ctx: Context) {
const data = ReadData("./data/ex2data/ex2data2.txt");
ctx.body = data;
}
最终画出来的图如下
我们看到图形可以判断是非线性的边界,而且根据题目的要求需要对自变量x1
和x2
进行转换才行
具体的转换方法如下
async MapFeature(feature: Array<Array<number>>) {
let data: Array<Array<number>> = [];
for (let i = 0; i < feature.length; i++) {
let e: Array<number> = [];
for (let pow = 1; pow <= 6; pow++) {
for (let j = 0; j <= pow; j++) {
e.push(Math.pow(feature[i][0], j) * Math.pow(feature[i][1], pow - j));
}
}
data.push(e);
}
return data;
}
画出lambda=1时候的边界线
代码如下,最终返回边界线的坐标。由于没有找到合适的等高线方法,因此这边调用了我自己写的方法。
async data2_GradientDescent_Lambda1(ctx: Context) {
const _ex2Service = new ex2Service();
const data = ReadData("./data/ex2data/ex2data2.txt");
const dataMap = await _ex2Service.MapFeature(data);
const thInit = tf.zeros([28, 1]);
const featureData = tf.tensor(data);
const feature = tf.tensor(dataMap);
const y = featureData.slice([0, (featureData.shape[1] || 1) - 1], [featureData.shape[0], 1]);
let theta = await _ex2Service.GradientDescent(feature, 0.01, thInit, y, 50000, 1);
var dataFor: Array<Array<number>> = [];
for (let x1 = -1.2; x1 <= 1.2; x1 += 0.01) {
for (let x2 = -1.2; x2 <= 1.2; x2 += 0.01) {
dataFor.push([x1, x2])
}
}
const dataForMap = await _ex2Service.MapFeature(dataFor);
const dataForH: any = (await _ex2Service.Polynomial(tf.tensor(dataForMap), theta)).dataSync();
const returnData = Contour(dataForH, -0.01, 0.01, -1.2, 1.2, -1.2, 1.2, 0.01);
ctx.body = returnData
}
前端实现方式就不贴代码了,具体的图如下所示
画出lambda=0时候的边界线
代码如下
和lambda=1
是一模一样的,只是参数不一样,另外具体的学习速度和梯度下降次数也慢慢试出来的
async data2_GradientDescent_Lambda0(ctx: Context) {
const _ex2Service = new ex2Service();
const data = ReadData("./data/ex2data/ex2data2.txt");
const dataMap = await _ex2Service.MapFeature(data);
const thInit = tf.zeros([28, 1]);
const featureData = tf.tensor(data);
const feature = tf.tensor(dataMap);
const y = featureData.slice([0, (featureData.shape[1] || 1) - 1], [featureData.shape[0], 1]);
let theta = await _ex2Service.GradientDescent(feature,10, thInit, y, 50000, 0);
//ctx.body = theta.dataSync();
var dataFor: Array<Array<number>> = [];
for (let x1 = -1.2; x1 <= 1.2; x1 += 0.01) {
for (let x2 = -1.2; x2 <= 1.2; x2 += 0.01) {
dataFor.push([x1, x2])
}
}
const dataForMap = await _ex2Service.MapFeature(dataFor);
const dataForH: any = (await _ex2Service.Polynomial(tf.tensor(dataForMap), theta)).dataSync();
const returnData = Contour(dataForH, -0.1, 0.1, -1.2, 1.2, -1.2, 1.2, 0.01);
ctx.body = returnData
}
具体的图如下所示
画出lambda=100时候的边界线
具体就不贴代码了,画出来的图如下所示
代码下载
将代码上传到了GitHub,想要的小伙伴直接下载吧 点击进入
标签:featureData,吴恩达,TypeScript,const,feature,shape,课程,tf,data From: https://blog.51cto.com/pptong/8330725