注:完整代码示例在文章末尾
111 Softmax\mathrm{Softmax}Softmax 回归原理
1.11.11.1 原理
- Softmax\mathrm{Softmax}Softmax 回归是一种用于多分类问题的广义线性模型,它是二分类逻辑回归的扩展,通过归一化指数函数(Softmax\mathrm{Softmax}Softmax 函数)将模型的输出转化为概率分布。
1.21.21.2 模型结构
线性部分:对于输入特征 x∈Rd\mathrm{x}\in\mathbb{R}^dx∈Rd(ddd),模型计算每个类别 kkk 的得分:
zk=wkTx+bkz_k=\mathrm{w}^T_k\mathrm{x}+b_kzk=wkTx+bk
其中 wk\mathrm{w}_kwk 是类别 kkk 的权重向量,bkb_kbk 是偏置项,KKK 是类别总数;Softmax\mathrm{Softmax}Softmax 函数:将得分转化为概率:
P(y=k∣x)=ezk∑j=1KezjP(y=k\mid\mathrm{x})=\frac{e^{z_k}}{\sum^{K}_{j=1}e^{z_j}}P(y=k∣x)=∑j=1Kezjezk
此函数需确保所有概率 P(y=k)∈[0,1],∑k=1KP(y=k)=1P(y=k)\in[0,1],\sum^K_{k=1}P(y=k)=1P(y=k)∈[0,1],∑k=1KP(y=k)=1。
1.31.31.3 损失函数
- 对于 Softmax\mathrm{Softmax}Softmax 回归来说,使用的是交叉熵损失来衡量预测概率与真实标签的一个差异:
L(W,b)=−1N∑i=1N∑k=1K1{yi=k∣xi}\mathcal{L}(\mathrm{W,b})=-\frac{1}{N}\sum^N_{i=1}\sum^{K}_{k=1}1\{y_i=k\mid\mathrm{x}_i\}L(W,b)=−N1i=1∑Nk=1∑K1{yi=k∣xi}
其中 NNN 是样本数量,1{yi=k}1\{y_i=k\}1{yi=k} 是指示函数(若样本 iii 属于类别 kkk 则为 111,否则为 000)。
1.41.41.4 参数优化
- 通过梯度下降法(比如 SGD、Adam\mathrm{SGD、Adam}SGD、Adam)最小化损失函数,更新权重 W\mathrm{W}W 和偏置 bbb。
注:当 K=2K=2K=2 时,Softmax\mathrm{Softmax}Softmax 回归等价于二分类逻辑回归;很多时候 Softmax\mathrm{Softmax}Softmax 函数会用于分类神经网络的最后一层。
222 代码分解
2.12.12.1 导入所需要的库
- 在这里我们使用 pytorch\mathrm{pytorch}pytorch 配合机器学习的经典库 scikit−learn\mathrm{scikit-learn}scikit−learn 实现。
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
在这里面 torch.nn\mathrm{torch.nn}torch.nn 是用来继承定义神经网络模型的,这里我们用于定义一个一层的 Softmax\mathrm{Softmax}Softmax 网络层;torch.optim\mathrm{torch.optim}torch.optim 是用于定义梯度下降优化参数的;而 sklearn.datasets\mathrm{sklearn.datasets}sklearn.datasets 里面的 make_blobs\mathrm{make\_blobs}make_blobs 方法是用来自定义生成一些符合自己需求的数据集的;sklearn.model_selection\mathrm{sklearn.model\_selection}sklearn.model_selection 里面的 train_test_split\mathrm{train\_test\_split}train_test_split 是用得比较多的一个用于划分数据集(训练集、测试集)的方法;sklearn.metrics\mathrm{sklearn.metrics}sklearn.metrics 里面的 accuracy_score\mathrm{accuracy\_score}accuracy_score 是用于计算模型精确度的一个方法。感兴趣的可以去单个查阅一下这些方法里面还有那些参数或者这些库中还有哪些方法,其实都在机器学习这块用得比较多的。
2.22.22.2 生成并划分数据集(三分类)
# 生成三分类数据集
def generate_data(n_samples=300, n_classes=3, random_state=42):
x, y = make_blobs(
n_samples=n_samples, # 样本数
centers=n_classes, # 类别数
n_features=2, # 2D特征方便可视化
random_state=random_state # 随机种子方便可复现(随便设置多少没关系)
)
x = torch.tensor(x, dtype=torch.float32) # 张量化数据集
y = torch.tensor(y, dtype=torch.long) # 类别标签为整数索引
return x, y
# 生成并划分数据集
X, y = generate_data()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42 # 测试集占2成,训练集占8成
)
2.32.32.3 定义 Softmax\mathrm{Softmax}Softmax 回归模型并初始化
# 定义Softmax回归模型
class SoftmaxRegression(nn.Module):
def __init__(self, input_dim, output_dim): # 特征维度和分类类别数
super().__init__() # 使用PyTorch的nn.Module初始化方法的必须操作
self.linear = nn.Linear(input_dim, output_dim) # 无隐藏层的线性层
def forward(self, x):
return self.linear(x) # 输出未归一化的线性输出(softmax的原始分数)
# 初始化模型
input_dim = 2 # 输入特征维度
output_dim = 3 # 类别数
model = SoftmaxRegression(input_dim, output_dim)
2.42.42.4 定义损失函数和优化器
# 3. 定义损失函数和优化器
criterion = nn.CrossEntropyLoss() # 交叉熵损失,内部自动结合Softmax
optimizer = optim.SGD(model.parameters(), lr=0.1) # 随机梯度下降法,lr为学习率
2.52.52.5 训练函数定义及模型训练
# 4. 训练函数
def train(model, X_train, y_train, epochs=100):
losses = [] # 损失
for epoch in range(epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train) # 损失计算
# 反向传播
optimizer.zero_grad() # PyTorch 每轮默认会积累梯度,所以每轮需要手动梯度清零重新计算。
loss.backward() # 反向传播计算所有参数的梯度
optimizer.step() # 更新参数
losses.append(loss.item())
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
return losses
# 训练模型
losses = train(model, X_train, y_train, epochs=100)
2.62.62.6 评估函数定义及模型评估
# 5. 评估函数
def evaluate(model, X_test, y_test):
with torch.no_grad(): # 测试评估不用更新参数就禁用梯度
outputs = model(X_test)
_, predicted = torch.max(outputs, 1) # 取概率最大的类别
acc = accuracy_score(y_test, predicted)
print(f'Test Accuracy: {acc * 100:.2f}%')
return predicted
# 评估模型
predicted = evaluate(model, X_test, y_test)
2.72.72.7 可视化训练结果
# 6. 可视化结果
def plot_results(X, y, model, title):
# 创建网格点
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = torch.meshgrid(
torch.linspace(x_min, x_max, 100),
torch.linspace(y_min, y_max, 100)
)
grid = torch.cat((xx.reshape(-1, 1), yy.reshape(-1, 1)), dim=1)
# 预测网格点类别
with torch.no_grad():
outputs = model(grid)
_, predictions = torch.max(outputs, 1)
z = predictions.reshape(xx.shape)
# 绘制决策边界和数据点
plt.contourf(xx, yy, z, alpha=0.3, cmap='viridis')
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', edgecolors='k')
plt.title(title)
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend(*scatter.legend_elements(), title='Classes')
plt.show()
# 绘制训练损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失变化图')
plt.show()
# 绘制决策边界
plot_results(X_train, y_train, model, 'Softmax Regression 决策边界')
333 完整代码示例
"""softmax回归(多类逻辑回归)"""
"""主要是解决多分类问题,依旧是线性模型,f(x)=wx+b,将输入特征的线性组合映射为概率分布,输出每个类别的预测概率(选最大的);采用的交叉熵损失。"""
"""y = softmax(x), y_i = e^{f(x)_i} / sum(e^{f(x)_i})"""
"""pytorch实现"""
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成三分类数据集
def generate_data(n_samples=300, n_classes=3, random_state=42):
x, y = make_blobs(
n_samples=n_samples, # 样本数
centers=n_classes, # 类别数
n_features=2, # 2D特征方便可视化
random_state=random_state # 随机种子方便可复现(随便设置多少没关系)
)
x = torch.tensor(x, dtype=torch.float32) # 张量化数据集
y = torch.tensor(y, dtype=torch.long) # 类别标签为整数索引
return x, y
# 生成并划分数据集
X, y = generate_data()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42 # 测试集占2成,训练集占8成
)
# 定义Softmax回归模型
class SoftmaxRegression(nn.Module):
def __init__(self, input_dim, output_dim): # 特征维度和分类类别数
super().__init__() # 使用PyTorch的nn.Module初始化方法的必须操作
self.linear = nn.Linear(input_dim, output_dim) # 无隐藏层的线性层
def forward(self, x):
return self.linear(x) # 输出未归一化的线性输出(softmax的原始分数)
# 初始化模型
input_dim = 2 # 输入特征维度
output_dim = 3 # 类别数
model = SoftmaxRegression(input_dim, output_dim)
# 3. 定义损失函数和优化器
criterion = nn.CrossEntropyLoss() # 交叉熵损失,内部自动结合Softmax
optimizer = optim.SGD(model.parameters(), lr=0.1) # 随机梯度下降法,lr为学习率
# 4. 训练函数
def train(model, X_train, y_train, epochs=100):
losses = [] # 损失
for epoch in range(epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train) # 损失计算
# 反向传播
optimizer.zero_grad() # PyTorch 每轮默认会积累梯度,所以每轮需要手动梯度清零重新计算。
loss.backward() # 反向传播计算所有参数的梯度
optimizer.step() # 更新参数
losses.append(loss.item())
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}')
return losses
# 训练模型
losses = train(model, X_train, y_train, epochs=100)
# 5. 评估函数
def evaluate(model, X_test, y_test):
with torch.no_grad(): # 测试评估不用更新参数就禁用梯度
outputs = model(X_test)
_, predicted = torch.max(outputs, 1) # 取概率最大的类别
acc = accuracy_score(y_test, predicted)
print(f'Test Accuracy: {acc * 100:.2f}%')
return predicted
# 评估模型
predicted = evaluate(model, X_test, y_test)
# 6. 可视化结果
def plot_results(X, y, model, title):
# 创建网格点
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = torch.meshgrid(
torch.linspace(x_min, x_max, 100),
torch.linspace(y_min, y_max, 100)
)
grid = torch.cat((xx.reshape(-1, 1), yy.reshape(-1, 1)), dim=1)
# 预测网格点类别
with torch.no_grad():
outputs = model(grid)
_, predictions = torch.max(outputs, 1)
z = predictions.reshape(xx.shape)
# 绘制决策边界和数据点
plt.contourf(xx, yy, z, alpha=0.3, cmap='viridis')
scatter = plt.scatter(X[:, 0], X[:, 1], c=y, cmap='viridis', edgecolors='k')
plt.title(title)
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend(*scatter.legend_elements(), title='Classes')
plt.show()
# 绘制训练损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('训练损失变化图')
plt.show()
# 绘制决策边界
plot_results(X_train, y_train, model, 'Softmax Regression 决策边界')