寫在前面
最近在pytorch運(yùn)行網(wǎng)絡(luò)模型時(shí),因?yàn)榘姹镜膯栴},有大量的warning輸出,這對(duì)于根據(jù)輸出查看訓(xùn)練狀態(tài)來說是很麻煩的,所以有了忽略warning輸出的需求.
Python中的warning Control
基本概念
warning類型分類
Python中有多種warning類型,其中Warning
為其他所有warning類型的基類.詳情如下:
-
UserWarning
: 用戶警告 -
DeprecationWarning
:棄用警告 -
SyntaxWarning
: 語法警告 -
RuntimeWarning
: 運(yùn)行時(shí)警告 -
FutureWarning
: 未來可能發(fā)生的警告 -
PendingDeprecationWarning
: 未來可能被棄用的警告 -
ImportWarning
: 導(dǎo)入警告 -
UnicodeWarning
: 與Unicode編碼相關(guān)的警告 -
BytesWarning
: 與字節(jié)或者字節(jié)數(shù)組相關(guān)的警告 -
ResourceWarning
: 與資源使用相關(guān)的警告
詳情可查看此處
warning filter
警告過濾器可以理解為是一個(gè)表中的一個(gè)條目,格式為(action, message, category, module, lineno)
,其中,action
表示匹配到的警告采用什么樣的頻率輸出, message
表示警告輸出信息,此處是一個(gè)正則匹配項(xiàng),category
表示匹配的warning類型, module
表示警告發(fā)出所在的模塊名,也是正則匹配項(xiàng), lineno
表示警告發(fā)出的行號(hào),只有(message, category, module, lineno)
都匹配到發(fā)出的警告時(shí),相應(yīng)的action
才會(huì)進(jìn)行.此處我們需要簡(jiǎn)單的理解一個(gè)警告發(fā)出的過程
- 首先調(diào)用
warnings.warn(message, category)
表示此處有個(gè)警告需發(fā)出 - 根據(jù)警告請(qǐng)求的
message, category, lineno, module
信息作為索引, 以在warning filter構(gòu)成的列表中尋找匹配項(xiàng),找到匹配的filter, 然后對(duì)該類型的警告執(zhí)行action
操作,如果沒有匹配的filter則對(duì)該類型警告執(zhí)行默認(rèn)操作.
因此我們可以看出warning filter的作用是用于控制警告的輸出動(dòng)作.
action分類
action
表示對(duì)于將發(fā)出的警告需執(zhí)行的動(dòng)作,包含著輸出的頻率
-
default
: 對(duì)于匹配的警告,打印在每個(gè)位置第一次出現(xiàn)時(shí)輸出(模塊+行號(hào)),之后在同一位置出現(xiàn)時(shí)不輸出. -
error
: 將匹配的警告變?yōu)楫惓?/li> -
ignore
: 忽略警告,不進(jìn)行輸出 -
always
: 總是打印輸出匹配的警告 -
module
: 對(duì)于匹配到的警告,在一個(gè)模塊中第一次出現(xiàn)時(shí)才輸出(與行號(hào)無關(guān)),之后不管在模塊的哪個(gè)位置出現(xiàn)都不輸出. -
once
: 對(duì)于匹配到的警告只打印輸出一次,而不管在哪個(gè)位置.
warn代碼原理剖析
警告發(fā)出的關(guān)鍵方法是warnings.warn()
,在warnings.py文件中,因此可以深入去理解該部分代碼,部分進(jìn)行了省略.
def warn(message, category=None, stacklevel=1, source=None):
# 1.類型檢查
# 2.設(shè)置行號(hào),設(shè)置模塊名, 設(shè)置文件名
registry = globals.setdefault("__warningregistry__", {})
warn_explicit(message, category, filename, lineno, module, registry,
globals, source)
def warn_explicit(message, category, filename, lineno,
module=None, registry=None, module_globals=None,
source=None):
# 3.獲取警告信息,設(shè)置行號(hào),構(gòu)建一個(gè)key
key = (text, category, lineno)
if registry.get(key): # registry是一個(gè)字典,保存了執(zhí)行once,module,default操作的警告請(qǐng)求
return
# 4.遍歷filter列表,尋找匹配項(xiàng)
for item in filters:
action, msg, cat, mod, ln = item
if ((msg is None or msg.match(text)) and
issubclass(category, cat) and
(mod is None or mod.match(module)) and
(ln == 0 or lineno == ln)):
break
else:
action = defaultaction # 沒有找到匹配的filter則使用default操作
# 5.根據(jù)對(duì)應(yīng)的action執(zhí)行是否輸出警告的操作
if action == "ignore":
return
if action == "error":
raise message
if action == "once":
registry[key] = 1
oncekey = (text, category)
if onceregistry.get(oncekey):
return
onceregistry[oncekey] = 1
elif action == "always":
pass
elif action == "module":
registry[key] = 1
altkey = (text, category, 0)
if registry.get(altkey):
return
registry[altkey] = 1
elif action == "default":
registry[key] = 1
else:
# 不能處理的錯(cuò)誤
raise RuntimeError(
"Unrecognized action (%r) in warnings.filters:\n %s" %
(action, item))
# 6. 按格式輸出警告信息
msg = WarningMessage(message, category, filename, lineno, source)
_showwarnmsg(msg)
注意: 在python2, 和python3中,略有不同,python2使用的是warnings.py所定義的方法,而在python3中雖然也提供了warnings.py,但是在warnings.py中導(dǎo)入了_warnings模塊,所以實(shí)際上python3中使用的warn方法定義在_warning模塊中,而模塊_warnings是在python虛擬機(jī)初始化中創(chuàng)建的模塊對(duì)象,也就是說用C語言內(nèi)置在python虛擬機(jī)中....不過我們可以將warnings.py中的模塊_warnings導(dǎo)入注釋掉,從而使用warnings.py中定義的方法.
警告控制的一些實(shí)踐
在理解python的警告控制相關(guān)的原理后,我們便能夠根據(jù)自己的需要控制警告信息如何輸出,因此從中,我們可以知道控制警告的關(guān)鍵便是如何設(shè)置warning filter.設(shè)置filter的方式有兩種,一是在代碼中,二是啟動(dòng)虛擬機(jī)時(shí)使用命令行參數(shù)
warnings.filterwarnings方法
在源代碼中,調(diào)用warnings.filterwarnings()
函數(shù)設(shè)置filter條目,示例如下
# 以下在python3
import warnings
def f():
warnings.warn("warn occur!", UserWarning)
warnings.filterwarnings('module', category=UserWarning)
print(warnings.filters)
f()
print('hello')
for i in range(3):
f()
print('world!')
# 輸出結(jié)果
[('module', None, <class 'UserWarning'>, None, 0), ('default', None, <class 'DeprecationWarning'>, '__main__', 0), ('ignore', None, <class 'DeprecationWarning'>, None, 0), ('ignore', None, <class 'PendingDeprecationWarning'>, None, 0), ('ignore', None, <class 'ImportWarning'>, None, 0), ('ignore', None, <class 'ResourceWarning'>, None, 0)]
test.py:4: UserWarning: warn occur!
warnings.warn("warn occur!", UserWarning)
hello
world!
注意: filter設(shè)置必須在發(fā)出警告請(qǐng)求之前
命令行參數(shù) -W
在啟動(dòng)虛擬機(jī)時(shí)使用-W
參數(shù)同樣可以設(shè)置filter條目,參數(shù)格式如下
-W action:message:category:module:lineno
其中各部分含義與warning filter條目設(shè)置各項(xiàng)含義相同
注意: 如果某項(xiàng)缺省,如message,module,lineno缺省則參數(shù)形式為-W ignore::UserWarning
,其中UserWarning
后面部分的符號(hào):
可以省略,但是前面的不行.
注意
python提供的警告控制也有著局限性,只有對(duì)于使用了warnings.warn()
函數(shù)發(fā)出的警告,可以通過設(shè)置warning filter條目控制警告輸出,但是對(duì)于一些使用底層函數(shù)進(jìn)行警告輸出的信息無法控制,例如在pytorch中,一些涉及到tensor操作的函數(shù)以底層C/C++實(shí)現(xiàn),并且將警告信息嵌入其中,對(duì)于這種情況輸出警告信息,我們沒法控制,所以如果不想看輸出的警告信息,還是乖乖根據(jù)警告提示修改代碼吧_