Numpy: BUG: تعمل وظائف np.vectorize () مرتين على العنصر الأول (كما يظهر عندما تعدل الوظيفة كائنًا قابل للتغيير)

تم إنشاؤها على ٨ مارس ٢٠١٧  ·  7تعليقات  ·  مصدر: numpy/numpy

لدي قائمة قواميس. أحاول استخدام 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}]
00 - Bug

التعليق الأكثر فائدة

"إذا لم يتم تحديد أنواع otypes ، فسيتم استخدام استدعاء للدالة مع الوسيطة الأولى لتحديد عدد المخرجات. سيتم تخزين نتائج هذه الاستدعاء مؤقتًا إذا كانت ذاكرة التخزين المؤقت True لمنع استدعاء الوظيفة مرتين. ومع ذلك ، للتنفيذ ذاكرة التخزين المؤقت ، يجب تغليف الوظيفة الأصلية مما يؤدي إلى إبطاء المكالمات اللاحقة ، لذلك لا تفعل ذلك إلا إذا كانت وظيفتك باهظة الثمن. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

لذلك فهو سلوك موثق جيدًا ، لكنه يبدو غير بديهي. لذلك ربما يكون هذا تحسينًا أكثر من كونه إصلاحًا للأخطاء.

ال 7 كومينتر

لقد قمت للتو بتحرير المنشور الخاص بي باختبار جديد يبدو أنه يؤكد أن التحقق من نوع العنصر الأول هو سبب المشكلة

أبسط حالة الاختبار:

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).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

marcocaccin picture marcocaccin  ·  4تعليقات

dmvianna picture dmvianna  ·  4تعليقات

qualiaa picture qualiaa  ·  3تعليقات

toddrjen picture toddrjen  ·  4تعليقات

kevinzhai80 picture kevinzhai80  ·  4تعليقات