레이블이 lucene인 게시물을 표시합니다. 모든 게시물 표시
레이블이 lucene인 게시물을 표시합니다. 모든 게시물 표시

2011년 7월 30일 토요일

Lucene에서 KStemmer 사용하기

정확한 명칭은 Krovertz Stemmer라고 부른다. Lucene등의 툴킷에서는 

KStemmer라는 이름의 약자로 되어 있다. 1993년 꽤 오래전에 논문이 나오고 

lucene, lemur, indri 등 대부분에 포함되어 있지만 생각보다는 많이 쓰이지는 

않는듯하다.

아무래도 사전기반의 알고리즘이다 보니 리소스가 꽤 많이 소비된다는 측면이 

클듯하다. 

가장 최신 버젼인 lucene-3.3.0 버젼에서는 추가된듯하다. 

( 이전 버젼부터 존재했었는지는 확실치 않다. )

자세한 사항이 궁금한 사람은 다음의 논문을 읽어 보도록 하자. 



다음의 표는 Porter Stemmer가 에러를 일으키는 경우를 나타낸다.



Krovetz Stemmer는 Porter Stemmer만큼은 공격적으로 stemming을 하지는 않기때문에 

에러가 적다. 

KStemmer의 알고리즘은 기존의 Porter Stemmer의 알고리즘과 사전기반의 알고리즘이 

결합된 형태이다. 

KStemmer는 Inflectional, Derivational 두가지 방법의 알고리즘을 적용한다.

전자는 suffix를 제거하는 쪽 후자는 추가해서 명사를 만드는 쪽이다.


Inflectional에서 단어가 사전에 있는지를 찾아본후 사전에서 단어를 발견하면

스템처리를 하지 않는다. 


Derivational 방법에서는 -er, -or, -ion, -ly, -ity, -al, ive, -ze, -ment, -able 등을 추가하는데 

논문에 따르면 longman 사전의 빈도수를 기반으로 이를 패턴화 시켜놓았다.

더 자세한 사항은 논문을 참조하도록 하자. 


lucene에는 KStemFilter는 포함되어 있으나 Analyzer는 없기때문에 클래스를 추가해주어야 한다.

간단히 다음과 같이 클래스를 만들어 사용해 보자. 

package org.apache.lucene.demo;

import org.apache.lucene.analysis.en.*;
import org.apache.lucene.analysis.*;
import org.apache.lucene.analysis.standard.*;
import org.apache.lucene.util.Version;

import java.io.Reader;

public class KStemAnalyzer extends Analyzer {
Version MatchVersion;
public KStemAnalyzer(Version matchVersion){
this.MatchVersion = matchVersion;
}
public TokenStream tokenStream(String fieldName, Reader reader){
return new KStemFilter(new StandardTokenizer(MatchVersion,reader));
}
}

코드는 lucene-3.3.0을 기준으로 작성하였다. 

Analyzer는 Filter와 Tokenizer를 모두 필요로 한다. 

위에서는 간단하게 StandardTokenizer를 사용하였지만 lucene에는 

ClassicTokenizer, WhitespaceTokenizer, LowerCaseTokenizer, 

KeywordTokenizer등 여러가지가 있으니 입맛에 맞게 사용하면 된다.

Tokenizer가 있으니 입맛에 따라 골라 사용하자. 

2011년 7월 28일 목요일

LUCENE으로 TREC WT10G dataset 인덱싱하기

WT10G dataset은 웹문서를 모아놓은 dataset으로 아직도 많이 쓰인다.

WT10G에는 1,692,096개의 문서가 있고 용량은 대략 4G정도 된다. 

데이터는 WTX001 ~ WTX104개의 폴더에 각각 B01.gz ~ B50.gz로 

되어 있다. 

하나의 BXX.gz 파일에는 여러개의 웹 문서가 들어있어서 독립적인 포맷으로 

합쳐져 있는데 포맷은 대략 다음과 같다. 























WT10G에 대해서 더 궁금하신 분은 

http://ir.dcs.gla.ac.uk/test_collections/wt10g.html를 가보시라.

검색을 해보면 lucene으로 WT10G 데이터를 인덱싱해서 낸 논문은 많지만

실제로 어떻게 인덱싱을 했지는 코드를 공개한 것은 거의 없다. 

http://code.google.com/p/lucene-web-search-and-trec-wt10g/ 주소로 

가보면 거의 유일하게 코드를 공개한 프로젝트이다. 중국의 어느대학

대학원생들이 텀프로젝트로 수행한 것을 공개한듯하다. 

어찌됐든 lucene으로 WT10G를 인덱싱하는 간단한 코드 IndexTrec.java와

TrecDocument.java를 첨부한다. 

두개의 파일을 org.apache.lucene.demo 패키지 폴더에 추가해 놓고 실행하면

된다. 

IndexTrec.java는 org.apache.lucene.demo 패키지의 IndexFiles.java를 기반으로

만들었으며 코드는 어렵지 않으니 직접 살펴보고 수정해도 좋을것이다.

크게 수정된 부분은 readDocs() 메소드이며 이 메소드에서 WT10G의 gz압축

파일의 스트림을 읽어들여 파싱하는 역할을 한다. 


TrecDocument.java에서는 인덱싱하는 필드를 조금 수정했다. 아래의 그림을 참고하자.














문서가 파일단위가 아닌 <DOCNO> </DOCNO>인 태그의 값으로 설정되어야 

해서 이부분을 추가했으며 기본적으로 WT10G는 웹문서의 집합이므로 

HTMLParser를 써야한다.  


소스 파일을 첨부한다.


2011년 7월 20일 수요일

lucene의 MoreLikeThis를 사용한 간단한 추천 시스템 만들기

 Lucene의 contrib중 queries 폴더에 보면 MoreLikeThis와

 FuzzyLikeThisQuery등이 포함되어 있다.

 사실 추천 시스템이라 말하기 좀 민망하긴 하지만 어찌됐던

 MoreLikeThis를 이용하여 문서 추천시스템을 만드는 것도 가능하다.

 MoreLikeThis는 주어진 다큐먼트의 Term Frequency Vector를 만들고

 이를 통해서 새로운 쿼리를 만드는 기능을 한다.

 즉 인덱싱된 문서중 주어진 문서와 가장 유사한 문서를 찾을수 있도록

 만들어 준다.

 TF를 이용해 문서의 key term을 추출하는 방식은 매우 고전적인 방식이지만

 아직도 많이 쓰인다. 궁금한 분은 다음의 논문을 읽어보도록 하자.


  "Newman, M.E.J. and M. Girvan, 2004. Finding and evaluating community structure in networks. Phys. Rev. E., 69: 026113"



 Lucene In Action 1판, 2판에 모두 MoreLikeThis에 관한 내용이 포함되어

 있으니 책에 있는 코드의 핵심 부분만 간단히 살펴보자.

  IndexReader reader = IndexReader.open(directory);
  MoreLikeThis mlt = new MoreLikeThis(reader);

  ...

  for ( int docID = 0 ; docID < numDocs; docID++){
    ...
    Query query = mlt.like(docID);
   TopDocs similarDocs = searcher.search(query,10);
    ...
  }

  1. 우선 IndexReader를 인자로 주어 MoreLikeThis 객체를 생성한다.

  2. 인덱스된 전체 문서에 대해서 각 문서의 term frequency vector를
      생성하고 이를 통해 query를 만든다.

  3. 주어진 쿼리로 인덱싱된 문서에서 다시 검색을 한다.


 즉 위의 코드는 인덱스된 모든 문서에 대해서 가장 유사한 문서들을

 찾는 역할을 한다.

 MoreLikeThis 객체의 like 메소드는 overloading되어 있어서

 사실 4개의 인자를 받을수 있는데 다음과 같다.

 1. 인덱싱된 문서의 번호


 2. File 객체


 3. reader 객체


 4. url 객체

 2,3,4 번의 경우 직접 term frequency를 구하여 term frequency vector를 만들고

 1번의 경우에는 IndexReader의 getTermFreqVector() 메소드를 이용하여

 term frequency vector만든다.


 lucene 내부에서 사용하는 term frequency vector는 TermFreqVector

 인터페이스를 implements 하거나 implements한 클래스를

 상속하여 만들어진다.

 IndexReader의 종류에 따라 사용하는 TermVector 클래스가 다르므로

이는 나중에 다른 글에서 설명하겠다.


 그럼 org.apache.lucene.demo에 들어있는 SearchFiles를 약간 수정하여

 인덱스된 문서들중 주어진 URL의 문서와 가장 유사한 문서를 찾는 예제를

 간단히 만들어 보겠다.

 1. 우선 다음의 import 구문을 추가한다.

 import org.apache.lucene.search.similar.MoreLikeThis;
 import java.net.*;

 2.  Search시에 사용하는 Query 객체와 관련한 구문을 다음과 같이 수정해보자.

 MoreLikeThis mlt = new MoreLikeThis(reader);
 mlt.setMinTermFreq(2);
 mlt.setMinDocFreq(2);

 Query query = mlt.like(new URL("http://wittgena.blogspot.com/2011/07/lucene-incremental-indexing.html"));


 사용법이 매우 간단하다.

 나머지는 모두 동일하고 while(true){} 구문만 제거하면 될것이다.

 MoreLikeThis에 필드는 따로 지정해주지 않았는데 지정해주지

 않으면 "contents" 필드를 기본값으로 사용한다.

 나의 경우 위의 코드로 linux kernel의 documentation 폴더의 일부 문서를

 인덱싱한후 돌려보니 대략 15개의 문서가 검색이 된다.

 각자 코드를 수정하여 실행해보자. 코드를 첨부한다.

 다운로드


 ps. mahout을 이용하여 lucene에서 term vector를 추출하는 방법도 있다.
      이는 나중에 다른 글에서 설명하도록 하겠다.
      lucene의 TermVector 객체를 알아두면 textmining에
      lucene을 사용할수도 있고 여러모로 유용하다.

2011년 7월 19일 화요일

lucene에서 n-gram, shingle 사용하기

 lucene에는 n-gram tokenizer와 shingle analyzer가

( 정확히는 shingle analyzer wrapper)

 포함되어 있긴 하지만 3.0.1 버젼을 기준으로 할때 contrib쪽에 포함되어 있다.

 n-gram과 shingle을 가끔 혼동하시는 분도 계신데 둘은 약간 다르다고 볼수 있다.

 n-gram이 한 문자를 기반으로 하는 반면에 shingle은 한 단어를 기반으로 한다.


 "you can select"을 예를 들어보면,

 n-gram 방식으로 bi-gram으로 한다면 yo, ou, uc, ca, an 이 될 것이고

 shingle 방식으로 2-shingles로 한다면 you can, can select가 될 것이다.

 검색을 해보니 lucene에서 n-gram을 사용하는 법에

 관한 블로그는 이미 있는 듯하여

 나는 lucene에서 shingle을 사용하는 방식을 간단히 설명하겠다.

 lucene의 contrib쪽에 보면 analyzers 소스 폴더가 있다.

이 analyzers에 n-gram과 shingle 둘다 포함되어 있다.


 우선 "ant build-contrib" 명령어를 통해 contrib쪽을 빌드하면

 build/contrib/analyzers/common 폴더 밑에 lucene-analyzers-x.x.x-dev.jar 파일이

 생성된다. ( x는 버젼명이다. 즉, 나의 경우는 3.0.1이 된다.)

 이 jar파일을 classpath에 포함한후 src/java/org/apache/lucene/demo의 IndexFiles.java

 에서 다음의 두 부분을 수정한후 빌드해 보자.

 1. import 부분에 다음을 추가한다.
  import org.apache.lucene.analysis.shingle.*;

  2. IndexWriter 객체의 생성 부분을 다음과 같이 수정한다.

  (대략 60 ~ 70 라인 사이에 있다. )
IndexWriter writer = new IndexWriter(FSDirectory.open(INDEX_DIR),
                                                //new StandardAnalyzer(Version.LUCENE_CURRENT),
                                                new ShingleAnalyzerWrapper(new        StandardAnalyzer(Version.LUCENE_CURRENT),3),
                                                true,
                                                IndexWriter.MaxFieldLength.LIMITED);

  IndexWriter의 생성자에서 무엇이 수정되었는지는 쉽게 알수 있을것이다.

 의미는 기본 analyzer로 StandardAnalyzer를 사용하고 shingle의 최대 크기를 3으로

 한다이다.

 이후 나머지 searcher를 이용한 검색과정등은 모두 동일하다.

Lucene의 Ranking Algorithm 변경

 lucene은 사실 그렇게 쉽지 많은 라이브러리이다.

 lucene의 ranking algorithm을 변경하는 방법은 여러가지인데  이중 가장

 기본적인것이 TF-IDF cosine similarity 공식 (또는 scoring fomula)을 변경하는

 것이다.  그 외에도 Query에서 가중치를 주거나 Searcher에서 HITS Collector의

 순서를 변경하는 방법, Document의 Field별 가중치를 다르게 주는법(boosting) 등등등

 여러가지가 있다.

 일단 scoring fomula를 수정하는 방법을 살펴보자.

( Lucene in action의 3장을 보면 이 내용이 잘 설명되어 있다.)

 우선 src/java/org/apache/lucene/search 폴더에 보면 DefaultSimilarity 클래스가 있다.

 메소드를 보면 computeNorm(), lengthNorm(), queryNorm, tf(), sloppyFreq(), idf()

 등등이 있다. 이 클래스는 IndexWriter 클래스에서 사용하는데 DefautSimilarity

 클래스를 상속해서 새로운 Similarity 클래스를 만들면 간단히 공식을

 변경할수 있다.

 물론 변경후 반드시 IndexWriter에 등록을 해주어야 한다.


 다음과 같이 DefaultSimilarity 클래스를 상속하여 새로운 클래스를 만들어보자.

import org.apache.lucene.search.*;

public class MySimilarity extends DefaultSimilarity {
public float tf(float freq){
return (float)(Math.sqrt(freq));
}

public float idf(int docFreq,int numDocs){
return (float)(Math.log(numDocs/(double)(docFreq+1))+1.0);
}

}

TF와 IDF를 계산하는 공식만 수정하는 간단한 소스이다.

 이제 IndexWriter 객체에 등록한다.



 IndexWriter writer = new IndexWriter(FSDirectory.open(index),
                                  new StandardAnalyzer(Version.LUCENE_CURRENT),              
                                  create,
                                  new IndexWriter.MaxFieldLength(1000000));
 writer.setSimilarity(new WikiSimilarity());



 당연한 얘기겠지만

 인덱싱을 하기전에 IndexWriter에 등록을 해주어야 한다.

 이제 수정된 공식으로 다큐먼트를 인덱싱하게 된다.

2011년 7월 17일 일요일

lucene incremental indexing

 Lucene은 증분 색인 ( incremental indexing)을 지원한다.

 incremental indexing을 하기 위해서는 기존의 인덱스와 새로 추가하는 문서들의

 인덱스를 합치는 작업을 해야하는데 이때 MergePolicy 클래스에서 이러한

 작업들의 기본 설정을 관리하게 되며 3.0.1 버전을 기준으로 봤을때

 대략 다음과 같이 3개의 Policy 클래스가 있다.

 1. LogByteSizeMergePolicy

 2. LogDocMergePolicy

 3. LogMergePolicy

 Lucene에서는 LogByteSizeMergePolicy를 기본설정으로 사용하고 있다.

 $LUENE_HOME/src/java/org/apache/lucene/index 폴더에서 이러한 Policy 클래스

 가 들어있고 IndexWriter.java의 소스를 보면 이러한 정책들이 셋팅되어 있는

 것을 확인할수 있다.



 LogByteMergePolicy의 알고리즘은

http://nlp.stanford.edu/IR-book/html/htmledition/dynamic-indexing-1.html에 잘 나와있다.


http://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html

위의 블로그에 가보면 블로그 저자가 TieredMergePolicy라는 새로운 policy를

 만들고 LUCENE-854 patch로 만들었다고 하는데 lucene-3.3.0 버전에는

 TieredMergePolicy가 포함되어 있다.

2011년 7월 16일 토요일

lucene의 실행 script

 Lucene 소스를 다운받아 보면 소스와 라이브러리, Jar 파일만 들어있기

 때문에 처음접하는 사람의 경우 어떻게 시작해야 할지 당황하기 쉽다.

 lucene의 자매품 같은 프로젝트로 nutch가 있는데

 nutch에는 bin 폴더 밑에 "nutch"라는 실행 스크립트가 들어있다.

 이를 수정해서 lucene용 스크립트로 사용하면 많은 수정없이

 lucene에 맞게 사용할수 있다.

 다음은 nutch 스크립트를 수정해서 만든 lucene 스크립트이다.

 스크립트를 사용하려면 lucene폴더 하에 bin 폴더를 만들고

 스크립트를 lucene이라는 이름으로 저장하고 실행하면 된다.

 우선 실행하는 방법을 보자.












 bin/lucene IndexFiles [Option]과 같이 간단하게 실행할수 있다.

 보면 알겠지만 스크립트에는 contrib와 기타 jar에 포함되어 있는

 모든 main을 포함하는 클래스를 포함시켰다.

스크립트의 소스는 다음과 같다.

 #!/bin/bash

if [ $# = 0 ]; then
  echo "Usage: lucene COMMAND"
  echo "where COMMAND is one of:"
  echo "DeleteFiles"
  echo "IndexFiles"
  echo "IndexHTML"
  echo "IndexTrec"
  echo "IndexPPT"
  echo "SearchFiles"
  echo "HTMLParseTest"
  echo "PorterStemmer"
  echo "CheckIndex"
  echo "IndexReader"
  echo "QueryParser"
  echo "--------------- contrib ----------------"
  echo "PatternParser"
  echo "TernaryTree"
  echo "Benchmark"
  echo "precisionrecall"
  echo "EvaluationTrec"
  echo "programSample"
  echo "QueryDriver"
  echo "QualityQueriesFinder"
  echo "ExtractReuters"
  echo "ExtractWikipedia"
  echo "SanityLoadLibrary"
  echo "FieldTermStack"
  echo "Lucli"
  echo "FieldNormModifier"
  echo "IndexSplitter"
  echo "MultiPassIndexSplitter"
  echo "HighFreqTerms"
  echo "IndexMergeTool"
  echo "PrecedenceQueryParser"
  echo "MoreLikeThis"
  echo "RemoteSearchable"
  echo "TestRemoteSort"
  echo "SnowballTestApp"
  echo "GeoHashUtils"
  echo "ListSearcherSimulater"
  echo "SynExpand"
  echo "SynLookup"
  echo "Syns2Index"
  echo "or"
  echo " CLASSNAME                  run the class named CLASSNAME"
  exit 1
fi

# get arguments
COMMAND=$1
shift

# some directories
THIS_DIR=`dirname "$THIS"`
#LUCENE_HOME=`cd "$THIS_DIR/.." ; pwd`
LUCENE_HOME=`echo $LUCENE_HOME`

# some Java parameters
if [ "$LUCENE_JAVA_HOME" != "" ]; then
  #echo "run java in $LUCENE_JAVA_HOME"
  JAVA_HOME=$LUCENE_JAVA_HOME
fi

if [ "$JAVA_HOME" = "" ]; then
  echo "Error: JAVA_HOME is not set."
  exit 1
fi

JAVA=$JAVA_HOME/bin/java
JAVA_HEAP_MAX=-Xmx1000m

# check envvars which might override default args
if [ "$LUCENE_HEAPSIZE" != "" ]; then
  #echo "run with heapsize $LUCENE_HEAPSIZE"
  JAVA_HEAP_MAX="-Xmx""$LUCENE_HEAPSIZE""m"
  #echo $JAVA_HEAP_MAX
fi

CLASSPATH=$LUCENE_HOME/build/lucene-core-3.0.1-dev.jar:$LUCENE_HOME/build/lucene-demos-3.0.1-dev.jar
CLASSPATH=${CLASSPATH}:$JAVA_HOME/lib/tools.jar
#CLASSPATH=${CLASSPATH}:$LUCENE_HOME/build/classes

# add contrib to classpath
#for f in $LUCENE_HOME/build/lib-contrib/*.jar; do
#  CLASSPATH=${CLASSPATH}:$f;
#done

# add libs to CLASSPATH
for f in $LUCENE_HOME/lib/*.jar; do
  CLASSPATH=${CLASSPATH}:$f;
done

if [ "x$JAVA_LIBRARY_PATH" != "x" ]; then
  LUCENE_OPTS="$LUCENE_OPTS -Djava.library.path=$JAVA_LIBRARY_PATH"
fi

# figure out which class to run
if [ "$COMMAND" = "DeleteFiles" ] ; then
    CLASS=org.apache.lucene.demo.DeleteFiles
elif [ "$COMMAND" = "IndexFiles" ] ; then
    CLASS=org.apache.lucene.demo.IndexFiles
elif [ "$COMMAND" = "IndexHTML" ] ; then
    CLASS=org.apache.lucene.demo.IndexHTML
elif [ "$COMMAND" = "IndexTrec" ] ; then
    CLASS=kr.ac.kaist.demo.IndexTrec
    #CLASS=org.apache.lucene.demo.IndexTrec
elif [ "$COMMAND" = "SearchFiles" ] ; then
    CLASS=kr.ac.kaist.demo.SearchFiles
elif [ "$COMMAND" = "HTMLParseTest" ] ; then
    CLASS=org.apache.lucene.demo.html.Test
elif [ "$COMMAND" = "PorterStemmer" ] ; then
    CLASS=org.apache.lucene.analysis.PorterStemmer
elif [ "$COMMAND" = "CheckIndex" ] ; then
    CLASS=org.apache.lucene.index.CheckIndex
elif [ "$COMMAND" = "IndexReader" ] ; then
    CLASS=org.apache.lucene.IndexReader
elif [ "$COMMAND" = "QueryParser" ] ; then
    CLASS=org.apache.lucene.queryParser.QueryParser
elif [ "$COMMAND" = "English" ] ; then
    CLASS=org.apache.lucene.util.English
elif [ "$COMMAND" = "PatternParser" ] ; then
    CLASS=org.apache.lucene.analysis.compound.hyphenation.PatternParser
elif [ "$COMMAND" = "TernaryTree" ] ; then
    CLASS=org.apache.lucene.analysis.compound.hyphenation.TernaryTree
elif [ "$COMMAND" = "Benchmark" ] ; then
    CLASS=org.apache.lucene.benchmark.byTask.Benchmark
elif [ "$COMMAND" = "precisionrecall" ] ; then
    CLASS=kr.ac.kaist.demo.PrecisionRecall
elif [ "$COMMAND" = "EvaluationTrec" ] ; then
    CLASS=kr.ac.kaist.demo.EvaluationTrec
elif [ "$COMMAND" = "programSample" ] ; then
    CLASS=org.apache.lucene.benchmark.byTask.programmatic.Sample
elif [ "$COMMAND" = "QueryDriver" ] ; then
    CLASS=org.apache.lucene.benchmark.quality.trec.QueryDriver
elif [ "$COMMAND" = "QualityQueriesFinder" ] ; then
    CLASS=org.apache.lucene.benchmark.quality.utils.QualityQueriesFinder
elif [ "$COMMAND" = "ExtractReuters" ] ; then
    CLASS=org.apache.lucene.benchmark.utils.ExtractWikipedia
elif [ "$COMMAND" = "ExtractWikipedia" ] ; then
    CLASS=org.apache.lucene.benchmark.utils.ExtractWikipedia
elif [ "$COMMAND" = "SanityLoadLibrary" ] ; then
    CLASS=org.apache.lucene.store.db.SanityLoadLibrary
elif [ "$COMMAND" = "FieldTermStack" ] ; then
    CLASS=org.apache.lucene.search.vectorhighlight.FieldTermStack
elif [ "$COMMAND" = "Lucli" ] ; then
    CLASS=lucli.Lucli
elif [ "$COMMAND" = "FieldNormModifier" ] ; then
    CLASS=org.apache.lucene.index.FieldNormModifier
elif [ "$COMMAND" = "IndexSplitter" ] ; then
    CLASS=org.apache.lucene.index.IndexSplitter
elif [ "$COMMAND" = "MultiPassIndexSplitter" ] ; then
    CLASS=org.apache.lucene.index.MultiPassIndexSplitter
elif [ "$COMMAND" = "HighFreqTerms" ] ; then
    CLASS=org.apache.lucene.misc.HighFreqTerms
elif [ "$COMMAND" = "IndexMergeTool" ] ; then
    CLASS=org.apache.lucene.misc.IndexMergeTool
elif [ "$COMMAND" = "PrecedenceQueryParser" ] ; then
    CLASS=org.apache.lucene.
elif [ "$COMMAND" = "MoreLikeThis" ] ; then
    CLASS=org.apache.lucene.search.similar.MoreLikeThis
elif [ "$COMMAND" = "RemoteSearchable" ] ; then
    CLASS=org.apache.lucene.search.RemoteSearchable
elif [ "$COMMAND" = "TestRemoteSort" ] ; then
    CLASS=org.apache.lucene.search.TestRemoteSort
elif [ "$COMMAND" = "SnowballTestApp" ] ; then
    CLASS=org.tartarus.snowball.TestApp
elif [ "$COMMAND" = "GeoHashUtils" ] ; then
    CLASS=org.apache.lucene.spatial.geohash.GeoHashUtils
elif [ "$COMMAND" = "ListSearcherSimulater" ] ; then
    CLASS=org.apache.lucene.swing.models.ListSearcherSimulator
elif [ "$COMMAND" = "SynExpand" ] ; then
    CLASS=org.apache.lucene.wordnet.SynExpand
elif [ "$COMMAND" = "SynLookup" ] ; then
    CLASS=org.apache.lucene.wordnet.SynLookup
elif [ "$COMMAND" = "Syns2Index" ] ; then
    CLASS=org.apache.lucene.wordnet.Syns2Index
else
  CLASS=$COMMAND
fi

exec "$JAVA" $JAVA_HEAP_MAX $LUCENE_OPTS -cp "$CLASSPATH" $CLASS "$@"