เตือนเกี่ยวกับตัวเลขที่เปิดกว้างเกินไป


166

ในสคริปต์ที่ฉันสร้างตัวเลขจำนวนมากfix, ax = plt.subplots(...)ฉันได้รับคำเตือนRuntimeWarning: เปิดตัวเลขมากกว่า 20 ตัว ตัวเลขที่สร้างผ่านส่วนต่อประสาน pyplot ( matplotlib.pyplot.figure) จะถูกเก็บไว้จนกว่าจะปิดอย่างชัดเจนและอาจใช้หน่วยความจำมากเกินไป

แต่ผมไม่เข้าใจว่าทำไมฉันจะได้รับการแจ้งเตือนนี้เพราะหลังจากบันทึกภาพกับฉันลบมันด้วยfig.savefig(...) fig.clear(); del figที่จุดในรหัสของฉันไม่มีฉันเปิดมากกว่าหนึ่งรูปในเวลา ยังฉันได้รับคำเตือนเกี่ยวกับตัวเลขที่เปิดกว้างเกินไป นั่นหมายความว่าฉันจะหลีกเลี่ยงการรับคำเตือนได้อย่างไร


9
หากคุณทำสิ่งนี้มากและไม่แสดงผลแบบโต้ตอบคุณอาจจะดีกว่าที่จะข้ามpltไปเลย เช่นstackoverflow.com/a/16337909/325565 (อย่าเสียบคำตอบของฉันเอง แต่มันเป็นคำตอบที่ฉันพบได้เร็วที่สุด ... )
Joe Kington

1
@JoeKington ขอบคุณนี่เป็นวิธีแก้ปัญหาที่ดีกว่า
hihell

คำตอบของ Joe Kington ควรอยู่ในรายการคำตอบหลัก มันใช้งานได้และยังแก้ไขปัญหาของ plt.close () ทำให้โปรแกรมช้าลงซึ่งดอนเคอร์บี้กล่าวถึง
NatalieL

คำตอบ:


199

ใช้.clfหรือ.claบนวัตถุรูปของคุณแทนที่จะสร้างรูปใหม่ จาก@DavidZwicker

สมมติว่าคุณได้นำเข้าpyplotเป็น

import matplotlib.pyplot as plt

plt.cla()ล้างแกนนั่นคือแกนที่ใช้งานในปัจจุบันในรูปปัจจุบัน มันปล่อยให้แกนอื่น ๆ ไม่มีใครแตะต้อง

plt.clf()ล้างตัวเลขปัจจุบันทั้งหมดด้วยแกนทั้งหมด แต่เปิดหน้าต่างทิ้งไว้เพื่อให้สามารถนำกลับมาใช้ใหม่สำหรับแปลงอื่นได้

plt.close()ปิดหน้าต่างซึ่งจะเป็นหน้าต่างปัจจุบันหากไม่ได้ระบุเป็นอย่างอื่น plt.close('all')จะปิดตัวเลขที่เปิดทั้งหมด

เหตุผลที่ใช้del figไม่ได้คือเครื่องpyplotรัฐทำการอ้างอิงถึงตัวเลขรอบ (เช่นถ้ามันจะต้องรู้ว่า 'ตัวเลขปัจจุบัน' คืออะไร) ซึ่งหมายความว่าแม้ว่าคุณจะลบของคุณอ้างอิงไปยังรูปภาพ แต่ก็มีการอ้างอิงสดอย่างน้อยหนึ่งครั้งดังนั้นมันจะไม่ถูกรวบรวมขยะ

เนื่องจากฉันสำรวจภูมิปัญญาโดยรวมที่นี่สำหรับคำตอบนี้ @JoeKington กล่าวถึงความคิดเห็นที่plt.close(fig)จะลบอินสแตนซ์ที่เฉพาะเจาะจงออกจากเครื่องสถานะpylab ( plt._pylab_helpers.Gcf ) และอนุญาตให้เก็บขยะได้


1
Mhh นอกจากนี้clfสำหรับfigureชั้นเรียน closeแต่ไม่ได้ เหตุใดจึงไม่del figปิดและลบตัวเลขจริง ๆ
andreas-h

2
@ andreas-h ฉันเดา: สำหรับสิ่งที่ซับซ้อนเช่นตัวจัดการหน้าต่างที่มีตัวจัดการของตัวเองอาจจำเป็นต้องมีการล้างข้อมูลมากกว่าการวางบางสิ่งออกนอกขอบเขต สิทธิของท่านที่closeจะไม่ทำงานบนวัตถุรูปที่เรียกว่าชอบแทนplt.close() fig.clf()
ติดยาเสพติด

5
@ andreas-h - โดยทั่วไปเหตุผลที่del figไม่ได้ผลคือการให้__del__วิธีการ (ซึ่งโดยทั่วไปเรียกว่าplt.close(fig)) จะทำให้เกิดการอ้างอิงแบบวงกลมในกรณีนี้โดยเฉพาะและfigการมี__del__วิธีการจะทำให้สิ่งอื่น ๆ ไม่ถูกเก็บรวบรวมขยะ . (หรือว่าเป็นความทรงจำที่คลุมเครือของฉันต่อไป.) ในอัตราใดก็แน่นอนบิตที่น่ารำคาญ แต่คุณควรจะเรียกแทนplt.close(fig) del figสังเกตด้านบน, matplotlib จริงๆอาจใช้จัดการบริบทสำหรับนี้ ...
โจ Kington

6
@Hooked - เพื่อให้ชัดเจนขึ้นคุณอาจแก้ไขคำถามของคุณเพื่อพูดถึงว่าplt.close(fig)จะลบอินสแตนซ์รูปที่เฉพาะเจาะจงออกจากเครื่องสถานะ pylab ( plt._pylab_helpers.Gcf) และอนุญาตให้รวบรวมขยะได้
Joe Kington

2
@ JoeKington pltค่อนข้างยุ่งเหยิงและมีความคิดว่าจะทำมันซ้ำอีกครั้งได้อย่างไร ผู้จัดการบริบทกำลังสนใจ .... ดูgithub.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/matplotlib/pull/2624
tacaswell

32

ต่อไปนี้เป็นรายละเอียดอีกเล็กน้อยที่จะขยายตัวในคำตอบที่ติดยาเสพติดของ ครั้งแรกที่ผมได้อ่านคำตอบที่ผมพลาดการเรียนการสอนที่จะเรียกแทนการสร้างร่างใหม่clf() clf()ด้วยตัวเองไม่ได้ช่วยถ้าคุณไปและสร้างรูปอื่น

นี่เป็นตัวอย่างเล็กน้อยที่ทำให้เกิดคำเตือน:

from matplotlib import pyplot as plt, patches
import os


def main():
    path = 'figures'
    for i in range(21):
        _fig, ax = plt.subplots()
        x = range(3*i)
        y = [n*n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.clf()
    print('Done.')

main()

เพื่อหลีกเลี่ยงการเตือนฉันต้องดึงสายไปsubplots()นอกวง เพื่อที่จะให้เห็นรูปสี่เหลี่ยมที่ฉันต้องเปลี่ยนไปclf() cla()ที่ล้างแกนโดยไม่ต้องถอดแกนเอง

from matplotlib import pyplot as plt, patches
import os


def main():
    path = 'figures'
    _fig, ax = plt.subplots()
    for i in range(21):
        x = range(3*i)
        y = [n*n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.cla()
    print('Done.')

main()

หากคุณกำลังสร้างแปลงใน batches คุณอาจจะต้องใช้ทั้งสองและcla() close()ฉันพบปัญหาที่ชุดสามารถมีมากกว่า 20 แปลงโดยไม่บ่น แต่มันจะบ่นหลังจาก 20 ชุด ฉันแก้ไขที่โดยใช้cla()หลังจากแต่ละจุดและclose()หลังแต่ละชุด

from matplotlib import pyplot as plt, patches
import os


def main():
    for i in range(21):
        print('Batch {}'.format(i))
        make_plots('figures')
    print('Done.')


def make_plots(path):
    fig, ax = plt.subplots()
    for i in range(21):
        x = range(3 * i)
        y = [n * n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.cla()
    plt.close(fig)


main()

ฉันวัดประสิทธิภาพเพื่อดูว่ามันคุ้มค่าที่จะนำตัวเลขกลับมาใช้ในชุดใหม่หรือไม่และโปรแกรมตัวอย่างเล็ก ๆ นี้ชะลอตัวจาก 41s เป็น 49s (ช้าลง 20%) เมื่อฉันเพิ่งเรียกใช้close()หลังจากทุกพล็อต


นี่คือคำตอบที่ดี คำตอบที่ได้รับการยอมรับไม่ได้แก้ปัญหาที่เกิดขึ้นจริงซึ่งเป็นปริมาณการใช้หน่วยความจำ
Kyle

24

หากคุณตั้งใจจะเก็บไว้ในหน่วยความจำจำนวนมากอย่างรู้เท่าทัน แต่ไม่ต้องการถูกเตือนคุณสามารถอัปเดตตัวเลือกของคุณก่อนสร้างตัวเลข

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})

สิ่งนี้จะป้องกันไม่ให้คำเตือนถูกปล่อยออกมาโดยไม่เปลี่ยนแปลงสิ่งใดเกี่ยวกับวิธีจัดการหน่วยความจำ


ในสภาพแวดล้อม Jupyter การจัดสรรหน่วยความจำยังคงอยู่ตราบใดที่เซลล์ที่แสดงพล็อตมีอยู่หรือไม่?
matanster

2
@matanster ฉันจะโพสต์ว่าเป็นคำถามของตัวเอง ฉันเริ่มตอบจากนั้นก็รู้ว่าฉันไม่รู้จริง ๆ เกี่ยวกับการจัดการเคอร์เนลของจูปีเตอร์เพื่อตอบอย่างสุจริต
อันยิ่งใหญ่

@matanster มีตัวแปรและหน่วยความจำทั้งหมดที่จัดสรรไว้จนกว่าเคอร์เนลจะถูกปิดอย่างชัดเจนโดยผู้ใช้ มันไม่ได้เชื่อมโยงกับเซลล์ ใน Jupyter Hub ที่ใหม่กว่าระบบสามารถปิดเมล็ดได้ (สามารถกำหนดค่าได้)
greatvovan

0

ตัวอย่างต่อไปนี้แก้ไขปัญหาให้ฉัน:


class FigureWrapper(object):
    '''Frees underlying figure when it goes out of scope. 
    '''

    def __init__(self, figure):
        self._figure = figure

    def __del__(self):
        plt.close(self._figure)
        print("Figure removed")


# .....
    f, ax = plt.subplots(1, figsize=(20, 20))
    _wrapped_figure = FigureWrapper(f)

    ax.plot(...
    plt.savefig(...
# .....

เมื่อ_wrapped_figureอยู่นอกขอบเขตรันไทม์จะเรียก__del__()วิธีการของเราด้วยplt.close()ภายใน มันเกิดขึ้นแม้ว่าข้อยกเว้นจะเกิดขึ้นหลังจากตัว_wrapped_figureสร้าง


0

สิ่งนี้มีประโยชน์หากคุณต้องการระงับการเตือนชั่วคราว:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.