LuceneとChemistry Development Kitを使用して分子構造検索を整理する

Luceneの全文検索ライブラリは、テキストドキュメントの検索を整理する機能を提供します。 OpenBabelなど、「類似した」化学構造の検索を整理できるツールもあります。 場合によっては、これら2つのタイプの検索を1つの「概要」にまとめる必要があります。 たとえば、そのような要求に答えることができるシステムを作成する必要がある場合:テキスト記述で、構造的にインドールに似た「アミノ酸」という単語がある物質を見つけます(アミノ酸トリプトファンが見つかると予想されます)。 この記事では、フルテキストエンジンLuceneに基づくこの問題の解決策について説明します。



ご存知のように、全文検索は逆索引の構築に基づいています。 文書のテキストは個別の単語に分割され、それぞれの単語に対して、単語が出現する文書のリストが作成されます。 通常、インデックスには、特定の単語のドキュメントのリストに加えて、各ドキュメント内の単語の位置が含まれます。



データベース内の類似分子の検索は、「分子インプリント」に基づいて行うことができます。 多くの場合、「分子フィンガープリント」は、各ビットが特定のプロパティまたは構造フラグメントの存在に対応するビット列です。 タニモト係数( ジャカード係数の特殊なケース)は、近接性の尺度として使用されます。



分子指紋の簡単な例として、これを考慮してください。長さnの分子指紋が必要だとします。 構造は、長さがk以下の1次元の原子チェーンに切断されます。 ハッシュ関数が結果の各チェーンに適用され、チェーンを0〜n-1の数値にマッピングします。 この番号は、指紋で1に設定されるビット番号を決定します。



化学構造を使用したこのような操作には、java-library Chemistry Development Kit (以降、単にCDK)を使用できます。 CDKはLGPLライセンスの下で配布され、構造を表現し、いくつかのタイプの分子フィンガープリントを計算するためのJavaクラスを含み、多くの化学ファイル形式をサポートしています。 たとえば、分子をフラグメントに切り分けるには、次のように実装できます。



/     CDK    IAtomContainer. IAtomContainer structure; ... for (IAtom startAtom : structure.atoms()) { //    CDK org.openscience.cdk.graph.PathTools,    getPathsOfLengthUpto,     . List<List<IAtom>> paths = PathTools.getPathsOfLengthUpto(structure, startAtom, searchDepth); //  -    ... }
      
      





説明されている「分子インプリント」は、CDKクラスorg.openscience.cdk.fingerprint.Fingerprinterを使用して計算されます。



Luceneを使用して構造を検索するために、指紋による構造の検索を一種の「単語検索」に再定式化します。 上記のインプリント構築の方法のように、構造は短い1次元のチェーンにカットされます。これは、テキスト検索での単語の類似物です。 したがって、システムに検索クエリとして別の構造を提供すると、クエリ構造もチェーンに分割され、結果は同じ原子チェーンを持つ構造になります。 関連性によって文書を配置するメカニズムは、結果の最初の位置に、一方では多くの一致があり、他方ではこれらの一致がわずかに薄められている構造をもたらすよう努めます。 つまり、そもそもまったく同じ構造(もちろん、インデックス付けされた構造の中にある場合)、次に1つの原子またはグループが異なる類似の構造、さらにはさらに異なる構造などがあることを期待するのは論理的です。



Luceneで使用されるデフォルトのランキング式(ベクトル空間モデル+ TF-IDF)は、Tanimoto近接測定とは異なることに注意してください。 LuceneからTanimoto係数式にSimilarityクラスを再定義できますが、特に式はデフォルトで「定性的に正しい」結果を提供するはずなので、これで終わりではありません。フラグメント交差の割合が大きい構造が結果として最初の場所にあります。



分子のインデックス作成と検索に対するこのアプローチは、Lucene向けの特別な「 化学トークナイザー 」を作成することで実装できます。 トークナイザーはLuceneコンポーネントで、テキストを個別のトークンに分割します(ほとんどの場合、トークンは単語です)。 このトークナイザーの基本的なロジックについては既に説明しました。 違いは、Luceneトークナイザーでは、サイクル内のすべてのトークンを一度に受け取る必要がないことです。 代わりに、 Tokenizer.incrementToken()メソッドを実装する必要があります。このメソッドは、アトミックチェーンのテキスト表現を1つずつ作成します。



分子をテキスト文字列として表すには、いくつかの形式があります。 ここまでは、そのうちの1つ( SMILES形式)のみのサポートに焦点を当てます。 CDKはSMILES文字列パーサー(クラスorg.openscience.cdk.smiles.SmilesParser)を提供します。 彼らが使用することは難しくありません:



 SmilesParser sp = new SmilesParser(DefaultChemObjectBuilder.getInstance()); String smiles = "OCC(O)C(O)C(O)C(O)CO"; //Sorbitol IAtomContainer structure = sp.parseSmiles( smiles ); //  -    ...
      
      





分子のSMILES表現を構造に変換して、さらにチェーントークンに分割することを目的としたロジックは、 SmilesTokenizerクラスに配置されます。



私は自分自身に多くの重要でない詳細を省略することを許可します 。ソースコードはGitHubで利用できるとしか言えません。 これらのクラスを使用して構造を検索する例に直接進みます。 検索ドキュメントに3つのフィールドが含まれているとします。nameは化合物の名前、descriptionはテキストの説明、smilesフィールドは分子の構造に関する情報です。



ドキュメントのインデックス作成は、次のようになります( SmilesTokenizerを作成するSmilesAnalyzerヘルパークラスを使用)。

 Map<String,Analyzer> analyzerPerField = new HashMap<String,Analyzer>(); analyzerPerField.put(SMILES_FIELD, new SmilesAnalyzer() ); PerFieldAnalyzerWrapper analyzerWrapper = new PerFieldAnalyzerWrapper(new StandardAnalyzer(Version.LUCENE_40), analyzerPerField); Document doc = new Document(); doc.add( new TextField( "name", "Acetic acid", Field.Store.YES ) ); doc.add( new TextField( "description", "Acetic acid is one of the simplest carboxylic acids. Liquid.", Field.Store.YES ) ); doc.add( new TextField( "smiles", "CC(O)=O", Field.Store.YES ) ); Directory directory = null; IndexWriter indexWriter = null; try { directory = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_40, analyzerWrapper ); indexWriter = new IndexWriter( directory, config); indexWriter.addDocument(doc); } ...
      
      





検索は次のようになります。

 reader = IndexReader.open(directory ); IndexSearcher searcher = new IndexSearcher(reader); String querystr = "smiles:CCC"; Query q = null; q = new QueryParser(Version.LUCENE_40, FREE_TEXT_FIELD, getAnalyzer()).parse(querystr); TopScoreDocCollector collector = TopScoreDocCollector.create(5, true); searcher.search(q, collector); ScoreDoc[] hits = collector.topDocs().scoreDocs; // -      hits ...
      
      






All Articles