9

それぞれ約500万文字の長さの3つの単語を反復処理しており、各単語を識別する20文字のシーケンスを見つけたいと考えています。つまり、長さ20のすべてのシーケンスを、その単語に固有の1つの単語で検索したいと思います。私の問題は、私が書いたコードの実行に非常に長い時間がかかることです。私は一晩で私のプログラムを実行している一言も完了したことがありません。

以下の関数は、辞書を含むリストを取得します。各辞書には、20の可能な各単語と、500万の長い単語の1つからの位置が含まれています。

誰かがこれを最適化する方法を知っているなら、私は本当に感謝するでしょう、私は続行する方法の手がかりがありません...

これが私のコードのサンプルです:

def findUnique(list):
    # Takes a list with dictionaries and compairs each element in the dictionaries
    # with the others and puts all unique element in new dictionaries and finally
    # puts the new dictionaries in a list.
    # The result is a list with (in this case) 3 dictionaries containing all unique
    # sequences and their locations from each string.
    dicList=[]
    listlength=len(list)
    s=0
    valuelist=[]
    for i in list:
        j=i.values()
        valuelist.append(j)
    while s<listlength:
        currdic=list[s]
        dic={}
        for key in currdic:
            currval=currdic[key]
            test=True
            n=0
            while n<listlength:
                if n!=s:
                    if currval in valuelist[n]: #this is where it takes to much time
                        n=listlength
                        test=False
                    else:
                        n+=1
                else:
                    n+=1
            if test:
                dic[key]=currval
        dicList.append(dic)
        s+=1
    return dicList
4

4 に答える 4

10
def slices(seq, length, prefer_last=False):
  unique = {}
  if prefer_last: # this doesn't have to be a parameter, just choose one
    for start in xrange(len(seq) - length + 1):
      unique[seq[start:start+length]] = start
  else: # prefer first
    for start in xrange(len(seq) - length, -1, -1):
      unique[seq[start:start+length]] = start
  return unique

# or find all locations for each slice:
import collections
def slices(seq, length):
  unique = collections.defaultdict(list)
  for start in xrange(len(seq) - length + 1):
    unique[seq[start:start+length]].append(start)
  return unique

この関数 (現在、私のiter_util モジュール内) は O(n) (n は各単語の長さ) であり、set(slices(..))(差分などの集合操作で) 使用して、すべての単語で一意のスライスを取得します (以下の例)。位置を追跡したくない場合は、セットを返す関数を作成することもできます。メモリ使用量は高く (依然として O(n) ですが、大きな要因です)、ベース シーケンス (文字列) を格納する特別な「レイジー スライス」クラスを使用すると軽減される可能性があります (ただし、長さが 20 だけの場合はそれほどではありません)。 start と stop (または start と length)。

ユニークなスライスの印刷:

a = set(slices("aab", 2)) # {"aa", "ab"}
b = set(slices("abb", 2)) # {"ab", "bb"}
c = set(slices("abc", 2)) # {"ab", "bc"}
all = [a, b, c]
import operator
a_unique = reduce(operator.sub, (x for x in all if x is not a), a)
print a_unique # {"aa"}

場所を含む:

a = slices("aab", 2)
b = slices("abb", 2)
c = slices("abc", 2)
all = [a, b, c]
import operator
a_unique = reduce(operator.sub, (set(x) for x in all if x is not a), set(a))
# a_unique is only the keys so far
a_unique = dict((k, a[k]) for k in a_unique)
# now it's a dict of slice -> location(s)
print a_unique # {"aa": 0} or {"aa": [0]}
               # (depending on which slices function used)

ランダムに生成された 500 万文字の単語と 20 のスライス長を使用する、条件に近いテスト スクリプトでは、メモリ使用量が非常に高く、テスト スクリプトはすぐに 1G のメイン メモリ制限に達し、仮想メモリをスラッシングし始めました。その時点で、Python は CPU にほとんど時間を費やしていなかったので、私はそれを殺しました。メインメモリ内に収まるようにスライスの長さまたはワードの長さのいずれかを減らし(重複を減らし、メモリ使用量を増やす完全にランダムなワードを使用したため)、1分未満で実行されました。この状況と元のコードの O(n**2) には永遠に時間がかかるため、アルゴリズムの時間と空間の複雑さが両方とも重要になります。

import operator
import random
import string

def slices(seq, length):
  unique = {}
  for start in xrange(len(seq) - length, -1, -1):
    unique[seq[start:start+length]] = start
  return unique

def sample_with_repeat(population, length, choice=random.choice):
  return "".join(choice(population) for _ in xrange(length))

word_length = 5*1000*1000
words = [sample_with_repeat(string.lowercase, word_length) for _ in xrange(3)]
slice_length = 20
words_slices_sets = [set(slices(x, slice_length)) for x in words]
unique_words_slices = [reduce(operator.sub,
                              (x for x in words_slices_sets if x is not n),
                              n)
                       for n in words_slices_sets]
print [len(x) for x in unique_words_slices]
于 2009-12-21T18:42:06.653 に答える
0

あなたは500万文字の「単語」を持っていると言っていますが、これが通常の意味での単語であるとは信じがたいです.

入力データに関する詳細情報を提供できる場合は、特定の解決策が利用できる可能性があります。

たとえば、英語のテキスト (またはその他の書き言葉) は、トライが使用できるほど十分に反復的である可能性があります。ただし、最悪の場合、256^20 のすべてのキーを構築するためにメモリが不足します。自分のインプットを知ることは、すべての違いを生み出します。


編集

ハードコーディングされた [acgt]->[0123] マッピングとトライ ノードごとに 4 つの子を使用して、このアイデアがどのように積み上げられたかを確認するために、いくつかのゲノム データを調べました。

  1. アデノウイルス 2 : 35,937bp -> 469,339 のトライノードを使用した 35,899 の異なる 20 塩基配列

  2. 腸内細菌ファージ ラムダ: 48,502bp -> 40,921 個の異なる 20 塩基配列 (529,384 個のトライ ノードを使用)。

データに冗長性や重複がある可能性がありますが、2 つのデータ セット内またはデータ セット間で衝突は発生しませんでした。試してみる必要があります。

有効な数の衝突が得られた場合は、3 つの入力を一緒に歩いて、1 つのトライを構築し、各葉の起点を記録し、トライからコリジョンを取り除きます。

キーを削除する方法が見つからない場合は、よりコンパクトな表現を使用してみてください。たとえば、[acgt]/[0123] を格納するのに 2 ビットしか必要としないため、コードが少し複雑になりますが、スペースを節約できます。

ただし、これを総当たりできるとは思いません。問題の規模を縮小する方法を見つける必要があり、それはドメインの知識に依存します。

于 2009-12-22T14:56:16.647 に答える
0

ロジャー・ペイトの答えを構築させてください。メモリが問題になる場合は、文字列を辞書のキーとして使用する代わりに、文字列のハッシュ値を使用することをお勧めします。これにより、文字列の余分なコピーをキーとして保存するコストを節約できます (最悪の場合、個々の「単語」の保存の 20 倍)。

import collections
def hashed_slices(seq, length, hasher=None):
  unique = collections.defaultdict(list)
  for start in xrange(len(seq) - length + 1):
    unique[hasher(seq[start:start+length])].append(start)
  return unique

(本当に凝りたい場合は、ローリング ハッシュを使用できますが、関数を変更する必要があります。)

これで、すべてのハッシュを組み合わせることができます:

unique = []  # Unique words in first string

# create a dictionary of hash values -> word index -> start position
hashed_starts = [hashed_slices(word, 20, hashing_fcn) for word in words]
all_hashed = collections.defaultdict(dict)
for i, hashed in enumerate(hashed_starts) :
   for h, starts in hashed.iteritems() :
     # We only care about the first word
     if h in hashed_starts[0] :
       all_hashed[h][i]=starts

# Now check all hashes
for starts_by_word in all_hashed.itervalues() :
  if len(starts_by_word) == 1 :
    # if there's only one word for the hash, it's obviously valid
    unique.extend(words[0][i:i+20] for i in starts_by_word.values())
  else :
    # we might have a hash collision
    candidates = {}
    for word_idx, starts in starts_by_word.iteritems() :
      candidates[word_idx] = set(words[word_idx][j:j+20] for j in starts)
    # Now go that we have the candidate slices, find the unique ones
    valid = candidates[0]
    for word_idx, candidate_set in candidates.iteritems() :
      if word_idx != 0 :
        valid -= candidate_set
    unique.extend(valid)

(3 つすべてを実行するように拡張してみました。可能ですが、複雑さによってアルゴリズムが損なわれる可能性があります。)

注意してください、私はこれをテストしていません。また、コードを単純化するためにできることはおそらくたくさんありますが、アルゴリズムは理にかなっています。難しいのは、ハッシュの選択です。衝突が多すぎると、何も得られません。少なすぎると、メモリの問題が発生します。DNA ベース コードだけを扱っている場合は、20 文字の文字列を 40 ビットの数値にハッシュしても衝突は発生しません。したがって、スライスはメモリのほぼ 4 分の 1 を占有します。これにより、Roger Pate の回答で約 250 MB のメモリが節約されます。

コードはまだ O(N^2) ですが、定数ははるかに低くなるはずです。

于 2009-12-22T21:53:46.433 に答える
0

Roger Pate の優れた回答を改善してみましょう。

まず、辞書の代わりにセットを保持しましょう - とにかく一意性を管理します。

第二に、CPU 時間 (および忍耐) が不足するよりも早くメモリが不足する可能性が高いため、メモリ効率のために CPU 効率を犠牲にすることができます。したがって、特定の 1 文字で始まる 20 だけを試してみてください。DNA の場合、これにより要件が 75% 削減されます。

seqlen = 20
maxlength = max([len(word) for word in words])
for startletter in letters:
    for letterid in range(maxlength):
        for wordid,word in words:
            if (letterid < len(word)):
                letter = word[letterid]
                if letter is startletter:
                    seq = word[letterid:letterid+seqlen]
                    if seq in seqtrie and not wordid in seqtrie[seq]:
                        seqtrie[seq].append(wordid)

または、それでもメモリが多すぎる場合は、可能な開始ペアごとに (DNA の 4 ではなく 16 パス)、または 3 つごとに (64 パス) などを実行できます。

于 2009-12-28T22:39:47.090 に答える