SVM预测术后建议、AdaBoost预测学生期末分数

第一题:用SVM分析post-operative数据

数据来源

https://archive.ics.uci.edu/ml/datasets/Post-Operative+Patient

运行环境

python3.7、PyCharm 2018.2.4 (Community Edition)

思路

根据所给数据集及其说明可以看出每组数据有8个特征和一个标签,标签取值为S、I、A,分别表示病人可以回家、病人送去加护病房和病人送去普通病房,在这里,可以将其分为送回家和继续呆在医院,所以将S看作正,其余看为负,分别用1和-1表示。再看特征,除了最后一个特征是0到20之间的一个数字之外,其余都是离散特征,可以将离散特征根据情况转为5、10、15和20,这样有利于应用SVM进行分析,除此之外,最后一个特征有三个缺失值,需要对其进行缺失值处理,考虑用此特征取值的众数代替。将此问题看作一个线性支持向量机问题,引入松弛变量和惩罚参数,采用SMO算法进行分析。其中,取松弛变量toler为0.01,取C为0.6,最大迭代次数为40。

源代码

# -*- coding: UTF-8 -*-
#Author:Yinli

import numpy as np
import random

#数据总数为90组
#取60组作为训练数据,其余用于测试
totalNum = 90
trainingNum = 60
testingNum = totalNum - trainingNum

#数据结构
class optStruct:
   def __init__(self, dataMat, classLabels, C, toler):
      self.X = dataMat                                #数据矩阵
      self.labelMat = classLabels                     #数据标签
      self.C = C                                      #松弛变量
      self.tol = toler                                #容错率
      self.m = np.shape(dataMat)[0]                   #矩阵行数
      self.alphas = np.mat(np.zeros((self.m, 1)))     #初始alpha为0
      self.b = 0                                      #初始b为0
      self.eCache = np.mat(np.zeros((self.m, 2)))     #第一列为是否为有效标志位,第二列为实际误差值

#计算误差
def calcEk(oS , k):
   #计算预测值
   fXk = float(np.multiply(oS.alphas, oS.labelMat).T*(oS.X*oS.X[k,:].T)) + oS.b
   #计算误差并返回
   Ek = fXk - float(oS.labelMat[k])
   return Ek

#随机挑选一个alphaj并返回
def selectJRandom(i, m):
   j = i
   while(j == i):
      j = int(random.uniform(0,m))
   return j

#挑选一个误差之差最大的alphaj
def selectJ(i, oS, Ei):
   #初始化j和最大误差之差以及j的误差
   maxK = -1
   maxDeltaE = 0
   Ej = 0
   #更新i的误差缓存
   oS.eCache[i] = [1,Ei]
   #返回误差不为0的数据的索引值
   validECacheList = np.nonzero(oS.eCache[:,0].A)[0]
   #如果有不为0的误差,则挑选最佳的j
   if(len(validECacheList) > 1):
      #遍历误差不为0的数据
      for k in validECacheList:
         #跳过i本身
         if k == i:
            continue
         #计算误差和与i误差的差值
         Ek = calcEk(oS, k)
         deltaE = abs(Ei - Ek)
         #若误差差值大于最大误差差值,更新
         if(deltaE > maxDeltaE):
            maxK = k
            maxDeltaE = deltaE
            Ej = Ek
      #返回索引值和对于误差
      return maxK, Ej
   #如果没有不为0的误差,则随机挑选j并返回
   else:
      j = selectJRandom(i, oS.m)
      Ej = calcEk(oS, j)
   return j, Ej

#计算k的误差并更新误差缓存
def updateEk(oS, k):
   Ek = calcEk(oS, k)
   oS.eCache[k] = [1, Ek]

#对于在范围之外的alpha,修剪并返回修剪之后的值
def clipAlpha(aj, L, H):
   if aj > H:
      aj = H
   if aj < L:
      aj = L
   return aj

#内循环,若成功更新了alpha则返回1,否则返回0
def innerL(i, oS):
   #计算i误差
   Ei = calcEk(oS,i)
   #根据容错率和范围确定i是否有效
   if((oS.labelMat[i]*Ei < -oS.tol) and (oS.alphas[i] < oS.C))or((oS.labelMat[i]*Ei > oS.tol)and(oS.labelMat[i] > 0)):
      #选择另外一个alphaj
      j,Ej = selectJ(i,oS,Ei)
      #记录原来的alpha值
      alphaIOld = oS.alphas[i].copy()
      alphaJOld = oS.alphas[j].copy()
      #根据i和j的标签值判断L和H
      if(oS.labelMat[i] != oS.labelMat[j]):
         L = max(0, oS.alphas[j] - oS.alphas[i])
         H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
      else:
         L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
         H = min(oS.C, oS.alphas[j] + oS.alphas[i])
      #若上下界相等则无效
      if L == H:
         print("L==H")
         return 0
      #计算eta,若eta大于等于0则无效
      eta = 2.0*oS.X[i,:]*oS.X[j,:].T - oS.X[i,:]*oS.X[i,:].T - oS.X[j,:]*oS.X[j,:].T
      if eta >= 0:
         print("eta >= 0")
         return 0
      #更新alphaj,并修剪
      oS.alphas[j] -= oS.labelMat[j] * (Ei-Ej)/eta
      oS.alphas[j] = clipAlpha(oS.alphas[j],L,H)
      #更新alphaj至缓存
      updateEk(oS,j)
      #如果alphaj更新得太小,则无效
      if (abs(oS.alphas[j] - alphaJOld) < 0.00001):
         print("alpha_j变化太小")
         return 0
      #更新alpha_i
      oS.alphas[i] += oS.labelMat[j]*oS.labelMat[i]*(alphaJOld - oS.alphas[j])
      updateEk(oS,i)
      #计算并更新b
      b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIOld) * oS.X[i,:]*oS.X[i,:].T - oS.labelMat[j] * (oS.alphas[j] - alphaJOld) * oS.X[i,:]*oS.X[j,:].T
      b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIOld) * oS.X[i,:]*oS.X[j,:].T - oS.labelMat[j] * (oS.alphas[j] - alphaJOld) * oS.X[j,:]*oS.X[j,:].T
      if(oS.alphas[i]>0)and(oS.alphas[i]0)and(oS.alphas[j] 0) or (entireSet)):
      alphaPairsChanged = 0
      #遍历整个数据集
      if entireSet:
         #用SMO算法遍历
         for i in range(oS.m):
            alphaPairsChanged += innerL(i,oS)
            print("全样本遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter,i,alphaPairsChanged))
         iter += 1
      #遍历非边界值
      else:
         #遍历不在边界0和C上的alpha
         nonBoundIs = np.nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
         for i in nonBoundIs:
            alphaPairsChanged += innerL(i,oS)
            print("非边界遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter,i,alphaPairsChanged))
         iter += 1
      #遍历完非边界值后再初始化为非边界遍历
      if entireSet:
         entireSet = False
      #如果alpha没有更新改为全局遍历
      elif (alphaPairsChanged == 0):
         entireSet = True
      print("迭代次数: %d" % iter)
   #返回SMO算法计算的b和alphas
   return oS.b,oS.alphas

#从文件中读取并处理数据返回特征矩阵和标签数据
def loadData(filename):
   fr = open(filename, 'r', encoding='utf-8')
   arrayOfLines = fr.readlines()
   featureSet = []
   labels = []
   #逐行处理数据
   for i in range(totalNum):
      #去掉每行末尾的'\n'
      arrayOfLines[i] = arrayOfLines[i].rstrip('\n')
      listFromLine = arrayOfLines[i].split(',')
      #将特征和标签数据分开
      featureSet.append(listFromLine[:-1])
      labels.append(listFromLine[-1])
   #返回特征矩阵和标签数据
   return featureSet,labels

#处理缺失值
def preProcess0(featureSet):
   numList = []
   #记录第8列特征的所有取值
   for i in range(totalNum):
      if(featureSet[i][7] != '?'):
         numList.append(int(featureSet[i][7]))
   #统计此特征的众数,设为替补值
   numArray = np.array(numList)
   counts = np.bincount(numArray)
   replaceNum = np.argmax(counts)
   #将缺失值替换为众数
   for i in range(totalNum):
      if(featureSet[i][7] == "?" or featureSet[i][7] == "07"):
         featureSet[i][7] = replaceNum
      else:
         featureSet[i][7] = int(featureSet[i][7])

#处理第j+1列的数据
#将low、mid和high特征转化为5、10和15
def dealWithColumn0(featureSet, j):
   for i in range(totalNum):
      if featureSet[i][j] == "low":
         featureSet[i][j] = 5
      elif featureSet[i][j] == "mid":
         featureSet[i][j] = 10
      else:
         featureSet[i][j] = 15

#处理第j+1列的数据
#将poor、fair、good和excellent转化为5、10、15和20
def dealWithColumn1(featureSet, j):
   for i in range(totalNum):
      if featureSet[i][j] == "poor":
         featureSet[i][j] = 5
      elif featureSet[i][j] == "fair":
         featureSet[i][j] = 10
      elif featureSet[i][j] == "good":
         featureSet[i][j] = 15
      else:
         featureSet[i][j] = 20

#处理第j+1列的数据
#将unstable、mod-stable和stable转化为5、10和15
def dealWithColumn2(featureSet, j):
   for i in range(totalNum):
      if featureSet[i][j] == "unstable":
         featureSet[i][j] = 5
      elif featureSet[i][j] == "mod-stable":
         featureSet[i][j] = 10
      else:
         featureSet[i][j] = 15

#将特征数据转化为数值
def preProcess1(featureSet):
   dealWithColumn0(featureSet, 0)
   dealWithColumn0(featureSet, 1)
   dealWithColumn1(featureSet, 2)
   dealWithColumn0(featureSet, 3)
   dealWithColumn2(featureSet, 4)
   dealWithColumn2(featureSet, 5)
   dealWithColumn2(featureSet, 6)

#将标签数据转化为1和-1数据,取S为1其余为-1
def preProcess2(labels):
   for i in range(totalNum):
      if labels[i] == "S":
         labels[i] = 1
      else:
         labels[i] = -1

#将特征和标签数据拆开分为训练数据和测试数据
def split(featureSet, labels):
   trainingSet = []
   trainingLabels = []
   testingSet = []
   testingLabels = []
   for i in range(trainingNum):
      trainingSet.append(featureSet[i])
      trainingLabels.append(labels[i])
   for i in range(testingNum):
      testingSet.append(featureSet[i+trainingNum])
      testingLabels.append(labels[i+trainingNum])
   return trainingSet,trainingLabels,testingSet,testingLabels

#根据计算出的alphas、特征矩阵和标签数据计算对应的w
def calcWs(alphas, dataSet, labels):
   dataMat = np.mat(dataSet)
   labelsMat = np.mat(labels).transpose()
   m,n = dataMat.shape
   w = np.zeros((n ,1))
   for i in range(m):
      w += np.multiply(alphas[i]*labelsMat[i], dataMat[i,:].T)
   return w

#主函数
if __name__ == "__main__":
   #处理文件得到结构化数据
   filename = "D:\python_things\code\第4次作业\SVM数据集\post-operative.data"
   featureSet, labels = loadData(filename)
   #对数据进行预处理
   preProcess0(featureSet)
   preProcess1(featureSet)
   preProcess2(labels)
   #将数据分为训练和测试
   trainingSet, trainingLabels,testingSet, testingLabels = split(featureSet, labels)
   #用SMO算法计算b和alphas,并算出w
   b,alphas = smoP(trainingSet,trainingLabels,0.8,0.001,40)
   w = calcWs(alphas, trainingSet, trainingLabels)
   #print(alphas)
   #print(w)
   #print(b)

   #用算出的w来对测试集进行分类并记录错误数
   errorNum = 0.0
   for i in range(testingNum):
      #计算分类结果
      classifyResult = np.dot(w.T, testingSet[i]) + b
      #将分类结果转化为1和-1
      if (classifyResult > 0):
         classifyLabel = 1
      else:
         classifyLabel = -1
      #如果分类结果与实际结果不符则记录并输出
      if (classifyResult > 0 and testingLabels[i] == -1) or (classifyResult < 0 and testingLabels[i] == 1):
         errorNum += 1
         print("!!!分类数值为%.4f,实际结果为%d" % (float(classifyResult), testingLabels[i]))

   print("一共%d组数据,其中%d组数据用于训练,%d组数据用于测试" % (totalNum, trainingNum, testingNum))
   print("一共%d组测试数据,判断错误%d组数据,错误率为%.3f%%" % (testingNum, errorNum, errorNum / testingNum * 100))

运行结果

SVM预测术后建议、AdaBoost预测学生期末分数_第1张图片
SVM.png

第二题:用AdaBoost分析student-mat数据集

数据来源

https://archive.ics.uci.edu/ml/datasets/student+performance

运行环境

python3.7、PyCharm 2018.2.4 (Community Edition)

思路

根据所给数据及其说明,共有两个数据集,分别是数学课程的数据和葡萄牙课程的数据,这里用数学课程的数据来进行分析,每组数据共有30个特征和三个标签,分别是第一阶段、第二阶段和第三阶段的分数,根据所给论文,第一二阶段的分数对第三阶段的分数影响很大,数值很接近,所以抛去不用,用30个特征来预测第三阶段的分数,这三十个特征都是离散数据,其中年龄数据也可以看作离散的,将分数大于10的记为通过,否则记为不通过,则转化为一个二分问题,采用AdaBoost算法来进行预测分类。每个弱分类器定为决策树桩,以特征取值为分类依据,等于此取值为1,不等于则为-1,定弱分类器的最大个数为40个。

源代码

# -*- coding: UTF-8 -*-
#Author:Yinli

import numpy as np
import re

#总数据数和训练数据数目以及测试数据数目
totalNum = 395
trainingNum = 300
testingNum = totalNum - trainingNum

#将文件处理转为训练数据及标签和测试数据及标签
def fileToData(filename):
   #读取文件
   fr = open(filename,'r',encoding='utf-8')
   arrayOfLines = fr.readlines()
   #初始化数据列表
   trainingSet = []
   trainingLabels = []
   testingSet = []
   testingLabels = []
   #处理训练数据及标签
   for i in range(trainingNum):
      #去掉每行开头的'"'和结尾的'\n'
      arrayOfLines[i+1] = arrayOfLines[i+1].rstrip('\n')
      arrayOfLines[i+1] = arrayOfLines[i+1].lstrip('"')
      #用正则表达式匹配去掉0或多个"+;+0或多个",得到每行的数据
      dataList = re.split('"*;"*',arrayOfLines[i+1])
      #得到第一列到倒数第四列的数据,即特征
      listFromLine = dataList[:-3].copy()
      trainingSet.append(listFromLine)
      #若最末尾的标签数值大于10.0,则记为1.表示通过,否则记为-1,表示没通过
      if(float(dataList[-1]) > 10.0):
         trainingLabels.append(1.0)
      else:
         trainingLabels.append(-1.0)
   #同样方式处理得到测试数据及标签
   for i in range(testingNum):
      arrayOfLines[i+trainingNum+1] = arrayOfLines[i+trainingNum+1].rstrip('\n')
      arrayOfLines[i+trainingNum+1] = arrayOfLines[i+trainingNum+1].lstrip('"')
      dataList = re.split('"*;"*', arrayOfLines[i + 1])
      listFromLine = dataList[:-3].copy()
      testingSet.append(listFromLine)
      if (float(dataList[-1]) > 10.0):
         testingLabels.append(1.0)
      else:
         testingLabels.append(-1.0)
   return trainingSet, trainingLabels, testingSet, testingLabels

#用决策树桩分类数据集
#传入特征矩阵、分类特征索引值和分类特征的取值
#返回分类结果向量
def stumpClassify(dataSet, dimen, classifyValue):
   #初始化分类结果向量为1
   retArray = np.ones((np.shape(dataSet)[0], 1))
   #对于数据集中指定特征与分类特征的取值不相等的数据,分类为-1
   retArray[dataSet[:,dimen] != classifyValue] = -1.0
   #返回分类结果向量
   return retArray

#根据权重建立最佳的决策树桩
#传入特征矩阵、标签向量以及数据权重
#返回最佳决策树桩、最小误差和最佳分类结果
def buildStump(dataSet, labels, D):
   dataMat = np.mat(dataSet)
   labelsMat = np.mat(labels).T
   m,n = np.shape(dataMat)
   #初始化最小误差为无穷大
   minError = float('inf')
   #用字典存储决策树桩
   bestStump = {}
   bestClassEst = np.mat(np.zeros((m,1)))
   #遍历每个特征
   for i in range(n):
      #得到一个不重复的包含所有指定特征取值的列表
      featList = [example[i] for example in dataSet]
      uniqueValues = set(featList)
      uniqueValues = list(uniqueValues)
      #遍历此特征的所有取值
      for j in range(len(uniqueValues)):
         #根据每个取值对数据进行分类,得到分类结果
         predictValues = stumpClassify(dataMat, i, uniqueValues[j])
         #分类错误的数据标记为1,正确标记为0
         errArray = np.mat(np.ones((m,1)))
         errArray[predictValues == labelsMat] = 0
         #将权重向量与错误向量相乘,得到考虑权重后的误差
         weightedError = D.T * errArray
         #若此特征取值的分类误差小于最小误差,则更新最小误差、最佳分类结果以及最佳决策树桩(特征索引值和特征取值)
         if(weightedError < minError):
            minError = weightedError
            bestClassEst = predictValues.copy()
            bestStump['dim'] = i
            bestStump['dimValue'] = uniqueValues[j]
   #返回最佳决策树桩、最小误差和最佳分类结果
   return bestStump, minError, bestClassEst

#采用adaBoost算法训练弱分类器组合
#传入特征矩阵、标签列表和最大分类器个数
def adaBoostTrainDS(dataSet, labels, numIt = 40):
   #初始化弱分类器列表
   weakClassArray = []
   m = np.shape(dataSet)[0]
   #初始权重为平均权重
   D = np.mat(np.ones((m,1)) / m)
   #用一个向量来记录每个数据的累计分类值,表示了弱分类器列表对数据的实时分类情况
   aggClassEst = np.mat(np.zeros((m, 1)))
   #逐个构造弱分类器,直到达到最大个数
   for i in range(numIt):
      print("第%d次迭代" % i)
      #根据此时权重计算最佳决策树桩
      bestStump, error, classEst = buildStump(dataSet, labels, D)
      print(bestStump)
      print("错误:%f" % error)
      #根据此决策树桩的分类错误率计算此弱分类器的权重alpha,并写入决策树桩字典
      alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16)))
      bestStump['alpha'] = alpha
      #将此弱分类器字典加入弱分类器列表
      weakClassArray.append(bestStump)
      #更新权重
      expon = np.multiply(-1 * alpha * np.mat(labels).T, classEst)
      D = np.multiply(D, np.exp(expon))
      D = D / D.sum()
      #更新弱分类器列表对每组数据的分类结果
      aggClassEst += alpha * classEst
      #统计此时的分类错误数目并打印
      aggErrorNum = numOfError(aggClassEst, labels)
      print("累计错误数:%d" % aggErrorNum)
      #若分类错误数为0则停止循环
      if(aggErrorNum == 0):
         break
   #返回弱分类器列表
   return weakClassArray

#根据预测结果和实际结果计算分类错误数
def numOfError(predictValue, labels):
   n = len(labels)
   errorNum = 0
   #遍历每组数据,若分类结果与预测结果不符则错误数加一
   for i in range(n):
      if(np.sign(predictValue[i]) != labels[i]):
         errorNum += 1
   #返回分类错误数
   return errorNum

#根据训练好的弱分类器列表对特征矩阵进行分类并返回结果
def adaClassify(dataToClassify, classifierArray):
   dataMat = np.mat(dataToClassify)
   m = np.shape(dataMat)[0]
   #初始化累计分类结果
   aggClassEst = np.mat(np.zeros((m,1)))
   #遍历弱分类器列表
   for i in range(len(classifierArray)):
      #根据每个分类器对特征矩阵进行分类,计算分类数值,并计入权重累加
      classEst = stumpClassify(dataMat, classifierArray[i]['dim'], classifierArray[i]['dimValue'])
      aggClassEst += classEst * classifierArray[i]['alpha']
   #返回分类结果
   return np.sign(aggClassEst)

#主函数
if __name__ == "__main__":
   #处理文件得到数据
   filename = "D:\python_things\code\第4次作业\Adaboost数据集更新\student-mat.csv"
   trainingSet, trainingLabels, testingSet, testingLabels = fileToData(filename)

   #训练弱分类器列表,最大分类器数目定为40个
   weakClassArray = adaBoostTrainDS(trainingSet, trainingLabels, 40)

   #用弱分类器列表对测试数据进行分类
   classifyResults = adaClassify(testingSet, weakClassArray)
   #初始化测试数据分类错误数目
   errorNum = 0
   #计算测试数据分类错误数目
   for i in range(testingNum):
      if(classifyResults[i] != testingLabels[i]):
         errorNum += 1
   #打印输出最后结果
   print("一共%d组数据,其中%d组数据用于训练,%d组数据用于测试" % (totalNum, trainingNum, testingNum))
   print("一共%d组测试数据,判断错误%d组数据,错误率为%.3f%%" % (testingNum, errorNum, errorNum / testingNum * 100))

运行结果

SVM预测术后建议、AdaBoost预测学生期末分数_第2张图片
AdaBoost.png

你可能感兴趣的:(SVM预测术后建议、AdaBoost预测学生期末分数)