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

包含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

猜你喜欢

转载自blog.csdn.net/zhenghaitian/article/details/83653931