Tl; dr是大胆的文字。
我正在处理一个带有布尔值的图像数据集" one-hot"图像注释(Celeba具体)。注释编码面部特征,如 bald ,男性,年轻。现在我想制作一个自定义的热门列表(以测试我的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方式,而不是必然最快的方式。
按重要性粗略排列:
.txt
标题中的属性。这就是我试图设计的重点。aenum
等非标准库。.txt
标头。例如:如果用户想要指定性别属性但不知道是否使用male
或female
,则应该很容易找到。dir()
的属性曝光足以满足这一点。我要比较和对比我想到的方式。 所有示例均使用:
import numpy as np
header = ("Arched_Eyebrows Attractive Bags_Under_Eyes "
"Bald Bangs Chubby Male Wearing_Necktie Young")
NUM_CLASSES = len(header.split()) # 9
显然我们可以使用字典来实现这个目标:
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的字典。
由于我现在正在学习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。
这是标准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。
我真的能打破我的后背:
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的轻微失败。
在Python中有很多方法可以实现文字零索引。 您是否会提供一个代码片段,说明如何完成我之后的工作并告诉我为什么您的方式正确?
(编辑:) 或解释为什么我的一个例子是适合这项工作的工具?
我还没有准备好接受答案,以防有人想要解决以下反馈/更新,或者出现任何新的解决方案。也许另外24小时?所有的回复都很有帮助,所以到目前为止,我对所有人都赞不绝口。您可能希望查看此repo我用于测试解决方案。 如果我的以下言论准确或不公平,请随时告诉我:
奇怪的是,Sphinx错误地记录了这个(在文档中有一个索引),但它确实记录了它!我想那是"问题"没有任何理想的功能失败。
我觉得Map
有点矫枉过正,但dotdict
是可以接受的。感谢两位使用dir()
解决方案的回答者。然而,它并没有表现出它能够无缝地工作"与狮身人面像。
正如所写,此解决方案比其他解决方案花费更长的时间。它的速度比namedtuple
慢10倍(比纯粹的dict快),比标准IntEnum
慢7倍(在numpy记录之后最慢)。这在当前规模上并不严重,也不是优先级,但快速Google搜索表明np.in1d
实际上很慢。让我们坚持
_label = np.zeros([NUM_CLASSES])
_label[[header_rec[key].item() for key in ["Young", "Bald", "Chubby"]]] = True
除非我在链接的回购邮件中实现了错误。这使执行速度进入与其他解决方案相比较的范围。再一次,没有Sphinx。
我不相信你的enum
批评。在我看来,你相信我接近这个问题是错误的。可以打电话给我,但我不知道如何使用namedtuple
与" Enum [它]将为每个常数提供单独的值。&{34}。 #34;我误解了你吗?
无论如何,namedtuple
出现在Sphinx中(正确编号,因为它值得)。在理想特征列表中,这与零枚举之前的零枚举和配置文件相同。
我接受了零点回答,因为答案给了我namedtuple
最好的挑战者。 按我的标准,namedtuple
是最好的解决方案。但是salparadise写了答案,让我对评估有信心。感谢所有回答的人。
答案 0 :(得分:3)
工厂函数如何创建零索引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>]
答案 1 :(得分:2)
您可以使用我想称为DotMap
的自定义类,或者如此处所提到的这个SO讨论为Map
:
Map
,更长的完整版本)dotdict
,更短的打火机版本)关于Map
:
features['male']
。features.male
来访问属性,并且dir(features)
时会显示属性。namedtuple
不同,您不需要预先定义它,您可以随意添加和删除密钥。Map
函数不兼容Python3,因为它使用iteritems()
。只需将其替换为items()
。关于dotdict
:
dotdict
提供Map
的相同优点,但不会覆盖dir()
方法,因此您将无法获取文档的属性。 @SigmaPiEpsilon已为此here提供了修复程序。dict.get
方法而不是dict.__getitem__
,因此当您是不存在的访问属性时,它将返回None
而不是抛出KeyError
。dotdict-iness
应用于嵌套词典,因此您将无法使用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()
Map
和dotdict
与@SigmaPiEpsilon指出的行为不同,所以我为两者添加了单独的描述。
答案 2 :(得分:1)
在你的例子中,3是对你的问题最狡猾的答案。
正如你所说,1甚至没有回答你的问题,因为这些名字并不明确。
2使用枚举,虽然在标准库中不是pythonic,通常不在Python的这些场景中使用。 (编辑):在这种情况下,你只需要两个不同的常量 - 目标值和其他常量。 Enum将为每个常量提供单独的值,这不是您的程序的目标,并且似乎是解决问题的迂回方式。
如果客户想要添加选项,那么4就无法维护,即使这是一项艰苦的工作。
3以可读和简洁的方式使用标准库中的众所周知的类。此外,它没有任何缺点,因为它是完全明确的。如果你不关心性能,那么太“沉重”并不重要,无论如何,你输入的大小都不会引起滞后。
答案 3 :(得分:1)
如果我理解正确,您的要求可以分为两部分:
以尽可能最pythonic的方式按名称访问.txt
中的标题元素的位置,并且外部依赖性最小
启用对包含标题名称的数据结构的点访问,以便能够调用dir()
并设置与Sphinx的简单界面
解决问题的最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 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
成为了自己的对象。