模型评估与选择——python
【学习机器学习】模型评估与选择
这学期的课很多都要实验啊,不过机器学习真的可以算其中比较离谱的了,不说当堂上交,就这个任务量也属实有点多。其实要到4月3号才是我们班做,提前找其他班同学要到了先用python写写,到时候可能还要用matlab重新实现。。。
0、实验总览
Ⅰ、编程实现训练数据集与测试数据集
Ⅱ、编程实现性能度量
Ⅲ、编程实现假设检验(不太会)
一、任务一
1.1 留出法
留出法原理很直观,就是按照设定的比例对原始数据随机划分成两个互斥的集合,注意训练/测试集划分要尽可能保持数据分布的一致性(其实只要是随机分的多数可以保证)
那代码实现,如果调用sklearn真的是一句话就完事儿了,例如:
count = 0
while count < 100:
data_train, data_test = train_test_split(data, train_size=0.80, random_state=None, shuffle=True)
np.savetxt('./留出法数据/train{no}.csv'.format(no=count+1), data_train, fmt='%f', delimiter=',')
np.savetxt('./留出法数据/test{no}.csv'.format(no=count+1), data_test, fmt='%f', delimiter=',')
count = count + 1
所以我决定对这个train_test_split的源码进行一下研究,看看具体怎么实现的(防止老师禁止调用现成函数)
train_test_split源码如下:
def train_test_split(*arrays, **options):
n_arrays = len(arrays) # 一共有几个数据集
if n_arrays == 0: # 不能为空数据集
raise ValueError("At least one array required as input")
test_size = options.pop('test_size', None) # test_size即测试集占比,0~1之间的浮点数
train_size = options.pop('train_size', None) # train_size即训练集的占比,0~1之间的浮点数
random_state = options.pop('random_state', None) # 可以是一个整数,指定随机数生成器的种子
stratify = options.pop('stratify', None) # stratify可以是一个数据或者None。如果它不是None,则原始数据会分层采样,采样的标记数据由该参数指定。
shuffle = options.pop('shuffle', True) # shuffle主要决定每次执行结果是否相同
if options: # 无效的选项
raise TypeError("Invalid parameters passed: %s" % str(options))
arrays = indexable(*arrays)
n_samples = _num_samples(arrays[0])
n_train, n_test = _validate_shuffle_split(n_samples, test_size, train_size,
default_test_size=0.25) # 检验参数选项是否合理,得到训练集和测试集大小
if shuffle is False:
if stratify is not None:
raise ValueError(
"Stratified train/test split is not implemented for "
"shuffle=False")
train = np.arange(n_train)
test = np.arange(n_train, n_train + n_test)
else:
if stratify is not None:
CVClass = StratifiedShuffleSplit
else:
CVClass = ShuffleSplit
cv = CVClass(test_size=n_test,
train_size=n_train,
random_state=random_state)
train, test = next(cv.split(X=arrays[0], y=stratify))
return list(chain.from_iterable((safe_indexing(a, train),
safe_indexing(a, test)) for a in arrays))
#返回值:一个列表,依次给出一个或者多个数据集的划分的结果。每个数据集都划分为两部分:训练集和测试集。
# Tell nose that train_test_split is not a test.
# (Needed for external libraries that may use nose.)
train_test_split.__test__ = False
一些参数的意义我已经写了注释,不过注释并不全面完整,因为这些参数可以取浮点值可以取整数值,还可以有布尔值的表示,我只列出了这次实验要用的最常用的。另外最后实际生效用来做划分的ShuffleSplit对象,以及调用split方法,大家可以点进链接看看比较专业的解答。
1.2 p次k折交叉验证法
交叉验证的原理也很好理解,把原本的数据集近似均分(大小相似即可)的划分为k个子集。每次用k-1个子集的并集作为训练集,剩下的那1个作为测试集,这样一来k折就会有k次训练、测试结果,而p次k折一共可以做出k*p次结果,却只需要p次划分,相当方便。
sklearn里面直接就有一个RepeatedKFold对象,完美实现:
count = 0
kf = RepeatedKFold(n_splits=10, n_repeats=5, random_state=0)
for train, test in kf.split(data):
# print("%s""\n""%s" % (train, test))
np.savetxt("./交叉验证法数据/train{no}.csv".format(no=count+1), train, fmt="%f", delimiter=",")
np.savetxt("./交叉验证法数据/test{no}.csv".format(no=count+1), test, fmt="%f", delimiter=",")
count = count + 1
这个的源码就不看了,因为连随机都用不到,直接等分即可。另外k折可以衍生出一种留一法,即假设数据有m条,那么就m折(每条数据算一个子集),这样即使数据不多也可以大幅提高测试量,结果一般会更准(但不一定),缺点是太费时。
1.3 自助法
自助法我喜欢叫抽签法,因为和概率论里那些抽来抽去的好像。基本原理就是对有m个数据的数据集进行m次有放回的抽取,把抽到的作为训练集D’,D-D’作为测试集。
从原理上讲,有大概35%的数据永远不会被抽到,因此一般只用于小量数据或者难以有效划分训练/测试集时才会用。
python实现:我只做了一些修改,原本见代码出处
bootstrapping = []
for i in range(len(data)):
bootstrapping.append(np.floor(np.random.random() * len(data)))
# 通过序号获得原始数据集中的数据
train_set = []
test_set = []
for i in range(len(data)):
train_set.append(data[int(bootstrapping[i])])
np.savetxt("./自助法数据/train.csv", train_set, fmt="%f", delimiter=",")
for i in range(len(data)):
if i not in bootstrapping:
test_set.append(data[int(bootstrapping[i])])
np.savetxt("./自助法数据/test.csv", test_set, fmt="%f", delimiter=",")
二、任务二
接下来求取一系列性能度量就开始让我有些疑惑了,主要是数据应该是什么格式呢?以下的大部分代码我都是假设有已知list集合:
其中[xi,yi],xi表示预测器得到的结果,yi表示标记值
2.1 均方误差MSE
均方误差的公式为: E ( f ; D ) = 1 m ∑ i = 1 m ( f ( x i ) − y i ) 2 E(f;D)=\cfrac{1}{m}\sum_{i=1}^m (f(xi)-yi)^2 E(f;D)=m1∑i=1m(f(xi)−yi)2
def calculateMSE(D):
in_bracket = []
for i in range(len(D)):
num = D[i][0] - D[i][1]
num = pow(num, 2)
in_bracket.append(num)
all_sum = sum(in_bracket)
MSE = all_sum / len(D)
return MSE
2.2 错误率与精度
从2.2错误率与精度开始,一直到2.5绘制ROC曲线,都是针对二分类问题。既然针对二分类问题,那么就需要设置一个阈值用以判断正例与反例,我这里以0.8作为阈值。
def calculate_ErrorRate_and_Accuracy(D, threshold):
error = 0
accurate = 0
for i in range(len(D)):
if D[i][0] <= threshold && D[i][1] == 0:
accurate = accurate + 1
elif D[i][0] > threshold and D[i][1] == 1:
accurate = accurate + 1
else:
error = error + 1
return error / len(D), accurate / len(D)
2.3 查准率与查全率
有了上面错误率与精度,实际上我们已经可以做出如下的分类结果混淆矩阵了:
按照上图中对结果的分类,我们就可以有查准率与查全率的定义了。
查准率(Precision):
T
P
T
P
+
F
P
\cfrac{TP}{TP+FP}
TP+FPTP
查全率(Recall):
T
P
T
P
+
F
N
\cfrac{TP}{TP+FN}
TP+FNTP
用大白话来解释就是,查准率表示:在所有我认为的正例中,真正的正例占比;查全率表示:在所有真正的正例中,我查出了多少正例。
2.4 F1指数
F1度量是从P-R图(Precision-Recall)的平衡点引申出来的,不过我不太想写关于P-R图的内容了,因为这个图还是比较好理解的。计算公式如下:
F
1
=
2
∗
P
∗
R
P
+
R
=
2
∗
T
P
m
+
T
P
−
T
N
F1=\cfrac{2*P*R}{P+R}=\cfrac{2*TP}{m+TP-TN}
F1=P+R2∗P∗R=m+TP−TN2∗TP
2.5 ROC曲线,计算AUC
这个ROC曲线我在课上没有听明白,还是自己查了些资料理解的,尤其是绘图的那个公式,强烈建议看一看这位答主关于ROC图的解答。实际上就是将阈值从最大的预测值逐一减小到最小的预测值,每次减小会增加一个你认为的真正例或者假正例,所以要+1。
那么具体的绘图我们还是使用sklearn自带的ROC计算,并用matplotlib绘制:代码出自,也感谢这位博主的讲解
def draw_ROC(data, target):
# 为数据增加噪声
random_state = np.random.RandomState(0)
n_samples, n_features = data.shape
data = np.c_[data, random_state.randn(n_samples, 200 * n_features)] # 按行连接数据矩阵和随机数值矩阵
# 用留出法分出训练集和测试集
data_train, data_test, target_train, target_test = train_test_split(data, target, test_size=0.2, random_state=0)
# Learn to predict each class against the other
clf = svm.SVC(kernel='linear', probability=True, random_state=random_state)
# 通过decision_function()计算得到的y_score的值,用在roc_curve()函数中
target_score = clf.fit(data_train, target_train).decision_function(data_test)
# Compute ROC curve and ROC area for each class
fpr, tpr, threshold = roc_curve(target_test, target_score) # 计算真正率和假正率,threshold阈值
roc_auc = auc(fpr, tpr) # 计算auc的值
plt.figure()
lw = 2 # 设置行距
plt.figure(figsize=(10, 10))
plt.plot(fpr, tpr, color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) # 假正率为横坐标,真正率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
print(roc_auc)
结果大概如图,iris数据集样本量不是很大,锯齿感不明显
三、任务三
不是很会,老师也说不做了。
参考文献
1、自助法:https://blog.csdn.net/weixin_43216017/article/details/86707415
2、ROC曲线:https://blog.csdn.net/xyz1584172808/article/details/81839230