【问题标题】:One-hot encoding: list membership errorOne-hot 编码:列表成员错误
【发布时间】:2018-01-29 23:25:06
【问题描述】:

给定可变数量的字符串,我想对它们进行一次热编码,如下例所示:

s1 = 'awaken my love'
s2 = 'awaken the beast'
s3 = 'wake beast love'

# desired result - NumPy array
array([[ 1.,  1.,  1.,  0.,  0.,  0.],
       [ 1.,  0.,  0.,  1.,  1.,  0.],
       [ 0.,  0.,  1.,  0.,  1.,  1.]])

当前代码:

def uniquewords(*args):
    """Create order-preserved string with unique words between *args"""
    allwords = ' '.join(args).split()
    return ' '.join(sorted(set(allwords), key=allwords.index)).split()

def encode(*args):
    """One-hot encode the given input strings"""
    unique = uniquewords(*args)
    feature_vectors = np.zeros((len(args), len(unique)))
    for vec, s in zip(feature_vectors, args):
        for num, word in enumerate(unique):                
            vec[num] = word in s
    return feature_vectors

问题出在这一行:

vec[num] = word in s

例如,将'wake' in 'awaken my love' 提取为True(这是正确的,但不符合我的需要)并给出以下稍微有点偏离的结果:

print(encode(s1, s2, s3))
[[ 1.  1.  1.  0.  0.  1.]
 [ 1.  0.  0.  1.  1.  1.]
 [ 0.  0.  1.  0.  1.  1.]]

我见过a solution 使用re,但不知道如何在这里申请。我怎样才能纠正上面的单线? (摆脱嵌套循环也不错,但我不要求进行常规代码编辑,除非有人提供。)

【问题讨论】:

  • word in s 未测试 'wake' in ['awaken']。它正在测试'wake' in 'awaken'
  • (好吧,真的是'wake' in 'awaken my love''wake' in 'awaken the beast'
  • 对字符串的单词集而不是字符串执行in 测试。
  • ...什么?不,您已经有了将字符串转换为一组单词的逻辑。只是使用它有点不同。
  • 我不是在谈论将集合的str 表示转换回集合。

标签: python python-3.x numpy one-hot-encoding


【解决方案1】:

您可以使用 pandas 从列表列表(例如,每个字符串随后拆分为单词列表的字符串列表)创建一次性编码转换。

import pandas as pd

s1 = 'awaken my love'
s2 = 'awaken the beast'
s3 = 'wake beast love'

words = pd.Series([s1, s2, s3])
df = pd.melt(words.str.split().apply(pd.Series).reset_index(), 
             value_name='word', id_vars='index')
result = (
    pd.concat([df['index'], pd.get_dummies(df['word'])], axis=1)
    .groupby('index')
    .any()
).astype(float)
>>> result
       awaken  beast  love  my  the  wake
index                                    
0           1      0     1   1    0     0
1           1      1     0   0    1     0
2           0      1     1   0    0     1

>>> result.values
array([[ 1.,  0.,  1.,  1.,  0.,  0.],
       [ 1.,  1.,  0.,  0.,  1.,  0.],
       [ 0.,  1.,  1.,  0.,  0.,  1.]])

说明

首先,从您的单词列表中创建一个系列。

然后将单词分成列并重置索引:

>>> words.str.split().apply(pd.Series).reset_index()
# Output:
#    index       0      1      2
# 0      0  awaken     my   love
# 1      1  awaken    the  beast
# 2      2    wake  beast   love

然后将这个中间数据框融合到上面,结果如下:

   index variable    word
0      0        0  awaken
1      1        0  awaken
2      2        0    wake
3      0        1      my
4      1        1     the
5      2        1   beast
6      0        2    love
7      1        2   beast
8      2        2    love

get_dummies 应用于单词并将结果连接到它们的索引位置。然后将生成的数据帧分组在index 上,any 用于聚合(所有值都是零或一,因此any 指示该单词是否存在一个或多个实例)。这将返回一个布尔矩阵,该矩阵被转换为浮点数。要返回 numpy 数组,请将.values 应用于结果。

【讨论】:

    【解决方案2】:

    这是一种方法 -

    def membership(list_strings):
        split_str = [i.split(" ") for i in list_strings]
        split_str_unq = np.unique(np.concatenate(split_str))
        out = np.array([np.in1d(split_str_unq, b_i) for b_i in split_str]).astype(int)
        df_out = pd.DataFrame(out, columns = split_str_unq)
        return df_out
    

    示例运行 -

    In [189]: s1 = 'awaken my love'
         ...: s2 = 'awaken the beast'
         ...: s3 = 'wake beast love'
         ...: 
    
    In [190]: membership([s1,s2,s3])
    Out[190]: 
       awaken  beast  love  my  the  wake
    0       1      0     1   1    0     0
    1       1      1     0   0    1     0
    2       0      1     1   0    0     1
    

    这是另一个使用 np.searchsorted 获取每行的列索引以设置到输出数组并希望更快 -

    def membership_v2(list_strings):
        split_str = [i.split(" ") for i in list_strings]
        all_strings = np.concatenate(split_str)
        split_str_unq = np.unique(all_strings)
        col = np.searchsorted(split_str_unq, all_strings)
        row = np.repeat(np.arange(len(split_str)) , [len(i) for i in split_str])
        out = np.zeros((len(split_str),col.max()+1),dtype=int)
        out[row, col] = 1
        df_out = pd.DataFrame(out, columns = split_str_unq)
        return df_out
    

    请注意,作为数据帧的输出主要是为了更好/更轻松地表示输出。

    【讨论】:

    • 你的v2 版本确实很快。
    【解决方案3】:

    如果您进行轻微的重构,以便将每个句子视为一个单词列表,它会删除很多您必须做的splitting 和joining,并使@ 的行为自然化987654323@一点。但是,set 是成员测试的首选,因为它可以在 O(1) 中执行此操作,并且您应该只为每个迭代的参数构造一个,因此您的代码将导致:

    import numpy as np
    import itertools
    
    def uniquewords(*args):
        """Create order-preserved string with unique words between *args"""
        allwords = list(itertools.chain(*args))
        return sorted(set(allwords), key=allwords.index)
    
    def encode(*args):
        """One-hot encode the given input strings"""
        args_with_words = [arg.split() for arg in args]
        unique = uniquewords(*args_with_words)
        feature_vectors = np.zeros((len(args), len(unique)))
        for vec, s in zip(feature_vectors, args_with_words):
            s_set = set(s)
            for num, word in enumerate(unique):                
                vec[num] = word in s_set
        return feature_vectors
    
    print encode("awaken my love", "awaken the beast", "wake beast love")
    

    正确的输出

    [[ 1.  1.  1.  0.  0.  0.]
     [ 1.  0.  0.  1.  1.  0.]
     [ 0.  0.  1.  0.  1.  1.]]
    

    完成此操作后,您可能会意识到您根本不需要成员资格测试,并且您可以迭代 s,只需处理需要设置为 1 的单词。这种方法可能比更大的数据集快得多。

    import numpy as np
    import itertools
    
    def uniquewords(*args):
        """Dictionary of words to their indices in the matrix"""
        words = {}
        n = 0
        for word in itertools.chain(*args):
            if word not in words:
                words[word] = n
                n += 1
        return words
    
    def encode(*args):
        """One-hot encode the given input strings"""
        args_with_words = [arg.split() for arg in args]
        unique = uniquewords(*args_with_words)
        feature_vectors = np.zeros((len(args), len(unique)))
        for vec, s in zip(feature_vectors, args_with_words):
            for word in s:                
                vec[unique[word]] = 1
        return feature_vectors
    
    print encode("awaken my love", "awaken the beast", "wake beast love")
    

    【讨论】:

    • 所有答案都很优雅,但我接受了这个答案,因为它比 numpy 方法快 4 倍,而且解释也很好。
    【解决方案4】:

    一个 Set 将使 in 运算符平均在 O(1) 中运行。

    变化:

    vec[num] = word in s
    

    到:

    vec[num] = word in set(s.split())
    

    最终版本:

    def encode(*args):
        """One-hot encode the given input strings"""
        unique = uniquewords(*args)
        feature_vectors = np.zeros((len(args), len(unique)))
        for vec, s in zip(feature_vectors, args):
            for num, word in enumerate(unique):
                vec[num] = word in set(s.split())
        return feature_vectors
    

    结果:

    [[ 1.  1.  1.  0.  0.  0.]
     [ 1.  0.  0.  1.  1.  0.]
     [ 0.  0.  1.  0.  1.  1.]]
    

    【讨论】:

    • 字符串默认按空格分割:word in s.split()
    • 已更改。虽然显式优于隐式:)
    • 不确定是否适用于此。可读性应始终优先。后者更具可读性。
    猜你喜欢
    • 2017-10-10
    • 2019-12-09
    • 2018-05-22
    • 1970-01-01
    • 2018-03-29
    • 1970-01-01
    • 2017-10-23
    • 2017-06-21
    • 2021-04-14
    相关资源
    最近更新 更多