【机器学习】DNN(三层多元分类模型)—— python3 实现方案

根据西瓜书《神经网络》章节反向传播的推导过程写成。

书上推导了单样本的反向传播,本例实现了批量梯度下降,设置batch_size=1,就是单样本随机梯度下降。

有别于其他分类算法的标签集,神经网络要对标签进行独热编码,在制作测试数据时要注意这点。

本例使用sklearn生成的3000个3类别的训练数据,多次调试后,能达到90%的正确率。

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


class NeutalNetwork:
    def __init__(self, q=10, learning_rate=0.1, iters=10000, batch_size=10):
        self.q = q  # 隐藏层神经元个数
        self.learning_rate = learning_rate  # 学习率
        self.iters = iters  # 迭代次数
        self.batch_size = batch_size  # 小批量包含的样本数,即每次迭代的样本数
        self.para = []  # 存储参数组合
        self.cost = []  # 储存损失值

    @staticmethod
    def sigmoid(x):
        """激活函数sigmoid"""
        return 1 / (1 + np.exp(-x))

    def rand_parameter(self, features, target):
        """
        初始化参数,输入层到隐藏层的权值v,隐藏层的阀值gamma,隐藏层到输出层的权值w,输出层的阀值theta
        :param features: 已增加 x0=1 列的特征集m*n,m为样本数,n表示特征数(实际特征是n-1)np.matrix
        :param target: 已经过独热处理的标签集m*k,k表示类别数
        :return: 取值在(0,1)的随机参数值
        """
        n, k = features.shape[1], target.shape[1]
        v = np.mat(np.random.rand(n, self.q))  # n*q
        gamma = np.mat(np.random.rand(1, self.q))  # 1*q
        w = np.mat(np.random.rand(self.q, k))  # q * k
        theta = np.mat(np.random.rand(1, k))  # 1 * k
        return v, gamma, w, theta

    @staticmethod
    def cal_g(y_pre, y_true):
        """
        计算theta的一阶导数,并令其等于g,用于计算别的参数的导数
        :param y_pre: 预测结果m*k,m=self.batch_size
        :param y_true: 真实结果m*k
        :return: 参数theta的一阶导数m*k
        """
        return np.multiply(-y_pre, np.multiply(y_pre - y_true, 1 - y_pre))

    @staticmethod
    def cal_e(g, w, b):
        """
        计算gamma的一阶导数,并令其等于e,用于计算别的参数的导数
        :param g: theta的一阶导数m*k,m=self.batch_size
        :param w: 隐藏层到输出层的权值去q*k
        :param b: 隐藏层的输出m*q
        :return: 参数gamma的一阶导数m*q
        """
        return np.multiply(np.multiply(1 - b, b), np.sum(np.dot(g, w.T), axis=1))

    @staticmethod
    def cal_cost(y_pre, y_true):
        """
        计算损失函数值
        :param y_pre: 预测结果m*k
        :param y_true: 真实结果m*k
        :return: 损失值
        """
        return np.sum(np.power(y_pre - y_true, 2)) / 2

    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=1列,作为偏差的特征
        features, target = np.mat(features), np.mat(target)  # 矩阵运算更方便. 若用数组运算,会出现二维数组相乘之后变成一维的情况
        m, n, k = features.shape[0], features.shape[1], target.shape[1]
        v, gamma, w, theta = self.rand_parameter(features, target)  # 初始化参数

        for i in range(self.iters):
            rand_batch = np.random.randint(0, m, self.batch_size)  # 从所有样本中随机选择batch_size个样本,作为此次迭代样本
            feature_train, target_train = features[rand_batch, :], target[rand_batch, :]

            alpha = np.dot(feature_train, v)  # 计算隐藏层的输入
            b = self.sigmoid(alpha - gamma)  # 计算隐藏层的输出
            beta = np.dot(b, w)  # 计算输出层的输入
            target_pre = self.sigmoid(beta - theta)  # 计算输出层的输出,即预测为各类别的概率
            error = self.cal_cost(target_pre, target_train) / self.batch_size  # 计算均方误差
            self.cost.append(error)  # 记录误差

            g = self.cal_g(target_pre, target_train)  # 推导过程中的辅助公式
            e = self.cal_e(g, w, b)  # 推导过程中的辅助公式

            # 计算各参数的平均梯度(一阶导数)
            grad_theta = np.sum(g, axis=0) / self.batch_size
            grad_w = -np.dot(b.T, g) / self.batch_size
            grad_gamma = np.sum(e, axis=0) / self.batch_size
            grad_v = -np.dot(feature_train.T, e) / self.batch_size

            # 更新各参数
            theta -= self.learning_rate * grad_theta
            w -= self.learning_rate * grad_w
            gamma -= self.learning_rate * grad_gamma
            v -= self.learning_rate * grad_v
        self.para.extend([v, gamma, w, theta])  # 存储组合变量
        return

    def predict(self, features):
        """
        预测输入样本的类别
        :param features: 待测样本
        :return: 独热编码格式的类别
        """
        features = np.mat(np.insert(features, 0, 1, axis=1))
        v, gamma, w, theta = self.para  # 获取参数
        # 前向传播计算输出
        alpha = np.dot(features, v)
        b = self.sigmoid(alpha - gamma)
        beta = np.dot(b, w)
        target_pre = self.sigmoid(beta - theta)

        target_pre = np.array(target_pre)  # 转换成np.array
        argmax = np.argmax(target_pre, axis=1)  # 获取最大概率值的索引
        # 把最大概率值变成1,其他值变成0
        target_pre[:, :] = 0
        for i in range(features.shape[0]):
            target_pre[i, argmax[i]] = 1
        return target_pre


def test():
    features, target = datasets.make_classification(n_samples=5000, n_informative=5, n_classes=5)  # 3000个具有3个类别的训练样本
    target = target.reshape(features.shape[0], 1)
    enc = preprocessing.OneHotEncoder()
    target = enc.fit_transform(target).toarray()  # 对y进行独热编码

    nn = NeutalNetwork(q=5, learning_rate=0.1, iters=10000, batch_size=10)
    nn.training(features, target)
    prediction = nn.predict(features)
    correct = [1 if (a == b).all() else 0 for a, b in zip(prediction, target)]
    print(correct.count(1) / len(correct))
    print(nn.cost[::500])


test()

 

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