前言
无监督学习
使用模型提取图片特征:
self.encoder = nn.Sequential( nn.Conv2d(3, 32, 3, stride=2, padding=1), # 320x180 → 160x90 nn.ReLU(), nn.Conv2d(32, 64, 3, stride=2, padding=1), # 160x90 → 80x45 nn.ReLU(), nn.Conv2d(64, 128, 3, stride=2, padding=1), # 80x45 → 40x23 nn.ReLU(), nn.Conv2d(128, 256, 3, stride=2, padding=1), # 40x23 → 20x12 nn.ReLU() ) self.decoder = nn.Sequential( nn.ConvTranspose2d(256, 128, 3, stride=2, padding=1, output_padding=1), # 20x12 → 40x23 nn.ReLU(), nn.ConvTranspose2d(128, 64, 3, stride=2, padding=1, output_padding=1), # 40x23 → 80x45 nn.ReLU(), nn.ConvTranspose2d(64, 32, 3, stride=2, padding=1, output_padding=1), # 80x45 → 160x90 nn.ReLU(), nn.ConvTranspose2d(32, 3, 3, stride=2, padding=1, output_padding=1), # 160x90 → 320x180 nn.Sigmoid() )
使用DBSCAN进行聚类:
scaler = StandardScaler()features_scaled = scaler.fit_transform(features)dbscan = DBSCAN( eps=eps, min_samples=min_samples, metric='euclidean', n_jobs=-1)
然而对于默认参数eps=0.5, min_samples=5来说,聚类效果并不理想:
INFO:cluster_all_images:聚类结果:INFO:cluster_all_images: 发现的簇数量: 3INFO:cluster_all_images: 噪声点数量: 4677INFO:cluster_all_images: 噪声点比例: 97.62%INFO:cluster_all_images: 簇 0 的样本数: 63 (1.31%)INFO:cluster_all_images: 簇 1 的样本数: 40 (0.83%)INFO:cluster_all_images: 簇 2 的样本数: 11 (0.23%)
绝大部分的图片都被归为了噪声,成簇的样本是一些完全一样的无关紧要的图片:
经过多次尝试,eps=187的时候有比较好的聚类效果:
INFO:cluster_all_images:聚类结果:INFO:cluster_all_images: 发现的簇数量: 85INFO:cluster_all_images: 噪声点数量: 630INFO:cluster_all_images: 噪声点比例: 13.15%INFO:cluster_all_images: 簇 0 的样本数: 13 (0.27%)INFO:cluster_all_images: 簇 1 的样本数: 6 (0.13%)INFO:cluster_all_images: 簇 2 的样本数: 5 (0.10%)INFO:cluster_all_images: 簇 3 的样本数: 157 (3.28%)INFO:cluster_all_images: 簇 4 的样本数: 27 (0.56%)INFO:cluster_all_images: 簇 5 的样本数: 21 (0.44%)···INFO:cluster_all_images: 簇 84 的样本数: 25 (0.52%)
此时模型可以把大部分相似的图片聚类为一个簇:
但是同时也发现了问题:
从状态理解上来说,上面都是处于不需要操作的对话状态,但是因为人物和背景的不同,导致画面有较大差异,被聚类成了两个簇。
另一方面,每次进行聚类分簇的结果是不确定的,模型并不能保证上一次的簇1和下一次的簇1相似,因此聚类后还是需要人工去辨别重新整合分类。
特征可视化
于是编写了一个脚本查看每层的特征图:
可以发现相比于经典的图像识别模型,这个模型几乎没有提取到有用的特征,画面的无关数据与关联数据没有区分开来(也有原因是数据集中我都是用一只马娘训练的)
顺便看了看上个状态识别模型的特征图:
(看归看先用着效果不好再解决)
继续无监督学习
既然一次性分类所有截图不太可能,把两个界面分开感觉还是有希望的。刚好上个状态识别模型中,我把养成主界面和训练主界面放在了一起,这次看看能不能把他们通过无监督学习分开来:
对于这两个界面来说,最大的区别在于下半部分的按钮区域,于是先对数据进行裁剪,只保留不一样的部分:
x, y, w, h = self.training_areaimage = image.crop((x, y, x + w, y + h))
使用Kmeans进行聚类:
with torch.no_grad(): for images, paths in data_loader: images = images.to(device) encoded, _ = model(images) # 展平特征 encoded = encoded.view(encoded.size(0), -1) # [batch_size, 256*20*12] features.extend(encoded.cpu().numpy()) image_paths.extend(paths) features = np.array(features) # [N, 256*20*12] logger.info(f"原始特征维度: {features.shape}") # 移除零方差特征 from sklearn.feature_selection import VarianceThreshold selector = VarianceThreshold(threshold=0.0) features = selector.fit_transform(features) logger.info(f"移除零方差特征后的维度: {features.shape}") # 标准化特征 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() features = scaler.fit_transform(features) # 使用PCA降维 from sklearn.decomposition import PCA # 保留95%的方差 pca = PCA(n_components=0.95) features = pca.fit_transform(features) logger.info(f"PCA降维后的维度: {features.shape}") logger.info(f"保留的方差比例: {sum(pca.explained_variance_ratio_):.3f}") logger.info(f"最终特征值范围: [{features.min():.3f}, {features.max():.3f}]") # 使用更稳定的KMeans参数 from sklearn.cluster import KMeans kmeans = KMeans( n_clusters=2, random_state=42, n_init=20, # 增加初始化次数 max_iter=500, # 增加最大迭代次数 tol=1e-4, # 调整收敛阈值 algorithm='lloyd' # 使用Lloyd算法,更稳定但更慢 )
INFO:cluster_images:原始特征维度: (1198, 61440)INFO:cluster_images:移除零方差特征后的维度: (1198, 51883)INFO:cluster_images:PCA降维后的维度: (1198, 336)INFO:cluster_images:保留的方差比例: 0.950INFO:cluster_images:最终特征值范围: [-290.572, 872.163]INFO:cluster_images:聚类完成,轮廓系数: 0.174INFO:cluster_images:聚类惯性: 49129444.000INFO:cluster_images:类别 0 的样本数: 861INFO:cluster_images:类别 1 的样本数: 337
效果很好,训练界面和养成主界面被完美的区分开了:
这样就可以进一步用标注数据去训练状态识别模型了。
下一步
数据量越来越多导致训练速度逐渐减缓,而因为数据是每秒采集一次的,数据集中有大量重复的图片,下一步准备先对数据集进行一次清洗再改进状态识别模型。