有文化的方式来索引每个元素都有解释的列表?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有文化的方式来索引每个元素都有解释的列表?相关的知识,希望对你有一定的参考价值。
Tl; dr是大胆的文字。
我正在处理一个带有布尔“一热”图像注释的图像数据集(Celeba具体)。注释编码面部特征,如秃头,男性,年轻。现在我想制作一个自定义的热门列表(以测试我的GAN模型)。我想提供一个有文化的界面。即,而不是指定features[12]=True
知道12
- 从零开始计数 - 对应于男性特征,我想要像features[male]=True
或features.male=True
这样的东西。
假设我的.txt
文件的标题是
Arched_Eyebrows Attractive Bags_Under_Eyes Bald Bangs Chubby Male Wearing_Necktie Young
我想编纂Young,Bald和Chubby。预期的产出是
[ 0. 0. 0. 1. 0. 1. 0. 0. 1.]
因为Bald是标题的第四个条目,Chubby是第六个,依此类推。在没有期望用户知道Bald是第四个条目等的情况下,最明智的方法是什么?
我正在寻找一种Pythonic方式,不一定是最快的方式。
Ideal Features
粗略的重要性:
- 实现我已经在Python社区中已经标准化的既定目标的方法将优先考虑。
- 用户/程序员不需要计入
.txt
头中的属性。这是我想要设计的重点。 - 不应期望用户拥有像
aenum
这样的非标准库。 - 用户/程序员不需要为属性名称/可用属性引用
.txt
头。一个例子:如果用户想要指定性别属性但不知道是否使用male
或female
,则应该很容易找到。 - 用户/程序员应该能够通过文档找到可用的属性(理想情况下由Sphinx api-doc生成)。也就是说,第4点应该尽可能少地读取代码。
dir()
的属性暴露足以满足这一点。 - 程序员应该找到自然的索引工具。具体来说,零索引应优先于从一个索引中减去。
- 在两个完全相同的解决方案之间,一个具有更好性能的解决方
Examples:
我将比较和对比立即出现在我脑海中的方式。所有例子都使用:
import numpy as np
header = ("Arched_Eyebrows Attractive Bags_Under_Eyes "
"Bald Bangs Chubby Male Wearing_Necktie Young")
NUM_CLASSES = len(header.split()) # 9
1:Dict理解
显然我们可以用字典来完成这个:
binary_label = np.zeros([NUM_CLASSES])
classes = head: idx for (idx, head) in enumerate(header.split())
binary_label[[classes["Young"], classes["Bald"], classes["Chubby"]]] = True
print(binary_label)
对于它的价值,它拥有最少的代码行,并且是唯一一个不依赖于内置的标准库的代码。至于否定,它并不完全是自我记录。要查看可用选项,您必须使用print(classes.keys())
- 它不会被dir()
暴露。这边界不满足特征5,因为它需要用户知道classes
是暴露特征AFAIK的字典。
2:枚举:
由于我现在正在学习C ++,所以首先想到的是Enum
:
import enum
binary_label = np.zeros([NUM_CLASSES])
Classes = enum.IntEnum("Classes", header)
features = [Classes.Young, Classes.Bald, Classes.Chubby]
zero_idx_feats = [feat-1 for feat in features]
binary_label[zero_idx_feats] = True
print(binary_label)
这给出了点符号,图像选项用dir(Classes)
公开。但是,enum
默认使用单索引(原因是documented)。解决方法让我觉得enum
不是Pythonic这样做的方式,并且完全不能满足功能6。
3:命名为元组
这是标准Python库中的另一个:
import collections
binary_label = np.zeros([NUM_CLASSES])
clss = collections.namedtuple(
"Classes", header)._make(range(NUM_CLASSES))
binary_label[[clss.Young, clss.Bald, clss.Chubby]] = True
print(binary_label)
使用namedtuple
,我们再次使用dir(clss)
获得点符号和自我文档。但是,namedtuple
级别比enum
重。我的意思是,namedtuple
具有我不需要的功能。这个解决方案似乎是我的例子中的领导者,但我不知道它是否满足功能1或者是否可以通过功能7“赢得”。
4:自定义枚举
我真的可以打破我的背影:
binary_label = np.zeros([NUM_CLASSES])
class Classes(enum.IntEnum):
Arched_Eyebrows = 0
Attractive = 1
Bags_Under_Eyes = 2
Bald = 3
Bangs = 4
Chubby = 5
Male = 6
Wearing_Necktie = 7
Young = 8
binary_label[
[Classes.Young, Classes.Bald, Classes.Chubby]] = True
print(binary_label)
这具有Ex的所有优点。但是,它有明显的明显缺点。我必须写出所有的功能(真正的数据集中有40个),只是为了零索引!当然,这是如何在C ++(AFAIK)中创建枚举,但它在Python中不是必需的。这是功能6的轻微故障。
Summary
有许多方法可以在Python中实现文字零索引。您是否会提供一个代码片段,说明您将如何完成我所追求的目标并告诉我为什么您的方式是正确的?
(编辑:)或解释为什么我的一个例子是适合这项工作的工具?
Status Update:
如果有人想要解决以下反馈/更新,或者出现任何新的解决方案,我还没准备好接受答案。也许另外24小时?所有的回复都很有帮助,所以到目前为止,我对所有人都赞不绝口。你可能想查看我用来测试解决方案的repo。如果我的以下言论准确或不公平,请随时告诉我:
zero-enum:
奇怪的是,Sphinx错误地记录了这个(在文档中是一个索引),但它确实记录了它!我认为“问题”不会失败任何理想的功能。
dotdict:
我觉得Map
有点矫枉过正,但dotdict
是可以接受的。感谢两位回答者,这个解决方案与dir()
合作。然而,它似乎并没有与Sphinx“无缝地”工作。
Numpy record:
如上所述,此解决方案比其他解决方案花费更长的时间。它的速度比namedtuple
慢10倍(最快落后于纯粹的dict),比标准的IntEnum
慢7倍(在numpy记录之后最慢)。这在目前的规模上并不是激烈的,也不是优先事项,但谷歌的快速搜索表明np.in1d
实际上很慢。让我们坚持下去
_label = np.zeros([NUM_CLASSES])
_label[[header_rec[key].item() for key in ["Young", "Bald", "Chubby"]]] = True
除非我在链接的回购中实现了错误。这使执行速度进入与其他解决方案相比较的范围。再一次,没有Sphinx。
namedtuple (and rassar's critiques)
我不相信你的enum
批评。在我看来,你认为我正在接近这个问题。打电话给我就好了,但是我没有看到使用namedtuple
与“Enum [哪个]将为每个常数提供单独的值”的根本不同。我误会了你吗?
无论如何,namedtuple
出现在狮身人面像(正确编号,为它的价值)。在理想特征列表中,这与零枚举前的零枚举和配置文件完全相同。
接受的理由
我接受了零回答,因为答案给了我namedtuple
最好的挑战者。根据我的标准,namedtuple
是最好的解决方案。但salparadise写了答案,让我对这个评估有信心。感谢所有回答的人。
工厂函数如何创建零索引IntEnum
,因为这是适合您需求的对象,而Enum
提供了灵活的构造:
from enum import IntEnum
def zero_indexed_enum(name, items):
# splits on space, so it won't take any iterable. Easy to change depending on need.
return IntEnum(name, ((item, value) for value, item in enumerate(items.split())))
然后:
In [43]: header = ("Arched_Eyebrows Attractive Bags_Under_Eyes "
...: "Bald Bangs Chubby Male Wearing_Necktie Young")
In [44]: Classes = zero_indexed_enum('Classes', header)
In [45]: list(Classes)
Out[45]:
[<Classes.Arched_Eyebrows: 0>,
<Classes.Attractive: 1>,
<Classes.Bags_Under_Eyes: 2>,
<Classes.Bald: 3>,
<Classes.Bangs: 4>,
<Classes.Chubby: 5>,
<Classes.Male: 6>,
<Classes.Wearing_Necktie: 7>,
<Classes.Young: 8>]
您可以使用我喜欢称为DotMap
的自定义类,或者如此处提到的这个SO讨论为Map
:
- https://stackoverflow.com/a/32107024/2598661(
Map
,更长的完整版) - https://stackoverflow.com/a/23689767/2598661(
dotdict
,更短的打火机版)
关于Map
:
- 它具有字典的功能,因为Map / DotMap的输入是一个字典。您可以使用
features['male']
访问属性。 - 此外,您可以使用点(即
features.male
)访问属性,并且当您执行dir(features)
时将显示属性。 - 为了启用点功能,它只需要很大的重量。
- 与
namedtuple
不同,您不需要预先定义它,您可以添加和删除密钥。 - SO问题中描述的
Map
函数不兼容Python3,因为它使用iteritems()
。只需用items()
替换它。
关于dotdict
:
dotdict
提供与Map
相同的优点,但它不会覆盖dir()
方法,因此您将无法获得文档的属性。 @SigmaPiEpsilon为这个here提供了一个修复程序。- 它使用
dict.get
方法而不是dict.__getitem__
因此,当您是不存在的访问属性时,它将返回None
而不是抛出KeyError
。 - 它不会递归地将
dotdict-iness
应用于嵌套dicts,因此您将无法使用features.foo.bar
。
这是dotdict
的更新版本,它解决了前两个问题:
class dotdict(dict):
__getattr__ = dict.__getitem__ # __getitem__ instead of get
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __dir__(self): # by @SigmaPiEpsilon for documentation
return self.keys()
Update
Map
和dotdict
与@SigmaPiEpsilon指出的行为不同,所以我为两者添加了单独的描述。
在你的例子中,3是你问题的最pythonic答案。
1,如你所说,甚至没有回答你的问题,因为这些名字并不明确。
2使用枚举,虽然在标准库中不是pythonic,通常不在Python的这些场景中使用。 (编辑):在这种情况下,你只需要两个不同的常量 - 目标值和其他常量。 Enum将为每个常量提供单独的值,这不是您的程序的目标,并且似乎是解决问题的迂回方式。
如果客户想要添加选项,那么4是不可维护的,即使它是艰苦的工作。
3以可读和简洁的方式使用标准库中的众所周知的类。此外,它没有任何缺点,因为它是完全明确的。如果你不关心性能,那么过于“沉重”并不重要,无论如何,输入大小的延迟都是不明显的。
如果我理解正确,您的要求可分为两部分:
- 以尽可能最pythonic的方式按名称访问
.txt
中的标题元素的位置,并且具有最小的外部依赖性 - 启用对包含标题名称的数据结构的点访问,以便能够调用
dir()
并使用Sphinx设置简单的界面
纯Python方式(没有外部依赖)
解决问题的最pythonic方法当然是使用字典的方法(字典是python的核心)。通过密钥搜索字典也比其他方法快得多。唯一的问题是这可以防止点访问。另一个答案提到Map和dotdict作为替代品。 dotdict
更简单,但它只能启用点访问,因为dir()
调用的dir()
方法在这些情况下没有被覆盖,所以它在__dir__()
的文档方面无济于事。因此,它只返回Python dict
的属性而不返回标题名称。见下文:
>>> class dotdict(dict):
... __getattr__ = dict.get
... __setattr__ = dict.__setitem__
... __delattr__ = dict.__delitem__
...
>>> somedict = 'a' : 1, 'b': 2, 'c' : 3
>>> somedotdict = dotdict(somedict)
>>> somedotdict.a
1
>>> 'a' in dir(somedotdict)
False
有两种方法可以解决这个问题。
选项1:覆盖__dir__()
方法,如下所示。但这只适用于在类的实例上调用dir()
时。要使更改适用于类本身,您必须为类创建元类。见here
#add this to dotdict
def __dir__(self):
return self.keys()
>>> somedotdictdir = dotdictdir(somedict)
>>> somedotdictdir.a
1
>>> dir(somedotdictdir)
['a', 'b', 'c']
选项2:第二个选项使得它更接近具有属性的用户定义对象是更新所创建对象的__dict__
属性。这就是Map也使用的。普通的python dict
没有这个属性。如果你添加它,那么你可以调用dir()
来获取属性/键以及python dict
的所有其他方法/属性。如果您只想要存储的属性和值,可以使用vars(somedotdictdir)
,这对文档也很有用。
class dotdictdir(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__.update(k : v for k,v in self.items())
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
self.__dict__.update(key : value)
__getattr__ = dict.get #replace with dict.__getitem__ if want raise error on missing key access
__setattr__ = __setitem__
__delattr__ = dict.__delitem__
>>> somedotdictdir = dotdictdir(somedict)
>>> somedotdictdir
'a': 3, 'c': 6, 'b': 4
>>> vars(somedotdictdir)
'a': 3, 'c': 6, 'b': 4
>>> 'a' in dir(somedotdictdir)
True
Numpy方式
另一个选择是使用一个允许点访问的numpy record
数组。我在你的代码中注意到你已经在使用numpy了。在这种情况下,必须覆盖__dir__()
才能获得属性。这可能会导致对具有大量其他数值的数据进行更快的操作(未测试)。
>>> headers = "Arched_Eyebrows Attractive Bags_Under_Eyes Bald Bangs Chubby Male Wearing_Necktie Young".split()
>>> header_rec = np.array([tuple(range(len(headers)))], dtype = zip(headers, [int]*len(headers)))
>>> header_rec.dtype.names
('Arched_Eyebrows', 'Attractive', 'Bags_Under_Eyes', 'Bald', 'Bangs', 'Chubby', 'Male', 'Wearing_Necktie', 'Young')
>>> np.in1d(header_rec.item(), [header_rec[key].item() for key in ["Young", "Bald", "Chubby"]]).astype(int)
array([0, 0, 0, 1, 0, 1, 0, 0, 1])
在Python 3中,你将需要使用dtype=list(zip(headers, [int]*len(headers)))
,因为zip
成为它自己的对象。
以上是关于有文化的方式来索引每个元素都有解释的列表?的主要内容,如果未能解决你的问题,请参考以下文章