一、逻辑回归实现二分类
-
sigmod函数
g(z) = 1 / (1 + e-z) -
替换推导
令z = WTX则g(z) = 因为g(z)函数的特性,它输出的结果也不再是预测结果,而是一个值预测为正例的概率,预测为负例的概率就是1-g(z)。
函数形式表达:P(y=0|w,x) = 1 – g(z) P(y=1|w,x) = g(z)
sigmod函数预测结果为一个0到1之间的小数,选定阈值的第一反应,大多都是选0.5,其实实际工作中并不一定是0.5,阈值的设定往往是根据实际情况来判断的。本小节我们只举例让大家理解为什么不完全是0.5,并不会有一个万能的答案,都是根据实际工作情况来定的。 -
最大似然估计求损失函数
若想让预测出的结果全部正确的概率最大,根据最大似然估计,就是所有样本预测正确的概率相乘得到的P(总体正确)最大,此时我们让数学表达形式如下:
通过两边同时取log的形式让其变成连加.
得到的这个函数越大,证明我们得到的W就越好.因为在函数最优化的时候习惯让一个函数越小越好,所以我们在前边加一个负号.得到公式如下:
这个函数就是我们逻辑回归(logistics regression)的损失函数,我们叫它交叉熵损失函数. -
求解交叉熵损失函数
求解步骤如下:1. 随机一组W. 2. 将W带入交叉熵损失函数,让得到的点沿着负梯度的方向移动. 3. 循环第二步.
求解梯度部分同样是对损失函数求偏导,过程如下:
交叉熵损失函数的梯度和最小二乘的梯度形式上完全相同,区别在于,此时的
,而最小二乘的 -
代码实现:
from sklearn.preprocessing import LabelEncoder, OneHotEncoder from sklearn.metrics import confusion_matrix, accuracy_score from matplotlib.colors import ListedColormap import matplotlib.pyplot as plt import pandas as pd import numpy as np import random def dataset_format(filename): """ 划分训练测试集 :param filename:文件路径 :return: """ dataset = pd.read_csv(filename) # M = dataset["Gender"] == "Male" # dataset["Gender"] = M train_data = dataset.sample(frac=0.75, random_state=100, axis=0) test_data = dataset[~dataset.index.isin(train_data.index)] X_train = train_data.iloc[:, 2:-1].values y_train = train_data.iloc[:, -1].values X_test = test_data.iloc[:, 2:-1].values y_test = test_data.iloc[:, -1].values return X_train, y_train, X_test, y_test def initialize_weights(X): """ 初始化权重 :param X: :return: """ # 初始化参数 # 参数范围[-1/sqrt(N), 1/sqrt(N)] n_features = X.shape[1] limit = np.sqrt(1 / n_features) w = np.random.uniform(-limit, limit, (n_features, 1)) w = np.insert(w, 0, 0, axis=0) # 加入偏值 return w def sigmoid(x): """ sigmoid函数 :param x: :return: """ return 1 / (1 + np.exp(-x)) def stander(X): """ 标准化数据,特征缩放 :param X: :return: """ X = np.mat(X, dtype=float) # regularize x_mean = np.mean(X, 0) x_std = np.std(X, 0) np.seterr(divide="ignore", invalid='ignore') # xVar中存在0元素 # 特征标准化: (特征-均值)/方差 X = (X - x_mean) / x_std return X def fit(X, y, learning_rate=0.001, n_iterations=500): """ 梯度下降训练数据 :param X: 训练集自变量 :param y: 训练集因变量 :param learning_rate: 学习率 :param n_iterations: 迭代次数 :return: 最优权重 """ W = initialize_weights(X) # 为X增加一列特征x1,x1 = 0 X = np.insert(X, 0, 1, axis=1) y = np.reshape(y, (len(X), 1)) # 梯度训练n_iterations轮 for i in range(n_iterations): h_x = X.dot(W) h_x = np.mat(h_x, dtype=float) y_pred = sigmoid(h_x) w_grad = X.T.dot(y_pred - y) W = W - learning_rate * w_grad return W def predict(X, w): """ 预测测试数据 :param X: 测试集自变量 :param w: 最优权重 :return: """ X = np.insert(X, 0, 1, axis=1) h_x = X.dot(w) h_x = np.mat(h_x, dtype=float) y_pred = np.round(sigmoid(h_x)) return y_pred.astype(int) X_train, y_train, X_test, y_test = dataset_format("Social_Network_Ads.csv") X_train = stander(X_train) X_test = stander(X_test) W = fit(X_train, y_train) y_test_hat = predict(X_test, W) y_train_hat = predict(X_train, W) cm = confusion_matrix(y_test, y_test_hat) accuracy = accuracy_score(y_test, y_test_hat) X_set, y_set = X_test, y_test X1, X2 = np.meshgrid(np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01), np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01)) plt.contourf(X1, X2, predict(np.array([X1.ravel(), X2.ravel()]).T, W).reshape(X1.shape), alpha=0.75, cmap=ListedColormap(('red', 'green'))) plt.xlim(X1.min(), X1.max()) plt.ylim(X2.min(), X2.max()) for i, j in enumerate(np.unique(y_set)): plt.scatter(X_set[y_set == j, 0].tolist(), X_set[y_set == j, 1].tolist(), c=ListedColormap(('orange', 'blue'))(i), label=j) plt.title('Logistic Regression (Test set)') plt.xlabel('Age') plt.ylabel('Estimated Salary') plt.legend() plt.show()
分类结果:
二、多分类实现(softmax)
-
softmax的定义
假设有一个数组V,Vi表示V中的第i个元素,那么这个元素的softmax值为:
-
交叉熵(softmax的损失函数):
交叉熵,其用来衡量在给定的真实分布下,使用非真实分布所指定的策略消除系统的不确定性所需要付出的努力的大小。使用交叉熵做分类问题中的损失函数,可以在一定程度上减少梯度消散。
softmax中使用的交叉熵公式如下:
在softmax中,ti表示真实值,yi表示求出的softmax值,输入一个样本,那么只有一个神经元对应了该样本的正确类别;若这个神经元输出的概率值越高,则按照以上的函数公式,其产生的损失就越小;反之,则产生的损失就越高。
举个简单的计算例子:
-
对损失函数进行求导
当预测第i个时,可以认为ti=1。此时损失函数变成了
对Loss求导。根据定义
将数值映射到了0-1之间,并且和为1,则有
具体求导过程
通过上面的求导结果,可以发现结果恰好为通过softmax函数求出了概率减1,那么我们就可以得到反向更新的梯度了。
举个计算的例子:通过若干层的计算,最后得到的某个训练样本的向量的分数是[ 2, 3, 4 ], 那么经过softmax函数作用后概率分别就是 [0.0903, 0.2447, 0.665], 如果这个样本正确的分类是第二个的话, 那么计算出来的偏导就是[0.0903, 0.2447-1, 0.665]=[0.0903, -0.7553, 0.665](划重点), 然后再根据这个进行back propagation就可以了。
-
代码实现
用到的公式:
def dataset_split(filename): """ 划分数据集 :param filename: :return: """ dataset = pd.read_csv(filename) # M = dataset["Gender"] == "Male" # dataset["Gender"] = M train_data = dataset.sample(frac=0.75, random_state=100, axis=0) test_data = dataset[~dataset.index.isin(train_data.index)] X_train = train_data.iloc[:, :-1].values y_train = train_data.iloc[:, -1].values X_test = test_data.iloc[:, :-1].values y_test = test_data.iloc[:, -1].values return X_train, y_train, X_test, y_test def label_encoder(y): """ 对标签进行编码 :param y: :return: """ label_encoder_y = LabelEncoder() y = label_encoder_y.fit_transform(y) return np.mat(y).T def cal_e(x, w, i): """ 公式三: e^(θi.xi) :param x: 随机的某行样本特征, 需转化成 特征个数行 x 1列 :param w: 分类个数行k x 特征个数列 :param i: 第i个类别 :return: 生成 1 x 1 矩阵 """ theata_l = w[i] # 1行 x 特征个数列 return np.exp(theata_l.dot(x.T)) def cal_probability(x, w, k, i): """ 公式二: P(yi = j|xi;θ) = e^(θi.xi) / ∑(j=1~k) e^(θj.xi) :param x: 随机的某行样本特征 :param w: 权值向量 :param k: k个分类 :param i: 第i个分类 :return: 某个特征的比率 """ molecule = cal_e(x, w, i) # 1行 x 1列 denominator = sum([cal_e(x, w, j) for j in range(k)]) return molecule / denominator def cal_partial_derivative(x, y, w, k, i, weight_lambda): """ 公式一: △θjJ(θ) = -xi * (1{yi = j} - P(yi = j|xi;θ)) + λθ :param x: 随机的某行样本特征 :param y: 随机的某行样本标签 :param w: 分类个数行k x 特征个数列 :param k: k个分类 :param i: 第i个分类 :param weight_lambda: 衰退权重 :return: △θjJ(θ) """ first = (y[0, 0] == i) # 计算示性函数 second = cal_probability(x, w, k, i)[0, 0] return -x * (first - second) + weight_lambda * w[i] def softmax_train(X, y, max_iteration=5000, weight_lambda=0.01, learning_step = 0.1): """ :param X: 特征 :param y: 标签 :param k: 几种类型标签 :return: """ X = np.insert(X, 0, 1, axis=1) k = y.max() + 1 w = np.zeros((k, X.shape[1])) random_max = y.shape[0] - 1 w = np.mat(w) for time in range(max_iteration): index = random.randint(0, random_max) l_x = X[index] l_y = y[index] derivatives = [cal_partial_derivative(l_x, l_y, w, k, j, weight_lambda) for j in range(k)] for c in range(k): w[c] -= learning_step * derivatives[c] return w def softmax_predict(X, w): X = np.insert(X, 0, 1, axis=1) r, c = X.shape y_t = np.zeros((r, 1)) for i in range(r): x_T = X[i] result = w.dot(x_T.T) positon = result.argmax() y_t[i] = positon return y_t X_train, y_train, X_test, y_test = dataset_split("iris.csv") X_train = stander(X_train) X_test = stander(X_test) y_train = label_encoder(y_train) y_test = label_encoder(y_test) w = softmax_train(X_train, y_train) y_test_hat = softmax_predict(X_test, w) cm = confusion_matrix(y_test, y_test_hat) accuracy = accuracy_score(y_test, y_test_hat) print("混淆矩阵\n", cm) print("准确度: ", accuracy)
输出结果:
混淆矩阵 [[ 8 0 0] [ 0 19 0] [ 0 1 9]] 准确度: 0.972972972972973