在 Node.js 和 MongoDB 环境中使用 MapReduce 进行 Tf-Idf 排名
在开发文档搜索应用程序时,其中一个挑战是根据您搜索的术语的出现对结果进行排序。 Tf-Idf 是一种数值统计,可帮助您权衡搜索结果。
Tf 代表词频。
Idf 代表反向文档频率。
为了掌握,我们将在 javascript 中开发一个 tf-idf 示例,作为节点模块。
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
函数 weights 将接受要搜索的文档和术语。
下面是一个例子
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
结果是:
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
现在我们将继续使用 MapReduce 方法。
我将使用 node.js。
首先,我们将安装 MongoDB 驱动程序:
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
然后我们将设置我们的 Mongo 数据库连接。初始化后,如果没有记录,我们将填充数据库以进行测试。
这将是一个两阶段的过程。
在第一阶段,我们必须计算 idf。
为此,我们将发布 MapReduce。
必须传递术语变量才能被 MapReduce 进程使用。
为了在 MapReduce 上使用动态变量,我们将使用范围参数。
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
结果是一个数字。
在第二阶段,我们必须计算每个文档的 tf,并将结果与之前计算的 idf 值相乘。
MapReduce 也将用于这种情况。
这次通过范围参数,我们将传递我们搜索的术语以及 idf 变量。
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
我们将实现结合前两个步骤的 tfIdf 函数。
该函数将我们需要搜索的术语作为参数。
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
我们的测试展示案例:
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
对于这两种情况,我们得到相同的结果。
function TfIdf() {
}
TfIdf.prototype.weights = function(documents,term) {
var results = []
var idf = this.idf(documents,term)
for(var i=0;i<documents.length;i++) {
var tf = this.tf(documents[i],term)
var tfidf = tf*idf
var result = {weight:tfidf,doc:documents[i]}
results.push(result)
}
return results
}
TfIdf.prototype.tf = function(words,term) {
var result = 0
for(var i=0;i<words.length;i++) {
var word = words[i]
if(word.indexOf(term)!=-1) {
result = result+1
}
}
return result/words.length
}
TfIdf.prototype.idf = function(documents,term) {
var occurence = 0
for(var j=0;j<documents.length;j++) {
var doc = documents[j]
if(this.__wordInsideDoc(doc,term)){
occurence = occurence+1
}
}
if(occurence==0) {
return undefined
}
return Math.log(documents.length/occurence)
}
TfIdf.prototype.__wordInsideDoc = function(doc,term) {
for(var i=0;i<doc.length;i++) {
var word = doc[i]
if(word.indexOf(term)!=-1) {
return true
}
}
return false
}
module.exports = TfIdf
为什么我应该使用 MapReduce 来解决这个问题?
tf-idf 排序问题是一个包含计算的问题,可以并行化。
顺序方法可能是其他环境的一种选择,但对于 Node.js 来说有很多缺点。
Node.js 是一个单线程环境,它不是为繁重的计算任务而设计的。
它的魔力与它执行 I/O 操作的能力有关。
考虑大型数据集问题的场景。
虽然 Node.js 进程将执行耗时的计算,但发出的请求将无法正确执行。
然而,基于 Node.js 的解决方案有一些解决方法,例如生成额外的节点并实现它们之间的通信方式。
总结
MapReduce 非常适合排名问题。它不仅消除了大部分计算开销,而且还消除了实现开销。