Pythonを始めたばかりですが、メモ化とは何か、どのように使用するのかわかりません。また、簡単な例がありますか?
Pythonを始めたばかりですが、メモ化とは何か、どのように使用するのかわかりません。また、簡単な例がありますか?
回答:
メモ化とは、メソッドの入力に基づいてメソッド呼び出しの結果を記憶(「メモ」→「メモ」→記憶)することを意味し、結果を再計算するのではなく、記憶された結果を返します。メソッドの結果のキャッシュと考えることができます。詳細については、アルゴリズムの概要(3e)、Cormenらの定義の387ページを参照してください。
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)
「デコレータ」と呼ばれる機能がPython 2.4に追加されました。これにより、次のように記述するだけで同じことを実行できます。
@Memoize
def factorial(k):
if k < 2: return 1
return k * factorial(k - 1)
Pythonのデコレータライブラリと呼ばれる同様のデコレータ持ってmemoized
少しより堅牢よりもMemoize
ここに示されているクラスを。
factorial_memo
、factorial
内部はdef factorial
まだ古いunmemoizeを呼び出しているため、と同じようには機能しませんfactorial
。
if k not in factorial_memo:
、を読むこともできますif not k in factorial_memo:
。
args
が、タプルです。def some_function(*args)
argsをタプルにします。
Python 3.2の新機能はfunctools.lru_cache
です。デフォルトでは、最近使用された128の呼び出しのみがキャッシュmaxsize
さNone
れますが、キャッシュを期限切れにしないことを示すようにを設定できます。
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
アノテーションを追加すると、特定の値に対して関数が最近呼び出された場合、その値は再計算されず、キャッシュされた以前の結果が使用されます。この場合、コードがキャッシングの詳細で乱雑になることはありませんが、速度が大幅に向上します。
fib
呼び出されたときは、メモ化が行われる前に、基本ケースまで再帰する必要があります。だから、あなたの行動はほぼ期待どおりです。
メモ化とは、継続的に再計算するのではなく、高価な計算の結果を保持し、キャッシュされた結果を返すことです。
次に例を示します。
def doSomeExpensiveCalculation(self, input):
if input not in self.cache:
<do expensive calculation>
self.cache[input] = result
return self.cache[input]
より完全な説明は、メモ化に関するウィキペディアのエントリにあります。
if input not in self.cache
と self.cache[input]
(has_key
以降...早期2.xシリーズでは廃止され、そうでない場合は2.0。 self.cache(index)
正しいことはなかったIIRC。)
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]
これは非常に便利だと思いました
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)
functools.wraps
。
memo
メモリを解放するために手動でクリアする必要がありますか?
メモ化とは、基本的に、後の段階で同じ計算が必要な場合に再帰ツリーをたどる必要性を減らすために、再帰アルゴリズムで行われた過去の操作の結果を保存することです。
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]
さて、私は最初の部分に最初に答える必要があります:メモ化とは何ですか?
それは、時間とメモリを交換する方法にすぎません。考えて乗算表。
Pythonでデフォルト値として可変オブジェクトを使用することは、通常は悪いと考えられています。しかし、賢く使用する場合、実際にを実装すると便利ですmemoization
。
これは、http://docs.python.org/2/faq/design.html#why-are-default-values-shared-between-objectsから変更された例です
dict
関数定義で可変を使用すると、中間の計算結果をキャッシュできます(たとえば、calculateの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]
以下は、うなりを出さずにリスト型またはdict型の引数を処理するソリューションです。
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.__name__ = 'memoized_' + fn.__name__
return foo
このアプローチは、handle_itemの特殊なケースとして独自のハッシュ関数を実装することで、任意のオブジェクトに自然に拡張できることに注意してください。たとえば、セットを入力引数として取る関数でこのアプローチを機能させるには、handle_itemに次のように追加します。
if is_instance(x, set):
return make_tuple(sorted(list(x)))
list
引数は、の値を持つ[1, 2, 3]
別のset
引数と誤って同じと見なされる可能性があります{1, 2, 3}
。また、セットは辞書のように順序付けされていないため、も必要sorted()
です。また、再帰的なデータ構造引数は無限ループを引き起こすことにも注意してください。
list
sとset
sが同じものに「タプル化」され、したがって互いに区別できなくなるという事実によるものです。sets
最新のアップデートで説明されているサポートを追加するためのサンプルコードは、私が恐れていることを避けていません。これは、個別に「メモ化」されたテスト関数に引数として渡して[1,2,3]
、{1,2,3}
2回呼び出されるかどうかを確認することで簡単に確認できます。
list
sとdict
sにも同様の問題があります。これは、aが辞書を呼び出した結果としてまったく同じものを持つ可能性があるlist
ためmake_tuple(sorted(x.items()))
です。どちらの場合も簡単な解決策は、type()
生成されたタプルにof値を含めることです。具体的にはset
s を処理するさらに簡単な方法を考えることができますが、それは一般化されていません。
キーワード引数が渡された順序とは関係なく、位置引数とキーワード引数の両方で機能するソリューション(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デコレータライブラリには、とは異なり、「ハッシュ化できない型」をメモすることもできるシンプルでありながら便利な実装がいくつかありfunctools.lru_cache
ます。