期末要求和数据集
本篇文章是基于iris鸢尾花数据集进行探究,该数据集也是最适合数据分析的基本数据
https://pan.baidu.com/s/1HPvQM3DuyzBgAq7g3O3TBg?pwd=awsm 提取码: awsm
注意:代码文件和数据集是放在一个目录下的
参考代码
导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import graphviz
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.cluster import KMeans
from sklearn.neighbors import KNeighborsClassifier
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score,recall_score,precision_score
导入Iris数据集
iris_data = pd.read_csv("./iris.csv")
iris_data.head()
查看Iris数据集的维度、特征名称和特征类型
print(f"数据集的维度:{iris_data.shape}")
#标签不是特征
print(f"特征名称:{iris_data.columns[:-1]}")
print("特征类型:\n",iris_data.dtypes)
打印所有列名
iris_data.columns
显示Iris数据集的基本统计学特性,如未缺失值的数值、均值、标准差、范围、四分位数等
#查看特征信息、未缺失的个数
iris_data.info()
#查看缺失值的个数
iris_data.isnull().sum()
#查看均值、标准差、范围、四分位
'''
count:个数
mean:每个特征的平均值
std:每个特征的标准差
min:每个特征的最小值
25%:下四分位数
50%:中位数
75%:上四分位数
max:每个特征的最大值
'''
iris_data.describe()
输出数据集 0 至10 行、1至3列 的数据
iris_data.iloc[:11,:3]
输出1至3列最大值
#axis=0表示找列的最大值,axis=0表示找行的最大值
iris_data.iloc[:,:3].max(axis=0)
根据class属性的值对数据进行分组,显示每组的统计数据,并绘制直方图
#按class列分组
groups = iris_data.groupby('class')
print(groups.groups)
#统计数据
for key in iris_data.columns[:-1]:
print(f"每组关于{key}的统计数据")
print(groups.describe()[key])
#统计数据也可以写成一下形式,只不过显示的信息过多,中间部分被省略
# groups.describe()
#直方图,每组个数
plt.figure(figsize=(6,4))
plt.subplot(1,1,1)
plt1 = iris_data['class'].value_counts().plot(kind='bar')
plt.show()
fig=plt.figure(figsize=(20,7))
for i,col in enumerate(iris_data.columns[:-1]):
fig=px.histogram(data_frame=iris_data, x=col,color='class',color_discrete_sequence=['#491D8B','#7D3AC1','#EB548C'],nbins=30)
fig.show()
#也可以通过下面方式画出每组在每个区间的直方图
plt.figure(figsize=(20,7))
for i,col in enumerate(iris_data.columns[:-1]):
plt.subplot(2,2,i+1)
sns.histplot(x=iris_data[col],hue=iris_data['class'],stat="count",multiple="dodge")
plt.show()
将Iris数据集中的数值属性进行Z-Score归一化。
z-score是使得处理过后的数据符合正态分布,公式如下:
其中代表均值,
代表标准差,代码如下:
x = iris_data.drop('class',axis=1)
y = iris_data['class']
def z_score(X):
means = X.mean(axis=0)
stds = X.std(axis=0)
return (X - means) / stds
x = z_score(x)
print(x)
当然,这里你也可以使用导包的方式标准化,代码如下:
#也可以使用sklearn的StandardScaler包进行z_score归一化
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
x = sc.fit_transform(x)
print(x)
利用k-means算法完成Iris数据集的聚类,并输出聚类结果的正确率和召回率
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.2,random_state=1)
1、直接使用sklearn中的包
kmeans = KMeans(n_clusters=3,random_state=0)
kmeans.fit(x_train) #训练模型
labels = kmeans.labels_ #获取聚类标签
print(labels)
#四维的图形无法画出,这里使用sepal length和sepal width来绘图
def plotData():
X = x_train.values
plt.scatter(X[:,0],X[:,1],c=labels)
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.title("Clusters Plot")
plt.show()
plotData()
2、手写Kmeans聚类算法
步骤:
- 随机初始簇中心(一般随机选择样本中的几个点作为初始簇中心)
- 利用欧式距离,计算每个样本点到簇中心的距离,离哪个簇中心最近就将其归属于该簇
- 分好簇后,求每个簇的特征均值作为新的簇中心
- 直到簇中心不再发生变化,就停止算法
1、计算欧式距离
#计算欧式距离
def compute_distance(x,y):
return np.sqrt(np.sum((x - y) ** 2))
2、计算每个样本到簇中心的距离,返回每个样本对应的簇的索引列表
def find_closet_centroids(X,centroids):
#
m = X.shape[0]
idx = np.zeros(m,dtype=int)
#簇中心的个数
k = len(centroids)
for i in range(m):
min_dist = 1000000.
#遍历每个簇中心,找出距离最短的簇中心
for j in range(k):
dist = compute_distance(X[i],centroids[j])
if dist < min_dist:
min_dist = dist
idx[i] = j #属于第几个簇
return idx
3、分好簇后,更新簇中心
#均值作为分类好的簇将会重新更新簇中心
def compute_centroid(X,idx,K):
n = X.shape[1]
centroids = np.zeros((K,n))
for j in range(K):
#找到每一个簇的样本
Xi = X[np.where(idx == j)]
m = len(Xi)
centroids[j] = np.mean(Xi,axis=0)
return centroids
4、画图
def plot_KMean(X,centroids,idx):
plt.scatter(X[:,0],X[:,1],c=idx,marker='+',cmap='rainbow')
plt.scatter(centroids[:,0],centroids[:,1],s=80,marker='o',c='b')
for i in range(len(centroids)):
plt.annotate('center',xy=(centroids[i,0],centroids[i,1]),xytext=\
(centroids[i,0]+0.5,centroids[i,1]+1),arrowprops=dict(facecolor='yellow'))
5、kmeans聚类算法
def run_Kmean(X,K,max_iters=10):
m,n = X.shape
#随机打乱选取簇中心
rand = np.random.permutation(X.shape[0])
centroids = X[rand[:K]]
idx = np.zeros(m)
for i in range(max_iters):
idx = find_closet_centroids(X,centroids)
centroids = compute_centroid(X,idx,K)
return centroids,idx
6、运行
X_train = x_train.values
Y_train = y_train.values
X_test = x_test.values
Y_test = y_test.values
K = 3
max_iters = 10
centroids,idx = run_Kmean(X_train,K,max_iters)
plot_KMean(X_train,centroids,idx)
运行结果如下:
3、 准确率和召回率
说实话,我也不明白这里为什么计算准确率,因为Kmeans聚类是无监督学习,没有真实标签参考,可能这里给出了真实标签。好吧,对于这部分我现在也一头雾水,大概说下我的思路吧:
- 从已知的训练集中各找出一个类别不同的样本点
- 计算这几个点到最后我们求得的簇中心的距离
- 离簇中心最近的样本点,就是该簇中心的标签
- 测试集分别计算到簇中心的距离,将其分配给距离最近的簇
- 然后计算精确率和召回率
代码如下:
#这里可能有不足之处,那就是以下的代码我假设了聚类的效果为最佳,这其实是不符合现实条件的
label = ['setosa','versicolor','virginica']
targets = []
for i in centroids[:-1]:
min_dist = 100000
target = -1
for l in label:
xl = X_train[np.where(y_train == l)][0]
dist = compute_distance(xl,i)
if dist < min_dist:
min_dist = dist
target = l
targets.append(target)
label.remove(target)
targets.append(label[0])
print(targets)
def predict(X,centroids,targets):
m = len(X)
cen = len(centroids)
pred = []
#遍历每一个测试样本
for i in range(m):
#有三个簇中心,计算到每个簇中心的距离
dists = np.zeros(cen)
for j in range(cen):
dists[j] = compute_distance(X[i],centroids[j])
#找到最小的距离
index = np.argmin(dists)
#index表示距离最小的索引
pred.append(targets[index])
return pred
predicts = predict(X_test,centroids,targets)
def score(Y,predicts):
precision = 0
accuracy = 0
recall = 0
#遍历每一个类别,计算每一个的精确率、召回率
for p in np.unique(Y):
#构造每一个类别的混淆矩阵
confusion_metric = np.zeros((2,2))
for i in range(len(predicts)):
pred = predicts[i]
if Y[i] == p and pred == p:
confusion_metric[0][0] += 1
if Y[i] != p and pred == p:
confusion_metric[0][1] += 1
if Y[i] == p and pred != p:
confusion_metric[1][0] += 1
if Y[i] != p and pred !=p:
confusion_metric[1][1] += 1
# print(confusion_metric)
#对照上面的混淆矩阵即可
TP = confusion_metric[0][0]
FP = confusion_metric[0][1]
FN = confusion_metric[1][0]
TN = confusion_metric[1][1]
precision += TP / (TP + FP)
accuracy += (TP + TN) / (TP + FP + FN + TN)
recall += TP / (TP + FN)
return precision,accuracy,recall
precision,accuracy,recall = score(Y_test,predicts)
print(f"Kmeans精确率:{precision / 3:.3f}")
print(f"Kmeans准确率:{accuracy / 3:.3f}")
print(f"Kmeans召回率:{recall / 3:.3f}")
利用相关决策树算法构建Iris数据集的决策树并图形化显示。输出测试集分类结果的正确率和召回率
这里依旧有两种方式可选
1、调用sklearn.tree的包
clf = DecisionTreeClassifier()
#使用网格化搜索,找到最优参数
params = {'criterion':['gini','entropy'],'max_depth':np.arange(2,10)}
grid = GridSearchCV(clf,param_grid=params,cv=3).fit(x_train,y_train)
print("the best params for DecisionTree is:",grid.best_params_,",the score is:",grid.best_score_)
计算准确率和召回率
clf = DecisionTreeClassifier(criterion='gini',max_depth=3)
clf.fit(x_train,y_train)
#预测测试集结果
y_pred = clf.predict(x_test)
print("精确率:%.3f"%precision_score(y_test,y_pred,average='micro'))
print("准确率:%.3f"%accuracy_score(y_test,y_pred))
print("召回率:%.3f"%recall_score(y_test,y_pred,average='micro'))
画出树,调用tree.plot_tree
plt.figure(figsize=(12,10))
tree.plot_tree(clf.fit(x_train,y_train),
feature_names=x_train.columns,
class_names=np.unique(y_train))
也可以画彩色的,代码如下:
dot_data = tree.export_graphviz(clf,out_file=None,
feature_names=x_train.columns,
class_names=np.unique(y_train),
filled=True,
rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
运行效果:
检查是否欠拟合和过拟合
- 欠拟合:训练集和测试集的得分都不高
- 过拟合:在训练集上表现很好,但是测试集表现很差,即训练集的得分远高于测试集的得分
#检查是否欠拟合或者过拟合
print(f"Training set score: {clf.score(x_train,y_train)}")
print(f"Test set score: {clf.score(x_test,y_test)}")
#运行结果
#Training set score: 0.9833333333333333
#Test set score: 0.9666666666666667
2、手写决策树(以CART决策树为例)
CART决策树采用Gini系数来选择最优的特征划分
Gini系数:
Gini系数越小越好,因为越大,说明该样本的纯度越高
步骤
- 计算Gini系数
- 遍历每个特征,计算
找到Gini系数最小的最优划分特征。对于连续型的处理,我们通常选择一个阈值来划分,这个阈值的选取是:先对该特征下的取值进行排序,选择前后每两个点的平均值作为划分阈值的备选
- 找到最优分类特征后,重复1.、2.步骤,递归构建左右子树
停止划分的条件:- 树的深度达到最大深度
- 叶子节点的样本个数小于某个阈值
- Gini系数的变化小于某个阈值,也就是变化不大的时候
1、Gini系数
#计算Gini系数
def compute_gini(X,y):
m = len(X)
Labelcnt = {}
for j in y:
if j not in Labelcnt:
#这里要赋值为1
Labelcnt[j] = 1
else:
Labelcnt[j] += 1
for key in Labelcnt.keys():
#计算每个类别的概率
Labelcnt[key] = (Labelcnt[key] / m) ** 2
gini = 1 - sum(Labelcnt.values())
return gini
2、生成左右子树
#按特征分成左右子树,这里的特征取值是连续的,所以采取排序取两数之间的平均值作为划分
#X:样本 index:特征的索引 value:划分的阈值
def split_dataset(X,index,value):
#存放左右子树
left_indices = []
right_indices = []
data = X[:,index]
for i in range(len(X)):
if data[i] <= value:
left_indices.append(i)
else:
right_indices.append(i)
return left_indices,right_indices
3、划分子树后,利用加权平均计算Gini系数
def compute_gini_DA(X,y,node_indices,value):
left_indices,right_indices = split_dataset(X,node_indices,value)
x_left,y_left = X[left_indices],y[left_indices]
x_right,y_right = X[right_indices],y[right_indices]
D = len(X)
w_left = len(left_indices) / D
w_right = len(right_indices) / D
gini_left = compute_gini(x_left,y_left)
gini_right = compute_gini(x_right,y_right)
giniDA = w_left * gini_left + w_right * gini_right
return giniDA
4、获取最优划分特征
def get_best_split(X,y):
features = len(X[0])
#如果特征只有一个,就返回该特征的索引,也就是0
if features == 1:
return 0
#初始化Gini系数和最优特征
best_gini = 100000
best_feature = -1
best_value = -1
for j in range(features):
feature = sorted(X[:,j])
#作为该特征下的划分条件备选,保留三位小数
values = [round((feature[i] + feature[i+1]) / 2,3) for i in range(len(feature) - 1)]
for value in values:
gini = compute_gini_DA(X,y,j,value)
if gini < best_gini:
best_gini = gini
best_feature = j
best_value = value
return best_feature,best_value
5、建树(递归)
from collections import Counter
def build_tree(X,y,feature,max_depth,cur_depth):
if max_depth == cur_depth or len(set(y)) == 1:
return y[0]
if len(X[0]) == 1:
collection = Counter(y)
return collection.most_common(1)[0]
best_findex,best_value = get_best_split(X,y)
best_feature = feature[best_findex]
tree = {best_feature:{}}
#递归左右子树
left_indices,right_indice = split_dataset(X,best_findex,best_value)
tree[best_feature][best_value] = build_tree(X[left_indices],y[left_indices],feature,max_depth,cur_depth+1)
tree[best_feature]["other"] = build_tree(X[right_indice],y[right_indice],feature,max_depth,cur_depth+1)
return tree
feature = ['sepal length','sepal width','petal length','petal width']
decision_tree = build_tree(X_train,Y_train,feature,max_depth=3,cur_depth=0)
print(decision_tree)
6、预测结果,同样使用递归找到对应叶子节点所属标签
#预测测试集结果
def classify(decision_tree, features, test):
# 根节点代表的属性
first_feature = list(decision_tree.keys())[0]
second_dict = decision_tree[first_feature]
index_of_first_feature = features.index(first_feature)
for key in second_dict.keys():
# 不等于'others'的key
if key != "other":
if test[index_of_first_feature] <= key:
if type(second_dict[key]).__name__ == 'dict':
# 需要递归查询
classLabel = classify(second_dict[key], features, test)
# 若当前second_dict的key的value是一个单独的值,返回该标签
else:
classLabel = second_dict[key]
# 如果测试样本在当前特征的取值不等于key,就说明它在当前特征的取值属于'others'
else:
# 如果second_dict['others']的值是个字符串,则直接输出
if isinstance(second_dict['other'],str):
classLabel = second_dict['other']
# 如果second_dict['others']的值是个字典,则递归查询
else:
classLabel = classify(second_dict['other'], features, test)
return classLabel
7、计算准确率和召回率
#预测测试集
labels = []
for i in range(len(x_test)):
labels.append(classify(decision_tree,feature,X_test[i]))
#利用测试集计算精确率、召回率
#因为是多分类问题,采用宏F1,计算出每个混淆矩阵的精确率和召回率,取平均,再计算F1-score
precision,accuracy,recall = score(Y_test,labels)
precision /= 3
accuracy /= 3
recall /= 3
F1_score = (2 * precision * recall) / (precision + recall)
print(f"精确率:{precision:.3f}")
print(f"准确率:{accuracy:.3f}")
print(f"召回率:{recall:.3f}")
print(f"F1 score:{F1_score:.3f}")
8、检查是否过拟合
#查看是否过拟合,如果训练集和测试集的得分相差不大,则不会过拟合
def compute_score(labels,y_true):
cnt = 0
for i in range(len(labels)):
if labels[i] == y_true[i]:
cnt += 1
return cnt / len(labels)
train_labels = []
for i in range(len(x_train)):
train_labels.append(classify(decision_tree,feature,X_train[i]))
print("训练集上的得分:",compute_score(train_labels,Y_train))
print("测试集上的得分:",compute_score(labels,Y_test))
#运行结果:
#训练集上的得分: 0.9833333333333333
#测试集上的得分: 0.9666666666666667
#结论:未过拟合
以上就是这次期末作业的全部内容,哈哈,有些小累。希望读者能够从文章中取得收获,也欢迎读者的指出不足之处!