ফিল্টারযুক্ত বাইনারি কার্টেসিয়ান পণ্য উত্পন্ন করুন


12

সমস্যা বিবৃতি

আমি সম্পূর্ণ বাইনারি কার্টেসিয়ান পণ্যগুলি তৈরি করার জন্য একটি কার্যকর উপায় সন্ধান করছি (নির্দিষ্ট সংখ্যক কলামগুলির সাথে সত্য এবং মিথ্যা সব সংমিশ্রনের সাথে সারণী), কিছু নির্দিষ্ট একচেটিয়া শর্ত দ্বারা ফিল্টার করা। উদাহরণস্বরূপ, তিনটি কলাম / বিটের জন্য n=3আমরা পুরো টেবিলটি পেয়ে যাব

df_combs = pd.DataFrame(itertools.product(*([[True, False]] * n)))
       0      1      2
0   True   True   True
1   True   True  False
2   True  False   True
3   True  False  False
...

নিম্নলিখিতগুলি পারস্পরিক একচেটিয়া সংমিশ্রণ সংজ্ঞায়িত করে অভিধান দ্বারা এটি ফিল্টার করা উচিত:

mutually_excl = [{0: False, 1: False, 2: True},
                 {0: True, 2: True}]

কীগুলি উপরের সারণীতে কলামগুলি বোঝায়। উদাহরণটি পড়তে হবে:

  • যদি 0 টি মিথ্যা এবং 1 টি মিথ্যা হয় তবে 2 টি সত্য হতে পারে না
  • যদি 0 সত্য হয়, 2 সত্য হতে পারে না

এই ফিল্টারগুলির উপর ভিত্তি করে, প্রত্যাশিত আউটপুটটি হ'ল:

       0      1      2
1   True   True  False
3   True  False  False
4  False   True   True
5  False   True  False
7  False  False  False

আমার ব্যবহারের ক্ষেত্রে, ফিল্টার করা টেবিলটি সম্পূর্ণ কারটিশিয়ান পণ্যটির চেয়ে ছোট আকারের একাধিক অর্ডার (উদাহরণস্বরূপ কিছু 1000 এর পরিবর্তে 2**24 (16777216))।

নীচে আমার তিনটি নিজস্ব সমাধান রয়েছে যার প্রত্যেকটির নিজস্ব মতামত রয়েছে এবং একেবারে শেষে আলোচনা করা হয়েছে।


import random
import pandas as pd
import itertools
import wrapt
import time
import operator
import functools

def get_mutually_excl(n, nfilt):  # generate random example filter
    ''' Example: `get_mutually_excl(9, 2)` creates a list of two filters with
    maximum index `n=9` and each filter length between 2 and `int(n/3)`:
    `[{1: True, 2: False}, {3: False, 2: True, 6: False}]` '''
    random.seed(2)
    return [{random.choice(range(n)): random.choice([True, False])
                           for _ in range(random.randint(2, int(n/3)))}
                           for _ in range(nfilt)]

@wrapt.decorator
def timediff(f, _, args, kwargs):
    t = time.perf_counter()
    res = f(*args)
    return res, time.perf_counter() - t

সমাধান 1: প্রথমে ফিল্টার করুন, তারপরে মার্জ করুন।

প্রতিটি ফিল্টার এন্ট্রি (উদাঃ {0: True, 2: True}) এই ফিল্টার এন্ট্রি ( [0, 2]) এর সূচীগুলির সাথে সংশ্লিষ্ট কলামগুলি সহ একটি উপ-টেবিলে প্রসারিত করুন । এই উপ-সারণী ( [True, True]) থেকে একক ফিল্টার করা সারি সরান । ফিল্টারযুক্ত সংমিশ্রণের সম্পূর্ণ তালিকা পেতে সম্পূর্ণ টেবিলের সাথে মার্জ করুন।

@timediff
def make_df_comb_filt_merge(n, nfilt):

    mutually_excl = get_mutually_excl(n, nfilt)

    # determine missing (unfiltered) columns
    cols_missing = set(range(n)) - set(itertools.chain.from_iterable(mutually_excl))

    # complete dataframe of unfiltered columns with column "temp" for full outer merge
    df_comb = pd.DataFrame(itertools.product(*([[True, False]] * len(cols_missing))),
                            columns=cols_missing).assign(temp=1)

    for filt in mutually_excl:  # loop through individual filters

        # get columns and bool values of this filters as two tuples with same order
        list_col, list_bool = zip(*filt.items())

        # construct dataframe
        df = pd.DataFrame(itertools.product(*([[True, False]] * len(list_col))),
                                columns=list_col)

        # filter remove a *single* row (by definition)
        df = df.loc[df.apply(tuple, axis=1) != list_bool]

        # determine which rows to merge on
        merge_cols = list(set(df.columns) & set(df_comb.columns))
        if not merge_cols:
            merge_cols = ['temp']
            df['temp'] = 1

        # merge with full dataframe
        df_comb = pd.merge(df_comb, df, on=merge_cols)

    df_comb.drop('temp', axis=1, inplace=True)
    df_comb = df_comb[range(n)]
    df_comb = df_comb.sort_values(df_comb.columns.tolist(), ascending=False)

    return df_comb.reset_index(drop=True)

সমাধান 2: সম্পূর্ণ সম্প্রসারণ, তারপরে ফিল্টার করুন

পূর্ণ কারটিশিয়ান পণ্যটির জন্য ডেটাফ্রেম তৈরি করুন: পুরো জিনিসটি স্মৃতিতে শেষ হয়। ফিল্টারগুলির মাধ্যমে লুপ করুন এবং প্রত্যেকের জন্য একটি মুখোশ তৈরি করুন। প্রতিটি মুখোশ টেবিল প্রয়োগ করুন।


@timediff
def make_df_comb_exp_filt(n, nfilt):

    mutually_excl = get_mutually_excl(n, nfilt)

    # expand all bool combinations into dataframe
    df_comb = pd.DataFrame(itertools.product(*([[True, False]] * n)),
                           dtype=bool)

    for filt in mutually_excl:

        # generate total filter mask for given excluded combination
        mask = pd.Series(True, index=df_comb.index)
        for col, bool_act in filt.items():
            mask = mask & (df_comb[col] == bool_act)

        # filter dataframe
        df_comb = df_comb.loc[~mask]

    return df_comb.reset_index(drop=True)

সমাধান 3: ফিল্টার পুনরুদ্ধারকারী

পূর্ণ কার্টেসিয়ান পণ্যটি একটি পুনরুক্তি রাখুন। প্রতিটি সারিতে এটি কোনও ফিল্টার দ্বারা বাদ দেওয়া হয়েছে কিনা তা পরীক্ষা করার সময় লুপ।

@timediff
def make_df_iter_filt(n, nfilt):

    mutually_excl = get_mutually_excl(n, nfilt)

    # switch to [[(1, 13), (True, False)], [(4, 9), (False, True)], ...]
    mutually_excl_index = [list(zip(*comb.items()))
                                for comb in mutually_excl]

    # create iterator
    combs_iter = itertools.product(*([[True, False]] * n))

    @functools.lru_cache(maxsize=1024, typed=True)  # small benefit
    def get_getter(list_):
        # Used to access combs_iter row values as indexed by the filter
        return operator.itemgetter(*list_)

    def check_comb(comb_inp, comb_check):
        return get_getter(comb_check[0])(comb_inp) == comb_check[1]

    # loop through the iterator
    # drop row if any of the filter matches
    df_comb = pd.DataFrame([comb_inp for comb_inp in combs_iter
                       if not any(check_comb(comb_inp, comb_check)
                                  for comb_check in mutually_excl_index)])

    return df_comb.reset_index(drop=True)

উদাহরণ চালান

dict_time = dict.fromkeys(itertools.product(range(16, 23, 2), range(3, 20)))

for n, nfilt in dict_time:
    dict_time[(n, nfilt)] = {'exp_filt': make_df_comb_exp_filt(n, nfilt)[1],
                             'filt_merge': make_df_comb_filt_merge(n, nfilt)[1],
                             'iter_filt': make_df_iter_filt(n, nfilt)[1]}

বিশ্লেষণ

import seaborn as sns
import matplotlib.pyplot as plt

df_time = pd.DataFrame.from_dict(dict_time, orient='index',
                                 ).rename_axis(["n", "nfilt"]
                                 ).stack().reset_index().rename(columns={'level_2': 'solution', 0: 'time'})

g = sns.FacetGrid(df_time.query('n in %s' % str([16,18,20,22])),
                  col="n",  hue="solution", sharey=False)
g = (g.map(plt.plot, "nfilt", "time", marker="o").add_legend())

এখানে চিত্র বর্ণনা লিখুন

সমাধান 3 : পুনরাবৃত্তভিত্তিক পদ্ধতির ( comb_iterator) এর চলমান সময়গুলি বিরক্তিকর, তবে মেমরির উল্লেখযোগ্য ব্যবহার নেই। আমি বোধ করি উন্নতির জন্য জায়গা রয়েছে যদিও অনিবার্য লুপটি চলমান সময়ের ক্ষেত্রে কঠোর সীমাবদ্ধ করে।

সমাধান 2 : সম্পূর্ণ কার্টেসিয়ান পণ্যটিকে ডেটাফ্রেমে ( exp_filt) প্রসারিত করার ফলে স্মৃতিতে উল্লেখযোগ্য স্পাইক তৈরি হয় যা আমি এড়াতে চাই। চলমান সময় ঠিক আছে।

সমাধান 1 : পৃথক ফিল্টারগুলি থেকে তৈরি ডেটা ফ্রেমগুলি মার্জ করা ( filt_merge) আমার ব্যবহারিক প্রয়োগের জন্য একটি ভাল সমাধানের মতো মনে হয় (আরও বেশি সংখ্যক ফিল্টারের জন্য চলমান সময় হ্রাস হ্রাস নোট করুন, যা ছোট cols_missingটেবিলের ফলস্বরূপ )। তবুও, এই পদ্ধতির সম্পূর্ণ তৃপ্তিদায়ক নয়: যদি কোনও একক ফিল্টারটিতে সমস্ত কলাম অন্তর্ভুক্ত থাকে তবে পুরো কার্টেসিয়ান পণ্য ( 2**n) স্মৃতিতে শেষ হয়ে যায়, এই সমাধানটির চেয়েও খারাপ তৈরি করে comb_iterator

প্রশ্ন: অন্য কোন ধারণা? একটা ক্রেজি স্মার্ট নম্পটি টু-লাইনার? পুনরুক্তি ভিত্তিক পদ্ধতির কি কোনওভাবে উন্নতি করা যেতে পারে?


1
প্রতিবন্ধকরা সমাধানকারীরা সম্ভবত এই পদ্ধতির চেয়ে বেশি কার্যকর হবে কারণ তারা অনুসন্ধানের জায়গা হ্রাস করে এই সমাধানগুলি খুঁজে বের করে। হয়তো বা সরঞ্জামগুলি একবার দেখুন। স্যাট এর জন্য এখানে একটি উদাহরণ।
ayhan

1
@ ইয়াহান, আমি চেষ্টা করেছি (উত্তর দেখুন) এটি একটি আকর্ষণীয় পদ্ধতির, তবে সাধারণ সমাধান হিসাবে সত্যই উপযুক্ত নয়। ইনপুট জন্য ধন্যবাদ। আমি কিছু শিখেছি :)
mcsoini

হ্যাঁ, এটি একটি স্যাট সমস্যার মতো শোনাচ্ছে , তাই সমস্যাটি যদি যথেষ্ট পরিমাণে থাকে তবে আপনার অবশ্যই একটি সমাধানকারী ব্যবহার করা উচিত। এছাড়াও আপনি চেষ্টা করে দেখতে পারেন or.stackexchange.com
Stradivari

স্যাট সমস্যা হিসাবে স্ট্র্যাডাওয়ারি গঠনের বিষয়টি অবশ্যই বোধগম্য। যদিও এই পদ্ধতির ফিল্টারগুলির সংখ্যার উপর আমি দৃ .় নির্ভরশীলতা পছন্দ করি না। হতে পারে যে আমি সমাধানগুলিতে সঠিকভাবে অ্যাক্সেস করছি না। যেহেতু আপনি আপনার সরঞ্জামগুলি বা সরঞ্জামগুলি সম্পর্কে জানেন তাই সম্ভবত আপনি আমার সম্পর্কিত প্রশ্নটি দেখতে চান ... এটির এখনও একটি স্বীকৃত উত্তর নেই;)
mcsoini

উত্তর:


1

নিম্নলিখিত সময় সময় চেষ্টা করুন:

def in_filter(arr, arr_filt, n):
    return ((arr[:, None] >> (n-1-arr_filt[:, 0])) & 1 == arr_filt[:, 1]).all(axis=1)

def bits_to_boolean(arr, n):
    return ((arr[:, None] >> np.arange(n, dtype=arr.dtype)[::-1]) & 1).astype(bool)

@timediff
def recursive_filter(n, nfilt, dtype='uint32'):
    filts = get_mutually_excl(n, nfilt)
    out = np.arange(2**n, dtype=dtype)
    for filt in filts:
        arr_filt = np.array(list(filt.items()))
        out = out[~in_filter(out, arr_filt, n)]
    return bits_to_boolean(out, n)[::-1]

এটি কার্টেসিয়ান বাইনারি পণ্যগুলির সাথে আচরণ করে যেমন বিটগুলি পূর্ণসংখ্যার সীমাতে এনকোড করা হয় 0..<2**nএবং প্রদত্ত ফিল্টারগুলির সাথে মেলে এমন বিট সিকোয়েন্স রয়েছে এমন নম্বরগুলি পুনরাবৃত্তভাবে সরানোর জন্য ভেক্টরাইজড ফাংশন ব্যবহার করে।

মেমরির দক্ষতা সমস্ত [True, False]কার্তেসিয়ান পণ্য বরাদ্দের চেয়ে ভাল কারণ প্রতিটি বুলিয়ান কমপক্ষে 8 টি বিট প্রতিটি (প্রয়োজনীয়তার চেয়ে 7 বিট বেশি ব্যবহার করে) সংরক্ষণ করা হবে তবে এটি পুনরুক্তি ভিত্তিক পদ্ধতির চেয়ে বেশি মেমরি ব্যবহার করবে। যদি আপনার বড় সমাধানের প্রয়োজন হয় তবে আপনি nএকবারে একটি উপ-রেঞ্জের বরাদ্দ করে এবং পরিচালনা করে এই কাজটি ভেঙে ফেলতে পারেন। আমার প্রথম প্রয়োগে এটি ছিল, তবে এটির জন্য খুব বেশি সুবিধা দেওয়া হয়নি n<=22এবং এর জন্য আউটপুট অ্যারের আকারের গণনা প্রয়োজন, যা ওভারল্যাপিং ফিল্টারগুলি থাকা অবস্থায় জটিল হয়ে ওঠে।


এটি সত্যিই আশ্চর্যজনক!
mcsoini

1

@ অহানের মন্তব্যের ভিত্তিতে আমি একটি বা সরঞ্জাম স্যাট-ভিত্তিক সমাধান প্রয়োগ করেছি। যদিও ধারণাটি দুর্দান্ত, এটি বাইনারি ভেরিয়েবলের বৃহত সংখ্যার জন্য সত্যই সংগ্রাম করে। আমি সন্দেহ করি এটি বড় আইপি সমস্যার মতো, যা পার্কে হাঁটাচলা করে না। যাইহোক, ফিল্টার সংখ্যার উপর দৃ depend় নির্ভরতা এটি নির্দিষ্ট প্যারামিটার কনফিগারেশনের জন্য একটি বৈধ বিকল্প করতে পারে। তবে সাধারণ সমাধান হিসাবে আমি এটি ব্যবহার করব না।

from ortools.sat.python import cp_model

class VarArraySolutionCollector(cp_model.CpSolverSolutionCallback):

    def __init__(self, variables):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self.__variables = variables
        self.solution_list = []

    def on_solution_callback(self):
        self.solution_list.append([self.Value(v) for v in self.__variables])


@timediff
def make_df_comb_sat(n, nfilt):

    mutually_excl = get_mutually_excl(n, nfilt)

    model = cp_model.CpModel()

    make_var_name = 'x{:02d}'.format
    vrs = dict.fromkeys(map(make_var_name, range(n)))
    for var_name in vrs:
        vrs[var_name] = model.NewBoolVar(var_name)

    for filt in mutually_excl:
        list_expr = [vrs[make_var_name(iv)]
                     if not bool_ else getattr(vrs[make_var_name(iv)], 'Not')()
                     for iv, bool_ in filt.items()]
        model.AddBoolOr(list_expr)

    solver = cp_model.CpSolver()
    solution_printer = VarArraySolutionCollector(vrs.values())
    solver.SearchForAllSolutions(model, solution_printer)

    df_comb = pd.DataFrame(solution_printer.solution_list).astype(bool)
    df_comb = df_comb.sort_values(df_comb.columns.tolist(), ascending=False)
    df_comb = df_comb.reset_index(drop=True)

    return df_comb

এখানে চিত্র বর্ণনা লিখুন

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.