From 9a29edf23922bc676ec875f274918a19f6b6de36 Mon Sep 17 00:00:00 2001 From: Mehmet Mert Yildiran Date: Sun, 5 Jul 2020 00:47:31 +0300 Subject: [PATCH 1/3] Add an example to demonstrate the training over an audio-visual data --- .gitignore | 3 + examples/audio_visual.py | 118 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 examples/audio_visual.py diff --git a/.gitignore b/.gitignore index b862943..a0d6d05 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,6 @@ examples/*_test.py # Visual Studio Code .vscode/ + +# Training videos +examples/videos diff --git a/examples/audio_visual.py b/examples/audio_visual.py new file mode 100644 index 0000000..0a2d62e --- /dev/null +++ b/examples/audio_visual.py @@ -0,0 +1,118 @@ +import time +import cv2 +import numpy as np +from pydub import AudioSegment +from pydub.playback import play +import cplexus as plexus + +VIDEO_FILE = 'videos/lower3.mp4' + +audio = AudioSegment.from_file(VIDEO_FILE, "mp4") +audio_samples = audio.get_array_of_samples() + +print('Normalizing audio...') +max_hz = max(audio_samples) +min_hz = min(audio_samples) +#audio_samples = [x + (abs(min_hz)) for x in audio_samples] +#audio_samples = [x / (max_hz + abs(min_hz)) for x in audio_samples] +audio_samples = np.array(audio_samples) +audio_samples = audio_samples + (abs(min_hz)) +audio_samples = np.true_divide(audio_samples, (max_hz + abs(min_hz))) + +cap = cv2.VideoCapture(VIDEO_FILE) +frameCount = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) +frameWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) +frameHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + +buf = np.empty((frameHeight, frameWidth, 3), np.dtype('uint8')) + +ret = True +chunk_size = int((1) / frameCount * len(audio_samples)) - int(0 / frameCount * len(audio_samples)) + +SIZE = chunk_size + frameWidth * frameHeight * 3 + 2048 +INPUT_SIZE = chunk_size +OUTPUT_SIZE = frameWidth * frameHeight * 3 +CONNECTIVITY = 16 / SIZE +PRECISION = 3 + +TRAINING_DURATION = 0.01 +RANDOMLY_FIRE = False +DYNAMIC_OUTPUT = False +VISUALIZATION = False + +net = plexus.Network( + SIZE, + INPUT_SIZE, + OUTPUT_SIZE, + CONNECTIVITY, + PRECISION, + RANDOMLY_FIRE, + DYNAMIC_OUTPUT, + VISUALIZATION +) + +print("\n*** LEARNING ***") +for n in range(1): + cap.set(cv2.CAP_PROP_POS_FRAMES, 0) + print("Doing iteration {0}.".format(str(n + 1))) + fc = 0 + while (fc < frameCount and ret): + print(fc) + (i, j) = ([fc * chunk_size, (fc + 1) * chunk_size]) + chunk = audio_samples[i:j] + + ret, buf = cap.read() + buf_normalized = np.true_divide(buf, 255).flatten() + + # Load data into network + net.load(chunk, buf_normalized) + + cv2.namedWindow('video') + cv2.imshow('video', buf) + + output = net.output + output = np.array(output) * 255 + learn = output.reshape((frameHeight, frameWidth, 3)) + learn = learn.astype(np.uint8) + cv2.namedWindow('learn') + cv2.imshow('learn', learn) + + cv2.waitKey(int(1000 * TRAINING_DURATION)) + fc += 1 + net.load(chunk) + +#cap.release() + +print("\n\n*** TESTING ***") + +fc = 0 + +while (fc < frameCount): + print(fc) + (i, j) = ([fc * chunk_size, (fc + 1) * chunk_size]) + chunk = audio_samples[i:j] + + # Wait for the data to propagate and get the output + net.load(chunk) + output = net.output + + output = np.array(output) * 255 + buf = output.reshape((frameHeight, frameWidth, 3)) + buf = buf.astype(np.uint8) + + cv2.namedWindow('output') + cv2.imshow('output', buf) + cv2.waitKey(int(1000 * TRAINING_DURATION)) + fc += 1 + +net.freeze() + +print("\n{0} waves are executed throughout the network".format( + str(net.wave_counter) +)) + +print("\nIn total: {0} times a random non-sensory neuron is fired\n".format( + str(net.fire_counter) +)) + +print("Exit the program") From cdcbbfbc8cc428358ba04fbc11d078716bc818f0 Mon Sep 17 00:00:00 2001 From: Mehmet Mert Yildiran Date: Sun, 5 Jul 2020 13:09:56 +0300 Subject: [PATCH 2/3] Save the resulting frames on training and evaluation --- examples/audio_visual.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/audio_visual.py b/examples/audio_visual.py index 0a2d62e..2022423 100644 --- a/examples/audio_visual.py +++ b/examples/audio_visual.py @@ -1,4 +1,5 @@ import time +from pathlib import Path import cv2 import numpy as np from pydub import AudioSegment @@ -7,6 +8,10 @@ VIDEO_FILE = 'videos/lower3.mp4' +Path("videos/output/original").mkdir(parents=True, exist_ok=True) +Path("videos/output/training").mkdir(parents=True, exist_ok=True) +Path("videos/output/evaluation").mkdir(parents=True, exist_ok=True) + audio = AudioSegment.from_file(VIDEO_FILE, "mp4") audio_samples = audio.get_array_of_samples() @@ -77,6 +82,9 @@ cv2.namedWindow('learn') cv2.imshow('learn', learn) + cv2.imwrite("videos/output/original/{0}.png".format(str(fc)), buf) + cv2.imwrite("videos/output/training/{0}.png".format(str(fc)), learn) + cv2.waitKey(int(1000 * TRAINING_DURATION)) fc += 1 net.load(chunk) @@ -102,6 +110,9 @@ cv2.namedWindow('output') cv2.imshow('output', buf) + + cv2.imwrite("videos/output/evaluation/{0}.png".format(str(fc)), buf) + cv2.waitKey(int(1000 * TRAINING_DURATION)) fc += 1 From c786b2d7c6eed05f35d3561994bf72ff7b39bc7b Mon Sep 17 00:00:00 2001 From: Mehmet Mert Yildiran Date: Sat, 25 Jul 2020 23:49:23 +0300 Subject: [PATCH 3/3] Update README.md --- README.md | 34 +++++++++++++++++----------------- docs/img/apply_activation.png | Bin 3655 -> 464 bytes docs/img/calc_of_loss.png | Bin 3217 -> 610 bytes docs/img/total_potential.png | Bin 5145 -> 763 bytes 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index dda428e..93ce652 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,23 @@ ## Core Principles -These are the core principles of **exceptionally bio-inspired**, a revolutionary approach to the artificial neural networks: +These are the core principles of **object-oriented** approach to the current state of artificial neural networks that is inspired by **synaptic plasticity** between **biological** neurons: - - **Neurons** must be **objects** not tensors between matrices. - - **Neurons** should be **GPU accelerated**. (ideally). - - **Network** must be **architecture-free** (i.e. adaptive). - - Network must have a **layerless design**. + - Unlike the current ANN implementations, **neurons** must be **objects** not tensors between matrices. + - Just the current ANN implementations, **neurons** should be **GPU accelerated** (ideally) to provide the necessary parallelism. + - While the current ANN implementations can only create special cases, a **Plexus Network** must be **architecture-free** (i.e. adaptive) to create a generalized solution of all machine learning problems. + - Instead of dealing with decision of choosing an ANN layer combination(such as Convolution, Pooling or Recurrent layers), the network must have a **layerless design**. - There must be fundamentally two types of neurons: **sensory neuron**, **interneuron**. - - Input of the network must be made of sensory neurons. Any interneuron can be picked as a **motor neuron** (an element of the output). There is literally no difference between an interneuron and a motor neuron except the intervene of the network for igniting the wick of learning process through the motor neurons. Any non-motor interneuron can be assumed as a **cognitive neuron** which collectively forms the cognition of network. + - Input of the network must be made of sensory neurons. Any interneuron can be picked as a **motor neuron** (an element of the output). There are literally no difference between an interneuron and a motor neuron except the intervene of the network for igniting the wick of learning process through the motor neurons. Any non-motor interneuron can be assumed as a **cognitive neuron** which collectively forms the cognition of network. - There can be arbitrary amount of I/O groups in a single network. - - Forget about batch size, iteration, and epoch concepts, training examples must be fed on time basis with a manner like; *learn first sample for ten seconds, OK done? then learn second sample for twenty seconds*. By this approach, you can assign importance factors to your samples with maximum flexibility. + - Instead of batch size, iteration, and epoch concepts, training examples must be fed on time basis with a manner like; *learn first sample for X seconds, OK done? then learn second sample for Y seconds*. By this approach, you can assign importance factors to your samples with maximum flexibility. - **Network** must be **retrainable**. - Network must be **modular**. In other words: You must be able to train a small network and then plug that network into a bigger network (we are talking about some kind of **self-fusing** here). - - Neurons must exhibit the characteristics of **cellular automata**. + - Neurons must exhibit the characteristics of **cellular automata** just like Conway's Game of Life. - **Number of neurons** in the network can be increased or decreased (**scalability**). - There must be **no** need for a network-wide **oscillation**. Yet the execution of neurons should follow a path very similar to flow of electric current nevertheless. - - Network should use **randomness** and/or **uncertainty principle** flawlessly. - - Most importantly, the network **must and can not iterate** through the whole dataset. Besides that, it's also generally impossible to iterate whole the dataset on real life situations if the system is continuous like in robotics. Because of that; the network must be designed to handle such a **continuous data stream** that literally endless and must be designed to handle that data stream chunk by chunk. Therefore, when you are feeding the network, use a diverse feed but not a grouped feed (*like 123123123123123123 but not like 111111222222333333*). + - Network should use **randomness** and/or **uncertainty principle** flawlessly. Consciousness is an emergent property from cellular level to macro scale, the network. But it's also an emergent property for the neuron from quantum level uncertainty to cellular mechanisms. In such a way that **randomness** is the cause of the illusion of consciousness. + - Most importantly, the network **must and can not iterate** through the whole dataset. Besides that, it's also generally impossible to iterate the whole dataset on real life situations if the system is continuous like in robotics. Because of that; the network must be designed to handle such a **continuous data stream** that literally endless and must be designed to handle that data stream chunk by chunk. Therefore, when you are feeding the network, use a diverse feed but not a grouped feed (*like 123123123123123123 but not like 111111222222333333*). ### Activation function @@ -87,26 +87,26 @@ Functionality of a neuron is relative to its type. **publications** holds literally the mirror data of *subscriptions* in the target neurons. In other words; any subscription creates also a publication reference in the target neuron. Similarly, *publications* is the Plexus Network equivalent of **Axons** in biological neurons. -**potential** is the overall total potential value of all subscriptions multiplied by the corresponding weights. Only in sensory neurons, it is directly assigned by the network. Value of **potential** may only be updated by the neuron's itself and its being calculated by this simple formula each time when the neuron is fired: +**potential** *`p`* is the overall total potential value of all subscriptions multiplied by the corresponding weights. Only in sensory neurons, it is directly assigned by the network. Value of **potential** may only be updated by the neuron's itself and its being calculated by this simple formula each time when the neuron is fired:

Total potential

- +

Apply activation

- + -**desired_potential** is the ideal value of the neuron's potential that is desired to eventually reach. For sensory neurons, it is meaningless. For motor neurons, it is assigned by the network. If it's **None** then the neuron does not learn anything and just calculates potential when it's fired. +**desired_potential** *`p'`* is the ideal value of the neuron's potential that is desired to eventually reach. For sensory neurons, it is meaningless. For motor neurons, it is assigned by the network. If it's **None** then the neuron does not learn anything and just calculates potential when it's fired. -**loss** is calculated not just at the output but in every neuron except sensory ones and it is equal to absolute difference (*distance*) between desired potential and current potential. +**loss** *`l`* is calculated not just at the output but in every neuron except sensory ones and it is equal to absolute difference (*distance*) between desired potential and current potential.

Calculation of loss

- + All numerical values inside a neuron are floating point numbers and all the calculations obey to the precision that given at start. @@ -129,7 +129,7 @@ On the second phase of the network initiation, any non-sensory neurons are force ## Algorithm -Even so the Python implementation of Plexus Network is easy to understand, it will be helpful for readers to explain the algorithm in pseudocode. +Even so the Python implementation of Plexus Network is easy to understand, it will be helpful for readers to explain the algorithm in pseudocode; ### Initiation diff --git a/docs/img/apply_activation.png b/docs/img/apply_activation.png index 578a663a3450f1b121717e4f37d118cc305c4e3b..fe886a36c1b915ec5dbcb9568ccde2ad5bedc2b5 100644 GIT binary patch literal 464 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 Q8H19Er>mdKI;Vst00D6Mu>b%7 literal 3217 zcmZ`*S5VW9v;85xs&u7y5JC%tBE5y)L_!n9&_WCCgU|v}1w;tl&_e)~B26SxMS2nZ zK;Tc6CPnZyT2Fs7s!+Xq&_T zOuL6lhxsldnHLuwVO&NP_j?0!a=X`^|6#TaVJXlPL}W ze)>jBJ!UUb*y^s4Z|X}icP}nePCB9ip!@#C(lyW((%z&8e7yxU|NbrU?tV!)TlDcn zC-_4dfQtv7oSdc1X0^nuP&2`&3mtC#U& z@Gy4Dz^=A>X$jU}Q|4MTlHcpqbU}3cD zVH#pBbrF^GB`+T>K6-1#@{as_E>>f~v<6pHx`;D1SDz5{P?n>#Qv{_n=LW|FXA|Ry zMW&Tr;KaP648{!M49yHe#)pht!FY+6<(~uK zLXATR4{)=8xk{h}>wN1%EGF3+*|)M8 zvNzr_KYry|?HTV`yw!%yz~+?dscax7( ztNM;T!W`PY+ac}LkvSce=;!EbObSCtf^!aqf_YBwx94zdA?q4FR z;UnN$d2|c5V>a|p^LN#%rPrI-o;*&OQ12n>Ke7uxv+if#N9JE5r+gt^wQk(bj_zuG z&H)jMs@{)@N;v;W=C;N zaG812YW29=wpZyZVKj8Hcha?S_%W=lDsdY_C(EYAEdc=uJ zL?LmzIB}diyc#|PuYlLUhvDuk)vF9WqkVIIu)Z-NMZrnIHlrYuYBLApLsMl;QwC?j zJVwhfNuQAZ0E5k%Ew0GR$%how<~wFdj?|4P=E;nm{7`V`f4vP3Co|Uk^i6>>hj~**cr7lYFWRZ>ci@-x7JmQuyzL~C$OED)0~Bf)5l8R1Ha>C zj6g7ScAI6(XseUcMDeWUb9rf%MOpnJ`?3wO9%iCxLfWs?Z;y4&H^O!n>8tXo{0eF4 z>}@MAf86n*n%-yFGhdYjy>*4?pHSTm+ok8Kew6|C6W$)#F6Kvv1x>AbFWnp&@n~yf zTlrpL?-Tv$irT+>r@qc+9%K9DC$y36*6oGr+t(2*xWnFAqm0}k$w!iGj*f(k5Z7hT zv+o2i|7qcK2vN0_JK)l}6L~#OpT_agS9z&Tt&6KAVEAmRcP{6n)co@NuMV%^k6zHyb?X(OFC6}6wfZQu>&{Ez$VUG3j6tPFHCt@Qi9=PvtiN#BX< z9LAHhGiEVP##qLPX^MT$dYb#xFq+Yix`oU^%b|Nc(S;-SslF^y1rxx_-Ibht~@L242dxv)JUwH zSP1IU=#O44S%}`^-vj!+)+?#)L<>rmU%S9{a9|W?^}$` zt|q4gb6d11RejhG-UJOH$sDP~OXDNj_KkmjZw4_Jh+qeH4lv6`wJfg;&1hD1iL>6k2R%jN;~pqYwFHyW}wQs%X15-v9L2t3X-^Gme;?ZD4Z zn+m$er><7q=Thn%4@_x-UuR#WstW|*+I^0uy_^P?(BhT8en&!|^tn^)AGRmH&1KdQ zvwqL84|U1paboZz+lHUS-u#+?P>`-okc)GWt1>*m^#%Z0X&D8Ov>ZqpVkIl1EG?@H zhKS!pY3bQpvS0s?zz6Aqa0~ta1yxST0XG8f|DNFI;-;*RuyhU7LjL3Hg79_MLZSdU yd6bN__>D~q_=Marng5q*?HUk>K>7maI(i@(NjY9ls50uN4;bj0>eOmIjrlJX{_w5< diff --git a/docs/img/total_potential.png b/docs/img/total_potential.png index 82edf507e290d9279d8118e26a9e961d625b9307..edba1efcd41289eb794fce7efec4b1564afd4185 100644 GIT binary patch literal 763 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 v;uQ*