目录
- 逻辑回归
- 前言
- 1. 鸢尾花分类问题
- 1.1 基于线性回归的思考
- 1.2 损失函数
- 2. 伯努利分布
- 3. 示例代码
- 3.1 数据可视化
- 3.2 日志信息打印
- 3.3 数据读取和处理
- 3.4 逻辑回归模型
- 3.5 完整示例代码
- 3.6 python实现
- 4. 思考
- 总结
逻辑回归
前言
手写AI推出的全新面向AI算法的C++课程 Algo C++,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要讲解逻辑回归
课程大纲可看下面的思维导图
1. 鸢尾花分类问题
我们先从一个实际问题出发
假设现在我们想要对两种鸢尾花进行分类,现在我们可以观察到鸢尾花的两个特征:花瓣个数和直径大小。那么我们该如何构建一个自动分类器,让算法可以根据这两个特征识别出当前的鸢尾花属于哪个类别呢?我们不妨把已经获得的数据画出来看一下,假设如下图所示,横坐标表示花瓣个数特征,纵坐标表示直径大小特征。
1.1 基于线性回归的思考
上节课我们讲解了通过线性回归解决房价预测问题,那么我们是否可以训练一个线性回归模型,通过判断输出值的大小来决定是什么类别?
考虑二分类问题,假设我们认为线性回归模型输出值大于等于0.5时为1,小于0.5时为0,因此我们希望存在一个函数
h
θ
(
x
)
h_{\theta}(x)
hθ(x) 使得对于线性回归模型的值,能够映射到0-1范围内,表示如下:
f
(
x
)
=
{
1
,
h
θ
(
x
)
⩾
0.5
0
,
h
θ
(
x
)
<
0.5
f(x)=\begin{cases}1,h_\theta(x)\geqslant0.5\\ 0,h_\theta(x)<0.5\end{cases}
f(x)={1,hθ(x)⩾0.50,hθ(x)<0.5
我们选择一个满足值域0-1特性的函数来作为映射函数:
g
(
z
)
=
1
1
+
e
−
z
g(z) = \frac{1}{1+e^{-z}}
g(z)=1+e−z1
函数(sigmoid)的图像如下:
函数的性质是,当 z z z 值为0时,函数值为0.5,当 z z z 趋于无穷大时,函数值趋于1,当 z z z 趋于无穷小时,函数值趋于0
我们回头看这个公式:
h
θ
(
x
)
=
1
1
+
e
−
(
k
x
+
b
)
h_\theta(x) =\frac{1}{1+e^{-(kx+b)}}
hθ(x)=1+e−(kx+b)1
注意
k
x
+
b
kx+b
kx+b 是特指一元逻辑回归,对于多元,常用
θ
T
x
\theta ^Tx
θTx 表示,由上式可得:
ln
h
θ
(
x
)
1
−
h
θ
(
x
)
=
k
x
+
b
\ln\dfrac{h_\theta(x)}{1-h_\theta(x)}=kx+b
ln1−hθ(x)hθ(x)=kx+b
模型预测的是正反例的对数概率,如果我们把
h
θ
(
x
)
h_{\theta}(x)
hθ(x) 视为样本
x
x
x 为正例的可能性,那么
1
−
h
θ
(
x
)
1-h_{\theta}(x)
1−hθ(x) 即为负例可能性,两者的比值的对数,称为对数几率,这也是为什么逻辑回归也称为对数几率回归。同样,反过来,我们通过对样本正反例可能性的伯努利分布推导(和指数族定义),也能得到上式的结果,因此,选择 sigmoid 的原因在于它符合描述样本 x 为正反例的可能性,而逻辑回归则是对该可能性建模,求解参数极大似然估计的过程
参考https://www.cnblogs.com/hustlx/p/5391772.html
参考https://blog.csdn.net/qq_31589695/article/details/79936938
1.2 损失函数
假设我们依旧按照线性回归的方式,代入下式:
L
(
θ
)
=
1
2
∑
i
=
1
m
(
y
(
i
)
−
h
θ
(
x
(
i
)
)
)
2
L(\theta)=\dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-h_\theta(x^{(i)}))^2
L(θ)=21i=1∑m(y(i)−hθ(x(i)))2
其中,
h
θ
(
x
)
=
1
1
+
e
−
(
k
x
+
b
)
h_\theta(x) =\frac{1}{1+e^{-(kx+b)}}
hθ(x)=1+e−(kx+b)1 , 其函数和导数图像如下:
这样的话,函数的导数值在多个区域接近0,使得在部分位置时难以迭代,不利于求解全局最优解,因此,直接采用线性回归的方式,可能在某些时候具备效果,但在理论上并不是合适的做法,也因此引出我们的主题,逻辑回归
2. 伯努利分布
对于上述分类问题中,y 取值 0,1 服从伯努利分布,则有:
y为1时的概率,表示为:
P
(
y
=
1
∣
x
;
θ
)
=
h
θ
(
x
)
P(y=1|x;\theta)=h_\theta(x)
P(y=1∣x;θ)=hθ(x)
y为0时的概率,表示为:
P
(
y
=
0
∣
x
;
θ
)
=
1
−
h
θ
(
x
)
P(y=0|x;\theta)=1 - h_\theta(x)
P(y=0∣x;θ)=1−hθ(x)
简化表示为:
P
(
y
∣
x
;
θ
)
=
h
θ
(
x
)
y
(
1
−
h
θ
(
x
)
)
1
−
y
,
y
=
0
,
1
P(y|x;\theta)=h_\theta(x)^y(1-h_\theta(x))^{1-y},y=0,1
P(y∣x;θ)=hθ(x)y(1−hθ(x))1−y,y=0,1
为了估计参数
θ
\theta
θ
我们有似然函数L:
L
(
θ
)
=
P
(
y
⃗
∣
x
;
θ
)
=
∏
i
=
1
m
P
(
y
(
i
)
∣
x
(
i
)
;
θ
)
L(\theta)=P(\vec{y}|x;\theta)=\prod_{i=1}^{m}P(y^{(i)}|x^{(i)};\theta)
L(θ)=P(y∣x;θ)=i=1∏mP(y(i)∣x(i);θ)
代入后有:
L
(
θ
)
=
∏
i
=
1
m
h
θ
(
x
(
i
)
)
y
(
i
)
(
1
−
h
θ
(
x
(
i
)
)
)
1
−
y
(
i
)
L(\theta)=\prod_{i=1}^{m}h_{\theta}(x^{(i)})^{y^{(i)}}(1-h_{\theta}(x^{(i)}))^{1-y^{(i)}}
L(θ)=i=1∏mhθ(x(i))y(i)(1−hθ(x(i)))1−y(i)
我们要计算参数
θ
\theta
θ 的极大似然估计,找到参数
θ
\theta
θ 使得似然性函数
L
(
θ
)
L(\theta)
L(θ) 的值最大化,即是为找到一组参数
θ
\theta
θ 使得:如果 y 为正类,模型预测 y 为1的概率最大,如果 y 为负类,模型预测 y 为0的概率最大
对于似然性最大化,可以使用似然性的对数最大化要比似然性最大化简单许多,我们有:
L
(
θ
)
=
log
L
(
θ
)
L(\theta) = \log L(\theta)
L(θ)=logL(θ)
展开后得:
L
(
θ
)
=
∑
i
=
1
m
y
(
i
)
log
h
θ
(
x
(
i
)
)
+
(
1
−
y
(
i
)
)
log
(
1
−
h
θ
(
x
(
i
)
)
)
L(\theta)=\sum\limits_{i=1}^m y^{(i)}\log h_\theta(x^{(i)})+(1-y^{(i)})\log(1-h_\theta(x^{(i)}))
L(θ)=i=1∑my(i)loghθ(x(i))+(1−y(i))log(1−hθ(x(i)))
上述式子也叫交叉熵(注意这里没有加负号),我们希望得到的是最大化值的参数估计,相当于是求 L 函数的最大值,对于求最大值,相比线性回归,我们唯一区别是这里使用了梯度上升法,仅仅是减改为加
θ
+
=
θ
+
η
⋅
∂
L
(
θ
)
∂
θ
\theta^{+}=\theta+\eta\cdot\frac{\partial L(\theta)}{\partial\theta}
θ+=θ+η⋅∂θ∂L(θ)
求导得
∂
L
(
θ
)
∂
θ
j
=
(
y
−
h
θ
(
x
)
)
x
j
\dfrac{\partial L(\theta)}{\partial\theta_j} = (y-h_\theta(x)) x_j
∂θj∂L(θ)=(y−hθ(x))xj
我们得到每一个参数得更新公式如下:
θ
j
+
=
θ
j
+
α
(
y
(
i
)
−
h
θ
(
x
(
i
)
)
)
x
j
(
i
)
\theta_j^+=\theta_j+\alpha(y^{(i)}-h_\theta(x^{(i)}))x_j^{(i)}
θj+=θj+α(y(i)−hθ(x(i)))xj(i)
其中
j
j
j 是下标,相当于是
x
x
x 的第
j
j
j 个参数
将其与之前的 MSE 图像对比如下:
3. 示例代码
本次以居民幸福感和住房面积及离地铁距离为例进行一个二分类问题的讨论
3.1 数据可视化
我们通过下面的代码将数据进行可视化
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv("workspace/shanghai.csv")
happiness = data.query("happiness == 1")
unhappiness = data.query("happiness == 0")
plt.plot(happiness.area, happiness.distance, "b*", label="happiness")
plt.plot(unhappiness.area, unhappiness.distance, "r*", label="unhappiness")
plt.legend()
plt.xlim([0, 500])
plt.ylim([0, 6000])
plt.xlabel("House Area")
plt.ylabel("Distance to subway")
plt.title("Are you living in happiness?")
plt.savefig("figure.jpg")
3.2 日志信息打印
日志信息打印的构建代码如下:
namespace logger{
#define INFO(...) Application::logger::__printf(__FILE__, __LINE__, __VA_ARGS__)
void __printf(const char* file, int line, const char* fmt, ...){
va_list vl;
va_start(vl, fmt);
// None = 0, // 无颜色配置
// Black = 30, // 黑色
// Red = 31, // 红色
// Green = 32, // 绿色
// Yellow = 33, // 黄色
// Blue = 34, // 蓝色
// Rosein = 35, // 品红
// Cyan = 36, // 青色
// White = 37 // 白色
/* 格式是: \e[颜色号m文字\e[0m */
printf("\e[32m[%s:%d]:\e[0m ", file, line);
vprintf(fmt, vl);
printf("\n");
}
};
上述示例代码通过宏定义实现了一个简单的日志输出功能,可以方便地输出包含文件名和行号的日志信息,方便程序调试和定位问题。
3.3 数据读取和处理
数据读取和处理代码如下:
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <tuple>
#include <iomanip>
#include <stdarg.h>
namespace io{
/* 通过csv文件加载数据 */
vector<Item> load_data(const string& file){
vector<Item> output;
fstream ifile(file, ios::binary | ios::in);
string line;
getline(ifile, line);
while(getline(ifile, line)){
int p0 = line.find(",");
int p1 = line.find(",", p0 + 1);
Item item;
item.area = atof(line.substr(0, p0).c_str());
item.distance = atof(line.substr(p0+1, p1).c_str());
item.label = atof(line.substr(p1+1).c_str());
// cout << item.area << ", " << item.distance << ", " << item.label << endl;
output.emplace_back(item);
}
return output;
}
};
namespace statistics{
/* 计算数据的均值和标准差 */
tuple<Item, Item> compute_mean_std(const vector<Item>& items){
Item mean{0, 0}, stdval{0, 0};
for(auto& item : items){
mean.area += item.area;
mean.distance += item.distance;
}
mean.area /= items.size();
mean.distance /= items.size();
for(auto& item : items){
stdval.area += std::pow(item.area - mean.area, 2.0f);
stdval.distance += std::pow(item.distance - mean.distance, 2.0f);;
}
stdval.area = std::sqrt(stdval.area / items.size());
stdval.distance = std::sqrt(stdval.distance / items.size());
return make_tuple(mean, stdval);
}
};
上述示例代码中,在 io 命名空间下的 load_data 函数中,通过读取 csv 文件,将数据加载到一个 vector<Item> 类型的数组中,每个 Item 结构体包含住房面积、离地铁站距离两个特征以及是否幸福标签。然后使用 statistics 命名空间下的 compute_mean_std 函数计算数据的均值和标准差,对数据进行归一化处理,使其均值为0,标准差为1。
3.4 逻辑回归模型
逻辑回归模型的构建代码如下:
int run(){
auto datas = io::load_data("shanghai.csv");
Item mean, stdval;
tie(mean, stdval) = statistics::compute_mean_std(datas);
/* 对数据进行减去均值除以标准差,使得均值为0,标准差为1 */
for(auto& item : datas){
item.area = (item.area - mean.area) / stdval.area;
item.distance = (item.distance - mean.distance) / stdval.distance;
}
float k_area = 0.1;
float k_dist = 0.1;
float b = 0;
float lr = 0.1;
for(int iter = 0; iter < 1000; ++iter){
float loss = 0;
float delta_k_area = 0;
float delta_k_dist = 0;
float delta_b = 0;
for(auto& item : datas){
float predict = k_area * item.area + k_dist * item.distance + b;
double logistic = nn::sigmoid(predict);
float L = -(std::log(logistic) * item.label + std::log(1 - logistic) * (1 - item.label));
delta_k_area += (logistic - item.label) * item.area;
delta_k_dist += (logistic - item.label) * item.distance;
delta_b += (logistic - item.label);
loss += L;
}
if(iter % 100 == 0)
cout << "Iter " << iter << ", Loss: " << setprecision(3) << loss << endl;
k_area = k_area - lr * delta_k_area;
k_dist = k_dist - lr * delta_k_dist;
b = b - lr * delta_b;
}
INFO("模型参数: k_area = %f, k_dist = %f, b = %f", k_area, k_dist, b);
INFO("数据集: area_mean = %f, dist_mean = %f, area_std = %f, dist_std = %f",
mean.area, mean.distance, stdval.area, stdval.distance
);
INFO(""
"k_area = %f\n"
"k_distance = %f\n"
"b = %f\n"
"area_mean = %f\n"
"area_std = %f\n"
"distance_mean = %f\n"
"distance_std = %f"
, k_area, k_dist, b, mean.area, stdval.area, mean.distance, stdval.distance);
float area = 100;
float distance = 2000;
float norm_area = (area - mean.area) / stdval.area;
float norm_dist = (distance - mean.distance) / stdval.distance;
float predict = k_area * norm_area + k_dist * norm_dist + b;
float logistic = nn::sigmoid(predict);
INFO("在上海,对于房屋面积 %.0f 平方米,距离地铁 %.0f 米的住户而言,他们觉得生活是 【%s】 的.",
area, distance, logistic > 0.5 ? "幸福" : "并不幸福"
);
return 0;
}
在上述示例代码中,我们采用逻辑回归模型来做幸福感二分类问题,其中 k_area、k_dist分别是权重系数,b 是偏置项。对每个数据项计算预测值 predict 和损失函数 L。然后分别计算权重系数和偏置项的梯度 delta_k_area、delta_k_dist、delata_b,用于更新权重和偏置项
3.5 完整示例代码
完整示例代码如下:
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <tuple>
#include <iomanip>
#include <stdarg.h>
using namespace std;
namespace Application{
struct Item{
float area;
float distance;
float label;
};
namespace logger{
#define INFO(...) Application::logger::__printf(__FILE__, __LINE__, __VA_ARGS__)
void __printf(const char* file, int line, const char* fmt, ...){
va_list vl;
va_start(vl, fmt);
// None = 0, // 无颜色配置
// Black = 30, // 黑色
// Red = 31, // 红色
// Green = 32, // 绿色
// Yellow = 33, // 黄色
// Blue = 34, // 蓝色
// Rosein = 35, // 品红
// Cyan = 36, // 青色
// White = 37 // 白色
/* 格式是: \e[颜色号m文字\e[0m */
printf("\e[32m[%s:%d]:\e[0m ", file, line);
vprintf(fmt, vl);
printf("\n");
}
};
namespace io{
/* 通过csv文件加载数据 */
vector<Item> load_data(const string& file){
vector<Item> output;
fstream ifile(file, ios::binary | ios::in);
string line;
getline(ifile, line);
while(getline(ifile, line)){
int p0 = line.find(",");
int p1 = line.find(",", p0 + 1);
Item item;
item.area = atof(line.substr(0, p0).c_str());
item.distance = atof(line.substr(p0+1, p1).c_str());
item.label = atof(line.substr(p1+1).c_str());
// cout << item.area << ", " << item.distance << ", " << item.label << endl;
output.emplace_back(item);
}
return output;
}
};
namespace statistics{
/* 计算数据的均值和标准差 */
tuple<Item, Item> compute_mean_std(const vector<Item>& items){
Item mean{0, 0}, stdval{0, 0};
for(auto& item : items){
mean.area += item.area;
mean.distance += item.distance;
}
mean.area /= items.size();
mean.distance /= items.size();
for(auto& item : items){
stdval.area += std::pow(item.area - mean.area, 2.0f);
stdval.distance += std::pow(item.distance - mean.distance, 2.0f);;
}
stdval.area = std::sqrt(stdval.area / items.size());
stdval.distance = std::sqrt(stdval.distance / items.size());
return make_tuple(mean, stdval);
}
};
namespace nn{
double sigmoid(double x){
return 1 / (1 + std::exp(-x));
}
};
int run(){
auto datas = io::load_data("shanghai.csv");
Item mean, stdval;
tie(mean, stdval) = statistics::compute_mean_std(datas);
/* 对数据进行减去均值除以标准差,使得均值为0,标准差为1 */
for(auto& item : datas){
item.area = (item.area - mean.area) / stdval.area;
item.distance = (item.distance - mean.distance) / stdval.distance;
}
float k_area = 0.1;
float k_dist = 0.1;
float b = 0;
float lr = 0.1;
for(int iter = 0; iter < 1000; ++iter){
float loss = 0;
float delta_k_area = 0;
float delta_k_dist = 0;
float delta_b = 0;
for(auto& item : datas){
float predict = k_area * item.area + k_dist * item.distance + b;
double logistic = nn::sigmoid(predict);
float L = -(std::log(logistic) * item.label + std::log(1 - logistic) * (1 - item.label));
delta_k_area += (logistic - item.label) * item.area;
delta_k_dist += (logistic - item.label) * item.distance;
delta_b += (logistic - item.label);
loss += L;
}
if(iter % 100 == 0)
cout << "Iter " << iter << ", Loss: " << setprecision(3) << loss << endl;
k_area = k_area - lr * delta_k_area;
k_dist = k_dist - lr * delta_k_dist;
b = b - lr * delta_b;
}
INFO("模型参数: k_area = %f, k_dist = %f, b = %f", k_area, k_dist, b);
INFO("数据集: area_mean = %f, dist_mean = %f, area_std = %f, dist_std = %f",
mean.area, mean.distance, stdval.area, stdval.distance
);
INFO(""
"k_area = %f\n"
"k_distance = %f\n"
"b = %f\n"
"area_mean = %f\n"
"area_std = %f\n"
"distance_mean = %f\n"
"distance_std = %f"
, k_area, k_dist, b, mean.area, stdval.area, mean.distance, stdval.distance);
float area = 100;
float distance = 2000;
float norm_area = (area - mean.area) / stdval.area;
float norm_dist = (distance - mean.distance) / stdval.distance;
float predict = k_area * norm_area + k_dist * norm_dist + b;
float logistic = nn::sigmoid(predict);
INFO("在上海,对于房屋面积 %.0f 平方米,距离地铁 %.0f 米的住户而言,他们觉得生活是 【%s】 的.",
area, distance, logistic > 0.5 ? "幸福" : "并不幸福"
);
return 0;
}
};
int main(){
return Application::run();
}
运行结果如下图所示:
3.6 python实现
根据 C++ 的示例代码分析,Python 版本的相对容易,完整的示例代码如下:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv("workspace/shanghai.csv").to_numpy()
area_mean, distance_mean, _ = np.mean(data, axis=0)
area_std, distance_std, _ = np.std(data, axis=0)
area, distance, label = data.T
area = (area - area_mean) / area_std
distance = (distance - distance_mean) / distance_std
def sigmoid(x):
return 1 / (1 + np.exp(-x))
############################# 训练流程 ####################################
k_area = 0.1
k_distance = 0.1
b = 0
lr = 0.1
for i in range(1000):
predict = area * k_area + distance * k_distance + b
logistic = sigmoid(predict)
loss = -(label * np.log(logistic) + (1 - label) * np.log(1 - logistic)).sum()
if i % 100 == 0:
print(f"Iter: {i}, Loss: {loss:.3f}")
dk_area = ((logistic - label) * area).sum()
dk_distance = ((logistic - label) * distance).sum()
db = (logistic - label).sum()
k_area = k_area - dk_area * lr
k_distance = k_distance - dk_distance * lr
b = b - db * lr
print(f"k_area = {k_area}\nk_distance = {k_distance}\nb = {b}")
print(f"area_mean = {area_mean:.6f}\narea_std = {area_std:.6f}\ndistance_mean = {distance_mean:.6f}\ndistance_std = {distance_std:.6f}")
运行效果如下:
我们可以将逻辑回归模型训练后的分类线进行可视化,代码如下:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_csv("workspace/shanghai.csv")
happiness = data.query("happiness == 1")
unhappiness = data.query("happiness == 0")
k_area = -10.555772
k_distance = -11.365233
b = 0.702190
area_mean = 225.294113
area_std = 133.112854
distance_mean = 2252.941162
distance_std = 1816.952026
# 分类线是
# k_area * x + k_distance * y + b = 0
# y = -(k_area * x + b) / k_distance
x = np.array([0, 500])
norm_x = (x - area_mean) / area_std
norm_y = -(k_area * norm_x + b) / k_distance
y = norm_y * distance_std + distance_mean
plt.plot(happiness.area, happiness.distance, "b*", label="happiness")
plt.plot(unhappiness.area, unhappiness.distance, "r*", label="unhappiness")
plt.plot(x, y, "g-")
plt.legend()
plt.xlim([0, 500])
plt.ylim([0, 6000])
plt.xlabel("House Area")
plt.ylabel("Distance to subway")
plt.title("Are you living in happiness?")
plt.savefig("figure.jpg")
分类线如下图所示:
4. 思考
逻辑回归求解二分类问题时,为什么最大化似然函数?(from chatGPT)
在二分类问题中,我们假设一个样本的标签
y
∈
0
,
1
y \in {0,1}
y∈0,1 是由一个未知参数
θ
\theta
θ 和一个概率分布生成的,这个概率分布就是二项伯努利分布,其概率质量函数为:
P
(
y
∣
θ
)
=
θ
y
(
1
−
θ
)
1
−
y
P(y|\theta) = \theta^y(1-\theta)^{1-y}
P(y∣θ)=θy(1−θ)1−y
其中,
θ
\theta
θ 是一个在
(
0
,
1
)
(0,1)
(0,1) 区间内的参数(在之前的分析中我们使用的是 sigmoid 函数),它表示
y
=
1
y=1
y=1 的概率,
1
−
θ
1-\theta
1−θ 表示
y
=
0
y=0
y=0 的概率。
现在给定
m
m
m 个样本
(
x
(
1
)
,
y
(
1
)
)
,
(
x
(
2
)
,
y
(
2
)
)
,
…
,
(
x
(
m
)
,
y
(
m
)
)
(x^{(1)}, y^{(1)}), (x^{(2)}, y^{(2)}), \dots, (x^{(m)}, y^{(m)})
(x(1),y(1)),(x(2),y(2)),…,(x(m),y(m)),它们独立同分布地满足上述二项伯努利分布,我们的目标是学习到参数
θ
\theta
θ,使得似然函数
L
(
θ
)
\mathcal{L}(\theta)
L(θ) 最大(为什么呢?🤔)。
L
(
θ
)
=
P
(
y
(
1
)
,
y
(
2
)
,
…
,
y
(
m
)
∣
x
(
2
)
,
x
(
2
)
,
…
,
x
(
m
)
;
θ
)
=
∏
i
=
1
m
θ
y
(
i
)
(
1
−
θ
)
1
−
y
(
i
)
.
\mathcal{L}(\theta)=P(y^{(1)},y^{(2)},\dots,y^{(m)}|x^{(2)},x^{(2)},\dots,x^{(m)};\theta)=\prod_{i=1}^{m}\theta^{y^{(i)}}(1-\theta)^{1-y^{(i)}}.
L(θ)=P(y(1),y(2),…,y(m)∣x(2),x(2),…,x(m);θ)=i=1∏mθy(i)(1−θ)1−y(i).
这样做的原因是如果我们最大化了似然函数,那么就相当于最大化了训练集的可能性,也就是说,我们找到了一组能够最好地描述训练集的参数
θ
\theta
θ。再最大化似然函数地过程中,我们可以使用梯度上升等优化算法来求解最优参数。(还是不怎么好理解😂)
可以这样理解:假设我们有一个数据集,其中包含了一些样本和对应的标签,我们希望使用逻辑回归模型对这些数据进行拟合,从而能够对新的样本进行分类预测。而逻辑回归模型是由参数 θ \theta θ 来决定的,因此我们需要通过学习来确定最优的参数 θ \theta θ
最大化似然函数实际上是在寻找最优的参数 θ \theta θ,使得模型对于训练集中已有的标签的预测结果尽可能接近真实的标签值。而似然函数 L ( θ ) \mathcal{L}(\theta) L(θ) 表示了在给定参数 θ \theta θ 的情况下,观察到当前数据的概率。我们通过最大化似然函数,就可以找到最优的参数 θ \theta θ,使得模型的预测结果更加接近真实标签,从而提高模型的准确率。
在二分类问题中,我们通常认为标签值
y
y
y 是服从伯努利分布的。假设样本
i
i
i 的真实标签为
y
i
y_i
yi,模型预测的标签为
y
^
i
\hat{y}_i
y^i,则似然函数为:
L
(
θ
)
=
∏
i
=
1
m
(
y
^
i
)
y
i
(
1
−
y
^
i
)
1
−
y
i
\mathcal{L}(\theta)=\prod_{i=1}^{m}\left(\hat{y}_i\right)^{y_i}\left(1-\hat{y}_i\right)^{1-y_i}
L(θ)=i=1∏m(y^i)yi(1−y^i)1−yi
我们的目标就是找到最优的参数
θ
\theta
θ,使得似然函数
L
(
θ
)
\mathcal{L}(\theta)
L(θ) 最大,即:
θ
^
=
arg
max
θ
∏
i
=
1
m
(
y
^
i
)
y
i
(
1
−
y
^
i
)
1
−
y
i
\hat{\theta}=\arg\max\limits_{\theta}\prod\limits_{i=1}^m(\hat{y}_i)^{y_i}\left(1-\hat{y}_i\right)^{1-y_i}
θ^=argθmaxi=1∏m(y^i)yi(1−y^i)1−yi
对上式取对数并取负号,就可以得到逻辑回归的损失函数(交叉熵):
J
(
θ
)
=
−
1
m
∑
i
=
1
m
[
y
i
log
y
^
i
+
(
1
−
y
i
)
log
(
1
−
y
^
i
)
]
J(\theta)=-\dfrac{1}{m}\sum_{i=1}^{m}\left[y_i\log\hat{y}_i+(1-y_i)\log\left(1-\hat{y}_i\right)\right]
J(θ)=−m1i=1∑m[yilogy^i+(1−yi)log(1−y^i)]
我们通过最小化损失函数
J
(
θ
)
J(\theta)
J(θ),也就是最小化预测值和真实值之间的差距,来找到最优的参数
θ
\theta
θ,从而提高模型的准确率。
下面是关于似然函数和最大似然估计法的定义(from book)
设 X 1 , X 2 , ⋅ ⋅ ⋅ , X n X_1,X_2, \cdot\cdot\cdot,X_n X1,X2,⋅⋅⋅,Xn 是来自总体 X X X 的样本, x 1 , x 2 , ⋅ ⋅ ⋅ , x n x_1,x_2, \cdot\cdot\cdot,x_n x1,x2,⋅⋅⋅,xn 是样本值, θ \theta θ 是待估参数。
似然函数
定义:对于离散型总体
X
X
X,设其概率分布为
P
{
X
=
a
i
}
=
p
(
a
i
;
θ
)
,
i
=
1
,
2
,
⋯
,
P\left\{X=a_{i}\right\}=p\left(a_{i};\theta\right),i=1,2,\cdots,
P{X=ai}=p(ai;θ),i=1,2,⋯, 称函数
L
(
θ
)
=
L
(
X
1
,
X
2
,
⋯
,
X
n
;
θ
)
=
∏
i
=
1
n
p
(
X
i
;
θ
)
L\left(\theta\right)=L\left(X_{1},X_{2},\cdots,X_{n};\theta\right)=\prod_{i=1}^{n}p\left(X_{i};\theta\right)
L(θ)=L(X1,X2,⋯,Xn;θ)=i=1∏np(Xi;θ)
为参数
θ
\theta
θ 的似然函数
对于连续型总体
X
X
X,概率密度为
f
(
x
;
θ
)
f(x;\theta)
f(x;θ),则称函数
L
(
θ
)
=
L
(
X
1
,
X
2
,
⋯
,
X
n
;
θ
)
=
∏
i
=
1
n
f
(
X
i
;
θ
)
L\left(\theta\right)=L\left(X_{1},X_{2},\cdots,X_{n};\theta\right)=\prod_{i=1}^{n}f\left(X_{i};\theta\right)
L(θ)=L(X1,X2,⋯,Xn;θ)=i=1∏nf(Xi;θ)
为参数
θ
\theta
θ 的似然函数。
最大似然估计法
定义:对于给定的样本值 x 1 , x 2 , ⋅ ⋅ ⋅ , x n x_1,x_2, \cdot\cdot\cdot,x_n x1,x2,⋅⋅⋅,xn,使似然函数 L ( x 1 , x 2 , ⋅ ⋅ ⋅ , x n ; θ ) L(x_1,x_2, \cdot\cdot\cdot,x_n;\theta) L(x1,x2,⋅⋅⋅,xn;θ) 达到最大值的参数值 θ ^ = θ ^ ( x 1 , x 2 , ⋯ , x n ) \widehat{\theta}=\widehat{\theta}\left(x_{1},x_{2},\cdots,x_{n}\right) θ =θ (x1,x2,⋯,xn) 称为未知参数 θ \theta θ 的最大似然估计值,相应地使似然函数 L ( X 1 , X 2 , ⋅ ⋅ ⋅ , X n ; θ ) L(X_1,X_2, \cdot\cdot\cdot,X_n;\theta) L(X1,X2,⋅⋅⋅,Xn;θ) 达到最大值地参数值 θ ^ = θ ^ ( X 1 , X 2 , ⋯ , X n ) \widehat{\theta}=\widehat{\theta}\left(X_{1},X_{2},\cdots,X_{n}\right) θ =θ (X1,X2,⋯,Xn) 称为 θ \theta θ 的最大似然估计量,一般统称为 θ \theta θ 的最大似然估计。称这种估计法为最大似然估计法
线性回归和逻辑回归的区别
线性回归和逻辑回归虽然都是机器学习中的回归算法,但是它们解决的问题有很大的区别。
线性回归旨在建立一个线性方程来预测一个连续型变量的值,例如根据一个人的年龄、性别、收入等特征,来预测他们的房屋价格。线性回归模型通常用最小二乘法来拟合模型参数,并利用拟合好的模型来进行预测。其输出的结果是一个实数值,代表连续型变量的预测值。
逻辑回归则是用于分类问题的,它预测的是一个离散的标签或类别,如图像分类问题中的猫或狗。逻辑回归采用 sigmoid 函数将线性方程的结果映射到 (0,1) 之间,用来表示一个概率,其输出的结果是一个概率值,代表某个样本属于某个类别的概率。逻辑回归通常采用极大似然估计来拟合模型参数,并利用拟合好的模型来进行预测。
二分类使用 sigmoid 函数将分类器的输出转化为概率分布,那多分类呢?(from chatGPT)
通常在多分类任务中,使用 Softmax 函数将
C
C
C 个分类器的输出转化为概率分布。具体来说,对于样本
i
i
i,假设
z
i
,
j
z_{i,j}
zi,j 表示样本
i
i
i 属于第
j
j
j 类的得分(例如经过线性变换后的输出),那么该样本属于第
j
j
j 类的概率可以通过 Softmax 函数计算:
p
i
,
j
=
e
z
i
,
j
∑
k
=
1
C
e
z
i
,
k
p_{i,j}=\dfrac{e^{z_i,j}}{\sum_{k=1}^C e^{z_i,k}}
pi,j=∑k=1Cezi,kezi,j
在求解损失函数时,我们可以将 Softmax 函数和交叉熵损失函数结合起来,得到 Softmax Cross Entropy Loss。该损失函数的具体表达式为:
L
(
θ
)
=
−
1
n
∑
i
=
1
n
∑
j
=
1
C
1
{
n
=
j
}
log
e
z
i
j
∑
k
=
1
C
e
2
t
i
k
L(\theta)=-\dfrac{1}{n}\sum\limits_{i=1}^n\sum\limits_{j=1}^C1_{\{n=j\}}\log\dfrac{e^{z_ij}}{\sum_{k=1}^C e^{2tik}}
L(θ)=−n1i=1∑nj=1∑C1{n=j}log∑k=1Ce2tikezij
该式可以看作是对交叉熵损失函数和 Softmax 函数的组合,用于在多分类问题中优化模型参数
θ
\theta
θ。
总结
本次课程主要学习了逻辑回归,通过二分类问题案例了解回归模型,并通过代码进行了相关实现。同时我们对逻辑回归展开了一些讨论,比如为什么最大化极大似然估计以及逻辑回归与前面提到过的线性回归的区别,期待下次课程😄