لدي قائمة قواميس. أحاول استخدام np.vectorize لتطبيق دالة تعدل عناصر القاموس لكل قاموس في القائمة. يبدو أن النتائج تظهر أن Vectorize يعمل مرتين على العنصر الأول. هل هذا خطأ يمكن إصلاحه؟ (ربما يكون مرتبطًا بحقيقة أن عمليات التحقق من نوع Vectorize على العنصر الأول؟) فيما يلي بعض الأمثلة على الحالات والإخراج:
حالة اختبار بسيطة بدون تعديلات في القاموس:
def fcn1(x):
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn1)(a))
print(a, '\n\n')
انتاج:
[{'b': 1}, {'b': 1}, {'b': 1}]
[1 1 1]
[{'b': 1}, {'b': 1}, {'b': 1}]
الآن قم بتعديل القاموس ولاحظ أن الوظيفة يتم تطبيقها مرتين على العنصر الأول:
def fcn2(x):
x['b'] += 1
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn2)(a))
print(a, '\n\n')
انتاج:
[{'b': 1}, {'b': 1}, {'b': 1}]
[3 2 2]
[{'b': 3}, {'b': 2}, {'b': 2}]
جرب تعديلًا مختلفًا للتحقق من اتساق الخطأ:
def fcn3(x):
x['b'] *= 2
return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn3)(a))
print(a, '\n\n')
انتاج:
[{'b': 1}, {'b': 1}, {'b': 1}]
[4 2 2]
[{'b': 4}, {'b': 2}, {'b': 2}]
يمكنك فعل الشيء نفسه دون تقديم قيمة مرتجعة (وهي الطريقة التي أحاول استخدامها في حالة الاستخدام الخاصة بي):
def fcn4(x):
x['b'] += 1
a = [{'b': 1} for _ in range(3) ]
print(a)
np.vectorize(fcn4)(a)
print(a, '\n\n')
انتاج:
[{'b': 1}, {'b': 1}, {'b': 1}]
[{'b': 3}, {'b': 2}, {'b': 2}]
وبالمناسبة ، لا يوجد شيء مميز في قائمة الطول 3 ، يمكنك تغيير ذلك ورؤية نفس سلوك العنصر الأول فقط الذي يتم تعديله مرتين.
لقد تأكدت من السلوك باستخدام الإصدارين 1.11.3 و 1.12.0 من Numpy
تعديل:
لقد عثرت على حل بديل يؤكد أيضًا أنه مشكلة "اختبار النوع على العنصر الأول". إذا حددت الوسيطة otypes
، فلن يتم ضرب العنصر الأول مرتين:
def fcn(x):
x['b'] += 1
return x['b']
a = [{'b': 1} for _ in range(3)]
print a
print np.vectorize(fcn, otypes=[dict])(a)
print a, '\n\n'
انتاج:
[{'b': 1}, {'b': 1}, {'b': 1}]
[2 2 2]
[{'b': 2}, {'b': 2}, {'b': 2}]
لقد قمت للتو بتحرير المنشور الخاص بي باختبار جديد يبدو أنه يؤكد أن التحقق من نوع العنصر الأول هو سبب المشكلة
أبسط حالة الاختبار:
a = np.array([1, 2, 3])
def f(x):
print('got', x)
return x
fv = np.vectorize(f)
y = fv(a)
يعطي:
got 1
got 1
got 2
got 3
"إذا لم يتم تحديد أنواع otypes ، فسيتم استخدام استدعاء للدالة مع الوسيطة الأولى لتحديد عدد المخرجات. سيتم تخزين نتائج هذه الاستدعاء مؤقتًا إذا كانت ذاكرة التخزين المؤقت True لمنع استدعاء الوظيفة مرتين. ومع ذلك ، للتنفيذ ذاكرة التخزين المؤقت ، يجب تغليف الوظيفة الأصلية مما يؤدي إلى إبطاء المكالمات اللاحقة ، لذلك لا تفعل ذلك إلا إذا كانت وظيفتك باهظة الثمن. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html
لذلك فهو سلوك موثق جيدًا ، لكنه يبدو غير بديهي. لذلك ربما يكون هذا تحسينًا أكثر من كونه إصلاحًا للأخطاء.
آه ، من الواضح أنني لم أقرأ جميع المستندات جيدًا بما يكفي. يؤدي ذلك إلى إصلاح مشكلة المكالمات المزدوجة ولكن على حساب التنفيذ البطيء. لذا أعتقد أنه لا توجد طريقة لمنع هذا الاتصال المزدوج مع الحفاظ على الأداء؟
على سبيل المثال ، في numpy/lib/function_base.py
في وظيفة فئة vectorize
_get_ufunc_and_otypes()
، أعتقد بسذاجة أنه يمكنك تعديل هذه الأسطر:
inputs = [arg.flat[0] for arg in args]
outputs = func(*inputs)
إلى:
#earlier
import copy
...
inputs = copy.deepcopy([arg.flat[0] for arg in args])
outputs = func(*inputs)
وبعد ذلك لن تضطر إلى استخدام التخزين المؤقت أو تحديد أنواع otypes ولكن أعتقد أنك تتجنب ضرب العناصر القابلة للتغيير الفعلية مرتين. لكنني أجهل مقدار نجاح الأداء الذي قد يُعطي مقارنةً بالتخزين المؤقت.
لقد فهمت للتو أن سلوك التخزين المؤقت تم تصميمه مع وضع وقت تنفيذ الوظيفة الباهظ في الاعتبار ، وليس التفكير في حالة وظيفة تعدل كائنًا قابل للتغيير. أعتقد أنه من المحتمل أن تتكيف مع تعديل الكائن القابل للتغيير دون أداء أداء التخزين المؤقت الذي كان في الاعتبار وقت تنفيذ وظيفة طويل.
أعتقد أنه بالنسبة للعمليات المتجهية على مصفوفات كائنات كبيرة جدًا ، فقد يكون الأمر أكثر كثافة من الناحية الحسابية لإنشاء نسخة عميقة من العنصر الأول أكثر من تكلفة تطبيق الوظيفة. لذلك قد يكون من الجيد أن يكون لديك هذه الوظيفة إذا لم يحدد المستخدم otype
، ولكن بعد ذلك قل في الوثائق أن الأداء قد يأخذ نجاحًا ما لم يتم تحديد otype
لمصفوفات كبيرة شاء. @ eric-wieser ما رأيك؟
copy.deepcopy()
غير آمن على العديد من المدخلات ، لذلك للأسف هذا ليس خيارًا قابلاً للتطبيق.
الطريقة الوحيدة لإصلاح ذلك هي إعادة كتابة جوهر vectorize
، والذي يستخدم حاليًا numpy.frompyfunc
لإنشاء ufunc فعليًا. يجب إنشاء حلقة داخلية بديلة ، على غرار ما نستخدمه لـ np.apply_along_axis
. لسوء الحظ ، لتجنب تدهور الأداء ، أعتقد أننا سنحتاج إلى تنفيذ الحلقة في لغة C (مثل frompyfunc
في ufunc it builds).
التعليق الأكثر فائدة
"إذا لم يتم تحديد أنواع otypes ، فسيتم استخدام استدعاء للدالة مع الوسيطة الأولى لتحديد عدد المخرجات. سيتم تخزين نتائج هذه الاستدعاء مؤقتًا إذا كانت ذاكرة التخزين المؤقت True لمنع استدعاء الوظيفة مرتين. ومع ذلك ، للتنفيذ ذاكرة التخزين المؤقت ، يجب تغليف الوظيفة الأصلية مما يؤدي إلى إبطاء المكالمات اللاحقة ، لذلك لا تفعل ذلك إلا إذا كانت وظيفتك باهظة الثمن. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html
لذلك فهو سلوك موثق جيدًا ، لكنه يبدو غير بديهي. لذلك ربما يكون هذا تحسينًا أكثر من كونه إصلاحًا للأخطاء.