web-dev-qa-db-ja.com

メモ化とは何ですか?またPythonでそれを使用するにはどうすればよいですか?

私はPythonを始めたばかりで、 memoization とは何か、そしてそれをどのように使うのかわかりません。また、簡単な例がありますか。

331
blur959

メモ化とは、メソッド入力に基づいてメソッド呼び出しの結果を覚えて(「メモ」→「メモ」→覚えておく)、結果を再度計算するのではなく返すことを意味します。あなたはそれをメソッド結果のキャッシュと考えることができます。詳細については、アルゴリズムの紹介(3e)のCormen et alの定義について387ページを参照してください。 al。

Pythonでメモ化を使用して階乗を計算する簡単な例は、次のようになります。

factorial_memo = {}
def factorial(k):
    if k < 2: return 1
    if k not in factorial_memo:
        factorial_memo[k] = k * factorial(k-1)
    return factorial_memo[k]

もっと複雑になり、メモ化プロセスをクラスにカプセル化できます。

class Memoize:
    def __init__(self, f):
        self.f = f
        self.memo = {}
    def __call__(self, *args):
        if not args in self.memo:
            self.memo[args] = self.f(*args)
        #Warning: You may wish to do a deepcopy here if returning objects
        return self.memo[args]

その後:

def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

factorial = Memoize(factorial)

" decorators "として知られる機能がPython 2.4で追加されました。これにより、同じことを実現するために次のように書くことができます。

@Memoize
def factorial(k):
    if k < 2: return 1
    return k * factorial(k - 1)

Python Decorator Library には memoized という同様のデコレータがあります。これは、ここに示すMemoizeクラスよりも若干堅牢です。

325
jason

Python 3.2で新しく追加されたのは functools.lru_cache です。デフォルトでは、最近使用された128個の呼び出しのみをキャッシュしますが、キャッシュが期限切れにならないようにmaxsizeNoneに設定できます。

import functools

@functools.lru_cache(maxsize=None)
def fib(num):
    if num < 2:
        return num
    else:
        return fib(num-1) + fib(num-2)

この機能自体は非常に遅いので、fib(36)を試してみてください。約10秒待つ必要があります。

lru_cacheアノテーションを追加すると、関数が特定の値に対して最近呼び出された場合、その値を再計算するのではなく、以前にキャッシュされた結果を使用します。この場合、コードがキャッシュの詳細に散らばっていない一方で、それは途方もない速度の向上につながります。

196
Flimm

他の答えはそれがかなりうまくいっていることをカバーしています。私はそれを繰り返さない。あなたに役立つかもしれないちょうどいくつかの点。

通常、メモ化は、何かを計算し(高価な)、値を返す任意の関数に適用できる操作です。このため、しばしば デコレータ として実装されています。実装は簡単で、これは次のようになります。

memoised_function = memoise(actual_function)

またはデコレータとして表現

@memoise
def actual_function(arg1, arg2):
   #body
59
Noufal Ibrahim

メモ化は、継続的に計算し直すのではなく、高価な計算の結果を保持してキャッシュ結果を返すことです。

これが例です:

def doSomeExpensiveCalculation(self, input):
    if input not in self.cache:
        <do expensive calculation>
        self.cache[input] = result
    return self.cache[input]

より完全な説明は、 メモ化に関するウィキペディアのエントリ にあります。

18
Bryan Oakley

手作りしたい人のために、組み込みのhasattr関数を忘れないでください。このようにして、(グローバルとは対照的に)memキャッシュを関数定義内に保持することができます。

def fact(n):
    if not hasattr(fact, 'mem'):
        fact.mem = {1: 1}
    if not n in fact.mem:
        fact.mem[n] = n * fact(n - 1)
    return fact.mem[n]
15
Karel Kubat

私はこれが非常に便利だと思った

def memoize(function):
    from functools import wraps

    memo = {}

    @wraps(function)
    def wrapper(*args):
        if args in memo:
            return memo[args]
        else:
            rv = function(*args)
            memo[args] = rv
            return rv
    return wrapper


@memoize
def fibonacci(n):
    if n < 2: return n
    return fibonacci(n - 1) + fibonacci(n - 2)

fibonacci(25)
9
mr.bjerre

メモ化は基本的に、同じ計算が後の段階で必要とされる場合に再帰木を横断する必要性を減らすために再帰的アルゴリズムで行われた過去の操作の結果を保存することです。

参照してください http://scriptbucket.wordpress.com/2012/12/11/introduction-to-memoization/

Pythonのフィボナッチメモ化の例:

fibcache = {}
def fib(num):
    if num in fibcache:
        return fibcache[num]
    else:
        fibcache[num] = num if num < 2 else fib(num-1) + fib(num-2)
        return fibcache[num]
6
Romaine Carter

さて私は最初の部分に最初に答えるべきです:何がメモ化ですか?

時間のためにメモリを交換するための方法です。考えてください 乗算テーブル

Pythonでデフォルト値として可変オブジェクトを使用することは通常悪いと見なされます。しかし、それを賢く使用するのであれば、実際にはmemoizationを実装すると便利です。

これは http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objects から改作した例です。

関数定義で可変のdictを使用して、中間の計算結果をキャッシュすることができます(例えば、factorial(10)を計算した後でfactorial(9)を計算するとき、すべての中間結果を再利用できます)。

def factorial(n, _cache={1:1}):    
    try:            
        return _cache[n]           
    except IndexError:
        _cache[n] = factorial(n-1)*n
        return _cache[n]
5
yegle

メモ化とは、関数をデータ構造に変換することです。通常、変換は段階的かつ遅延的に行われることが望まれます(特定のドメイン要素または「キー」の要求に応じて)。遅延機能言語では、この遅延変換は自動的に行われる可能性があるため、メモ化は(明示的な)副作用なしに実装できます。

5
Conal

これは泣き言を言わずにリスト型または辞書型の引数を扱う解決策です:

def memoize(fn):
    """returns a memoized version of any function that can be called
    with the same list of arguments.
    Usage: foo = memoize(foo)"""

    def handle_item(x):
        if isinstance(x, dict):
            return make_Tuple(sorted(x.items()))
        Elif hasattr(x, '__iter__'):
            return make_Tuple(x)
        else:
            return x

    def make_Tuple(L):
        return Tuple(handle_item(x) for x in L)

    def foo(*args, **kwargs):
        items_cache = make_Tuple(sorted(kwargs.items()))
        args_cache = make_Tuple(args)
        if (args_cache, items_cache) not in foo.past_calls:
            foo.past_calls[(args_cache, items_cache)] = fn(*args,**kwargs)
        return foo.past_calls[(args_cache, items_cache)]
    foo.past_calls = {}
    foo.__= 'memoized_' + fn.__name__
    return foo

独自のハッシュ関数をhandle_itemの特別なケースとして実装することで、このアプローチは任意のオブジェクトに自然に拡張できることに注意してください。たとえば、入力引数として集合を取る関数に対してこのアプローチを機能させるには、handle_itemに追加します。

if is_instance(x, set):
    return make_Tuple(sorted(list(x)))
4
RussellStewart

キーワード引数が渡された順序とは無関係に、位置引数とキーワード引数の両方を扱うソリューション( inspect.getargspec を使用):

import inspect
import functools

def memoize(fn):
    cache = fn.cache = {}
    @functools.wraps(fn)
    def memoizer(*args, **kwargs):
        kwargs.update(dict(Zip(inspect.getargspec(fn).args, args)))
        key = Tuple(kwargs.get(k, None) for k in inspect.getargspec(fn).args)
        if key not in cache:
            cache[key] = fn(**kwargs)
        return cache[key]
    return memoizer

同様の質問: Pythonでメモ化を行うための同等の可変引数関数の識別

3
ndpu

functools.lru_cacheとは異なり、 Pythonデコレータライブラリ はすでに提供されている回答に追加したいだけで、「手に負えない型」を記憶することもできる単純でありながら便利な実装をいくつか備えています。

2
Sid
cache = {}
def fib(n):
    if n <= 1:
        return n
    else:
        if n not in cache:
            cache[n] = fib(n-1) + fib(n-2)
        return cache[n]
2
Vikrant Singh