如何在这个简单的数据集中找到最大的集群?
Posted
技术标签:
【中文标题】如何在这个简单的数据集中找到最大的集群?【英文标题】:How do I find the largest cluster in this simple dataset? 【发布时间】:2021-12-22 16:18:49 【问题描述】:我有关于用户及其兴趣的数据。一些用户比其他用户有更多的兴趣。数据如下所示。
如何找到具有最多共同兴趣的最大用户群?形式上,我试图最大化(集群中的用户数 * 集群中的共享兴趣数)
在下面的数据中,最大的簇是:
正确答案
用户:[1,2,3]
兴趣:[2,3]
集群价值:3 个用户 x 2 个共同兴趣 = 6
数据
用户 1:3,2
用户 2:3,2,4
用户 3:2,3,8
用户 4:7
用户 5:7
用户 6:9
如何找到最有共同兴趣的最大用户群?
这是一个假设的数据生成过程:
import random
# Generate 300 random (user, interest) tupples
def generate_data():
data = []
while len(data) < 300:
data_pt = "user": random.randint(1,100), "interest":random.randint(50)
if data_pt not in data:
data.append(data_pt)
return data
def largest_cluster(data):
return None
更新:正如有人指出的那样,数据过于解析。在实际情况下,用户会多于兴趣。所以我更新了数据生成过程。
【问题讨论】:
刚刚更新了! 代码 sn-p 生成的假设数据非常稀疏(10000 个可能的元组中的 300 个元组),以至于答案几乎总是 1 个最感兴趣的人,或 1 个由大多数人共享的兴趣. 这个问题真的很有趣,但由于一般问题是 NP 难的,任何合理的答案都将在很大程度上取决于数据的确切分布。你有多少用户,你总共有多少不同的兴趣,每个用户大约有多少兴趣? 【参考方案1】:在我看来,这类似于属于 NP-Hard 复杂性类别的组合优化问题,这当然意味着很难为超过 30 个用户的实例找到精确的解决方案。
如果您要为具有这样的指数搜索空间的问题找到一个可用的算法(这里的解决方案空间是所有 2^n 个用户子集),动态编程将是您想要使用的工具,但我由于缺乏重叠的子问题,在这里看不到 DP 帮助我们。也就是说,要让 DP 提供帮助,我们必须能够在多项式时间内使用较小子问题的解决方案并将其组合成一个整体解决方案,而我不知道我们该如何解决这个问题。
假设您有一个 size=k 问题的解决方案,使用有限的用户子集 u1, u2,...uk,并且您希望在添加另一个用户 u 时使用该解决方案来找到新的解决方案(k+1)。问题是在增量更大的实例中设置的解决方案可能与之前的解决方案完全不重叠(它可能是完全不同的用户/兴趣组),因此我们无法有效地将子问题的解决方案组合起来以获得整体解决方案。如果不尝试仅使用大小 k 问题的单一最优解来推理大小 k+1 问题,而是存储来自较小实例的所有可能的用户组合及其分数,那么您当然可以很容易地进行设置将这些组的兴趣与新用户的兴趣相交,以找到新的最优解。但是,这种方法的问题当然是您必须存储的信息会随着迭代而加倍,产生的指数时间算法并不比蛮力解决方案好。如果您尝试将您的 DP 建立在逐步添加兴趣而不是用户的基础上,您会遇到类似的问题。
因此,如果您知道自己只有少数用户,则可以使用蛮力方法:生成所有用户组合,获取每个组合兴趣的集合交集,评分并保存最高分。处理较大实例的最佳方法可能是通过搜索算法使用近似解决方案(除非有我看不到的 DP 解决方案)。您可以迭代地添加/减去/交换用户以提高分数并朝着最佳方向攀升,或者使用分支定界算法系统地探索所有用户组合但停止探索任何具有零兴趣交集的用户子集分支(如添加该子集的其他用户仍将产生空交集)。您可能有很多具有零兴趣交叉点的用户组,因此后一种方法实际上可以通过修剪大部分搜索空间来相当快,如果您在没有深度限制的情况下运行它,它最终会找到确切的解决方案.
Branch-and-bound 会像这样工作:
def getLargestCluster((user, interest)[]):
userInterestDict := user -> set of user's interests # build a dict
# generate and score user clusters
users := userInterestDict.keys() # save list of users to iterate over
bestCluster, bestInterests, bestClusterScore := , , 0
generateClusterScores()
return [bestCluster, bestInterests bestClusterScore]
# (define locally in getLargestCluster or pass needed values
def generateClusterScores(i = 0, userCluster = , clusterInterests = ):
curScore := userCluster.size * clusterInterests.size
if curScore > bestScore:
bestScore, bestCluster, bestInterests := curScore, curCluster, clusterInterests
if i = users.length: return
curUser := users[i]
curInterests := userInterestDict[curUser]
newClusterInterests := userCluster.size = 0 ? curInterests : setIntersection(clusterInterests, curInterests)
# generate rest subsets with and without curUser (copy userCluster if pass by reference)
generateClusterScores(i+1, userCluster, clusterInterests)
if !newClusterInterests.isEmpty(): # bound the search here
generateClusterScores(i+1, userCluster.add(curUser), newClusterInterests)
您也许可以做一个更复杂的边界(例如,如果您可以计算出当前的集群分数不能超过您当前的最佳分数,即使所有剩余的用户都已添加到集群中并且兴趣交叉点保持在同样),但检查一个空的兴趣交叉点很简单。这适用于 100 个用户,50 个兴趣点,最多约 800 个数据点。您还可以通过迭代 |interests| 的最小值来提高效率。和 |用户| (以生成更少的递归调用/组合)并仅反映兴趣较低的情况的逻辑。此外,您会获得更多有趣的集群,但用户/兴趣更少
【讨论】:
“对于超过 30 个用户的实例,很难找到一个精确的解决方案” 公平地说,该声明应同时考虑用户数量和不同兴趣的总数。簇数最多为2 ** min(number of users, 2 ** (number of interests))
。
在这种情况下,我无法将头绕在 brnach 和 bound 方法上。你能写出任何伪代码吗?它基本上只是一种蛮力方法,但如果交叉点为零则不会比较?
是的,没错。与其预先生成所有用户组合(用户子集),然后循环遍历它们,交叉他们的兴趣并评分以找到最佳组合,不如在生成用户组合时找到它们的兴趣交叉点,而不是递归探索具有空交集的组合的超集。因此,例如,如果您正在测试用户组合 u1, u2 并且 u1 和 u2 没有共同兴趣,那么您不必费心检查 u1, u2, u3, u1, u2, u4.. . u1, u2, u3, u4...等我会添加伪代码以上是关于如何在这个简单的数据集中找到最大的集群?的主要内容,如果未能解决你的问题,请参考以下文章