【机器学习】LR(逻辑分类&softmax分类)—— python3 实现方案

包含sigmoid和softmax模型,优化算法为批量梯度下降法

使用数据是吴恩达机器学习第二第三节的作业。

import numpy as np
from sklearn import preprocessing
from sklearn import datasets


class LogisticRegression:
    def __init__(self, learning_rate=0.1, lamb=0.001, iters=1000, kernel='sigmoid'):
        self.learning_rate = learning_rate  # 学习速率,默认值为1
        self.lamb = lamb  # 正则化参数,默认值为1
        self.iters = iters  # 最大迭代次数
        self.kernel = kernel  # 内核函数,sigmoid或者softmax
        self.theta = np.zeros((1, 1))  # 声明参数是二维array格式
        self.cost = []  # 记录损失值

    @staticmethod
    def sigmoid(features, theta):
        """
        逻辑函数,用于二元分类,数据集均为np.array格式
        :param features: 特征集m*(n+1),m为样本数,n为特征数
        :param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
        :return: 函数计算结果
        """
        inner = np.dot(features, theta.T)  # 计算内核
        return 1 / (1 + np.exp(-inner))

    @staticmethod
    def softmax(features, theta):
        """
        softmax函数,用于多元分类,数据集均为np.array格式
        :param features: 特征集m*(n+1),m为样本数,n为特征数
        :param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
        :return: 函数计算结果
        """
        inner = features.dot(theta.T)
        return np.exp(inner) / np.sum(np.exp(inner), axis=1, keepdims=True)  # inner的格式为m*k,如此设置np.sum的参数,可使按行相加后的结果m*1

    def cal_cost(self, features, target, theta, lamb):
        """
        计算损失函数(对数损失),使用L2正则化
        :param features: 特征集m*(n+1),m为样本数,n为特征数
        :param target: 目标集m*k,k为类别数
        :param theta: 参数集k*(n+1),k为标签的类别数,n为特征数
        :param lamb: 正则化参数,默认值为1
        :return: 对数损失
        """
        m = features.shape[0]  # 样本数
        if self.kernel == 'sigmoid':
            inner = self.sigmoid(features, theta)  # softmax和sigmoid的损失函数格式上一致
        else:
            inner = self.softmax(features, theta)
        first = np.multiply(-target, np.log(inner))  # 前半部分
        second = np.multiply((1 - target), 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, features, target):
        """
        使用批量梯度下降算法优化
        :param features: 特征集m*n,m为样本数,n为特征数
        :param target: 目标集m*k,k为类别数
        :return: 更新参数和损失值,无返回
        """
        features = np.insert(features, 0, 1, axis=1)  # 特征集增加一列x0,且令x0=1,以便于矩阵运算
        m, n = features.shape
        k = target.shape[1]  # 目标类别数
        self.theta = np.zeros((k, n))  # 此时n=特征数+1
        for _ in range(self.iters):  # 梯度下降
            if self.kernel == 'sigmoid':
                inner = self.sigmoid(features, self.theta)
            else:
                inner = self.softmax(features, self.theta)

            error = inner - target  # 误差
            grad = error.T.dot(features) / m + self.lamb / m * self.theta  # 计算梯度
            grad[:, 0] = np.sum(error, axis=0) / m  # 上一步对所有theta都进行了正则化,这一步重新计算theta0的梯度,以取消正则化
            self.theta -= self.learning_rate * grad  # 更新theta
            self.cost.append(self.cal_cost(features, target, self.theta, self.lamb))  # 添加当前损失值
        return

    def predict(self, features, threshold=0.5):
        """
        根据输入特征集和参数theta,输出预测值
        :param features: 待测样本1*n,n为特征数
        :param threshold: 阀值,默认值为0.5,大于0.5输出正类别,反之负类别.仅当kernel=sigmoid时使用
        :return: 若kernel=sigmoid,输出1或0(表示正类别或负类别);若干kernel=softmax,输出概率最大类别的索引,m*1
        """
        features = np.insert(features, 0, 1, axis=1)
        if self.kernel == 'sigmoid':
            inner = self.sigmoid(features, self.theta)
            return [1 if i[0] >= threshold else 0 for i in inner]
        else:
            inner = self.softmax(features, self.theta)
            return np.argmax(inner, axis=1)  # 概率最大类别的索引


def test_sigmoid():  # 使用sklearn生成的双类别数据测试sigmoid
    features, target = datasets.make_classification(n_samples=300)
    target = target.reshape(target.shape[0], 1)

    lr = LogisticRegression()
    lr.training(features, target)
    predict = lr.predict(features)  # 获取最大预测索引
    correct = [0 if a ^ b else 1 for a, b in zip(predict, target)]
    accuracy = correct.count(1) / len(correct)  # 计算准确度
    print('accuracy={}%'.format(accuracy * 100))


def test_softmax():
    """使用鸢尾花进行测试的时候,可以做到93%的预测准确率
    使用sklearn生成的多类别数据测试softmax,当类别增多时,准确率迅速下降,原因可能是生成的数据不存在良好的线性关系
    """
    # features, target = datasets.make_classification(n_samples=5000, n_informative=4, n_classes=5)
    dataset = datasets.load_iris()  # 鸢尾花数据集
    features, target = dataset['data'], dataset['target']
    target = target.reshape(-1, 1)
    enc = preprocessing.OneHotEncoder()
    target_train = enc.fit_transform(target).toarray()  # 对目标集独热编码

    lr = LogisticRegression(learning_rate=0.001, lamb=0, iters=5000, kernel='softmax')
    lr.training(features, target_train)
    predict = lr.predict(features)
    correct = [1 if a == b else 0 for a, b in zip(predict, target)]  # 本例中,索引值正好等于原数据
    accuracy = correct.count(1) / len(correct)
    print('accuracy={}%'.format(accuracy * 100))


test_sigmoid()
test_softmax()

 

你可能感兴趣的:(机器学习)