如何在GPT的帮助下利用Python进行无监督学习?(以INT104的Report3为例)

前文

我们在之前已经在GPT的帮助下用Python完成了数据处理和监督学习,前文链接如下:

如何在GPT的帮助下利用Python实现数据可视化?(以INT104的Report1为例)-CSDN博客

如何在GPT的帮助下利用Python进行监督学习?(以INT104的Report2为例)-CSDN博客

本文会在前文的基础上继续最后一部分使用GPT在Python上对数据进行无监督学习,从而以Report3为例获取所选学生的特征与专业之间的关系。

无监督学习

1.什么是无监督学习?

从上一篇文章的时候我们在学习监督学习的时候其实我们就已经说到了无监督学习,它是不带标签训练数据,这是它和监督学习最大的不同点。无需使用标签训练数据的意义是去发现数据中隐藏的模式、结构和关系。因此对于一组数据我们不了解其背后关系的时候我们可以尝试使用无监督学习,我们要是知道某种关系的时候我们可以使用监督学习。

让我们回到我们的主题,我们想要探究学生的专业与其他特征之间的关系,而无监督学习是在我们对数据不了解的时候尝试发现其关系的,因此这里可能有一些冲突,无监督学习产生的结果可能是某种关系,但不一定是我们需要的关系。这个问题主要是因为我们的原始数据不理想,但这个影响不大,我们依然可以实践一下获取知识,学习到对应的知识才是最重要的。

无监督学习的主要任务包括聚类(clustering),降维(dimensionality reduction)、关联规则学习(association rule learning),但我们只集中于聚类之中。

2.无监督学习的方法

本次Report主要想使用GMM(Gaussian Mixture Model,高斯混合模型),K-means,层次聚类三种方法从而探究背后的关系。

开始实践

3.1 GMM(Gaussian Mixture Model,高斯混合模型)

首先它假设数据是由若干个高斯分布(正态分布)组合而成的,其中每个高斯分布对应于模型中的一个分量,而多个高斯分布的组合形成了混合模型。 

对于聚类问题,确定合适的聚类数量很重要,聚类数量太少就无法达到预期效果,而聚类数量太多就过饱和,你可以想象成每个点是一类,这样分就没有意义了。为了估计合适的聚类数量我们可以使用最大似然估计或者期望最大化(EM)算法去寻找最合适的聚类数量。但其实我们现在是想找专业和其他特征之间的关系,所以我们应该聚的类是专业的类,然后分析为什么这么聚,这样聚出来的关系才是我们需要的关系,因此我们明确了这个聚类数量是4。

同样我们可以使用Silhouette Score(轮廓系数)或者Elbow Method评价模型聚类的质量,从而帮助我们确定聚类的数量。前者是越高代表越好,而后者选择值越来越大但是distortion变化不明显的点(也可以理解为减速趋近于0的点),你可以尝试去输出一下这些值,甚至你可以尝试去输出一下赤池信息准则AIC、贝叶斯信息准则BIC和聚类数量的变化从而选择你认为最好的聚类数量的点。

GMM的概念听上去简单,但里面的算法太复杂,我们其实先看一下接下来的这两种方法的定义从而了解聚类。

K-means是先随机选择几个聚类中心点,哪些点和这些随机选择的点近就将这些化作一类,然后这些点的中心点计算出来,更新的点作为新的聚类中心,将这些类重新划分,哪些点与聚类中心近就归这一类,再次重复上面的过程计算出新的聚类中心,再重新划分,重复上述两步直到聚类中心不再变化为止。

DBSCAN(Density-Based Spatial Clustering of Applications with Noise),它首先规定了两个概念——邻域半径(ε)和最小样本数(minPts),当一个点的邻域内包含至少minPts个点(包括该点自身),则该点被视为核心点。档一个点的邻域内包含少于minPts个点,但落在其他核心点的邻域内,则该点被视为边界点。除了这两个点剩下的点被称为噪声点。

通过这两个方法我们对聚类有了一个初步的了解,再用一点通俗的话来说,如果现在一个平面直角坐标系中,四个象限都有一些点,它们在各自的象限都呈一个圆形分布。我们可以很容易将他们聚成四类。

让我们在回忆一下我们之前PCA的结果。 

那我们现在要聚成四类,上面Programme1、2、4肯定因为混淆在一起无法成功聚成三类,由于现在上下可以看成4个部分,所以我们现在可以想象他是这样聚的四类,他也可能是像我们之前监督学习那样,将下面两个的Programme3单独聚成了一类 ,然后上面两个又聚成两类或三类。如果是这样也能再次证明我们之前的结论是正确的。

所以我想的是将数据降到2维再进行聚类,聚类的结果与之前的结果对照,如果重合率高,那就说明这个降维的划分因素所代表的正是专业和选择的特征的关系。这么做其实也有问题,因为降维会导致数据损失,你也可以尝试先聚类再降维。我这里打算先尝试只将Grade和Total作为特征数据进行聚类,因为我们之前在监督学习中证明Gender不影响Programme,而Total是由MCQ、Q1、Q2、Q3、Q4、Q5组成的,我想尽量让Grade和Total这两个特征不损失,然后将这些先聚类,然后聚类的结果与原来的标签相匹配,如果匹配程度和之前类似,就说明我们Grade和Total确实和Programme有关。

因此我们有了以下代码。

import numpy as np
import pandas as pd
import  seaborn as sns
from matplotlib import pyplot as plt
from sklearn.metrics import silhouette_score
from sklearn.mixture import GaussianMixture

data = pd.read_excel('CW_Data.xlsx')
# 选择用于聚类分析的特征
features = ['Grade', 'Total']

# 选择programme作为标签,用于后续验证聚类结果
labels = data['Programme']

# 选择GMM模型中的聚类中心数量为4
n_components = 4

# 初始化一个GMM模型
gmm = GaussianMixture(n_components=n_components,random_state=21)

# 训练GMM模型
gmm.fit(data[features])

# 使用训练好的模型进行聚类预测
predicted_labels = gmm.predict(data[features])

# 将 GMM 聚类的结果与原生标签合并为一个新的 DataFrame
cluster_result = pd.DataFrame({'predicted_labels': predicted_labels, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
new_labels = cluster_result.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
mapped_labels = new_labels[cluster_result['predicted_labels']].values

# 计算一致性
new_accuracy = np.mean(mapped_labels == labels)
print(f"GMM的聚类结果与原生标签的一致性:{new_accuracy}")
# 计算GMM聚类的轮廓系数
silhouette_avg = silhouette_score(data[features], mapped_labels)
print(f"GMM聚类的轮廓系数为:{silhouette_avg}")

# 将预测的聚类标签添加到数据框中
data['PredictedLabel'] = mapped_labels

# 使用seaborn库绘制散点图
sns.scatterplot(x='Grade', y='Total', hue='Programme', style='PredictedLabel', data=data)
plt.title('Scatter plot of Programme based on Grade and Total')
plt.show()

效果并未像我们预期的一样,他分成了3类,它将其几乎是按成绩分成了两类,效果非常的差。

看来我们还是将PCA的结果交给GMM试一试,看看所有数据都再但是有损失的情况下效果怎么样。

scaler = StandardScaler()
columns = [ "Gender","Grade", "Total", "MCQ", "Q1", "Q2", "Q3", "Q4", "Q5"]
scaled_data = scaler.fit_transform(data[columns])
scaled_data = pd.DataFrame(scaled_data, columns=columns)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(scaled_data)
plt.figure(figsize=(10, 6))
colors = ['red', 'blue', 'green', 'yellow']
cmap = ListedColormap(colors)
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c = data["Programme"], cmap = cmap, alpha=0.8)
plt.title('PCA Visualization')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.legend(handles = scatter.legend_elements()[0], labels = ["1", "2", "3", "4"], loc = "upper right", bbox_to_anchor = (1.13, 1), title = "Programme")
plt.show()


# 将PCA的主成分1和主成分2作为特征
pca_features = X_pca[:, 0:2]

# 初始化一个GMM模型
pca_gmm = GaussianMixture(n_components=11,random_state=21)

# 训练GMM模型
pca_gmm.fit(pca_features)

# 使用训练好的模型进行聚类预测
pca_predicted_labels = pca_gmm.predict(pca_features)

# 将 GMM 聚类的结果与原生标签合并为一个新的 DataFrame
pca_cluster_result = pd.DataFrame({'predicted_labels': pca_predicted_labels, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
pca_new_labels = pca_cluster_result.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
pca_mapped_labels = pca_new_labels[pca_cluster_result['predicted_labels']].values

# 计算调整后的一致性
pca_new_accuracy = np.mean(pca_mapped_labels == labels)
print(f"PCA降维后的GMM聚类结果与原生标签的一致性:{pca_new_accuracy}")
# 计算GMM聚类的轮廓系数
silhouette_avg = silhouette_score(data[features], pca_mapped_labels)
print(f"PCA降维后的GMM聚类的轮廓系数为:{silhouette_avg}")

# 将调整后的聚类标签添加到数据框中
data['PCAPredictedLabel'] = pca_mapped_labels

# 使用seaborn库绘制散点图
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue = data["Programme"], style='PCAPredictedLabel', data=data, palette="viridis", alpha=0.8)
plt.title('Scatter plot of Programme based on PCA Components and GMM Clustering')
plt.show()

根据下面的图你能看出,他将Programme3单独区分了出来,然后上面他将其按左右划分成了Programme1和Programme4,是我们之前监督学习的那种感觉,最后的重合率也和监督学习的accuracy差不多。说明Grade可以帮我们区分Programme3出来,而Total的话可以帮我们稍微区分剩下的1、2、4但是效果不是很好。 如果你上一章节自己尝试以下我最后的集成分类器,将那个结果的混淆矩阵输出出来,你也可以发现所有的Programme2都被错误的划分成了Programme1或Programme4。

3.2 K-means

前文说过K-means的概念,它选择K个初始点作为聚类中心,然后将所有样本点分配到离它们最近的聚类中心所对应的簇中。接着,更新每个簇的中心点为该簇内所有样本点的平均值,重复这个过程直到收敛为止。最终,得到K个簇,使得每个样本点都被分配到一个簇中。

我们试着将原始数据和PCA都代入模型试一试。

# 选择K-Means模型中的聚类中心数量为4
n_clusters = 4

# 初始化一个K-Means模型
kmeans = KMeans(n_clusters=n_clusters, n_init=10)

# 训练K-Means模型
kmeans.fit(data[features])

# 使用训练好的模型进行聚类预测
predicted_labels_kmeans = kmeans.predict(data[features])

# 将 K-Means 聚类的结果与原生标签合并为一个新的 DataFrame
cluster_result_kmeans = pd.DataFrame({'predicted_labels': predicted_labels_kmeans, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
new_labels_kmeans = cluster_result_kmeans.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
mapped_labels_kmeans = new_labels_kmeans[cluster_result_kmeans['predicted_labels']].values

# 计算一致性
new_accuracy_kmeans = np.mean(mapped_labels_kmeans == labels)
print(f"K-Means聚类结果与原生标签的一致性:{new_accuracy_kmeans}")

# 计算K-Means聚类的轮廓系数
silhouette_avg_kmeans = silhouette_score(data[features], mapped_labels_kmeans)
print(f"K-Means聚类的轮廓系数为:{silhouette_avg_kmeans}")

# 将预测的聚类标签添加到数据框中
data['PredictedLabel'] = mapped_labels_kmeans

# 使用seaborn库绘制散点图
sns.scatterplot(x='Grade', y='Total', hue='Programme', style='PredictedLabel', data=data)
plt.title('Scatter plot of Programme based on Grade and Total')
plt.show()

# 计算每个簇中不同Programme值的分布情况
cluster_programme_distribution = data.groupby('PredictedLabel')['Programme'].value_counts().unstack(fill_value=0)

# 使用柱状图可视化簇与Programme的关系
cluster_programme_distribution.plot(kind='bar', stacked=True, figsize=(10, 6))
plt.title('Cluster vs Programme Distribution')
plt.xlabel('Cluster')
plt.ylabel('Count')
plt.show()
# 将PCA的主成分1和主成分2作为特征
pca_features = X_pca[:, 0:2]

# 初始化一个KMeans模型
kmeans_pca = KMeans(n_clusters=n_clusters, n_init=10)

# 训练KMeans模型
kmeans_pca.fit(pca_features)

# 使用训练好的模型进行聚类预测
kmeans_pca_predicted_labels = kmeans_pca.predict(pca_features)

# 将 KMeans 聚类的结果与原生标签合并为一个新的 DataFrame
kmeans_pca_cluster_result = pd.DataFrame({'predicted_labels': kmeans_pca_predicted_labels, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
kmeans_pca_new_labels = kmeans_pca_cluster_result.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
kmeans_pca_mapped_labels = kmeans_pca_new_labels[kmeans_pca_cluster_result['predicted_labels']].values

# 计算调整后的一致性
kmeans_pca_new_accuracy = np.mean(kmeans_pca_mapped_labels == labels)
print(f"PCA降维后的KMeans聚类结果与原生标签的一致性:{kmeans_pca_new_accuracy}")

# 计算KMeans聚类的轮廓系数
silhouette_avg_kmeans = silhouette_score(data[features], kmeans_pca_mapped_labels)
print(f"PCA降维后的KMeans聚类的轮廓系数为:{silhouette_avg_kmeans}")

# 将调整后的聚类标签添加到数据框中
data['PCAKMeansPredictedLabel'] = kmeans_pca_mapped_labels

# 使用seaborn库绘制散点图
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue = data["Programme"], style='PCAKMeansPredictedLabel', data=data, palette="viridis", alpha=0.8)
plt.title('Scatter plot of Programme based on PCA Components and KMeans Clustering')
plt.show()

这里还尝试使用柱状图展现每个聚类的结果。

当然我们还可以将这个结果生成一个热力图去查看情况。

从这张图我们也可以看出当我们使用原始数据Grade和Total的时候我们得出的重合率其实比较低,但是我们使用PCA后的时候得出的结果会变好,这可能是我们使用PCA的时候刚好丢失了那些与Programme无关的数据。但如果我们对比这里K-means最后的轮廓系数和前面GMM的轮廓系数,我们会发现K-means的轮廓系数在PCA的帮助下上升了,而GMM的轮廓系数在PCA的作用下下降了,这一点很有趣。这可能是数据更偏向于明显分离的情况。因此K-means更好,而GMM更适合处理混合分布的数据。

其实我们观察这几张热力图的分布,我们可以发现有些Programme4的学生被错误地划分到Programme1中,同理有些Programme1的学生被错误地划分到Programme4中,而这两个占比几乎类似。其实我们在之前监督学习的时候也提过,Programme1的学生整体成绩比Programme4好,但这群学生中有不符合这个表现的,这个比例刚好和Programme4中学习成绩好的比例类似。而Programme2更多地被认成了Programme4也说明Programme2的成绩更像Programme4,也就是Programme2和Programme4的学习成绩表现比Programme1差一些,这与我们之前监督学习的结果近似。同样我们刚刚的一个专业中不符合大多数人的学习表现的概率在各个专业中的比例是近似的也可以在Programme2中找到证明。

3.3 层次聚类

首先层次聚类有两种类型:凝聚(agglomerative)层次聚类和分裂(divisive)层次聚类,前者从单个样本点开始,逐渐合并相邻的簇,直到所有样本点都被合并到一个簇为止。这种方法从下往上构建聚类结构,最终形成一个包含所有样本点的簇;后者从包含所有样本点的簇开始,逐渐将簇分裂成更小的子簇,直到每个子簇只包含一个样本点为止。这种方法从上往下构建聚类结构,最终形成每个样本点都是一个簇的情况。我们常用前者这种从下而上的方式进行聚类。

详细操作不理解的可以查看这个视频:【10分钟算法】层次聚类之最近邻算法-带例子/Nearest Neighbor Algorithm for hierarchical clustering_哔哩哔哩_bilibili

他的聚类结果可以呈现为一个类似树状图的结果,但是我们的数据很大我觉得这样生成的图我们完全无法解读,所以生成这样的图没有什么意义。 这里我们还是按照前面的思路继续。

# 选择分层聚类的聚类簇数量
n_clusters = 4

# 初始化一个分层聚类模型
hierarchical = AgglomerativeClustering(n_clusters=n_clusters)

# 训练分层聚类模型
hierarchical.fit(data[features])

# 使用训练好的模型进行聚类预测
predicted_labels_hierarchical = hierarchical.labels_

# 将分层聚类的结果与原生标签合并为一个新的 DataFrame
cluster_result_hierarchical = pd.DataFrame({'predicted_labels': predicted_labels_hierarchical, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
new_labels_hierarchical = cluster_result_hierarchical.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
mapped_labels_hierarchical = new_labels_hierarchical[cluster_result_hierarchical['predicted_labels']].values

# 计算一致性
new_accuracy_hierarchical = np.mean(mapped_labels_hierarchical == labels)
print(f"分层聚类结果与原生标签的一致性:{new_accuracy_hierarchical}")

# 计算分层聚类的轮廓系数
silhouette_avg_hierarchical = silhouette_score(data[features], mapped_labels_hierarchical)
print(f"分层聚类的轮廓系数为:{silhouette_avg_hierarchical}")

# 将预测的聚类标签添加到数据框中
data['PredictedLabel'] = mapped_labels_hierarchical

# 使用seaborn库绘制散点图
sns.scatterplot(x='Grade', y='Total', hue='Programme', style='PredictedLabel', data=data)
plt.title('Scatter plot of Programme based on Grade and Total')
plt.show()
# 将PCA的主成分1和主成分2作为特征
pca_features = X_pca[:, 0:2]

# 初始化一个分层聚类模型
hierarchical_pca = AgglomerativeClustering(n_clusters=n_clusters)

# 训练分层聚类模型
hierarchical_pca.fit(pca_features)

# 使用训练好的模型进行聚类预测
hierarchical_pca_predicted_labels = hierarchical_pca.labels_

# 将分层聚类的结果与原生标签合并为一个新的 DataFrame
hierarchical_pca_cluster_result = pd.DataFrame({'predicted_labels': hierarchical_pca_predicted_labels, 'true_labels': labels})

# 对每个聚类进行统计,取多数原生标签作为新标签
hierarchical_pca_new_labels = hierarchical_pca_cluster_result.groupby('predicted_labels')['true_labels'].agg(lambda x: x.value_counts().idxmax())

# 将新标签与聚类结果进行对应
hierarchical_pca_mapped_labels = hierarchical_pca_new_labels[hierarchical_pca_cluster_result['predicted_labels']].values

# 计算调整后的一致性
hierarchical_pca_new_accuracy = np.mean(hierarchical_pca_mapped_labels == labels)
print(f"PCA降维后的分层聚类结果与原生标签的一致性:{hierarchical_pca_new_accuracy}")

# 计算分层聚类的轮廓系数
silhouette_avg_hierarchical = silhouette_score(pca_features, hierarchical_pca_mapped_labels)
print(f"PCA降维后的分层聚类的轮廓系数为:{silhouette_avg_hierarchical}")

# 将调整后的聚类标签添加到数据框中
data['PCAHierarchicalPredictedLabel'] = hierarchical_pca_mapped_labels

# 使用seaborn库绘制散点图
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue = data["Programme"], style='PCAHierarchicalPredictedLabel', data=data, palette="viridis", alpha=0.8)
plt.title('Scatter plot of Programme based on PCA Components and Hierarchical Clustering')
plt.show()

我们发现结果是原始数据分成了3组,而PCA数据我们分成了2组,我们可以修改linkage为complete,如下,另一处同理。

hierarchical = AgglomerativeClustering(n_clusters=n_clusters, linkage='complete')

最后的结果与我们之前的类似。 

你也可以尝试其他linkage,也可以将最后的分布情况可视化出来分析背后的原因。

因此我们最后的结论是由于数据的原因我们没有非常明确的专业和其他特征之间的关系,但是我们获得了以下两个结论:1.Programme3的年级比另外3个专业更高;2.Programme1的整体成绩表现比Programme2和Programme4更高。而Programme2和Programme4之间数据表现类似是我们无法进一步探究的主要原因。

其他问题

1. 关于Report

这次Report可能会让人比较困惑和误解,因为我们在前面监督学习的时候我们就很难将Programme之间分开了,而这种数据相互交融在一起的情况我们很难使用聚类得出我们想要的结论。但是没有关系,课程设计主要是想让大家了解人工智能的基本知识,我们要学会区分监督学习和无监督学习的不同,知道什么情况下该使用监督学习,什么情况下该使用无监督学习,学到知识,写出逻辑自洽能展现自己构思的Report才是最重要的。

2. 总结

我们通过三篇文章教会大家在GPT的帮助下使用Python完成数据可视化、监督学习、无监督学习。其中我们有的人可能未来不从事AI方面的研究,但其实比如数据可视化是很多我们写论文时候展现数据很重要的方式,它在数据分析中是很重要的一种手段。我在三篇文章的很多地方都使用了数据可视化,它可以让我们更清晰地表现数据帮助我们更好的理解里面的内容,展现我们想展现的内容,以及我们未来很多时候可能还会跟GMM或者朴素贝叶斯在其他领域打交道。比如人机交互领域会涉及到GMM,而做一些词汇量之类的这种类型的测试后面也可能是朴素贝叶斯的原理。因此你也可以发现这门功课的知识是相当重要的,希望你能从这三篇文章中有所收获。

猜你喜欢

转载自blog.csdn.net/sensen_kiss/article/details/142695033