| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Nothing is more fascinating than learning machines. Well, except of course... never mind. Anyway, this observation is about a machine (pic) learning a sequence from another machine (pic), without any human intervention and with no more feedback than a percentage correct. One pic takes a random sequence, and the other has to learn what this sequence is. In the run in the screen shot below the correct sequence (1-3-2-4) was learned after 44 tries. After that the student pic answered correct 100% of the time. The sequence consists of four steps. All possible combinations of 1,2,3 and 4 are allowed. So 1-2-3-4 is permitted, but so is 2-2-2-2. Anything goes, as long as it consists of four steps. The sequence is represented by LEDs. For example sequence 1-1-1-4 would mean flash LED 1 three times and LED 4 one time. The students guesses using his four LEDs. The teacher has four LDR's to detect the student's sequence. Both pics are connected by usart serial so the teacher can relay to the student the score for his attempt (0 - 100% correct).
Two things are noteworthy:
These two 'deviations from logic' are necessary to maintain flexibility. The self-learning mechanism is geared towards flexible adaptivity to the environment. Learning systems like this are best used in changing environments. This means that if the correct answer is changed at any time (even after the student has successfully learned the correct answer) the program is flexible enough to immediately detect this and start learning the new sequence. These setting represent a trade off between learning fast the first time and learning fast upon change. Feel free to experiment and draw your own conclusions. The 10% chance of selecting a totally random solution is to fill an empty brain gradually, and more importantly to prevent the program from getting stuck in a local, but not global, optimum. This screen shot is from a prototype of this brain. The prototype brain functions exactly the same as the one in the pic. The red line in the graph shows the correctness of each answer (which varies quite a lot, but as soon as it hits '4 correct' it stays there), the purple line is the average correctness. It shows that the proper sequence was changed twice, and both times the program was quick to recuperate and learn the new sequence. And these changes were quite dramatic, from 1-2-3-4 to 1-1-2-4 to 4-4-3-3. That is the true power of learning machines! The breadboard looks like this. The long wires are from the Wisp628 in circuit programmer. Note the yellow wire connecting the pin 1's of both pics. This way both pics restart when one of them gets a new program, and both restart when the programmer is switched to pass through mode. This makes sure that both programs run synchronized. The switch on the right hand side, with the red LED, is simply the on/off switch. Note that the LED-LDR combo's are not shielded against ambient light. If you program during the day shielding may be required, even though auto calibration is incorporated into the software. Based on the following schematics (programmer wires and the yellow pin1-pin1 line are not shown): The Visual Basic program which displays the progress of the student-teacher relationship is not required. It only displays data and has no influence on the learning process. The visualization is very nice however, so you can download the source here. Obviously you need Visual Basic to run the source. The pic programs follow this sequence (synchronized at each step):
This is the student source. It needs to be loaded into the pic with the LEDs.
-- jal 4.55
include JRTL
include f877_20
include jlib
include usart19200
include seriali1200
include seriali
include random3
var byte mydata ,correctCounter, FirstRun, UitTabel, keuze1, keuze2,
keuze3, keuze4, t
var byte answer, selectie, temp, NewValue
serial_setup
-- define the pins
pin_b1_direction = output
pin_b2_direction = output
pin_b3_direction = output
pin_b4_direction = output
pin_b5_direction = output
var bit LED1 is pin_b5
var bit LED2 is pin_b4
var bit LED3 is pin_b3
var bit LED4 is pin_b2
var bit LEDYELLOW is pin_b1
-- array type function to store the brain content in array (x,y)
function MemoryPut(byte in x, byte in y, byte in answer) return byte is
-- max x = 5 (number of lines)
-- max y = 6 where 1 serial number
-- 2,3,4,5 solution
-- 6 fitness
-- starting position in memory = 1
eeprom_put( ( x - 1) * 6 + y + 1, answer )
end function
function MemoryGet(byte in x, byte in y) return byte is
eeprom_get( ( x - 1) * 6 + y + 1, answer )
return answer
end function
-- function to generate a random number 1,2,3 or 4
function RandomNumber return byte is
temp = random_byte
if temp < 64 then answer = 1 end if
if temp >= 64 & temp < 128 then answer = 2 end if
if temp >= 128 & temp < 192 then answer = 3 end if
if temp >= 192 then answer = 4 end if
return answer
end function
-- procedure to light a LED
procedure LEDaan(byte in x) is
if x == 1 then
LED1 = high
delay_1s
LED1 = low
delay_100ms
end if
if x == 2 then
LED2 = high
delay_1s
LED2 = low
delay_100ms
end if
if x == 3 then
LED3 = high
delay_1s
LED3 = low
delay_100ms
end if
if x == 4 then
LED4 = high
delay_1s
LED4 = low
delay_100ms
end if
end procedure
-- fill the fitness part of the brain
-- with zero's and enter the serial numbers
t = 0
for 5 loop
t = t + 1
MemoryPut(t,1,t)
MemoryPut(t,6,0)
end loop
-- give other pic time to start up
delay_1s
-- this is the first run (so the brain is empty)
FirstRun = 1
forever loop
-- send current position to the PC
asynch_send("X")
asynch_send("0")
-- first synch -----
mydata = "0"
while mydata != "A" loop
F877_serial_receive (mydata)
end loop
F877_serial_transmit ( "B" )
-- send current position to the PC
asynch_send("X")
asynch_send("1")
-- *************** AI part 1: selection of the solution ****************
-- indicator to remember whether the selected solution is from the brain or not
UitTabel = 0
-- see if the correct answer is known and if yes select
t = 0
for 5 loop
t = t + 1
MemoryGet(t,6)
if answer == 4 then
UitTabel = t
MemoryGet(UitTabel,2)
keuze1 = answer
MemoryGet(UitTabel,3)
keuze2 = answer
MemoryGet(UitTabel,4)
keuze3 = answer
MemoryGet(UitTabel,5)
keuze4 = answer
end if
end loop
-- if correct answer is not known
if UitTabel == 0 then
-- make a choice from the brain (90%)or make up an
-- answer on the spot (10%)
selectie = random_byte
if selectie > 230 | FirstRun == 1 then
-- select 4 random numbers in the range 1,2,3,4
RandomNumber
keuze1 = answer
RandomNumber
keuze2 = answer
RandomNumber
keuze3 = answer
RandomNumber
keuze4 = answer
FirstRun = 0
else
-- random selection from the brain
RandomNumber
UitTabel = answer
MemoryGet(UitTabel,2)
keuze1 = answer
MemoryGet(UitTabel,3)
keuze2 = answer
MemoryGet(UitTabel,4)
keuze3 = answer
MemoryGet(UitTabel,5)
keuze4 = answer
-- make a random mutation (90 % chance)
selectie = random_byte
if selectie < 230 then
-- reset UitTabel because this is now a new solution
UitTabel = 0
RandomNumber
NewValue = answer
RandomNumber
if answer == 1 then keuze1 = NewValue end if
if answer == 2 then keuze2 = NewValue end if
if answer == 3 then keuze3 = NewValue end if
if answer == 4 then keuze4 = NewValue end if
end if
end if
end if
-- second synch -----
mydata = "0"
while mydata != "C" loop
F877_serial_receive (mydata)
end loop
F877_serial_transmit ( "D" )
-- switch yellow led on
LEDYELLOW = high
-- send current position to the PC
asynch_send("X")
asynch_send("2")
-- calibrate LED's on
LED1 = high
LED2 = high
LED3 = high
LED4 = high
-- third synch -----
mydata = "0"
while mydata != "E" loop
F877_serial_receive (mydata)
end loop
F877_serial_transmit ( "F" )
-- switch yellow led off
LEDYELLOW = low
-- send current position to the PC
asynch_send("X")
asynch_send("3")
-- calibrate LED's off
LED1 = low
LED2 = low
LED3 = low
LED4 = low
-- fourth synch -----
mydata = "0"
while mydata != "G" loop
F877_serial_receive (mydata)
end loop
F877_serial_transmit ( "H" )
-- send current position to the PC
asynch_send("X")
asynch_send("4")
-- display the selected sequence
LEDaan(keuze1)
LEDaan(keuze2)
LEDaan(keuze3)
LEDaan(keuze4)
-- fifth synch -----
mydata = "0"
while mydata != "I" loop
F877_serial_receive (mydata)
end loop
F877_serial_transmit ( "J" )
-- send current position to the PC
asynch_send("X")
asynch_send("5")
-- activity signal
LEDYELLOW = high
-- receive percentage correct from the other PIC
F877_serial_receive ( correctCounter )
-- and forward to the PC
asynch_send("H")
asynch_send(correctCounter )
-- yellow led off
LEDYELLOW = low
-- go from percentages to range 1-4
correctCounter = correctCounter / 25
-- *************** AI part 2: process feedback ****************
-- process CorrectCounter
if UitTabel == 0 then
-- if this was a new choice see if a worse one exists in the brain
-- table and replace that one
t = 0
for 5 loop
t = t + 1
MemoryGet(t,6)
if answer < correctCounter then
MemoryPut(t,2,keuze1)
MemoryPut(t,3,keuze2)
MemoryPut(t,4,keuze3)
MemoryPut(t,5,keuze4)
MemoryPut(t,6,correctCounter)
end if
end loop
else
-- this is an existing selection, so modify the fitness in the brain
MemoryPut(UitTabel,6,correctCounter)
end if
end loop
This is the teacher source, it needs to be loaded into the pic with the LDRs:
-- jal 4.55
include JRTL
include f877_20
include jlib
include usart19200
include seriali1200
include seriali
-- AD converter settings
const PIC_ADC_Nchan = 5
const PIC_ADC_NVref = 0
const PIC_ADC_Rsource = 20_000
const PIC_ADC_high_resolution = true
-- AD converter variables
var volatile byte ADCON0 at 0x1F
var volatile bit GO at ADCON0 : 2
var volatile byte ADRESH at 0x1E
var volatile byte ADRESL at 0x9E
var volatile byte ADCON1 at 0x9F
-- AD library
include pic_adc
-- define variables
var bit loopfix, firstpass, alloff
var bit SeePinA0, SeePinA1, SeePinA2, SeePinA3
var byte position, x, Correct, correctCounter
var byte RESIST, Dummy, chan, mydata, yelled, counter
var byte threshold0, threshold1, threshold2, threshold3
-- initialize the AD converter
PIC_ADC_init
-- initialize the serial connection to PC
serial_setup
-- give each pic time to initialize comm buffers
delay_1s
-- define the pins
pin_a0_direction = input -- variable resistor
pin_a1_direction = input -- variable resistor
pin_a2_direction = input -- variable resistor
pin_a3_direction = input -- variable resistor
pin_b1_direction = input -- switch
pin_b4_direction = output -- yellow led
var bit LEDYELLOW is pin_b4
-- array type function to store 'answer' in array (x,y)
function PositionPut(byte in x, byte in position) return byte is
-- starting position in memory = 0
eeprom_put( x + 0 , position )
end function
function PositionGet(byte in x) return byte is
-- starting position in memory = 0
eeprom_get( x + 0 , position )
return position
end function
-- array type function to store 'answer' in array (x,y)
function CorrectAnswerPut(byte in x, byte in correct) return byte is
-- starting position in memory = 5
eeprom_put( x + 4 , correct )
end function
function CorrectAnswerGet(byte in x) return byte is
-- starting position in memory = 5
eeprom_get( x + 4 , correct )
return correct
end function
procedure AutoCalibrate is
-- pin A0
PIC_ADC_read (0, Dummy, RESIST)
asynch_send( "1" )
asynch_send( Resist )
asynch_send( "2" )
if firstpass == true then
-- the threshold has no value yet so pick the first one
threshold0 = resist
else
-- from now on take the average
threshold0 = ( threshold0 / 2) + ( resist / 2 )
end if
asynch_send( threshold0)
delay_100ms
-- pin A1
PIC_ADC_read (1, Dummy, RESIST)
asynch_send( "3" )
asynch_send( resist )
asynch_send( "4" )
if firstpass == true then
threshold1 = resist
else
threshold1 = ( threshold1 / 2 ) + ( resist / 2 )
end if
asynch_send( threshold1)
delay_100ms
-- pin A2
PIC_ADC_read (2, Dummy, RESIST)
asynch_send( "5" )
asynch_send( resist )
asynch_send( "6" )
if firstpass == true then
threshold2 = resist
else
threshold2 = ( threshold2 / 2 ) + ( resist / 2 )
end if
asynch_send( threshold2)
delay_100ms
-- pin A3
PIC_ADC_read (3, Dummy, RESIST)
asynch_send( "7" )
asynch_send( resist )
asynch_send( "8" )
if firstpass == true then
threshold3 = resist
else
threshold3 = ( threshold3 / 2 ) + ( resist / 2 )
end if
asynch_send( threshold3)
delay_100ms
end procedure
procedure MeasureData is
counter = 1
SeePinA0 = true
SeePinA1 = true
SeePinA2 = true
SeePinA3 = true
alloff = false
while counter < 5 | alloff == false loop
alloff = true
-- pin A0
PIC_ADC_read (0, Dummy, RESIST)
-- tell the PC from which pin it will receive data
asynch_send( "1" )
if RESIST > threshold0 then
-- pin is high
asynch_send( 1 )
-- obviously not all led's are off, do not exit main loop yet
alloff = false
if SeePinA0 then
PositionPut(counter, 1)
counter = counter + 1
-- remember to not count this pin again
SeePinA0 = false
end if
else
asynch_send( 0 )
-- pin is low, can count again if needed
SeePinA0 = true
end if
-- pin A1
PIC_ADC_read (1, Dummy, RESIST)
-- tell the PC from which pin it will receive data
asynch_send( "3" )
if RESIST > threshold1 then
asynch_send( 1 )
alloff = false
if SeePinA1 then
PositionPut(counter, 2)
counter = counter + 1
SeePinA1 = false
end if
else
asynch_send( 0 )
-- pin is low, can count again if needed
SeePinA1 = true
end if
-- pin A2
PIC_ADC_read (2, Dummy, RESIST)
asynch_send( "5" )
if RESIST > threshold2 then
asynch_send( 1 )
alloff = false
if SeePinA2 then
PositionPut(counter, 3)
counter = counter + 1
SeePinA2 = false
end if
else
asynch_send( 0 )
SeePinA2 = true
end if
-- pin A3
PIC_ADC_read (3, Dummy, RESIST)
asynch_send( "7" )
if RESIST > threshold3 then
asynch_send( 1 )
alloff = false
if SeePinA3 then
PositionPut(counter, 4)
counter = counter + 1
SeePinA3 = false
end if
else
asynch_send( 0 )
SeePinA3 = true
end if
end loop
end procedure
forever loop
-- send current position to the PC
asynch_send("X")
asynch_send("0")
-- first synch -----
F877_serial_transmit ( "A" )
mydata = "0"
while mydata != "B" loop
F877_serial_receive (mydata)
end loop
-- send current position to the PC
asynch_send("X")
asynch_send("1")
-- store the correct answer ( 1 - 3 - 2 - 4 )
CorrectAnswerPut(1,1)
CorrectAnswerPut(2,3)
CorrectAnswerPut(3,2)
CorrectAnswerPut(4,4)
-- and send to PC
CorrectAnswerGet(1)
asynch_send("I")
asynch_send(correct)
CorrectAnswerGet(2)
asynch_send("J")
asynch_send(correct)
CorrectAnswerGet(3)
asynch_send("K")
asynch_send(correct)
CorrectAnswerGet(4)
asynch_send("L")
asynch_send(correct)
-- second synch -----
-- calibrate with LED's on
F877_serial_transmit ( "C" )
mydata = "0"
while mydata != "D" loop
F877_serial_receive (mydata)
end loop
-- switch yellow led on
LEDYELLOW = high
asynch_send("9")
asynch_send("1")
-- send current position to the PC
asynch_send("X")
asynch_send("2")
-- wait a bit to make sure LED's are actually on
delay_100ms
-- calibrate
firstpass = true
for 2 loop
AutoCalibrate
firstpass = false
delay_10ms
end loop
-- third synch -----
-- calibrate with LED's off
F877_serial_transmit ( "E" )
mydata = "0"
while mydata != "F" loop
F877_serial_receive (mydata)
end loop
-- switch yellow led off
LEDYELLOW = low
asynch_send("9")
asynch_send("0")
-- send current position to the PC
asynch_send("X")
asynch_send("3")
-- wait a bit to make sure LED's are actually on
delay_100ms
-- calibrate
for 2 loop
AutoCalibrate
delay_10ms
end loop
-- fourth synch -----
-- observe the sequence
F877_serial_transmit ( "G" )
mydata = "0"
while mydata != "H" loop
F877_serial_receive (mydata)
end loop
-- send current position to the PC
asynch_send("X")
asynch_send("4")
-- reading student data
MeasureData
-- and send result (sequence) to the PC
-- read arry and send to PC
PositionGet(1)
asynch_send("C")
asynch_send(position)
PositionGet(2)
asynch_send("D")
asynch_send(position)
PositionGet(3)
asynch_send("E")
asynch_send(position)
PositionGet(4)
asynch_send("F")
asynch_send(position)
-- check for correctness
CorrectCounter = 0
counter = 1
while counter < 5 loop
PositionGet(counter)
CorrectAnswerGet(counter)
if position == correct then
correctCounter = correctCounter + 1
end if
counter = counter + 1
end loop
-- send percentage correct to the PC
asynch_send("H")
asynch_send(correctCounter * 25)
-- fifth synch -----
F877_serial_transmit ( "I" )
mydata = "0"
while mydata != "J" loop
F877_serial_receive (mydata)
end loop
-- send current position to the PC
asynch_send("X")
asynch_send("5")
-- send percentage correct to the other PIC
F877_serial_transmit ( correctCounter * 25)
end loop
The include file seriali1200.jal contains the following: -- settings required for seriali to work const asynch_baudrate = 1200 const asynch_polarity = active_high const asynch_parity = parity_none const asynch_stopbits = 2 -- define input and output pins var volatile bit asynch_in_pin is pin_b6 var volatile bit asynch_in_direction is pin_b6_direction var volatile bit asynch_out_pin is pin_b7 var volatile bit asynch_out_direction is pin_b7_direction The include file usart19200.jal contains the following:
-- --------------------------------------------------------------------
--
-- file : usart19200.jal
-- author : Javier Martinez, put in a lib by Bert van Dam, added
-- poll routine
-- date : 13 june 2004
-- purpose : setup file for usart for 19200 baud
-- which is pin c6/c7 (25 and 26 on a 16F877)
-- usage : include this file, call serial_setup to initialise
--
-- --------------------------------------------------------------------
-- setting for USART use
-- receive is RC7 (pin 26)
-- send is RC6 (pin 25)
var volatile bit f877_BRGH at f877_TXSTA : 2
var volatile bit f877_TXEN at f877_TXSTA : 5
var volatile bit f877_CREN at f877_RCSTA : 4
var volatile bit f877_SPEN at f877_RCSTA : 7
var volatile bit f877_TXIF at f877_PIR1 : 4
var volatile bit f877_RCIF at f877_PIR1 : 5
-- variable to see if data is received
var bit polltrue
Procedure serial_setup is begin
-- general setup procedure 19200,8,n,1 No handshaking
Bank_1
f877_SPBRG = 64
f877_BRGH = high
f877_TXEN = High
Bank_0
f877_CREN = high
f877_SPEN = high
end procedure
Procedure F877_serial_receive ( byte out data ) is begin
-- wait for incoming data and receive
Bank_0
while ! f877_RCIF loop
end loop
file_get ( 0x1A , data )
end procedure
Procedure F877_serial_transmit ( byte in data ) is begin
-- transmit data
Bank_0
while ! f877_TXIF loop
end loop
file_put ( 0x19 , data )
end procedure
Procedure F877_serial_poll ( byte out data ) is begin
-- see if there's any data to receive, if yes receive the
-- data and set the polltrue flag to true
Bank_0
polltrue = false
if f877_RCIF then
file_get ( 0x1A , data )
polltrue = true
end if
end procedure
A lot of data is send to the PC by each of the pics. Normally only the teacher is connected to the PC, the student messages to the PC are for debugging only. This is the code list of all messages:
Submitted: 06/02/2005 Article content copyright © Bert van Dam, 2005.
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
All content copyright © 1998-2007, Generation5 unless otherwise noted.
- Privacy Policy - Legal - Terms of Use -