peringatan tentang terlalu banyak angka terbuka

166

Dalam naskah tempat saya membuat banyak angka fix, ax = plt.subplots(...), saya mendapatkan peringatan RuntimeWarning: Lebih dari 20 angka telah dibuka. Angka yang dibuat melalui antarmuka pyplot ( matplotlib.pyplot.figure) dipertahankan hingga ditutup secara eksplisit dan dapat menggunakan terlalu banyak memori.

Namun, saya tidak mengerti mengapa saya mendapatkan peringatan ini, karena setelah menyimpan angka dengan fig.savefig(...), saya menghapusnya dengan fig.clear(); del fig. Tidak ada titik dalam kode saya, saya memiliki lebih dari satu angka terbuka pada suatu waktu. Namun, saya mendapat peringatan tentang terlalu banyak angka terbuka. Apa artinya itu / bagaimana saya bisa menghindari mendapat peringatan?

andreas-h
sumber
9
Jika Anda melakukan banyak hal ini, dan tidak menampilkan apa pun secara interaktif, Anda mungkin lebih baik mem-bypass pltsemuanya. Misalnya stackoverflow.com/a/16337909/325565 (Bukan untuk menyambungkan salah satu jawaban saya sendiri, tetapi itu adalah jawaban yang paling cepat saya temukan ...)
Joe Kington
1
@ JoKington terima kasih ini adalah solusi yang lebih baik
hihell
Jawaban Joe Kington harus ada dalam daftar jawaban utama. Ini berfungsi dan juga mengatasi masalah dengan plt.close () memperlambat program yang disebutkan Don Kirby.
NatalieL

Jawaban:

199

Gunakan .clfatau .clapada objek gambar Anda alih-alih membuat gambar baru . Dari @DavidZwicker

Dengan asumsi Anda telah mengimpor pyplotsebagai

import matplotlib.pyplot as plt

plt.cla()membersihkan sumbu , yaitu sumbu yang saat ini aktif dalam gambar saat ini. Itu membuat sumbu lainnya tidak tersentuh.

plt.clf()membersihkan seluruh gambar saat ini dengan semua sumbunya, tetapi membiarkan jendela terbuka, sehingga dapat digunakan kembali untuk plot lainnya.

plt.close()menutup jendela , yang akan menjadi jendela saat ini, jika tidak ditentukan sebaliknya. plt.close('all')akan menutup semua angka terbuka.

Alasan yang del figtidak berhasil adalah bahwa pyplotmesin-negara menyimpan referensi ke gambar di sekitar (sebagaimana mestinya jika itu akan tahu apa 'angka saat ini'). Ini berarti bahwa bahkan jika Anda menghapus referensi Anda ke angka tersebut, setidaknya ada satu referensi langsung, maka itu tidak akan pernah menjadi sampah yang dikumpulkan.

Karena saya memilih pendapat kolektif di sini untuk jawaban ini, @JoeKington menyebutkan dalam komentar yang plt.close(fig)akan menghapus contoh figur spesifik dari mesin status pylab ( plt._pylab_helpers.Gcf ) dan mengizinkannya untuk dikumpulkan menjadi sampah.

Doyan
sumber
1
Mhh. Ada clfuntuk figurekelas, tetapi tidak close. Mengapa del figsebenarnya tidak menutup dan menghapus gambar?
andreas-h
2
@ andreas-h Dugaan saya: untuk sesuatu yang kompleks seperti window manager dengan penangannya sendiri, mungkin ada lebih banyak pembersihan yang diperlukan daripada meletakkan sesuatu di luar ruang lingkup. Hak Anda yang closetidak akan berfungsi pada objek gambar, panggil saja plt.close(), bukan fig.clf().
Ketagihan
5
@ andreas-h - Pada dasarnya, alasan yang del figtidak berhasil adalah bahwa memberikan __del__metode (yang pada dasarnya akan memanggil plt.close(fig)) akan berakhir menyebabkan referensi melingkar dalam kasus khusus ini, dan figmemiliki __del__metode akan menyebabkan hal-hal lain tidak terkumpul menjadi sampah. . (Atau itu ingatan samar-samar saya, bagaimanapun.) Bagaimanapun, itu memang agak menjengkelkan, tetapi Anda harus menelepon plt.close(fig)bukan del fig. Sebagai tambahan, matplotlib benar-benar dapat menggunakan manajer konteks untuk ini ...
Joe Kington
6
@Hooked - Untuk membuatnya sedikit lebih jelas, Anda dapat mengedit pertanyaan Anda untuk menyebutkan bahwa plt.close(fig)akan menghapus contoh angka spesifik dari mesin status pylab ( plt._pylab_helpers.Gcf) dan memungkinkannya menjadi sampah yang dikumpulkan.
Joe Kington
2
@ JoKington pltagak berantakan dan ada pemikiran tentang bagaimana melakukan kembali banyak hal. Manajer konteks menarik .... Lihat github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell
33

Ini sedikit lebih detail untuk diperluas pada jawaban Hooked . Ketika saya pertama kali membaca jawaban itu, saya melewatkan instruksi untuk menelepon clf() alih-alih membuat angka baru . clf()sendiri tidak membantu jika Anda kemudian pergi dan membuat gambar lain.

Berikut ini contoh sepele yang menyebabkan peringatan:

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

Untuk menghindari peringatan itu, saya harus menarik panggilan ke subplots()luar loop. Agar terus melihat persegi panjang, saya harus beralih clf()ke cla(). Itu membersihkan sumbu tanpa melepas sumbu itu sendiri.

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

Jika Anda membuat plot dalam batch, Anda mungkin harus menggunakan keduanya cla()dan close(). Saya mengalami masalah di mana satu batch dapat memiliki lebih dari 20 plot tanpa mengeluh, tetapi akan mengeluh setelah 20 batch. Saya memperbaikinya dengan menggunakan cla()setelah setiap plot, dan close()setelah setiap batch.

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

Saya mengukur kinerja untuk melihat apakah layak menggunakan kembali angka dalam satu batch, dan program sampel kecil ini melambat dari 41 menjadi 49 (20% lebih lambat) ketika saya baru saja menelepon close()setelah setiap plot.

Don Kirkby
sumber
Ini jawaban yang bagus. Jawaban yang diterima tidak benar-benar mengatasi masalah aktual yang ada, yaitu konsumsi memori.
Kyle
24

Jika Anda bermaksud menyimpan banyak plot secara sengaja, tetapi tidak ingin diperingatkan, Anda dapat memperbarui opsi sebelum menghasilkan angka.

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

Ini akan mencegah peringatan agar tidak dipancarkan tanpa mengubah apa pun tentang cara memori dikelola.

mungkin
sumber
di lingkungan Jupyter, apakah alokasi memori tetap ada selama sel yang menunjukkan plot itu ada?
matanster
2
@ matanster, saya akan memposting itu karena pertanyaannya sendiri. Saya mulai menjawab, kemudian menyadari bahwa saya benar-benar tidak cukup tahu tentang manajemen kernel jupyter untuk menjawab dengan jujur.
mayypile
@matanster Semua variabel dan memori yang dialokasikan untuk mereka ada sampai kernel dimatikan secara eksplisit oleh pengguna. Itu tidak terkait dengan sel. Di Jupyter Hub yang lebih baru, sistem dapat mematikan kernel (dapat dikonfigurasi).
greatvovan
0

Cuplikan berikut memecahkan masalah bagi saya:


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(...
# .....

Ketika _wrapped_figurekeluar dari ruang lingkup runtime memanggil __del__()metode kami dengan plt.close()di dalamnya. Ini terjadi bahkan jika pengecualian menyala setelah _wrapped_figurekonstruktor.

Dmitry
sumber
0

Ini juga berguna jika Anda hanya ingin untuk sementara menekan peringatan:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
rwb
sumber