如何用R内置函数实现无监督K近邻查找相似文章
如何用R的class包中监督式的knn函数实现无监督的文章相似性检索?
问题描述
现有一个数据集:
- 行数=1000,每行对应一篇文章
- 列数=502:第1列是文章标题,第2列是文章文本,第3至502列是语料库唯一单词的出现次数
需求是:给定某篇文章的标题,用K近邻算法找出10篇内容最相似的文章。希望使用R的现有函数,已知class包的
knn()是监督学习用的,需要说明怎么把它用到这个无监督场景里。
回答
嘿,这个问题挺巧妙的——把监督式的knn()用到无监督的相似性检索上,其实只需要加个"伪标签"的小技巧就行!下面一步步给你拆解实现过程:
1. 拆分特征与元数据
首先我们要把用于计算相似性的词频特征和标题、文本这类元数据分开,方便后续操作。假设你的数据集叫article_data:
# 提取词频特征矩阵(第3到502列) feature_matrix <- article_data[, 3:502] # 保留标题和文本,后续用来输出相似文章的信息 meta_data <- article_data[, 1:2]
2. 构造伪监督标签
knn()必须要有训练集的类别标签,那我们就给目标文章单独设一个独特标签,其他所有文章设成通用标签。这样knn()就会帮我们找出和目标标签最"像"的样本——也就是内容最相似的文章。
先定位到目标文章的行索引(假设目标标题是target_title):
# 找到目标文章在原数据中的行号 target_idx <- which(meta_data$标题 == target_title) # 创建标签向量:默认都是"others",把目标文章标成"target" labels <- rep("others", nrow(article_data)) labels[target_idx] <- "target"
3. 用knn查找最近邻
这里要注意参数设置:我们把除了目标文章之外的所有样本当训练集,把目标文章当测试集,k设为11(因为会把目标自己算进去,之后要排除掉,刚好剩下10个相似文章)。
library(class) # 训练集:去掉目标文章的特征和标签 train_features <- feature_matrix[-target_idx, ] train_labels <- labels[-target_idx] # 测试集:目标文章的特征(加drop=FALSE保证是矩阵格式) test_feature <- feature_matrix[target_idx, , drop = FALSE] # 运行knn,顺便返回距离信息 nn_result <- knn(train = train_features, test = test_feature, cl = train_labels, k = 11, prob = TRUE)
4. 提取并整理相似文章
knn()返回的结果里,attr(nn_result, "nn.index")会给出训练集中最近邻的索引,我们需要把这些索引映射回原数据集,然后提取对应的标题和文本:
# 获取训练集中最近邻的索引 nn_train_indices <- attr(nn_result, "nn.index")[1, ] # 映射回原数据集的行号(训练集去掉了目标文章,所以要对应回去) nn_original_indices <- (1:nrow(article_data))[-target_idx][nn_train_indices] # 提取前10个相似文章(排除目标自己) similar_articles <- meta_data[nn_original_indices, ] # 加上距离信息并排序(距离越小越相似) similar_articles$distance <- attr(nn_result, "nn.dist")[1, ] similar_articles <- similar_articles[order(similar_articles$distance), ]
关键逻辑说明
为什么这个方法能行?因为knn()的核心是计算样本间的距离,然后选出距离最近的k个样本。我们给目标文章单独设标签,本质上是让knn()完成"找和测试样本(目标文章)距离最近的训练样本"这个任务——这完全就是我们要的无监督相似性检索!另外,如果你觉得欧氏距离不适合文本,还可以通过调整距离计算方式(比如用余弦距离,需要先对特征矩阵做归一化处理)来优化结果。
内容的提问来源于stack exchange,提问作者Aargo




