| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391 |
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('gpu.js')) :
- typeof define === 'function' && define.amd ? define(['exports', 'gpu.js'], factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.brain = {}, global.gpu_js));
- }(this, (function (exports, gpu_js) { 'use strict';
- /**
- * Relu Activation, aka Rectified Linear Unit Activation
- * @description https://en.wikipedia.org/wiki/Rectifier_(neural_networks)
- */
- function activate$3(weight) {
- return Math.max(0, weight);
- }
- /**
- * Relu derivative
- */
- function measure$3(weight, delta) {
- if (weight <= 0) {
- return 0;
- }
- return delta;
- }
- var relu$2 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- activate: activate$3,
- measure: measure$3
- });
- /**
- * sigmoid activation
- */
- function activate$2(value) {
- return 1 / (1 + Math.exp(-value));
- }
- /**
- * sigmoid derivative
- */
- function measure$2(weight, error) {
- return weight * (1 - weight) * error;
- }
- var sigmoid$2 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- activate: activate$2,
- measure: measure$2
- });
- /**
- * Hyperbolic tan
- */
- function activate$1(weight) {
- return Math.tanh(weight);
- }
- /**
- * @description grad for z = tanh(x) is (1 - z^2)
- */
- function measure$1(weight, error) {
- return (1 - weight * weight) * error;
- }
- var tanh$2 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- activate: activate$1,
- measure: measure$1
- });
- /**
- * Leaky Relu Activation, aka Leaky Rectified Linear Unit Activation
- * @description https://en.wikipedia.org/wiki/Rectifier_(neural_networks)
- */
- function activate(weight) {
- return weight > 0 ? weight : 0.01 * weight;
- }
- /**
- * Leaky Relu derivative
- */
- function measure(weight, error) {
- return weight > 0 ? error : 0.01 * error;
- }
- var leakyRelu$1 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- activate: activate,
- measure: measure
- });
- var index$1 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- relu: relu$2,
- sigmoid: sigmoid$2,
- tanh: tanh$2,
- leakyRelu: leakyRelu$1
- });
- class CrossValidate {
- constructor(initClassifier) {
- this.json = {
- avgs: {
- error: 0,
- iterations: 0,
- testTime: 0,
- trainTime: 0,
- },
- stats: {
- total: 0,
- testSize: 0,
- trainSize: 0,
- },
- sets: [],
- };
- this.initClassifier = initClassifier;
- }
- testPartition(trainOpts, trainSet, testSet) {
- const classifier = this.initClassifier();
- const beginTrain = Date.now();
- const trainingStats = classifier.train(trainSet, trainOpts);
- const beginTest = Date.now();
- const testStats = classifier.test(testSet);
- const endTest = Date.now();
- return {
- ...testStats,
- trainTime: beginTest - beginTrain,
- testTime: endTest - beginTest,
- iterations: trainingStats.iterations,
- error: trainingStats.error,
- total: testStats.total,
- network: classifier.toJSON(),
- };
- }
- /**
- * Randomize array element order in-place.
- * Using Durstenfeld shuffle algorithm.
- * source: http://stackoverflow.com/a/12646864/1324039
- */
- shuffleArray(array) {
- for (let i = array.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- const temp = array[i];
- array[i] = array[j];
- array[j] = temp;
- }
- return array;
- }
- train(data, trainOpts = {}, k = 4) {
- if (data.length < k) {
- throw new Error(`Training set size is too small for ${data.length} k folds of ${k}`);
- }
- this.shuffleArray(data);
- const size = data.length / k;
- const avgs = {
- trainTime: 0,
- testTime: 0,
- iterations: 0,
- error: 0,
- };
- const stats = {
- total: 0,
- testSize: 0,
- trainSize: 0,
- };
- const binaryStats = {
- total: 0,
- testSize: 0,
- trainSize: 0,
- truePos: 0,
- trueNeg: 0,
- falsePos: 0,
- falseNeg: 0,
- precision: 0,
- recall: 0,
- accuracy: 0,
- };
- const results = [];
- let isBinary = null;
- for (let i = 0; i < k; i++) {
- const dclone = data.slice(0);
- const testSet = dclone.splice(i * size, size);
- const trainSet = dclone;
- const result = this.testPartition(trainOpts, trainSet, testSet);
- if (isBinary === null) {
- isBinary =
- result.hasOwnProperty('falseNeg') &&
- result.hasOwnProperty('falsePos') &&
- result.hasOwnProperty('trueNeg') &&
- result.hasOwnProperty('truePos');
- if (isBinary) {
- Object.assign(stats, binaryStats);
- }
- }
- avgs.iterations += result.iterations;
- avgs.testTime += result.testTime;
- avgs.trainTime += result.trainTime;
- avgs.error += result.error;
- stats.total += result.total;
- if (CrossValidate.isBinaryStats(stats) &&
- CrossValidate.isBinaryPartitionResults(result)) {
- stats.accuracy += result.accuracy;
- stats.falseNeg += result.falseNeg;
- stats.falsePos += result.falsePos;
- stats.precision += result.precision;
- stats.recall += result.recall;
- stats.trueNeg += result.trueNeg;
- stats.truePos += result.truePos;
- }
- results.push(result);
- }
- avgs.error /= k;
- avgs.iterations /= k;
- avgs.testTime /= k;
- avgs.trainTime /= k;
- if (CrossValidate.isBinaryStats(stats)) {
- stats.precision = stats.truePos / (stats.truePos + stats.falsePos);
- stats.recall = stats.truePos / (stats.truePos + stats.falseNeg);
- stats.accuracy = (stats.trueNeg + stats.truePos) / stats.total;
- }
- stats.testSize = size;
- stats.trainSize = data.length - size;
- this.json = {
- avgs: avgs,
- stats: stats,
- sets: results,
- };
- return this.json;
- }
- toNeuralNetwork() {
- return this.fromJSON(this.json);
- }
- toJSON() {
- return this.json;
- }
- fromJSON(crossValidateJson) {
- const winningJSON = crossValidateJson.sets.reduce((prev, cur) => (prev.error < cur.error ? prev : cur));
- return this.initClassifier().fromJSON(winningJSON.network);
- }
- }
- CrossValidate.isBinaryStats = (stats) => {
- return (stats.accuracy !== undefined);
- };
- CrossValidate.isBinaryResults = (stats) => stats.stats.accuracy !== undefined;
- CrossValidate.isBinaryPartitionResults = (stats) => stats.accuracy !==
- undefined;
- let gpuInstance = null;
- /**
- * Sets up the gpu.js instance
- */
- function setup(value) {
- gpuInstance = value;
- }
- function makeKernel(fn, settings) {
- let _gpuInstance = gpuInstance;
- if (_gpuInstance === null) {
- _gpuInstance = new gpu_js.GPU({ mode: 'gpu' });
- setup(_gpuInstance);
- }
- return _gpuInstance
- .createKernel(fn, settings)
- .setPipeline(true);
- }
- function makeKernelMap(map, fn, settings) {
- let _gpuInstance = gpuInstance;
- if (_gpuInstance === null) {
- _gpuInstance = new gpu_js.GPU({ mode: 'gpu' });
- setup(_gpuInstance);
- }
- return _gpuInstance
- .createKernelMap(map, fn, settings)
- .setPipeline(true);
- }
- /**
- * Compiles a function into a gpu.js dev mode kernel
- */
- // export function makeDevKernel(
- // fn: ThreadFunction,
- // settings: makeKernelSettings
- // ): IKernelRunShortcut {
- // if ('map' in settings) {
- // throw new Error('map kernels are not supported by dev kernels');
- // }
- // const gpu = new GPU({ mode: 'dev' });
- // return gpu.createKernel(fn, settings);
- // }
- function kernelInput(value, size) {
- return new gpu_js.Input(value, size);
- }
- /**
- * Deletes a gpu.js texture and frees VRAM
- */
- function release(possibleTexture) {
- if (possibleTexture instanceof gpu_js.Texture) {
- possibleTexture.delete();
- }
- }
- /**
- * Cleans ie sets all elements to 0 of a Texture or a js array
- */
- function clear(value) {
- if (value instanceof gpu_js.Texture) {
- value.clear();
- return;
- }
- // array
- if (Array.isArray(value)) {
- if (typeof value[0] === 'number') {
- value.fill(0);
- }
- else if (typeof value[0][0] === 'number') {
- for (let x = 0; x < value.length; x++) {
- value[x].fill(0);
- }
- return;
- }
- else if (typeof value[0][0][0] === 'number') {
- // cube
- for (let y = 0; y < value.length; y++) {
- const row = value[y];
- for (let x = 0; x < row.length; x++) {
- row[x].fill(0);
- }
- }
- return;
- }
- }
- if (value instanceof Float32Array) {
- value.fill(0);
- return;
- }
- throw new Error('unhandled value');
- }
- /**
- * Clones a value
- */
- function clone(value) {
- if (value instanceof gpu_js.Texture) {
- return value.clone();
- }
- if (value instanceof Float32Array) {
- return value.slice(0);
- }
- if (Array.isArray(value)) {
- if (typeof value[0] === 'number') {
- return value.slice(0);
- }
- else if (typeof value[0][0] === 'number') {
- const matrix = new Array(value.length);
- for (let x = 0; x < value.length; x++) {
- matrix[x] = value[x].slice(0);
- }
- return matrix;
- }
- else if (typeof value[0][0][0] === 'number') {
- const cube = new Array(value.length);
- for (let y = 0; y < value.length; y++) {
- const row = value[y];
- const matrix = new Array(row.length);
- for (let x = 0; x < row.length; x++) {
- matrix[x] = row[x].slice(0);
- }
- }
- return cube;
- }
- }
- throw new Error('unhandled value');
- }
- /**
- * 2D Mean Squared Error
- */
- function mse2d(errors) {
- let sum = 0;
- for (let y = 0; y < this.constants.height; y++) {
- for (let x = 0; x < this.constants.width; x++) {
- sum += errors[y][x] ** 2;
- }
- }
- return sum / this.constants.length;
- }
- class MeanSquaredError {
- constructor({ width, height }) {
- this.calculate = makeKernel(mse2d, {
- output: [1],
- constants: {
- width,
- height,
- length: width * height,
- },
- immutable: true,
- });
- this.addAbsolute = makeKernel(function (prevError, prevLayerErrors) {
- return prevError[0] + Math.abs(prevLayerErrors[0][0]);
- }, {
- output: [1],
- immutable: true,
- });
- this.add = makeKernel(function (value1, value2) {
- return value1[0] + value2[0];
- }, {
- output: [1],
- immutable: true,
- });
- this.divide = makeKernel(function (length, mseSum) {
- const value = mseSum[0];
- if (value > 0) {
- return value / length;
- }
- return 0;
- }, {
- output: [1],
- immutable: true,
- });
- }
- }
- const baseLayerDefaultSettings = {
- width: 1,
- height: 1,
- depth: null,
- weights: null,
- deltas: null,
- praxis: null,
- praxisOpts: null,
- cleanupDeltas: true,
- };
- class BaseLayer {
- constructor(settings) {
- this.praxis = null;
- this.predictKernel = null;
- this.compareKernel = null;
- if (settings) {
- this.settings = { ...baseLayerDefaultSettings, ...settings };
- }
- else {
- this.settings = { ...baseLayerDefaultSettings };
- }
- this.setupPraxis();
- }
- get width() {
- var _a;
- return (_a = this.settings.width) !== null && _a !== void 0 ? _a : 0;
- }
- get height() {
- var _a;
- return (_a = this.settings.height) !== null && _a !== void 0 ? _a : 0;
- }
- get depth() {
- var _a;
- return (_a = this.settings.depth) !== null && _a !== void 0 ? _a : 0;
- }
- get weights() {
- return this.settings.weights;
- }
- set weights(weights) {
- this.settings.weights = weights;
- if (this.settings.cleanupDeltas && this.deltas) {
- clear(this.deltas);
- }
- }
- get deltas() {
- return this.settings.deltas;
- }
- set deltas(deltas) {
- this.settings.deltas = deltas;
- }
- get id() {
- var _a;
- return (_a = this.settings.id) !== null && _a !== void 0 ? _a : '';
- }
- set id(title) {
- this.settings.id = title;
- }
- setupPraxis() {
- const { initPraxis, praxis, praxisOpts } = this.settings;
- if (!this.praxis) {
- if (initPraxis) {
- if (praxisOpts) {
- this.praxis = initPraxis(this, praxisOpts);
- }
- else {
- this.praxis = initPraxis(this);
- }
- }
- else if (praxis) {
- this.praxis = praxis;
- }
- }
- }
- /*
- get weights() {
- return this._weights;
- }
-
- set weights(value) {
- if (value) {
- if (value.dimensions) {
- if (value.dimensions[0] !== this.width) {
- throw new Error(`${this.constructor.name}.weights being set with improper value width`);
- }
- if (value.dimensions[1] !== this.height) {
- throw new Error(`${this.constructor.name}.weights being set with improper value height`);
- }
- } else {
- if (value[0].length !== this.width) {
- throw new Error(`${this.constructor.name}.weights being set with improper value width`);
- }
- if (value.length !== this.height) {
- throw new Error(`${this.constructor.name}.weights being set with improper value height`);
- }
- }
- }
- this._weights = value;
- }
-
- get deltas() {
- return this._deltas;
- }
-
- set deltas(value) {
- if (value) {
- if (value.dimensions) {
- if (value.dimensions[0] !== this.width) {
- throw new Error(`${this.constructor.name}.deltas being set with improper value width`);
- }
- if (value.dimensions[1] !== this.height) {
- throw new Error(`${this.constructor.name}.deltas being set with improper value height`);
- }
- } else {
- if (value[0].length !== this.width) {
- throw new Error(`${this.constructor.name}.deltas being set with improper value width`);
- }
- if (value.length !== this.height) {
- throw new Error(`${this.constructor.name}.deltas being set with improper value height`);
- }
- }
- }
- this._deltas = value;
- } */
- validate() {
- if (Number.isNaN(this.height)) {
- throw new Error(`${this.constructor.name} layer height is not a number`);
- }
- if (Number.isNaN(this.width)) {
- throw new Error(`${this.constructor.name} layer width is not a number`);
- }
- if (this.height < 1) {
- throw new Error(`${this.constructor.name} layer height is less than 1`);
- }
- if (this.width < 1) {
- throw new Error(`${this.constructor.name} layer width is less than 1`);
- }
- }
- setupKernels(isTraining) { }
- reuseKernels(layer) {
- if (layer.width !== this.width) {
- throw new Error(`${this.constructor.name} kernel width mismatch ${layer.width} is not ${this.width}`);
- }
- if (layer.height !== this.height) {
- throw new Error(`${this.constructor.name} kernel width mismatch ${layer.height} is not ${this.height}`);
- }
- if (layer.hasOwnProperty('predictKernel') && layer.predictKernel !== null) {
- if (!layer.predictKernel.immutable) {
- throw new Error(`${layer.constructor.name}.predictKernel is not reusable, set kernel.immutable = true`);
- }
- this.predictKernel = layer.predictKernel;
- }
- if (layer.hasOwnProperty('compareKernel') && layer.compareKernel !== null) {
- if (!layer.compareKernel.immutable) {
- throw new Error(`${layer.constructor.name}.compareKernel is not reusable, set kernel.immutable = true`);
- }
- this.compareKernel = layer.compareKernel;
- }
- this.praxis = layer.praxis;
- }
- predict(inputs) { }
- compare(targetValues) { }
- learn(learningRate) { }
- toArray() {
- return Array.isArray(this.weights)
- ? this.weights
- : this.weights.toArray();
- }
- toJSON() {
- return BaseLayer.toJSON(this);
- }
- static toJSON(layer) {
- const { weights } = layer;
- return {
- width: layer.width,
- height: layer.height,
- depth: layer.depth,
- weights: toUntypedArray((weights && weights instanceof gpu_js.Texture
- ? weights.toArray()
- : weights)),
- type: layer.constructor.name,
- praxisOpts: layer.praxis ? layer.praxis.toJSON() : null,
- };
- }
- }
- function toUntypedArray(weights) {
- if (weights === null)
- return null;
- if (Array.isArray(weights)) {
- if (typeof weights[0] === 'number') {
- return weights;
- }
- else if (Array.isArray(weights[0]) && typeof weights[0][0] === 'number') {
- return weights;
- }
- else if (Array.isArray(weights[0][0]) &&
- typeof weights[0][0][0] === 'number') {
- return weights;
- }
- else if (weights[0] instanceof Float32Array) {
- const matrix = weights;
- return matrix.map((row) => {
- return Array.from(row);
- });
- }
- else if (weights[0][0] instanceof Float32Array) {
- const cube = weights;
- return cube.map((matrix) => {
- return matrix.map((row) => {
- return Array.from(row);
- });
- });
- }
- }
- else if (weights) {
- return Array.from(weights);
- }
- throw new Error('unexpected value');
- }
- /**
- * Returns an array of zeros
- */
- function zeros$1(size) {
- return new Float32Array(size);
- }
- /**
- * Returns a 2D tensor(matrix) of zeros
- */
- function zeros2D(width, height) {
- const result = new Array(height);
- for (let y = 0; y < height; y++) {
- result[y] = zeros$1(width);
- }
- return result;
- }
- /**
- * Returns a 3D tensor of arrays
- */
- function zeros3D(width, height, depth) {
- const result = new Array(depth);
- for (let z = 0; z < depth; z++) {
- result[z] = zeros2D(width, height);
- }
- return result;
- }
- class Activation extends BaseLayer {
- constructor(inputLayer, settings) {
- super(settings);
- this.inputLayer = inputLayer;
- const { width, height, depth } = this;
- this.predictKernel = null;
- this.compareKernel = null;
- this.validate();
- if (depth > 0) {
- this.weights = zeros3D(width, height, depth);
- this.deltas = zeros3D(width, height, depth);
- }
- else if (height > 0) {
- this.weights = zeros2D(width, height);
- this.deltas = zeros2D(width, height);
- }
- this.setupPraxis();
- }
- get width() {
- return this.inputLayer.width;
- }
- get height() {
- return this.inputLayer.height;
- }
- get depth() {
- return this.inputLayer.depth;
- }
- }
- class Filter extends BaseLayer {
- constructor(settings, inputLayer) {
- super();
- this.settings = settings;
- this.inputLayer = inputLayer;
- }
- get width() {
- return this.inputLayer.width;
- }
- get height() {
- return this.inputLayer.height;
- }
- get depth() {
- return this.inputLayer.depth;
- }
- get filterCount() {
- return this.settings.filterCount;
- }
- get filterWidth() {
- return this.settings.filterWidth;
- }
- get filterHeight() {
- return this.settings.filterHeight;
- }
- get filters() {
- return this.settings.filters;
- }
- set filters(filters) {
- this.settings.filters = filters;
- }
- get filterDeltas() {
- return this.settings.filterDeltas;
- }
- set filterDeltas(filterDeltas) {
- this.settings.filterDeltas = filterDeltas;
- }
- }
- class Internal {
- constructor() {
- this.predictKernel = null;
- this.compareKernel = null;
- this.praxis = null;
- }
- get width() {
- return this.settings.width;
- }
- get height() {
- return this.settings.height;
- }
- get depth() {
- return this.settings.depth;
- }
- get weights() {
- return this.settings.weights;
- }
- set weights(weights) {
- this.settings.weights = weights;
- }
- get deltas() {
- return this.settings.deltas;
- }
- set deltas(deltas) {
- this.settings.deltas = deltas;
- }
- toJSON() {
- return BaseLayer.toJSON(this);
- }
- }
- class Modifier extends BaseLayer {
- constructor(inputLayer, settings) {
- super({
- ...settings,
- width: inputLayer.width,
- height: inputLayer.height,
- depth: inputLayer.depth,
- });
- this.inputLayer = inputLayer;
- }
- validate() {
- var _a;
- super.validate();
- if (this.width !== this.inputLayer.width) {
- throw new Error(`width of ${this.width} does not match inputLayer.width of ${this.inputLayer.width}`);
- }
- if (this.height !== this.inputLayer.height) {
- throw new Error(`height of ${this.height} does not match inputLayer.height of ${this.inputLayer.height}`);
- }
- if (this.depth !== ((_a = this.inputLayer.depth) !== null && _a !== void 0 ? _a : 0)) {
- throw new Error(`depth of ${this.depth} does not match inputLayer.depth of ${this.inputLayer.depth}`);
- }
- }
- }
- class Operator extends BaseLayer {
- constructor(inputLayer1, inputLayer2, settings) {
- super(settings);
- this.inputLayer1 = inputLayer1;
- this.inputLayer2 = inputLayer2;
- this.validate();
- this.weights = zeros2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- this.setupPraxis();
- }
- }
- function compare1D(weights, targetValues) {
- return weights[this.thread.y][this.thread.x] - targetValues[this.thread.x];
- }
- function compare2D$5(weights, targetValues) {
- return (weights[this.thread.y][this.thread.x] -
- targetValues[this.thread.y][this.thread.x]);
- }
- class Target extends BaseLayer {
- constructor(settings, inputLayer) {
- super(settings);
- this.inputLayer = inputLayer;
- this.validate();
- if (this.depth) {
- throw new Error('Target layer not implemented for depth');
- }
- else if (this.height) {
- this.weights = zeros2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- this.errors = zeros2D(this.width, this.height);
- }
- else {
- this.weights = zeros$1(this.width);
- this.deltas = zeros$1(this.width);
- this.errors = zeros$1(this.width);
- }
- }
- setupKernels() {
- if (this.width === 1) {
- this.compareKernel = makeKernel(compare1D, {
- output: [this.width, this.height],
- immutable: true,
- });
- }
- else {
- this.compareKernel = makeKernel(compare2D$5, {
- output: [this.width, this.height],
- immutable: true,
- });
- }
- }
- predict() {
- // TODO: should we clone here?
- // NOTE: this looks like it shouldn't be, but the weights are immutable, and this is where they are reused.
- release(this.weights);
- this.weights = clone(this.inputLayer.weights);
- }
- compare(targetValues) {
- // this is where weights attach to deltas
- // deltas will be zero on learn, so save it in error for comparing to mse later
- release(this.deltas);
- release(this.errors);
- release(this.inputLayer.deltas);
- this.deltas = this.compareKernel(this.weights, targetValues);
- this.inputLayer.deltas = clone(this.deltas);
- this.errors = clone(this.deltas);
- }
- setupPraxis() { }
- }
- function target(settings, inputLayer) {
- return new Target(settings, inputLayer);
- }
- // eslint-disable-next-line @typescript-eslint/no-extraneous-class
- class InternalModel {
- }
- // eslint-disable-next-line @typescript-eslint/no-extraneous-class
- class EntryPoint extends BaseLayer {
- }
- // eslint-disable-next-line @typescript-eslint/no-extraneous-class
- class Model extends BaseLayer {
- learn(learningRate) {
- // TODO: do we need to release here?
- const { weights: oldWeights } = this;
- if (!this.praxis)
- throw new Error('this.praxis not defined');
- this.weights = this.praxis.run(this, learningRate);
- release(oldWeights);
- }
- }
- /* Functions for turning sparse hashes into arrays and vice versa */
- const lookup = {
- /**
- * Performs `[{a: 1}, {b: 6, c: 7}] -> {a: 0, b: 1, c: 2}`
- * @param {Object} hashes
- * @returns {Object}
- */
- toTable(hashes) {
- const hash = hashes.reduce((memo, hash) => {
- return Object.assign(memo, hash);
- }, {});
- return lookup.toHash(hash);
- },
- /**
- * Performs `[{a: 1}, {b: 6, c: 7}] -> {a: 0, b: 1, c: 2}`
- */
- toTable2D(objects2D) {
- const table = {};
- let valueIndex = 0;
- for (let i = 0; i < objects2D.length; i++) {
- const objects = objects2D[i];
- for (let j = 0; j < objects.length; j++) {
- const object = objects[j];
- for (const p in object) {
- if (object.hasOwnProperty(p) && !table.hasOwnProperty(p)) {
- table[p] = valueIndex++;
- }
- }
- }
- }
- return table;
- },
- toInputTable2D(data) {
- const table = {};
- let tableIndex = 0;
- for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
- const input = data[dataIndex].input;
- for (let i = 0; i < input.length; i++) {
- const object = input[i];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- if (!table.hasOwnProperty(p)) {
- table[p] = tableIndex++;
- }
- }
- }
- }
- return table;
- },
- toOutputTable2D(data) {
- const table = {};
- let tableIndex = 0;
- for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
- const output = data[dataIndex].output;
- for (let i = 0; i < output.length; i++) {
- const object = output[i];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- if (!table.hasOwnProperty(p)) {
- table[p] = tableIndex++;
- }
- }
- }
- }
- return table;
- },
- /**
- * performs `{a: 6, b: 7} -> {a: 0, b: 1}`
- */
- toHash(hash) {
- const lookup = {};
- let index = 0;
- const keys = Object.keys(hash);
- for (let i = 0; i < keys.length; i++) {
- lookup[keys[i]] = index++;
- }
- return lookup;
- },
- /**
- * performs `{a: 0, b: 1}, {a: 6} -> [6, 0]`
- */
- toArray(lookup, object, arrayLength) {
- const result = new Float32Array(arrayLength);
- for (const p in lookup) {
- if (!lookup.hasOwnProperty(p))
- continue;
- result[lookup[p]] = object.hasOwnProperty(p) ? object[p] : 0;
- }
- return result;
- },
- toArrayShort(lookup, object) {
- const result = [];
- for (const p in lookup) {
- if (!lookup.hasOwnProperty(p))
- continue;
- if (!object.hasOwnProperty(p))
- break;
- result[lookup[p]] = object[p];
- }
- return Float32Array.from(result);
- },
- toArrays(lookup, objects, arrayLength) {
- const result = [];
- for (let i = 0; i < objects.length; i++) {
- result.push(this.toArray(lookup, objects[i], arrayLength));
- }
- return result;
- },
- /**
- * performs `{a: 0, b: 1}, [6, 7] -> {a: 6, b: 7}`
- * @param {Object} lookup
- * @param {Array} array
- * @returns {Object}
- */
- toObject(lookup, array) {
- const object = {};
- for (const p in lookup) {
- if (!lookup.hasOwnProperty(p))
- continue;
- object[p] = array[lookup[p]];
- }
- return object;
- },
- toObjectPartial(lookup, array, offset = 0, limit = 0) {
- const object = {};
- let i = 0;
- for (const p in lookup) {
- if (!lookup.hasOwnProperty(p))
- continue;
- if (offset > 0) {
- if (i++ < offset)
- continue;
- }
- if (limit > 0) {
- if (i++ >= limit)
- continue;
- }
- object[p] = array[lookup[p] - offset];
- }
- return object;
- },
- dataShape(data) {
- const shape = [];
- let lastData;
- if (data.hasOwnProperty('input')) {
- shape.push('datum');
- lastData = data.input;
- }
- else if (Array.isArray(data)) {
- if (data[0] &&
- data[0].input) {
- shape.push('array', 'datum');
- lastData = data[0].input;
- }
- else if (Array.isArray(data[0])) {
- shape.push('array');
- lastData = data[0];
- }
- else {
- lastData = data;
- }
- }
- else {
- lastData = data;
- }
- let p;
- while (lastData) {
- p = Object.keys(lastData)[0];
- if (Array.isArray(lastData) ||
- typeof lastData.buffer === 'object') {
- shape.push('array');
- const possibleNumber = lastData[parseInt(p)];
- if (typeof possibleNumber === 'number') {
- shape.push('number');
- break;
- }
- else {
- lastData = possibleNumber;
- }
- }
- else if (typeof lastData === 'object' &&
- typeof lastData.buffer !== 'object') {
- shape.push('object');
- const possibleNumber = lastData[p];
- if (typeof possibleNumber === 'number') {
- shape.push('number');
- break;
- }
- else {
- lastData = possibleNumber;
- }
- }
- else {
- throw new Error('unhandled signature');
- }
- }
- return shape;
- },
- addKeys(value, table) {
- if (Array.isArray(value))
- return table;
- let i = Object.keys(table).length;
- for (const p in value) {
- if (!value.hasOwnProperty(p))
- continue;
- if (table.hasOwnProperty(p))
- continue;
- table[p] = i++;
- }
- return table;
- },
- };
- class BasePraxis {
- constructor(layerTemplate, settings = {}) {
- this.layerTemplate = layerTemplate;
- this.settings = { ...settings };
- this.kernel = null;
- }
- get width() {
- return this.layerTemplate.width;
- }
- get height() {
- return this.layerTemplate.height;
- }
- get depth() {
- return this.layerTemplate.depth;
- }
- setupKernels() { }
- reuseKernels(praxis) {
- if (praxis.width !== this.width) {
- throw new Error(`${this.constructor.name} kernel width mismatch ${praxis.width} is not ${this.width}`);
- }
- if (praxis.height !== this.height) {
- throw new Error(`${this.constructor.name} kernel width mismatch ${praxis.height} is not ${this.height}`);
- }
- if (praxis.hasOwnProperty('kernel')) {
- this.kernel = praxis.kernel;
- }
- }
- toJSON() {
- return { ...this.settings };
- }
- }
- function update$2(weights, deltas) {
- return (weights[this.thread.y][this.thread.x] +
- this.constants.learningRate * deltas[this.thread.y][this.thread.x]);
- }
- const defaultSettings$1 = {
- learningRate: 0.3,
- };
- class ArthurDeviationBiases extends BasePraxis {
- constructor(layer, settings) {
- super(layer);
- this.settings = { ...defaultSettings$1, ...settings };
- this.kernel = null;
- }
- run(layer) {
- return this.kernel(layer.weights, layer.deltas);
- }
- setupKernels() {
- this.kernel = makeKernel(update$2, {
- output: [this.width, this.height],
- constants: {
- learningRate: this.settings.learningRate,
- },
- });
- }
- }
- function arthurDeviationBiases(layer, settings) {
- return new ArthurDeviationBiases(layer, settings);
- }
- function updateChange(value) {
- return value;
- }
- function update$1(changes, weights, incomingWeights, inputDeltas) {
- const lastChange = changes[this.thread.y][this.thread.x];
- const inputDelta = inputDeltas[this.thread.y][0];
- const weight = weights[this.thread.y][this.thread.x];
- const incoming = incomingWeights[this.thread.x][0];
- const change = this.constants.learningRate * inputDelta * incoming +
- this.constants.momentum * lastChange;
- return weight + change;
- }
- const defaultSettings = {
- learningRate: 0.3,
- momentum: 0.1,
- weightsLayer: null,
- incomingLayer: null,
- deltaLayer: null,
- };
- class ArthurDeviationWeights extends BasePraxis {
- constructor(layer, settings) {
- super(layer);
- this.kernelMap = null;
- this.settings = { ...defaultSettings, ...settings };
- this.changes = zeros2D(layer.width, layer.height);
- }
- get learningRate() {
- return this.settings.learningRate;
- }
- get momentum() {
- return this.settings.momentum;
- }
- get weightsLayer() {
- return this.settings.weightsLayer;
- }
- set weightsLayer(layer) {
- this.settings.weightsLayer = layer;
- }
- get deltaLayer() {
- return this.settings.deltaLayer;
- }
- set deltaLayer(layer) {
- this.settings.deltaLayer = layer;
- }
- get incomingLayer() {
- return this.settings.incomingLayer;
- }
- set incomingLayer(layer) {
- this.settings.incomingLayer = layer;
- }
- run() {
- const output = this.kernelMap(this.changes, this.weightsLayer.weights, this.incomingLayer.weights, this.deltaLayer.deltas);
- this.changes = output.changes;
- return output.result;
- }
- setupKernels() {
- this.kernelMap = makeKernelMap({
- changes: updateChange,
- }, update$1, {
- output: [this.width, this.height],
- constants: {
- learningRate: this.learningRate,
- momentum: this.momentum,
- },
- });
- }
- }
- function arthurDeviationWeights(layer, settings) {
- return new ArthurDeviationWeights(layer, settings);
- }
- function getMomentum(delta, decay, previousMomentum) {
- return previousMomentum * decay + (1 - decay) * delta * delta;
- }
- function clipByValue(value, max, min) {
- if (value > max) {
- return max;
- }
- if (value < min) {
- return min;
- }
- return value;
- }
- /**
- * @description Momentum Root Mean Square Propagation Function
- */
- function update(weights, deltas, previousMomenta) {
- const delta = deltas[this.thread.y][this.thread.x];
- const clippedDelta = clipByValue(delta, this.constants.clipValue, -this.constants.clipValue);
- const weight = weights[this.thread.y][this.thread.x];
- const previousMomentum = previousMomenta[this.thread.y][this.thread.x];
- const momentum = getMomentum(delta, this.constants.decayRate, previousMomentum);
- return (weight +
- (-this.constants.learningRate * clippedDelta) /
- Math.sqrt(momentum + this.constants.smoothEps) -
- this.constants.regularizationStrength * weight);
- }
- const defaults$8 = {
- decayRate: 0.999,
- regularizationStrength: 0.000001,
- learningRate: 0.01,
- smoothEps: 1e-8,
- clipValue: 5,
- };
- class MomentumRootMeanSquaredPropagation extends BasePraxis {
- constructor(layerTemplate, settings = {}) {
- super(layerTemplate);
- this.kernelMap = null;
- this.settings = { ...defaults$8, ...settings };
- this.momenta = zeros2D(layerTemplate.width, layerTemplate.height);
- }
- get clipValue() {
- return this.settings.clipValue;
- }
- get decayRate() {
- return this.settings.decayRate;
- }
- get learningRate() {
- return this.settings.learningRate;
- }
- get regularizationStrength() {
- return this.settings.regularizationStrength;
- }
- get smoothEps() {
- return this.settings.smoothEps;
- }
- run(layer) {
- const { momenta, result } = this.kernelMap(layer.weights, layer.deltas, this.momenta);
- release(this.momenta);
- this.momenta = momenta;
- return result;
- }
- setupKernels() {
- this.kernelMap = makeKernelMap({
- momenta: getMomentum,
- }, update, {
- output: [this.width, this.height],
- constants: {
- clipValue: this.clipValue,
- decayRate: this.decayRate,
- learningRate: this.learningRate,
- regularizationStrength: this.regularizationStrength,
- smoothEps: this.smoothEps,
- },
- functions: [clipByValue],
- immutable: true,
- });
- }
- }
- function momentumRootMeanSquaredPropagation(layer, settings) {
- return new MomentumRootMeanSquaredPropagation(layer, settings);
- }
- /**
- * @description Mathematician friendly name of MomentumRootMeanSquaredPropagation class. For those that are not mere mortals
- */
- const MRmsProp = MomentumRootMeanSquaredPropagation;
- const mRmsProp = momentumRootMeanSquaredPropagation;
- var index = /*#__PURE__*/Object.freeze({
- __proto__: null,
- ArthurDeviationBiases: ArthurDeviationBiases,
- arthurDeviationBiases: arthurDeviationBiases,
- ArthurDeviationWeights: ArthurDeviationWeights,
- arthurDeviationWeights: arthurDeviationWeights,
- MomentumRootMeanSquaredPropagation: MomentumRootMeanSquaredPropagation,
- momentumRootMeanSquaredPropagation: momentumRootMeanSquaredPropagation,
- MRmsProp: MRmsProp,
- mRmsProp: mRmsProp
- });
- function traverseLayersFrom(layer, cb) {
- if (layer.hasOwnProperty('inputLayer')) {
- traverseLayersFrom(layer.inputLayer, cb);
- }
- else {
- if (layer.hasOwnProperty('inputLayer1')) {
- traverseLayersFrom(layer.inputLayer1, cb);
- }
- if (layer.hasOwnProperty('inputLayer2')) {
- traverseLayersFrom(layer.inputLayer2, cb);
- }
- }
- cb(layer);
- }
- function flattenLayers(layers) {
- const result = layers.slice(0);
- for (let i = 0; i < result.length; i++) {
- let offset = 0;
- traverseLayersFrom(result[i], (layer) => {
- if (!result.includes(layer)) {
- result.splice(i + offset, 0, layer);
- offset++;
- }
- });
- }
- return result;
- }
- function checkSameSize(layer1, layer2) {
- if (layer1.width !== layer2.width) {
- throw new Error(`Layer width mismatch of ${layer1.width} and ${layer2.width}`);
- }
- if (layer1.height !== layer2.height) {
- throw new Error(`Layer height mismatch of ${layer1.height} and ${layer2.height}`);
- }
- }
- function predict$8(inputWeights1, inputWeights2) {
- return (inputWeights1[this.thread.y][this.thread.x] +
- inputWeights2[this.thread.y][this.thread.x]);
- }
- class Add extends Operator {
- get width() {
- return this.inputLayer1.width;
- }
- get height() {
- return this.inputLayer1.height;
- }
- get depth() {
- return this.inputLayer1.depth;
- }
- validate() {
- super.validate();
- checkSameSize(this.inputLayer1, this.inputLayer2);
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict$8, {
- output: [this.width, this.height],
- immutable: true,
- });
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer1.weights, this.inputLayer2.weights);
- }
- compare() {
- // TODO: Do we need release and clone here?
- release(this.inputLayer1.deltas);
- release(this.inputLayer2.deltas);
- this.inputLayer1.deltas = clone(this.deltas);
- this.inputLayer2.deltas = clone(this.deltas);
- }
- }
- function add$1(inputLayer1, inputLayer2, settings) {
- return new Add(inputLayer1, inputLayer2, settings);
- }
- function randomWeight() {
- return Math.random() * 0.4 - 0.2;
- }
- /**
- * Returns a random float between given min and max bounds (inclusive)
- * @param min Minimum value of the ranfom float
- * @param max Maximum value of the random float
- */
- function randomFloat(min, max) {
- return Math.random() * (max - min) + min;
- }
- /**
- * Complicated math. All you need to know is that it returns a random number.
- * More info: https://en.wikipedia.org/wiki/Normal_distribution
- */
- function gaussRandom() {
- if (gaussRandom.returnV) {
- gaussRandom.returnV = false;
- return gaussRandom.vVal;
- }
- const u = 2 * Math.random() - 1;
- const v = 2 * Math.random() - 1;
- const r = u * u + v * v;
- if (r === 0 || r > 1) {
- return gaussRandom();
- }
- const c = Math.sqrt((-2 * Math.log(r)) / r);
- gaussRandom.vVal = v * c; // cache this
- gaussRandom.returnV = true;
- return u * c;
- }
- /**
- * Returns a random integer between given min and max bounds
- * @param min Minimum value of the random integer
- * @param max Maximum value of the random integer
- */
- function randomInteger(min, max) {
- return Math.floor(Math.random() * (max - min) + min);
- }
- /**
- * If you know what this is: https://en.wikipedia.org/wiki/Normal_distribution
- * @param mu
- * @param std
- */
- function randomN(mu, std) {
- return mu + gaussRandom() * std;
- }
- gaussRandom.returnV = false;
- gaussRandom.vVal = 0;
- var random$1 = /*#__PURE__*/Object.freeze({
- __proto__: null,
- randomFloat: randomFloat,
- gaussRandom: gaussRandom,
- randomInteger: randomInteger,
- randomN: randomN
- });
- /**
- * Returns an array of given size, full of randomness
- */
- function randos(size, std = null) {
- const array = new Float32Array(size);
- if (std === null) {
- for (let i = 0; i < size; i++) {
- array[i] = randomWeight();
- }
- }
- else {
- for (let i = 0; i < size; i++) {
- array[i] = randomFloat(-std, std);
- }
- }
- return array;
- }
- /**
- * Returns a 2D matrix of given size, full of randomness
- */
- function randos2D(width, height, std) {
- const result = new Array(height);
- for (let y = 0; y < height; y++) {
- result[y] = randos(width, std);
- }
- return result;
- }
- /**
- * Returns a 3D tensor of given size, full of randomness
- */
- function randos3D(width, height, depth, std) {
- const result = new Array(depth);
- for (let z = 0; z < depth; z++) {
- result[z] = randos2D(width, height, std);
- }
- return result;
- }
- const defaults$7 = {
- ...baseLayerDefaultSettings,
- std: null,
- };
- class Random extends Model {
- constructor(settings) {
- super();
- this.settings = { ...defaults$7, ...settings };
- this.setupPraxis();
- this.validate();
- if (!this.weights) {
- this.weights = randos2D(this.width, this.height, settings.std);
- }
- if (!this.deltas) {
- this.deltas = zeros2D(this.width, this.height);
- }
- }
- predict() { }
- compare() { }
- }
- function random(settings) {
- return new Random(settings);
- }
- function predict$7(weights1, weights2) {
- let sum = 0;
- for (let i = 0; i < this.constants.size; i++) {
- sum += weights1[this.thread.y][i] * weights2[i][this.thread.x];
- }
- return sum;
- }
- function compareFromX(deltas, inputDeltas, inputWeights) {
- let sum = inputDeltas[this.thread.y][this.thread.x];
- for (let i = 0; i < this.constants.size; i++) {
- sum += deltas[this.thread.y][i] * inputWeights[this.thread.x][i];
- }
- return sum;
- }
- function compareFromY(deltas, inputDeltas, inputWeights) {
- let sum = inputDeltas[this.thread.y][this.thread.x];
- for (let i = 0; i < this.constants.size; i++) {
- sum += deltas[i][this.thread.x] * inputWeights[i][this.thread.y];
- }
- return sum;
- }
- class Multiply extends Operator {
- constructor() {
- super(...arguments);
- this.compareKernel1 = null;
- this.compareKernel2 = null;
- }
- get width() {
- return this.inputLayer2.width;
- }
- set width(width) {
- throw new Error('Cannot set width on Multiply');
- }
- get height() {
- return this.inputLayer1.height;
- }
- set height(height) {
- throw new Error('Cannot set height on Multiply');
- }
- get depth() {
- return this.inputLayer1.depth;
- }
- set depth(depth) {
- throw new Error('Cannot set depth on Multiply');
- }
- validate() {
- super.validate();
- if (this.inputLayer1.width !== this.inputLayer2.height) {
- throw new Error(`Layer width mismatch of ${this.inputLayer1.width} and ${this.inputLayer2.height}`);
- }
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict$7, {
- output: [this.width, this.height],
- constants: {
- size: this.inputLayer2.height,
- },
- immutable: true,
- });
- this.compareKernel1 = makeKernel(compareFromX, {
- output: [this.inputLayer1.width, this.inputLayer1.height],
- constants: {
- size: this.inputLayer2.width,
- },
- immutable: true,
- });
- this.compareKernel2 = makeKernel(compareFromY, {
- output: [this.inputLayer2.width, this.inputLayer2.height],
- constants: {
- size: this.inputLayer1.height,
- },
- immutable: true,
- });
- }
- reuseKernels(layer) {
- super.reuseKernels(layer);
- this.compareKernel1 = layer.compareKernel1;
- this.compareKernel2 = layer.compareKernel2;
- }
- predict() {
- release(this.weights);
- if (!this.predictKernel)
- throw new Error('this.predictKernel is not set');
- this.weights = this.predictKernel(this.inputLayer1.weights, this.inputLayer2.weights);
- }
- compare() {
- if (!this.compareKernel1)
- throw new Error('this.compareKernel1 not set');
- if (!this.compareKernel2)
- throw new Error('this.compareKernel2 not set');
- const inputLayer1Deltas = this.inputLayer1.deltas;
- const inputLayer2Deltas = this.inputLayer2.deltas;
- const newDeltas1 = this.compareKernel1(this.deltas, this.inputLayer1.deltas, this.inputLayer2.weights);
- const newDeltas2 = this.compareKernel2(this.deltas, this.inputLayer2.deltas, this.inputLayer1.weights);
- this.inputLayer2.deltas = newDeltas2;
- this.inputLayer1.deltas = newDeltas1;
- release(inputLayer1Deltas);
- release(inputLayer2Deltas);
- }
- setupPraxis() { }
- toJSON() {
- return {
- ...super.toJSON(),
- width: this.width,
- height: this.height,
- };
- }
- }
- function multiply$1(inputLayer1, inputLayer2, settings) {
- return new Multiply(inputLayer1, inputLayer2, settings);
- }
- function predict2D$4(inputs) {
- return 1 / (1 + Math.exp(-inputs[this.thread.y][this.thread.x]));
- }
- function predict3D$5(inputs) {
- return (1 / (1 + Math.exp(-inputs[this.thread.z][this.thread.y][this.thread.x])));
- }
- function compare2D$4(weights, deltas) {
- const weight = weights[this.thread.y][this.thread.x];
- const delta = deltas[this.thread.y][this.thread.x];
- return weight * (1 - weight) * delta;
- }
- function compare3D$4(weights, deltas) {
- const weight = weights[this.thread.z][this.thread.y][this.thread.x];
- const delta = deltas[this.thread.z][this.thread.y][this.thread.x];
- return weight * (1 - weight) * delta;
- }
- class Sigmoid extends Activation {
- setupKernels() {
- if (this.depth > 0) {
- this.predictKernel = makeKernel(predict3D$5, {
- output: [this.width, this.height, this.depth],
- functions: [activate$2],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare3D$4, {
- output: [this.width, this.height, this.depth],
- functions: [measure$2],
- immutable: true,
- });
- }
- else {
- this.predictKernel = makeKernel(predict2D$4, {
- output: [this.width, this.height],
- functions: [activate$2],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare2D$4, {
- output: [this.width, this.height],
- functions: [measure$2],
- immutable: true,
- });
- }
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- compare() {
- release(this.inputLayer.deltas);
- this.inputLayer.deltas = this.compareKernel(this.weights, this.deltas);
- }
- learn(learningRate) { }
- }
- function sigmoid$1(inputLayer, settings) {
- return new Sigmoid(inputLayer, settings);
- }
- function arthurFeedForward(settings, inputLayer) {
- const { height } = settings;
- function initWeightsPraxis(layerTemplate, settings) {
- const praxis = arthurDeviationWeights(layerTemplate, settings);
- praxis.setupKernels();
- return praxis;
- }
- function initBiasesPraxis(layerTemplate, settings) {
- const praxis = arthurDeviationBiases(layerTemplate, settings);
- praxis.setupKernels();
- return praxis;
- }
- const weightsLayer = random({
- id: 'weights',
- height,
- width: inputLayer.height,
- initPraxis: initWeightsPraxis,
- });
- const biasesLayer = random({
- id: 'biases',
- height,
- initPraxis: initBiasesPraxis,
- });
- const multiplyLayer = multiply$1(weightsLayer, inputLayer);
- const addLayer = add$1(multiplyLayer, biasesLayer);
- const sigmoidLayer = sigmoid$1(addLayer);
- const weightsPraxis = weightsLayer.praxis;
- weightsPraxis.weightsLayer = weightsLayer;
- weightsPraxis.incomingLayer = inputLayer;
- weightsPraxis.deltaLayer = sigmoidLayer;
- return sigmoidLayer;
- }
- function getStride(settings, defaults) {
- if (typeof settings.stride === 'number') {
- return { strideX: settings.stride, strideY: settings.stride };
- }
- else {
- let strideX = defaults.stride;
- let strideY = defaults.stride;
- if (typeof settings.strideX === 'number') {
- strideX = settings.strideX;
- }
- if (typeof settings.strideY === 'number') {
- strideY = settings.strideY;
- }
- return { strideX, strideY };
- }
- }
- function getPadding(settings, defaults) {
- if (typeof settings.padding === 'number') {
- return { paddingX: settings.padding, paddingY: settings.padding };
- }
- else {
- let paddingX = defaults.padding;
- let paddingY = defaults.padding;
- if (typeof settings.paddingX === 'number') {
- paddingX = settings.paddingX;
- }
- if (typeof settings.paddingY === 'number') {
- paddingY = settings.paddingY;
- }
- return { paddingX, paddingY };
- }
- }
- /**
- * Returns an array of a given size with each element filled with a single value
- */
- function values(size, value) {
- return new Float32Array(size).fill(value);
- }
- function predict$6(inputs, filters, biases) {
- const startFilterX = this.constants.paddingX - this.thread.x * this.constants.strideX;
- const startInputX = this.thread.x * this.constants.strideX - this.constants.paddingX;
- const endFilterX = Math.min(this.constants.filterWidth, startFilterX + this.constants.inputWidth);
- const startFilterY = this.constants.paddingY - this.thread.y * this.constants.strideY;
- const startInputY = this.thread.y * this.constants.strideY - this.constants.paddingY;
- const endFilterY = Math.min(this.constants.filterHeight, startFilterY + this.constants.inputHeight);
- let sum = 0;
- for (let z = 0; z < this.constants.inputDepth; z++) {
- for (let filterY = Math.max(0, startFilterY), inputY = Math.max(0, startInputY); filterY < endFilterY; filterY++, inputY++) {
- for (let filterX = Math.max(0, startFilterX), inputX = Math.max(0, startInputX); filterX < endFilterX; filterX++, inputX++) {
- sum += filters[z][filterY][filterX] * inputs[z][inputY][inputX];
- }
- }
- }
- return sum + biases[this.thread.z];
- }
- function compareFilterDeltas$1(filterDeltas, inputs, deltas) {
- const startDeltaX = Math.max(0, Math.ceil((this.constants.paddingX - this.thread.x) / this.constants.strideX));
- const startInputX = startDeltaX * this.constants.strideX +
- this.thread.x -
- this.constants.paddingX;
- const endDeltaX = Math.min(this.constants.deltaWidth, Math.floor((this.constants.inputWidth -
- 1 -
- this.thread.x +
- this.constants.paddingX) /
- this.constants.strideX) + 1);
- const startDeltaY = Math.max(0, Math.ceil((this.constants.paddingY - this.thread.y) / this.constants.strideY));
- const startInputY = startDeltaY * this.constants.strideY +
- this.thread.y -
- this.constants.paddingY;
- const endDeltaY = Math.min(this.constants.deltaHeight, Math.floor((this.constants.inputHeight -
- 1 -
- this.thread.y +
- this.constants.paddingY) /
- this.constants.strideY) + 1);
- let sum = filterDeltas[this.thread.z][this.thread.y][this.thread.x];
- for (let deltaY = startDeltaY, inputY = startInputY; deltaY < endDeltaY; deltaY++, inputY += this.constants.strideY) {
- for (let deltaX = startDeltaX, inputX = startInputX; deltaX < endDeltaX; deltaX++, inputX += this.constants.strideX) {
- sum +=
- inputs[this.thread.z][inputY][inputX] *
- deltas[this.constants.deltaZ][deltaY][deltaX];
- }
- }
- return sum;
- }
- function compareInputDeltas$1(inputDeltas, filters, deltas) {
- const x = this.thread.x + this.constants.paddingX;
- const startDeltaX = x < this.constants.filterWidth
- ? 0
- : Math.floor((x - this.constants.filterWidth + this.constants.strideX) /
- this.constants.strideX);
- const startFilterX = x - startDeltaX * this.constants.strideX;
- const endDeltaX = Math.min(startDeltaX + Math.floor(startFilterX / this.constants.strideX) + 1, this.constants.deltaWidth);
- const y = this.thread.y + this.constants.paddingY;
- const startDeltaY = y < this.constants.filterHeight
- ? 0
- : Math.floor((y - this.constants.filterHeight + this.constants.strideY) /
- this.constants.strideY);
- const startFilterY = y - startDeltaY * this.constants.strideY;
- const endDeltaY = Math.min(startDeltaY + Math.floor(startFilterY / this.constants.strideY) + 1, this.constants.deltaHeight);
- let sum = inputDeltas[this.thread.z][this.thread.y][this.thread.x];
- let deltaY = startDeltaY;
- for (let filterY = startFilterY; deltaY < endDeltaY; filterY -= this.constants.strideY, deltaY++) {
- let deltaX = startDeltaX;
- for (let filterX = startFilterX; deltaX < endDeltaX; filterX -= this.constants.strideX, deltaX++) {
- sum +=
- filters[this.thread.z][filterY][filterX] *
- deltas[this.constants.deltaZ][deltaY][deltaX];
- }
- }
- return sum;
- }
- function compareBiases$1(biasDeltas, deltas) {
- let sum = 0;
- for (let y = 0; y < this.constants.deltaHeight; y++) {
- for (let x = 0; x < this.constants.deltaWidth; x++) {
- sum += deltas[this.thread.z][y][x];
- }
- }
- return biasDeltas[this.thread.z][this.thread.y][this.thread.x] + sum;
- }
- const defaults$6 = {
- stride: 0,
- padding: 0,
- bias: 0.1,
- filterCount: 1,
- filterWidth: 0,
- filterHeight: 0,
- };
- class Convolution extends Filter {
- constructor(settings, inputLayer) {
- var _a, _b, _c;
- super(settings, inputLayer);
- this.compareFilterDeltasKernel = null;
- this.compareInputDeltasKernel = null;
- this.compareBiasesKernel = null;
- this.settings = {
- ...defaults$6,
- ...settings,
- ...getPadding(settings, defaults$6),
- ...getStride(settings, defaults$6),
- };
- this.weights = (_a = settings.weights) !== null && _a !== void 0 ? _a : randos3D(this.width, this.height, this.depth);
- this.deltas = zeros3D(this.width, this.height, this.depth);
- this.biases = values(this.depth, this.bias);
- this.biasDeltas = (_b = settings.biasDeltas) !== null && _b !== void 0 ? _b : randos(this.depth);
- this.filters = (_c = settings.filters) !== null && _c !== void 0 ? _c : randos3D(this.filterWidth, this.filterHeight, this.filterCount);
- this.filterDeltas = zeros3D(this.filterWidth, this.filterHeight, this.filterCount);
- this.validate();
- }
- get strideX() {
- return this.settings.strideX;
- }
- get strideY() {
- return this.settings.strideY;
- }
- get paddingX() {
- return this.settings.paddingX;
- }
- get paddingY() {
- return this.settings.paddingX;
- }
- get width() {
- return Math.floor((this.inputLayer.width + this.paddingX * 2 - this.filterWidth) /
- this.strideX +
- 1);
- }
- get height() {
- return Math.floor((this.inputLayer.height + this.paddingY * 2 - this.filterHeight) /
- this.strideY +
- 1);
- }
- get bias() {
- return this.settings.bias;
- }
- get depth() {
- return this.filterCount;
- }
- get biases() {
- return this.settings.biases;
- }
- set biases(biases) {
- this.settings.biases = biases;
- }
- get biasDeltas() {
- return this.settings.biasDeltas;
- }
- set biasDeltas(weights) {
- this.settings.biasDeltas = weights;
- }
- get filters() {
- return this.settings.filters;
- }
- set filters(filters) {
- this.settings.filters = filters;
- }
- get filterDeltas() {
- return this.settings.filterDeltas;
- }
- set filterDeltas(filterDeltas) {
- this.settings.filterDeltas = filterDeltas;
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict$6, {
- constants: {
- inputWidth: this.inputLayer.width,
- inputHeight: this.inputLayer.height,
- inputDepth: this.inputLayer.depth,
- strideX: this.strideX,
- strideY: this.strideY,
- paddingX: this.paddingX,
- paddingY: this.paddingY,
- filterWidth: this.filterWidth,
- filterHeight: this.filterHeight,
- },
- output: [this.width, this.height, this.depth],
- immutable: true,
- });
- this.compareFilterDeltasKernel = makeKernel(compareFilterDeltas$1, {
- constants: {
- deltasWidth: this.width,
- deltasHeight: this.height,
- deltasDepth: this.depth,
- inputWidth: this.inputLayer.width,
- inputHeight: this.inputLayer.height,
- inputDepth: this.inputLayer.depth,
- strideX: this.strideX,
- strideY: this.strideY,
- paddingX: this.paddingX,
- paddingY: this.paddingY,
- filterWidth: this.filterWidth,
- filterHeight: this.filterHeight,
- },
- output: [this.width, this.height, this.depth],
- immutable: true,
- });
- this.compareInputDeltasKernel = makeKernel(compareInputDeltas$1, {
- constants: {
- filterCount: this.filterCount,
- },
- output: [
- this.inputLayer.width,
- this.inputLayer.height,
- this.inputLayer.depth,
- ],
- immutable: true,
- });
- this.compareBiasesKernel = makeKernel(compareBiases$1, {
- output: [1, 1, this.depth],
- constants: {
- deltaWidth: this.width,
- deltaHeight: this.height,
- },
- immutable: true,
- });
- }
- predict() {
- this.weights = this.predictKernel(this.inputLayer.weights, this.filters, this.biases);
- }
- compare() {
- const { filterDeltas, biasDeltas } = this;
- this.filterDeltas = this.compareFilterDeltasKernel(filterDeltas, this.inputLayer.weights, this.deltas);
- release(filterDeltas);
- this.biasDeltas = this.compareBiasesKernel(biasDeltas, this.deltas);
- release(biasDeltas);
- release(this.deltas);
- this.deltas = this.compareInputDeltasKernel(this.filters, this.inputLayer.deltas);
- release(this.inputLayer.deltas);
- // TODO: do we need to clone here?
- this.inputLayer.deltas = clone(this.deltas);
- }
- learn(learningRate) {
- // TODO: handle filters
- // TODO: do we need to release here?
- const { weights: oldWeights } = this;
- this.weights = this.praxis.run(this, learningRate);
- release(oldWeights);
- }
- }
- function convolution(settings, inputLayer) {
- return new Convolution(settings, inputLayer);
- }
- function setDropout(dropout) {
- return dropout;
- }
- function trainingPredict(inputs) {
- if (setDropout(Math.random()) < this.constants.probability) {
- return 0;
- }
- return inputs[this.thread.y][this.thread.x];
- }
- function predict$5(inputs) {
- return inputs[this.thread.y][this.thread.x] * this.constants.probability;
- }
- function compare$3(dropouts, deltas) {
- if (dropouts[this.thread.y][this.thread.x] === 0) {
- return 0;
- }
- return deltas[this.thread.y][this.thread.x];
- }
- const dropoutDefaults = {
- ...baseLayerDefaultSettings,
- probability: 0.5,
- };
- class Dropout extends Filter {
- constructor(inputLayer, settings) {
- super(settings, inputLayer);
- this.predictKernelMap = null;
- this.settings = { ...dropoutDefaults, ...settings };
- this.dropouts = null;
- this.validate();
- }
- setupKernels(isTraining) {
- const output = [this.width, this.height];
- if (isTraining) {
- this.predictKernelMap = makeKernelMap({ dropouts: setDropout }, trainingPredict, {
- output,
- immutable: true,
- });
- this.compareKernel = makeKernel(compare$3, { output, immutable: true });
- }
- else {
- this.predictKernelMap = makeKernelMap({}, predict$5, { output, immutable: true });
- }
- }
- predict() {
- release(this.weights);
- if (this.dropouts) {
- release(this.dropouts);
- }
- const { result, dropouts } = this
- .predictKernelMap(this.inputLayer.weights);
- this.weights = result;
- this.dropouts = dropouts;
- }
- compare() {
- release(this.deltas);
- this.deltas = this.compareKernel(this.dropouts, this.inputLayer.deltas);
- }
- }
- function dropout(inputLayer, settings) {
- return new Dropout(inputLayer, settings);
- }
- function feedForward(settings, input) {
- const { height, praxisOpts = null } = settings;
- const weights = random({
- id: 'weights',
- height,
- width: input.height,
- praxisOpts,
- });
- const biases = random({ id: 'biases', height, praxisOpts });
- return sigmoid$1(add$1(multiply$1(weights, input, { praxisOpts }), biases, { praxisOpts }), { praxisOpts });
- }
- function predict$4(inputs, filters, biases) {
- let output = 0;
- let i = 0;
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- output += inputs[y][x] * filters[this.thread.x][i];
- i++;
- }
- }
- return output + biases[this.thread.x];
- }
- function predict3D$4(inputs, filters, biases) {
- let output = 0;
- let i = 0;
- for (let z = 0; z < this.constants.inputDepth; z++) {
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- output += inputs[z][y][x] * filters[this.thread.x][i];
- i++;
- }
- }
- }
- return output + biases[this.thread.x];
- }
- function compareInputDeltas(inputDeltas, deltas, filters) {
- let sum = 0;
- const filterX = this.thread.x + this.thread.y * this.output.x;
- for (let filterY = 0; filterY < this.constants.filterCount; filterY++) {
- sum += filters[filterY][filterX] * deltas[0][filterY];
- }
- return sum + inputDeltas[this.thread.y][this.thread.x];
- }
- function compareInputDeltas3D(inputDeltas, deltas, filters) {
- let sum = 0;
- const filterX = this.thread.x + this.thread.y * this.output.x;
- for (let filterY = 0; filterY < this.constants.filterCount; filterY++) {
- sum += filters[filterY][filterX] * deltas[0][filterY];
- }
- return sum + inputDeltas[this.thread.z][this.thread.y][this.thread.x];
- }
- function compareBiases(biases, deltas) {
- return biases[this.thread.x] + deltas[this.thread.y][this.thread.x];
- }
- function compareFilterDeltas(filterDeltas, inputWeights, deltas) {
- return (filterDeltas[this.thread.y][this.thread.x] +
- inputWeights[this.thread.y][this.thread.x] *
- deltas[this.constants.deltaY][this.constants.deltaX]);
- }
- function compareFilterDeltas3D(filterDeltas, inputWeights, deltas) {
- const inputZ = Math.floor(this.thread.x / (this.constants.inputWidth * this.constants.inputHeight));
- const inputY = Math.floor((this.thread.x -
- inputZ * this.constants.inputWidth * this.constants.inputHeight) /
- this.constants.inputWidth);
- const inputX = this.thread.x -
- this.constants.inputWidth * (inputY + this.constants.inputHeight * inputZ);
- return (filterDeltas[this.thread.y][this.thread.x] +
- inputWeights[inputZ][inputY][inputX] * deltas[0][this.thread.y]);
- }
- class FullyConnected extends Filter {
- constructor(settings, inputLayer) {
- super(settings, inputLayer);
- this.compareFilterDeltasKernel = null;
- this.compareInputDeltasKernel = null;
- this.compareBiasesKernel = null;
- this.settings = { ...settings };
- this.validate();
- const connectionCount = inputLayer.width * inputLayer.height * inputLayer.depth;
- this.biases = values(this.height, this.bias);
- this.biasDeltas = zeros$1(this.height);
- this.filters = randos2D(connectionCount, this.height);
- this.filterDeltas = zeros2D(connectionCount, this.height);
- if (this.depth > 0) {
- this.weights = randos3D(this.width, this.height, this.depth);
- this.deltas = zeros3D(this.width, this.height, this.depth);
- }
- else if (this.height > 0) {
- this.weights = randos2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- }
- }
- get bias() {
- return this.settings.bias;
- }
- get biases() {
- return this.settings.biases;
- }
- set biases(biases) {
- this.settings.biases = biases;
- }
- get biasDeltas() {
- return this.settings.biases;
- }
- set biasDeltas(biasDeltas) {
- this.settings.biasDeltas = biasDeltas;
- }
- validate() {
- super.validate();
- if (this.depth > 0)
- throw new Error('depth not supported');
- }
- setupKernels() {
- const { inputLayer } = this;
- const connectionCount = inputLayer.width * inputLayer.height * inputLayer.depth;
- if (inputLayer.depth > 0) {
- this.predictKernel = makeKernel(predict3D$4, {
- output: [this.width, this.height],
- constants: {
- inputHeight: inputLayer.height,
- inputWidth: inputLayer.width,
- inputDepth: inputLayer.depth,
- },
- });
- this.compareFilterDeltasKernel = makeKernel(compareFilterDeltas3D, {
- output: [connectionCount, this.height],
- constants: {
- inputWidth: inputLayer.width,
- inputHeight: inputLayer.height,
- },
- immutable: true,
- });
- this.compareInputDeltasKernel = makeKernel(compareInputDeltas3D, {
- output: [inputLayer.width, inputLayer.height, inputLayer.depth],
- constants: {
- filterCount: this.height,
- },
- immutable: true,
- });
- }
- else {
- this.predictKernel = makeKernel(predict$4, {
- output: [this.width, this.height],
- constants: {
- inputHeight: inputLayer.height,
- inputWidth: inputLayer.width,
- },
- });
- this.compareFilterDeltasKernel = makeKernel(compareFilterDeltas, {
- output: [connectionCount, this.height],
- constants: {
- inputWidth: inputLayer.width,
- },
- });
- this.compareInputDeltasKernel = makeKernel(compareInputDeltas, {
- output: [inputLayer.width, inputLayer.height],
- constants: {
- filterCount: this.height,
- },
- });
- }
- this.compareBiasesKernel = makeKernel(compareBiases, {
- output: [this.width, this.height],
- });
- }
- predict() {
- this.weights = this.predictKernel(this.inputLayer.weights, this.filters, this.biases);
- }
- compare() {
- const inputLayerDeltas = this.inputLayer.deltas;
- this.inputLayer.deltas = this
- .compareInputDeltasKernel(inputLayerDeltas, this.deltas, this.filters);
- release(inputLayerDeltas);
- const { biasDeltas, filterDeltas } = this;
- // TODO: handle biasDeltas learn
- this.biasDeltas = this.compareBiasesKernel(this.biases, this.deltas);
- // TODO: handle filterDeltas learn
- this.filterDeltas = this.compareFilterDeltasKernel(filterDeltas, this.inputLayer.weights, this.deltas);
- release(biasDeltas);
- release(filterDeltas);
- }
- }
- function fullyConnected(settings, inputLayer) {
- return new FullyConnected(settings, inputLayer);
- }
- function predict$3(weights) {
- return -weights[this.thread.y][this.thread.x];
- }
- class Negative extends Modifier {
- constructor(inputLayer, settings) {
- super(inputLayer, settings);
- this.validate();
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict$3, {
- output: [this.width, this.height],
- });
- }
- predict() {
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- }
- function negative(inputLayer, settings) {
- return new Negative(inputLayer, settings);
- }
- function predict$2(inputLayerWeights1, inputLayerWeights2) {
- return (inputLayerWeights1[this.thread.y][this.thread.x] *
- inputLayerWeights2[this.thread.y][this.thread.x]);
- }
- function compare$2(weights, deltas) {
- return (weights[this.thread.y][this.thread.x] * deltas[this.thread.y][this.thread.x]);
- }
- class MultiplyElement extends Operator {
- get width() {
- return this.inputLayer1.width;
- }
- get height() {
- return this.inputLayer1.height;
- }
- get depth() {
- return this.inputLayer1.depth;
- }
- validate() {
- super.validate();
- checkSameSize(this.inputLayer1, this.inputLayer2);
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict$2, {
- output: [this.width, this.height],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare$2, {
- output: [this.width, this.height],
- immutable: true,
- });
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer1.weights, this.inputLayer2.weights);
- }
- compare() {
- release(this.inputLayer1.deltas);
- release(this.inputLayer2.deltas);
- this.inputLayer1.deltas = this.compareKernel(this.inputLayer2.weights, this.deltas);
- this.inputLayer2.deltas = this.compareKernel(this.inputLayer1.weights, this.deltas);
- }
- }
- function multiplyElement$1(inputLayer1, inputLayer2, settings) {
- return new MultiplyElement(inputLayer1, inputLayer2, settings);
- }
- function ones$1(size) {
- return new Float32Array(size).fill(1);
- }
- function ones2D(width, height) {
- const result = new Array(height);
- for (let y = 0; y < height; y++) {
- result[y] = ones$1(width);
- }
- return result;
- }
- class Ones extends Model {
- constructor(settings) {
- super(settings);
- this.validate();
- this.weights = ones2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- }
- }
- function ones(settings) {
- return new Ones(settings);
- }
- function predict2D$3(inputs) {
- return activate$1(inputs[this.thread.y][this.thread.x]);
- }
- function predict3D$3(inputs) {
- return activate$1(inputs[this.thread.z][this.thread.y][this.thread.x]);
- }
- function compare2D$3(weights, errors) {
- return measure$1(weights[this.thread.y][this.thread.x], errors[this.thread.y][this.thread.x]);
- }
- function compare3D$3(weights, errors) {
- return measure$1(weights[this.thread.z][this.thread.y][this.thread.x], errors[this.thread.z][this.thread.y][this.thread.x]);
- }
- class Tanh extends Activation {
- setupKernels() {
- if (this.depth > 0) {
- this.predictKernel = makeKernel(predict3D$3, {
- output: [this.width, this.height, this.depth],
- functions: [activate$1],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare3D$3, {
- output: [this.width, this.height, this.depth],
- functions: [measure$1],
- immutable: true,
- });
- }
- else {
- this.predictKernel = makeKernel(predict2D$3, {
- output: [this.width, this.height],
- functions: [activate$1],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare2D$3, {
- output: [this.width, this.height],
- functions: [measure$1],
- immutable: true,
- });
- }
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- compare() {
- release(this.inputLayer.deltas);
- this.inputLayer.deltas = this.compareKernel(this.weights, this.deltas);
- }
- }
- function tanh$1(inputLayer, settings) {
- return new Tanh(inputLayer, settings);
- }
- class Zeros extends Model {
- constructor(settings) {
- super(settings);
- this.validate();
- this.weights = zeros2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- }
- predict() {
- // throw new Error(`${this.constructor.name}-predict is not yet implemented`)
- }
- compare() {
- // throw new Error(`${this.constructor.name}-compare is not yet implemented`)
- }
- }
- function zeros(settings) {
- return new Zeros(settings);
- }
- function gru(settings, recurrentInput, input) {
- const { height } = settings;
- const updateGateWeights = random({ height, width: input.height });
- const updateGatePeepholes = random({ width: height, height });
- const updateGateBias = zeros({ height });
- const updateGate = sigmoid$1(add$1(add$1(multiply$1(updateGateWeights, input), multiply$1(updateGatePeepholes, recurrentInput)), updateGateBias));
- const resetGateWeights = random({ height, width: input.height });
- const resetGatePeepholes = random({ width: height, height });
- const resetGateBias = zeros({ height });
- const resetGate = sigmoid$1(add$1(add$1(multiply$1(resetGateWeights, input), multiply$1(resetGatePeepholes, recurrentInput)), resetGateBias));
- const cellWeights = random({ height, width: input.height });
- const cellPeepholes = random({ width: height, height });
- const cellBias = zeros({ height });
- const cell = tanh$1(add$1(add$1(multiply$1(cellWeights, input), multiply$1(cellPeepholes, multiplyElement$1(resetGate, recurrentInput))), cellBias));
- // compute hidden state as gated, saturated cell activations
- // negate updateGate
- return add$1(multiplyElement$1(add$1(ones({ width: updateGate.width, height: updateGate.height }), negative(updateGate)), cell), multiplyElement$1(recurrentInput, updateGate));
- }
- const defaults$5 = {
- weights: null,
- };
- class Input extends EntryPoint {
- constructor(settings) {
- super({ ...defaults$5, ...settings });
- this.reshapeInput = null;
- this.validate();
- this.reshapeInput = null;
- this.deltas = zeros2D(this.width, this.height);
- }
- setupKernels() {
- if (this.width === 1) {
- this.predict = this.predict1D;
- this.reshapeInput = makeKernel(function (value) {
- return value[this.thread.y];
- }, {
- output: [1, this.height],
- immutable: true,
- });
- }
- }
- reuseKernels(layer) {
- // super.reuseKernels(layer);
- this.reshapeInput = layer.reshapeInput;
- }
- predict(inputs) {
- if ((Array.isArray(inputs) || inputs instanceof Float32Array) &&
- typeof inputs[0] === 'number' &&
- inputs.length === this.height * this.width) {
- release(this.weights);
- this.weights = kernelInput(inputs, [this.width, this.height]);
- }
- else if (Array.isArray(inputs) &&
- inputs.length === this.height &&
- (Array.isArray(inputs[0]) || inputs[0] instanceof Float32Array) &&
- inputs[0].length === this.width) {
- this.weights = clone(inputs);
- }
- else {
- throw new Error('Inputs are not of sized correctly');
- }
- }
- predict1D(inputs) {
- if (this.weights)
- release(this.weights);
- if (this.reshapeInput) {
- this.weights = this.reshapeInput(inputs);
- }
- else {
- this.weights = inputs;
- }
- }
- compare() {
- // throw new Error(`${this.constructor.name}-compare is not yet implemented`)
- }
- learn() { }
- }
- function input(settings) {
- return new Input(settings);
- }
- function predict2D$2(inputs) {
- return activate(inputs[this.thread.y][this.thread.x]);
- }
- function predict3D$2(inputs) {
- return activate(inputs[this.thread.z][this.thread.y][this.thread.x]);
- }
- function compare2D$2(weights, deltas) {
- return measure(weights[this.thread.y][this.thread.x], deltas[this.thread.y][this.thread.x]);
- }
- function compare3D$2(weights, deltas) {
- return measure(weights[this.thread.z][this.thread.y][this.thread.x], deltas[this.thread.z][this.thread.y][this.thread.x]);
- }
- class LeakyRelu extends Activation {
- setupKernels() {
- const { width, height, depth } = this.inputLayer;
- if (this.depth > 0) {
- this.predictKernel = makeKernel(predict3D$2, {
- output: [width, height, depth],
- functions: [activate],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare3D$2, {
- output: [width, height, depth],
- functions: [measure],
- immutable: true,
- });
- }
- else {
- this.predictKernel = makeKernel(predict2D$2, {
- output: [width, height],
- functions: [activate],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare2D$2, {
- output: [width, height],
- functions: [measure],
- immutable: true,
- });
- }
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- compare() {
- const { deltas } = this;
- this.deltas = this.compareKernel(this.weights, deltas);
- release(deltas);
- }
- }
- function leakyRelu(inputLayer, settings) {
- return new LeakyRelu(inputLayer, settings);
- }
- function lstmCell(settings, input, recurrentInput) {
- const { height } = settings;
- if (typeof height !== 'number') {
- throw new Error('no settings.height given');
- }
- if (recurrentInput.setDimensions) {
- recurrentInput.setDimensions(1, height);
- }
- const inputGateWeights = random({
- width: input.height,
- height,
- std: 0.08,
- id: 'inputGateWeights',
- });
- const inputGatePeepholes = random({
- width: height,
- height,
- std: 0.08,
- id: 'inputGatePeepholes',
- });
- const inputGateBias = zeros({ width: 1, height, id: 'inputGateBias' });
- const inputGate = sigmoid$1(add$1(add$1(multiply$1(inputGateWeights, input), multiply$1(inputGatePeepholes, recurrentInput)), inputGateBias), { id: 'inputGate' });
- const forgetGateWeights = random({
- width: input.height,
- height,
- std: 0.08,
- id: 'forgetGateWeights',
- });
- const forgetGatePeepholes = random({
- width: height,
- height,
- std: 0.08,
- id: 'forgetGatePeepholes',
- });
- const forgetGateBias = zeros({ width: 1, height, id: 'forgetGateBias' });
- const forgetGate = sigmoid$1(add$1(add$1(multiply$1(forgetGateWeights, input), multiply$1(forgetGatePeepholes, recurrentInput)), forgetGateBias), { id: 'forgetGate' });
- const outputGateWeights = random({
- width: input.height,
- height,
- std: 0.08,
- id: 'outputGateWeights',
- });
- const outputGatePeepholes = random({
- width: height,
- height,
- std: 0.08,
- id: 'outputGatePeepholes',
- });
- const outputGateBias = zeros({ width: 1, height, id: 'outputGateBias' });
- const outputGate = sigmoid$1(add$1(add$1(multiply$1(outputGateWeights, input), multiply$1(outputGatePeepholes, recurrentInput)), outputGateBias), { id: 'outputGate' });
- const memoryWeights = random({
- width: input.height,
- height,
- std: 0.08,
- id: 'memoryWeights',
- });
- const memoryPeepholes = random({
- width: height,
- height,
- std: 0.08,
- id: 'memoryPeepholes',
- });
- const memoryBias = zeros({ width: 1, height, id: 'memoryBias' });
- const memory = tanh$1(add$1(add$1(multiply$1(memoryWeights, input), multiply$1(memoryPeepholes, recurrentInput)), memoryBias), { id: 'memory' });
- // compute new cell activation
- const retainCell = multiplyElement$1(forgetGate, recurrentInput, {
- id: 'retainCell',
- }); // what do we keep from cell
- const writeCell = multiplyElement$1(inputGate, memory, { id: 'writeCell' }); // what do we write to cell
- const cell = add$1(retainCell, writeCell, { id: 'cell' }); // new cell contents
- // compute hidden state as gated, saturated cell activations
- return multiplyElement$1(outputGate, tanh$1(cell), { id: 'activations' });
- }
- function output(settings, inputLayer) {
- const { height } = settings;
- const outputGate = random({
- height,
- width: inputLayer.height,
- id: 'outputGate',
- std: 0.08,
- });
- const output = random({ height, id: 'output', std: 0.08 });
- const outputGateConnector = multiply$1(outputGate, inputLayer, {
- id: 'outputGateConnected',
- });
- return target({ id: 'target', ...settings }, add$1(outputGateConnector, output));
- }
- function setSwitchY(value) {
- return value;
- }
- function setSwitchX(value) {
- return value;
- }
- function predict$1(inputs) {
- const startFilterX = this.constants.paddingX - this.thread.x * this.constants.strideX;
- const startInputX = this.thread.x * this.constants.strideX - this.constants.paddingX;
- const endFilterX = Math.min(this.constants.filterWidth, startFilterX + this.constants.inputWidth);
- const startFilterY = this.constants.paddingY - this.thread.y * this.constants.strideY;
- const startInputY = this.thread.y * this.constants.strideY - this.constants.paddingY;
- const endFilterY = Math.min(this.constants.filterHeight, startFilterY + this.constants.inputHeight);
- let largestValue = -99999;
- // convolve centered at this particular location
- for (let filterY = Math.max(0, startFilterY), inputY = Math.max(0, startInputY); filterY < endFilterY; filterY++, inputY++) {
- for (let filterX = Math.max(0, startFilterX), inputX = Math.max(0, startInputX); filterX < endFilterX; filterX++, inputX++) {
- if (inputY >= 0 &&
- inputY < this.constants.inputHeight &&
- inputX >= 0 &&
- inputX < this.constants.inputWidth) {
- const input = inputs[this.thread.z][inputY][inputX];
- if (input > largestValue) {
- largestValue = input;
- }
- }
- }
- }
- return largestValue;
- }
- function compare$1(deltas, switchY, switchX) {
- const x = Math.floor((this.thread.x / this.output.x) * this.constants.outputWidth);
- const y = Math.floor((this.thread.y / this.output.y) * this.constants.outputHeight);
- let value = 0;
- for (let deltasY = 0; deltasY < this.constants.inputHeight; deltasY++) {
- for (let deltasX = 0; deltasX < this.constants.inputWidth; deltasX++) {
- const switchXValue = switchX[deltasY][deltasX];
- const switchYValue = switchY[deltasY][deltasX];
- if (switchXValue === x && switchYValue === y) {
- value += deltas[deltasY][deltasX];
- }
- }
- }
- return value;
- }
- const defaults$4 = {
- padding: 0,
- stride: 0,
- filterWidth: 0,
- filterHeight: 0,
- filterCount: 0,
- };
- class Pool extends Filter {
- constructor(settings, inputLayer) {
- super(settings, inputLayer);
- this.predictKernelMap = null;
- this.settings = {
- ...settings,
- ...getStride(settings, defaults$4),
- ...getPadding(settings, defaults$4),
- };
- this.weights = randos3D(this.width, this.height, this.depth);
- this.deltas = zeros3D(this.width, this.height, this.depth);
- this.filters = randos3D(this.filterWidth, this.filterHeight, this.filterCount);
- this.filterDeltas = zeros3D(this.filterWidth, this.filterHeight, this.filterCount);
- this.validate();
- }
- get strideX() {
- return this.settings.strideX;
- }
- get strideY() {
- return this.settings.strideY;
- }
- get paddingX() {
- return this.settings.paddingX;
- }
- get paddingY() {
- return this.settings.paddingY;
- }
- get width() {
- return Math.floor((this.inputLayer.width + this.paddingX * 2 - this.filterWidth) /
- this.strideX +
- 1);
- }
- get height() {
- return Math.floor((this.inputLayer.height + this.paddingY * 2 - this.filterHeight) /
- this.strideY +
- 1);
- }
- get depth() {
- return this.settings.filterCount;
- }
- get filterCount() {
- // TODO: handle 1 depth?
- return this.settings.filterCount;
- }
- get switchX() {
- return this.settings.switchX;
- }
- set switchX(switchX) {
- this.settings.switchX = switchX;
- }
- get switchY() {
- return this.settings.switchY;
- }
- set switchY(switchY) {
- this.settings.switchY = switchY;
- }
- setupKernels() {
- this.predictKernelMap = makeKernelMap({
- switchX: setSwitchX,
- switchY: setSwitchY,
- }, predict$1, {
- output: [this.width, this.height, this.depth],
- constants: {
- inputWidth: this.inputLayer.width,
- inputHeight: this.inputLayer.height,
- paddingX: this.paddingX,
- paddingY: this.paddingY,
- filterHeight: this.filterHeight,
- filterWidth: this.filterWidth,
- },
- });
- this.compareKernel = makeKernel(compare$1, {
- output: [
- this.inputLayer.width,
- this.inputLayer.height,
- this.inputLayer.depth,
- ],
- constants: {
- inputWidth: this.inputLayer.width,
- inputHeight: this.inputLayer.height,
- outputWidth: this.width,
- outputHeight: this.height,
- },
- });
- }
- predict() {
- const { result: weights, switchX, switchY } = this
- .predictKernelMap(this.inputLayer.weights);
- this.switchX = switchX;
- this.switchY = switchY;
- this.weights = weights;
- }
- compare() {
- // debugger;
- // const depth = this.inputLayer.deltas.length;
- // const height = this.inputLayer.deltas[0].length;
- // const width = this.inputLayer.deltas[0][0].length;
- // const type = typeof this.inputLayer.deltas[0][0][0];
- const inputLayerDeltas = this.inputLayer.deltas;
- this.inputLayer.deltas = this.compareKernel(this.deltas, this.switchX, this.switchY);
- release(inputLayerDeltas);
- // debugger;
- // if (depth !== this.inputLayer.deltas.length) debugger;
- // if (height !== this.inputLayer.deltas[0].length) debugger;
- // if (width !== this.inputLayer.deltas[0][0].length) debugger;
- // if (type !== typeof this.inputLayer.deltas[0][0][0]) debugger;
- }
- }
- function pool(settings, inputLayer) {
- return new Pool(settings, inputLayer);
- }
- class RecurrentInput extends Internal {
- constructor(recurrentInput) {
- super();
- this.praxis = null;
- this.predictKernel = null;
- this.compareKernel = null;
- this.settings = {};
- this.recurrentInput = recurrentInput;
- this.validate();
- }
- get width() {
- return this.recurrentInput.width;
- }
- get height() {
- return this.recurrentInput.height;
- }
- get depth() {
- return this.recurrentInput.depth;
- }
- get deltas() {
- return this.recurrentInput.deltas;
- }
- set deltas(deltas) {
- const recurrentInputDeltas = this.recurrentInput.deltas;
- this.recurrentInput.deltas = deltas;
- release(recurrentInputDeltas);
- }
- get weights() {
- return this.recurrentInput.weights;
- }
- set weights(weights) {
- const recurrentInputWeights = this.recurrentInput.weights;
- this.recurrentInput.weights = weights;
- release(recurrentInputWeights);
- }
- validate() {
- BaseLayer.prototype.validate.call(this);
- if (this.width !== this.recurrentInput.width) {
- throw new Error(`${this.constructor.name} layer width ${this.width} and ${this.recurrentInput.constructor.name} width (${this.recurrentInput.width}) are not same`);
- }
- if (this.height !== this.recurrentInput.height) {
- throw new Error(`${this.constructor.name} layer height ${this.height} and ${this.recurrentInput.constructor.name} width (${this.recurrentInput.height}) are not same`);
- }
- }
- setDimensions(width, height) {
- this.recurrentInput.width = width;
- this.recurrentInput.height = height;
- }
- predict() {
- // throw new Error(`${this.constructor.name}-predict is not yet implemented`)
- }
- compare() {
- // throw new Error(`${this.constructor.name}-compare is not yet implemented`)
- }
- learn() {
- // throw new Error(`${this.constructor.name}-learn is not yet implemented`)
- }
- setupKernels() {
- // throw new Error(
- // `${this.constructor.name}-setupKernels is not yet implemented`
- // )
- }
- reuseKernels() {
- // throw new Error(
- // `${this.constructor.name}-reuseKernels is not yet implemented`
- // )
- }
- }
- class RecurrentZeros extends Internal {
- constructor(settings) {
- super();
- this.praxis = null;
- this.settings = {};
- this.predictKernel = null;
- this.compareKernel = null;
- if (settings) {
- this.settings = { ...settings };
- }
- }
- setDimensions(width, height) {
- this.praxis = null;
- this.settings = {
- ...this.settings,
- width,
- height,
- weights: zeros2D(width, height),
- deltas: zeros2D(width, height),
- };
- }
- setupKernels() {
- // throw new Error(
- // `${this.constructor.name}-setupKernels is not yet implemented`
- // )
- }
- reuseKernels() {
- // throw new Error(
- // `${this.constructor.name}-reuseKernels is not yet implemented`
- // )
- }
- predict() {
- // throw new Error(`${this.constructor.name}-predict is not yet implemented`)
- }
- compare() {
- // throw new Error(`${this.constructor.name}-compare is not yet implemented`)
- }
- learn(learningRate) {
- const { weights: oldWeights } = this;
- this.weights = this.praxis.run(this, learningRate);
- // this.deltas = deltas;
- release(oldWeights);
- }
- }
- function recurrentZeros() {
- return new RecurrentZeros();
- }
- function predict2D$1(inputs) {
- return activate$3(inputs[this.thread.y][this.thread.x]);
- }
- function compare2D$1(weights, deltas) {
- return measure$3(weights[this.thread.y][this.thread.x], deltas[this.thread.y][this.thread.x]);
- }
- function predict3D$1(inputs) {
- return activate$3(inputs[this.thread.z][this.thread.y][this.thread.x]);
- }
- function compare3D$1(weights, deltas) {
- return measure$3(weights[this.thread.z][this.thread.y][this.thread.x], deltas[this.thread.z][this.thread.y][this.thread.x]);
- }
- class Relu extends Activation {
- setupKernels() {
- const { width, height, depth } = this.inputLayer;
- if (depth > 0) {
- this.predictKernel = makeKernel(predict3D$1, {
- output: [width, height, depth],
- functions: [activate$3],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare3D$1, {
- output: [width, height, depth],
- functions: [measure$3],
- immutable: true,
- });
- }
- else {
- this.predictKernel = makeKernel(predict2D$1, {
- output: [width, height],
- functions: [activate$3],
- immutable: true,
- });
- this.compareKernel = makeKernel(compare2D$1, {
- output: [width, height],
- functions: [measure$3],
- immutable: true,
- });
- }
- }
- predict() {
- release(this.weights);
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- compare() {
- release(this.inputLayer.deltas);
- this.inputLayer.deltas = this.compareKernel(this.weights, this.deltas);
- }
- }
- function relu$1(inputLayer, settings) {
- return new Relu(inputLayer, settings);
- }
- function rnnCell(settings, input, recurrentInput) {
- const { height } = settings;
- if (typeof height !== 'number')
- throw new Error('height not set');
- if (recurrentInput.setDimensions) {
- recurrentInput.setDimensions(1, height);
- }
- // wxh
- const weight = random({
- id: 'weight',
- height,
- width: input.height,
- std: 0.08,
- });
- // whh
- const transition = random({
- id: 'transition',
- height,
- width: height,
- std: 0.08,
- });
- // bhh
- const bias = zeros({ id: 'bias', height });
- return relu$1(add$1(add$1(multiply$1(weight, input), multiply$1(transition, recurrentInput)), bias));
- }
- class Regression extends BaseLayer {
- constructor(settings, inputLayer) {
- super(settings);
- this.inputLayer = inputLayer;
- this.validate();
- }
- predict() {
- release(this.weights);
- this.weights = clone(this.inputLayer.weights);
- }
- learn() {
- // throw new Error(`${this.constructor.name}-learn is not yet implemented`)
- }
- }
- // TODO: handle `loss += 0.5*dy*dy;` total and sum in learn
- function regression(settings, inputLayer) {
- return new Regression(settings, inputLayer);
- }
- function getMaxValue2D(inputs) {
- let maxInput = -Infinity;
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- const input = inputs[y][x];
- if (input > maxInput) {
- maxInput = input;
- }
- }
- }
- return maxInput;
- }
- function getMaxValue3D(inputs) {
- let maxInput = -Infinity;
- for (let z = 0; z < this.constants.inputDepth; z++) {
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- const input = inputs[z][y][x];
- if (input > maxInput) {
- maxInput = input;
- }
- }
- }
- }
- return maxInput;
- }
- function getSum2D(inputs) {
- let sum = 0;
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- sum += inputs[y][x];
- }
- }
- return sum;
- }
- function getSum3D(inputs) {
- let sum = 0;
- for (let z = 0; z < this.constants.inputDepth; z++) {
- for (let y = 0; y < this.constants.inputHeight; y++) {
- for (let x = 0; x < this.constants.inputWidth; x++) {
- sum += inputs[z][y][x];
- }
- }
- }
- return sum;
- }
- function getExponentials(inputs, maxInput) {
- return Math.exp(inputs[this.thread.x] - maxInput[0]);
- }
- function getExponentials3D(inputs, maxInput) {
- return Math.exp(inputs[this.thread.z][this.thread.y][this.thread.x] - maxInput[0]);
- }
- function predict2D(exponentials, exponentialsSum) {
- return exponentials[this.thread.y][this.thread.x] / exponentialsSum[0];
- }
- function predict3D(exponentials, exponentialsSum) {
- return (exponentials[this.thread.z][this.thread.y][this.thread.x] /
- exponentialsSum[0]);
- }
- function compare2D(target, exponentials) {
- let indicator = 0;
- const index = this.thread.x + this.thread.y * this.output.x;
- if (index === target) {
- indicator = 1;
- }
- return -(indicator - exponentials[this.thread.y][this.thread.x]);
- }
- function compare3D(target, exponentials) {
- let indicator = 0;
- const index = this.thread.x +
- this.thread.y * this.output.x +
- this.thread.z * this.output.x * this.output.y;
- if (index === target) {
- indicator = 1;
- }
- return -(indicator - exponentials[this.thread.z][this.thread.y][this.thread.x]);
- }
- // TODO: handle: `return -Math.log(this.es[y]);` in learn
- class SoftMax extends Modifier {
- constructor(inputLayer, settings) {
- super(inputLayer, settings);
- this.errors = null;
- this.getExponentialsKernel = null;
- this.getMaxValueKernel = null;
- this.getSumKernel = null;
- this.validate();
- if (this.depth > 0) {
- this.weights = randos3D(this.width, this.height, this.depth);
- this.deltas = zeros3D(this.width, this.height, this.depth);
- }
- else if (this.height > 0) {
- this.weights = randos2D(this.width, this.height);
- this.deltas = zeros2D(this.width, this.height);
- }
- else {
- this.weights = randos(this.width);
- this.deltas = zeros$1(this.width);
- }
- }
- setupKernels() {
- const { width, height, depth } = this;
- if (depth > 0) {
- this.getExponentialsKernel = makeKernel(getExponentials3D, {
- output: [width, height, depth],
- });
- this.getMaxValueKernel = makeKernel(getMaxValue3D, {
- output: [1, 1, 1],
- constants: {
- inputWidth: width,
- inputHeight: height,
- inputDepth: depth,
- },
- });
- this.getSumKernel = makeKernel(getSum3D, {
- output: [1, 1, 1],
- constants: {
- inputWidth: width,
- inputHeight: height,
- inputDepth: depth,
- },
- });
- this.predictKernel = makeKernel(predict3D, {
- output: [width, height, depth],
- });
- this.compareKernel = makeKernel(compare3D, {
- output: [width, height, depth],
- immutable: true,
- });
- }
- else {
- this.getExponentialsKernel = makeKernel(getExponentials, {
- output: [width, height],
- });
- this.getMaxValueKernel = makeKernel(getMaxValue2D, {
- output: [1, 1],
- constants: {
- inputWidth: width,
- inputHeight: height,
- },
- });
- this.getSumKernel = makeKernel(getSum2D, {
- output: [1, 1],
- constants: {
- inputWidth: width,
- inputHeight: height,
- },
- });
- this.predictKernel = makeKernel(predict2D, {
- output: [width, height],
- });
- this.compareKernel = makeKernel(compare2D, {
- output: [width, height],
- immutable: true,
- });
- }
- }
- predict() {
- const maxValue = this.getMaxValueKernel(this.inputLayer.weights);
- const exponentials = this.getExponentialsKernel(this.inputLayer.weights, maxValue);
- const exponentialsSum = this.getSumKernel(exponentials);
- this.weights = this.predictKernel(exponentials, exponentialsSum);
- }
- compare(targetValues) {
- const { deltas, errors } = this;
- this.errors = this.compareKernel(targetValues[0], deltas);
- this.deltas = clone(this.errors);
- release(deltas);
- release(errors);
- const inputLayerDeltas = this.inputLayer.deltas;
- this.inputLayer.deltas = clone(this.deltas);
- release(inputLayerDeltas);
- }
- }
- function softMax(inputLayer, settings) {
- return new SoftMax(inputLayer, settings);
- }
- class SVM extends BaseLayer {
- constructor(inputLayer, settings) {
- super(settings);
- this.inputLayer = inputLayer;
- }
- predict() {
- release(this.weights);
- this.weights = clone(this.inputLayer.weights);
- this.validate();
- }
- learn() {
- // throw new Error(`${this.constructor.name}-learn is not yet implemented`)
- }
- }
- // function learn(target) {
- // if (y === i) {
- // continue;
- // }
- // const ydiff = -yscore + x.w[i] + margin;
- // if (ydiff > 0) {
- // // violating dimension, apply loss
- // x.dw[i] += 1;
- // x.dw[y] -= 1;
- // loss += ydiff;
- // }
- // }
- function svm(inputLayer, settings) {
- return new SVM(inputLayer, settings);
- }
- function predict(value) {
- return value[this.thread.x][this.thread.y];
- }
- const compare = predict;
- class Transpose extends Modifier {
- get width() {
- return this.inputLayer.height;
- }
- get height() {
- return this.inputLayer.width;
- }
- constructor(inputLayer) {
- super(inputLayer);
- this.validate();
- }
- setupKernels() {
- this.predictKernel = makeKernel(predict, {
- output: [this.height, this.width],
- });
- this.compareKernel = makeKernel(compare, {
- output: [this.width, this.height],
- });
- }
- predict() {
- this.weights = this.predictKernel(this.inputLayer.weights);
- }
- compare() {
- this.inputLayer.deltas = this.compareKernel(this.deltas);
- }
- }
- function transpose(inputLayer) {
- return new Transpose(inputLayer);
- }
- const layerTypes = {
- Activation,
- Internal,
- InternalModel,
- EntryPoint,
- Filter,
- Model,
- Modifier,
- Operator,
- Target,
- };
- var layer = /*#__PURE__*/Object.freeze({
- __proto__: null,
- layerTypes: layerTypes,
- Add: Add,
- add: add$1,
- arthurFeedForward: arthurFeedForward,
- BaseLayer: BaseLayer,
- baseLayerDefaultSettings: baseLayerDefaultSettings,
- Convolution: Convolution,
- convolution: convolution,
- Dropout: Dropout,
- dropout: dropout,
- feedForward: feedForward,
- FullyConnected: FullyConnected,
- fullyConnected: fullyConnected,
- gru: gru,
- Input: Input,
- input: input,
- LeakyRelu: LeakyRelu,
- leakyRelu: leakyRelu,
- lstmCell: lstmCell,
- Multiply: Multiply,
- multiply: multiply$1,
- MultiplyElement: MultiplyElement,
- multiplyElement: multiplyElement$1,
- Negative: Negative,
- negative: negative,
- Ones: Ones,
- ones: ones,
- output: output,
- Pool: Pool,
- pool: pool,
- Random: Random,
- random: random,
- RecurrentInput: RecurrentInput,
- RecurrentZeros: RecurrentZeros,
- rnnCell: rnnCell,
- Regression: Regression,
- regression: regression,
- Relu: Relu,
- relu: relu$1,
- Sigmoid: Sigmoid,
- sigmoid: sigmoid$1,
- SoftMax: SoftMax,
- softMax: softMax,
- SVM: SVM,
- svm: svm,
- Tanh: Tanh,
- tanh: tanh$1,
- Target: Target,
- target: target,
- Transpose: Transpose,
- transpose: transpose,
- Zeros: Zeros,
- zeros: zeros
- });
- const layerNameTypes = Object.keys(layer);
- function layerFromJSON(jsonLayer, inputLayer1, inputLayer2) {
- if (!layerNameTypes.find((layerNameType) => layerNameType === jsonLayer.type)) {
- return null;
- }
- const Layer = layer[jsonLayer.type];
- if (Layer.prototype instanceof layerTypes.Filter) {
- if (!inputLayer1)
- throw new Error('inputLayer missing');
- return new Layer(jsonLayer, inputLayer1);
- }
- else if (Layer.prototype instanceof layerTypes.Activation ||
- Layer.prototype instanceof layerTypes.Modifier) {
- if (!inputLayer1)
- throw new Error('inputLayer missing');
- return new Layer(inputLayer1, jsonLayer);
- }
- else if (Layer.prototype instanceof layerTypes.Internal) {
- return new Layer(jsonLayer);
- }
- else if (Layer.prototype instanceof layerTypes.Operator) {
- if (!inputLayer1)
- throw new Error('inputLayer1 missing');
- if (!inputLayer2)
- throw new Error('inputLayer2 missing');
- return new Layer(inputLayer1, inputLayer2, jsonLayer);
- }
- else if (Layer.prototype instanceof layerTypes.InternalModel ||
- Layer.prototype instanceof layerTypes.EntryPoint ||
- Layer.prototype instanceof layerTypes.Model) {
- return new Layer(jsonLayer);
- }
- else if (Layer === Target) {
- if (!inputLayer1)
- throw new Error('inputLayer missing');
- return new Layer(jsonLayer, inputLayer1);
- }
- return null;
- }
- class LookupTable {
- constructor(data, prop) {
- this.prop = null;
- this.table = {};
- this.length = 0;
- const table = this.table;
- if (prop) {
- this.prop = prop;
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- const object = datum[prop];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- if (table.hasOwnProperty(p))
- continue;
- table[p] = this.length++;
- }
- }
- }
- else if (Array.isArray(data) && Array.isArray(data[0])) {
- for (let i = 0; i < data.length; i++) {
- const array = data[i];
- for (let j = 0; j < array.length; j++) {
- const object = array[j];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- if (table.hasOwnProperty(p))
- continue;
- table[p] = this.length++;
- }
- }
- }
- }
- else {
- for (let i = 0; i < data.length; i++) {
- const object = data[i];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- if (table.hasOwnProperty(p))
- continue;
- table[p] = this.length++;
- }
- }
- }
- }
- }
- const defaults$3 = {
- learningRate: 0.3,
- binaryThresh: 0.5,
- initPraxis: (layerTemplate, settings) => {
- var _a;
- return momentumRootMeanSquaredPropagation(layerTemplate, (_a = layerTemplate.settings.praxisOpts) !== null && _a !== void 0 ? _a : settings);
- },
- };
- const trainDefaults$3 = {
- iterations: 20000,
- errorThresh: 0.005,
- log: false,
- logPeriod: 10,
- learningRate: 0.3,
- callbackPeriod: 10,
- errorCheckInterval: 100,
- timeout: Infinity,
- };
- class FeedForward {
- constructor(options = {}) {
- this.trainOpts = {};
- this.layers = null;
- this._inputLayer = null;
- this._hiddenLayers = null;
- this._outputLayer = null;
- this._model = null;
- this.meanSquaredError = null;
- this.inputLookup = null;
- this.inputLookupLength = null;
- this.outputLookup = null;
- this.outputLookupLength = null;
- this.options = { ...defaults$3, ...options };
- this._updateTrainingOptions({
- ...trainDefaults$3,
- ...options,
- });
- }
- static _validateTrainingOptions(options) {
- const { iterations, errorThresh, log, logPeriod, learningRate, callback, callbackPeriod, timeout, } = options;
- const validations = {
- iterations: () => typeof iterations === 'number' && iterations > 0,
- errorThresh: () => typeof errorThresh === 'number' && errorThresh > 0 && errorThresh < 1,
- log: () => typeof log === 'function' || typeof log === 'boolean',
- logPeriod: () => typeof logPeriod === 'number' && logPeriod > 0,
- learningRate: () => typeof learningRate === 'number' &&
- learningRate > 0 &&
- learningRate < 1,
- callback: () => typeof callback === 'function' || callback === null,
- callbackPeriod: () => typeof callbackPeriod === 'number' && callbackPeriod > 0,
- timeout: () => typeof timeout === 'number' && timeout > 0,
- };
- Object.keys(trainDefaults$3).forEach((key) => {
- if (validations.hasOwnProperty(key) && !validations[key]()) {
- const val = options[key];
- throw new Error(`[${key}, ${(val !== null && val !== void 0 ? val : 'undefined').toString()}] is out of normal training range, your network will probably not train.`);
- }
- });
- }
- /**
- * if a method is passed in method is used
- * if false passed in nothing is logged
- */
- _setLogMethod(log) {
- if (typeof log === 'function') {
- this.trainOpts.log = log;
- }
- else if (log) {
- // eslint-disable-next-line
- this.trainOpts.log = console.log;
- }
- else {
- this.trainOpts.log = false;
- }
- }
- _updateTrainingOptions(opts) {
- var _a;
- this.trainOpts = { ...trainDefaults$3, ...this.trainOpts, ...opts };
- FeedForward._validateTrainingOptions(this.trainOpts);
- this._setLogMethod((_a = opts.log) !== null && _a !== void 0 ? _a : this.trainOpts.log);
- const { callback, callbackPeriod, errorCheckInterval } = this.trainOpts;
- if (callback && callbackPeriod !== errorCheckInterval) {
- console.warn(`options.callbackPeriod with value of ${(callbackPeriod !== null && callbackPeriod !== void 0 ? callbackPeriod : 'undefined').toString()} does not match options.errorCheckInterval with value of ${(errorCheckInterval !== null && errorCheckInterval !== void 0 ? errorCheckInterval : 'undefined').toString()}, if logging error, it will repeat. These values may need to match`);
- }
- }
- _connectOptionsLayers() {
- const { inputLayerIndex, outputLayerIndex, layers } = this.options;
- if (!layers)
- throw new Error('this.options.layers in unexpected state');
- if (typeof inputLayerIndex !== 'number')
- throw new Error('inputLayerIndex not a number');
- if (typeof outputLayerIndex !== 'number')
- throw new Error('inputLayerIndex not a number');
- const inputLayer = layers[inputLayerIndex];
- if (!inputLayer) {
- throw new Error('inputLayer not found in this.options.layers');
- }
- const outputLayer = layers[outputLayerIndex];
- if (!outputLayer) {
- throw new Error('outputLayer not found in this.options.layers');
- }
- this._inputLayer = inputLayer;
- this._hiddenLayers = layers.slice(inputLayerIndex, outputLayerIndex - inputLayerIndex);
- this._outputLayer = outputLayer;
- return layers;
- }
- _connectNewLayers() {
- const { inputLayer, outputLayer } = this.options;
- if (!inputLayer)
- throw new Error('inputLayer not defined');
- const layers = [];
- this._inputLayer = inputLayer();
- const hiddenLayers = this._connectHiddenLayers(this._inputLayer);
- if (!outputLayer)
- throw new Error('outputLayer not defined');
- this._outputLayer = outputLayer(hiddenLayers[hiddenLayers.length - 1], hiddenLayers.length);
- layers.push(this._inputLayer);
- layers.push(...hiddenLayers);
- layers.push(this._outputLayer);
- return flattenLayers(layers);
- }
- _connectHiddenLayers(previousLayer) {
- this._hiddenLayers = [];
- const result = [];
- const { hiddenLayers } = this.options;
- if (!hiddenLayers)
- throw new Error('hiddenLayers not defined');
- for (let i = 0; i < hiddenLayers.length; i++) {
- const hiddenLayer = hiddenLayers[i](previousLayer, i);
- result.push(hiddenLayer);
- this._hiddenLayers.push(hiddenLayer);
- previousLayer = hiddenLayer;
- }
- return result;
- }
- initialize() {
- this.layers = this.options.layers
- ? this._connectOptionsLayers()
- : this._connectNewLayers();
- this.initializeLayers(this.layers);
- this._model = this.layers.filter((l) => l instanceof Model);
- }
- initializeLayers(layers) {
- var _a, _b;
- for (let i = 0; i < layers.length; i++) {
- const layer = layers[i];
- // TODO: optimize for when training or just running
- layer.setupKernels(true);
- if (layer instanceof Model &&
- layer.praxis === null &&
- typeof this.options.initPraxis === 'function') {
- layer.praxis = this.options.initPraxis(layer, (_b = (_a = layer.settings.praxisOpts) !== null && _a !== void 0 ? _a : this.options.praxisOpts) !== null && _b !== void 0 ? _b : {});
- layer.praxis.setupKernels();
- }
- }
- const lastLayer = layers[layers.length - 1];
- this.meanSquaredError = new MeanSquaredError({
- width: lastLayer.width,
- height: lastLayer.height,
- });
- }
- run(input) {
- let typeSafeInput;
- if (Array.isArray(input) || input.buffer) {
- typeSafeInput = input;
- }
- else {
- if (this.inputLookup) {
- typeSafeInput = lookup.toArray(this.inputLookup, input, this.inputLookupLength);
- }
- else {
- throw new Error('input is incompatible with net');
- }
- }
- let output = this.runInput(typeSafeInput);
- if (output instanceof gpu_js.Texture) {
- output = output.toArray();
- }
- if (this.outputLookup) {
- return lookup.toObject(this.outputLookup, output);
- }
- return output;
- }
- runInput(input) {
- if (!this.layers)
- throw new Error('not initialized');
- this.layers[0].predict(input);
- for (let i = 1; i < this.layers.length; i++) {
- this.layers[i].predict();
- }
- return this.layers[this.layers.length - 1].weights;
- }
- train(data, options = {}) {
- const { preparedData, status, endTime } = this._prepTraining(data, options);
- let continueTicking = true;
- const calculateError = () => this._calculateTrainingError(preparedData);
- const trainPatters = () => this._trainPatterns(preparedData);
- while (continueTicking) {
- continueTicking = this._trainingTick(status, endTime, calculateError, trainPatters);
- }
- return status;
- }
- _trainingTick(status, endTime, calculateError, trainPatterns) {
- const { trainOpts } = this;
- if (status.iterations >= trainOpts.iterations ||
- status.error <= trainOpts.errorThresh ||
- Date.now() >= endTime) {
- return false;
- }
- if (typeof trainOpts.log === 'function' &&
- status.iterations % trainOpts.logPeriod === 0) {
- status.error = calculateError();
- trainOpts.log(`iterations: ${status.iterations}, training error: ${status.error}`);
- }
- else if (status.iterations % trainOpts.errorCheckInterval ===
- 0) {
- status.error = calculateError();
- }
- else {
- trainPatterns();
- }
- if (trainOpts.callback &&
- status.iterations % trainOpts.callbackPeriod === 0) {
- trainOpts.callback(Object.assign(status));
- }
- status.iterations++;
- return true;
- }
- _prepTraining(data, options) {
- this._updateTrainingOptions(options);
- const formattedData = this.formatData(data);
- const endTime = this.trainOpts.timeout
- ? Date.now() + this.trainOpts.timeout
- : 0;
- const status = {
- error: 1,
- iterations: 0,
- };
- this.verifyIsInitialized();
- return {
- preparedData: this.transferData(formattedData),
- status,
- endTime,
- };
- }
- verifyIsInitialized() {
- if (!this._model) {
- this.initialize();
- }
- }
- _calculateTrainingError(preparedData) {
- let sum = new Float32Array([0]);
- const meanSquaredError = this.meanSquaredError;
- for (let i = 0; i < preparedData.length; ++i) {
- const prevSum = sum;
- const error = this._trainPattern(preparedData[i].input, preparedData[i].output, true);
- sum = meanSquaredError.add(sum, error);
- release(error);
- release(prevSum);
- }
- const result = meanSquaredError.divide(preparedData.length, sum);
- release(sum);
- if (result instanceof gpu_js.Texture) {
- const resultArray = result.toArray();
- release(result);
- return resultArray[0];
- }
- return result[0];
- }
- /**
- * @param data
- * @private
- */
- _trainPatterns(data) {
- for (let i = 0; i < data.length; ++i) {
- this._trainPattern(data[i].input, data[i].output, false);
- }
- }
- _trainPattern(input, target, logErrorRate) {
- var _a;
- // forward propagate
- this.runInput(input);
- // back propagate
- this._calculateDeltas(target);
- this.adjustWeights();
- if (logErrorRate) {
- if (!((_a = this._outputLayer) === null || _a === void 0 ? void 0 : _a.errors)) {
- throw new Error('outputLayer.errors not defined');
- }
- return this.meanSquaredError.calculate(this._outputLayer.errors);
- }
- return null;
- }
- _calculateDeltas(target) {
- const layers = this.layers;
- for (let i = layers.length - 1; i > -1; i--) {
- layers[i].compare(target);
- }
- }
- /**
- *
- */
- adjustWeights() {
- const _model = this._model;
- for (let i = 0; i < _model.length; i++) {
- _model[i].learn(this.trainOpts.learningRate);
- }
- }
- /**
- *
- * @param data
- * @returns {*}
- */
- formatData(data) {
- if (!Array.isArray(data)) {
- // turn stream datum into array
- const tmp = [];
- tmp.push(data);
- data = tmp;
- }
- // turn sparse hash input into arrays with 0s as filler
- const inputDatumCheck = data[0].input;
- let formattedData;
- if (Array.isArray(data) &&
- !Array.isArray(inputDatumCheck) &&
- !(inputDatumCheck instanceof Float32Array)) {
- if (!this.inputLookup) {
- const lookupTable = new LookupTable(data, 'input');
- this.inputLookup = lookupTable.table;
- this.inputLookupLength = lookupTable.length;
- }
- formattedData = data.map((datumParam) => {
- const array = lookup.toArray(this.inputLookup, datumParam.input, this.inputLookupLength);
- return { input: array };
- }, this);
- }
- else {
- formattedData = data;
- }
- const outputDatumCheck = data[0].output;
- if (!Array.isArray(outputDatumCheck) &&
- !(outputDatumCheck instanceof Float32Array)) {
- if (!this.outputLookup) {
- const lookupTable = new LookupTable(data, 'output');
- this.outputLookup = lookupTable.table;
- this.outputLookupLength = lookupTable.length;
- }
- formattedData = data.map((datumParam, index) => {
- const array = lookup.toArray(this.outputLookup, datumParam.output, this.inputLookupLength);
- return {
- input: formattedData[index].input,
- output: array,
- };
- }, this);
- }
- return formattedData;
- }
- transferData(formattedData) {
- const transferredData = new Array(formattedData.length);
- const transferInput = makeKernel(function (value) {
- return value[this.thread.x];
- }, {
- output: [formattedData[0].input.length],
- immutable: true,
- });
- const transferOutput = makeKernel(function (value) {
- return value[this.thread.x];
- }, {
- output: [formattedData[0].output.length],
- immutable: true,
- });
- for (let i = 0; i < formattedData.length; i++) {
- const formattedDatum = formattedData[i];
- transferredData[i] = {
- input: transferInput(formattedDatum.input),
- output: transferOutput(formattedDatum.output),
- };
- }
- return transferredData;
- }
- /**
- *
- * @param data
- * @returns {
- * {
- * error: number,
- * misclasses: Array
- * }
- * }
- */
- test() {
- throw new Error(`${this.constructor.name}-test is not yet implemented`);
- }
- /**
- *
- */
- toJSON() {
- var _a;
- if (!this.layers) {
- this.initialize();
- }
- if (!this._model ||
- !this.layers ||
- !this._inputLayer ||
- !this._hiddenLayers ||
- !this._outputLayer) {
- throw new Error('network is not initialized');
- }
- const jsonLayers = [];
- for (let i = 0; i < this.layers.length; i++) {
- const layer = this.layers[i];
- const jsonLayer = layer.toJSON();
- if (layer.hasOwnProperty('inputLayer')) {
- jsonLayer.inputLayerIndex = this.layers.indexOf(layer.inputLayer);
- }
- else if (layer.hasOwnProperty('inputLayer1') &&
- layer.hasOwnProperty('inputLayer2')) {
- jsonLayer.inputLayer1Index = this.layers.indexOf(layer.inputLayer1);
- jsonLayer.inputLayer2Index = this.layers.indexOf(layer.inputLayer2);
- }
- jsonLayers.push(jsonLayer);
- }
- return {
- type: this.constructor.name,
- sizes: (_a = this.options.sizes) !== null && _a !== void 0 ? _a : [this._inputLayer.height]
- .concat(this._hiddenLayers.map((l) => l.height))
- .concat([this._outputLayer.height]),
- outputLayerIndex: this.layers.indexOf(this._outputLayer),
- layers: jsonLayers,
- inputLayerIndex: this.layers.indexOf(this._inputLayer),
- };
- }
- static fromJSON(json, getLayer) {
- var _a, _b, _c, _d;
- const jsonLayers = json.layers;
- const layers = [];
- const inputLayer = getLayer
- ? (_a = layerFromJSON(jsonLayers[0])) !== null && _a !== void 0 ? _a : getLayer(jsonLayers[0]) : layerFromJSON(jsonLayers[0]);
- if (!inputLayer)
- throw new Error('unable to find layer');
- layers.push(inputLayer);
- for (let i = 1; i < jsonLayers.length; i++) {
- const jsonLayer = jsonLayers[i];
- if (typeof jsonLayer.inputLayerIndex === 'undefined' &&
- typeof jsonLayer.inputLayer1Index === 'undefined' &&
- typeof jsonLayer.inputLayer2Index === 'undefined') {
- const layer = getLayer
- ? (_b = layerFromJSON(jsonLayer)) !== null && _b !== void 0 ? _b : getLayer(jsonLayer) : layerFromJSON(jsonLayer);
- if (!layer)
- throw new Error('unable to find layer');
- layers.push(layer);
- }
- else if (typeof jsonLayer.inputLayerIndex === 'number') {
- const inputLayer = layers[jsonLayer.inputLayerIndex];
- if (!inputLayer) {
- throw new Error('inputLayer1 not found');
- }
- const layer = getLayer
- ? (_c = layerFromJSON(jsonLayer, inputLayer)) !== null && _c !== void 0 ? _c : getLayer(jsonLayer, inputLayer) : layerFromJSON(jsonLayer, inputLayer);
- if (!layer)
- throw new Error('unable to find layer');
- layers.push(layer);
- }
- else {
- if (typeof jsonLayer.inputLayer1Index !== 'number') {
- throw new Error('Cannot create network from provided JSON. inputLayer1Index not defined.');
- }
- if (typeof jsonLayer.inputLayer2Index !== 'number') {
- throw new Error('Cannot create network from provided JSON. inputLayer2Index not defined.');
- }
- const inputLayer1 = layers[jsonLayer.inputLayer1Index];
- const inputLayer2 = layers[jsonLayer.inputLayer2Index];
- if (inputLayer1 === undefined)
- throw new Error(`Cannot create network from provided JSON. layer of index ${jsonLayer.inputLayer1Index} not found.`);
- if (inputLayer2 === undefined)
- throw new Error(`Cannot create network from provided JSON. layer of index ${jsonLayer.inputLayer2Index} not found.`);
- const layer = getLayer
- ? (_d = layerFromJSON(jsonLayer, inputLayer1, inputLayer2)) !== null && _d !== void 0 ? _d : getLayer(jsonLayer, inputLayer1, inputLayer2) : layerFromJSON(jsonLayer, inputLayer1, inputLayer2);
- if (!layer)
- throw new Error('unable to find layer');
- layers.push(layer);
- }
- }
- return new this({ ...json, layers });
- }
- /**
- *
- * @returns {Function}
- */
- toFunction() {
- throw new Error(`${this.constructor.name}-toFunction is not yet implemented`);
- }
- /**
- * This will create a TrainStream (WriteStream) for us to send the training data to.
- * @param opts training options
- * @returns {TrainStream|*}
- */
- createTrainStream() {
- throw new Error(`${this.constructor.name}-createTrainStream is not yet implemented`);
- }
- }
- function likely(input, net) {
- if (!net) {
- throw new TypeError(`Required parameter 'net' is of type ${typeof net}. Must be of type 'brain.NeuralNetwork'`);
- }
- const output = net.run(input);
- let maxProp = null;
- let maxValue = -1;
- Object.entries(output).forEach(([key, value]) => {
- if (typeof value !== 'undefined' &&
- typeof value === 'number' &&
- value > maxValue) {
- maxProp = key;
- maxValue = value;
- }
- });
- return maxProp;
- }
- var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
- function createCommonjsModule(fn, basedir, module) {
- return module = {
- path: basedir,
- exports: {},
- require: function (path, base) {
- return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
- }
- }, fn(module, module.exports), module.exports;
- }
- function commonjsRequire () {
- throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
- }
- var browser = createCommonjsModule(function (module, exports) {
- var __assign = (commonjsGlobal && commonjsGlobal.__assign) || function () {
- __assign = Object.assign || function(t) {
- for (var s, i = 1, n = arguments.length; i < n; i++) {
- s = arguments[i];
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
- t[p] = s[p];
- }
- return t;
- };
- return __assign.apply(this, arguments);
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.thaw = exports.Block = exports.Thaw = void 0;
- /**
- * thaw an array of items
- */
- var Thaw = /** @class */ (function () {
- function Thaw(items, options) {
- var _this = this;
- if (options === void 0) { options = {}; }
- var _a = __assign(__assign({}, Thaw.defaultSettings), options), each = _a.each, done = _a.done;
- this.i = 0;
- this.isStopped = false;
- this.items = items;
- this.options = options;
- this.tick = function () {
- if (_this.isStopped)
- return;
- _this.timeout = setTimeout(_this.tick, 0);
- if (Thaw.thawing)
- return;
- var item = _this.items[_this.i];
- if (_this.i >= _this.items.length) {
- if (done !== null) {
- Thaw.thawing = true;
- done();
- Thaw.thawing = false;
- }
- _this.isStopped = true;
- clearTimeout(_this.timeout);
- return;
- }
- if (each !== null) {
- Thaw.thawing = true;
- each(item, _this.i);
- Thaw.thawing = false;
- }
- else if (item !== undefined) {
- item();
- }
- _this.i++;
- };
- Thaw.thaws.push(this);
- if (!options.delay) {
- this.tick();
- }
- }
- Object.defineProperty(Thaw, "isThawing", {
- /**
- * returns if Thaw.js is thawing
- */
- get: function () {
- return Thaw.thawing;
- },
- enumerable: false,
- configurable: true
- });
- /**
- * Stops all Thaw instances
- */
- Thaw.stopAll = function () {
- for (var i = 0; i < Thaw.thaws.length; i++) {
- Thaw.thaws[i].stop();
- }
- };
- /**
- * readies thaw to continue
- */
- Thaw.prototype.makeReady = function () {
- if (this.isStopped) {
- this.isStopped = false;
- return true;
- }
- return false;
- };
- /**
- * Adds an item to the end of this instance of Thaw and readies Thaw to process it
- */
- Thaw.prototype.add = function (item) {
- this.items.push(item);
- if (this.makeReady()) {
- this.tick();
- }
- return this;
- };
- /**
- * Inserts an item just after the current item being processed in Thaw and readies Thaw to process it
- */
- Thaw.prototype.insert = function (item) {
- this.items.splice(this.i, 0, item);
- if (this.makeReady()) {
- this.tick();
- }
- return this;
- };
- /**
- * Adds an Array to the end of this instance of Thaw and readies Thaw to process it
- */
- Thaw.prototype.addArray = function (items) {
- this.items = this.items.concat(items);
- if (this.makeReady()) {
- this.tick();
- }
- return this;
- };
- /**
- * Inserts an Array just after the current item being processed in Thaw and readies Thaw to process them
- */
- Thaw.prototype.insertArray = function (items) {
- var before = this.items.splice(0, this.i);
- var after = this.items;
- this.items = before.concat(items, after);
- if (this.makeReady()) {
- this.tick();
- }
- return this;
- };
- /**
- * Stops this instance of Thaw
- */
- Thaw.prototype.stop = function () {
- this.isStopped = true;
- clearTimeout(this.timeout);
- if (this.options.done) {
- this.options.done();
- }
- return this;
- };
- Thaw.thawing = false;
- Thaw.thaws = [];
- Thaw.defaultSettings = {
- each: null,
- done: null
- };
- return Thaw;
- }());
- exports.Thaw = Thaw;
- /**
- * simple thaw
- */
- function thaw(items, options) {
- return new Thaw(items, options);
- }
- exports.thaw = thaw;
- var Block = /** @class */ (function () {
- function Block(options, count) {
- if (count === void 0) { count = 200; }
- this.index = 0;
- this.thaws = [];
- this.count = count;
- this.options = options;
- }
- /**
- * add an item to the end of items
- */
- Block.prototype.add = function (item) {
- var next = this.next();
- next.add(item);
- return this;
- };
- /**
- * add an Array to the end of items
- */
- Block.prototype.addArray = function (items) {
- var next = this.next();
- next.addArray(items);
- return this;
- };
- /**
- * insert an item into items @ current position
- */
- Block.prototype.insert = function (item) {
- var next = this.next();
- next.insert(item);
- return this;
- };
- /**
- * insert and array into items @ current position
- */
- Block.prototype.insertArray = function (items) {
- var next = this.next();
- next.insertArray(items);
- return this;
- };
- /**
- * Stops all thaws in this block
- */
- Block.prototype.stop = function () {
- for (var i = 0; i < this.thaws.length; i++) {
- this.thaws[i].stop();
- }
- return this;
- };
- /**
- * Get next available in block
- */
- Block.prototype.next = function () {
- var thaw;
- var thaws = this.thaws;
- if (thaws.length < this.count) {
- thaw = new Thaw([], this.options);
- thaws.push(thaw);
- }
- else {
- thaw = thaws[this.index] || null;
- }
- this.index++;
- if (this.index >= this.count) {
- this.index = 0;
- }
- return thaw;
- };
- return Block;
- }());
- exports.Block = Block;
- if (typeof window !== 'undefined') {
- // @ts-ignore
- window.Thaw = Thaw;
- // @ts-ignore
- window.thaw = thaw;
- // @ts-ignore
- window.Thaw.Block = Block;
- }
- });
- function arraysToFloat32Arrays(arrays) {
- const result = [];
- for (let i = 0; i < arrays.length; i++) {
- result.push(Float32Array.from(arrays[i]));
- }
- return result;
- }
- function inputOutputArraysToFloat32Arrays(input, output) {
- const result = [];
- for (let i = 0; i < input.length; i++) {
- result.push(Float32Array.from(input[i]));
- }
- for (let i = 0; i < output.length; i++) {
- result.push(Float32Array.from(output[i]));
- }
- return result;
- }
- function arrayToFloat32Arrays(array) {
- const result = [];
- for (let i = 0; i < array.length; i++) {
- result.push(Float32Array.from([array[i]]));
- }
- return result;
- }
- function inputOutputArrayToFloat32Arrays(input, output) {
- const result = [];
- for (let i = 0; i < input.length; i++) {
- result.push(Float32Array.from([input[i]]));
- }
- for (let i = 0; i < output.length; i++) {
- result.push(Float32Array.from([output[i]]));
- }
- return result;
- }
- function arrayToFloat32Array(array) {
- return Float32Array.from(array);
- }
- function inputOutputObjectsToFloat32Arrays(input, output, inputTable, outputTable, inputLength, outputLength) {
- const results = [];
- for (let i = 0; i < input.length; i++) {
- const object = input[i];
- const result = new Float32Array(inputLength);
- for (const p in object) {
- if (object.hasOwnProperty(p)) {
- result[inputTable[p]] = object[p];
- }
- }
- results.push(result);
- }
- for (let i = 0; i < output.length; i++) {
- const object = output[i];
- const result = new Float32Array(outputLength);
- for (const p in object) {
- if (object.hasOwnProperty(p)) {
- result[outputTable[p]] = object[p];
- }
- }
- results.push(result);
- }
- return results;
- }
- function objectToFloat32Arrays(object) {
- const result = [];
- for (const p in object) {
- if (!object.hasOwnProperty(p))
- continue;
- result.push(Float32Array.from([object[p]]));
- }
- return result;
- }
- function inputOutputObjectToFloat32Arrays(input, output) {
- const result = [];
- for (const p in input) {
- if (!input.hasOwnProperty(p))
- continue;
- result.push(Float32Array.from([input[p]]));
- }
- for (const p in output) {
- if (!output.hasOwnProperty(p))
- continue;
- result.push(Float32Array.from([output[p]]));
- }
- return result;
- }
- function objectToFloat32Array(object, table, length) {
- const result = new Float32Array(length);
- for (const p in object) {
- if (object.hasOwnProperty(p)) {
- result[table[p]] = object[p];
- }
- }
- return result;
- }
- function max(values) {
- if (Array.isArray(values) || values instanceof Float32Array) {
- return Math.max(...values);
- }
- else {
- return Math.max(...Object.values(values));
- }
- }
- function mse$1(errors) {
- // mean squared error
- let sum = 0;
- for (let i = 0; i < errors.length; i++) {
- sum += errors[i] ** 2;
- }
- return sum / errors.length;
- }
- function getTypedArrayFn(value, table) {
- if (value.buffer instanceof ArrayBuffer) {
- return null;
- }
- if (Array.isArray(value)) {
- return arrayToFloat32Array;
- }
- if (!table)
- throw new Error('table is not Object');
- const { length } = Object.keys(table);
- return (v) => {
- const array = new Float32Array(length);
- for (const p in table) {
- if (!table.hasOwnProperty(p))
- continue;
- array[table[p]] = v[p] || 0;
- }
- return array;
- };
- }
- function defaults$2() {
- return {
- inputSize: 0,
- outputSize: 0,
- binaryThresh: 0.5,
- };
- }
- function trainDefaults$2() {
- return {
- activation: 'sigmoid',
- iterations: 20000,
- errorThresh: 0.005,
- log: false,
- logPeriod: 10,
- leakyReluAlpha: 0.01,
- learningRate: 0.3,
- momentum: 0.1,
- callbackPeriod: 10,
- timeout: Infinity,
- beta1: 0.9,
- beta2: 0.999,
- epsilon: 1e-8,
- };
- }
- class NeuralNetwork {
- constructor(options = {}) {
- this.options = defaults$2();
- this.trainOpts = trainDefaults$2();
- this.sizes = [];
- this.outputLayer = -1;
- this.biases = [];
- this.weights = []; // weights for bias nodes
- this.outputs = [];
- // state for training
- this.deltas = [];
- this.changes = []; // for momentum
- this.errors = [];
- this.errorCheckInterval = 1;
- this.inputLookup = null;
- this.inputLookupLength = 0;
- this.outputLookup = null;
- this.outputLookupLength = 0;
- this._formatInput = null;
- this._formatOutput = null;
- this.runInput = (input) => {
- this.setActivation();
- return this.runInput(input);
- };
- this.calculateDeltas = (output) => {
- this.setActivation();
- return this.calculateDeltas(output);
- };
- // adam
- this.biasChangesLow = [];
- this.biasChangesHigh = [];
- this.changesLow = [];
- this.changesHigh = [];
- this.iterations = 0;
- this.options = { ...this.options, ...options };
- this.updateTrainingOptions(options);
- const { inputSize, hiddenLayers, outputSize } = this.options;
- if (inputSize && outputSize) {
- this.sizes = [inputSize].concat(hiddenLayers !== null && hiddenLayers !== void 0 ? hiddenLayers : []).concat([outputSize]);
- }
- }
- /**
- *
- * Expects this.sizes to have been set
- */
- initialize() {
- if (!this.sizes.length) {
- throw new Error('Sizes must be set before initializing');
- }
- this.outputLayer = this.sizes.length - 1;
- this.biases = new Array(this.outputLayer); // weights for bias nodes
- this.weights = new Array(this.outputLayer);
- this.outputs = new Array(this.outputLayer);
- // state for training
- this.deltas = new Array(this.outputLayer);
- this.changes = new Array(this.outputLayer); // for momentum
- this.errors = new Array(this.outputLayer);
- for (let layerIndex = 0; layerIndex <= this.outputLayer; layerIndex++) {
- const size = this.sizes[layerIndex];
- this.deltas[layerIndex] = zeros$1(size);
- this.errors[layerIndex] = zeros$1(size);
- this.outputs[layerIndex] = zeros$1(size);
- if (layerIndex > 0) {
- this.biases[layerIndex] = randos(size);
- this.weights[layerIndex] = new Array(size);
- this.changes[layerIndex] = new Array(size);
- for (let nodeIndex = 0; nodeIndex < size; nodeIndex++) {
- const prevSize = this.sizes[layerIndex - 1];
- this.weights[layerIndex][nodeIndex] = randos(prevSize);
- this.changes[layerIndex][nodeIndex] = zeros$1(prevSize);
- }
- }
- }
- this.setActivation();
- if (this.trainOpts.praxis === 'adam') {
- this._setupAdam();
- }
- }
- setActivation(activation) {
- const value = activation !== null && activation !== void 0 ? activation : this.trainOpts.activation;
- switch (value) {
- case 'sigmoid':
- this.runInput = this._runInputSigmoid;
- this.calculateDeltas = this._calculateDeltasSigmoid;
- break;
- case 'relu':
- this.runInput = this._runInputRelu;
- this.calculateDeltas = this._calculateDeltasRelu;
- break;
- case 'leaky-relu':
- this.runInput = this._runInputLeakyRelu;
- this.calculateDeltas = this._calculateDeltasLeakyRelu;
- break;
- case 'tanh':
- this.runInput = this._runInputTanh;
- this.calculateDeltas = this._calculateDeltasTanh;
- break;
- default:
- throw new Error(`Unknown activation ${value}. Available activations are: 'sigmoid', 'relu', 'leaky-relu', 'tanh'`);
- }
- }
- get isRunnable() {
- return this.sizes.length > 0;
- }
- run(input) {
- if (!this.isRunnable) {
- throw new Error('network not runnable');
- }
- let formattedInput;
- if (this.inputLookup) {
- formattedInput = lookup.toArray(this.inputLookup, input, this.inputLookupLength);
- }
- else {
- formattedInput = input;
- }
- if (formattedInput.length !== this.sizes[0]) {
- throw new Error(`input is not in correct length of ${this.sizes[0]}`);
- }
- const output = this.runInput(formattedInput).slice(0);
- if (this.outputLookup) {
- return lookup.toObject(this.outputLookup, output);
- }
- return output;
- }
- _runInputSigmoid(input) {
- this.outputs[0] = input; // set output state of input layer
- let output = null;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const activeLayer = this.sizes[layer];
- const activeWeights = this.weights[layer];
- const activeBiases = this.biases[layer];
- const activeOutputs = this.outputs[layer];
- for (let node = 0; node < activeLayer; node++) {
- const weights = activeWeights[node];
- let sum = activeBiases[node];
- for (let k = 0; k < weights.length; k++) {
- sum += weights[k] * input[k];
- }
- // sigmoid
- activeOutputs[node] = 1 / (1 + Math.exp(-sum));
- }
- output = input = activeOutputs;
- }
- if (!output) {
- throw new Error('output was empty');
- }
- return output;
- }
- _runInputRelu(input) {
- this.outputs[0] = input; // set output state of input layer
- let output = null;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const activeSize = this.sizes[layer];
- const activeWeights = this.weights[layer];
- const activeBiases = this.biases[layer];
- const activeOutputs = this.outputs[layer];
- for (let node = 0; node < activeSize; node++) {
- const weights = activeWeights[node];
- let sum = activeBiases[node];
- for (let k = 0; k < weights.length; k++) {
- sum += weights[k] * input[k];
- }
- // relu
- activeOutputs[node] = sum < 0 ? 0 : sum;
- }
- output = input = activeOutputs;
- }
- if (!output) {
- throw new Error('output was empty');
- }
- return output;
- }
- _runInputLeakyRelu(input) {
- this.outputs[0] = input; // set output state of input layer
- const { leakyReluAlpha } = this.trainOpts;
- let output = null;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const activeSize = this.sizes[layer];
- const activeWeights = this.weights[layer];
- const activeBiases = this.biases[layer];
- const activeOutputs = this.outputs[layer];
- for (let node = 0; node < activeSize; node++) {
- const weights = activeWeights[node];
- let sum = activeBiases[node];
- for (let k = 0; k < weights.length; k++) {
- sum += weights[k] * input[k];
- }
- // leaky relu
- activeOutputs[node] = Math.max(sum, leakyReluAlpha * sum);
- }
- output = input = activeOutputs;
- }
- if (!output) {
- throw new Error('output was empty');
- }
- return output;
- }
- _runInputTanh(input) {
- this.outputs[0] = input; // set output state of input layer
- let output = null;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const activeSize = this.sizes[layer];
- const activeWeights = this.weights[layer];
- const activeBiases = this.biases[layer];
- const activeOutputs = this.outputs[layer];
- for (let node = 0; node < activeSize; node++) {
- const weights = activeWeights[node];
- let sum = activeBiases[node];
- for (let k = 0; k < weights.length; k++) {
- sum += weights[k] * input[k];
- }
- // tanh
- activeOutputs[node] = Math.tanh(sum);
- }
- output = input = activeOutputs;
- }
- if (!output) {
- throw new Error('output was empty');
- }
- return output;
- }
- /**
- *
- * Verifies network sizes are initialized
- * If they are not it will initialize them based off the data set.
- */
- verifyIsInitialized(preparedData) {
- if (this.sizes.length)
- return;
- this.sizes = [];
- this.sizes.push(preparedData[0].input.length);
- if (!this.options.hiddenLayers) {
- this.sizes.push(Math.max(3, Math.floor(preparedData[0].input.length / 2)));
- }
- else {
- this.options.hiddenLayers.forEach((size) => {
- this.sizes.push(size);
- });
- }
- this.sizes.push(preparedData[0].output.length);
- this.initialize();
- }
- updateTrainingOptions(trainOpts) {
- const merged = { ...this.trainOpts, ...trainOpts };
- this.validateTrainingOptions(merged);
- this.trainOpts = merged;
- this.setLogMethod(this.trainOpts.log);
- }
- validateTrainingOptions(options) {
- const validations = {
- activation: () => {
- return ['sigmoid', 'relu', 'leaky-relu', 'tanh'].includes(options.activation);
- },
- iterations: () => {
- const val = options.iterations;
- return typeof val === 'number' && val > 0;
- },
- errorThresh: () => {
- const val = options.errorThresh;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- log: () => {
- const val = options.log;
- return typeof val === 'function' || typeof val === 'boolean';
- },
- logPeriod: () => {
- const val = options.logPeriod;
- return typeof val === 'number' && val > 0;
- },
- leakyReluAlpha: () => {
- const val = options.leakyReluAlpha;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- learningRate: () => {
- const val = options.learningRate;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- momentum: () => {
- const val = options.momentum;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- callback: () => {
- const val = options.callback;
- return typeof val === 'function' || val === undefined;
- },
- callbackPeriod: () => {
- const val = options.callbackPeriod;
- return typeof val === 'number' && val > 0;
- },
- timeout: () => {
- const val = options.timeout;
- return typeof val === 'number' && val > 0;
- },
- praxis: () => {
- const val = options.praxis;
- return !val || val === 'adam';
- },
- beta1: () => {
- const val = options.beta1;
- return val > 0 && val < 1;
- },
- beta2: () => {
- const val = options.beta2;
- return val > 0 && val < 1;
- },
- epsilon: () => {
- const val = options.epsilon;
- return val > 0 && val < 1;
- },
- };
- for (const p in validations) {
- const v = options;
- if (!validations[p]()) {
- throw new Error(`[${p}, ${v[p]}] is out of normal training range, your network will probably not train.`);
- }
- }
- }
- /**
- *
- * Gets JSON of trainOpts object
- * NOTE: Activation is stored directly on JSON object and not in the training options
- */
- getTrainOptsJSON() {
- const { activation, iterations, errorThresh, log, logPeriod, leakyReluAlpha, learningRate, momentum, callbackPeriod, timeout, praxis, beta1, beta2, epsilon, } = this.trainOpts;
- return {
- activation,
- iterations,
- errorThresh,
- log: typeof log === 'function'
- ? true
- : typeof log === 'boolean'
- ? log
- : false,
- logPeriod,
- leakyReluAlpha,
- learningRate,
- momentum,
- callbackPeriod,
- timeout: timeout === Infinity ? 'Infinity' : timeout,
- praxis,
- beta1,
- beta2,
- epsilon,
- };
- }
- setLogMethod(log) {
- if (typeof log === 'function') {
- this.trainOpts.log = log;
- }
- else if (log) {
- this.trainOpts.log = this.logTrainingStatus;
- }
- else {
- this.trainOpts.log = false;
- }
- }
- logTrainingStatus(status) {
- console.log(`iterations: ${status.iterations}, training error: ${status.error}`);
- }
- calculateTrainingError(data) {
- let sum = 0;
- for (let i = 0; i < data.length; ++i) {
- sum += this.trainPattern(data[i], true);
- }
- return sum / data.length;
- }
- trainPatterns(data) {
- for (let i = 0; i < data.length; ++i) {
- this.trainPattern(data[i]);
- }
- }
- trainingTick(data, status, endTime) {
- const { callback, callbackPeriod, errorThresh, iterations, log, logPeriod, } = this.trainOpts;
- if (status.iterations >= iterations ||
- status.error <= errorThresh ||
- Date.now() >= endTime) {
- return false;
- }
- status.iterations++;
- if (log && status.iterations % logPeriod === 0) {
- status.error = this.calculateTrainingError(data);
- log(status);
- }
- else if (status.iterations % this.errorCheckInterval === 0) {
- status.error = this.calculateTrainingError(data);
- }
- else {
- this.trainPatterns(data);
- }
- if (callback && status.iterations % callbackPeriod === 0) {
- callback({
- iterations: status.iterations,
- error: status.error,
- });
- }
- return true;
- }
- prepTraining(data, options = {}) {
- this.updateTrainingOptions(options);
- const preparedData = this.formatData(data);
- const endTime = Date.now() + this.trainOpts.timeout;
- const status = {
- error: 1,
- iterations: 0,
- };
- this.verifyIsInitialized(preparedData);
- return {
- preparedData,
- status,
- endTime,
- };
- }
- train(data, options = {}) {
- const { preparedData, status, endTime } = this.prepTraining(data, options);
- while (true) {
- if (!this.trainingTick(preparedData, status, endTime)) {
- break;
- }
- }
- return status;
- }
- async trainAsync(data, options = {}) {
- const { preparedData, status, endTime } = this.prepTraining(data, options);
- return await new Promise((resolve, reject) => {
- try {
- const thawedTrain = new browser.Thaw(new Array(this.trainOpts.iterations), {
- delay: true,
- each: () => this.trainingTick(preparedData, status, endTime) ||
- thawedTrain.stop(),
- done: () => resolve(status),
- });
- thawedTrain.tick();
- }
- catch (trainError) {
- reject(trainError);
- }
- });
- }
- trainPattern(value, logErrorRate) {
- // forward propagate
- this.runInput(value.input);
- // back propagate
- this.calculateDeltas(value.output);
- this.adjustWeights();
- if (logErrorRate) {
- return mse$1(this.errors[this.outputLayer]);
- }
- return null;
- }
- _calculateDeltasSigmoid(target) {
- for (let layer = this.outputLayer; layer >= 0; layer--) {
- const activeSize = this.sizes[layer];
- const activeOutput = this.outputs[layer];
- const activeError = this.errors[layer];
- const activeDeltas = this.deltas[layer];
- const nextLayer = this.weights[layer + 1];
- for (let node = 0; node < activeSize; node++) {
- const output = activeOutput[node];
- let error = 0;
- if (layer === this.outputLayer) {
- error = target[node] - output;
- }
- else {
- const deltas = this.deltas[layer + 1];
- for (let k = 0; k < deltas.length; k++) {
- error += deltas[k] * nextLayer[k][node];
- }
- }
- activeError[node] = error;
- activeDeltas[node] = error * output * (1 - output);
- }
- }
- }
- _calculateDeltasRelu(target) {
- for (let layer = this.outputLayer; layer >= 0; layer--) {
- const currentSize = this.sizes[layer];
- const currentOutputs = this.outputs[layer];
- const nextWeights = this.weights[layer + 1];
- const nextDeltas = this.deltas[layer + 1];
- const currentErrors = this.errors[layer];
- const currentDeltas = this.deltas[layer];
- for (let node = 0; node < currentSize; node++) {
- const output = currentOutputs[node];
- let error = 0;
- if (layer === this.outputLayer) {
- error = target[node] - output;
- }
- else {
- for (let k = 0; k < nextDeltas.length; k++) {
- error += nextDeltas[k] * nextWeights[k][node];
- }
- }
- currentErrors[node] = error;
- currentDeltas[node] = output > 0 ? error : 0;
- }
- }
- }
- _calculateDeltasLeakyRelu(target) {
- const alpha = this.trainOpts.leakyReluAlpha;
- for (let layer = this.outputLayer; layer >= 0; layer--) {
- const currentSize = this.sizes[layer];
- const currentOutputs = this.outputs[layer];
- const nextDeltas = this.deltas[layer + 1];
- const nextWeights = this.weights[layer + 1];
- const currentErrors = this.errors[layer];
- const currentDeltas = this.deltas[layer];
- for (let node = 0; node < currentSize; node++) {
- const output = currentOutputs[node];
- let error = 0;
- if (layer === this.outputLayer) {
- error = target[node] - output;
- }
- else {
- for (let k = 0; k < nextDeltas.length; k++) {
- error += nextDeltas[k] * nextWeights[k][node];
- }
- }
- currentErrors[node] = error;
- currentDeltas[node] = output > 0 ? error : alpha * error;
- }
- }
- }
- _calculateDeltasTanh(target) {
- for (let layer = this.outputLayer; layer >= 0; layer--) {
- const currentSize = this.sizes[layer];
- const currentOutputs = this.outputs[layer];
- const nextDeltas = this.deltas[layer + 1];
- const nextWeights = this.weights[layer + 1];
- const currentErrors = this.errors[layer];
- const currentDeltas = this.deltas[layer];
- for (let node = 0; node < currentSize; node++) {
- const output = currentOutputs[node];
- let error = 0;
- if (layer === this.outputLayer) {
- error = target[node] - output;
- }
- else {
- for (let k = 0; k < nextDeltas.length; k++) {
- error += nextDeltas[k] * nextWeights[k][node];
- }
- }
- currentErrors[node] = error;
- currentDeltas[node] = (1 - output * output) * error;
- }
- }
- }
- /**
- *
- * Changes weights of networks
- */
- adjustWeights() {
- const { learningRate, momentum } = this.trainOpts;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const incoming = this.outputs[layer - 1];
- const activeSize = this.sizes[layer];
- const activeDelta = this.deltas[layer];
- const activeChanges = this.changes[layer];
- const activeWeights = this.weights[layer];
- const activeBiases = this.biases[layer];
- for (let node = 0; node < activeSize; node++) {
- const delta = activeDelta[node];
- for (let k = 0; k < incoming.length; k++) {
- let change = activeChanges[node][k];
- change = learningRate * delta * incoming[k] + momentum * change;
- activeChanges[node][k] = change;
- activeWeights[node][k] += change;
- }
- activeBiases[node] += learningRate * delta;
- }
- }
- }
- _setupAdam() {
- this.biasChangesLow = [];
- this.biasChangesHigh = [];
- this.changesLow = [];
- this.changesHigh = [];
- this.iterations = 0;
- for (let layer = 0; layer <= this.outputLayer; layer++) {
- const size = this.sizes[layer];
- if (layer > 0) {
- this.biasChangesLow[layer] = zeros$1(size);
- this.biasChangesHigh[layer] = zeros$1(size);
- this.changesLow[layer] = new Array(size);
- this.changesHigh[layer] = new Array(size);
- for (let node = 0; node < size; node++) {
- const prevSize = this.sizes[layer - 1];
- this.changesLow[layer][node] = zeros$1(prevSize);
- this.changesHigh[layer][node] = zeros$1(prevSize);
- }
- }
- }
- this.adjustWeights = this._adjustWeightsAdam;
- }
- _adjustWeightsAdam() {
- this.iterations++;
- const { iterations } = this;
- const { beta1, beta2, epsilon, learningRate } = this.trainOpts;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const incoming = this.outputs[layer - 1];
- const currentSize = this.sizes[layer];
- const currentDeltas = this.deltas[layer];
- const currentChangesLow = this.changesLow[layer];
- const currentChangesHigh = this.changesHigh[layer];
- const currentWeights = this.weights[layer];
- const currentBiases = this.biases[layer];
- const currentBiasChangesLow = this.biasChangesLow[layer];
- const currentBiasChangesHigh = this.biasChangesHigh[layer];
- for (let node = 0; node < currentSize; node++) {
- const delta = currentDeltas[node];
- for (let k = 0; k < incoming.length; k++) {
- const gradient = delta * incoming[k];
- const changeLow = currentChangesLow[node][k] * beta1 + (1 - beta1) * gradient;
- const changeHigh = currentChangesHigh[node][k] * beta2 +
- (1 - beta2) * gradient * gradient;
- const momentumCorrection = changeLow / (1 - Math.pow(beta1, iterations));
- const gradientCorrection = changeHigh / (1 - Math.pow(beta2, iterations));
- currentChangesLow[node][k] = changeLow;
- currentChangesHigh[node][k] = changeHigh;
- currentWeights[node][k] +=
- (learningRate * momentumCorrection) /
- (Math.sqrt(gradientCorrection) + epsilon);
- }
- const biasGradient = currentDeltas[node];
- const biasChangeLow = currentBiasChangesLow[node] * beta1 + (1 - beta1) * biasGradient;
- const biasChangeHigh = currentBiasChangesHigh[node] * beta2 +
- (1 - beta2) * biasGradient * biasGradient;
- const biasMomentumCorrection = currentBiasChangesLow[node] / (1 - Math.pow(beta1, iterations));
- const biasGradientCorrection = currentBiasChangesHigh[node] / (1 - Math.pow(beta2, iterations));
- currentBiasChangesLow[node] = biasChangeLow;
- currentBiasChangesHigh[node] = biasChangeHigh;
- currentBiases[node] +=
- (learningRate * biasMomentumCorrection) /
- (Math.sqrt(biasGradientCorrection) + epsilon);
- }
- }
- }
- formatData(data) {
- if (!Array.isArray(data[0].input)) {
- if (this.inputLookup) {
- this.inputLookupLength = Object.keys(this.inputLookup).length;
- }
- else {
- const inputLookup = new LookupTable(data, 'input');
- this.inputLookup = inputLookup.table;
- this.inputLookupLength = inputLookup.length;
- }
- }
- if (!Array.isArray(data[0].output)) {
- if (this.outputLookup) {
- this.outputLookupLength = Object.keys(this.outputLookup).length;
- }
- else {
- const lookup = new LookupTable(data, 'output');
- this.outputLookup = lookup.table;
- this.outputLookupLength = lookup.length;
- }
- }
- if (!this._formatInput) {
- this._formatInput = getTypedArrayFn(data[0].input, this.inputLookup);
- }
- if (!this._formatOutput) {
- this._formatOutput = getTypedArrayFn(data[0].output, this.outputLookup);
- }
- // turn sparse hash input into arrays with 0s as filler
- if (this._formatInput && this._formatOutput) {
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push({
- input: this._formatInput(data[i].input),
- output: this._formatOutput(data[i].output),
- });
- }
- return result;
- }
- if (this._formatInput) {
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push({
- input: this._formatInput(data[i].input),
- output: data[i].output,
- });
- }
- return result;
- }
- if (this._formatOutput) {
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push({
- input: data[i].input,
- output: this._formatOutput(data[i].output),
- });
- }
- return result;
- }
- return data;
- }
- addFormat(data) {
- var _a, _b;
- if (!Array.isArray(data.input) || typeof data.input[0] !== 'number') {
- this.inputLookup = lookup.addKeys(data.input, (_a = this.inputLookup) !== null && _a !== void 0 ? _a : {});
- if (this.inputLookup) {
- this.inputLookupLength = Object.keys(this.inputLookup).length;
- }
- }
- if (!Array.isArray(data.output) || typeof data.output[0] !== 'number') {
- this.outputLookup = lookup.addKeys(data.output, (_b = this.outputLookup) !== null && _b !== void 0 ? _b : {});
- if (this.outputLookup) {
- this.outputLookupLength = Object.keys(this.outputLookup).length;
- }
- }
- }
- test(data) {
- const { preparedData } = this.prepTraining(data);
- // for binary classification problems with one output node
- const isBinary = preparedData[0].output.length === 1;
- // for classification problems
- const misclasses = [];
- // run each pattern through the trained network and collect
- // error and misclassification statistics
- let errorSum = 0;
- if (isBinary) {
- let falsePos = 0;
- let falseNeg = 0;
- let truePos = 0;
- let trueNeg = 0;
- for (let i = 0; i < preparedData.length; i++) {
- const output = this.runInput(preparedData[i].input);
- const target = preparedData[i].output;
- const actual = output[0] > this.options.binaryThresh ? 1 : 0;
- const expected = target[0];
- if (actual !== expected) {
- const misclass = preparedData[i];
- misclasses.push({
- input: misclass.input,
- output: misclass.output,
- actual,
- expected,
- });
- }
- if (actual === 0 && expected === 0) {
- trueNeg++;
- }
- else if (actual === 1 && expected === 1) {
- truePos++;
- }
- else if (actual === 0 && expected === 1) {
- falseNeg++;
- }
- else if (actual === 1 && expected === 0) {
- falsePos++;
- }
- errorSum += mse$1(output.map((value, i) => {
- return target[i] - value;
- }));
- }
- return {
- error: errorSum / preparedData.length,
- misclasses,
- total: preparedData.length,
- trueNeg,
- truePos,
- falseNeg,
- falsePos,
- precision: truePos > 0 ? truePos / (truePos + falsePos) : 0,
- recall: truePos > 0 ? truePos / (truePos + falseNeg) : 0,
- accuracy: (trueNeg + truePos) / preparedData.length,
- };
- }
- for (let i = 0; i < preparedData.length; i++) {
- const output = this.runInput(preparedData[i].input);
- const target = preparedData[i].output;
- const actual = output.indexOf(max(output));
- const expected = target.indexOf(max(target));
- if (actual !== expected) {
- const misclass = preparedData[i];
- misclasses.push({
- input: misclass.input,
- output: misclass.output,
- actual,
- expected,
- });
- }
- errorSum += mse$1(output.map((value, i) => {
- return target[i] - value;
- }));
- }
- return {
- error: errorSum / preparedData.length,
- misclasses,
- total: preparedData.length,
- };
- }
- toJSON() {
- var _a, _b;
- if (!this.isRunnable) {
- this.initialize();
- }
- // use Array.from, keeping json small
- const jsonLayerWeights = this.weights.map((layerWeights) => {
- return layerWeights.map((layerWeights) => Array.from(layerWeights));
- });
- const jsonLayerBiases = this.biases.map((layerBiases) => Array.from(layerBiases));
- const jsonLayers = [];
- const outputLength = this.sizes.length - 1;
- for (let i = 0; i <= outputLength; i++) {
- jsonLayers.push({
- weights: (_a = jsonLayerWeights[i]) !== null && _a !== void 0 ? _a : [],
- biases: (_b = jsonLayerBiases[i]) !== null && _b !== void 0 ? _b : [],
- });
- }
- return {
- type: 'NeuralNetwork',
- sizes: [...this.sizes],
- layers: jsonLayers,
- inputLookup: this.inputLookup ? { ...this.inputLookup } : null,
- inputLookupLength: this.inputLookupLength,
- outputLookup: this.outputLookup ? { ...this.outputLookup } : null,
- outputLookupLength: this.outputLookupLength,
- options: { ...this.options },
- trainOpts: this.getTrainOptsJSON(),
- };
- }
- fromJSON(json) {
- this.options = { ...defaults$2(), ...json.options };
- if (json.hasOwnProperty('trainOpts')) {
- const trainOpts = {
- ...json.trainOpts,
- timeout: json.trainOpts.timeout === 'Infinity'
- ? Infinity
- : json.trainOpts.timeout,
- };
- this.updateTrainingOptions(trainOpts);
- }
- this.sizes = json.sizes;
- this.initialize();
- this.inputLookup = json.inputLookup ? { ...json.inputLookup } : null;
- this.inputLookupLength = json.inputLookupLength;
- this.outputLookup = json.outputLookup ? { ...json.outputLookup } : null;
- this.outputLookupLength = json.outputLookupLength;
- const jsonLayers = json.layers;
- const layerWeights = this.weights.map((layerWeights, layerIndex) => {
- return jsonLayers[layerIndex].weights.map((layerWeights) => Float32Array.from(layerWeights));
- });
- const layerBiases = this.biases.map((layerBiases, layerIndex) => Float32Array.from(jsonLayers[layerIndex].biases));
- for (let i = 0; i <= this.outputLayer; i++) {
- this.weights[i] = layerWeights[i] || [];
- this.biases[i] = layerBiases[i] || [];
- }
- return this;
- }
- toFunction(cb) {
- const { activation, leakyReluAlpha } = this.trainOpts;
- let needsVar = false;
- const nodeHandle = (layerIndex, nodeIndex) => {
- if (layerIndex === 0) {
- return `(input[${nodeIndex}]||0)`;
- }
- const weights = this.weights[layerIndex][nodeIndex];
- const bias = this.biases[layerIndex][nodeIndex];
- if (!weights) {
- throw new Error(`weights at layerIndex ${layerIndex} & nodeIndex ${nodeIndex} not found`);
- }
- if (!bias) {
- throw new Error(`bias as layerIndex ${layerIndex} & nodeIndex ${nodeIndex} not found`);
- }
- const weightsArray = [];
- weights.forEach((weight, subNodeIndex) => {
- if (weight < 0) {
- weightsArray.push(`${weight}*${nodeHandle(layerIndex - 1, subNodeIndex)}`);
- }
- else {
- weightsArray.push(`+${weight}*${nodeHandle(layerIndex - 1, subNodeIndex)}`);
- }
- });
- const result = `(${bias.toString()}${weightsArray.join('')})`;
- switch (activation) {
- case 'sigmoid':
- return `1/(1+1/Math.exp(${result}))`;
- case 'relu': {
- needsVar = true;
- return `((v=${result})<0?0:v)`;
- }
- case 'leaky-relu': {
- needsVar = true;
- return `Math.max((v=${result}),${leakyReluAlpha}*v)`;
- }
- case 'tanh':
- return `Math.tanh(${result})`;
- default:
- throw new Error(`Unknown activation ${activation}. Available activations are: 'sigmoid', 'relu', 'leaky-relu', 'tanh'`);
- }
- };
- function checkKeys(keys) {
- if (keys.find((v) => v.includes('"'))) {
- throw new Error(`key contains '"', which is not compatible`);
- }
- }
- const layersAsMath = [];
- let result;
- let inputLookup = '';
- if (this.inputLookup) {
- const keys = Object.keys(this.inputLookup);
- checkKeys(keys);
- inputLookup = `input = new Float32Array([${Object.keys(this.inputLookup)
- .map((key) => `input["${key}"]`)
- .join(',')}]);`;
- }
- if (this.sizes.length < 1)
- throw new Error('No layers');
- for (let nodeIndex = 0; nodeIndex < this.sizes[this.outputLayer]; nodeIndex++) {
- layersAsMath.push(nodeHandle(this.outputLayer, nodeIndex));
- }
- if (this.outputLookup) {
- const keys = Object.keys(this.outputLookup);
- checkKeys(keys);
- const values = keys
- .map((key, i) => `"${key}":${layersAsMath[i]}`)
- .join(',');
- result = `{${values}}`;
- }
- else {
- result = `[${layersAsMath.join(',')}]`;
- }
- const source = `${inputLookup}${needsVar ? 'var v;' : ''}return ${result};`;
- // eslint-disable-next-line @typescript-eslint/no-implied-eval,no-new-func
- return new Function('input', cb ? cb(source) : source);
- }
- }
- function weightedSumSigmoid(weights, biases, inputs) {
- let sum = biases[this.thread.x];
- for (let k = 0; k < this.constants.size; k++) {
- sum += weights[this.thread.x][k] * inputs[k];
- }
- // sigmoid
- return 1 / (1 + Math.exp(-sum));
- }
- function weightedSumRelu(weights, biases, inputs) {
- let sum = biases[this.thread.x];
- for (let k = 0; k < this.constants.size; k++) {
- sum += weights[this.thread.x][k] * inputs[k];
- }
- // relu
- return sum < 0 ? 0 : sum;
- }
- function weightedSumLeakyRelu(weights, biases, inputs) {
- let sum = biases[this.thread.x];
- for (let k = 0; k < this.constants.size; k++) {
- sum += weights[this.thread.x][k] * inputs[k];
- }
- // leaky relu
- return sum < 0 ? 0 : 0.01 * sum;
- }
- function weightedSumTanh(weights, biases, inputs) {
- let sum = biases[this.thread.x];
- for (let k = 0; k < this.constants.size; k++) {
- sum += weights[this.thread.x][k] * inputs[k];
- }
- // tanh
- return Math.tanh(sum);
- }
- function calcErrorOutput(output, target) {
- return target - output;
- }
- function calcDeltasSigmoid(error, output) {
- // sigmoid derivative
- return error * output * (1 - output);
- }
- function calcDeltasRelu(error, output) {
- // relu derivative
- return output > 0 ? error : 0;
- }
- function calcDeltasLeakyRelu(error, output) {
- // leaky relu derivative
- return output > 0 ? error : 0.01 * error;
- }
- function calcDeltasTanh(error, output) {
- // tanh derivative
- return (1 - output * output) * error;
- }
- function calcError(x, size, nextWeights, nextDeltas) {
- let error = 0;
- for (let k = 0; k < size; k++) {
- error += nextDeltas[k] * nextWeights[k][x];
- }
- return error;
- }
- function calcChanges(learningRate, momentum, previousChange, delta, previousOutput) {
- return learningRate * delta * previousOutput + momentum * previousChange;
- }
- function addWeights(change, weight) {
- return change + weight;
- }
- function addBiases(biases, deltas) {
- return (biases[this.thread.x] + deltas[this.thread.x] * this.constants.learningRate);
- }
- // mean squared error, reimplemented for GPU
- function mse(errors) {
- let sum = 0;
- for (let i = 0; i < this.constants.size; i++) {
- sum += errors[i] ** 2;
- }
- return sum / this.constants.size;
- }
- class NeuralNetworkGPU extends NeuralNetwork {
- constructor(options = {}) {
- super(options);
- this.texturizeInputData = () => {
- throw new Error('not yet setup');
- };
- this.forwardPropagate = [];
- this.backwardPropagate = [];
- this.changesPropagate = [];
- this.biasesPropagate = [];
- this.getMSE = () => {
- throw new Error('not yet setup');
- };
- this._addMSE = () => {
- throw new Error('not yet setup');
- };
- this._divideMSESum = () => {
- throw new Error('not yet setup');
- };
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.outputs = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.deltas = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.errors = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.weights = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.changes = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.biases = [];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.runInput = (input) => {
- let output;
- this.outputs[0] = input;
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- release(this.outputs[layer]);
- this.outputs[layer] = this.forwardPropagate[layer](this.weights[layer], this.biases[layer], input);
- output = input = this.outputs[layer];
- }
- return output;
- };
- this.calculateDeltas = (target) => {
- for (let layer = this.outputLayer; layer > 0; layer--) {
- release(this.deltas[layer]);
- release(this.errors[layer]);
- let output;
- if (layer === this.outputLayer) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- output = this.backwardPropagate[layer](this.outputs[layer], target);
- }
- else {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- output = this.backwardPropagate[layer](this.weights[layer + 1], this.outputs[layer], this.deltas[layer + 1]);
- }
- this.deltas[layer] = output.result;
- this.errors[layer] = output.error;
- }
- };
- this.errorCheckInterval = 100;
- this.gpu = new gpu_js.GPU({ mode: options.mode });
- }
- initialize() {
- super.initialize();
- this.buildRunInput();
- this.buildCalculateDeltas();
- this.buildGetChanges();
- this.buildChangeBiases();
- this.buildGetMSE();
- }
- setActivation() { }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- trainPattern(value, logErrorRate) {
- // forward propagate
- this.runInput(value.input);
- // back propagate
- this.calculateDeltas(value.output);
- this.adjustWeights();
- if (logErrorRate) {
- return this.getMSE(this.errors[this.outputLayer]);
- }
- return null;
- }
- calculateTrainingError(data) {
- let sum = new Float32Array([0]);
- for (let i = 0; i < data.length; ++i) {
- const prevSum = sum;
- const error = this.trainPattern(data[i], true);
- sum = this._addMSE(sum, error);
- release(error);
- release(prevSum);
- }
- const result = this._divideMSESum(data.length, sum);
- release(sum);
- return (result instanceof gpu_js.Texture
- ? result.toArray()
- : result)[0];
- }
- adjustWeights() {
- this.getChanges();
- this.changeBiases();
- }
- buildRunInput() {
- let weightedSum = null;
- switch (this.trainOpts.activation) {
- case 'sigmoid':
- weightedSum = weightedSumSigmoid;
- break;
- case 'relu':
- weightedSum = weightedSumRelu;
- break;
- case 'leaky-relu':
- weightedSum = weightedSumLeakyRelu;
- break;
- case 'tanh':
- weightedSum = weightedSumTanh;
- break;
- default:
- throw new Error(`Unknown activation ${this.trainOpts.activation}. Available activations are: 'sigmoid', 'relu', 'leaky-relu', 'tanh'`);
- }
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- this.forwardPropagate[layer] = this.gpu.createKernel(weightedSum, {
- output: [this.sizes[layer]],
- pipeline: true,
- constants: {
- size: this.sizes[layer - 1],
- },
- immutable: true,
- });
- }
- this.texturizeInputData = this.gpu.createKernel(function (value) {
- return value[this.thread.x];
- }, {
- output: [this.sizes[1]],
- pipeline: true,
- immutable: true,
- });
- }
- buildCalculateDeltas() {
- let calcDeltas;
- switch (this.trainOpts.activation) {
- case 'sigmoid':
- calcDeltas = calcDeltasSigmoid;
- break;
- case 'relu':
- calcDeltas = calcDeltasRelu;
- break;
- case 'leaky-relu':
- calcDeltas = calcDeltasLeakyRelu;
- break;
- case 'tanh':
- calcDeltas = calcDeltasTanh;
- break;
- default:
- throw new Error(`Unknown activation ${this.trainOpts.activation}. Available activations are: 'sigmoid', 'relu', 'leaky-relu', 'tanh'`);
- }
- calcDeltas = gpu_js.alias(gpu_js.utils.getMinifySafeName(() => calcDeltas), calcDeltas);
- this.gpu.addFunction(calcDeltas);
- for (let layer = this.outputLayer; layer > 0; layer--) {
- if (layer === this.outputLayer) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.backwardPropagate[this.outputLayer] = this.gpu.createKernelMap({
- error: calcErrorOutput,
- }, function (outputs, targets) {
- const output = outputs[this.thread.x];
- const target = targets[this.thread.x];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return calcDeltas(calcErrorOutput(output, target), output);
- }, {
- output: [this.sizes[this.outputLayer]],
- pipeline: true,
- immutable: true,
- });
- }
- else {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.backwardPropagate[layer] = this.gpu.createKernelMap({
- error: calcError,
- }, function (nextWeights, outputs, nextDeltas) {
- const output = outputs[this.thread.x];
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return calcDeltas(calcError(this.thread.x, this.constants.size, nextWeights, nextDeltas), output);
- }, {
- output: [this.sizes[layer]],
- pipeline: true,
- constants: {
- size: this.sizes[layer + 1],
- },
- immutable: true,
- });
- }
- }
- }
- buildGetChanges() {
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.changesPropagate[layer] = this.gpu.createKernelMap({
- weights: addWeights,
- changes: calcChanges,
- }, function (previousOutputs, deltas, weights, previousChanges) {
- const change = calcChanges(this.constants.learningRate, this.constants.momentum, previousChanges[this.thread.y][this.thread.x], deltas[this.thread.y], previousOutputs[this.thread.x]);
- return addWeights(change, weights[this.thread.y][this.thread.x]);
- }, {
- output: [this.sizes[layer - 1], this.sizes[layer]],
- pipeline: true,
- constants: {
- size: this.sizes[layer - 1],
- learningRate: this.trainOpts.learningRate,
- momentum: this.trainOpts.momentum,
- },
- immutable: true,
- });
- }
- }
- getChanges() {
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const weights = this.weights[layer];
- const changes = this.changes[layer];
- const output = this.changesPropagate[layer](this.outputs[layer - 1], this.deltas[layer], weights, changes);
- release(weights);
- release(changes);
- this.weights[layer] = output.weights;
- this.changes[layer] = output.changes;
- release(output.result);
- }
- }
- buildChangeBiases() {
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- this.biasesPropagate[layer] = this.gpu.createKernel(addBiases, {
- output: [this.sizes[layer]],
- pipeline: true,
- constants: {
- learningRate: this.trainOpts.learningRate,
- },
- immutable: true,
- });
- }
- }
- changeBiases() {
- for (let layer = 1; layer <= this.outputLayer; layer++) {
- const biases = this.biases[layer];
- this.biases[layer] = this.biasesPropagate[layer](biases, this.deltas[layer]);
- release(biases);
- }
- }
- buildGetMSE() {
- this.getMSE = this.gpu.createKernel(mse, {
- output: [1],
- constants: {
- size: this.sizes[this.outputLayer],
- },
- pipeline: true,
- immutable: true,
- });
- this._addMSE = this.gpu.createKernel(function (value1, value2) {
- return value1[0] + value2[0];
- }, {
- output: [1],
- pipeline: true,
- immutable: true,
- });
- this._divideMSESum = this.gpu.createKernel(function (length, mseSum) {
- const value = mseSum[0];
- if (value > 0) {
- return value / length;
- }
- return 0;
- }, {
- output: [1],
- });
- }
- run(input) {
- if (!this.isRunnable) {
- throw new Error('network not runnable');
- }
- let formattedInput;
- if (this.inputLookup) {
- formattedInput = lookup.toArray(this.inputLookup, input, this.inputLookupLength);
- }
- else {
- formattedInput = input;
- }
- const outputTextures = this.runInput(formattedInput);
- const output = outputTextures instanceof gpu_js.Texture
- ? outputTextures.toArray()
- : outputTextures;
- if (this.outputLookup) {
- return lookup.toObject(this.outputLookup, output);
- }
- return output;
- }
- // @ts-expect-error the underlying network works as normal, but we are working on the GPU
- prepTraining(data, options = {}) {
- this.updateTrainingOptions(options);
- const preparedData = this.formatData(data);
- const endTime = Date.now() + this.trainOpts.timeout;
- const status = {
- error: 1,
- iterations: 0,
- };
- this.verifyIsInitialized(preparedData);
- const texturizeOutputData = this.gpu.createKernel(function (value) {
- return value[this.thread.x];
- }, {
- output: [preparedData[0].output.length],
- pipeline: true,
- immutable: true,
- });
- return {
- preparedData: preparedData.map((set) => ({
- input: this.texturizeInputData(set.input),
- output: texturizeOutputData(set.output),
- })),
- status,
- endTime,
- };
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- toFunction() {
- throw new Error(`${this.constructor.name}-toFunction is not yet implemented`);
- }
- toJSON() {
- var _a, _b;
- if (this.sizes === null) {
- this.initialize();
- }
- // use Array.from, keeping json small
- const jsonLayerWeights = this.weights.map((layerWeights) => {
- return (layerWeights instanceof gpu_js.Texture
- ? layerWeights.toArray()
- : layerWeights).map((layerWeights) => Array.from(layerWeights));
- });
- const jsonLayerBiases = this.biases.map((layerBiases) => Array.from(layerBiases instanceof gpu_js.Texture
- ? layerBiases.toArray()
- : layerBiases));
- const jsonLayers = [];
- for (let i = 0; i <= this.outputLayer; i++) {
- jsonLayers.push({
- weights: (_a = jsonLayerWeights[i]) !== null && _a !== void 0 ? _a : [],
- biases: (_b = jsonLayerBiases[i]) !== null && _b !== void 0 ? _b : [],
- });
- }
- return {
- type: 'NeuralNetworkGPU',
- sizes: [...this.sizes],
- layers: jsonLayers,
- inputLookup: this.inputLookup ? { ...this.inputLookup } : null,
- inputLookupLength: this.inputLookupLength,
- outputLookup: this.outputLookup ? { ...this.outputLookup } : null,
- outputLookupLength: this.outputLookupLength,
- options: { ...this.options },
- trainOpts: this.getTrainOptsJSON(),
- };
- }
- }
- class RecurrentConnection extends Internal {
- constructor() {
- super(...arguments);
- this.settings = {};
- this.layer = null;
- }
- setLayer(layer) {
- this.layer = layer;
- }
- get width() {
- if (!this.layer)
- throw new Error('layer not set');
- return this.layer.width;
- }
- set width(value) {
- throw new Error(`${this.constructor.name}-width is not yet implemented`);
- }
- get height() {
- if (!this.layer)
- throw new Error('layer not set');
- return this.layer.height;
- }
- set height(value) {
- throw new Error(`${this.constructor.name}-height is not yet implemented`);
- }
- get deltas() {
- if (!this.layer)
- throw new Error('layer not set');
- return this.layer.deltas;
- }
- set deltas(deltas) {
- if (!this.layer)
- throw new Error('layer not set');
- release(this.layer.deltas);
- this.layer.deltas = deltas;
- }
- get weights() {
- if (!this.layer)
- throw new Error('layer not set');
- return this.layer.weights;
- }
- set weights(weights) {
- if (!this.layer)
- throw new Error('layer not set');
- release(this.layer.weights);
- this.layer.weights = weights;
- }
- predict() {
- // throw new Error(`${this.constructor.name}-predict is not yet implemented`)
- }
- compare() {
- // throw new Error(`${this.constructor.name}-compare is not yet implemented`)
- }
- learn() {
- throw new Error('no longer using');
- }
- setupKernels() {
- // throw new Error(
- // `${this.constructor.name}-setupKernels is not yet implemented`
- // )
- }
- reuseKernels() {
- // throw new Error(
- // `${this.constructor.name}-reuseKernels is not yet implemented`
- // )
- }
- }
- class Recurrent extends FeedForward {
- // TODO: use generics in extend
- constructor(options = {}) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- super(options);
- this.trainOpts = {};
- this._outputConnection = null;
- this._layerSets = [];
- this._hiddenLayerOutputIndices = [];
- this._model = null;
- }
- _connectLayers() {
- if (!this.options.inputLayer) {
- throw new Error('inputLayer not found');
- }
- if (!this.options.outputLayer) {
- throw new Error('outputLayer not found');
- }
- const inputLayer = this.options.inputLayer();
- const hiddenLayers = this._connectHiddenLayers(inputLayer);
- const outputLayer = this.options.outputLayer(hiddenLayers[hiddenLayers.length - 1], -1);
- return {
- inputLayer,
- hiddenLayers,
- outputLayer,
- };
- }
- _connectLayersDeep() {
- const layers = [];
- const previousLayers = this._layerSets[this._layerSets.length - 1];
- let usedHiddenLayerOutputIndex = 0;
- function findInputLayer(inputLayer) {
- const index = previousLayers.indexOf(inputLayer);
- if (index < 0)
- throw new Error('unable to find layer');
- return layers[index];
- }
- function layerSettings(layer) {
- return {
- ...layer.settings,
- weights: null,
- deltas: null,
- praxis: null,
- };
- }
- for (let i = 0; i < previousLayers.length; i++) {
- const previousLayer = previousLayers[i];
- let layer;
- if (previousLayer instanceof Activation) {
- layer = new previousLayer.constructor(findInputLayer(previousLayer.inputLayer), layerSettings(previousLayer));
- }
- else if (previousLayer instanceof EntryPoint) {
- layer = new previousLayer.constructor(layerSettings(previousLayer));
- }
- else if (previousLayer instanceof Filter) {
- layer = new previousLayer.constructor(layerSettings(previousLayer.inputLayer), findInputLayer(previousLayer.inputLayer));
- }
- else if (previousLayer instanceof Internal) {
- const previousHiddenLayerOutput = previousLayers[this._hiddenLayerOutputIndices[usedHiddenLayerOutputIndex++]];
- if (previousLayer instanceof RecurrentConnection) {
- throw new Error('unfinished');
- }
- else if (previousLayer instanceof RecurrentInput) {
- layer = new RecurrentInput(previousHiddenLayerOutput);
- }
- else if (previousLayer instanceof RecurrentZeros) {
- layer = new RecurrentInput(previousHiddenLayerOutput);
- }
- else {
- throw new Error(`hidden layer ${previousLayer.constructor.name} extends unknown hidden layer`);
- }
- }
- else if (previousLayer instanceof InternalModel ||
- previousLayer instanceof Model) {
- layer = previousLayer;
- }
- else if (previousLayer instanceof Modifier) {
- layer = new previousLayer.constructor(findInputLayer(previousLayer.inputLayer), layerSettings(previousLayer.inputLayer));
- }
- else if (previousLayer instanceof Operator) {
- layer = new previousLayer.constructor(findInputLayer(previousLayer.inputLayer1), findInputLayer(previousLayer.inputLayer2), layerSettings(previousLayer));
- }
- else if (previousLayer instanceof Target) {
- layer = new previousLayer.constructor(layerSettings(previousLayer), findInputLayer(previousLayer.inputLayer));
- }
- else {
- throw new Error(`hidden layer ${previousLayer.constructor.name} extends unknown hidden layer`);
- }
- layers.push(layer);
- }
- return layers;
- }
- _connectHiddenLayers(previousLayer) {
- const hiddenLayers = [];
- if (!this.options.hiddenLayers)
- throw new Error('hiddenLayers not defined');
- for (let i = 0; i < this.options.hiddenLayers.length; i++) {
- const recurrentInput = new RecurrentZeros();
- const hiddenLayer = this.options.hiddenLayers[i](previousLayer, recurrentInput, i);
- previousLayer = hiddenLayer;
- hiddenLayers.push(hiddenLayer);
- }
- return hiddenLayers;
- }
- initialize() {
- this._outputConnection = new RecurrentConnection();
- let layerSet;
- if (this.options.layers) {
- layerSet = this._connectOptionsLayers();
- }
- else {
- const { inputLayer, hiddenLayers, outputLayer } = this._connectLayers();
- layerSet = flattenLayers([inputLayer, ...hiddenLayers, outputLayer]);
- this._hiddenLayerOutputIndices = hiddenLayers.map((l) => layerSet.indexOf(l));
- this._inputLayer = inputLayer;
- this._hiddenLayers = hiddenLayers;
- this._outputLayer = outputLayer;
- }
- this.layers = layerSet;
- this._layerSets = [layerSet];
- this._model = layerSet.filter((l) => l instanceof Model || l instanceof InternalModel);
- this.initializeLayers(layerSet);
- }
- initializeDeep() {
- const layers = this._connectLayersDeep();
- for (let i = 0; i < layers.length; i++) {
- const layer = layers[i];
- layer.setupKernels(true);
- layer.reuseKernels(this._layerSets[0][i]);
- }
- this._layerSets.push(layers);
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- run(inputs) {
- while (this._layerSets.length <= inputs.length) {
- this.initializeDeep();
- }
- const result = this.runInputs(inputs);
- if (result instanceof gpu_js.Texture) {
- return result.toArray();
- }
- return result;
- }
- runInput(input) {
- throw new Error('use .runInputs()');
- }
- runInputs(inputs) {
- while (this._layerSets.length < inputs.length) {
- this.initializeDeep();
- }
- const max = inputs.length - 1; // last output will be compared with last index
- for (let x = 0; x <= max; x++) {
- const layerSet = this._layerSets[x];
- layerSet[0].predict(inputs[x]);
- for (let i = 1; i < layerSet.length; i++) {
- layerSet[i].predict();
- }
- }
- const lastLayerUsed = this._layerSets[max];
- const result = lastLayerUsed[lastLayerUsed.length - 1].weights;
- this.end();
- return result;
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- train(data, options = {}) {
- const { preparedData, status, endTime } = this._prepTraining(data, options);
- let continueTicking = true;
- const calculateError = () => this._calculateTrainingError(preparedData);
- const trainPatters = () => this._trainPatterns(preparedData);
- while (continueTicking) {
- continueTicking = this._trainingTick(status, endTime, calculateError, trainPatters);
- }
- return status;
- }
- end() {
- const x = this._layerSets.length - 1;
- const lastLayerSet = this._layerSets[x];
- lastLayerSet[0].predict([new Float32Array([0])]);
- for (let i = 1; i < lastLayerSet.length; i++) {
- lastLayerSet[i].predict();
- }
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- transferData(formattedData) {
- return formattedData;
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- _prepTraining(data, options) {
- this._updateTrainingOptions(options);
- const endTime = this.trainOpts.timeout
- ? Date.now() + this.trainOpts.timeout
- : 0;
- const status = {
- error: 1,
- iterations: 0,
- };
- this.verifyIsInitialized();
- return {
- preparedData: this.transferData(data),
- status,
- endTime,
- };
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- _calculateTrainingError(data) {
- if (!this.meanSquaredError) {
- throw new Error('this.meanSquaredError not setup');
- }
- let sum = new Float32Array(1);
- for (let i = 0; i < data.length; ++i) {
- const prevSum = sum;
- const error = this._trainPattern(data[i], true);
- sum = this.meanSquaredError.add(sum, error);
- release(error);
- release(prevSum);
- }
- const result = this.meanSquaredError.divide(data.length, sum);
- release(sum);
- if (result instanceof gpu_js.Texture) {
- const resultArray = result.toArray();
- return resultArray[0];
- }
- return result[0];
- }
- // TODO: more types
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- formatData(data) {
- return data;
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- _calculateDeltas(target) {
- const lastLayerSet = this._layerSets[this._layerSets.length - 1];
- // Iterate from the second to last layer backwards, propagating 0's
- for (let i = lastLayerSet.length - 2; i >= 0; i--) {
- lastLayerSet[i].compare();
- }
- for (let x = target.length - 2; x >= 0; x--) {
- const layerSet = this._layerSets[x];
- layerSet[layerSet.length - 1].compare(target[x + 1]);
- for (let i = layerSet.length - 2; i >= 0; i--) {
- layerSet[i].compare();
- }
- }
- }
- adjustWeights() {
- var _a;
- const _model = this._model;
- for (let i = 0; i < _model.length; i++) {
- _model[i].learn((_a = this.options.learningRate) !== null && _a !== void 0 ? _a : 0);
- }
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- _trainPatterns(data) {
- for (let i = 0; i < data.length; ++i) {
- this._trainPattern(data[i], false);
- }
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- _trainPattern(inputs, logErrorRate) {
- // forward propagate
- this.runInputs(inputs);
- // back propagate
- this._calculateDeltas(inputs);
- this.adjustWeights();
- if (logErrorRate) {
- if (!this.meanSquaredError) {
- throw new Error('this.meanSquaredError not setup');
- }
- let error = new Float32Array(1);
- for (let i = 0, max = inputs.length - 2; i <= max; i++) {
- const layerSet = this._layerSets[i];
- const lastLayer = layerSet[layerSet.length - 1];
- const prevError = error;
- error = this.meanSquaredError.addAbsolute(prevError, lastLayer.errors);
- release(prevError);
- }
- return clone(this.meanSquaredError.divide(inputs.length, error));
- }
- return null;
- }
- }
- /**
- * A matrix
- */
- class Matrix {
- constructor(rows, columns) {
- this.rows = 0;
- this.columns = 0;
- if (rows)
- this.rows = rows;
- if (columns)
- this.columns = columns;
- this.weights = zeros$1(this.rows * this.columns);
- this.deltas = zeros$1(this.rows * this.columns);
- }
- getWeight(row, col) {
- // slow but careful accessor function
- // we want row-major order
- const ix = this.columns * row + col;
- if (ix < 0 || ix >= this.weights.length) {
- throw new Error('get accessor is skewed');
- }
- return this.weights[ix];
- }
- setWeight(row, col, v) {
- // slow but careful accessor function
- const ix = this.columns * row + col;
- if (ix < 0 || ix >= this.weights.length) {
- throw new Error('set accessor is skewed');
- }
- this.weights[ix] = v;
- return this;
- }
- getDelta(row, col) {
- // slow but careful accessor function
- // we want row-major order
- const ix = this.columns * row + col;
- if (ix < 0 || ix >= this.deltas.length) {
- throw new Error('get accessor is skewed');
- }
- return this.deltas[ix];
- }
- setDelta(row, col, v) {
- // slow but careful accessor function
- const ix = this.columns * row + col;
- if (ix < 0 || ix >= this.weights.length) {
- throw new Error('set accessor is skewed');
- }
- this.deltas[ix] = v;
- return this;
- }
- toJSON() {
- return {
- rows: this.rows,
- columns: this.columns,
- weights: Array.from(this.weights.slice(0)),
- };
- }
- static fromJSON(json) {
- const matrix = new Matrix(json.rows, json.columns);
- for (let i = 0, max = json.rows * json.columns; i < max; i++) {
- matrix.weights[i] = json.weights[i]; // copy over weights
- }
- return matrix;
- }
- static fromArray(weights) {
- const matrix = new Matrix(weights.length, weights[0].length);
- matrix.fromArray(weights);
- return matrix;
- }
- deltasToArray() {
- return this.toArray('deltas');
- }
- weightsToArray() {
- return this.toArray('weights');
- }
- toArray(prop = 'weights') {
- const result = new Array(this.rows);
- this.iterate({
- row: (rowIndex) => {
- result[rowIndex] = new Array(this.columns);
- },
- column: (rowIndex, columnIndex) => {
- if (prop === 'weights') {
- result[rowIndex][columnIndex] = this.getWeight(rowIndex, columnIndex);
- }
- else if (prop === 'deltas') {
- result[rowIndex][columnIndex] = this.getDelta(rowIndex, columnIndex);
- }
- },
- });
- return result;
- }
- fromArray(array, prop = 'weights') {
- if (array.length !== this.rows) {
- throw new Error('rows do not match');
- }
- if (array[0].length !== this.columns) {
- throw new Error('columns do not match');
- }
- this.iterate({
- column: (rowIndex, columnIndex) => {
- const value = array[rowIndex][columnIndex];
- if (typeof value !== 'number') {
- throw new Error('value not number');
- }
- if (prop === 'weights') {
- this.setWeight(rowIndex, columnIndex, value);
- }
- else if (prop === 'deltas') {
- this.setDelta(rowIndex, columnIndex, value);
- }
- },
- });
- return this;
- }
- iterate(callbacks) {
- const rows = this.rows;
- const columns = this.columns;
- for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
- if (callbacks.row) {
- callbacks.row(rowIndex);
- }
- for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
- if (callbacks.column) {
- callbacks.column(rowIndex, columnIndex);
- }
- }
- }
- return this;
- }
- }
- /** return Matrix but filled with random numbers from gaussian
- */
- class RandomMatrix extends Matrix {
- constructor(rows, columns, std) {
- super(rows, columns);
- this.std = std;
- for (let i = 0, max = this.weights.length; i < max; i++) {
- this.weights[i] = randomFloat(-std, std);
- }
- }
- }
- class DataFormatter {
- constructor(values, maxThreshold = 0) {
- this.values = values;
- this.indexTable = {};
- this.characterTable = {};
- this.characters = [];
- this.specialIndexes = [];
- this.isSetup = false;
- if (values === undefined)
- return;
- this.setup(values, maxThreshold);
- }
- setup(values, maxThreshold = 0) {
- if (this.isSetup)
- throw new Error('DataFormatter is already setup');
- this.values = values;
- // go over all characters and keep track of all unique ones seen
- // count up all characters
- this.buildCharactersFromIterable(values);
- this.buildTables(maxThreshold);
- if (values[0].input) {
- this.addInputOutput();
- }
- this.addUnrecognized();
- this.isSetup = true;
- }
- buildCharactersFromIterable(values) {
- const tempCharactersTable = {};
- for (let dataFormatterIndex = 0, dataFormatterLength = values.length; dataFormatterIndex < dataFormatterLength; dataFormatterIndex++) {
- const characters = values[dataFormatterIndex];
- // if (typeof characters === 'string') {
- // const character = characters;
- // if (tempCharactersTable.hasOwnProperty(character)) continue;
- // tempCharactersTable[character] = true;
- // this.characters.push(character);
- if (characters.hasOwnProperty('length')) {
- const iteratable = characters;
- for (let characterIndex = 0, charactersLength = iteratable.length; characterIndex < charactersLength; characterIndex++) {
- const character = iteratable[characterIndex];
- if (tempCharactersTable.hasOwnProperty(character))
- continue;
- tempCharactersTable[character] = true;
- this.characters.push(character);
- }
- }
- else if (typeof characters === 'number') {
- if (tempCharactersTable.hasOwnProperty(characters))
- continue;
- tempCharactersTable[characters] = true;
- this.characters.push(characters);
- }
- else if (typeof characters === 'boolean') {
- const character = characters.toString();
- if (tempCharactersTable.hasOwnProperty(character))
- continue;
- tempCharactersTable[character] = true;
- this.characters.push(character);
- }
- else if (Array.isArray(characters) &&
- typeof characters[0] === 'string') {
- for (let i = 0; i < characters.length; i++) {
- const character = characters[i];
- if (tempCharactersTable.hasOwnProperty(character))
- continue;
- tempCharactersTable[character] = true;
- this.characters.push(character);
- }
- }
- else if (Array.isArray(characters) &&
- (typeof characters[0] === 'number' ||
- typeof characters[0] === 'boolean')) {
- for (let i = 0; i < characters.length; i++) {
- const character = characters[i].toString();
- if (tempCharactersTable.hasOwnProperty(dataFormatterIndex))
- continue;
- tempCharactersTable[character] = true;
- this.characters.push(character);
- }
- }
- else if (characters.hasOwnProperty('input') &&
- characters.hasOwnProperty('output')) {
- const { input, output } = characters;
- if (Array.isArray(input)) {
- this.addCharacters(input, tempCharactersTable);
- }
- else {
- this.addCharacters(input.toString(), tempCharactersTable);
- }
- if (Array.isArray(output)) {
- this.addCharacters(output, tempCharactersTable);
- }
- else {
- this.addCharacters(output.toString(), tempCharactersTable);
- }
- }
- else {
- throw new Error('Unhandled value');
- }
- }
- }
- addCharacters(characters, charactersTable) {
- for (let i = 0; i < characters.length; i++) {
- const character = characters[i].toString();
- if (charactersTable.hasOwnProperty(character))
- continue;
- charactersTable[character] = true;
- this.characters.push(character);
- }
- }
- buildTables(maxThreshold) {
- // filter by count threshold and create pointers
- const charactersLength = this.characters.length;
- for (let characterIndex = 0; characterIndex < charactersLength; characterIndex++) {
- const character = this.characters[characterIndex];
- if (characterIndex >= maxThreshold) {
- // add character to dataFormatter
- this.indexTable[character] = characterIndex;
- this.characterTable[characterIndex] = character;
- }
- }
- }
- toIndexes(value, maxThreshold = 0) {
- const result = [];
- const { indexTable } = this;
- switch (typeof value) {
- case 'number':
- case 'boolean':
- value = value.toString();
- }
- for (let i = 0, max = value.length; i < max; i++) {
- const character = value[i].toString();
- let index = indexTable[character];
- if (index === undefined) {
- if (indexTable.unrecognized) {
- index = indexTable.unrecognized;
- }
- else {
- throw new Error(`unrecognized character "${character}"`);
- }
- }
- if (index < maxThreshold)
- continue;
- result.push(index);
- }
- return result;
- }
- toIndexesInputOutput(input, output, maxThreshold = 0) {
- const result = this.toIndexesValue(input, maxThreshold, true);
- if (typeof output === 'undefined')
- return result;
- return result.concat(this.toIndexesValue(output, maxThreshold, false));
- }
- toIndexesValue(value, maxThreshold, isInput) {
- if (typeof value === 'string') {
- value = value.split('');
- }
- else if (typeof value === 'number' || typeof value === 'boolean') {
- value = value.toString().split('');
- }
- else if (Array.isArray(value) &&
- (typeof value[0] === 'number' ||
- typeof value[0] === 'boolean' ||
- typeof value[0] === 'string')) {
- value = value.map((v) => v.toString());
- }
- else {
- throw new Error('unrecognized value');
- }
- if (isInput) {
- value = value.concat(['stop-input', 'start-output']);
- }
- return this.toIndexes(value, maxThreshold);
- }
- toCharacters(indices, maxThreshold = 0) {
- const result = [];
- const { indexTable, characterTable } = this;
- for (let i = 0, max = indices.length; i < max; i++) {
- const index = indices[i];
- if (index < maxThreshold)
- continue;
- let character = characterTable[index];
- if (character === undefined) {
- if (indexTable.unrecognized) {
- character = characterTable[indexTable.unrecognized];
- }
- else {
- throw new Error(`unrecognized index "${index}"`);
- }
- }
- else if (character !== null) {
- result.push(character.toString());
- }
- }
- return result;
- }
- toString(indices, maxThreshold) {
- return this.toCharacters(indices, maxThreshold).join('');
- }
- addInputOutput() {
- this.addSpecial('stop-input');
- this.addSpecial('start-output');
- }
- addUnrecognized() {
- this.addSpecial('unrecognized');
- }
- static fromAllPrintable(maxThreshold, values = ['\n']) {
- for (let i = 32; i <= 126; i++) {
- values.push(String.fromCharCode(i));
- }
- return new DataFormatter(values, maxThreshold);
- }
- static fromAllPrintableInputOutput(maxThreshold, values = ['\n']) {
- const dataFormatter = DataFormatter.fromAllPrintable(maxThreshold, values);
- dataFormatter.addInputOutput();
- dataFormatter.addUnrecognized();
- return dataFormatter;
- }
- static fromStringInputOutput(string, maxThreshold) {
- const values = Array.from(new Set(string)).join('');
- const dataFormatter = new DataFormatter(values.split(''), maxThreshold);
- dataFormatter.addInputOutput();
- dataFormatter.addUnrecognized();
- dataFormatter.isSetup = true;
- return dataFormatter;
- }
- static fromArrayInputOutput(data, maxThreshold) {
- const values = [];
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- values.push(validateAndCast(datum.input), validateAndCast(datum.output));
- }
- const flatArray = Array.isArray(values)
- ? values.flat()
- : values;
- const dataFormatter = new DataFormatter(Array.from(new Set(flatArray)), maxThreshold);
- dataFormatter.addInputOutput();
- dataFormatter.addUnrecognized();
- dataFormatter.isSetup = true;
- return dataFormatter;
- }
- static fromString(string, maxThreshold = 0) {
- const values = Array.from(new Set(string)).join('');
- return new DataFormatter(values.split(''), maxThreshold);
- }
- toJSON() {
- return {
- indexTable: this.indexTable,
- characterTable: this.characterTable,
- values: this.values,
- characters: this.characters,
- specialIndexes: this.specialIndexes,
- };
- }
- /** TODO: Type better, The type of json is not "string that is a valid JSON", it is a POJO in the shape of DataFormatter.
- * this method re-hydrates the the data as an instance of DataFormatter.
- */
- static fromJSON(json) {
- const dataFormatter = new DataFormatter();
- dataFormatter.indexTable = json.indexTable;
- dataFormatter.characterTable = json.characterTable;
- dataFormatter.values = json.values;
- dataFormatter.characters = json.characters;
- dataFormatter.specialIndexes = json.specialIndexes;
- return dataFormatter;
- }
- addSpecial(special, character = null) {
- const specialIndex = (this.indexTable[special] = this.characters.length);
- this.characterTable[specialIndex] = character;
- this.specialIndexes.push(this.characters.length);
- this.characters.push(special);
- }
- toFunctionString() {
- return `
- var characterTable = ${JSON.stringify(this.characterTable)};
- var indexTable = ${JSON.stringify(this.indexTable)};
- var characters = ${JSON.stringify(this.characters)};
- var dataFormatter = {
- toIndexes: function ${this.toIndexes.toString()},
- toIndexesInputOutput: function ${this.toIndexesInputOutput.toString()},
- toCharacters: function ${this.toCharacters.toString()},
- toIndexesValue: function ${this.toIndexesValue.toString()},
- };`;
- }
- formatDataIn(input, output) {
- var _a;
- if (input === undefined)
- return [];
- if (Array.isArray(input) && typeof input[0] === 'number') {
- return input;
- }
- if ((_a = this.indexTable) === null || _a === void 0 ? void 0 : _a.hasOwnProperty('stop-input')) {
- return this.toIndexesInputOutput(input, output);
- }
- return this.toIndexes(input);
- }
- formatDataOut(input, output) {
- return this.toCharacters(output).join('');
- }
- format(data) {
- if (typeof data[0] === 'number' &&
- !Array.isArray(data[0]) &&
- (!data[0].hasOwnProperty('input') || !data[0].hasOwnProperty('output'))) {
- return data;
- }
- const result = [];
- if (typeof data[0] === 'string' ||
- typeof data[0] === 'number' ||
- Array.isArray(data[0])) {
- if (!this.isSetup) {
- this.setup(data);
- for (let i = 0; i < data.length; i++) {
- result.push(this.formatDataIn(validateAndCast(data[i])));
- }
- }
- else {
- for (let i = 0, max = data.length; i < max; i++) {
- result.push(this.formatDataIn(data[i]));
- }
- }
- }
- else if (data[0].input && data[0].output) {
- if (!this.isSetup) {
- this.setup(data);
- }
- for (let i = 0, max = data.length; i < max; i++) {
- result.push(this.formatDataIn(validateAndCast(data[i].input), validateAndCast(data[i].output)));
- }
- }
- else {
- throw new Error('unrecognized data');
- }
- return result;
- }
- }
- function validateAndCast(value) {
- if (typeof value === 'string')
- return value;
- if (typeof value === 'number')
- return value.toString();
- if (typeof value === 'boolean')
- return value.toString();
- if (Array.isArray(value) && typeof value[0] === 'string')
- return value;
- if (typeof value[0] === 'boolean') {
- return value.map((v) => v.toString());
- }
- if (typeof value[0] === 'number') {
- return value.map((v) => v.toString());
- }
- throw new Error('unrecognized value, expected string[], string, number[], number, boolean[], or boolean');
- }
- function copy(product, left) {
- product.rows = left.rows;
- product.columns = left.columns;
- product.weights = left.weights.slice(0);
- product.deltas = left.deltas.slice(0);
- }
- /**
- * add {left} and {right} matrix weights into {into}
- */
- function add(product, left, right) {
- for (let i = 0; i < left.weights.length; i++) {
- product.weights[i] = left.weights[i] + right.weights[i];
- product.deltas[i] = 0;
- }
- }
- /**
- * adds {from} deltas to {left} and {right} deltas
- */
- function addB(product, left, right) {
- for (let i = 0; i < product.deltas.length; i++) {
- left.deltas[i] = product.deltas[i];
- right.deltas[i] = product.deltas[i];
- }
- }
- /**
- * makes matrix weights and deltas all ones
- */
- function allOnes(product) {
- for (let i = 0; i < product.weights.length; i++) {
- product.weights[i] = 1;
- product.deltas[i] = 0;
- }
- }
- function cloneNegative(product, left) {
- product.rows = left.rows;
- product.columns = left.columns;
- product.weights = left.weights.slice(0);
- product.deltas = left.deltas.slice(0);
- for (let i = 0; i < left.weights.length; i++) {
- product.weights[i] = -left.weights[i];
- product.deltas[i] = 0;
- }
- }
- /**
- * multiply {left} and {right} matrix weights to {into}
- */
- function multiply(product, left, right) {
- const leftRows = left.rows;
- const leftColumns = left.columns;
- const rightColumns = right.columns;
- // loop over rows of left
- for (let leftRow = 0; leftRow < leftRows; leftRow++) {
- const leftRowBase = leftColumns * leftRow;
- const rightRowBase = rightColumns * leftRow;
- // loop over cols of right
- for (let rightColumn = 0; rightColumn < rightColumns; rightColumn++) {
- // dot product loop
- let dot = 0;
- // loop over columns of left
- for (let leftColumn = 0; leftColumn < leftColumns; leftColumn++) {
- const rightColumnBase = rightColumns * leftColumn;
- const leftIndex = leftRowBase + leftColumn;
- const rightIndex = rightColumnBase + rightColumn;
- dot += left.weights[leftIndex] * right.weights[rightIndex];
- left.deltas[leftIndex] = 0;
- right.deltas[rightIndex] = 0;
- }
- product.weights[rightRowBase + rightColumn] = dot;
- }
- }
- }
- /**
- * multiplies {from} deltas to {left} and {right}
- */
- function multiplyB(product, left, right) {
- const leftRows = left.rows;
- const leftColumns = left.columns;
- const rightColumns = right.columns;
- // loop over rows of left
- for (let leftRowRoot = 0; leftRowRoot < leftRows; leftRowRoot++) {
- const leftRowBase = leftColumns * leftRowRoot;
- const rightRowBase = rightColumns * leftRowRoot;
- // loop over cols of right
- for (let rightColumn = 0; rightColumn < rightColumns; rightColumn++) {
- // loop over columns of left
- for (let leftColumn = 0; leftColumn < leftColumns; leftColumn++) {
- const rightColumnBase = rightColumns * leftColumn;
- const leftRow = leftRowBase + leftColumn;
- const rightRow = rightColumnBase + rightColumn;
- const backPropagateValue = product.deltas[rightRowBase + rightColumn];
- left.deltas[leftRow] += right.weights[rightRow] * backPropagateValue;
- right.deltas[rightRow] += left.weights[leftRow] * backPropagateValue;
- }
- }
- }
- }
- function multiplyElement(product, left, right) {
- const { weights } = left;
- for (let i = 0; i < weights.length; i++) {
- product.weights[i] = left.weights[i] * right.weights[i];
- product.deltas[i] = 0;
- }
- }
- /**
- * multiplies {left} and {right} weight by {from} deltas into {left} and {right} deltas
- */
- function multiplyElementB(product, left, right) {
- for (let i = 0; i < left.weights.length; i++) {
- left.deltas[i] = right.weights[i] * product.deltas[i];
- right.deltas[i] = left.weights[i] * product.deltas[i];
- }
- }
- /**
- *
- * relu {m} weights to {into} weights
- */
- function relu(product, left) {
- for (let i = 0; i < left.weights.length; i++) {
- product.weights[i] = Math.max(0, left.weights[i]); // relu
- product.deltas[i] = 0;
- }
- }
- /**
- * adds {from} deltas to {m} deltas when {m} weights are above other a threshold of 0
- */
- function reluB(product, left) {
- for (let i = 0; i < product.deltas.length; i++) {
- left.deltas[i] = left.weights[i] > 0 ? product.deltas[i] : 0;
- }
- }
- function rowPluck(product, left, rowPluckIndex) {
- const { columns } = left;
- const rowBase = columns * rowPluckIndex;
- for (let column = 0; column < columns; column++) {
- product.weights[column] = left.weights[rowBase + column];
- product.deltas[column] = 0;
- }
- }
- /**
- * adds {from} deltas into {m} deltas
- */
- function rowPluckB(product, left, rowIndex) {
- const { columns } = left;
- const rowBase = columns * rowIndex;
- for (let column = 0; column < columns; column++) {
- left.deltas[rowBase + column] = product.deltas[column];
- }
- }
- function sigmoid(product, left) {
- // sigmoid nonlinearity
- for (let i = 0; i < left.weights.length; i++) {
- product.weights[i] = 1 / (1 + Math.exp(-left.weights[i]));
- product.deltas[i] = 0;
- }
- }
- // function sig(x) {
- // // helper function for computing sigmoid
- // return 1 / (1 + Math.exp(-x));
- // }
- function sigmoidB(product, left) {
- for (let i = 0; i < product.deltas.length; i++) {
- const mwi = product.weights[i];
- left.deltas[i] = mwi * (1 - mwi) * product.deltas[i];
- }
- }
- function softmax(matrix) {
- // probability volume
- const result = new Matrix(matrix.rows, matrix.columns);
- let maxVal = -999999;
- for (let i = 0; i < matrix.weights.length; i++) {
- if (matrix.weights[i] > maxVal) {
- maxVal = matrix.weights[i];
- }
- }
- let s = 0;
- for (let i = 0; i < matrix.weights.length; i++) {
- result.weights[i] = Math.exp(matrix.weights[i] - maxVal);
- s += result.weights[i];
- }
- for (let i = 0; i < matrix.weights.length; i++) {
- result.weights[i] /= s;
- }
- // no backward pass here needed
- // since we will use the computed probabilities outside
- // to set gradients directly on m
- return result;
- }
- function tanh(product, left) {
- // tanh nonlinearity
- for (let i = 0; i < left.weights.length; i++) {
- product.weights[i] = Math.tanh(left.weights[i]);
- product.deltas[i] = 0;
- }
- }
- function tanhB(product, left) {
- for (let i = 0; i < product.deltas.length; i++) {
- // grad for z = tanh(x) is (1 - z^2)
- const mwi = product.weights[i];
- left.deltas[i] = (1 - mwi * mwi) * product.deltas[i];
- }
- }
- class Equation {
- constructor() {
- this.states = [];
- this.inputRow = 0;
- }
- add(left, right) {
- if (left.weights.length !== right.weights.length) {
- throw new Error('misaligned matrices');
- }
- const product = new Matrix(left.rows, left.columns);
- this.states.push({
- name: 'add',
- product,
- left,
- right,
- forwardFn: add,
- backpropagationFn: addB,
- });
- return product;
- }
- allOnes(rows, columns) {
- const product = new Matrix(rows, columns);
- this.states.push({
- name: 'allOnes',
- product,
- left: product,
- forwardFn: allOnes,
- backpropagationFn: () => { },
- });
- return product;
- }
- cloneNegative(matrix) {
- const product = new Matrix(matrix.rows, matrix.columns);
- this.states.push({
- name: 'cloneNegative',
- product,
- left: matrix,
- forwardFn: cloneNegative,
- backpropagationFn: () => { },
- });
- return product;
- }
- /**
- * connects two matrices together by subtract
- */
- subtract(left, right) {
- if (left.weights.length !== right.weights.length) {
- throw new Error('misaligned matrices');
- }
- return this.add(this.add(this.allOnes(left.rows, left.columns), this.cloneNegative(left)), right);
- }
- /**
- * connects two matrices together by multiply
- */
- multiply(left, right) {
- if (left.columns !== right.rows) {
- throw new Error('misaligned matrices');
- }
- const product = new Matrix(left.rows, right.columns);
- this.states.push({
- name: 'multiply',
- product,
- left,
- right,
- forwardFn: multiply,
- backpropagationFn: multiplyB,
- });
- return product;
- }
- /**
- * connects two matrices together by multiplyElement
- */
- multiplyElement(left, right) {
- if (left.weights.length !== right.weights.length) {
- throw new Error('misaligned matrices');
- }
- const product = new Matrix(left.rows, left.columns);
- this.states.push({
- name: 'multiplyElement',
- product,
- left,
- right,
- forwardFn: multiplyElement,
- backpropagationFn: multiplyElementB,
- });
- return product;
- }
- /**
- * connects a matrix to relu
- */
- relu(matrix) {
- const product = new Matrix(matrix.rows, matrix.columns);
- this.states.push({
- name: 'relu',
- product,
- left: matrix,
- forwardFn: relu,
- backpropagationFn: reluB,
- });
- return product;
- }
- /**
- * input a matrix
- */
- input(input) {
- this.states.push({
- name: 'input',
- product: input,
- forwardFn: (product) => {
- if (!this.inputValue)
- return;
- if (this.inputValue.length !== product.weights.length) {
- throw new Error('this.inputValue is of wrong dimensions');
- }
- product.weights = input.weights = this.inputValue;
- },
- backpropagationFn: () => { },
- });
- return input;
- }
- /**
- * connects a matrix via a row
- */
- inputMatrixToRow(matrix) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- const product = new Matrix(matrix.columns, 1);
- this.states.push({
- name: 'inputMatrixToRow',
- product,
- left: matrix,
- get right() {
- return self.inputRow;
- },
- forwardFn: rowPluck,
- backpropagationFn: rowPluckB,
- });
- return product;
- }
- /**
- * connects a matrix to sigmoid
- */
- sigmoid(matrix) {
- const product = new Matrix(matrix.rows, matrix.columns);
- this.states.push({
- name: 'sigmoid',
- product,
- left: matrix,
- forwardFn: sigmoid,
- backpropagationFn: sigmoidB,
- });
- return product;
- }
- /**
- * connects a matrix to tanh
- */
- tanh(matrix) {
- const product = new Matrix(matrix.rows, matrix.columns);
- this.states.push({
- name: 'tanh',
- product,
- left: matrix,
- forwardFn: tanh,
- backpropagationFn: tanhB,
- });
- return product;
- }
- /**
- *
- * Observe a matrix for debugging
- */
- observe(matrix) {
- this.states.push({
- name: 'observe',
- product: new Matrix(),
- forwardFn: () => { },
- backpropagationFn: () => { },
- });
- return matrix;
- }
- /**
- * Run index through equations via forward propagation
- */
- runIndex(rowIndex = 0) {
- this.inputRow = rowIndex;
- let state = this.states[0];
- for (let i = 0, max = this.states.length; i < max; i++) {
- state = this.states[i];
- if (!state.hasOwnProperty('forwardFn'))
- continue;
- state.forwardFn(state.product, state.left, state.right);
- }
- return state.product;
- }
- /**
- * Run value through equations via forward propagation
- */
- runInput(inputValue) {
- this.inputValue = inputValue;
- let state = this.states[0];
- for (let i = 0, max = this.states.length; i < max; i++) {
- state = this.states[i];
- if (!state.hasOwnProperty('forwardFn'))
- continue;
- state.forwardFn(state.product, state.left, state.right);
- }
- return state.product;
- }
- /**
- * Run value through equations via back propagation
- */
- backpropagate() {
- let i = this.states.length;
- let state = this.states[0];
- while (i-- > 0) {
- state = this.states[i];
- if (!state.hasOwnProperty('backpropagationFn'))
- continue;
- state.backpropagationFn(state.product, state.left, state.right);
- }
- return state.product;
- }
- /**
- * Run index through equations via back propagation
- */
- backpropagateIndex(rowIndex = 0) {
- this.inputRow = rowIndex;
- let i = this.states.length;
- let state = this.states[0];
- while (i-- > 0) {
- state = this.states[i];
- if (!state.hasOwnProperty('backpropagationFn'))
- continue;
- state.backpropagationFn(state.product, state.left, state.right);
- }
- return state.product;
- }
- /**
- * Predict a target value from equation
- */
- predictTarget(input, target) {
- let errorSum = 0;
- const output = this.runInput(input);
- for (let i = 0; i < output.weights.length; i++) {
- const error = output.weights[i] - target[i];
- // set gradients into log probabilities
- errorSum += Math.abs(error);
- // write gradients into log probabilities
- output.deltas[i] = error;
- }
- return errorSum;
- }
- /**
- * Predict a target index from equation
- */
- predictTargetIndex(input, target) {
- const output = this.runIndex(input);
- // set gradients into log probabilities
- const logProbabilities = output; // interpret output as log probabilities
- const probabilities = softmax(output); // compute the softmax probabilities
- // write gradients into log probabilities
- logProbabilities.deltas = probabilities.weights.slice(0);
- logProbabilities.deltas[target] -= 1;
- // accumulate base 2 log prob and do smoothing
- return -Math.log2(probabilities.weights[target]);
- }
- }
- function maxI(matrix) {
- // argmax of array w
- const { weights } = matrix;
- let maxv = weights[0];
- let maxix = 0;
- for (let i = 1; i < weights.length; i++) {
- const v = weights[i];
- if (v < maxv)
- continue;
- maxix = i;
- maxv = v;
- }
- return maxix;
- }
- function sampleI(matrix) {
- // sample argmax from w, assuming w are
- // probabilities that sum to one
- const r = randomFloat(0, 1);
- const w = matrix.weights;
- let x = 0;
- let i = 0;
- while (true) {
- x += w[i];
- if (x > r) {
- return i;
- }
- i++;
- }
- }
- const trainDefaults$1 = {
- iterations: 20000,
- errorThresh: 0.005,
- log: false,
- logPeriod: 10,
- learningRate: 0.01,
- callbackPeriod: 10,
- timeout: Infinity,
- };
- const defaults$1 = () => {
- return {
- inputSize: 20,
- inputRange: 20,
- hiddenLayers: [20, 20],
- outputSize: 20,
- decayRate: 0.999,
- smoothEps: 1e-8,
- regc: 0.000001,
- clipval: 5,
- maxPredictionLength: 100,
- dataFormatter: new DataFormatter(),
- };
- };
- class RNN {
- constructor(options = {}) {
- this.options = { ...defaults$1() };
- this.trainOpts = { ...trainDefaults$1 };
- this.stepCache = {};
- this.runs = 0;
- this.ratioClipped = 0;
- this.model = Object.seal({
- isInitialized: false,
- input: new Matrix(0, 0),
- hiddenLayers: [],
- output: new Matrix(0, 0),
- equations: [],
- allMatrices: [],
- equationConnections: [],
- outputConnector: new RandomMatrix(0, 0, 0.08),
- });
- this.initialLayerInputs = [];
- this.options = { ...this.options, ...options };
- this.updateTrainingOptions({
- ...trainDefaults$1,
- });
- if (options.json) {
- this.fromJSON(options.json);
- }
- }
- initialize() {
- const { dataFormatter } = this.options;
- if (dataFormatter === null || dataFormatter === void 0 ? void 0 : dataFormatter.characters.length) {
- this.options.inputSize = this.options.inputRange = this.options.outputSize =
- dataFormatter.characters.length;
- }
- this.model = this.mapModel();
- }
- createHiddenLayers() {
- const { hiddenLayers, inputSize } = this.options;
- const hiddenLayersModel = [];
- // 0 is end, so add 1 to offset
- hiddenLayersModel.push(this.getHiddenLayer(hiddenLayers[0], inputSize));
- let prevSize = hiddenLayers[0];
- for (let d = 1; d < hiddenLayers.length; d++) {
- // loop over depths
- const hiddenSize = hiddenLayers[d];
- hiddenLayersModel.push(this.getHiddenLayer(hiddenSize, prevSize));
- prevSize = hiddenSize;
- }
- return hiddenLayersModel;
- }
- getHiddenLayer(hiddenSize, prevSize) {
- return {
- // wxh
- weight: new RandomMatrix(hiddenSize, prevSize, 0.08),
- // whh
- transition: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- // bhh
- bias: new Matrix(hiddenSize, 1),
- };
- }
- getEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- if (!hiddenLayer.weight || !hiddenLayer.transition || !hiddenLayer.bias) {
- throw new Error('hiddenLayer does not have expected properties');
- }
- const relu = equation.relu.bind(equation);
- const add = equation.add.bind(equation);
- const multiply = equation.multiply.bind(equation);
- return relu(add(add(multiply(hiddenLayer.weight, inputMatrix), multiply(hiddenLayer.transition, previousResult)), hiddenLayer.bias));
- }
- createInputMatrix() {
- const { inputRange, inputSize } = this.options;
- if (inputRange < 1)
- throw new Error('this.options.inputRange not an expected number');
- if (inputSize < 1)
- throw new Error('this.options.inputSize not an expected number');
- // 0 is end, so add 1 to offset
- return new RandomMatrix(inputRange + 1, inputSize, 0.08);
- }
- createOutputMatrices() {
- const { outputSize, hiddenLayers } = this.options;
- const lastHiddenSize = last(hiddenLayers);
- // 0 is end, so add 1 to offset
- return {
- // whd
- outputConnector: new RandomMatrix(outputSize + 1, lastHiddenSize, 0.08),
- // 0 is end, so add 1 to offset
- // bd
- output: new Matrix(outputSize + 1, 1),
- };
- }
- bindEquation() {
- const { model } = this;
- const { hiddenLayers } = this.options;
- const equation = new Equation();
- const outputs = [];
- const equationConnection = model.equationConnections.length > 0
- ? last(model.equationConnections)
- : this.initialLayerInputs;
- // 0 index
- let output = this.getEquation(equation, equation.inputMatrixToRow(model.input), equationConnection[0], model.hiddenLayers[0]);
- outputs.push(output);
- // 1+ indices
- for (let i = 1, max = hiddenLayers.length; i < max; i++) {
- if (!equationConnection[i]) {
- throw new Error(`Cannot find equation at index ${i}`);
- }
- output = this.getEquation(equation, output, equationConnection[i], model.hiddenLayers[i]);
- outputs.push(output);
- }
- model.equationConnections.push(outputs);
- equation.add(equation.multiply(model.outputConnector, output), model.output);
- model.equations.push(equation);
- }
- mapModel() {
- const allMatrices = [];
- this.initialLayerInputs = this.options.hiddenLayers.map((size) => new Matrix(size, 1));
- const input = this.createInputMatrix();
- allMatrices.push(input);
- const hiddenLayers = this.createHiddenLayers();
- if (!hiddenLayers.length)
- throw new Error('net.hiddenLayers not set');
- for (let i = 0, max = hiddenLayers.length; i < max; i++) {
- const hiddenMatrix = hiddenLayers[i];
- for (const property in hiddenMatrix) {
- if (!hiddenMatrix.hasOwnProperty(property))
- continue;
- allMatrices.push(hiddenMatrix[property]);
- }
- }
- const { output, outputConnector } = this.createOutputMatrices();
- allMatrices.push(outputConnector);
- allMatrices.push(output);
- return Object.seal({
- isInitialized: true,
- input,
- hiddenLayers,
- output,
- equations: [],
- allMatrices,
- equationConnections: [],
- outputConnector,
- });
- }
- trainInput(input) {
- this.runs++;
- const { model } = this;
- const max = input.length;
- let log2ppl = 0;
- let equation;
- while (model.equations.length <= input.length + 1) {
- // last is zero
- this.bindEquation();
- }
- for (let inputIndex = -1, inputMax = input.length; inputIndex < inputMax; inputIndex++) {
- // start and end tokens are zeros
- const equationIndex = inputIndex + 1;
- equation = model.equations[equationIndex];
- const source = inputIndex === -1 ? 0 : input[inputIndex] + 1; // first step: start with START token
- const target = inputIndex === max - 1 ? 0 : input[inputIndex + 1] + 1; // last step: end with END token
- log2ppl += equation.predictTargetIndex(source, target);
- }
- return Math.pow(2, log2ppl / (max - 1)) / 100;
- }
- backpropagate(input) {
- let i = input.length;
- const { model } = this;
- const { equations } = model;
- while (i > 0) {
- equations[i].backpropagateIndex(input[i - 1] + 1);
- i--;
- }
- equations[0].backpropagateIndex(0);
- }
- adjustWeights() {
- const { regc, clipval, decayRate, smoothEps } = this.options;
- const { trainOpts, model, stepCache } = this;
- const { learningRate } = trainOpts;
- const { allMatrices } = model;
- let numClipped = 0;
- let numTot = 0;
- for (let matrixIndex = 0; matrixIndex < allMatrices.length; matrixIndex++) {
- const matrix = allMatrices[matrixIndex];
- const { weights, deltas } = matrix;
- if (!(matrixIndex in stepCache)) {
- stepCache[matrixIndex] = zeros$1(matrix.rows * matrix.columns);
- }
- const cache = stepCache[matrixIndex];
- for (let i = 0; i < weights.length; i++) {
- let r = deltas[i];
- const w = weights[i];
- // rmsprop adaptive learning rate
- cache[i] = cache[i] * decayRate + (1 - decayRate) * r * r;
- // gradient clip
- if (r > clipval) {
- r = clipval;
- numClipped++;
- }
- else if (r < -clipval) {
- r = -clipval;
- numClipped++;
- }
- numTot++;
- // update (and regularize)
- weights[i] =
- w + (-learningRate * r) / Math.sqrt(cache[i] + smoothEps) - regc * w;
- }
- }
- this.ratioClipped = numClipped / numTot;
- }
- get isRunnable() {
- if (this.model && this.model.equations.length === 0) {
- console.error(`No equations bound, did you run train()?`);
- return false;
- }
- return true;
- }
- checkRunnable() {
- if (!this.isRunnable) {
- throw new Error('Network not runnable');
- }
- }
- run(rawInput = [], isSampleI = false, temperature = 1) {
- const maxPredictionLength = this.options.maxPredictionLength +
- (rawInput !== null ? rawInput.length : 0) +
- (this.options.dataFormatter
- ? this.options.dataFormatter.specialIndexes.length
- : 0);
- this.checkRunnable();
- const input = this.options.dataFormatter && rawInput.length > 0
- ? this.options.dataFormatter.formatDataIn(rawInput)
- : rawInput;
- const { model } = this;
- const output = [];
- let i = 0;
- while (true) {
- const previousIndex = i === 0 ? 0 : i < input.length ? input[i - 1] + 1 : output[i - 1];
- while (model.equations.length <= i) {
- this.bindEquation();
- }
- const equation = model.equations[i];
- // sample predicted letter
- const outputMatrix = equation.runIndex(previousIndex);
- const logProbabilities = new Matrix(model.output.rows, model.output.columns);
- copy(logProbabilities, outputMatrix);
- if (temperature !== 1 && isSampleI) {
- /**
- * scale log probabilities by temperature and re-normalize
- * if temperature is high, logProbabilities will go towards zero
- * and the softmax outputs will be more diffuse. if temperature is
- * very low, the softmax outputs will be more peaky
- */
- for (let j = 0, max = logProbabilities.weights.length; j < max; j++) {
- logProbabilities.weights[j] /= temperature;
- }
- }
- const probs = softmax(logProbabilities);
- const nextIndex = isSampleI ? sampleI(probs) : maxI(probs);
- i++;
- if (nextIndex === 0) {
- // END token predicted, break out
- break;
- }
- if (i >= maxPredictionLength) {
- // something is wrong
- break;
- }
- output.push(nextIndex);
- }
- /**
- * we slice the input length here, not because output contains it, but it will be erroneous as we are sending the
- * network what is contained in input, so the data is essentially guessed by the network what could be next, till it
- * locks in on a value.
- * Kind of like this, values are from input:
- * 0 -> 4 (or in English: "beginning on input" -> "I have no idea? I'll guess what they want next!")
- * 2 -> 2 (oh how interesting, I've narrowed down values...)
- * 1 -> 9 (oh how interesting, I've now know what the values are...)
- * then the output looks like: [4, 2, 9,...]
- * so we then remove the erroneous data to get our true output
- */
- return this.options.dataFormatter.formatDataOut(input, output.slice(input.length).map((value) => value - 1));
- }
- /**
- *
- * Verifies network sizes are initialized
- * If they are not it will initialize them
- */
- verifyIsInitialized() {
- if (!this.model.isInitialized) {
- this.initialize();
- }
- }
- /**
- *
- * @param options
- * Supports all `trainDefaults` properties
- * also supports:
- * learningRate: (number),
- * momentum: (number),
- * activation: 'sigmoid', 'relu', 'leaky-relu', 'tanh'
- */
- updateTrainingOptions(options) {
- var _a;
- this.trainOpts = { ...trainDefaults$1, ...options };
- this.validateTrainingOptions(this.trainOpts);
- this.setLogMethod((_a = options.log) !== null && _a !== void 0 ? _a : this.trainOpts.log);
- // TODO: Remove this?
- // this.activation = options.activation || this.activation;
- }
- validateTrainingOptions(options) {
- const validations = {
- iterations: () => {
- const val = options.iterations;
- return typeof val === 'number' && val > 0;
- },
- errorThresh: () => {
- const val = options.errorThresh;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- log: () => {
- const val = options.log;
- return typeof val === 'function' || typeof val === 'boolean';
- },
- logPeriod: () => {
- const val = options.logPeriod;
- return typeof val === 'number' && val > 0;
- },
- learningRate: () => {
- const val = options.learningRate;
- return typeof val === 'number' && val > 0 && val < 1;
- },
- callback: () => {
- const val = options.callback;
- return typeof val === 'function' || val === undefined;
- },
- callbackPeriod: () => {
- const val = options.callbackPeriod;
- return typeof val === 'number' && val > 0;
- },
- timeout: () => {
- const val = options.timeout;
- return typeof val === 'number' && val > 0;
- },
- };
- for (const p in validations) {
- const v = options;
- if (!validations[p]()) {
- throw new Error(`[${p}, ${v[p]}] is out of normal training range, your network will probably not train.`);
- }
- }
- }
- setLogMethod(log) {
- if (typeof log === 'function') {
- this.trainOpts.log = log;
- }
- else if (log) {
- this.trainOpts.log = console.log;
- }
- else {
- this.trainOpts.log = false;
- }
- }
- prepTraining(data, options) {
- var _a;
- this.updateTrainingOptions(options);
- const preparedData = this.options.dataFormatter.format(data);
- const endTime = Date.now() + ((_a = this.trainOpts.timeout) !== null && _a !== void 0 ? _a : 0);
- const status = {
- error: 1,
- iterations: 0,
- };
- this.verifyIsInitialized();
- return {
- preparedData,
- status,
- endTime,
- };
- }
- train(data, trainOpts = {}) {
- var _a;
- this.trainOpts = trainOpts = {
- ...trainDefaults$1,
- ...trainOpts,
- };
- const { iterations, errorThresh, logPeriod, callback, callbackPeriod, } = this.trainOpts;
- const log = trainOpts.log === true ? console.log : trainOpts.log;
- let error = Infinity;
- let i;
- let inputs;
- if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.dataFormatter) {
- inputs = this.options.dataFormatter.format(data);
- }
- else if (Array.isArray(data) &&
- Array.isArray(data[0]) &&
- typeof data[0][0] === 'number') {
- inputs = data;
- }
- else {
- throw new Error('training not in expected format of number[][]');
- }
- this.verifyIsInitialized();
- for (i = 0; i < iterations && error > errorThresh; i++) {
- let sum = 0;
- for (let j = 0; j < inputs.length; j++) {
- const err = this.trainPattern(inputs[j], true);
- sum += err;
- }
- error = sum / data.length;
- if (isNaN(error)) {
- throw new Error('Network error rate is unexpected NaN, check network configurations and try again. Most probably input format is not correct or training data is not enough. ');
- }
- if (log && i % logPeriod === 0) {
- log(`iterations: ${i}, training error: ${error}`);
- }
- if (callback && i % callbackPeriod === 0) {
- callback({ error, iterations: i });
- }
- }
- return {
- error,
- iterations: i,
- };
- }
- addFormat() {
- throw new Error('not yet implemented');
- }
- toJSON() {
- if (!this.model.isInitialized) {
- this.initialize();
- }
- const { model, options } = this;
- return {
- type: this.constructor.name,
- options: { ...options, dataFormatter: options.dataFormatter.toJSON() },
- trainOpts: {
- ...this.trainOpts,
- timeout: this.trainOpts.timeout === Infinity
- ? 'Infinity'
- : this.trainOpts.timeout,
- },
- input: model.input.toJSON(),
- hiddenLayers: model.hiddenLayers.map((hiddenLayer) => {
- const layers = {};
- for (const p in hiddenLayer) {
- if (!hiddenLayer.hasOwnProperty(p))
- continue;
- layers[p] = hiddenLayer[p].toJSON();
- }
- return layers;
- }),
- outputConnector: this.model.outputConnector.toJSON(),
- output: this.model.output.toJSON(),
- };
- }
- fromJSON(json) {
- const { options } = json;
- const allMatrices = [];
- const input = Matrix.fromJSON(json.input);
- allMatrices.push(input);
- const hiddenLayers = [];
- json.hiddenLayers.forEach((hiddenLayer) => {
- const layers = {};
- for (const p in hiddenLayer) {
- layers[p] = Matrix.fromJSON(hiddenLayer[p]);
- allMatrices.push(layers[p]);
- }
- hiddenLayers.push(layers);
- });
- const outputConnector = Matrix.fromJSON(json.outputConnector);
- allMatrices.push(outputConnector);
- const output = Matrix.fromJSON(json.output);
- allMatrices.push(output);
- if (options.dataFormatter) {
- this.options = {
- ...defaults$1(),
- ...options,
- dataFormatter: DataFormatter.fromJSON(options.dataFormatter),
- };
- }
- else {
- this.options = {
- ...defaults$1(),
- ...options,
- dataFormatter: new DataFormatter(),
- };
- }
- this.model = Object.seal({
- isInitialized: true,
- input,
- hiddenLayers,
- output,
- allMatrices,
- outputConnector,
- equations: [],
- equationConnections: [],
- });
- this.initialLayerInputs = this.options.hiddenLayers.map((size) => new Matrix(size, 1));
- this.bindEquation();
- return this;
- }
- toFunction(cb) {
- const { model } = this;
- const { equations } = this.model;
- const equation = equations[1];
- const { states } = equation;
- const jsonString = JSON.stringify(this.toJSON());
- function previousConnectionIndex(m) {
- const connection = model.equationConnections[0];
- const { states } = equations[0];
- for (let i = 0, max = states.length; i < max; i++) {
- if (states[i].product === m) {
- return i;
- }
- }
- return connection.indexOf(m);
- }
- function matrixOrigin(m, stateIndex) {
- for (let i = 0, max = states.length; i < max; i++) {
- const state = states[i];
- if (i === stateIndex) {
- const j = previousConnectionIndex(m);
- if (j > -1 && (m === state.left || m === state.right)) {
- return `typeof prevStates[${j}] === 'object' ? prevStates[${j}].product : new Matrix(${m.rows}, ${m.columns})`;
- }
- return `new Matrix(${m.rows}, ${m.columns})`;
- }
- if (m === state.product)
- return `states[${i}].product`;
- if (m === state.right)
- return `states[${i}].right`;
- if (m === state.left)
- return `states[${i}].left`;
- }
- return '';
- }
- function matrixToString(m, stateIndex) {
- if (!m || !m.rows || !m.columns)
- return 'null';
- if (m === model.input)
- return `json.input`;
- if (m === model.outputConnector)
- return `json.outputConnector`;
- if (m === model.output)
- return `json.output`;
- for (let i = 0, max = model.hiddenLayers.length; i < max; i++) {
- const hiddenLayer = model.hiddenLayers[i];
- for (const p in hiddenLayer) {
- if (!hiddenLayer.hasOwnProperty(p))
- continue;
- if (hiddenLayer[p] !== m)
- continue;
- return `json.hiddenLayers[${i}].${p}`;
- }
- }
- return matrixOrigin(m, stateIndex);
- }
- function toInner(fnString) {
- // crude, but should be sufficient for now
- // function() { body }
- const fnParts = fnString.toString().split('{');
- fnParts.shift();
- // body }
- const fnBodyString = fnParts.join('{');
- const fnBodyParts = fnBodyString.split('}');
- fnBodyParts.pop();
- // body
- return fnBodyParts
- .join('}')
- .split('\n')
- .join('\n ')
- .replace('product.deltas[i] = 0;', '')
- .replace('product.deltas[column] = 0;', '')
- .replace('left.deltas[leftIndex] = 0;', '')
- .replace('right.deltas[rightIndex] = 0;', '')
- .replace('product.deltas = left.deltas.slice(0);', '');
- }
- function fileName(fnName) {
- return `src/recurrent/matrix/${fnName.replace(/[A-Z]/g, function (value) {
- return `-${value.toLowerCase()}`;
- })}.js`;
- }
- const statesRaw = [];
- const usedFunctionNames = {};
- const innerFunctionsSwitch = [];
- for (let i = 0, max = states.length; i < max; i++) {
- const state = states[i];
- statesRaw.push(`states[${i}] = {
- name: '${state.forwardFn.name}',
- left: ${state.left ? matrixToString(state.left, i) : 'undefined'},
- right: ${state.right ? matrixToString(state.right, i) : 'undefined'},
- product: ${matrixToString(state.product, i)}
- }`);
- const fnName = state.forwardFn.name;
- if (!usedFunctionNames[fnName]) {
- usedFunctionNames[fnName] = true;
- innerFunctionsSwitch.push(` case '${fnName}': //compiled from ${fileName(fnName)}
- ${toInner(state.forwardFn.toString())}
- break;`);
- }
- }
- const src = `
- if (typeof rawInput === 'undefined') rawInput = [];
- if (typeof isSampleI === 'undefined') isSampleI = false;
- if (typeof temperature === 'undefined') temperature = 1;
- var json = ${jsonString};
- ${this.options.dataFormatter
- ? `${this.options.dataFormatter.toFunctionString()};
- Object.assign(dataFormatter, json.options.dataFormatter);`
- : ''}
- ${this.options.dataFormatter &&
- typeof this.options.dataFormatter.formatDataIn === 'function'
- ? `const formatDataIn = function (input, output) { ${toInner(this.options.dataFormatter.formatDataIn.toString())} }.bind(dataFormatter);`
- : ''}
- ${this.options.dataFormatter !== null &&
- typeof this.options.dataFormatter.formatDataOut === 'function'
- ? `const formatDataOut = function formatDataOut(input, output) { ${toInner(this.options.dataFormatter.formatDataOut.toString())} }.bind(dataFormatter);`
- : ''}
- var maxPredictionLength =
- ${this.options.maxPredictionLength} +
- rawInput.length +
- ${this.options.dataFormatter
- ? this.options.dataFormatter.specialIndexes.length
- : 0};
- var input = ${this.options.dataFormatter &&
- typeof this.options.dataFormatter.formatDataIn === 'function'
- ? 'formatDataIn(rawInput)'
- : 'rawInput'};
- var _i = 0;
- var output = [];
- var states = [];
- var prevStates;
- while (true) {
- var previousIndex = (_i === 0
- ? 0
- : _i < input.length
- ? input[_i - 1] + 1
- : output[_i - 1])
- ;
- var rowPluckIndex = previousIndex;
- prevStates = states;
- states = [];
- ${statesRaw.join(';\n ')};
- for (var stateIndex = 0, stateMax = ${statesRaw.length}; stateIndex < stateMax; stateIndex++) {
- var state = states[stateIndex];
- var product = state.product;
- var left = state.left;
- var right = state.right;
- switch (state.name) {
- ${innerFunctionsSwitch.join('\n')}
- }
- }
- var logProbabilities = state.product;
- if (temperature !== 1 && isSampleI) {
- for (var q = 0, nq = logProbabilities.weights.length; q < nq; q++) {
- logProbabilities.weights[q] /= temperature;
- }
- }
- var probs = softmax(logProbabilities);
- var nextIndex = isSampleI ? sampleI(probs) : maxI(probs);
- _i++;
- if (nextIndex === 0) {
- break;
- }
- if (_i >= maxPredictionLength) {
- break;
- }
- output.push(nextIndex);
- }
- ${this.options.dataFormatter &&
- typeof this.options.dataFormatter.formatDataOut === 'function'
- ? 'return formatDataOut(input, output.slice(input.length).map(function(value) { return value - 1; }))'
- : 'return output.slice(input.length).map(function(value) { return value - 1; })'};
- function Matrix(rows, columns) {
- this.rows = rows;
- this.columns = columns;
- this.weights = zeros(rows * columns);
- }
- ${zeros$1.toString()}
- ${softmax.toString().replace('_1.Matrix', 'Matrix')}
- ${randomFloat.toString()}
- ${sampleI.toString()}
- ${maxI.toString()}`;
- // eslint-disable-next-line
- return new Function('rawInput', 'isSampleI', 'temperature', cb ? cb(src) : src);
- }
- trainPattern(input, logErrorRate) {
- const error = this.trainInput(input);
- this.backpropagate(input);
- this.adjustWeights();
- if (logErrorRate) {
- return error;
- }
- return 0;
- }
- }
- function last(values) {
- return values[values.length - 1];
- }
- class GRU extends RNN {
- getHiddenLayer(hiddenSize, prevSize) {
- return getGRUHiddenLayer(hiddenSize, prevSize);
- }
- getEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- return getGRUEquation(equation, inputMatrix, previousResult, hiddenLayer);
- }
- }
- function getGRUHiddenLayer(hiddenSize, prevSize) {
- return {
- // update Gate
- // wzxh
- updateGateInputMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- updateGateHiddenMatrix: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- updateGateBias: new Matrix(hiddenSize, 1),
- // reset Gate
- // wrxh
- resetGateInputMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- resetGateHiddenMatrix: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- resetGateBias: new Matrix(hiddenSize, 1),
- // cell write parameters
- // wcxh
- cellWriteInputMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- cellWriteHiddenMatrix: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- cellWriteBias: new Matrix(hiddenSize, 1),
- };
- }
- function getGRUEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- if (!hiddenLayer.updateGateInputMatrix ||
- !hiddenLayer.updateGateHiddenMatrix ||
- !hiddenLayer.updateGateBias ||
- !hiddenLayer.resetGateInputMatrix ||
- !hiddenLayer.resetGateHiddenMatrix ||
- !hiddenLayer.resetGateBias ||
- !hiddenLayer.cellWriteInputMatrix ||
- !hiddenLayer.cellWriteHiddenMatrix ||
- !hiddenLayer.cellWriteBias) {
- throw new Error('hiddenLayer does not have expected properties');
- }
- const sigmoid = equation.sigmoid.bind(equation);
- const add = equation.add.bind(equation);
- const multiply = equation.multiply.bind(equation);
- const multiplyElement = equation.multiplyElement.bind(equation);
- const tanh = equation.tanh.bind(equation);
- const allOnes = equation.allOnes.bind(equation);
- const cloneNegative = equation.cloneNegative.bind(equation);
- // update gate
- const updateGate = sigmoid(add(add(multiply(hiddenLayer.updateGateInputMatrix, inputMatrix), multiply(hiddenLayer.updateGateHiddenMatrix, previousResult)), hiddenLayer.updateGateBias));
- // reset gate
- const resetGate = sigmoid(add(add(multiply(hiddenLayer.resetGateInputMatrix, inputMatrix), multiply(hiddenLayer.resetGateHiddenMatrix, previousResult)), hiddenLayer.resetGateBias));
- // cell
- const cell = tanh(add(add(multiply(hiddenLayer.cellWriteInputMatrix, inputMatrix), multiply(hiddenLayer.cellWriteHiddenMatrix, multiplyElement(resetGate, previousResult))), hiddenLayer.cellWriteBias));
- // compute hidden state as gated, saturated cell activations
- // negate updateGate
- return add(multiplyElement(add(allOnes(updateGate.rows, updateGate.columns), cloneNegative(updateGate)), cell), multiplyElement(previousResult, updateGate));
- }
- class ArrayLookupTable {
- constructor(data, prop) {
- this.prop = prop;
- this.length = 0;
- this.table = {};
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- const ioValue = datum[prop];
- for (let j = 0; j < ioValue.length; j++) {
- const value = ioValue[j];
- for (const p in value) {
- if (!value.hasOwnProperty(p))
- continue;
- if (this.table.hasOwnProperty(p))
- continue;
- this.table[p] = this.length++;
- }
- }
- }
- }
- }
- const defaults = () => {
- return {
- ...defaults$1(),
- inputSize: 1,
- hiddenLayers: [20],
- outputSize: 1,
- inputRange: 0,
- };
- };
- class RNNTimeStep extends RNN {
- constructor(options = {}) {
- super();
- this.inputLookupLength = 0;
- this.inputLookup = null;
- this.outputLookup = null;
- this.outputLookupLength = 0;
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.model = Object.seal({
- isInitialized: false,
- hiddenLayers: [],
- output: new Matrix(0, 0),
- equations: [],
- allMatrices: [],
- equationConnections: [],
- outputConnector: new RandomMatrix(0, 0, 0.08),
- });
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.options = defaults();
- this.options = { ...this.options, ...options };
- this.updateTrainingOptions({
- ...trainDefaults,
- ...options,
- });
- if (options.json) {
- this.fromJSON(options.json);
- }
- }
- createInputMatrix() {
- throw new Error('Input Matrices do not exist on RNNTimeStep');
- }
- createOutputMatrices() {
- const { outputSize } = this.options;
- const lastHiddenSize = last(this.options.hiddenLayers);
- // whd
- const outputConnector = new RandomMatrix(outputSize, lastHiddenSize, 0.08);
- // bd
- const output = new RandomMatrix(outputSize, 1, 0.08);
- return { output, outputConnector };
- }
- bindEquation() {
- const { model, options } = this;
- const { hiddenLayers, inputSize } = options;
- const layers = model.hiddenLayers;
- const equation = new Equation();
- const outputs = [];
- const equationConnection = model.equationConnections.length > 0
- ? model.equationConnections[model.equationConnections.length - 1]
- : this.initialLayerInputs;
- // 0 index
- let output = this.getEquation(equation, equation.input(new Matrix(inputSize, 1)), equationConnection[0], layers[0]);
- outputs.push(output);
- // 1+ indices
- for (let i = 1, max = hiddenLayers.length; i < max; i++) {
- output = this.getEquation(equation, output, equationConnection[i], layers[i]);
- outputs.push(output);
- }
- model.equationConnections.push(outputs);
- equation.add(equation.multiply(model.outputConnector, output), model.output);
- model.equations.push(equation);
- }
- initialize() {
- this.model = this.mapModel();
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- mapModel() {
- const allMatrices = [];
- this.initialLayerInputs = this.options.hiddenLayers.map((size) => new Matrix(size, 1));
- const hiddenLayers = this.createHiddenLayers();
- for (let i = 0, max = hiddenLayers.length; i < max; i++) {
- const hiddenMatrix = hiddenLayers[i];
- for (const property in hiddenMatrix) {
- if (!hiddenMatrix.hasOwnProperty(property))
- continue;
- allMatrices.push(hiddenMatrix[property]);
- }
- }
- const { outputConnector, output } = this.createOutputMatrices();
- allMatrices.push(outputConnector);
- allMatrices.push(output);
- return Object.seal({
- isInitialized: true,
- hiddenLayers,
- output,
- equations: [],
- allMatrices,
- equationConnections: [],
- outputConnector,
- });
- }
- backpropagate() {
- for (let i = this.model.equations.length - 1; i > -1; i--) {
- this.model.equations[i].backpropagate();
- }
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- run(rawInput) {
- const shape = lookup.dataShape(rawInput).join(',');
- switch (shape) {
- case 'array,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.runArray(rawInput);
- case 'array,array,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.runArrayOfArray(rawInput);
- case 'object,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.runObject(rawInput); // Backward compatibility, will be result of `unknown` and need casting. Better to just use net.runObject() directly
- case 'array,object,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.runArrayOfObject(rawInput);
- default:
- throw new Error(`Unrecognized data shape ${shape}`);
- }
- }
- forecast(rawInput, count = 1) {
- const shape = lookup.dataShape(rawInput).join(',');
- switch (shape) {
- case 'array,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.forecastArray(rawInput, count);
- case 'array,array,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.forecastArrayOfArray(rawInput, count);
- case 'object,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.runObject(rawInput);
- case 'array,object,number':
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- return this.forecastArrayOfObject(rawInput, count);
- default:
- throw new Error(`Unrecognized data shape ${shape}`);
- }
- }
- forecastArray(input, count = 1) {
- this.checkRunnable();
- const { model } = this;
- const { equations } = model;
- const length = input.length + count;
- while (equations.length <= length) {
- this.bindEquation();
- }
- let lastOutput;
- let equationIndex = 0;
- if (this.options.inputSize === 1) {
- for (let i = 0; i < input.length; i++) {
- lastOutput = equations[equationIndex++].runInput(Float32Array.from([input[i]]));
- }
- }
- else {
- for (let i = 0; i < input.length; i++) {
- lastOutput = equations[equationIndex++].runInput(Float32Array.from([]));
- }
- }
- if (!lastOutput) {
- throw new Error('lastOutput not set');
- }
- const result = [lastOutput.weights[0]];
- for (let i = 0, max = count - 1; i < max; i++) {
- lastOutput = equations[equationIndex++].runInput(lastOutput.weights);
- result.push(lastOutput.weights[0]);
- }
- this.end();
- return Float32Array.from(result);
- }
- forecastArrayOfArray(input, count = 1) {
- this.checkRunnable();
- const { model } = this;
- const { equations } = model;
- const length = input.length + count;
- while (equations.length <= length) {
- this.bindEquation();
- }
- let lastOutput;
- let equationIndex = 0;
- for (let i = 0; i < input.length; i++) {
- lastOutput = equations[equationIndex++].runInput(input[i]);
- }
- if (!lastOutput) {
- throw new Error('lastOutput not set');
- }
- const result = [Float32Array.from(lastOutput.weights)];
- for (let i = 0, max = count - 1; i < max; i++) {
- lastOutput = equations[equationIndex++].runInput(lastOutput.weights);
- result.push(Float32Array.from(lastOutput.weights.slice(0)));
- }
- this.end();
- return result;
- }
- forecastArrayOfObject(input, count = 1) {
- if (!this.inputLookup) {
- throw new Error('this.inputLookup not set');
- }
- if (!this.outputLookup) {
- throw new Error('this.outputLookup not set');
- }
- const formattedData = input.map((value) => lookup.toArray(this.inputLookup, value, this.inputLookupLength));
- return this.forecastArrayOfArray(formattedData, count).map((value) => lookup.toObject(this.outputLookup, value));
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- train(data, trainOpts = {}) {
- this.trainOpts = trainOpts = {
- ...trainDefaults$1,
- ...trainOpts,
- };
- // Don't destructure here because this.setSize() can reset this.options.
- if (this.options.inputSize === 1 && this.options.outputSize === 1) {
- this.setSize(data);
- }
- this.verifySize();
- const formattedData = this.formatData(data);
- let error = Infinity;
- let i;
- this.verifyIsInitialized();
- const { iterations, errorThresh, logPeriod, callback, callbackPeriod, } = this.trainOpts;
- const log = trainOpts.log === true ? console.log : trainOpts.log;
- for (i = 0; i < iterations && error > errorThresh; i++) {
- let sum = 0;
- for (let j = 0; j < formattedData.length; j++) {
- const err = this.trainPattern(formattedData[j], true);
- sum += err;
- }
- error = sum / formattedData.length;
- if (isNaN(error))
- throw new Error('Network error rate is unexpected NaN, check network configurations and try again. Most probably input format is not correct or training data is not enough. ');
- if (log && i % logPeriod === 0) {
- log(`iterations: ${i}, training error: ${error}`);
- }
- if (callback && i % callbackPeriod === 0) {
- callback({ error, iterations: i });
- }
- }
- return {
- error,
- iterations: i,
- };
- }
- trainArrayOfArray(input) {
- if (input.length < 2) {
- throw new Error('input must be an array of 2 or more');
- }
- const { equations } = this.model;
- while (equations.length < input.length) {
- this.bindEquation();
- }
- let errorSum = 0;
- for (let i = 0, max = input.length - 1; i < max; i++) {
- errorSum += equations[i].predictTarget(input[i], input[i + 1]);
- }
- this.end();
- return errorSum / input.length;
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- trainPattern(input, logErrorRate) {
- const error = this.trainArrayOfArray(input);
- this.backpropagate();
- this.adjustWeights();
- if (logErrorRate) {
- return error;
- }
- return 0;
- }
- setSize(data) {
- let size = 0;
- const dataShape = lookup.dataShape(data).join(',');
- switch (dataShape) {
- case 'array,array,number':
- case 'array,object,number':
- case 'array,datum,array,number':
- case 'array,datum,object,number':
- size = 1;
- // probably 1
- break;
- case 'array,array,array,number':
- size = data[0][0].length;
- break;
- case 'array,array,object,number':
- // inputs and outputs should match
- size = Object.keys(lookup.toTable2D(data)).length;
- break;
- case 'array,datum,array,array,number':
- size = data[0].input[0].length;
- break;
- case 'array,datum,array,object,number':
- size = Object.keys(lookup.toInputTable2D(data)).length;
- break;
- default:
- throw new Error('unknown data shape or configuration');
- }
- this.options = Object.seal({
- ...this.options,
- inputSize: size,
- outputSize: size,
- });
- }
- verifySize() {
- if (this.options.inputSize || this.options.outputSize) {
- if (this.options.inputSize !== this.options.outputSize) {
- throw new Error('manually set inputSize and outputSize mismatch');
- }
- }
- }
- runArray(input) {
- this.checkRunnable();
- const { equations } = this.model;
- while (equations.length <= input.length) {
- this.bindEquation();
- }
- let lastOutput;
- for (let i = 0; i < input.length; i++) {
- lastOutput = equations[i].runInput(new Float32Array([input[i]]));
- }
- this.end();
- return lastOutput.weights[0];
- }
- runArrayOfArray(input) {
- this.checkRunnable();
- const { model } = this;
- const { equations } = model;
- while (equations.length <= input.length) {
- this.bindEquation();
- }
- let lastOutput;
- for (let i = 0; i < input.length; i++) {
- const outputMatrix = equations[i].runInput(input[i]);
- lastOutput = outputMatrix.weights;
- }
- this.end();
- return lastOutput !== null && lastOutput !== void 0 ? lastOutput : Float32Array.from([]);
- }
- runObject(input) {
- if (!this.inputLookup) {
- throw new Error('this.inputLookup not set');
- }
- if (!this.outputLookup) {
- throw new Error('this.outputLookup not set');
- }
- if (!this.outputLookupLength) {
- throw new Error('this.outputLookupLength not set');
- }
- if (this.inputLookup === this.outputLookup) {
- const inputArray = lookup.toArrayShort(this.inputLookup, input);
- return lookup.toObjectPartial(this.outputLookup, this.forecastArray(inputArray, this.outputLookupLength - inputArray.length), inputArray.length);
- }
- return lookup.toObject(this.outputLookup, this.forecastArray(lookup.toArray(this.inputLookup, input, this.inputLookupLength), this.outputLookupLength));
- }
- runArrayOfObject(input) {
- if (this.inputLookup === null) {
- throw new Error('this.inputLookup not set');
- }
- if (this.outputLookup === null) {
- throw new Error('this.outputLookup not set');
- }
- const formattedInput = input.map((value) => lookup.toArray(this.inputLookup, value, this.inputLookupLength));
- return this.forecastArrayOfArray(formattedInput, 1).map((value) => lookup.toObject(this.outputLookup, value))[0];
- }
- runArrayOfObjectOfArray(input) {
- if (!this.inputLookup) {
- throw new Error('this.inputLookup not set');
- }
- if (!this.outputLookup) {
- throw new Error('this.outputLookup not set');
- }
- return lookup.toObject(this.outputLookup, this.runArrayOfArray(lookup.toArrays(this.inputLookup, input, this.inputLookupLength)));
- }
- end() {
- this.model.equations[this.model.equations.length - 1].runInput(new Float32Array(this.options.outputSize));
- }
- requireInputOutputOfOne() {
- if (this.options.inputSize !== 1) {
- throw new Error('inputSize must be 1 for this data size');
- }
- if (this.options.outputSize !== 1) {
- throw new Error('outputSize must be 1 for this data size');
- }
- }
- // Handles data shape of 'array,number'
- formatArray(data) {
- const result = [];
- this.requireInputOutputOfOne();
- for (let i = 0; i < data.length; i++) {
- result.push(Float32Array.from([data[i]]));
- }
- return [result];
- }
- // Handles data shape of 'array,array,number'
- formatArrayOfArray(data) {
- const result = [];
- const { inputSize, outputSize } = this.options;
- if (inputSize === 1 && outputSize === 1) {
- for (let i = 0; i < data.length; i++) {
- result.push(arrayToFloat32Arrays(data[i]));
- }
- return result;
- }
- if (inputSize !== data[0].length) {
- throw new Error('inputSize must match data input size');
- }
- if (outputSize !== data[0].length) {
- throw new Error('outputSize must match data output size');
- }
- for (let i = 0; i < data.length; i++) {
- result.push(Float32Array.from(data[i]));
- }
- return [result];
- }
- // Handles data shape of 'array,object,number'
- formatArrayOfObject(data) {
- this.requireInputOutputOfOne();
- if (!this.inputLookup) {
- const lookupTable = new LookupTable(data);
- this.inputLookup = this.outputLookup = lookupTable.table;
- this.inputLookupLength = this.outputLookupLength = lookupTable.length;
- }
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push(objectToFloat32Arrays(data[i]));
- }
- return result;
- }
- // Handles data shape of 'array,object,number' when this.options.inputSize > 1
- formatArrayOfObjectMulti(data) {
- if (!this.inputLookup) {
- const lookupTable = new LookupTable(data);
- this.inputLookup = this.outputLookup = lookupTable.table;
- this.inputLookupLength = this.outputLookupLength = lookupTable.length;
- }
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push([
- objectToFloat32Array(data[i], this.inputLookup, this.inputLookupLength),
- ]);
- }
- return result;
- }
- // Handles data shape of 'array,datum,array,number'
- formatArrayOfDatumOfArray(data) {
- const result = [];
- this.requireInputOutputOfOne();
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- result.push(inputOutputArrayToFloat32Arrays(datum.input, datum.output));
- }
- return result;
- }
- // Handles data shape of 'array,datum,object,number'
- formatArrayOfDatumOfObject(data) {
- this.requireInputOutputOfOne();
- if (!this.inputLookup) {
- const inputLookup = new LookupTable(data, 'input');
- this.inputLookup = inputLookup.table;
- this.inputLookupLength = inputLookup.length;
- }
- if (!this.outputLookup) {
- const outputLookup = new LookupTable(data, 'output');
- this.outputLookup = outputLookup.table;
- this.outputLookupLength = outputLookup.length;
- }
- const result = [];
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- result.push(inputOutputObjectToFloat32Arrays(datum.input, datum.output));
- }
- return result;
- }
- // Handles data shape of 'array,array,array,number'
- formatArrayOfArrayOfArray(data) {
- const result = [];
- for (let i = 0; i < data.length; i++) {
- result.push(arraysToFloat32Arrays(data[i]));
- }
- return result;
- }
- // Handles data shape of 'array,array,object,number'
- formatArrayOfArrayOfObject(data) {
- if (!this.inputLookup) {
- const lookupTable = new LookupTable(data);
- this.inputLookup = this.outputLookup = lookupTable.table;
- this.inputLookupLength = this.outputLookupLength = lookupTable.length;
- }
- const result = [];
- for (let i = 0; i < data.length; i++) {
- const array = [];
- for (let j = 0; j < data[i].length; j++) {
- array.push(objectToFloat32Array(data[i][j], this.inputLookup, this.inputLookupLength));
- }
- result.push(array);
- }
- return result;
- }
- // Handles data shape of 'array,datum,array,array,number'
- formatArrayOfDatumOfArrayOfArray(data) {
- const result = [];
- const { inputSize, outputSize } = this.options;
- if (inputSize !== data[0].input[0].length) {
- throw new Error('inputSize must match data input size');
- }
- if (outputSize !== data[0].output[0].length) {
- throw new Error('outputSize must match data output size');
- }
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- result.push(inputOutputArraysToFloat32Arrays(datum.input, datum.output));
- }
- return result;
- }
- // 'Handles data shape of array,datum,array,object,number'
- formatArrayOfDatumOfArrayOfObject(data) {
- if (!this.inputLookup) {
- const inputLookup = new ArrayLookupTable(data, 'input');
- this.inputLookup = inputLookup.table;
- this.inputLookupLength = inputLookup.length;
- }
- if (!this.outputLookup) {
- const outputLookup = new ArrayLookupTable(data, 'output');
- this.outputLookup = outputLookup.table;
- this.outputLookupLength = outputLookup.length;
- }
- if (!this.outputLookupLength) {
- throw new Error('this.outputLookupLength not set to usable number');
- }
- const result = [];
- for (let i = 0; i < data.length; i++) {
- const datum = data[i];
- result.push(inputOutputObjectsToFloat32Arrays(datum.input, datum.output, this.inputLookup, this.outputLookup, this.inputLookupLength, this.outputLookupLength));
- }
- return result;
- }
- formatData(data) {
- const dataShape = lookup.dataShape(data).join(',');
- switch (dataShape) {
- case 'array,number':
- return this.formatArray(data);
- case 'array,array,number':
- return this.formatArrayOfArray(data);
- case 'array,object,number':
- if (this.options.inputSize === 1) {
- return this.formatArrayOfObject(data);
- }
- else {
- return this.formatArrayOfObjectMulti(data);
- }
- case 'array,datum,array,number':
- return this.formatArrayOfDatumOfArray(data);
- case 'array,datum,object,number':
- return this.formatArrayOfDatumOfObject(data);
- case 'array,array,array,number':
- return this.formatArrayOfArrayOfArray(data);
- case 'array,array,object,number':
- return this.formatArrayOfArrayOfObject(data);
- case 'array,datum,array,array,number':
- return this.formatArrayOfDatumOfArrayOfArray(data);
- case 'array,datum,array,object,number':
- return this.formatArrayOfDatumOfArrayOfObject(data);
- default:
- throw new Error('unknown data shape or configuration');
- }
- }
- test(data) {
- // for classification problems
- const misclasses = [];
- // run each pattern through the trained network and collect
- // error and misclassification statistics
- let errorSum = 0;
- const formattedData = this.formatData(data);
- for (let i = 0; i < formattedData.length; i++) {
- const input = formattedData[i];
- const output = this.run(input.splice(0, input.length - 1));
- const target = input[input.length - 1];
- let errors = 0;
- let errorCount = 0;
- for (let j = 0; j < output.length; j++) {
- errorCount++;
- const error = target[j] - output[j];
- // mse
- errors += error * error;
- }
- errorSum += errors / errorCount;
- const errorsAbs = Math.abs(errors);
- if (errorsAbs > this.trainOpts.errorThresh) {
- const misclass = data[i];
- misclasses.push({
- value: misclass,
- actual: output,
- });
- }
- }
- return {
- error: errorSum / formattedData.length,
- misclasses,
- total: formattedData.length,
- };
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- addFormat(value) {
- var _a, _b, _c, _d, _e, _f;
- const dataShape = lookup.dataShape(value).join(',');
- switch (dataShape) {
- case 'array,array,number':
- case 'datum,array,array,number':
- case 'array,number':
- case 'datum,array,number':
- return;
- case 'datum,object,number': {
- this.inputLookup = lookup.addKeys(value.input, (_a = this.inputLookup) !== null && _a !== void 0 ? _a : {});
- if (this.inputLookup) {
- this.inputLookupLength = Object.keys(this.inputLookup).length;
- }
- this.outputLookup = lookup.addKeys(value.output, (_b = this.outputLookup) !== null && _b !== void 0 ? _b : {});
- if (this.outputLookup) {
- this.outputLookupLength = Object.keys(this.outputLookup).length;
- }
- break;
- }
- case 'object,number': {
- this.inputLookup = this.outputLookup = lookup.addKeys(value, (_c = this.inputLookup) !== null && _c !== void 0 ? _c : {});
- if (this.inputLookup) {
- this.inputLookupLength = this.outputLookupLength = Object.keys(this.inputLookup).length;
- }
- break;
- }
- case 'array,object,number': {
- const typedValue = value;
- for (let i = 0; i < typedValue.length; i++) {
- this.inputLookup = this.outputLookup = lookup.addKeys(typedValue[i], (_d = this.inputLookup) !== null && _d !== void 0 ? _d : {});
- if (this.inputLookup) {
- this.inputLookupLength = this.outputLookupLength = Object.keys(this.inputLookup).length;
- }
- }
- break;
- }
- case 'datum,array,object,number': {
- const typedValue = value;
- const typedInput = typedValue.input;
- for (let i = 0; i < typedInput.length; i++) {
- this.inputLookup = lookup.addKeys(typedInput[i], (_e = this.inputLookup) !== null && _e !== void 0 ? _e : {});
- if (this.inputLookup) {
- this.inputLookupLength = Object.keys(this.inputLookup).length;
- }
- }
- const typedOutput = typedValue.output;
- for (let i = 0; i < typedOutput.length; i++) {
- this.outputLookup = lookup.addKeys(typedOutput[i], (_f = this.outputLookup) !== null && _f !== void 0 ? _f : {});
- if (this.outputLookup) {
- this.outputLookupLength = Object.keys(this.outputLookup).length;
- }
- }
- break;
- }
- default:
- throw new Error('unknown data shape or configuration');
- }
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- toJSON() {
- if (!this.model) {
- this.initialize();
- }
- const { model } = this;
- const options = { ...this.options, ...defaults$1 };
- return {
- type: this.constructor.name,
- options,
- hiddenLayers: model.hiddenLayers.map((hiddenLayer) => {
- const layers = {};
- for (const p in hiddenLayer) {
- if (!hiddenLayer.hasOwnProperty(p))
- continue;
- layers[p] = hiddenLayer[p].toJSON();
- }
- return layers;
- }),
- outputConnector: model.outputConnector.toJSON(),
- output: model.output.toJSON(),
- inputLookup: this.inputLookup,
- inputLookupLength: this.inputLookupLength,
- outputLookup: this.outputLookup,
- outputLookupLength: this.outputLookupLength,
- };
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- fromJSON(json) {
- const { options } = json;
- const allMatrices = [];
- const hiddenLayers = [];
- // backward compatibility for hiddenSizes
- json.hiddenLayers.forEach((hiddenLayer) => {
- const layers = {};
- for (const p in hiddenLayer) {
- layers[p] = Matrix.fromJSON(hiddenLayer[p]);
- allMatrices.push(layers[p]);
- }
- hiddenLayers.push(layers);
- });
- const outputConnector = Matrix.fromJSON(json.outputConnector);
- allMatrices.push(outputConnector);
- const output = Matrix.fromJSON(json.output);
- allMatrices.push(output);
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- this.options = { ...defaults(), ...options };
- this.inputLookup = json.inputLookup;
- this.inputLookupLength = json.inputLookupLength;
- this.outputLookup = json.outputLookup;
- this.outputLookupLength = json.outputLookupLength;
- this.model = Object.seal({
- isInitialized: true,
- hiddenLayers,
- output,
- allMatrices,
- outputConnector,
- equations: [],
- equationConnections: [],
- });
- this.initialLayerInputs = options.hiddenLayers.map((size) => new Matrix(size, 1));
- this.bindEquation();
- return this;
- }
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-expect-error
- toFunction(cb) {
- const { model, inputLookup, inputLookupLength, outputLookup, outputLookupLength, } = this;
- const { inputSize } = this.options;
- const { equations } = model;
- const equation = equations[1];
- const { states } = equation;
- const jsonString = JSON.stringify(this.toJSON());
- function previousConnectionIndex(m) {
- const connection = model.equationConnections[0];
- const { states } = equations[0];
- for (let i = 0, max = states.length; i < max; i++) {
- if (states[i].product === m) {
- return i;
- }
- }
- return connection.indexOf(m);
- }
- function matrixOrigin(m, stateIndex) {
- for (let i = 0, max = states.length; i < max; i++) {
- const state = states[i];
- if (i === stateIndex) {
- const j = previousConnectionIndex(m);
- switch (m) {
- case state.left:
- if (j > -1) {
- return `typeof prevStates[${j}] === 'object' ? prevStates[${j}].product : new Matrix(${m.rows}, ${m.columns})`;
- }
- // eslint-disable-next-line no-fallthrough
- case state.right:
- if (j > -1) {
- return `typeof prevStates[${j}] === 'object' ? prevStates[${j}].product : new Matrix(${m.rows}, ${m.columns})`;
- }
- // eslint-disable-next-line no-fallthrough
- case state.product:
- return `new Matrix(${m.rows}, ${m.columns})`;
- default:
- throw Error('unknown state');
- }
- }
- if (m === state.product)
- return `states[${i}].product`;
- if (m === state.right)
- return `states[${i}].right`;
- if (m === state.left)
- return `states[${i}].left`;
- }
- return '';
- }
- function matrixToString(m, stateIndex) {
- if (!m || !m.rows || !m.columns)
- return 'null';
- if (m === model.outputConnector)
- return `json.outputConnector`;
- if (m === model.output)
- return `json.output`;
- for (let i = 0, max = model.hiddenLayers.length; i < max; i++) {
- const hiddenLayer = model.hiddenLayers[i];
- for (const p in hiddenLayer) {
- if (!hiddenLayer.hasOwnProperty(p))
- continue;
- if (hiddenLayer[p] !== m)
- continue;
- return `json.hiddenLayers[${i}].${p}`;
- }
- }
- return matrixOrigin(m, stateIndex);
- }
- function formatInputData() {
- if (!inputLookup)
- return '';
- if (inputSize === 1) {
- if (inputLookup === outputLookup) {
- return `function lookupInput(input) {
- var table = ${JSON.stringify(inputLookup)};
- var result = [];
- for (var p in table) {
- if (!input.hasOwnProperty(p)) break;
- result.push(Float32Array.from([input[p]]));
- }
- return result;
- }`;
- }
- return `function lookupInput(input) {
- var table = ${JSON.stringify(inputLookup)};
- var result = [];
- for (var p in table) {
- result.push(Float32Array.from([input[p]]));
- }
- return result;
- }`;
- }
- return `function lookupInput(rawInputs) {
- var table = ${JSON.stringify(inputLookup)};
- var result = [];
- for (var i = 0; i < rawInputs.length; i++) {
- var rawInput = rawInputs[i];
- var input = new Float32Array(${inputLookupLength});
- for (var p in table) {
- input[table[p]] = rawInput.hasOwnProperty(p) ? rawInput[p] : 0;
- }
- result.push(input);
- }
- return result;
- }`;
- }
- function formatOutputData() {
- if (!outputLookup)
- return '';
- if (inputSize === 1) {
- if (inputLookup === outputLookup) {
- return `function lookupOutputPartial(output, input) {
- var table = ${JSON.stringify(outputLookup)};
- var offset = input.length;
- var result = {};
- var i = 0;
- for (var p in table) {
- if (i++ < offset) continue;
- result[p] = output[table[p] - offset][0];
- }
- return result;
- }`;
- }
- return `function lookupOutput(output) {
- var table = ${JSON.stringify(outputLookup)};
- var result = {};
- for (var p in table) {
- result[p] = output[table[p]][0];
- }
- return result;
- }`;
- }
- return `function lookupOutput(output) {
- var table = ${JSON.stringify(outputLookup)};
- var result = {};
- for (var p in table) {
- result[p] = output[table[p]];
- }
- return result;
- }`;
- }
- function toInner(fnString) {
- // crude, but should be sufficient for now
- // function() { body }
- // crude, but should be sufficient for now
- // function() { body }
- const fnParts = fnString.toString().split('{');
- fnParts.shift();
- // body }
- const fnBodyString = fnParts.join('{');
- const fnBodyParts = fnBodyString.split('}');
- fnBodyParts.pop();
- // body
- return fnBodyParts
- .join('}')
- .split('\n')
- .join('\n ')
- .replace('product.deltas[i] = 0;', '')
- .replace('product.deltas[column] = 0;', '')
- .replace('left.deltas[leftIndex] = 0;', '')
- .replace('right.deltas[rightIndex] = 0;', '')
- .replace('product.deltas = left.deltas.slice(0);', '');
- }
- function fileName(fnName) {
- return `src/recurrent/matrix/${fnName.replace(/[A-Z]/g, function (value) {
- return `-${value.toLowerCase()}`;
- })}.js`;
- }
- const statesRaw = [];
- const usedFunctionNames = {};
- const innerFunctionsSwitch = [];
- for (let i = 0, max = states.length; i < max; i++) {
- const state = states[i];
- statesRaw.push(`states[${i}] = {
- name: '${state.forwardFn.name}',
- left: ${state.left ? matrixToString(state.left, i) : 'undefined'},
- right: ${state.right ? matrixToString(state.right, i) : 'undefined'},
- product: ${matrixToString(state.product, i)}
- }`);
- const fnName = state.forwardFn.name;
- if (!usedFunctionNames[fnName]) {
- usedFunctionNames[fnName] = true;
- if (state.name === 'input') {
- innerFunctionsSwitch.push(`case '${fnName}':`);
- innerFunctionsSwitch.push(inputLookup && inputSize === 1
- ? 'product.weights = _i < input.length ? input[_i]: prevStates[prevStates.length - 1].product.weights;'
- : inputSize === 1
- ? 'product.weights = [input[_i]];'
- : 'product.weights = input[_i];');
- innerFunctionsSwitch.push('break;');
- }
- else {
- innerFunctionsSwitch.push(` case '${fnName}':${fnName !== 'forwardFn'
- ? ` //compiled from ${fileName(fnName)}`
- : ''}
- ${toInner(state.forwardFn.toString())}
- break;`);
- }
- }
- }
- const forceForecast = inputSize === 1 && this.outputLookup;
- const src = `
- var input = ${this.inputLookup ? 'lookupInput(rawInput)' : 'rawInput'};
- var json = ${jsonString};
- var output = [];
- var states = [];
- var prevStates;
- var state;
- var max = ${forceForecast
- ? inputLookup === outputLookup
- ? inputLookupLength
- : `input.length + ${outputLookupLength - 1}`
- : 'input.length'};
- for (var _i = 0; _i < max; _i++) {
- prevStates = states;
- states = [];
- ${statesRaw.join(';\n ')};
- for (var stateIndex = 0, stateMax = ${statesRaw.length}; stateIndex < stateMax; stateIndex++) {
- state = states[stateIndex];
- var product = state.product;
- var left = state.left;
- var right = state.right;
- switch (state.name) {
- ${innerFunctionsSwitch.join('\n')}
- }
- }
- ${inputSize === 1 && inputLookup
- ? 'if (_i >= input.length - 1) { output.push(state.product.weights); }'
- : 'output = state.product.weights;'}
- }
- ${outputLookup
- ? outputLookup === inputLookup
- ? 'return lookupOutputPartial(output, input)'
- : 'return lookupOutput(output)'
- : inputSize === 1
- ? 'return output[0]'
- : 'return output'};
- ${formatInputData()}
- ${formatOutputData()}
- function Matrix(rows, columns) {
- this.rows = rows;
- this.columns = columns;
- this.weights = zeros(rows * columns);
- }
- ${zeros$1.toString()}
- ${softmax.toString().replace('_2.default', 'Matrix')}
- ${randomFloat.toString()}
- ${sampleI.toString()}
- ${maxI.toString()}`;
- // eslint-disable-next-line
- return new Function('rawInput', cb ? cb(src) : src);
- }
- }
- const trainDefaults = { ...trainDefaults$1 };
- class GRUTimeStep extends RNNTimeStep {
- getHiddenLayer(hiddenSize, prevSize) {
- return getGRUHiddenLayer(hiddenSize, prevSize);
- }
- getEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- return getGRUEquation(equation, inputMatrix, previousResult, hiddenLayer);
- }
- }
- class LSTM extends RNN {
- getHiddenLayer(hiddenSize, prevSize) {
- return getHiddenLSTMLayer(hiddenSize, prevSize);
- }
- getEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- return getLSTMEquation(equation, inputMatrix, previousResult, hiddenLayer);
- }
- }
- function getHiddenLSTMLayer(hiddenSize, prevSize) {
- return {
- // gates parameters
- // wix
- inputMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- inputHidden: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- inputBias: new Matrix(hiddenSize, 1),
- // wfx
- forgetMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- forgetHidden: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- forgetBias: new Matrix(hiddenSize, 1),
- // wox
- outputMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- outputHidden: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- outputBias: new Matrix(hiddenSize, 1),
- // cell write params
- // wcx
- cellActivationMatrix: new RandomMatrix(hiddenSize, prevSize, 0.08),
- cellActivationHidden: new RandomMatrix(hiddenSize, hiddenSize, 0.08),
- cellActivationBias: new Matrix(hiddenSize, 1),
- };
- }
- function getLSTMEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- if (!hiddenLayer.inputMatrix ||
- !hiddenLayer.inputHidden ||
- !hiddenLayer.inputBias ||
- !hiddenLayer.forgetMatrix ||
- !hiddenLayer.forgetHidden ||
- !hiddenLayer.forgetBias ||
- !hiddenLayer.outputMatrix ||
- !hiddenLayer.outputHidden ||
- !hiddenLayer.outputBias ||
- !hiddenLayer.cellActivationMatrix ||
- !hiddenLayer.cellActivationHidden ||
- !hiddenLayer.cellActivationBias) {
- throw new Error('hiddenLayer does not have expected properties');
- }
- const sigmoid = equation.sigmoid.bind(equation);
- const add = equation.add.bind(equation);
- const multiply = equation.multiply.bind(equation);
- const multiplyElement = equation.multiplyElement.bind(equation);
- const tanh = equation.tanh.bind(equation);
- const inputGate = sigmoid(add(add(multiply(hiddenLayer.inputMatrix, inputMatrix), multiply(hiddenLayer.inputHidden, previousResult)), hiddenLayer.inputBias));
- const forgetGate = sigmoid(add(add(multiply(hiddenLayer.forgetMatrix, inputMatrix), multiply(hiddenLayer.forgetHidden, previousResult)), hiddenLayer.forgetBias));
- // output gate
- const outputGate = sigmoid(add(add(multiply(hiddenLayer.outputMatrix, inputMatrix), multiply(hiddenLayer.outputHidden, previousResult)), hiddenLayer.outputBias));
- // write operation on cells
- const cellWrite = tanh(add(add(multiply(hiddenLayer.cellActivationMatrix, inputMatrix), multiply(hiddenLayer.cellActivationHidden, previousResult)), hiddenLayer.cellActivationBias));
- // compute new cell activation
- const retainCell = multiplyElement(forgetGate, previousResult); // what do we keep from cell
- const writeCell = multiplyElement(inputGate, cellWrite); // what do we write to cell
- const cell = add(retainCell, writeCell); // new cell contents
- // compute hidden state as gated, saturated cell activations
- return multiplyElement(outputGate, tanh(cell));
- }
- class LSTMTimeStep extends RNNTimeStep {
- getHiddenLayer(hiddenSize, prevSize) {
- return getHiddenLSTMLayer(hiddenSize, prevSize);
- }
- getEquation(equation, inputMatrix, previousResult, hiddenLayer) {
- return getLSTMEquation(equation, inputMatrix, previousResult, hiddenLayer);
- }
- }
- /**
- *
- * @param start
- * @param end
- * @returns {Array}
- */
- function range(start, end) {
- const result = [];
- for (; start < end; start++) {
- result.push(start);
- }
- return result;
- }
- function toArray(values) {
- if (Array.isArray(values)) {
- return Float32Array.from(values);
- }
- return Float32Array.from(Object.values(values));
- }
- function drawInput({ pixelX, pixelY, radius, inputs, row, line, fontSize, fontClassName, }) {
- let svg = `<rect
- x="${pixelX / 2 - radius}"
- y="${pixelY / 2 + row * pixelY - radius}"
- width="${2 * radius}"
- height="${2 * radius}"
- stroke="black"
- stroke-width="1"
- fill="${inputs.color}"
- class="${inputs.className}" />
- <line
- x1="${pixelX / 4}"
- y1="${pixelY / 2 + row * pixelY}"
- x2="${pixelX / 2 - radius}"
- y2="${pixelY / 2 + row * pixelY}"
- style="stroke:${line.color};stroke-width:${line.width}"
- class="${line.className}" />`;
- if (inputs.labels) {
- svg += `<text
- x="${pixelX / 8}"
- y="${pixelY / 2 + row * pixelY - 5}"
- fill="black"
- font-size="${fontSize}"
- class="${fontClassName}">${inputs.labels[row]}</text>`;
- }
- return svg;
- }
- function drawNeuron({ pixelX, pixelY, row, column, radius, hidden, }) {
- return `<circle
- cx="${pixelX / 2 + column * pixelX}"
- cy="${pixelY / 2 + row * pixelY}"
- r="${radius}"
- stroke="black"
- stroke-width="1"
- fill="${hidden.color}"
- class="${hidden.className}" />`;
- }
- function drawOutput({ pixelX, pixelY, row, column, line, outputs, radius, }) {
- return `<circle
- cx="${pixelX / 2 + column * pixelX}"
- cy="${pixelY / 2 + row * pixelY}"
- r="${radius}"
- stroke="black"
- stroke-width="1"
- fill="${outputs.color}"
- class="${outputs.className}" />
- <line
- x1="${pixelX / 2 + column * pixelX + radius}"
- y1="${pixelY / 2 + row * pixelY}"
- x2="${pixelX / 2 + column * pixelX + pixelX / 4}"
- y2="${pixelY / 2 + row * pixelY}"
- style="stroke:${line.color};stroke-width:${line.width}"
- class="${line.className}" />`;
- }
- function drawBackwardConnections({ pixelX, pixelY, row, column, radius, lineY, line, previousConnectionIndex, }) {
- return `<line
- x1="${pixelX / 2 + (column - 1) * pixelX + radius}"
- y1="${lineY / 2 + previousConnectionIndex * lineY}"
- x2="${pixelX / 2 + column * pixelX - radius}"
- y2="${pixelY / 2 + row * pixelY}"
- style="stroke:${line.color};stroke-width:${line.width}"
- class="${line.className}" />`;
- }
- function neuralNetworkToInnerSVG(options) {
- const { sizes, height, width } = options;
- let svg = '';
- const pixelX = width / sizes.length;
- for (let column = 0; column < sizes.length; column++) {
- const size = sizes[column];
- const pixelY = height / size;
- for (let row = 0; row < size; row++) {
- if (column === 0) {
- svg += drawInput({ pixelX, pixelY, row, column, ...options });
- }
- else {
- if (column === sizes.length - 1) {
- svg += drawOutput({ pixelX, pixelY, row, column, ...options });
- }
- else {
- svg += drawNeuron({ pixelX, pixelY, row, column, ...options });
- }
- const previousSize = sizes[column - 1];
- const lineY = height / previousSize;
- for (let previousConnectionIndex = 0; previousConnectionIndex < previousSize; previousConnectionIndex++) {
- svg += drawBackwardConnections({
- pixelX,
- pixelY,
- row,
- column,
- lineY,
- previousConnectionIndex,
- ...options,
- });
- }
- }
- }
- }
- return svg;
- }
- function drawRecurrentConnections({ pixelX, pixelY, row, column, radius, recurrentLine, }) {
- const moveX = pixelX / 2 + column * pixelX + radius + 1;
- const moveY = pixelY / 2 + row * pixelY;
- const x = moveX - radius * 2 - 2;
- const y = moveY;
- const x1 = x + 100;
- const y1 = y + 50;
- const x2 = moveX - 100;
- const y2 = moveY + 50;
- return `<path
- d="M ${moveX} ${moveY} C ${x1} ${y1}, ${x2} ${y2}, ${x} ${y}"
- stroke="${recurrentLine.color}"
- stroke-width="${recurrentLine.width}"
- fill="transparent"
- stroke-linecap="round"
- marker-end="url(#arrow)"
- class="${recurrentLine.className}" />`;
- }
- function rnnToInnerSVG(options) {
- const { width, height, recurrentLine, sizes, radius } = options;
- const pixelX = width / sizes.length;
- let svg = `<defs>
- <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
- <path d="M0,0 L0,6 L9,3 z" fill="${recurrentLine.color}" />
- </marker>
- </defs>`;
- svg += neuralNetworkToInnerSVG(options);
- for (let column = 1; column < sizes.length; column++) {
- const size = sizes[column];
- const pixelY = height / size;
- for (let row = 0; row < size; row++) {
- svg += drawRecurrentConnections({
- pixelX,
- pixelY,
- row,
- column,
- radius,
- recurrentLine,
- });
- }
- }
- return svg;
- }
- function getFeedForwardLayers(network) {
- const { options } = network;
- if (!options) {
- throw new Error('options not defined');
- }
- if (!options.inputLayer) {
- throw new Error('options.inputLater not defined');
- }
- if (!options.hiddenLayers) {
- throw new Error('options.hiddenLayers not defined');
- }
- if (options.hiddenLayers.length < 1) {
- throw new Error('options.hiddenLayers is empty');
- }
- if (!options.outputLayer) {
- throw new Error('options.outputLayer not defined');
- }
- const inputLayer = options.inputLayer();
- const hiddenLayers = [];
- hiddenLayers.push(options.hiddenLayers[0](inputLayer, 0));
- for (let i = 1; i < options.hiddenLayers.length; i++) {
- hiddenLayers.push(options.hiddenLayers[i](hiddenLayers[i - 1], i));
- }
- const outputLayer = options.outputLayer(hiddenLayers[hiddenLayers.length - 1], hiddenLayers.length);
- return {
- inputSize: inputLayer.height,
- hiddenLayers: hiddenLayers.map((hiddenLayer) => hiddenLayer.height),
- outputSize: outputLayer.height,
- };
- }
- function getRecurrentLayers(network) {
- const hiddenLayers = [];
- const { options } = network;
- if (!options.inputLayer) {
- throw new Error('inputLayer not defined');
- }
- if (!options.outputLayer) {
- throw new Error('outputLayer not defined');
- }
- const inputLayer = options.inputLayer();
- hiddenLayers.push(options.hiddenLayers[0](inputLayer, recurrentZeros(), 0));
- for (let i = 1; i < options.hiddenLayers.length; i++) {
- hiddenLayers.push(options.hiddenLayers[i](hiddenLayers[i - 1], recurrentZeros(), i));
- }
- const outputLayer = options.outputLayer(hiddenLayers[hiddenLayers.length - 1], -1);
- return {
- inputSize: inputLayer.height,
- hiddenLayers: hiddenLayers.map((hiddenLayer) => hiddenLayer.height),
- outputSize: outputLayer.height,
- };
- }
- function wrapOuterSVG(svgBody, width, height) {
- // language=html
- return `<svg
- xmlns="http://www.w3.org/2000/svg"
- xmlns:xlink="http://www.w3.org/1999/xlink"
- version="1.1"
- width="${width}"
- height="${height}">${svgBody}</svg>`;
- }
- function getNeuralNetworkJSONSizes(json) {
- return json.sizes;
- }
- function getNeuralNetworkSizes(net) {
- const { options, sizes } = net;
- const { inputSize, outputSize, hiddenLayers } = options;
- if (!sizes) {
- if (typeof inputSize === 'number' && inputSize < 1) {
- throw new Error('inputSize not set');
- }
- if (typeof outputSize === 'number' && outputSize < 1) {
- throw new Error('outputSize not set');
- }
- if (hiddenLayers === null || hiddenLayers === void 0 ? void 0 : hiddenLayers.some((v) => v < 1)) {
- throw new Error('hiddenLayers not set');
- }
- }
- return typeof inputSize === 'number' &&
- Array.isArray(hiddenLayers) &&
- typeof outputSize === 'number'
- ? [inputSize].concat(hiddenLayers).concat([outputSize])
- : sizes;
- }
- function getRNNSizes(net) {
- const { options } = net;
- const { inputSize, outputSize, hiddenLayers } = options;
- return [inputSize].concat(hiddenLayers).concat([outputSize]);
- }
- function defaultOptions() {
- return {
- line: {
- width: 0.5,
- color: 'black',
- className: 'connection',
- },
- recurrentLine: {
- width: 1,
- color: 'red',
- className: 'recurrence',
- },
- inputs: {
- color: 'rgba(0, 128, 0, 0.5)',
- labels: null,
- className: 'input',
- },
- outputs: {
- color: 'rgba(100, 149, 237, 0.5)',
- className: 'output',
- },
- hidden: {
- color: 'rgba(255, 127, 80, 0.5)',
- className: 'hidden-neuron',
- },
- fontSize: '14px',
- fontClassName: 'label',
- radius: 8,
- width: 400,
- height: 250,
- sizes: [],
- };
- }
- function toSVG(net, options) {
- const mergedOptions = { ...defaultOptions(), ...options };
- const { width, height, inputs } = mergedOptions;
- // Get network size array for NeuralNetwork or NeuralNetworkGPU
- let sizes = [];
- if (net instanceof NeuralNetwork || net instanceof NeuralNetworkGPU) {
- sizes = getNeuralNetworkSizes(net);
- }
- // get network size for Recurrent
- else if (net instanceof Recurrent) {
- const { inputSize, hiddenLayers, outputSize } = getRecurrentLayers(net);
- sizes = [inputSize].concat(hiddenLayers).concat([outputSize]);
- }
- // get network size for FeedForward
- else if (net instanceof FeedForward) {
- const { inputSize, hiddenLayers, outputSize } = getFeedForwardLayers(net);
- sizes = [inputSize].concat(hiddenLayers).concat([outputSize]);
- }
- // handle json, recurrent first
- else if (net instanceof RNN ||
- net instanceof LSTM ||
- net instanceof GRU ||
- net instanceof RNNTimeStep ||
- net instanceof LSTMTimeStep ||
- net instanceof GRUTimeStep) {
- return wrapOuterSVG(rnnToInnerSVG({
- ...mergedOptions,
- sizes: checkSizes(getRNNSizes(net), inputs.labels),
- }), width, height);
- }
- // handle json, NeuralNetwork
- else if (net.hasOwnProperty('type')) {
- switch (net.type) {
- case 'NeuralNetwork':
- case 'NeuralNetworkGPU':
- return wrapOuterSVG(neuralNetworkToInnerSVG({
- ...mergedOptions,
- sizes: checkSizes(getNeuralNetworkJSONSizes(net), inputs.labels),
- }), width, height);
- case 'RNN':
- case 'GRU':
- case 'LSTM':
- case 'RNNTimeStep':
- case 'GRUTimeStep':
- case 'LSTMTimeStep':
- return wrapOuterSVG(rnnToInnerSVG({
- ...mergedOptions,
- sizes: checkSizes(getRNNSizes(net), inputs.labels),
- }), width, height);
- default:
- throw new Error('unrecognized network');
- }
- }
- else if (net.hasOwnProperty('inputSize') &&
- net.hasOwnProperty('hiddenLayers') &&
- net.hasOwnProperty('outputSize')) {
- const { inputSize, hiddenLayers, outputSize } = net;
- sizes = [inputSize, ...hiddenLayers, outputSize];
- }
- else if (net.hasOwnProperty('sizes')) {
- sizes = net.sizes;
- }
- else {
- throw new Error('unrecognized network');
- }
- return wrapOuterSVG(neuralNetworkToInnerSVG({
- ...mergedOptions,
- sizes: checkSizes(sizes, inputs.labels),
- }), width, height);
- }
- function checkSizes(sizes, labels) {
- if (!sizes) {
- throw new Error('sizes not set');
- }
- if (sizes.some((size) => size < 1)) {
- throw new Error('sizes not set correctly');
- }
- if (labels && labels.length !== sizes[0]) {
- throw new Error('not enough labels for inputs');
- }
- return sizes;
- }
- const recurrent = {
- RNNTimeStep,
- LSTMTimeStep,
- GRUTimeStep,
- RNN,
- LSTM,
- GRU,
- };
- const utilities = {
- max,
- mse: mse$1,
- ones: ones$1,
- ones2D,
- random: random$1,
- randomWeight,
- randos,
- range,
- toArray,
- DataFormatter,
- zeros: zeros$1,
- toSVG,
- };
- exports.CrossValidate = CrossValidate;
- exports.FeedForward = FeedForward;
- exports.NeuralNetwork = NeuralNetwork;
- exports.NeuralNetworkGPU = NeuralNetworkGPU;
- exports.Recurrent = Recurrent;
- exports.activation = index$1;
- exports.layer = layer;
- exports.layerTypes = layerTypes;
- exports.likely = likely;
- exports.lookup = lookup;
- exports.praxis = index;
- exports.recurrent = recurrent;
- exports.utilities = utilities;
- Object.defineProperty(exports, '__esModule', { value: true });
- })));
- //# sourceMappingURL=brain-browser.js.map
|