我建议您尝试以下方法。我没有数学证据证明它会产生绝对最好的“分数”,或者确实是其他一些“善意的衡量标准”,
但我确实相信它可能会对您有所帮助,当然,除非案例要求您实际证明并找到绝对最好的。
我不会说python,但是策略和随后的实现很简单,甚至不需要伪代码来解释。
不过我会用很多词,不是因为它很复杂,而是要清楚(我希望)。
您将需要一种方法对矩阵进行对角化,或者至少找到与最大特征值对应的特征向量。
并非每种语言都提供本地方法。在 C++ 中,您可能会使用 Eigen 库。在我曾经测试过的 C# 中,我连接了 MathNet。在python中,我不知道。
对角化/特征向量工具的要求是有限的:矩阵总是实数、对称的,所有元素都是正数。
然而,尽管对角线元素在其行/列中至少与其他任何元素一样大,但矩阵肯定不是“对角线占优”。
您提出的问题可以抽象地表述为整数,而不是单词,这使得(对我来说......)更方便地制定和实现
(但除此之外,它是完全不相关的)。
事实上,我们只需要您的“common_words”,因此需要 100 个整数 [0..99],并且您只需将单词放入数组或列表中并使用它们的索引,就可以设置单词和整数之间的映射。
附带说明:对于您的语言应用程序,该策略可能(远)更适用于您可能构建“恶意”困难输入的整数的完全广义问题。
原因是我们将基本上利用成对相关性,如果有的话,在项目之间(同一个句子中的单词),而三元组、四元组的强相关性……可能会降低效率(但我们会为三胞胎做点什么)。
显然,在格式良好的语言中,您会期望相关性:“am”通常与“I”一起出现,
虽然“have”总体上可能比“has”更频繁,但一旦你找到“he”,你可能更有可能在同一个句子中得到“has”而不是“have”。以此类推。
一些定义是有序的。
NCommon 是 common_words 中元素的数量(即 100)。 common_words “是”整数 [0..NCommon-1]。
NMaxSet 是您最终愿意使用的问题限制(即 20)“单词”的最大数量。
F 是一个过滤器:您用来定义哪些句子包含在某个集合中的“单词”集合。
最后,您必须调整您的过滤器,使 F.Count 不超过 NMaxSet。
M(N) 是一个 NxN 方阵;行和列索引在 [0..N-1]
S(F) 是满足过滤器 F 的句子(来自问题输入)的集合。
“句子”在这里始终是 [0..NCommon-1] 范围内的整数集合,见上文。
同一句子中的重复单词是不相关的(问题描述),并且此类重复已从我们的整数句子中删除。
我们开始吧。
我。准备。
初始化一个矩阵 MCommon = M(NCommon)。
创建包含所有 common_words 的 FCommon 过滤器。
过滤输入,去除句子中的重复单词。
这会产生一个句子集 S0 = S(FCommon),这是您的“真实”输入:所有不相关的内容都已删除。
在运行中,在接受句子到 S0 的同时,填充矩阵 MCommon:{for (j in sentence): for(k in sentence): M(j,k)+=1}。
矩阵是对称的,所以你可以只填充右上三角形并在末端镜像到左下三角形。
完成扫描后,M[j,k] 是包含 j 的“句子”中出现的 k 次(反之亦然:矩阵是对称的)。
M[k,k] 是 S 中所有句子中 k 的总数。
M 是(成对)相关矩阵,告诉您在基础句子集 S 中出现 {j,k} 组合的可能性有多大。
(总是,在处理此类矩阵时:如果最后碰巧有空列(因此行):删除这些列和行,
同时删除作为矩阵基础的过滤器中的相应条目,显然它不起作用。
此后我们将假设这已经完成(除非另有说明),即矩阵中没有一列/行是空的。)
二。计算结果(主要方法,我们将在下面细化):
计算最大特征值对应的 MCommon 的特征向量 E:E 是一个具有 NCommon 系数的数组(向量)。
设置 NTarget=NSetMax
确定 E 中哪些是 NTarget 最大的系数。我们对它们的值不感兴趣,而是对它们的索引感兴趣。
将这些索引放在一起:它们定义了一个新的过滤器 F(NTarget)。
通过新过滤器运行 S0 以生成 STarget。
计算所有“单词”的出现次数,找到它们的最小值:这就是你的“设定质量”值。
例如,您可以通过计算关联的 MTarget 并扫描对角线值 MTarget[k,k] 来这样做。
这似乎涉及不必要的额外工作,因为您还要计算非对角线元素,但我们会看到 MTarget 在后续改进中可能会派上用场。
三。改进。
A) 我们需要验证是否通过从过滤器 F(NsetMax) 中删除一个或几个项目,将其减少到小于 NSetMax 的某个 NTarget,我们会得到更好的分值。
这需要注意:删除两个(或三个,...)项目很可能会提高分数,但删除其中任何一个都会降低分数。
让我们对这个问题进行第一次(并且相当合理)的尝试。
在生成 STarget 的同时,您还填充了一个新的矩阵 MTarget(您看,它很方便......),就像您之前对 MCommon 所做的那样。大小为 NTarget。
得到它的最大特征值特征向量。确定 SMALLEST 系数。从过滤器中删除相应的索引。
再次过滤(作为输入,您当然可以现在使用 STarget 集合)并计算分数。
如果更好:标记/记住。
在所有情况下(无论是否改进)都以相同的方式继续,逐个减少过滤器,直到您一无所有。
B)人们可能会期望,正如简要解释的那样,在进一步将过滤器进一步减小到 NSetMax 以下的“谨慎”方法的原因 - 一次一个 -
也可能在某种程度上适用于我们在一次大罢工中将 F(NCommon) 减少到 F(NTarget=NMaxSet) 的第一步。
为了适应这一点,我们可能希望逐步从 NCommon 到 NMaxSet。为了不产生过多的计算开销,我们不会
采取大小为 1 的步骤,而是每次减少大约 10%,或类似的东西。
因此,在上面的 II 中,不要立即将 NTarget 设置为 NSetMax,而是(例如)设置 NTarget = 90。
构造相应的滤波器,将滤波器应用于 S0,给出 S1,同时生成矩阵 M1,得到其特征向量(最大特征值)。
重复:设置 NTarget=80、70、60 等等。在后期您可能会变得更加谨慎,将 40 降低到 35 到 30 到 28 到 26 ......
在每个步骤中,您都在前一个步骤的基础上构建并使用结果,以优化减少大小和计算工作量。
您一直想监控最大的 NSetMax(算法这一部分中的最终值)系数是否总是以相同的索引出现。
这将提供有关最终结果是否可靠的一些信息:预期最终过滤器相对于算法路径的稳定性。
C) 考虑我们已经减少到 NSetMax 的情况,并调查是否应该以一次一次的方法进一步减少它。
(同样的情况也适用于 (B) 的最后阶段,如果从上方接近 NSetMax 时,一旦接近 NSetMax,就“一次一个”。)
假设在算法的这个阶段,在您的(第一个或以后的)STarget 集合中,
某些“单词”对,这样从过滤器中删除这样的特定对会改善事情,
而它们在特征向量中的各个系数都不是最小的。
我不确定这是否可能或什至可能,但让我们看看我们如何处理它。
如果(您的)测试表明它无关紧要,您可以在最终实现中从算法中删除此功能。
假设我们有一些 NTarget,一个关联的过滤器 FTarget 和矩阵 MTarget。
(过滤器中项目('words')的顺序总是(当然)等于矩阵中行和列的顺序。)
从 MTarget 我们可以直接推断出一些关于如果我们从过滤器中删除第 j 个项目会发生什么的信息。
当然,矩阵中的第 j 行和第 j 列变为空(全为零)。
更有趣的是,M[j,k] 表示在所有包含项目 j 的句子中项目 k 出现的次数。
因此,当消除所有 j 时(通过从过滤器中删除它),我们预先知道在得到的新矩阵 MReducted 中,
元素 MReduced[k,k] 将精确地减少该 M[j,k] 值。
(顺便说一句,这提供了一种确定要删除的项目的替代方法(如果您选择仅删除一个):
最小值{j: M[j,j]} 是关联集 S 的分数,从 M 我们可以计算所有对角线元素在移除特定项目 j 时会如何变化,从而能够对结果分数进行预计算)
然而,在这里,我们想知道在删除一些项目 j 和 k 后,分数会受到怎样的影响。
我们现在需要确定所有不是 j 或 k 的 p 如何影响 M[p,p]。
这个,你不能直接从 M 计算:
删除 j 会影响第 k 行,虽然我们知道它如何改变 [k.k] 和任何其他 [p,p],但我们不知道它会如何改变 [k,p],
这是计算 ALSO(“随后”)删除 k 将如何改变 [p,p] 所需要的。
顺便说一句,无论是先删除 j 再删除 k 或反之亦然,或同时删除两者,最终结果都必须是无关紧要的。
为此,我们需要某种“衍生物”。
幸运的是,我们可以计算和处理它,而不需要太多的计算工作(鉴于 NTarget 已经相当小,大约 20 个)。
考虑一个与当前过滤器F相关的'reductionfilter' G(F; j),简单地定义为处理F但忽略其中的项目j。
对于 G,我们以与往常相同的方式计算“归约矩阵”N(G),为了方便讨论(和实现),我们保持相同的大小(不删除空列/行)。
当然,在这样的 N 矩阵中,第 j 行第 j 列是空的。
它的对角元素将具有如上所述的值(我们可以直接从 M 计算这些值)。
但是,现在我们也有所有由移除 j 产生的非对角元素。
从 N(G{F;j}) 我们现在可以确定如果我们删除 ALSO 项目 k 会发生什么,请参阅上面关于如何从当前矩阵中获得预期分数的详细说明。
因此,这允许在移除 {j,k} 对时计算分数。
如果 setsize 为 20,我们必须计算 20 个 N(G(F;j)) 矩阵,我认为这是一个相对较小的工作(您的句子集合现在也将比原来小很多)。
然后,拥有所有 N,为 20*19/2 唯一对中的每一个计算得到的对移除分数,你就可以
选择要从过滤器中删除的 PAIR(而不是单个元素)。
您可以在运行中将其与“一次一个”的减少进行比较,并做出适当的选择,如何系统地减少过滤器以寻求更好的分数。
有很多方法可以进行这种比较。一个相对简单的方法是:先计算一对,然后再计算一个(总共 3 个元素)的减少。
与先删除一个然后删除一对进行比较。
选择这两者中最好的,但只从该选择中执行第一步(单个或一对)并重复该过程。
请注意,使用这些“派生”过滤器 G、矩阵 N 和随后的分数预计算,如所述,
您有效地引入了 TRIPLES 项目之间的相关性:您确定所有 {j,k,p} 在删除 j 和 k 时对 p 的频率会发生什么。
当然,这个想法可以扩展到合并四倍甚至更多,但是(a)我不相信它实际上会有很大帮助,并且(b)你在这条路上走得越远,计算量就会增加得越快.
我什至怀疑这里解释的引入“三元组”是否相关,
但我不是语言专家,除了一些额外的编程工作外,没有什么大的缺点。
D) 该策略的主干是依靠具有最大特征值的特征向量来指向相关项目以进行后续过滤。
当然,可能会发生两个或多个特征值“几乎”相同,并且相应的特征向量可能指向完全不同的项目集
在分析它们最大的组成部分时。
在这种情况下,“分支”将是值得的,即:与其中一个一起工作,工作到最后,然后
与他们的竞争对手重做所有事情,看看他们是否会产生更好的结果。
如果您遇到很多分支(在解决问题的道路上的各个点),就会出现问题。这不太可能发生,但
如果确实发生,实施应该有一些策略以实际的方式处理它。
我建议您首先保留任何分支,但您确实要监视“竞争性”最大特征值的出现,以便向用户输出警告。
或者,您可以实现对此类分支的处理,但对程序认为“几乎相等”的内容非常严格,或者设置一些限制为
到要研究的(总)分支的数量,从而减少计算时间失控的可能性。
我不得不把它留在这里(因为时间不够......),希望我已经充分解释了这个想法,以及一些需要考虑的细节和改进。
我没有时间为自己组织相关的“真实语言”句子作为输入并针对它进行测试。
我已经在 C# 中编写了基本步骤,针对随机整数“句子”进行了测试,并带有一些偏见以迫使某些“单词”比其他“单词”更频繁地出现,
但不关心“句子”中“单词”之间的相关性。
结果对我来说似乎很合理,但我没有时间深入分析它。
鉴于我用作输入的“随机”序列缺乏结构,而真实语言预计会表现出显着的成对相关性,
该策略所利用的,
我很希望它可能对您有用。
[已编辑] 补充说明:在上面我偶尔会草率地谈论“j”、“k”等。对此我很抱歉。
有时“j”指的是某物的第 j 个条目(矩阵行,过滤器列表中的索引),有时它指的是(通常)过滤器中的相应 VALUE。
例如,一个过滤器可能包含 18 个项目,编号(索引)0..17,但它们的 VALUES 始终引用原始 Common_words 列表,
所以它们可能是 {3, 15, 29, 30, 31, 40, ...}。
然后,当它说“从过滤器中删除 j”时,通常意味着“从过滤器中删除第 j 个条目(并且该条目可能具有 [0..NCommon-1] 中的任何值)。
将过滤器应用于句子时,您将过滤器中的值与句子中的值进行比较。
我希望上下文 - 结合对推理路线的公平理解 - 总能清楚地说明真正的含义。
[编辑:一些测试结果]
我已经运行了我的 C# 实现,使用上述算法(或多或少:描述了大多数但不是所有改进/细节)来对它会产生什么有一些印象。
作为输入,我从 Gutenberg 项目中获取了 4 本书(纯文本)。总共(仅)27k 个句子,380k 个单词(20k 个不同),所以样本量相当小。
根据该输入确定的常用词列表以“the”、“of”、“and”、“to”开头...(按总输入中的出现频率排序)。
该算法过滤了 14 个单词(“i”、“what”、“do”、“you”、“it”、“yes”……)以产生“最优”“set-quality-value” 8 个(仅找到这 14 个单词的 139 个句子)。
由于我对只使用 100 个“常用词”的假设持怀疑态度,因此我先验地允许使用 500 个常用词,而不是 100 个,而且在进入最终过滤器的词中,肯定有 4 个频率编号高于 100(“yes”、“know”、“say”、“think”):例如,如果您要按所有输入中的出现对它们进行排序,“yes”在整体列表中排名第 224,大概是“常用词”的基础。