AI玩游戏的一点尝试(2)—— 初探无监督学习与特征可视化

60 阅读5分钟

前言

AI玩游戏的一点尝试(1)—— 架构设计与初步状态识别

无监督学习

书接上回准备用无监督学习自动对画面状态进行分类,先准备模型。

使用模型提取图片特征:

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:  发现的簇数量: 3
INFO:cluster_all_images:  噪声点数量: 4677
INFO: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%)

绝大部分的图片都被归为了噪声,成簇的样本是一些完全一样的无关紧要的图片:

image.png

image.png

经过多次尝试,eps=187的时候有比较好的聚类效果:

INFO:cluster_all_images:聚类结果:
INFO:cluster_all_images:  发现的簇数量: 85
INFO:cluster_all_images:  噪声点数量: 630
INFO: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%)

此时模型可以把大部分相似的图片聚类为一个簇:

image.png

image.png

但是同时也发现了问题:

image.png

image.png

从状态理解上来说,上面都是处于不需要操作的对话状态,但是因为人物和背景的不同,导致画面有较大差异,被聚类成了两个簇。

另一方面,每次进行聚类分簇的结果是不确定的,模型并不能保证上一次的簇1和下一次的簇1相似,因此聚类后还是需要人工去辨别重新整合分类。

特征可视化

于是编写了一个脚本查看每层的特征图:

layer_1_features.png

layer_2_features.png

layer_3_features.png

layer_4_features.png

可以发现相比于经典的图像识别模型,这个模型几乎没有提取到有用的特征,画面的无关数据与关联数据没有区分开来(也有原因是数据集中我都是用一只马娘训练的)

顺便看了看上个状态识别模型的特征图:

conv1.png

conv2.png

conv3.png

(看归看先用着效果不好再解决)

继续无监督学习

既然一次性分类所有截图不太可能,把两个界面分开感觉还是有希望的。刚好上个状态识别模型中,我把养成主界面和训练主界面放在了一起,这次看看能不能把他们通过无监督学习分开来:

image.png

对于这两个界面来说,最大的区别在于下半部分的按钮区域,于是先对数据进行裁剪,只保留不一样的部分:

x, y, w, h = self.training_area
image = 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.950
INFO:cluster_images:最终特征值范围: [-290.572, 872.163]
INFO:cluster_images:聚类完成,轮廓系数: 0.174
INFO:cluster_images:聚类惯性: 49129444.000
INFO:cluster_images:类别 0 的样本数: 861
INFO:cluster_images:类别 1 的样本数: 337

效果很好,训练界面和养成主界面被完美的区分开了:

image.png

image.png

这样就可以进一步用标注数据去训练状态识别模型了。

下一步

数据量越来越多导致训练速度逐渐减缓,而因为数据是每秒采集一次的,数据集中有大量重复的图片,下一步准备先对数据集进行一次清洗再改进状态识别模型。