首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于自定义距离函数优化R码生成距离矩阵

基于自定义距离函数优化R码生成距离矩阵
EN

Stack Overflow用户
提问于 2015-09-02 08:45:50
回答 3查看 1.4K关注 0票数 6

我试图根据自定义的距离函数为字符串创建一个距离矩阵(用于集群)。我在一个6000字的列表上运行了这段代码,它从90分钟以来还在运行。我有8GB内存和Intel-i5,所以问题只是代码。这是我的代码:

代码语言:javascript
复制
library(stringdist)
#Calculate distance between two monograms/bigrams
stringdist2 <- function(word1, word2)
{
    #for bigrams - phrases with two words
    if (grepl(" ",word1)==TRUE) {
        #"Hello World" and "World Hello" are not so different for me
        d=min(stringdist(word1, word2),
        stringdist(word1, gsub(word2, 
                          pattern = "(.*) (.*)", 
                          repl="\\2,\\1")))
    }
    #for monograms(words)
    else{
        #add penalty of 5 points if first character is not same
        #brave and crave are more different than brave and bravery
        d=ifelse(substr(word1,1,1)==substr(word2,1,1),
                            stringdist(word1,word2),
                            stringdist(word1,word2)+5)
    }   
    d
}
#create distance matrix
stringdistmat2 = function(arr)
{
    mat = matrix(nrow = length(arr), ncol= length(arr))
    for (k in 1:(length(arr)-1))
    {
        for (j in k:(length(arr)-1))
        {           
            mat[j+1,k]  = stringdist2(arr[k],arr[j+1])      
        }
    }
    as.dist(mat)    
}

test = c("Hello World","World Hello", "Hello Word", "Cello Word")
mydmat = stringdistmat2(test)
> mydmat
  1 2 3
2 1    
3 1 2  
4 2 3 1

我认为问题可能是我使用循环而不是应用--但后来我发现在许多地方循环并没有那么低效率。更重要的是,我没有足够的技术来使用我的循环,因为我的循环是嵌套的,类似于k in 1:nj in k:n。我想知道是否还有其他可以优化的东西。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-09-02 11:59:34

有趣的问题。所以一步一步地走:

1 - stringdist函数已经矢量化:

代码语言:javascript
复制
#> stringdist("byye", c('bzyte','byte'))
#[1] 2 1

#> stringdist(c('doggy','gadgy'), 'dodgy')
#[1] 1 2

但是,如果给出两个长度相同的向量,stringdist将导致在每个向量上并行循环(而不是产生一个具有交叉结果的矩阵),就像Map所做的那样:

代码语言:javascript
复制
#> stringdist(c("byye","alllla"), c('bzyte','byte'))
#[1] 2 6

2 -重写您的函数,以便您的新函数保持这个矢量化功能

代码语言:javascript
复制
stringdistFast <- function(word1, word2)
{
    d1 = stringdist(word1, word2)
    d2 = stringdist(word1, gsub("(.+) (.+)", "\\2 \\1", word2))

    ifelse(d1==d2,d1+5*(substr(d1,1,1)!=substr(d2,1,1)),pmin(d1,d2))
}

它确实是以同样的方式运作:

代码语言:javascript
复制
#> stringdistFast("byye", c('bzyte','byte'))
#[1] 2 1

#> stringdistFast("by ye", c('bzyte','byte','ye by'))
#[1] 3 2 0

3 -只在一个循环中重写分解矩阵函数,并且只重写一个三角形部分(没有outer,它很慢!):

代码语言:javascript
复制
stringdistmatFast <- function(test)
{
    m = diag(0, length(test))
    sapply(1:(length(test)-1), function(i)
    {
        m[,i] <<- c(rep(0,i), stringdistFast(test[i],test[(i+1):length(test)]))
    }) 

    `dimnames<-`(m + t(m), list(test,test))
}

4 -使用以下函数:

代码语言:javascript
复制
#> stringdistmatFast(test)
#            Hello World World Hello Hello Word Cello Word
#Hello World           0           0          1          2
#World Hello           0           0          1          2
#Hello Word            1           1          0          1
#Cello Word            2           2          1          0
票数 4
EN

Stack Overflow用户

发布于 2015-09-02 10:57:09

循环确实非常低效,下面是一个快速示例,它表明:

代码语言:javascript
复制
x=rnorm(1000000)
system.time({y1=sum(x)})
system.time({
        y2=0
        for(i in 1:length(x)){
                y2=y2+x[i]
        }
})

这是内部向量化函数sum()的简单比较,实质上只是在内部计算循环中所有元素的和;第二个函数在R代码中做同样的操作,这使得它一次又一次地调用另一个内部函数+,这不是很有效。

首先,在用户定义的函数中存在一些错误/不一致。这部分:gsub(word2, pattern = "(.*) (.*)", repl="\\2,\\1")用comas替换所有空格,这会自动增加+1到距离得分(这是有意的吗?)其次,对于字符串中有空格的字符串,不需要比较第一个字母,因为只有函数的第一部分被执行。这是正确的,即使只有第一个比较词包含空格,所以"Hello“和"Cello”的比较将被计算为比"Hello“和"Cello”更近的距离。

除此之外,您的代码似乎很容易向量化,因为您使用的所有函数都已经向量化了: stringdist()、grepl()、gsub()、substr()等。基本上,您对每个单词执行3次计算:简单的' stringdist() ',交换单词的字符串(如果第一个单词中有空格),以及简单地比较添加+5点的首字母(如果它们不同的话)。

下面是以矢量化方式再现函数的代码,它使计算300x300矩阵的速度提高了大约50倍:

代码语言:javascript
复制
stringdist3<-function(words1,words2){
m1<-stringdist(words1,words2)
m2<-stringdist(words1,gsub(words2, 
                           pattern = "(.*) (.*)", 
                           repl="\\2,\\1"))
m=mapply(function(x,y) min(x,y),m1,m2)

m3<-5*(substr(words1,1,1)!=substr(words2,1,1) & !grepl(" ",words1))

m3+m
}
stringdistmat3 = function(arr){
        outer(arr,arr,function(x,y) stringdist3(x,y))
}
test = c("Hello World","World Hello", "Hello Word", "Cello Word")
arr=sample(test,size=300,replace=TRUE)
system.time({mat = stringdistmat2(arr)})
system.time({
        mat2=stringdistmat3(arr)
        })
票数 3
EN

Stack Overflow用户

发布于 2015-09-03 05:52:15

我还试图创造另一种方法来改进我的答案。基本上,我删除了创建距离的函数,直接创建了distances.So矩阵,这就是我提出的。我知道这个解决方案可以改进。因此,欢迎任何建议。

代码语言:javascript
复制
strdistmat2 <- function(v1,v2,type="m"){
    #for monograms
    if (type=="m")  {
        penalty = sapply(substr(v1,1,1),stringdist,b=substr(v2,1,1)) * 5
        d = sum(sapply(v1,stringdist,b=v2),penalty)
    }
    #for bigrams
    else if(type=="b")  {       
        d1 = sapply(v1,stringdist,b=v2) 
        d2 = sapply(v1,stringdist,b=gsub(v2,pattern = "(.*) (.*)", repl="\\2 \\1"))
        d = pmin(d1,d2)
    }
    d
}

下面我比较了各种解决方案的时间。

代码语言:javascript
复制
> test = c("Hello World","World Hello", "Hello Word", "Cello Word")
> arr=sample(test,size=6000,replace=TRUE)
> system.time({mat=strdistmat2(arr,arr,"b")})
   user  system elapsed 
  96.89    1.63   70.36 
> system.time({mat2=stringdistmat3(arr)})
   user  system elapsed 
 469.40    5.69  439.96 
> system.time({mat3=stringdistmatFast(arr)})
   user  system elapsed 
  57.34    0.72   41.22 

因此-上校回答是最快的。

另外,在实际数据上,我的代码和Maksim代码都崩溃了,只有上校的回答有效。以下是结果

代码语言:javascript
复制
> system.time({mat3=stringdistmatFast(words)})
   user  system elapsed 
 314.63    1.78  291.94 

当我在实际数据上运行我的解决方案时--错误消息是--无法分配684 MB的向量,在运行Maksim的解决方案时-R停止工作。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32348529

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档