मेरे पास एक बहु-आयामी मैट्रिक्स (6D) है जिसे मुझे एक नया 6D-मैट्रिक्स बनाने के लिए पुनरावृति करने की आवश्यकता है। अभी मैं कोड को यथासंभव स्वच्छ बनाने के लिए सूची समझ का उपयोग करता हूं, हालांकि यह वास्तव में छोटा है। मैं उम्मीद कर रहा था कि मेरी मदद करने के लिए कुछ बिल्ड-इन numpy फ़ंक्शन थे, लेकिन सूचियों के भीतर उपयोग किए जाने वाले स्वयं के कार्यों के कारण ऐसे कार्यों को ढूंढना मुश्किल है।

मैंने पहले ही np.fromIter की कोशिश की है, लेकिन यह त्रुटियां, क्योंकि मैं एक बहुआयामी सूची का उपयोग करता हूं। World.allReachableCoords(x1, y1, len(Q1), len(Q1[0]) आसपास के सभी निर्देशांकों का एक सेट देता है ({(x1, y1), (x1 + 1, y1), (x1, y1 + 1) ...}) और world.amountOfPossibleActions सिर्फ 5 लौटाता है।

एल्गोरिथ्म के साथ शुरू होता है

Q1 = np.zeros((heightWorld, widthWorld, heightWorld, widthWorld, world.amountOfPossibleActions,
               world.amountOfPossibleActions))

और फिर नीचे की प्रक्रिया को कई बार दोहराता है।

Q1 = np.array([[[[[[sum(
        world.joinedTransition((x1, y1), sf1, (x2, y2), sf2, action1, action2) *
        (world.joinedU((x1, y1), sf1, (x2, y2), sf2, action1, action2, player) +
         world.joinedU((x1, y1), sf1, (x2, y2), sf2, action2, action1, otherPlayer) +
         gamma * np.amax(Q1[sf1[1]][sf1[0]][sf2[1]][sf2[0]]))
        for sf1 in World.allReachableCoords(x1, y1, len(Q1), len(Q1[0]), world)
        for sf2 in World.allReachableCoords(x2, y2, len(Q1), len(Q1[0]), world)
    )
        for action1 in range(world.amountOfPossibleActions)]
        for action2 in range(world.amountOfPossibleActions)]
        for x1 in range(widthWorld)] for y1 in range(heightWorld)]
        for x2 in range(widthWorld)] for y2 in range(heightWorld)])

जहां शामिल संक्रमण ज्यादातर if-statement की एक स्ट्रिंग है:

# Transition function: Returns 0 if the final state is out of bounds, impassable terrain or too far from the
# initial state. If the given action explains the relation between si and sf return 1, otherwise 0.
def standardTransition(self, si, sf, a):
    if not (0 <= sf[0] <= len(self.grid[0]) and 0 <= sf[1] <= len(self.grid)):
        return 0
    if not (0 <= si[0] <= len(self.grid[0]) and 0 <= si[1] <= len(self.grid)):
        return 0
    if self.grid[sf[1]][sf[0]] == self.i or self.grid[si[1]][si[0]] == self.i:
        return 0
    if abs(si[0] - sf[0]) > 1 or abs(si[1] - sf[1]) > 1:
        return 0
    return {
        0: 1 if sf[0] == si[0] and sf[1] == si[1] else 0,  # Stay
        1: 1 if sf[0] == si[0] and sf[1] == si[1] + 1 else 0,  # Down
        2: 1 if sf[0] == si[0] and sf[1] == si[1] - 1 else 0,  # Up
        3: 1 if sf[0] == si[0] - 1 and sf[1] == si[1] else 0,  # Left
        4: 1 if sf[0] == si[0] + 1 and sf[1] == si[1] else 0  # Right
    }[a]

def joinedTransition(self, si1, sf1, si2, sf2, a1, a2):
    if sf1 == sf2: return 0  # Ending in the same square is impossible.
    if si1 == sf2 and si2 == sf1: return 0  # Going through each other is impossible.
    # Fighting for the same square.
    if si1 == sf1 and performAction(si1, a1) == sf2:  # Player 1 loses the fight
        return self.standardTransition(si1, sf2, a1) * self.standardTransition(si2, sf2,
                                                                               a2) * self.chanceForPlayer1ToWinDuel
    if si2 == sf2 and performAction(si2, a2) == sf1:  # Player 2 loses the fight
        return self.standardTransition(si1, sf1, a1) * self.standardTransition(si2, sf1, a2) * (
                1 - self.chanceForPlayer1ToWinDuel)
    return self.standardTransition(si1, sf1, a1) * self.standardTransition(si2, sf2, a2)

और, allReachableCoords जैसा ऊपर कहा गया है:

def allReachableCoords(x1, y1, height, width, world):
    li = {(x2, y1) for x2 in range(x1 - 1, x1 + 2)}.union({(x1, y2) for y2 in range(y1 - 1, y1 + 2)})
    li = list(filter(lambda r: 0 <= r[0] < width and 0 <= r[1] < height, li))
    return list(filter(lambda r: world.grid[r[1]][r[0]] != world.i, li))

क्या प्रदर्शन में सुधार करने के कोई तरीके हैं? मुझे लगता है कि समाधान सुन्न है, लेकिन अन्य समाधानों का भी स्वागत है। मैं यह भी सोच रहा था कि क्या यह ऐसा कुछ है जो टेंसरफ़्लो में अधिक सुंदर और कुशलता से किया जा सकता है।

0
Emiel Lanckriet 23 अगस्त 2019, 12:03

1 उत्तर

सबसे बढ़िया उत्तर

उपाय: cProfile / line_profiler

किसी कार्यक्रम को गति देने में पहला कदम हमेशा यह मापना होना चाहिए: बिल्कुल कहाँ आपका समय व्यतीत हो रहा है? हमेशा ऐसी चीजें होंगी जो तेज/बेहतर हो सकती हैं, लेकिन अगर गति आपकी मुख्य चिंता है, तो आप पहले अपने कोड के सबसे धीमे हिस्सों से निपटना चाहते हैं।

शुरू करने के लिए, आप हमेशा डिफ़ॉल्ट प्रोफाइलर का उपयोग कर सकते हैं cProfile जो इसके साथ आता है अजगर। कोड की प्रति पंक्ति थोड़ा अधिक विस्तृत दृश्य के लिए, मैं line_profiler को देखने का सुझाव दूंगा। यद्यपि सेटअप थोड़ा अधिक शामिल है, यह आपको बेहतर परिणाम दे सकता है यदि समय ज्यादातर कार्यों के बजाय संचालन में व्यतीत होता है।

समयबद्ध प्रयोग

यह देखते हुए कि मुझे आपके कोड के किसी भी प्रोफाइलिंग परिणाम का पता नहीं है, अभी भी कुछ अन्य चीजें हैं जो मैंने देखी हैं। पाइथन के बिल्ट-इन timeit मॉड्यूल के साथ छोटे-छोटे प्रयोगों को चलाने के बाद, यहां आपके कोड को तेज़, स्वच्छ या दोनों बनाने के लिए कुछ स्पष्ट सुझाव दिए गए हैं।

सुन्न अनुक्रमण

प्रदर्शन में सुधार करने का एक प्रारंभिक तरीका केवल अपनी अनुक्रमणिका बदलना हो सकता है। जब आप एक सरणी को numpy में अनुक्रमित करते हैं, तो ऐसा लगता है कि यह एक मध्यवर्ती वस्तु लौटाता है। तो वर्ग कोष्ठक का प्रत्येक नया सेट एक नया __getitem__ फ़ंक्शन कॉल है, जिसमें सभी संबद्ध ओवरहेड हैं। इसका मतलब है कि आपका Q1[v][w][x][y] अनुवादित (सॉर्ट-ऑफ़) है

Q1.__getitem__(v).__getitem__(w).__getitem__(x).__getitem__(y)

Numpy मूल रूप से tuple द्वारा अनुक्रमण का समर्थन करता है, जिसका उपयोग आप बिना उपयोग कर सकते हैं स्पष्ट रूप से एक टपल बनाना:

Q1[v][w][x][y]  # This is slow
Q1[(v,w,x,y)]   # This is faster
Q1[v,w,x,y]     # This does the same thing

उत्तरार्द्ध का उपयोग करके, आप पहले से ही उस आइटम को अनुक्रमित करने में लगने वाले समय का लगभग आधा बचा सकते हैं जिसे आप ढूंढ रहे हैं।

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0]][sf[1]][sf[0]][sf[1]]"
1000000 loops, best of 3: 0.651 usec per loop

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0],sf[1],sf[0],sf[1]]"
1000000 loops, best of 3: 0.298 usec per loop

यानी np.amax(Q1[sf1[1]] [sf1[0]] [sf2[1]] [sf2[0]]) को np.amax(Q1[sf1[1], sf1[0], sf2[1], sf2[0]]) से बदलना।

इसके अतिरिक्त, आप अपने sf वेरिएबल्स को लूप के बजाय (for sf1_0, sf1_1 in ...) में अनपैक कर सकते हैं, जिससे एक और समय निकल जाएगा:

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf_0, sf_1=(3,4)" "q = Q1[sf_0,sf_1,sf_0,sf_1]"
1000000 loops, best of 3: 0.216 usec per loop

np.amax(Q1[sf1_1, sf1_0, sf2_1, sf2_0]) देना, जो मुझे लगता है कि थोड़ा साफ भी है :)

पुनरावृति: itertools.product

आप वर्तमान में मैन्युअल रूप से/स्पष्ट रूप से श्रेणियों पर लूपिंग कर रहे हैं, लेकिन आपके पास वास्तव में केवल अंतरतम लूप में गणना है। इसका मतलब है कि इसके बाहर के पांच लूपों में, आप केवल range ऑब्जेक्ट बना रहे हैं और उन्हें समाप्त कर रहे हैं। प्रदर्शन के लिहाज से, यह कोई बड़ी अड़चन नहीं है, लेकिन यह बहुत साफ नहीं है। सौभाग्य से, अंतर्निहित itertools लाइब्रेरी में product< है /a> इस प्रकार के कार्यों के लिए उपकरण:

# 6 nested loops
$python -m timeit -n 100 -s "a=0" "for u in range(10):" 
                                  "    for v in range(10):" 
                                  "        for w in range(10):" 
                                  "            for x in range(10):" 
                                  "                for y in range(10):" 
                                  "                    for z in range(10):" 
                                  "                        a+=1"
100 loops, best of 3: 57.3 msec per loop

# itertools.product
$python -m timeit -n 100 -s "from itertools import product; a=0" 
                            "for u,v,w,x,y,z in product(range(10),range(10),range(10),range(10),range(10),range(10)):" 
                            "    a+=1"
100 loops, best of 3: 55.6 msec per loop

इस उदाहरण में यह नेस्टिंग के 5 (!) स्तरों को बचाता है, जबकि यह थोड़ा तेज़ भी होता है। यह केवल एक बार सामने product() बनाकर और बाद में इसका पुन: उपयोग करके थोड़ा तेज़ हो सकता है, क्योंकि आप पूरे समय एक ही लूप पर पुनरावृति कर रहे हैं। बस इसका स्पष्ट रूप से एक list() लेना सुनिश्चित करें, क्योंकि product() एक जनरेटर लौटाएगा जो खाली है यदि आप इसे दो बार उपयोग करने का प्रयास करते हैं (उदाहरण के लिए यहां अधिक जानकारी के लिए)।

# Explicitly storing the resulting `product()` up front
$python -m timeit -n 100 -s "from itertools import product; a=0; it=list(product(range(10),range(10),range(10),range(10),range(10),range(10)))" 
                            "for u,v,w,x,y,z in it:" 
                            "    a+=1"
$100 loops, best of 3: 52.8 msec per loop

कैशिंग

अपने आंतरिक लूप में आप अपने World ऑब्जेक्ट के तरीकों का एक समूह भी कहते हैं। यदि उन विधियों के परिणाम Q1 पर बिल्कुल भी निर्भर नहीं हैं, तो आप निश्चित रूप से एक ही चीज़ को दो बार पुनर्गणना कर रहे हैं। फिर आप स्मृति के लिए गणना समय का व्यापार कर सकते हैं: सभी मानों को एक बार पूर्व-गणना करें और उन्हें किसी अन्य numpy सरणी में संग्रहीत करें। कंप्यूटेशंस के साथ फ़ंक्शन कॉल की तुलना में एक सरणी लुक-अप लगभग (बहुत) तेज होने की गारंटी है।

यह तय करने के लिए कि पहले इसे कहां करना है, आपको अपने प्रोफाइलिंग प्रयासों के परिणामों का संदर्भ लेना चाहिए;)

2
Energya 2 सितंबर 2019, 02:07