심심해서 하는 블로그 :: 심심해서 하는 블로그

1. LED 회로 구성하기


이전 포스트에서는 NodeJS의 serialport 패키지를 이용해서 아두이노에 연결된 센서의 값을 받아오는 과정을 진행했어요. 이번에는 라즈베리파이의 GPIO에 LED와 버튼을 연결하고 버튼을 누르면 LED가 켜지고 꺼지는 기능을 구현해 보겠습니다. 물론 NodeJS를 이용해서 말이죠!


우선 LED와 버튼 두 개를 이용해서 회로를 간단하게 구현을 해보려구 해요. 버튼 하나는 On 버튼으로 다른 버튼은 Off 버튼으로 사용하면 끄고 켜고 기능을 할 수 있겠죠? 물론 버튼 하나 만으로도 구현이 가능해요. 근데 아두이노나 라즈베리파이에 쓰는 버튼은 한 번 누르면 한번으로 인식하지 않고 여러 번으로 인식해서 그냥 두 개를 씁니다.



그림과 같이 구성합니다. 매번 느끼지만 라즈베리파이의 GPIO에.. 좀.. 아두이노처럼 GPIO 위치를 기판에 새겨 주면 좋겠다는 생각을 매번 해요. 지금은 아무 코드도 작성하지 않았으니까, 버튼을 눌러도 아무런 반응이 없을 거예요. 이제 코드를 작성해서 버튼에 따라서 LED가 켜지고 꺼지도록 만들어 볼게요!!


2. onoff 패키지 사용하기


라즈베리파이로 프로젝트를 진행하는 사람이 많아져서 그런가요?? 이런 패키지들이 생각보다 많네요. 

우선 onoff 패키지를 설치합니다. 프로젝트 파일들이 있는 경로에서 아래와 같이 명령어를 입력합니다.


npm install onoff --save


설치가 완료된 후 이제 소스코드를 작성해 봅시다. LED는 18번에 ON 버튼은 24번, OFF 버튼을 23번에 연결한 후 작성된 코드입니다. 여러분이 하실 때에는 연결하신 번호대로 작성해주시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// GPIO : LED 제어 부분
var GPIO = require('onoff').Gpio,
    LED = new GPIO(18'out'),
    ON_sw = new GPIO(24'in''both'),
    OFF_sw = new GPIO(23'in''both');
 
ON_sw.watch(function(err, state){
         if(state == 1){
                 LED.writeSync(1);                               
         }
});
 
OFF_sw.watch(function(err, state){
         if(state == 1){
                 LED.writeSync(0);
         }
});
 
 
 
cs


작성을 한 후 실행을 하면 파란 버튼을 누르면 불이 꺼지고 빨간 버튼을 누르면 불이 켜지는 걸 확인했습니다. 

스마트 스위치 같은 것을 실습하는 데에 좋은 패키지가 아닐까 생각합니다.





,

1. 아두이노 - 센서 조립


요새 설계과목에 제출할 과제로 스마트 스위치를 만들고 있어요. 라즈베리파이2랑 아두이노, 조도센서를 이용해서 날이 너무 밝으면 불을 꺼주고, 날이 너무 어두우면 불을 켜주는 기능을 만들고 있는데 그러려면 아두이노에서 센서 값을 받은 다음 라즈베리파이로 전송하는 과정이 설계상 필요하더라고요. 라즈베리파이2가 웹서버로 NodeJS를 사용하고 있는데 설마.. 시리얼 통신 패키지도 있나 했더니.. 정말 있네요.. node.. 당신은 도대체..


개발 보드 : 아두이노 나노, 조도 센서, 라즈베리파이2

OS : 라즈베리안 OS

언어 : NodeJS

(굳이 위의 개발보드와 OS가 아니더라도 가능합니다. 저도 Window에서 Test 후 파이로 옮겨서 수행했습니다.)



우선 조도 센서를 아두이노와 연결해요. 조도 센서는 TEMT6000 센서를 사용했습니다.

VCC - 5V, GND - GND, OUT - ANALOG 중 하나에 점핑 선을 연결해줍니다. 

아두이노 나노는 A0, A1 등등 A로 시작하는 곳이 ANALOG에요.

다른 조도 센서를 사용하시는 분이라면 제품 번호를 확인하시고 연결하세요. 



그리고 아두이노를 라즈베리파이에 USB 선을 이용해서 연결해줍니다. 그 후 SSH로 접속한 후  dmesg | tail 명령어를 입력해 USB 포트 번호를 확인합니다. 저는 ttyUSB0에 아두이노 나노가 있다고 하네요. 



Windows 환경이나 리눅스 쉘 커맨드에 익숙하지 않다면 연결 후 아두이노 스케치를 실행해서 USB 포트 정보를 확인할 수도 있습니다. 마찬가지로 ttyUSB0에 연결되어 있음을 알 수 있죠. 이 정보는 이어서 소개할 serialport 패키지를 사용하는데 필요하니까 반드시 알아 두시는 게 좋아요!!


2. serialport 패키지 사용하기


serialport는 웹서버로 주로 사용하는 NodeJS에서 센서 값을 읽고 기기를 제어할 수 있게 하는 패키지에요!!

우선 nodeJS 프로젝트를 만든 후 해당 경로에서 리눅스 명령어 쉘(Windows에서는 cmd) 아래의 명령어로 serialport 패키지를 설치합니다. 


npm install serialport --save


설치가 완료되면 자바 스크립트 파일을 하나 만든 후 아래와 같이 코드를 작성해 봅시다.


1
2
3
4
5
6
7
8
9
10
11
var SerialPort = require('serialport'),
      portName = '/dev/ttyUSB0',
      sp = new SerialPort(portName),
      sensorVal = 0;
 
sp.on('open'function(){
    console.log('Serial Port OPEN');
    sp.on('data'function(data){
        console.log("Light Sensor Value : ", data[0]);
});
}); 
cs


시리얼 포트를 통해 아두이노에서 송신된 데이터를 수신하는 코드에요. 성공적으로 수신하면 화면에서 조도 센서 값을 읽을 수가 있어요. 그리고 이제 아두이노 스케치를 이용해서 아두이노에서 시리얼 통신으로 센서 값을 보내는 코드를 작성해 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void setup()
{
  // 전송 속도를 설정해줍니다.     
    Serial.begin(9600); 
}
void loop()
{
    int val;
 
    // 조도 센서의 측정된 값을 읽어옵니다. 
    // 센서에 연결한 아날로그 포트 번호를 매개변수로 지정합니다.
    // 저는 A1에 연결하였으므로 1번을 사용했습니다.
    val = analogRead(1);         
    
    // 조건문은 무시하셔도 됩니다. 
    // Serial.write(val)을 통해 시리얼 통신을 한다는 점만 기억하시면 됩니다.
    if(val > 40)
        Serial.write(val);    
    else if(val < 5)
        Serial.write(val);    
     
    delay(1000);
}
 
 
cs



센서 값을 시리얼 통신을 통해서 성공적으로 전송받을 수 있는 것을 확인하였습니다. 다시 말씀드리지만 이것은 NodeJS를 이용해서 작성된 코드입니다. 물론 NodeJS 외에도 다른 언어를 이용해서 라즈베리파이와 아두이노 간의 시리얼 통신이 가능합니다. 


도움이 되셨나요?? 아래에 하트 버튼은 저에게 큰 힘이 됩니다. 즐거운 하루되세요!! 

,

1. 의사 결정 트리(Decison Tree)


아키네이터라는 게임해보셨나요?? 이상한 요술 램프 지니같이 생긴 캐릭터가 여러 가지를 물으면서 사용자가 생각하는 내용을 맞추는 게임인데요. 짜증 날 정도로 잘 맞춰서 나중에는 엄청 말도 안 되는 걸 생각해서 정신 승리를 추구하려는 주변인+나 자신을 많이 봤습니다. (저도 그러면서 컴퓨터 따위가 감히 내 생각을 맞출 수가 없지 하고 만족했지만...)


저의 조심스러운 추측이지만 아키네이터는 의사 결정 트리를 사용한 게 아닐까 생각이 듭니다. 의사 결정 트리는 일종의 분류기인데 트리를 내려가면서 다양한 질문을 받게 됩니다. 그리고 그 질문의 응답에 따라 어떤 분류에 속하는지를 결론을 내려줍니다. 의사 결정 트리의 장점은 분류 결과를 사람이 쉽게 이해할 수 있다는 겁니다. 왜냐하면 의사 결정 트리는 사람이 생각하는 과정과 매우 유사하기 때문이죠. 예를 하나 봅시다.


 

만약 여러분이 소개팅 주선자이고 소개팅 상대를 물어보는 친구가 있다고 생각합시다. 소개팅을 받는 친구는 아주 설렌 마음에 자기의 이상형에 맞는지 여러분께 계속 물어볼 겁니다. 키는 큰지 돈은 많은지 차는 있는지 잘 생겼는지 성격이 어떤지 말이죠. 질문과 답이 오가는 과정에서 친구는 내 이상형이다 또는 내 이상형이 아니라고 결정을 내리게 됩니다. 그 결정과정을 보여주는 것이 위의 그림입니다. 그림을 보았을 때 어떠한 과정으로 분류를 해나가는지를 쉽게 알 수 있습니다.


의사 결정 트리의 단점은 과적합(Overfitting)이 되기 쉽다는 점입니다. 과적합은 훈련 데이터 내에서는 완전 딱 들어맞는 형태를 보여주지만 새로운 데이터에 대해서 예측 결과가 현저히 떨어지는 현상을 의미합니다.



2. 정보 이득(Infomation Gain)


정보 이득은 어떤 순서로 트리를 구성하는 것이 효과적인지를 선정하는 지표로 사용합니다. 위에 소개팅의 예를 들어서 정보 이득의 간단한 개요를 설명해 보겠습니다.



다시 여러분의 친구에게 주변에 좋은 사람을 소개팅하려고 합니다. 위에서 예를 들었던 거와 다른 순서로 질문을 합니다. 키가 큰지 노래를 잘 하는지 말이죠. 전부 다 마음에 드는 상황에서 마지막으로 남자냐고 물었더니 아니라고 대답해 버린 주선자.. 제 친구였으면 아마 반 죽여 버렸을 겁니다. 처음에 남자인지 물어봤으면 그 아래의 내용은 질문할 필요조차 없었는데 말입니다. 이처럼 질문들이 어떤 것이 더욱 결정적이고 변별력 있는지를 수치적으로 계산하는 것이 정보 이득입니다.


정보 이득을 계산하기 위해서는 섀넌 엔트로피라는 개념이 필요합니다. 일반적으로 물리나 수학을 공부하신 분이라면 엔트로피라는 개념이 무질서도를 의미한다는 것을 의미한다는 걸 알고 있을 겁니다. 만약 엔트로피가 크다면 무질서한 정도가 크다는 걸 의미합니다. 이해가 안 된다면 여러분이 지금 제멋대로 막 흩어 놓은 책상을 보시면 됩니다.(정말 지저분하죠?? 엔트로피가 크다는 겁니다.) 


섀넌 엔트로피는 비슷한 의미로 생각하시면 됩니다. 섀넌 엔트로피가 크다는 것은 데이터가 정렬이 아주 안 된 상태를 의미하고 반대로 작다면 데이터가 정렬이 잘 되어 있는 상태를 의미합니다. 살짝 멀미가 나시겠지만 수식으로 섀넌 엔트로피를 확인해 봅시다.



새넌 엔트로피는 위의 식으로 정의합니다. 그림에도 나와 있지만 p(x) 값이 크면 클수록 로그 함수의 결과가 작아짐을 알 수 있습니다. l(x) 값은 항상 음수이기 때문에 H(x)를 구할 때에 부호를 변경하기 위해 음수를 곱해주는 모습도 볼 수 있습니다. 이어서 정보 이득을 구하는 수식도 한꺼번에 계산하는 식을 살펴봅시다.


간단한 예제도 같이 만들어 보았습니다. 한 사람을 대상으로 이상형의 조건에 따라 질문을 하고 그에 대한 답을 얻는 과정입니다. 우선 전체 엔트로피 H(S)를 구하는 과정입니다. 최종 결과인 이상형인 확률과 이상률이 아닐 확률을 구합니다. 각각 4개 중에 2개씩 차지하니까 확률 p(x)는 1/2, 위의 섀넌 엔트로피 공식에 집어넣으니 1이 됩니다. 


이제는 정보 이득을 한번 구해 보겠습니다. 어떤 질문을 먼저 할까 고민하다가 "잘 생겼어?"라는 질문을 먼저 했을 경우에 얼마나 정보이득을 보는지 볼까요?



간단히 설명하면 잘생겼다는 속성을 가진 데이터는 총 3개이며 그중에 이상형인 확률과 아닐 확률을 통해 섀넌 엔트로피를 구합니다. 못생겼다도 마친가지로 진행하면 되겠죠?? 그럼 이번엔 노래를 잘하는가에 대한 질문을 먼저 했을 때 정보 이득을 구해 봅시다.



똑같은 방법을 적용해서 계산을 했더니 잘 생겼다에 대한 질문의 결과보다 정보 이득이 크다는 것을 알 수 있어요. 그 말은 "노래 잘해?"라는 질문이 "잘 생겼어?"라는 질문보다 변별력이 크다는 것을 의미해요. 따라서 트리를 작성할 때 제일 먼저 "노래 잘해?"를 먼저 물어보는 것이 좋겠네요.. 이처럼 정보 이득을 사용하면 질문의 순서를 정하는데 근거가 되어 줄 수 있어요.

3. 구현(python)

블로그를 방문하신 분들은 위의 이론들보다 그래서 어떻게 구현하는데? 가 더욱 중요할 겁니다.

구현 순서는 섀넌 엔트로피와 정보 이득을 계산하여 트리를 구성하는 것까지 진행해보겠습니다.


섀넌 엔트로피 계산

1

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from math import log
 
def shannonEntropy(dataSet):
    # 확률 계산할 때 분모로 들어가는 부분
    dataSize = len(dataSet)
    
    # 라벨별로 몇 개씩 있는 지 저장
    labelCount = {}
    entropy = 0.0
    
    # 데이터 집합 중 라벨 별로 몇 개씩 있는지 계산
    for dataLine in dataSet:
        curLabel = dataLine[-1]
        if curLabel not in labelCount.keys():
            labelCount[curLabel] = 0
        labelCount[curLabel] += 1
 
    # 확률 계산 및 섀넌 엔트로피 계산
    for key in labelCount:
        prop = float(labelCount[key]) / dataSize
        entropy -= prop * log(prop, 2)
    
    return entropy    
        
cs


정보이득 계산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
    잘 생김, 노래 잘부름 등등 속성 중에서 하나를 선택합니다.
    그 속성 값을 제외한 나머지 값들을 결과로 반환하는 함수입니다.
    (정보이득을 계산하기 위함)
"""
def pickResult(dataSet, index, value):
    result = []
    for dataLine in dataSet:
        if dataLine[index] == value:
            tempVec = dataLine[:index]
            tempVec.extend(dataLine[index+1:])
            result.append(tempVec)
 
    return result
 
# 어떤 질문을 먼저 해야 효율적일지 선별하는 과정
def getBestInfoGainFeat(dataSet):
    baseEntropy = shannonEntropy(dataSet)
    bestInfoGain = 0.0
    index = 0
    numOfFeat = len(dataSet[0]) - 1
    for i in range(numOfFeat):
        featValueSet = set([value[i] for value in dataSet])
        newEntropy = 0.0
        
        # 정보이득을 구하는 과정
        for element in featValueSet:
            subDataSet = pickResult(dataSet, i, element)
            prop = len(subDataSet) / float(len(dataSet))
            newEntropy += prop * shannonEntropy(subDataSet)
        
        infoGain = baseEntropy - newEntropy
        
        if bestInfoGain < infoGain:
            bestInfoGain = infoGain
            index = i
    
    return index      
cs


트리 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 다수결 
def majority(dataSet):
    classCount = {}
    for data in dataSet:
        if data not in classCount.keys():
            classCount[data] = 0
        classCount[data] += 1
    sortedClassCount = sorted(classCount,key=operator.itemgetter(1), reversed=True)
    return sortedClassCount[0][0]
 
 
 
def createTree(dataSet, labels):
    classifyList = [data[-1for data in dataSet]
 
    # 정지조건 1. 모든 라벨 값이 동일한 경우
    if classifyList.count(classifyList[0]) == len(classifyList):
        return classifyList[0]
 
    # 정지조건 2. 더 이상 분류할 속성이 없는 경우 -> 다수결로 결정
    if len(dataSet[0]) == 1:
        return majority(classifyList)
 
    bestIndex = getBestInfoGainFeat(dataSet)
    bestIndexLabel = labels[bestIndex]
    Tree = {bestIndexLabel : {}}
    del(labels[bestIndex])
    featValue = set([example[bestIndex] for example in dataSet])
    for value in featValue:
        subLabels = labels[:]
        Tree[bestIndexLabel][value] = createTree(pickResult(dataSet, bestIndex, value), subLabels)
        
    return Tree 
cs




,