目录

引言

逻辑回归

Matlab代码

效果展示

 Python代码

效果展示

 C++代码

效果展示


引言

        本专栏第三个机器学习算法:逻辑回归算法,全部代码通过Github下载,使用Matlab,Python以及C++三种语言进行实现。其中Matlab的代码可以直接运行,Python与C++的代码需要分别安装Numpy以及Numcpp两个库才能运行。

逻辑回归

        以二分类为例,假设待分类样本的特征是二维的即可以用坐标(x,y)表示一个样本,逻辑回归的主要任务就是找到一条分类线,这条分类线我们又称之为决策边界,使得两个类别的样本分布在分类线的两侧如下图所示。当输入一个新的样本时只需要判断其在分类线的哪一侧即可知道它所属哪类。

那么如何得到这条分类线呢?以下为实现步骤(训练阶段):

1、随机初始化这条分类线,可以是 y=x , y=0 , x=0 , y=x+1等等,代码中为初始化权重参数w和b,分类线使用w*x+b表示

2、定义损失函数如下图所示

3、运行梯度下降不断更新w和b,直到达到要求(损失值低于某一设定值或是已经全部分类正确),梯度下降以及损失函数的相关计算公式如下(假设一共有m个样本,

java 计算 逻辑回归 逻辑回归代码实现_matlab

表示第i个样本,

java 计算 逻辑回归 逻辑回归代码实现_matlab_02

代表第i个样本的标签)

java 计算 逻辑回归 逻辑回归代码实现_matlab_03

之后的代码都将完成以下任务:

训练集坐标:

Class_1:(220,90)(240,95)(220,95)(180,95)(140,90)
Class_2:(180,90)(210,90)(140,90)(90,80)(78,80)

测试集坐标:
Test_Data :(180,90)(210,90)(140,90)(90,80)(78,80)

通过对训练集的训练,绘制一条决策边界(分类线)能够得到测试集的类别。

Matlab代码

这里的权重参数是随机初始化的所以每次运行效果都不同,但都能分类正确

clc;
close all;
clear all;
%% 定义训练样本
Class_1_o=  [220 90;240 95;220 95;180 95;140 90];
Class_2_o=  [80 85;85 80;85 85;82 80;78 80];
Test_Data_o = [180 90;210 90;140 90;90 80;78 80];
% 数据集归一化
Class_x = mapminmax([Class_1_o(:,1)',Class_2_o(:,1)',Test_Data_o(:,1)'], 0, 1); 
Class_y = mapminmax([Class_1_o(:,2)',Class_2_o(:,2)',Test_Data_o(:,2)'], 0, 1);
Class_1 = [Class_x(1:5)',Class_y(1:5)'];
Class_2 = [Class_x(6:10)',Class_y(6:10)'];
Test_Data = [Class_x(11:15)',Class_y(11:15)'];

[m_1,n_1] = size(Class_1);
[m_2,n_2] = size(Class_2);
%% 绘图程序
figure;
plot(Class_1(:,1),Class_1(:,2),'r*','LineWidth',2); hold on;
plot(Class_2(:,1),Class_2(:,2),'bo','LineWidth',2); hold on;
plot(Test_Data(:,1),Test_Data(:,2),'gs','LineWidth',2); hold on;
%% 定义训练参数
Train_Data = [Class_1;Class_2];
[m_3,n_3] = size(Train_Data);
Y = [1,1,1,1,1,0,0,0,0,0]; % 定义标签值
w = randn([2 1]);  % 随机初始化权重以及参数
b = randn(1);
a = 0.01;  % 学习率
epoch = 0;   % 定义迭代次数
%% 使用批处理方式计算权值修正量
while epoch<=100
    epoch = epoch + 1;
    for i=1:m_3
        fx = w'*Train_Data(i,:)' + b;  
        gz = 1/(1+exp(-fx));
        w(1) = w(1) - a*Train_Data(i,1)'*(gz - Y(i));
        w(2) = w(2) - a*Train_Data(i,2)'*(gz - Y(i));
        b = b- a*(gz-Y(i));
    end
    % 判断何时停止
    fx = w'*Train_Data' + b;
    gz = 1./(1+exp(-fx));
    Loss = (-Y*log(gz)' - (1-Y)*log(1-gz)')/m_3;
    if Loss <= 0.3
        break;
    end
end
%% 打印结果
f=[num2str(w(1)) '*x+(' num2str(w(2)) ')*y+(' num2str(b) ')'];
h = ezplot(f,[-0.5,1.5]);
grid on;
set(h,'Color','r');
legend('class1','class2','decisionboundary')

效果展示

java 计算 逻辑回归 逻辑回归代码实现_python_04

 Python代码

python需要用到numpy库,没安装的朋友运行不了哦,此处w初始化为[1,1]与b初始化为1,方便与C++结果进行对比

import matplotlib.pyplot as plt
import torch
import numpy as np


# 进行归一化
def normalization(Data1, Data2, Data3):
    # 要对全部的X坐标单独归一化,全部的Y坐标同理
    Data_x = np.concatenate((Data1[:, 0], Data2[:, 0], Data3[:, 0]))
    Data_y = np.concatenate((Data1[:, 1], Data2[:, 1], Data3[:, 1]))
    # 参照Matlab的mapminmax函数进行归一化
    Data_x = (Data_x - np.min(Data_x)) / (np.max(Data_x) - np.min(Data_x))
    Data_y = (Data_y - np.min(Data_y)) / (np.max(Data_y) - np.min(Data_y))
    Data_x = np.expand_dims(Data_x, 1)
    Data_y = np.expand_dims(Data_y, 1)

    Data1 = np.concatenate((Data_x[:5], Data_y[:5]), 1)
    Data2 = np.concatenate((Data_x[5:10], Data_y[5:10]), 1)
    Data3 = np.concatenate((Data_x[10:], Data_y[10:]), 1)
    return Data1, Data2, Data3


if __name__ == "__main__":
    # 定义数据集
    class_1_o = np.array([[220, 90], [240, 95], [220, 95], [180, 95], [140, 90]], dtype=np.float)
    class_2_o = np.array([[80, 85], [85, 80], [85, 85], [82, 80], [78, 80]], dtype=np.float)
    Test_Data_o = np.array([[180, 90], [210, 90], [140, 90], [90, 80], [78, 80]], dtype=np.float)
    Class_1, Class_2, Test_Data = normalization(class_1_o, class_2_o, Test_Data_o)
    # 开始绘图
    plt.figure()
    plt.plot(Class_1[:, 0], Class_1[:, 1], 'r*', label='Class_1')
    plt.plot(Class_2[:, 0], Class_2[:, 1], 'b*', label='Class_2')
    plt.plot(Test_Data[:, 0], Test_Data[:, 1], 'gs', label='Test_Data')
    plt.legend(loc='best')
    # 定义训练参数
    Train_Data = np.concatenate((Class_1, Class_2), axis=0)
    Y = np.array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0]], dtype=np.float)
    c = Train_Data.shape
    # w = np.random.randn(3, 1)
    w = np.array([[1], [1]], dtype=np.float)  # 使用[1;1;1]方便与C++的运行结果比对(因为C++无法使用matlpotlib绘图)
    b = np.array([1])
    a = 0.01
    epoch = 0
    # 开始训练
    while True:
        epoch = epoch + 1
        for i in range(0, c[0]):  # 遍历训练集
            fx = np.matmul(w.T, Train_Data[i, :].T) + b  # matmul为矩阵乘法(线代知识)
            gz = 1 / (1 + np.exp(-fx))
            # 开始梯度下降
            w[0] = w[0] - a * Train_Data[i, 0] * (gz[0] - Y[0, i])
            w[1] = w[1] - a * Train_Data[i, 1] * (gz[0] - Y[0, i])
            b = b - a * (gz[0] - Y[0, i])
        # 判断何时停止训练
        fx = np.matmul(w.T, Train_Data.T) + b
        gz = 1 / (1 + np.exp(-fx))
        Loss = (np.matmul(-Y, np.log(gz).T) - np.matmul((1-Y), np.log(1 - gz).T)) / c[0]
        if Loss <= 0.3:
            break
    print(w)
    # 绘制分类线
    xlist = np.linspace(-0.5, 1.5, 100)
    ylist = np.linspace(-0.5, 1.5, 100)
    x, y = np.meshgrid(xlist, ylist)  # 计算圆所在区域的网格
    f = w[0] * x + w[1] * y + b < 0
    plt.contourf(x, y, f, cmap="cool")
    plt.show()
    print('w的值为\n{}\n,b的值为\n{}'.format(w, b))

效果展示

java 计算 逻辑回归 逻辑回归代码实现_Data_05

java 计算 逻辑回归 逻辑回归代码实现_java 计算 逻辑回归_06

 C++代码

C++使用到了Numcpp库,没安装的朋友运行不了哦!

#include"NumCpp.hpp"
#include"boost/filesystem.hpp"   // 这一行貌似可以去掉
#include<iostream>
using namespace std;

tuple<nc::NdArray<float>, nc::NdArray<float>, nc::NdArray<float>> normalization(nc::NdArray<float> Data1, nc::NdArray<float> Data2, nc::NdArray<float> Data3);


void main()
{
	// 定义数据集
	nc::NdArray<float> class_1_o = { {220,90},{240,95},{220,95},{180,95},{140,90} };
	nc::NdArray<float> class_2_o = { {80,85},{85,80},{85,85},{82,80},{78,80} };
	nc::NdArray<float> Test_Data_o = { {180,90},{210,90},{140,90},{90,80},{78,80} };
	// 进行归一化Data是一个元组
	auto Data = normalization(class_1_o, class_2_o, Test_Data_o); 
	auto Class_1 = get<0>(Data);
	auto Class_2 = get<1>(Data);
	auto Test_Data = get<2>(Data);
	// 定义训练参数
	auto Train_Data = nc::vstack({ Class_1, Class_2 });
	auto m = Train_Data.shape();
	nc::NdArray<float> Y = { 1,1,1,1,1,0,0,0,0,0 };
	//auto w = nc::random::randN<float>({ 3,1 });
	nc::NdArray<float> w = {1,1};
	w = w.reshape(2, 1);
	nc::NdArray<float> b = { 1 };
	float a = 0.01;
	int epoch = 0;
	// 开始训练
	while (true)
	{
		epoch += 1;
		for (int i = 0; i < m.rows; i++)
		{
			// 这里与python不同numcpp进行向量相乘后得出的是一个1*1的矩阵,必须先用at()将数据提取出来后再进行运算
			auto fx = nc::matmul(w.transpose(), Train_Data(i, Train_Data.cSlice()).transpose()) + b;
			auto gz = 1 / (1 + nc::exp(-fx).at(0));
			// 开始梯度下降
			w[0] -= a * Train_Data(i, 0) * (gz - Y[i]);
			w[1] -= a * Train_Data(i, 1) * (gz - Y[i]);
			b -= a * (gz - Y[i]);
		}
		auto fx = nc::matmul(w.transpose(), Train_Data.transpose()) + b.at(0);
		auto gz = 1.0f / (1.0f + nc::exp(-fx));
		auto Loss = (nc::matmul(-Y, nc::log(gz).transpose()) - nc::matmul((1.0f - Y), nc::log(1.0f - gz).transpose())) / (float)m.rows;
		cout << Loss << endl;
		if (Loss.at(0) <= 0.3)
		{
			break;
		}
	}
	cout << "w的值为 \n" << w << endl;  // 输出训练结果
	cout << "b的值为 \n" << b << endl;
}


// 归一化函数
tuple<nc::NdArray<float>, nc::NdArray<float>, nc::NdArray<float>> normalization(nc::NdArray<float> Data1, nc::NdArray<float> Data2, nc::NdArray<float> Data3)
{
	//  AXis::Row相当于python的axis=0     Data1(Data1.rSlice(),0)相当于python的 Data1[:,0]
	auto Data_x = nc::concatenate({ Data1(Data1.rSlice(),0),Data2(Data2.rSlice(),0),Data3(Data3.rSlice(),0) }, nc::Axis::ROW);
	auto Data_y = nc::concatenate({ Data1(Data1.rSlice(),1),Data2(Data2.rSlice(),1),Data3(Data3.rSlice(),1) }, nc::Axis::ROW);

	// nc::min(Data_x) 返回1*1的矩阵需要用at(0)将数字提取出来再运算
	Data_x = (Data_x - nc::min(Data_x).at(0)) / (nc::max(Data_x).at(0) - nc::min(Data_x).at(0));
	Data_y = (Data_y - nc::min(Data_y).at(0)) / (nc::max(Data_y).at(0) - nc::min(Data_y).at(0));
	Data1 = nc::concatenate({ Data_x({0,5},0), Data_y({0,5},0) },nc::Axis::COL);
	Data2 = nc::concatenate({ Data_x({5,10},0), Data_y({5,10},0) }, nc::Axis::COL);
	Data3 = nc::concatenate({ Data_x({10,15},0), Data_y({10,15},0) }, nc::Axis::COL);	
	tuple<nc::NdArray<float>, nc::NdArray<float>, nc::NdArray<float>> Data(Data1,Data2,Data3);  // 定义一个元组作为返回值
	return Data;
}

效果展示

貌似也存在Matplotlib的C++版本,但是我没有安装。。。所以就直接打印训练结果了,可以看到训练结果与Python的一致,代表分类正确。

java 计算 逻辑回归 逻辑回归代码实现_java 计算 逻辑回归_07