天天看點

pearson 相關系數 & 機器學習模型中不平衡樣本問題

0 前言:本文讨論一下幾點:

       1, pearson 相關系數(Pearson Correlation Coeffient) ---  皮爾遜相關系數

   2,資訊增益(InfoGain) 、卡方檢驗 與特征選擇

   3,機器學習模型中不平衡樣本問題

1,pearson 相關系數(Pearson Correlation Coeffient) ---  皮爾遜相關系數:  

(1)皮爾遜相關系數的适用範圍:

當兩個變量的标準差都不為零時,相關系數才有定義,皮爾遜相關系數适用于: 

1). 兩個變量之間是線性關系,都是連續資料。 

2). 兩個變量的總體是正态分布,或接近正态的單峰分布。 

3). 兩個變量的觀測值是成對的,每對觀測值之間互相獨立。

(2)定義:

第一種形式(也就是定義的形式):

pearson 相關系數 & 機器學習模型中不平衡樣本問題

其變形(第二種形式):

pearson 相關系數 & 機器學習模型中不平衡樣本問題

(其中,E為數學期望或均值,N為資料的數目,E{ [X-E(X)] [Y-E(Y)]}稱為随機變量X與Y的協方差,記為Cov(X,Y))

(3)代碼實作:

#encoding=utf8
from math import sqrt
import sys
import traceback

def multiply(a,b):
	#a,b兩個清單的資料一一對應相乘之後求和
	sum_ab=0.0
	for i in range(len(a)):
		temp=a[i]*b[i]
		sum_ab+=temp
	return sum_ab

def cal_pearson(x,y):
	n=len(x)
	#求x_list、y_list元素之和
	sum_x=sum(x)
	sum_y=sum(y)
	#求x_list、y_list元素乘積之和
	sum_xy=multiply(x,y)
	#求x_list、y_list的平方和
	sum_x2 = sum([pow(i,2) for i in x])
	sum_y2 = sum([pow(j,2) for j in y])
	molecular=sum_xy-(float(sum_x)*float(sum_y)/n)
	#計算Pearson相關系數,molecular為分子,denominator為分母
	denominator=sqrt((sum_x2-float(sum_x**2)/n)*(sum_y2-float(sum_y**2)/n))
	if denominator!=0:
		return molecular/denominator
	else:
		return 0


data={}
data_y = {}

idx_2_pearson = {}

def load_data(file_in):
	f=open(file_in,'r')
	lable = 0
	lines=f.readlines()
	for line in lines:
		#strip用于去掉換行符,split()通過指定分隔符對字元串進行切片,傳回子字元串
		cols=line.strip('\n').split(' ')
		for i in range(len(cols)):
			#float将字元串轉成浮點數
			if i == 0:
				lable = float(cols[0])
				continue
			idx_val = cols[i].split(":")
			if len(idx_val) < 2:
				continue
			try:
				idx = int(idx_val[0])
				val = float(idx_val[1])
				data.setdefault(idx,[]).append(val)
				data_y.setdefault(idx,[]).append(lable)
			except:
				traceback.print_exc()

def print_result():
	#idx_pearson_list = sorted(idx_2_pearson.items(), key=lambda d: abs(d[1]), reverse=True)
	idx_pearson_list = sorted(idx_2_pearson.items(), key=lambda d: abs(d[1]), reverse=True)
	for idx, pearson_score in idx_pearson_list:
		print idx, pearson_score 

def cal_pearson_one_by_one():
	for idx, Xi in data.items():
		lables=data_y[idx]
		#print lables,Xi
		#print ("x_list,y_list的Pearson相關系數為:"+str(cal_pearson(lables,Xi)))
		#print idx, cal_pearson(lables, Xi)
		idx_2_pearson[idx] = cal_pearson(lables, Xi)
	print_result()


	

if __name__=='__main__':
	load_data(sys.argv[1])
	cal_pearson_one_by_one()
           

2,機器學習模型中不平衡樣本問題

(1)為什麼不平衡學習

傳統的學習方法以降低總體分類精度為目标,将所有樣本一視同仁,同等對待,造成了分類器在多數類的分類精度較高而在少數類的分類精度很低。

機器學習模型都有一個待優化的損失函數,以我們最常用最簡單的二進制分類器邏輯回歸為例,邏輯回歸以優化總體的精度為目标,

不同類别的誤分類情況産生的誤差是相同的,考慮一個$500:1$的資料集,即使把所有樣本都預測為多數類其精度也能達到$500/501$之高,

很顯然這并不是一個很好的學習效果,是以傳統的學習算法在不平衡資料集中具有較大的局限性。

(2)解決方案:

解決方法主要分為兩個方面,第一種方案主要從資料的角度出發,主要方法為抽樣,可以通過某種政策進行抽樣,讓我們的資料相對均衡一些;

第二種方案從算法的角度出發,考慮不同誤分類情況代價的差異性對算法進行優化。

pearson 相關系數 &amp; 機器學習模型中不平衡樣本問題

另外可以參考 另外一篇文章

3,資訊增益與特征選擇

(1)  知乎上綜述:

特征選擇是特征工程中的重要問題(另一個重要的問題是特征提取),坊間常說:資料和特征決定了機器學習的上限,而模型和算法隻是逼近這個上限而已。 由此可見,特征工程尤其是特征選擇在機器學習中占有相當重要的地位。

通常而言,特征選擇是指選擇獲得相應模型和算法最好性能的特征集,工程上常用的方法有以下:

1. 計算每一個特征與響應變量的相關性:工程上常用的手段有計算皮爾遜系數和互資訊系數,皮爾遜系數隻能衡量線性相關性而互資訊系數能夠很好地度量各種相關性, 但是計算相對複雜一些,好在很多toolkit裡邊都包含了這個工具(如sklearn的MINE),得到相關性之後就可以排序選擇特征了;

2. 建構單個特征的模型,通過模型的準确性為特征排序,借此來選擇特征,另外,記得JMLR'03上有一篇論文介紹了一種基于決策樹的特征選擇方法,本質上是等價的。 當選擇到了目标特征之後,再用來訓練最終的模型;

3. 通過L1正則項來選擇特征:L1正則方法具有稀疏解的特性,是以天然具備特征選擇的特性,但是要注意,L1沒有選到的特征不代表不重要, 原因是兩個具有高相關性的特征可能隻保留了一個,如果要确定哪個特征重要應再通過L2正則方法交叉檢驗;

4. 訓練能夠對特征打分的預選模型:RandomForest和Logistic Regression等都能對模型的特征打分,通過打分獲得相關性後再訓練最終模型;

5. 通過特征組合後再來選擇特征:如對使用者id和使用者特征最組合來獲得較大的特征集再來選擇特征,這種做法在推薦系統和廣告系統中比較常見, 這也是所謂億級甚至十億級特征的主要來源,原因是使用者資料比較稀疏,組合特征能夠同時兼顧全局模型和個性化模型,這個問題有機會可以展開講。

6. 通過深度學習來進行特征選擇:目前這種手段正在随着深度學習的流行而成為一種手段,尤其是在計算機視覺領域,原因是深度學習具有自動學習特征的能力, 這也是深度學習又叫unsupervised feature learning的原因。從深度學習模型中選擇某一神經層的特征後就可以用來進行最終目标模型的訓練了。

整體上來說,特征選擇是一個既有學術價值又有工程價值的問題,目前在研究領域也比較熱,值得所有做機器學習的朋友重視。

(2) 比較好的特征選擇方法,除了卡方檢驗外還有就是資訊增益(xgboost也可以實作) ---- 若是自己實作InfoGain,同時回帶來一個問題:決策樹如何對連續性特征進行分段?

決策樹對于連續性特征(比如數字特征:比如符合一點分布的類似高斯分布)進行分段呢?舉個例子,一組特征為0~5的連續數字,決策樹如何分段生成類似:x<3,x>=3的if-else規則呢?,如何确定分段點是3而不是其他呢?

并不用考慮每一個example, 對第i個feature,首先以feature i 為key sort(feature_i, label_i)然後将label 有變動的地方作為可能的劃分點,

比如 label 為[1,1,0,0,0,1]隻需要考慮兩個地方即 [1,1]後面 和[1,1,0,0,0]後面。對于每一個可能的劃分點可以求information gain 讓他最大,在求information gain 的時候可以用entropy 也可以用gini。

(3)執行個體代碼 (沒有解決連續特征問題)

#!/usr/bin/python 
#encoding=gbk

import sys
import traceback
import math

def calcShannonEnt(dataSet):
	numEntries = len(dataSet)
	labelCounts = {}
	## count by label 
	for featVec in dataSet:
		currentLabel = featVec[0]
		if currentLabel not in labelCounts:
			labelCounts[currentLabel] = 0
		labelCounts[currentLabel] += 1
	## calculate entropy & information gain by probability(word frequency)
	shannonEnt = 0.0
	for key in labelCounts:
		prob = float(labelCounts[key]) / numEntries
		shannonEnt -= (prob * math.log(prob, 2) )

	return shannonEnt


# params 待劃分的資料集, 劃分資料集的特征, 劃分資料的特征所對應的特征值 
# return 按照axis (特征下标) & value(特征值) 傳回的資料集
def splitDataSet(dataSet, axis, value): 
	retDataSet = []
	for featVec in dataSet:
		if featVec[axis] == value: ### 是不是可以改成>, < 等其他的符号呢
			reduceFeatVec = featVec[:axis]
			reduceFeatVec.extend(featVec[axis+1:])
			retDataSet.append(reduceFeatVec)
		else:
			pass
	return retDataSet 

## this function (chooseBestFeatureToSplit) will call two funs (calcShannonEnt & splitDataSet)
## data require: 1,資料必須是一種由清單元素組成的清單(二維數組),并且資料次元相同;
## 2,資料的第一列(idx=0)是目前執行個體的标簽; 3,資料類型沒有要求,既可以是數字也可是是字元串
### 資訊增益是熵的減少 或者 是資料無序度的減少 

## return the bestFeature idx...
def chooseBestFeatureToSplit(dataSet):
	numFeatures = len(dataSet[0]) -1
	baseEntropy = calcShannonEnt(dataSet)
	bestInfoGain = 0.0; bestFeature = -1
	i = 0
	while i < numFeatures:
		i += 1 ## because the first val in list is lable
		featList = [example[i] for example in dataSet]
		uniqueVals = set(featList)
		newEntropy = 0.0
		for value in uniqueVals: ## 如果離散值太多,>>100, 就會帶來問題
		   subDataSet = splitDataSet(dataSet, i, value)
		   prob = len(subDataSet) / float(len(dataSet))
		   newEntropy += prob * calcShannonEnt(subDataSet)
		infoGain = baseEntropy - newEntropy 
		## OutPut
		print i, infoGain
		if infoGain > bestInfoGain:
			bestInfoGain = infoGain
			bestFeature = i

	return bestFeature 

## deal with the anomaly...
import operator
def majorityCnt(classList):
	classCount = {}
	for vote in classList:
		if vote not in classCount.keys(): 
			classCount[vote] = 0
		classCount[vote] += 1

	sortedClassCount = sorted(classCount.itemitems(), key=operator.itemgetter(1), reverse=True)
	## same to sorted(classCount.items(), key=lambda d: d[1], reverse=True)
	return sortedClassCount[0][0]

## python 的傳參是引用,是以每次遞歸調用時,都要重建立立資料集 
def createTree(dataSet, labels):
	classList = [example[0] for example in dataSet]
	if classList.count(classList[0]) == len(classList): ## already same lable
		return classList[0]
	if len(dataSet[0]) == 1: ## 周遊完所有特征時,仍然無法将資料集劃分成為僅包含唯一類别的組,則傳回出現次數最多的label
		return majorityCnt(classList) 
	bestFeat = chooseBestFeatureToSplit(dataSet)
	print >> sys.stderr,"##", bestFeat, labels, dataSet
	bestFeatLabel = labels[bestFeat] ## 根據bestFeat(idx)取得label
	myTree = {bestFeatLabel: {} }
	del(labels[bestFeat])
	featValues = [example[bestFeat] for example in dataSet]
	uniqueVals = set(featValues)
	for value in uniqueVals:
		subLabels = labels[:]
		myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) ## recursive fun..

	return myTree 

## decisionTree store and fetch
def storeTree(inputTree, filename):
	import pickle 
	fw = open(filename, 'w')
	pickle.dump(inputTree, fw) ## 序列化對象到磁盤上,用的時候再讀取出來即可,不用每次都重新訓練生成決策樹
	fw.close()

## load model..
def grabTree(filename):
	import pickle
	fr = open(filename, 'r')
	return pickle.load(fr)

## use this decisionTree model to classify...
def classify(inputTree, featLabels, testVec):
	firstStr = inputTree.keys()[0]
	secondDict = inputTree[firstStr]
	print >> sys.stderr, "&&", firstStr, secondDict
	featIndex = featLabels.index(firstStr) ## 将标簽字元串轉化為索引 
	classLabel = None
	for key in secondDict.keys():
		if testVec[featIndex-1] == key: ### labels idx_0 is non_using, so need to -1
			if type(secondDict[key]).__name__ == 'dict':
				classLabel = classify(secondDict[key], featLabels, testVec)
			else:
				classLabel = secondDict[key]

	return classLabel

## 1--1 Init data 
def createDataSet():
	dataSet = [['1', 1, 1],
			['1', 1, 1],
			['0', 1, 0],
			['0', 0, 1],
			['0', 0, 1]]
	labels = ['label_ignore', 'sunshine', 'non-sunshine']
	return dataSet, labels 

## 1--2 Init data 
def load_data(file_in):
	labels = ['label_ignore', 'sunshine', 'non-sunshine']
	dataSet = []
	f=open(file_in,'r')
	lable = 0
	lines=f.readlines()
	for line in lines:
		#strip用于去掉換行符,split()通過指定分隔符對字元串進行切片,傳回子字元串
		data=[]
		idx_cur = 0 ## is next
		cols=line.strip('\n').split(' ')
		for i in range(len(cols)):
			#float将字元串轉成浮點數
			if i == 0:
				data.append(float(cols[0]))
				idx_cur += 1
				continue
			idx_val = cols[i].split(":")
			if len(idx_val) < 2:
				continue
			try:
				idx = int(idx_val[0])
				val = float(idx_val[1])
				## sparse ---> dense...
				while idx_cur < idx:
					data.append(0.0)
					idx_cur += 1
				data.append(val)
				idx_cur += 1
			except:
				traceback.print_exc()
		## sparse ---> dense...
		while idx_cur < 1000:
			data.append(0.0) 
			idx_cur += 1 
		dataSet.append(data)
		#print data
	return dataSet, labels



def test1():
	myDat, labels = createDataSet()
	## just two labels
	print myDat
	print calcShannonEnt(myDat)
	print splitDataSet(myDat, 1, 1)
	print splitDataSet(myDat, 1, 0)

def test2():
	myDat, labels = createDataSet()
	## change the first sample for a new lable, then the entropy will add
	myDat[0][0] = '2'
	print myDat
	print calcShannonEnt(myDat)

def test3():
	myDat, labels = load_data(sys.argv[1])
	## change the first sample for a new lable, then the entropy will add
	#print myDat
	print chooseBestFeatureToSplit(myDat)

def test4():
	myDat, labels = createDataSet()
	print myDat, labels 
	storeTree(labels, "labels.txt")    
	decisionTree = createTree(myDat, labels)
	print decisionTree 
	storeTree(decisionTree, "decisionTree.txt")

def test5():
	decisionTree = grabTree("decisionTree.txt")
	labels = grabTree("labels.txt")
	print "###", decisionTree, labels
	#testSample = [2, 0]
	testSample = [1, 0]
	#testSample = [1, 1]
	preLabel = classify(decisionTree, labels, testSample)
	print "predict label: ", preLabel


if "__main__" == __name__:
   #test1()
   #test2()
   test3()
   #test4()
   #test5()
           
下一篇: GUI Qt5

繼續閱讀