机器学习 基于Adult数据集的逻辑回归与朴素贝叶斯分类

一:逻辑回归分类的原理

逻辑回归和线性回归最大的区别在于线性回归的输出一般是连续的,而逻辑回归的输出一般是离散的,但是输入可以是连续的。逻辑回归也使用了线性回归的函数,即h(θ)=θ.T*X,但是线性回归的输出值的范围是负无穷到正无穷的,我们要把输出值压缩到0-1这个范围,因此引入了sigmoid函数
在这里插入图片描述

当z趋于负无穷时,g(z)趋于0,当z趋于正无穷时,g(z)趋于1,我们线性回归的输出当做逻辑回归的输入即可将输出压缩在0-1之间,即z=θ.T*X,因此可以构造出预测函数
在这里插入图片描述

至此我们就可以用线性回归的知识来完成剩下的工作,同样也是构造出损失函数然后令损失函数取得极小值。令H代表上式,H的维度是(n+1)*1,Y维度也为(n+1)*1,损失函数可表示为
Cost = -Y log(H) - (1 - Y)log(1-H)
对其求θ的偏导,得到
d(Cost)/d(θ) = X.T(H - Y)
因此可以得到θ的更新规则:
θ := θ - α(X.T(H - Y))
不断迭代直至损失函数收敛即可得到θ。

二:朴素贝叶斯分类的原理

贝叶斯公式为P(Y∣X)=P(X∣Y)P(Y)/P(X),其原理是应用所观察到的现象对有关概率分布的主观判断(即先验概率)进行修正的标准方法。朴素贝叶斯是贝叶斯分类算法中的一种,与贝叶斯的不同之处在于朴素贝叶斯进行了独立性假设,假设各个特征之间相互独立不相关。应用到分类中,可以定义
P(类别|特征)=(P(类别)P(特征|类别))/P(特征)
也即由当前已知特征求得该样本属于什么类别的概率,最终结果是概率最大值的类别。由于分母是不变的,所以只需要比较分子即可,P(类别)可以由该类别在所有训练样本中所占的比例求得,称为先验概率;然后求条件概率P(特征|类别),由于假设X的n个维度之间相互独立,Ck表示类别,可以得到
在这里插入图片描述

当所有特征是连续型变量时,可以假设所有特征均符合正态分布,通过样本计算出均值和方差,也就是得到正态分布的密度函数,有了密度函数,就可以把值代入,算出某一点的密度函数的值。

三:程序清单

(一)梯度下降参数求解:

import pandas as pd
import numpy as np
from itertools import chain
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

data = pd.read_csv('./adult.csv',header=None)
data.columns=['age', 'workclass', 'fnlwgt', 'education', 'education-num','marital-status','occupation','relationship','race','sex','capital-gain','capital-loss','hours-per-week','native-country','income']

#删除冗余数据
# data.drop(['education','fnlwgt'], axis=1, inplace=True)

# 去除字符串数值前面的空格
str_cols=[1,3,5,6,7,8,9,13,14]
for col in str_cols:
    data.iloc[:,col]=data.iloc[:,col].map(lambda x: x.strip())

# 删除缺失值样本
data.replace("?",np.nan,inplace=True)
data.dropna(inplace=True)

# 对字符数据进行编码
from sklearn import preprocessing
label_encoder=[] # 放置每一列的encoder
encoded_set = np.empty(data.shape)
for col in range(data.shape[1]):
    encoder=None
    if data.iloc[:,col].dtype==object: # 字符型数据
        encoder=preprocessing.LabelEncoder()
        encoded_set[:,col]=encoder.fit_transform(data.iloc[:,col])
    else:  # 数值型数据
        encoded_set[:,col]=data.iloc[:,col]
    label_encoder.append(encoder)


# 对某些列进行范围缩放
cols=[2,10,11]
data_scalers=[] # 专门用来放置scaler
for col in cols:
    data_scaler=preprocessing.MinMaxScaler(feature_range=(-1,1)) 
    encoded_set[:,col]=np.ravel(data_scaler.fit_transform(encoded_set[:,col].reshape(-1,1)))
    data_scalers.append(data_scaler)

# 拆分数据集为train set和test set
dataset_X,dataset_y=encoded_set[:,:-1],encoded_set[:,-1]
from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y=train_test_split(dataset_X,dataset_y,
                                                  test_size=0.3,random_state=42)

#划分数据
Y = train_y.reshape(-1,1)  # 训练集Y
X = train_X  
X = np.hstack([np.ones((len(X), 1)), X])  # 训练集X

y = test_y.reshape(-1,1)  # 测试集y
x = test_X
x = np.hstack([np.ones((len(x), 1)), x])  # 测试集x


#sigmoid函数
def sigmoid(X,theta):
    return 1/(1+np.exp(-X.dot(theta)))


#损失函数
def cost(X,Y,theta):
    H = sigmoid(X,theta)
    return (1-Y).T.dot(np.log(1-H+1e-5)) - Y.T.dot((np.log(H+1e-5)))

#梯度下降
y_t = []
def Gradient_descent(X,Y,alpha,maxIter):
    #初始化theta
    np.random.seed(42)
    theta = np.mat(np.random.randn(15,1))
    loss = cost(X,Y,theta)
    y_t.append(loss)
    #更新theta
    for i in range(maxIter):
        H = sigmoid(X,theta)
        dtheta = X.T.dot((H - Y))/len(Y)
        theta -= alpha*dtheta
        loss = cost(X,Y,theta)
        y_t.append(loss)
    return theta

theta = Gradient_descent(X,Y,0.0014,10000)

#查看何时收敛
y_t = np.array(y_t)
y_t = list(chain.from_iterable(y_t))
plt.plot(y_t)
plt.xlabel('iterations')
plt.ylabel('loss_value')
plt.show()

print("梯度下降:")
print("theta=")
print(theta)

# 计算准确率
correct = 0
for Xi, Yi in zip(x,y):
    pred = sigmoid(Xi,theta)
    pred = 1 if pred > 0.5 else 0
    if pred == Yi:
        correct += 1
print("正确率:",correct/len(x))

# 计算AUC
Y_predict = 1/(1+np.exp(-x.dot(theta)))
Y_predict = np.asarray(Y_predict)
Y_true = np.asarray(y)
auc = roc_auc_score(Y_true,Y_predict)
print('AUC=',auc)

图1 梯度下降损失函数收敛过程
图2 梯度下降损失函数收敛过程局部图
图3 梯度下降迭代800次结果
图4 梯度下降迭代4000次结果
图5 梯度下降迭代10000次结果
对图1可以看到损失函数在迭代次数为800次左右时候取得了一个最小值,但是当迭代次数为800次时候,准确率仅为64%左右,AUC指标仅为0.54,这说明该分类器对正例和负例毫无区分能力,对于不论真实类别是1还是0的样本,分类器预测为1的概率是相等的。显然这种分类器是不乐观的,类似于抛硬币。因此更换迭代次数,当迭代次数为4000次左右时候,可以看到函数基本收敛,此时的准确率为76%,与迭代800次相比要好很多,而且AUC指标也达到了了0.73,。继续增加迭代次数,当迭代到10000次左右时候准确率达到了79%,而迭代次数达到11000次时候准确率又开始下降,因此最好的迭代次数为10000左右,此时的分类效果是最好的,此时的theta值为:
图7 梯度下降θ值

(二)朴素贝叶斯求解

import math
import numpy as np
import pandas as pd
from sklearn.metrics import roc_auc_score

data = pd.read_csv('adult.csv',header=None)


#去除字符串数值前面的空格
str_cols=[1,3,5,6,7,8,9,13,14]
for col in str_cols:
    data.iloc[:,col]=data.iloc[:,col].map(lambda x: x.strip())

# 删除缺失值样本
data.replace("?",np.nan,inplace=True)
data.dropna(inplace=True)

# 对字符数据进行编码
from sklearn import preprocessing
label_encoder=[] # 放置每一列的encoder
encoded_set = np.empty(data.shape)
for col in range(data.shape[1]):
    encoder=None
    if data.iloc[:,col].dtype==object: # 字符型数据
        encoder=preprocessing.LabelEncoder()
        encoded_set[:,col]=encoder.fit_transform(data.iloc[:,col])
    else:  # 数值型数据
        encoded_set[:,col]=data.iloc[:,col]
    label_encoder.append(encoder)

# 划分训练集与测试集
def splitData(data_list,ratio):
  train_size = int(len(data_list)*ratio)
  np.random.seed(44)
  np.random.shuffle(data_list)
  train_set = data_list[:train_size]
  test_set = data_list[train_size:]
  return train_set,test_set

data_list = np.array(encoded_set).tolist()
trainset,testset = splitData(data_list,ratio = 0.7)
print("朴素贝叶斯求解:")
print('Split {0} samples into {1} train and {2} test samples '.format(len(data), len(trainset), len(testset)))

# 按类别划分数据
def seprateByClass(dataset):
  seprate_dict = {
    
    }
  info_dict = {
    
    }
  for vector in dataset:
      if vector[-1] not in seprate_dict:
          seprate_dict[vector[-1]] = []
          info_dict[vector[-1]] = 0
      seprate_dict[vector[-1]].append(vector)
      info_dict[vector[-1]] +=1
  return seprate_dict,info_dict

train_separated,train_info = seprateByClass(trainset) #划分好的数据

# 计算每个类别的先验概率(P(yi))
def calulateClassPriorProb(dataset,dataset_info):
  dataset_prior_prob = {
    
    }
  sample_sum = len(dataset)
  for class_value, sample_nums in dataset_info.items():
      dataset_prior_prob[class_value] = sample_nums/float(sample_sum)
  return dataset_prior_prob

prior_prob = calulateClassPriorProb(trainset,train_info) # 每个类别的先验概率(P(yi))

# 均值
def mean(list):
  list = [float(x) for x in list] #字符串转数字
  return sum(list)/float(len(list))

# 方差
def var(list):
  list = [float(x) for x in list]
  avg = mean(list)
  var = sum([math.pow((x-avg),2) for x in list])/float(len(list)-1)
  return var

# 概率密度函数
def calculateProb(x,mean,var):
    exponent = math.exp(math.pow((x-mean),2)/(-2*var))
    p = (1/math.sqrt(2*math.pi*var))*exponent
    return p

# 计算每个属性的均值和方差
def summarizeAttribute(dataset):
    dataset = np.delete(dataset,-1,axis = 1) # delete label
    summaries = [(mean(attr),var(attr)) for attr in zip(*dataset)] #按列提取
    return summaries

# 按类别提取属性特征 会得到 类别数目*属性数目 组
def summarizeByClass(dataset):
  summarize_by_class = {
    
    }
  for classValue, vector in train_separated.items():
      summarize_by_class[classValue] = summarizeAttribute(vector)
  return summarize_by_class

train_Summary_by_class = summarizeByClass(trainset) # 按类别提取属性特征

#计算属于某类的类条件概率(P(x|yi))
def calculateClassProb(input_data,train_Summary_by_class):
  prob = {
    
    }
  for class_value, summary in train_Summary_by_class.items():
      prob[class_value] = 1
      for i in range(len(summary)):
        mean,var = summary[i]
        x = input_data[i]
        p = calculateProb(x,mean,var)
        prob[class_value] *=p
  return prob


#  朴素贝叶斯分类器
def bayesianPredictOneSample(input_data):
  classprob_dict = calculateClassProb(input_data,train_Summary_by_class) # 计算属于某类的类条件概率(P(x|yi))
  result = {
    
    }
  for class_value,class_prob in classprob_dict.items():
    p = class_prob*prior_prob[class_value]
    result[class_value] = p
  return max(result,key=result.get)

# 单个样本测试
# print(testset[6][14])
# input_vector = testset[6]
# input_data = input_vector[:-1]
# result = bayesianPredictOneSample(input_data)
# print("the sameple is predicted to class: {0}.".format(result))

# 计算准确率
save = []
def calculateAccByBeyesian(dataset):
  correct = 0
  for vector in dataset:
      input_data = vector[:-1]
      label = vector[-1]
      result = bayesianPredictOneSample(input_data)
      save.append(result)
      if result == label:
          correct+=1
  return correct/len(dataset)

acc = calculateAccByBeyesian(testset)
print("正确率:",acc)

#计算AUC
Y_predict = np.array(save)
temp = np.array(testset)
Y_true = np.array(temp[:,-1])
auc = roc_auc_score(Y_true,Y_predict)
print('AUC=',auc)

图7 朴素贝叶斯运行结果
可以看到朴素贝叶斯算法相较于梯度下降其准确率提高了一点,但是其AUC指标却明显低很多,仅为0.64。理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相互独立,而实际生活中要想属性之间是相互独立的是不太实际的,就比如数据集中的工作时间和国籍,不同的国家之间的工作时间是不一样的,而且每个国家之间的收入也是不同的。

猜你喜欢

转载自blog.csdn.net/weixin_45750572/article/details/124156254