包含sigmoid和softmax模型,优化算法为批量梯度下降法
使用数据是吴恩达机器学习第二第三节的作业。
import numpy as np
import pandas as pd
from sklearn import preprocessing as pp
from scipy.io import loadmat
class LogisticRegression():
def sigmoid(self, X, theta):
'''
逻辑函数,用于二元分类,数据集均为np.array格式
:param X: 特征集m*(n+1),m为样本数,n为特征数
:param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
:return: 函数计算结果
'''
inner = X.dot(theta.T) # 计算内核
return 1 / (1 + np.exp(-inner))
def softmax(self, X, theta):
'''
softmax函数,用于多元分类,数据集均为np.array格式
:param X: 特征集m*(n+1),m为样本数,n为特征数
:param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
:return: 函数计算结果
'''
inner = X.dot(theta.T)
return np.exp(inner) / np.sum(np.exp(inner), axis=1, keepdims=True) # inner的格式为m*k,如此设置np.sum的参数,可使按行相加后的结果m*1
def calCost(self, X, y, theta, kenel='sigmoid', lamb=1):
'''
计算损失函数(对数损失),使用L2正则化
:param X: 特征集m*(n+1),m为样本数,n为特征数
:param y: 目标集m*k,k为类别数
:param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
:param kenel: 内核函数,sigmod或者softmax
:param lamb: 正则化参数,默认值为1
:return: 对数损失
'''
m = X.shape[0] # 样本数
if kenel == 'sigmoid':
inner = self.sigmoid(X, theta) # softmax和sigmod的损失函数格式上一致
else:
inner = self.softmax(X, theta)
first = np.multiply(-y, np.log(inner)) # 前半部分
second = np.multiply((1 - y), np.log(1 - inner)) # 后半部分
reg = lamb / (2 * m) * np.sum(np.power(theta[:, 1:], 2)) # 正则化
return np.sum(first - second) / m + reg
def training(self, X, y, kenel='sigmoid', learning_rate=1, lamb=0, steps=1000):
'''
使用批量梯度下降算法优化
:param X: 特征集m*n,m为样本数,n为特征数
:param y: 目标集m*k,k为类别数
:param kenel: 内核函数,sigmod或者softmax
:param learning_rate: 学习速率,默认值为1
:param lamb: 正则化参数,默认值为1
:param steps: 训练次数(梯度下降次数)
:return: 训练好的参数theta 和 每一步的损失值列表
'''
X = np.insert(X, 0, 1, axis=1) # 特征集增加一列x0,且令x0=1,以便于矩阵运算
m, n = X.shape
k = y.shape[1] # 目标类别数
theta = np.zeros((k, n)) # 此时n=特征数+1
cost = [] # 损失值
for _ in range(steps): # 梯度下降
if kenel == 'sigmoid': inner = self.sigmoid(X, theta)
else: inner = self.softmax(X, theta)
error = inner - y # 误差
grad = error.T.dot(X) / m + lamb / m * theta # 计算梯度
grad[:, 0] = np.sum(error, axis=0) / m # 上一步对所有theta都进行了正则化,这一步重新计算theta0的梯度,以取消正则化
theta = theta - learning_rate * grad # 更新theta
cost.append(self.calCost(X, y, theta, kenel=kenel, lamb=lamb)) # 添加当前损失值
return theta, cost
def predict(self, X, theta, kenel='sigmoid', threshold=0.5):
'''
根据输入特征集和参数theta,输出预测值
:param x: 待测样本1*n,n为特征数
:param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
:param kenel: 内核函数,sigmod或者softmax
:param threshold: 阀值,默认值为0.5,大于0.5输出正类别,反之负类别.仅当kenel=sigmod时使用
:return: 若kenel=sigmod,输出1或0(表示正类别或负类别);若干kenel=softmax,输出概率最大类别的索引,m*1
'''
X = np.insert(X, 0, 1, axis=1)
if kenel == 'sigmoid':
inner = self.sigmoid(X, theta)
return [1 if i[0] >= threshold else 0 for i in inner]
else:
inner = self.softmax(X, theta)
return np.argmax(inner, axis=1) # 概率最大类别的索引
def test_Sigmoid(): # 测试sigmoid功能
# 导入数据
data = pd.read_csv('data/ex2data1.txt')
X = np.array(data.iloc[:, : -1].values)
X = pp.scale(X) # 归一化
y = np.array(data.iloc[:, -1:].values)
lr = LogisticRegression()
theta, cost = lr.training(X, y)
# 计算准确度
predict = lr.predict(X, theta)
correct = [0 if a ^ b else 1 for a, b in zip(predict, y)]
accuracy = correct.count(1) / len(correct)
print('accuracy={}%'.format(accuracy * 100))
return lr
def test_Softmax(): # 测试softmax功能
# 导入数据
data = loadmat('data/ex3data1.mat')
X, y = data['X'], data['y'] # 获取特征集和目标集
k = np.unique(y).shape[0] # 获取类别数
data = np.concatenate((X, y), axis=1) # 合成数据集,洗牌用
np.random.shuffle(data) # 洗牌
X, y = data[:, : -1], data[:, -1:] # 分离
m = X.shape[0]
y_0 = y # 记录初始y,用于最后计算准确度
# 以下操作是把目标集进行one-hot编码
temp = np.zeros((m, k))
for i in range(m):
temp[i, int(y[i] - 1)] = 1
y = temp
lr = LogisticRegression()
theta, cost = lr.training(X, y, kenel='softmax')
predict = lr.predict(X, theta, kenel='softmax') # 获取最大预测索引
correct = [1 if a == b else 0 for a, b in zip(predict + 1, y_0)] # 本例中,索引值+1,正好等于原数据
accuracy = correct.count(1) / len(correct)
print('accuracy={}%'.format(accuracy * 100))
return lr