6.10. Levittäminen (broadcasting)¶
Kun binäärioperaattori saa kaksi taulukkoa, joiden muodot eivät täsmää tarkalleen, numpy ei nosta poikkeusta – se levittää. Levittäminen on pieni joukko sääntöjä, jotka päättävät, ovatko kaksi muotoa yhteensopivia ja, jos ovat, miten pienempi venytetään virtuaalisesti vastaamaan suurempaa.
6.10.1. Säännöt¶
Kun kahdella operandilla on muodot A ja B, numpy käy ne läpi kahdessa vaiheessa.
Sovita asteet (rank). Jos toisella operandilla on vähemmän akseleita kuin toisella,
numpytäydentää virtuaalisesti sen muodon alkuun kokoa-1 olevia akseleita, kunnes molemmilla muodoilla on sama määrä akseleita. Muotoa(3,)oleva 1-ulotteinen operandi yhdistettynä muotoa(2, 3)olevaan 2-ulotteiseen operandiin muuttuu muodoksi(1, 3)muotoa(2, 3)vastaan.Tarkista jokainen akseli. Käytäessä nyt yhtä pitkät muodot läpi akseli akselilta, kunkin kokoparin on täytettävä jokin kahdesta ehdosta: koot ovat yhtä suuret tai jompikumpi niistä on 1. Kokoa-1 oleva akseli venytetään virtuaalisesti operaatiota varten toisen puolen kokoiseksi. Pari
(1, 3)muotoa(2, 3)vastaan on yhteensopiva, koska ensimmäisellä akselilla on 1 (venyy 2:ksi) ja toinen akseli täsmää (3 == 3); tuloksen muoto on(2, 3).
Jos jokin akselipari ei täytä kumpaakaan ehtoa, muodot ovat yhteensopimattomat ja operaattori nostaa poikkeuksen ValueError.
6.10.2. Esimerkkejä¶
Skalaari mitä tahansa taulukkoa vastaan. Skalaari toimii kuin muoto (1,) ja venyy mihin tahansa:
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10 # (2, 3) + scalar -> (2, 3)
1-ulotteinen vektori 2-ulotteisen matriisin yli. Sääntö 1 lisää eteen kokoa-1 olevan akselin tehden muodosta (3,) muodon (1, 3); sääntö 2 venyttää sitten kyseisen rivin a:n jokaisen sarakkeen alas:
row = np.array([100, 200, 300], dtype=np.float)
a + row # (2, 3) + (3,) -> (2, 3)
Kaksi yhtä pitkää 1-ulotteista taulukkoa lisätään alkioittain – levittämistä ei tarvita:
np.arange(4) + np.arange(4)
Sarakevektori rivivektoria vastaan tuottaa 2-ulotteisen ”ulkoisen” muodon: (4, 1) yhdistettynä muotoon (3,) muuttuu muodoksi (4, 1) muotoa (1, 3) vastaan asteen lisäyksen jälkeen, ja sääntö 2 venyttää kummankin operandin sen kokoa-1 olevaa akselia pitkin:
x = np.array([1, 2, 3, 4]).reshape((4, 1)) # column
y = np.array([10, 20, 30]) # row
x + y # (4, 3) matrix
Samat muotosäännöt pätevät mihin tahansa kaksiargumenttiseen ufunc-funktioon, arctan2() mukaan lukien:
np.arctan2(y, 1.0)
np.arctan2(y, x)
6.10.3. Mitä levittäminen ei varaa¶
Venytys on virtuaalinen. numpy käy molemmat operandit yhdessä läpi lukien pienempää uudelleen sen levitysakselin suunnassa sen sijaan, että kopioisi sen. Lyhyemmän taulukon dataa ei koskaan monisteta muistissa.
Muistin kannalta merkitsee tulostaulukon koko. a + row varaa tuloksen, jonka muoto on a:n muoto, ei a:n muoto plus row:n muoto. Pitkät levitysketjut voivat silti tuottaa suuria välituloksia.
6.10.4. Kun levittäminen menee pieleen¶
Klassinen epäonnistuminen on kaksi muotoa, joista kummallakaan ei ole kokoa-1 olevaa akselia venytettäväksi ja koot ovat eri suuret – esimerkiksi (3, 4) muotoa (4, 3) vastaan. Sääntö 2 ei voi sovittaa 3:a 4:ää vastaan, joten numpy nostaa poikkeuksen ValueError.
Hienovaraisempi ongelma on levitys, joka onnistuu, mutta ei sillä tavalla kuin sovellus tarkoitti. (5,) muotoa (5, 1) vastaan on tyyppiesimerkki: asteen lisäys muuttaa muodon (5,) muodoksi (1, 5), joka levittyy muotoa (5, 1) vastaan tuottaen (5, 5)-matriisin – kahden vektorin ulkoisen yhdistelmän, ei pituuden-5 alkioittaista tulosta, jonka sovellus luultavasti halusi. Epäselvissä tapauksissa tulosta shape molemmilta puolilta ja käy säännöt läpi askel kerrallaan ennen kuin turvaudut metodeihin reshape() tai transpose().