심심해서 하는 블로그 :: '분류 전체보기' 카테고리의 글 목록 (5 Page)


전체 소스 코드 : https://github.com/ssooni/ssoonidev_blog

Spring Boot : ver 1.5.9

JAVA : ver 1.8 

Build Tool : MAVEN


1. 목표

사용자가 View를 통해 데이터를 조회하여, 새로운 데이터를 입력하거나 기존 데이터를 삭제, 수정이 가능한 웹페이지를 작성하는 것이 이번 포스팅의 목표입니다.


2. pom.xml

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
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<!-- MYSQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
 
<!-- JSP / JSTL -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
 
cs


해당 Spring Boot 프로젝트에 필요한 Dependency Library를 추가해 줍니다. 

주요 라이브러리를 간단하게 소개하고 넘어가죠.


mybatis-spring-boot-starter

Mapper라는 아주 좋은 기능을 가지고 있는 Persistence Framework MyBatis를 사용합니다. 

JDBC만으로 코드를 작성하면 PrepareStatement, Connection 등을 사용하여 연결하고, 데이터베이스의 결과를 직접 매핑을 해야 하는 번거로움이 있었는데 @Mapper 와 설정 파일을 이용하면, MyBatis가 매핑까지 해줘서 간단하게 코딩이 가능합니다. 


mysql-connector-java

정형 데이터베이스 MySQL와 연결을 위해 사용하는 라이브러리입니다. Spring Boot의 설정 파일 application.properies에 작성한 URL과 mysql User 정보 등을 읽고 데이터 베이스에 접근합니다.


3. MySQL 테이블 생성

1
2
3
4
5
6
7
create database ssooni;
use ssooni;
create table board(
    bno int auto_increment primary key
    userName varchar(10),
    contents text );
insert into board(userName, contents) values("ssooni""Welcome!!");
cs


(MySQL 설치 및 설정 방법은 추후에 포스팅할 예정입니다.) 

MySQL Workbench나 cmd(또는 bash)를 이용하여 MySQL 데이터베이스에 접속해봅시다.

그 후, 위의 쿼리를 이용해서 데이터베이스를 생성하고 해당 데이터베이스에 사용할 board 테이블을 만들고, 데이터베이스에 Sample 데이터를 입력하는 과정까지 진행해 봅시다.


그리고 테이블 생성과 데이터 입력이 잘 되었는지 확인해 봅시다.

desc [테이블 명] 과 select * from [테이블 명]을 이용하여 위의 그림처럼 조회가 된다면 성공입니다.


4. application.properties 설정

1
2
3
4
5
6
7
8
## View Path Config ## 
spring.mvc.view.prefix=/WEB-INF/view/
spring.mvc.view.suffix=.jsp
 
## Mysql Config ##
spring.datasource.username=ssooni
spring.datasource.password=ssooni
spring.datasource.url=jdbc:mysql://localhost:3306/ssooni?useSSL=false
cs


Spring Boot로 개발을 하면서 가장 괜찮았던 점은 properties 파일에 모든 설정값을 넣어 관리가 가능하다는 점입니다. yaml 파일 형태로도 물론 관리가 가능하며, 이전 프로젝트에서 활용한 경험이 있습니다. 



가장 첫 번째 단락은 jsp 파일이 있는 경로와 확장자를 지정하는 설정입니다. 기존 Spring Framework에서는 xml 파일을 뒤져서 해당 경로를 지정하는 방법을 쓰는데, xml에 비하면 가독성이 아주 좋은 편이라 편리하네요. 참고로 webapp/WEB-INF/view/ 경로는 위의 그림 파일처럼 직접 폴더를 생성해야 합니다. 


두 번째 단락은 MySQL의 URL 정보와 DB 로그인을 위한 ID / PW 정보를 설정합니다. 

URL의 구조는 보통 jdbc:mysql://[ host ip ] : [ host port ] / [ database name ] ? [ option ]으로 구성됩니다. 따라서 지금 저는 localhost의 3306번 포트에 ssooni라는 데이터 베이스에 접근하는 거죠. 

useSSL은 패킷을 암호화하는 Secure Socket Layer의 사용 여부를 설정하는 건데 database에 SSL 설정을 안 했기 때문에 사용하지 않음(false) 옵션을 주었습니다.


5. 설정 끝!! 프로그래밍 하자.

VO 또는 DTO 또는 Domain 클래스 작성

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
package com.example.domain;
 
public class BoardDomain {
    
    private int bno;
    private String userName;
    private String contents;
    
    // GETTER AND SETTER + TOSTRING
    public int getBno() {
        return bno;
    }
    public void setBno(int bno) {
        this.bno = bno;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getContents() {
        return contents;
    }
    public void setContents(String contents) {
        this.contents = contents;
    }
}
 
cs


사람들마다 다르게 명명하지만, 아무튼 비슷하게 사용하고 있는 모델 클래스입니다. 데이터 베이스의 각각의 칼럼에 대응하게 작성하여, 조회 결과를 담을 수 있고 새로운 칼럼을 추가하기 위해 데이터베이스에 데이터를 실을 때 사용하는 클래스입니다. 사실상 getter와 setter, Log에 이쁘게 남기기 위한 toString() 함수로 구성된 아주 간단한 클래스입니다.


Annotation 작성 vs mapper.xml 작성

다음은 Mapper Interface 작성입니다. Mapper Class 하나는 MySQL 데이터베이스의 테이블 하나와 대응합니다. MyBatis는 xml 파일 또는 Annotation을 이용해서 쿼리를 설정하고 데이터베이스에 접근을 합니다. 저는 두 가지 다 이용해 보려고 합니다. 각각의 장단점은 분명히 있으니깐요.


선택 1) Annotation 작성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Mapper
public interface BoardMapper {
    
    @Select("SELECT * FROM board WHERE bno = #{bno}")
    public List<BoardDomain> findByBno(@Param("bno"int bno);
    
    @Insert("INSERT INTO board(userName, contents) VALUES(#{userName}, #{contents})")
    public void insert(BoardDomain board);
    
    @Update("UPDATE board SET contents=#{contents} where bno=#{bno}")
    public void update(BoardDomain board);
    
    @Delete("DELETE FROM board where bno=#{bno}")
    public void delete(@Param("bno"int bno);
 
}
cs

com.example.dao.BoardMapper.java 코드


MySQL로 할 수 있는 조회(@Select), 생성(@Insert), 갱신(@Update), 삭제(@Delete)를 할 수 있는 코드입니다. @Mapper Annotation과 각각의 함수마다 어떤 쿼리를 수행하는지를 작성하는 것이 끝입니다. 이 방법은 쿼리가 단순한 경우에 사용하면 좋습니다. 


선택 2) Mapper.xml 작성 

1
2
3
4
## Mapper Config ##
mybatis.type-aliases-package=com.example.domain
mybatis.mapper-locations=mapper/**/*.xml

cs


xml 파일로 mapper를 설정하려면 우선 application.properties에 mapper의 위치와 domain 패키지를 추가로 설정해줍니다. domain 패키지는 나중에 mapper.xml에서 resultType을 지정할 때  com.example.domain.BoardDomain 형태가 아닌 BoardDomain으로 간략하게 적기 위해 설정했습니다.

그리고 mapper-locations은 resourse 폴더 안에 mapper 폴더를 만든 후 설정해주시면 됩니다.


 

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd"
 
<mapper namespace="com.example.dao.BoardMapper"
    <select id="findByBno2" parameterType="Integer" resultType="BoardDomain"
        SELECT * FROM borad WHERE bno = #{bno} 
    </select
</mapper>
 
public List<BoardDomain> findByBno(@Param("bno") int bno);
 
/cs

/resource/mapper/BoardMapper.xml(11번째 줄은 제외)


11번 줄의 코드와 같은 결과를 보여주는 xml 파일을 하나 생성하였습니다. 물론 <insert>등도 가능합니다. 가장 위의 DTD를 입력하면 Ctrl + space 자동 완성을 지원해줘서 작성하기 편리해집니다. 


namepspace에는 mapper를 사용할 인터페이스 명을 사용합니다. 저는 선택 1번에서 사용한 BoardMapper.java 파일을 그대로 사용할 예정입니다. 그리고 parameterType과 resultType을 설정해줍니다.


1
2
3
4
5
6
7
8
9
@Mapper
public interface BoardMapper {
    
    @Select("SELECT * FROM board WHERE bno = #{bno}")
    public List<BoardDomain> findByBno(@Param("bno"int bno);
    
    //중간 생략
    public List<BoardDomain> findByBno2(int bno);
}
cs

com.example.dao.BoardMapper.java


아까 작성한 BoardMapper.java에 findByBno2 함수를 다음과 같이 작성했습니다. xml 작성 방법의 장점은 동적 SQL처럼 복잡한 쿼리를 사용할 때 Annotation 보다 훨씬 수월하게 작성할 수 있다는 점입니다.  


Service 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.service;
 
import java.util.List;
 
import com.example.domain.BoardDomain;
 
public interface BoardService {
 
    public List<BoardDomain> findByBno(int bno);
    public List<BoardDomain> findByBno2(int bno);
    public void insert(BoardDomain board);
    public void update(BoardDomain board);
    public void delete(int bno);
 
}
cs

com.example.service.BoardService.java


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
39
40
41
42
43
44
package com.example.service.impl;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.example.dao.BoardMapper;
import com.example.domain.BoardDomain;
import com.example.service.BoardService;
 
@Service
public class BoardServiceImpl implements BoardService{
 
    
    @Autowired
    BoardMapper boardMapper;
    
    @Override
    public List<BoardDomain> findByBno(int bno) {
        return boardMapper.findByBno(bno);
    }
        
    @Override
    public List<BoardDomain> findByBno2(int bno) {
        return boardMapper.findByBno2(bno);
    }
 
    @Override
    public void insert(BoardDomain board) {
        boardMapper.insert(board);
    }
 
    @Override
    public void update(BoardDomain board) {
        boardMapper.update(board);
    }
 
    @Override
    public void delete(int bno) {
        boardMapper.delete(bno);
    }
    
}
cs

com.example.service.Impl.BoardServiceImpl.java


"아니.. DAO랑 똑같은데 굳이 또 해야 해???" 

이런 질문을 주변에서 간혹 받기도 합니다. 앞서 말했듯이 DAO는 테이블 당 하나씩 연결되는 것이라면 Service는 비즈니스 또는 기능당 하나씩 사용하는 겁니다. 지금은 Service 클래스가 데이터 베이스의 테이블을 하나 밖에 안 쓰는 경우이지만 , 하나의 기능 상에 테이블을 두 개 이상 사용하는 경우가 실제 프로젝트에서 상당히 많습니다. 예를 들면 게시물을 작성하는 동시에 게시물 History 테이블에 이력을 저장하는 경우가 대표적입니다.


6. Test

여기까지 수행하면 데이터베이스에 데이터를 읽는 코드를 작성해서 데이터를 잘 불러오는지 테스트를 진행해보겠습니다. controller 패키지와 그 안에 BoardController Class를 생성하고 아래 코드를 작성해 봅시다.


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
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.ModelAndView;
 
import com.example.domain.BoardDomain;
import com.example.service.BoardService;
 
@Controller
public class BoardController {
 
    @Autowired
    BoardService boardService;
    
    @GetMapping("/test")
    public ModelAndView readTest() {
        List<BoardDomain> boardList = boardService.findByBno(1);
        ModelAndView nextPage = new ModelAndView("board/readTest");
        nextPage.addObject("boardList", boardList);
        return nextPage;
    }
}
cs
com.example.controller.Boardcontroller.java

그리고 화면을 보여줄 JSP 파일도 만들어 줍니다. view 폴더 안에 board폴더를 생성한 후 readTest.jsp 파일을 생성해 아래와 같이 작성해 줍니다.


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
39
40
 
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
<link rel="stylesheet"
    href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
 
<title>Test Read Data</title>
</head>
<body>
    <div class="container">
        <h2>Board</h2>
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>bno</th>
                    <th>contents</th>
                    <th>userName</th>
                </tr>
            </thead>
            <tbody>
                <c:forEach var="board" items="${boardList}">
                    <tr>
                        <td>${board.bno}</td>
                        <td><a href="#">${board.contents}</a></td>
                        <td>${board.userName}</td>
                    </tr>
                </c:forEach>
            </tbody>
        </table>
    </div>
</body>
</html>
cs


결 과 


다음 포스팅에서는 Controller와 View에 대하여 알아보는 시간을 갖도록 해볼게요.

도움이 되셨으면 좋겠습니다.

 

,

정보처리기출_2006~2015.zip

1. 합격했습니다!!





2017년에 지금까지 제가 한 일 중에 가장 잘한 일이라고 생각해요.

직접 서울 공단에 가서 자격증을 수령했습니다. 여권같이 생긴 게 멋있더라고요.

저는 2017년 1차 필기와 1차 실기 시험을 쳤고요. 그동안 공부를 했던 경험들을 떠올리면서 포스팅을 진행할게요

시험을 준비하시는 많은 분들께 도움이 될 수 있었으면 합니다.

(참고로 저는 4학년 소프트웨어학과 전공자입니다)


2. 필기 공부방법

공단에서 2017년부터 정보처리기사 시험의 유형을 다르게 하겠다고 선언했었습니다. 이제 겨우 시험을 칠 자격을 얻었는데 실기는 서술형으로 나온다고 하질 않나.. 필기시험은 어렵게 낸다 하질 않나. 운이 지지리도 없다고 생각했어요. 그래도 4년 동안 대학교에서 공부를 했으니까 어느 정도는 알겠지 했는데, 기출문제를 풀어보니까 막상 그런 건 또 아니더라구요.(이럴려고 4년 공부했나.. 자괴감 들고...)




저 같은 경우에는 자격증 공부를 할 때, 책 없이 하기에는 많이 힘이 들어서 시나공 summary 정보처리기사 필기 책을 사용했습니다. 이 책은 정말 간단한 요약정리와 기출문제들로 구성되어 있어요. 그리고 필기 기출문제 파일을 따로 얻어서 문제를 풀었습니다. 파일로 첨부해 드렸으니까 공부하실 때 요긴하게 사용하세요. 


저는 일단 파일에 있는 기출문제를 2012년부터 2015년까지 출력해서 문제를 풀었습니다. 

프린트한 문제를 풀고 나서, 오답을 체크할 때 summary 책을 이용해서 어떤 개념이 있었는지를 프린트한 기출문제에 다시 적어서 암기를 하고 넘어가는 방법으로 매 회를 진행하였습니다. 


기출문제를 공부하시다 보면 반복되는 문제가 한두 문제씩 나옵니다. 이 문제 같은 경우에는 반드시 꼭꼭꼭 집고 가시는 게 좋습니다. 실제 시험을 치면 생판 처음 보는 문제들도 있지만, 오아시스 같은 이런 문제들이 빈번하게 출제됩니다. 심지어 답도 똑같이 출제되는 경우도 있으니까 과락을 면하기 위해서 반드시 꼭 공부하시길 바랍니다.


보통 필기를 공부하신 분들 후기를 보면 전자계산기 구조 과목과 데이터 통신 과목이 어렵다고 하시는 분들이 많습니다. 저도 또한 저 두 과목이 가장 공부하면서 어렵다고 느낀 점이 범위가 넓고, 외울 게 너무 많습니다.  특히 데이터 통신은 프로토콜을 외우는 게 정말 힘들었습니다. 그래서 복습을 할 때에도 이 두 과목에는 좀 더 신경을 쓰면서 공부를 했습니다. 파트별로 공부를 하시는 분들은 데이터베이스, 운영체제, 소프트웨어 공학을 먼저 공부해서 점수를 끌어올리고 나머지 두 과목에서 과락을 면하자는 목표를 가지고 공부를 하는 게 좋습니다.  먼저 공부하는 3과목의 경우에는 기출 문제에서 반복되는 문제가 많고, 외우는 게 어렵다고  두 과목보다는 적습니다. 


필기시험은 과목당 30분씩 2시간 반 동안 시험을 보고 절반 이상 시간이 지나면 퇴실이 가능합니다. 

수능 과학 탐구영역처럼 30분씩 딱딱 정해서 과목별 시험지를 배부하는 것은 아닙니다. 모든 과목이 있는 시험지를 배부하고 순서도 원하는 데로 푸시면 됩니다. 그리고 시험지는 나중에 집에 가져가실 수 있는데, 시험이 끝나고 6시쯤에 가채점 답안이 공단에서 공시합니다. 그 동안 시험치신 분들이 이의를 제기할 수도 있고, 그게 받아 지는 경우 문제의 답도 달라질 수 있으니. 시험 결과가 나올 때까지 가채점은 그냥 가채점이라고 생각하시면 마음 편합니다. 실제로 제가 친 시험에도 문제에 오류가 있어서 해당 문제에 답이 없다라고 정정되었습니다.



전자 계산기 과목에 조금 더 많이 신경을 쓰다 보니 전자계산기 과목 점수가 높게 나왔어요. 물론 찍은 것도 어느 정도 맞아서 높은 점수가 나왔을 뿐이지 그동안 공부하면서 푼 기출문제의 평균은 60점대 초반을 왔다 갔다 했었어요. 필기는 다시 말씀드리지만 기출문제를 정확하게 푸는 것이 가장 중요하다고 생각합니다.


 3. 실기 공부방법

필기를 무사히 통과하고 실기 준비를 시작했어요. 예전에는 실기도 객관식이고 데이터베이스와 알고리즘을 어느 정도 맞추면 사실상 합격이라 사람들이 저 두 과목만 공부를 하고 합격을 하는 사례가 많아지자 공단에서 싹 다 서술형, 단답식으로 변경하겠다고 공언해 버렸죠.


실기 책도 역시 시나공으로 준비했습니다.

실기는 3시간 동안 시험을 치며, 총 20문제에 합격은 60점이상, 문제마다 배점은 각각 달리되어 있습니다.


알고리즘 - 25점

데이터베이스 - 25점

업무 프로세스 - 15점

전산영어 - 10점

IT 신기술 동향 및 시스템 관리 - 25점 


저는 일단 알고리즘과 데이터 베이스는 무조건 다 맞추겠다고 목표를 세웠습니다. 전공 수업으로 웬만한 것은 풀 수 있고, 신기술 동향이나 전산 영어는 외워야 할 범위가 엄청 많습니다. 거기다가 영어 풀네임을 요구하는 문제가 간혹 있기 때문에 외우기 부담이 되는 경우도 있어요. 이 두 과목은 매일매일 어느 정도 범위를 정해놓고 꾸준히 보는게 중요한 거 같습니다. 시간이 없다면 보안에 관련된 전산 영어를 보고 가시는 걸 추천합니다. 


알고리즘은 C / Java의 입출력문, 연산식, 조건문 등 간단한 문법과 문제를 푸는 방법에 대해서 이해를 하면 충분히 맞출 거라고 생각해요. 데이터 베이스는 쿼리문, 관계 대수, 정규화를 집중적으로 공부했습니다. 알고리즘은 전체 코드를 코딩하는 개념보다는 빈칸에 들어가야 할 코드에 대하여 푸는 문제가 나왔었고, 데이터베이스는 요구하는 사항에 대한 쿼리를 작성하는 문제가 나왔습니다. 


직접 데이터베이스나 컴파일러를 설치해서 실행해 보는 것이 이해하는데 가장 편하겠지만, 이쪽으로 갈 생각 없이 단순히 스펙으로 자격증을 따겠다고 생각하신 분들은 교재에 나온 문제를 모두 풀어보는 것이 좋습니다. 알고리즘과 데이터베이스는 약간의 암기 + 많은 응용을 요구하기 때문에 문제를 많이 풀면서 이해하는 것이 좋습니다.


업무 프로세스는 엄청 긴 지문을 읽고 각각에 해당되는 부분을 찾아 넣는 문제가 나왔는데, 시험 치기 전에 간단하게 몇 문제를 풀어보면 어려움 없이 문제를 해결하실 수 있을 거라 생각합니다. 



서술형으로 변경된 후 처음으로 치러진 시험이라 그런지 1회 난이도는 생각했던 것보다 쉬웠습니다. 답안 채점도 생각보다 후하게 준 거 같아요. 인터넷에 떠도는 가채점 답안으로 채점한 결과보다 높은 결과가 나오면서 무사히 합격했습니다. 시험을 친지 꽤 오래돼서 문제에 대한 기억과 공부한 방법에 대해서 가물가물하긴 해요. 하지만 궁금하신 걸 물어보신다면 답변해 드리겠습니다. 다들 좋은 결과 있으시길 바랍니다. 


도움이 되셨다면 밑에 공감 하트 한 번씩만 눌러주세요. 하트는 저에게 큰 힘이 됩니다.

,

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




,

1. pyplot 시작하기

python에서 데이터 가시화를 위한 라이브러리로 matplotlib.pyplot을 사용한다. 파이썬이 데이터를 처리하는 데에는 일가견이 있는 언어인데 가시화 라이브러리도 있다니.. (R에만 있는 줄 알았는데)

일단 저는 윈도우에서 PyCharm을 설치해서 사용하기 때문에 윈도우에서 설치하는 과정만 소개하겠습니다.



위의 URL에서 사용중인 파이썬의 버전에 맞춰서 다운받은 다음 실행하시면 됩니다.


2. 그래프 그리기 : plot()

간단한 직선 그리기

그래프를 그릴 때 필요한 것은 X축과 Y축, 데이터겠죠?? 

일단 처음 그려보는거니까 간단한 걸로 한번 그려 봅시다.


1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt
 
# y좌표 데이터만 있는 경우
plt.plot([123])
# (x,y) 좌표를 둘 다 사용할 경우
plt.plot([1,2,3],[2,4,6])
# axis([x축 시작, x축 끝, y축 시작, y축 끝])
plt.axis([0,4,0,7])
plt.show()
cs


첫 번째 그린 그래프가 파란색 선이에요. Y좌표만 입력받았을 경우에는 축의 시작점 부터 1씩 증가하는 꼴로

좌표가 자동으로 매칭되요. plot([1,2,3])의 경우에는, 따라서, (0,1), (1,2), (2,3)을 이은 선이 그려지는 거죠.


두 번째 주황색 그래프는 (X, Y) 좌표를 직접 입력한 경우에요. 앞의 리스트에는 X축의 좌표, 

뒤에는 Y축의 좌표로 사용해요. 따라서 plot([1,2,3],[2,4,6])은 (1,2), (2,4), (3,6)을 이은 을 그리게 되죠.


axis는 화면에 보여줄 축의 최대 값과 최소 값을 지정하는 함수로 주석에서 보셨듯이, 

이 그래프의 X축은 0~4 까지 Y축은 0 ~ 7까지로  설정된 것을 그래프를 통해서도 확인이 가능할 거에요.

그리고 그래프를 화면에 보여주는 show()함수를 사용하는 것으로 마무리!!!



이쁘게 그리고 싶어요..

plot에는 다양한 옵션들이 있어요. 그래프 색깔을 지정할 수 있는 옵션과 선의 스타일도 지정하는 옵션도 있어요. 저는 그 중에서 몇가지 종류만 사용해 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.plot(x, 2**x, 'b')
plt.axis([0,6,0,40])
plt.show()
cs



 r

빨강 

 --

점선 

 b

파랑 

 o

동그라미 

g

초록 

^

삼각형 

y

노랑 

 *

별 


이 정도가 제가 알고 있는 스타일과 색상이에요. pyplot의 공식페이지에서 더욱 다양한 옵션들이 있어요.


하나의 창 여러 개 그래프

그래프를 이 때까지는 하나의 축상에서 두 개씩 그리는 것을 연습했어요. 그런데 여러 개의 그래프를 각각의 창으로 표현하는 방법도 있어요. 바로 subplot()을 이용하는 방법이죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
# y = x^2 그래프
plt.subplot(221)
plt.plot(x, x**2'r--')
# y = 2^x 그래프
plt.subplot(222)
plt.plot(x, 2**x, 'b-')
 
# y = x
plt.subplot(223)
plt.plot(x, x, 'g')
 
# y = 1/(x+1)
plt.subplot(224)
plt.plot(x, 1/(x+1))
 
plt.show()
cs



subplot()에 들어가는 숫자에 대하여 해설을 하자면 221은 전체창을 2 * 2로 나눈 뒤 1번째 그래프를 의미해요. 222는 2 * 2 중 2번째 이런 식으로 말이죠. 분할하고자 하는 화면의 갯수에 따라서 숫자를 다르게 쓰면 됩니다.


그래프를 상세하게 표기하기

여러 개의 그래프를 subplot으로 그리는 경우나 하나의 축에 그래프를 그리는 경우 제목이나 범례가 없으면 어떤 그래프인지 알기 힘들 겁니다. title() 함수와 legend() 함수를 이용해서 그래프의 제목과 범례를 지정할 수 있답니다. 그리고 X축과 Y축의 의미도 있으면 좋겠죠? X축과 Y축에 라벨을 붙여주는 xlabel(), ylabel()함수도 한 번 사용해보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
 
# x 좌표는 0부터 0.01씩 더해져 최대 5까지
= np.arange(0.05.00.01)
 
# 그래프의 타이틀 설정
plt.title('two graph')
plt.plot(x, x, 'g', label='y=x')
plt.plot(x, 2 * (x+1), 'r', label='y=2*(x+1)')
 
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs



3. Scatter 사용하기

산포도 그래프를 그리고 싶다!

분류기를 사용하여 데이터를 분류하는 경우 산포도 그래프로 데이터를 가시화하는 것을 많이 볼 수 있습니다. 데이터가 어느 쪽에 밀집해 있는지를 확인하기 쉽기 때문인데요. matplotlib.pyplot의 scatter() 함수를 이용하면 산포도 그래프를 손쉽게 그릴 수 있답니다. 예제 코드를 보시죠!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 

x1 = [12112069]
y1 = [3710201630]
 
x2 = [571692]
y2 = [381023432]
 
# 그래프의 타이틀 설정
plt.title('two scatter')
plt.scatter(x1, y1, marker='s', c='r')
plt.scatter(x2, y2, marker='*', c='b')
# X, Y축에 이름을 붙어주기
plt.xlabel('X Data')
plt.ylabel('Y Data')
 
# 범례 표시
plt.legend()
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs


두 개의 데이터를 간단하게 준비하고 scatter() 함수를 사용해서 그래프를 그립니다. 속성 중에 marker 속성이 있는데 이 속성을 이용하면 네모, 세모, 별 모양 등의 모양을 지정할 수 있고, c는 색깔 속성을 의미합니다. 그 외 다양한 속성들은 pyplot.scatter() 페이지에서 확인할 수 있습니다!


4. annotation 이용하기

아니 이런 기능도 있어??

단순히 그래프 그리는 용도로 선 그래프나 산포도 그래프 등의 그래프를 그리는 함수만 있는 게 아니다. 그래프에 주석을 달거나 화살표를 그리는 등의 기능도 있다.  이 기능을 사용해서 트리형 그래프를 그릴 수도 있고 표시가 필요한 부분을 표시하는 등의 기능을 수행할 수 있습니다. 예제 코드를 한 번 볼까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import matplotlib.pyplot as plt
import numpy as np
 
x1 = [12112069]
y1 = [3710201630]
 
# box 속성
bboxType = dict(boxstyle="round4", alpha=0.8, fc='w')
# 화살표 속성
arrow = dict(arrowstyle='-|>', connectionstyle='arc3,rad=0.2', fc="w")
# 그래프의 타이틀 설정
plt.title('annotation')
plt.scatter(x1, y1, marker='*', c='r', s=40 )
 
# xy는 도착점, xytext는 text가 위치할 장소
plt.annotate('here!!', xy=(11,10), xytext=(1515), size=20,
             # 수평 정렬 / 수직 정렬
             ha='center', va='center',
             # 화살표와 박스의 속성을 설정
             arrowprops=arrow, bbox=bboxType, )
 
# 눈금 표시를 해주는 함수
plt.grid(True)
plt.show()
cs

우선 아까 scatter 함수를 연습할 때 사용한 데이터로 산포도 그래프를 그려줍니다. 저는 이 데이터 중에 (11, 10) 지점이 중요한 데이터라고 생각하고 표시를 해둘려고 합니다. 이럴 때 annotate() 함수를 사용합니다. 속성이 꽤 많이 필요한걸 볼 수 있는데요. 주석에 정리했지만 아래에 다시 정리 해보았습니다.


xy : 표시할 데이터가 있는 곳입니다. 쉽게 도착점이라고 생각합시다

xytext : 텍스트 박스를 둘 위치를 지정합니다. 이 때 박스의 중앙점의 좌표를 입력하면 됩니다.

ha / va : 각각 수평 / 수직 방향 정렬 속성입니다.

arrowprop : 화살표의 속성을 지정합니다. dict() 함수를 이용하여 지정합니다.

bbox : 텍스트 박스의 속성을 지정합니다. 마친가지로 dict()함수로 지정합니다.


박스와 화살표는 다양한 모양과 속성들이 있습니다. 모든 속성을 블로그에 옮겨 적는데에는 한계가 있어서 api 링크를 납깁니다. 긴 글 읽어주셔서 감사합니다!!


,

Python2.x를 사용하여 구현하였습니다. 3.x버전을 사용하시는 분들은 참고하시길 바랍니다.


1.  k-NN 알고리즘

가장 가까운 이웃을 찾아라

k-NN 알고리즘은 기존의 데이터들은 각각의 라벨이 붙어 있고, 라벨이 없는 임의의 데이터 X가 과연 어떤 종류에 들어가는지 분류하는 알고리즘이다. 이때 X와 기존의 데이터들의 거리를 모두 구한 후 그중 가장 가까운 k 개의 데이터의 라벨들 중 가장 많은 수의 라벨이 X 데이터의 라벨로 결정하게 하는 알고리즘이다.


일반적으로 k-NN 알고리즘을 적용하기 위해서는 수치형 값을 사용하는 편인데, 임의의 데이터와 기존 데이간의 거리를 계산하기 위해서이다. 특별히 훈련하는 과정도 없는 특징도 바로 모든 기존의 데이터와 거리를 계산하는 특징으로 나타나는 것이다.


2.  NumPy로 구현


알고리즘의 구현 순서

1) 파일로부터 데이터를 수집 

2) 정규화(Normalization)

3) 가시화(Visualization)

4) 테스트 데이터와 모든 데이터 간의 거리 계산 후 가장 근접한 k개 데이터 선정후 다수결로 결정

   (k-NN 알고리즘)


1) 파일로부터 데이터 수집

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
from numpy import *
import operator
 
def file2matrix(filename):
    # 데이터에 저장된 Iris의 종류는 총 3개입니다.
    # 각각을 숫자로 매핑한 Dictionary를 구현합니다.
    Iris_dict = {"Iris-setosa" : 1"Iris-versicolor" : 2"Iris-virginica" : 3}
    
    # 파일을 열고 라인 단위로 파일을 읽어 드립니다.
    fr = open(filename)
    TextLine = fr.readline()
    
    # numOfData  : 데이터의 수를 의미
    # featMatrix : 파일로 읽어드린 데이터를 행렬로 저장하기 위해 영행렬로 초기화합니다.
    #              이 때 Feacture의 수가 총 3개이므로 Data의 수 X 3의 행렬을 만듭니다.  
    numOfData = len(TextLine)
    featMatrix = zeros((numOfData, 3))
 
    # labelVector : dataMatrix의 각 행에 대응하는 lebel을 저장합니다.
    labelVector = []
    index = 0
    
    for line in TextLine:
        # 각각의 데이터는 ','으로 구분되어 있습니다.
        # 따라서 ','을 기준으로 분리하여 줍니다.
        splitData = line.split(',')    
        # 파일에 데이터가 "종류,특징1,특징2,특징3"으로 저장되어 있으므로
        # 두번째 열부터 끝까지가 Feature입니다. 
        featMatrix[index, : ] = splitData[1:]
        labelVector.append(Iris_dictionary.get(splitData[0]))
        index += 1
    
    return featMatrix, labelVector   
cs


2) 정규화

정규화를 하는 이유는 각각의 특징(Feature)들이 서로 다른 단위를 가진다는 점에서 시작합니다. 
만약 달리기를 하는 사람의 데이터가 아래와 같이 있다고 가정하여 봅시다

Feature 1 : 지칠 때까지 최대한 달린 거리
Feature 2 : 100m 달리기 기록

Data 1 :  (10,300m, 13.40초, 장거리 선수)
Data 2 :  (5,000m, 11.30초, 단거리 선수)

Data 1 ~ Data의 거리 :

     


kNN은 Feature 간의 거리를 계산하는 알고리즘입니다. Data 1 ~ Data 2간의 거리 값에서 가장 큰 비중을

차지하는 것은 Feature 1이 되어 버리겠죠. 왜냐면 Feature를 구성하는 단위의 크기가 어마하게 차이가

나기 때문입니다. 100m 달리기 기록을 아무리 단축하여도 거리에는 큰 영향을 못 준다.


데이터를 분석하는 사람의 판단으로 Feature 1이나 Feature 2나 동일하게 중요하다고 판단을 내리는 경우

위의 같은 사례는 썩 좋아하는 상황이 아닙니다. 따라서 데이터를 일정 범위로 줄여 버리는 과정인 정규화 과정을 거칩니다.


정규화


분모에는 각 Feature의 범위가 들어가고 분자에는 data Set에 저장되어있는 Feature 값과 최소값의 차이값이 들어갑니다. 이 식을 활용해서 파이썬으로 마찬가지로 구현하면 아래와 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 위의 함수에서 이어서 사용합니다.
def normarlization(dataSet):
    # dataSet 중의 최소 값과 최대 값을 구합니다
    # 그리고 두 값의 차이로 범위를 구해줍니다.
    minVal = dataSet.min(0)
    maxVal = dataSet.max(0)
    ranges = maxVal - minVal
 
    # 정규화 결과를 저장하는 행렬을 준비합니다.
    normalDataSet = zeros(shape(dataSet))
    
    # dataSet의 행의 갯수를 저장합니다.
    rowCnt = dataSet.shape[0]
 
    # 정규화 공식에 맞게 적용하였습니다.
    normalDataSet =(dataSet - tile(minVal, (rowCnt, 1))) / tile(ranges, (rowCnt, 1)) 
    
    return normalDataSet
cs


3) 가시화

보기에도 좋은 떡이 맛도 좋다는 말이 있죠? 데이터도 숫자로 되어 있으면 뭐가 뭔지 의미를 쉽게 알 수 없을
겁니다. 파이썬에는 matplotlib라는 좋은 그래프 그리는 라이브러리가 있으니까 그것을 적극적으로 활용해 봅시다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 위의 함수에서 이어서 사용합니다.
def visualizeData(dataSet, labels):
    import matplotlib
    matplotlib.use('Agg')
 
    import matplotlib.pyplot as plt
 
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 1], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot1.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,0], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot2.svg')
    
    fig, ax = plt.subplots(1,1)
    ax.scatter(dataSet[:,1], dataSet[ :, 2], 15.0*array(labels), 15.0 * array(labels))
    fig.savefig('plot3.svg')
 
cs


짜잔.. 숫자로 봤을 때 보다 훨씬 좋죠?


4) k-NN 알고리즘

이제 마지막으로 k-NN 분류기를 구현해보겠습니다.
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
# 위의 함수에서 이어서 사용합니다.
def classify0(testDataSet, trainDataSet, labels, k):
    trainDataSetSize = trainDataSet.shape[0]
    
    # 테스트 데이터와 훈련 데이터간의 거리를 계산하는 과정입니다.
    diffMat = tile(testDataSet, (trainDataSetSize, 1)) - trainDataSet 
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances ** 0.5
    
    # 오름차순으로 argsort를 합니다.
    # 거리가 짧은 순서대로 정렬이 됩니다.
    sortedDistIndex = distances.argsort()     
    
    # 다수결의 결과를 저장하는 곳입니다.
    classCount={}
    
    for i in range(k):
        # 본격 개표 방송
        # 오름차순으로 정렬한 데이터 중 k번째 데이터까지 라벨을 확인한 후
        # 투표 결과를 저장합니다.
        votelabel = labels[sortedDistIndex[i]]
        classCount[votelabel] = classCount.get(votelabel,0+ 1
    
    # 내림차순으로 정렬합니다
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    
    return sortedClassCount[0][0]   
 
cs


3.  마치면서..

k-NN은 단순하지만 데이터를 분류하는데 효과적인 알고리즘입니다. 하지만 데이터를 전부다 순회하면서

거리를 측정해야 하기 때문에 대규모의 데이터에 적용할 때는 시간이 오래걸리는 단점이 있습니다.

앞으로 여러 가지 알고리즘을 공부해서 포스팅 하겠습니다.


정말 긴 글 읽어 주셔서 감사합니다. 밑에 하트 한 번만 눌러주시면 저에게 큰 힘이 됩니다 ㅠㅠ



,

Andrew Ng 교수님의 Machine Learning 강좌(http://www.coursera.org)

모두를 위한 머신러닝 강좌(http://hunkim.github.io/ml/)를 참고하여 포스팅하였습니다.


1. 선형 회귀

대표하는 직선!!  

왼쪽 그림과 같이 데이터가 분포하여 있을 때 X=9일 때 Y 값을 예측을 하고자 한다. 

단순히 왼쪽 그림만으로 Y 값을 예측하기에는 근거가 부족하다. 

우리가 4지 선다형 문제를 찍는 데에도 근거가 있으면 (예를 들면 답이 유독 4가 없다는 등)

묘하게 설득력이 생긴다. 통계학자들은 이런 근거를 만들고자 오른쪽과 같이 직선을 그어보았다.

이 직선 주변으로 데이터를 나타나는 점들이 분포하여 마치 데이터를 대표하는 모델로 역할을 수행한다.

따라서 이제 X=9에서 Y 값을 예측하는 것은 이 직선에서 X=9를 대입하면 끝!! 근거도 있어서 맘도 편하다.

이와 같은 방법으로 데이터를 예측하는 모델을 선형 회귀라고 하고, 이 직선의 방정식을 가설 함수라 한다.



직선 그리는 것도 각양각색

위의 그림에서 직선의 모양, 즉 가설 함수에 따라서 X=9일 때 예측값 Y의 값이 달라짐을 알 수 있다.

따라서 대표하는 직선을 어떤 기준으로 그릴 거냐는 문제가 발생한다.  



위의 그림에서 d는 원래 데이터의 값 y1과 예측값 H(x1)의 거리를 의미한다.

H(X)의 함수식 Θ1, Θ0에 따라서 d값이 달라질 것이며 어떤 점에 대해서는 너무 멀고 

어떤 점에서는 너무 가깝지 않는, 즉 분산이 최소가 되는 값을 구하여야 한다.

이 때 d값의 분산을 Cost Function이라 하고 Cost Function 최소가 되는 Θ1, Θ0를 구하는 것이 목표이다.



산을 타고 내려갑니다.

위의 그래프는 Θ에 따른 cost(Θ)의 변화이다. 앞서 말했듯이 우리가 원하는 것은 

cost(Θ)을 최솟값으로 만드는 Θ값을 구하는 것이다.

이때 사용하는 알고리즘이 Gradient descent이다. 쉽게 말해서 산타고 내려가는 것!! 

Θ를 일정한 양만큼 계속 줄여가면서 바닥으로 내려가는 것인데 만약 바닥이라면 기울기가 0이 되므로

위의 박스친 편미분 부분이 0이 되어 더 이상 감소하지 않는다. 

그리고 위의 식에서 α을 Learning rate라고 한다. α의 값은 개발자가 직접 설정해야 한다.

만약 α값이 너무 크면 바닥까지 내려오지 않을 수도 있고, α값이 너무 작으면 반복하는 횟수가 증가한다.

따라서 α값을 변형하면서 적절한 값을 찾는 것이 중요하다.


2. Tensorflow 구현

소스 코드 

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
39
40
41
42
43
import tensorflow as tf
import matplotlib.pyplot as plt
 
# 학습 데이터
X_data = [3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.1677.042,10.791,5.313,7.997,5.654,9.27,3.1]
Y_data = [1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.2212.827,3.465,1.65,2.904,2.42,2.94,1.3]
 
numOfData = len(X_data)     # 데이터의 갯수
alpha = 0.001               # Learning Rate alpha
itr = 3000                  # 반복 횟수 1000회
 
theta1 = tf.Variable(tf.random_uniform([1], 0.03.0))      # theta1
theta0 = tf.Variable(tf.random_uniform([1], 0.03.0))      # theta0
 
# H(x) = theta1 * x + theta0
hypothesis = tf.add(tf.mul(theta1, X_data), theta0)
 
# cost() = 1/2m * sum(pow(H(x) - y))
cost_function = tf.reduce_sum(tf.pow(hypothesis - Y_data, 2)) / (2 * numOfData)
 
# Gradient Descent 알고리즘
optimizer = tf.train.GradientDescentOptimizer(alpha).minimize(cost_function)
 
# 변수 초기화
init = tf.global_variables_initializer()
 
# Tensorflow session 시작
with tf.Session() as sess:
    sess.run(init)
 
    # 3000회 반복 수행
    for i in range(itr):
        sess.run(optimizer)
        if i % 20 == 0:
            print(i, sess.run(cost_function), sess.run(theta1), sess.run(theta0))
 
    # 데이터 시각화
    plt.plot(X_data, Y_data, 'ro', label='Training Data')
    plt.plot(X_data, sess.run(theta1) * X_data + sess.run(theta0), label='Linear Regression Result')
    plt.legend()
    plt.show()
 
 
cs

결 과


 

,

1. Overview

접근제어(Access Control)란??

접근제어는 자원에 대한 권한이 없는 주체(Subject)가 해당 자원의 사용을 막는 것을 의미한다.

접근제어를 하기 위해서 주체를 구별하기 위한 Identification과 본인 여부를 확인하는 인증 절차가 필요하다.

특히 인증의 성공 여부에 따라 리소스에 접근할 권한을 줄지 안 줄지 결정되므로 인증이 핵심 절차이다.


주체(Subject)는 컴퓨터를 사용하는 사람, 그룹, 프로세스, 컴퓨터와 같이 정보를 요청하는 요소이며,

객체(Object)는 파일이나 폴더, 디바이스, 메모리 등 정보를 가지고 있는 요소이다. 

주체는 접근 권한에 따라 객체를 읽거나, 쓰기, 실행이 가능하며 권한은 객체별로 다른 형태로 부여한다.


요구사항

- 신뢰가능한 입력(reliable input) : 신뢰가능함은 인증을 통과한 주체가 행하는 입력을 의미한다.

- 포괄적 vs 세분화 : 권한의 범위를 어느 수준까지 설정하는가 문제

- 최소 권한 : 주체에게 너무 큰 권한을 부여하면 시스템의 문제의 여지가 될 수 있다.

- 열린 정책 vs 닫힌 정책 / 정책 간 충돌 문제

- 정책 관리 : 관리자만이 정책을 변경할 수 있다.

- 동시성 제어 : 하나의 객체에 여러 주체가 동시에 접근할 경우 어떻게 처리할지 문제

- 의무의 분리 : 중요한 행위는 반드시 두 개이상의 user나 권한으로 나누어서 실행한다.


2. DAC(Discretionary Acess Control)

주인장 맘대로

자원의 주인이 해당 자원에 대한 다른 사람들의 권한을 결정하여 접근 제어를 하는 방법이다.

따라서 이 방법을 사용하기 위해서 각자를 구별할 ID와 해당 자원의 소유권, 그리고 다른 유저에게 분배된 권한을 정리할 파일이 필요하다. 권한을 저장하는 방법은 Access Control List, Capability List, Access Matrix가 있다. Access Matrix의 경우에는 사용자가 많고 자원들이 많은 실제 상용 시스템에서 사용하면 공란이 많은 희소 행렬의 형태라 공간 낭비가 심해 사용하지 않는다.




Access Control List vs Capability List

ACLs는 자원에 대하여 각 사용자들의 권한을 저장하는 방법이고 CL은 반대로 사용자에 대하여 각 자원들의 권한을 저장하는 방법이다. 다시 말해 ACLs는 자원 중심, CL은 사용자 중심의 저장 방법이다. 따라서 사용자에 비해 자원이 많은 개인용 PC 같은 환경에서는 ACLs, 사용자의 수가 많은 웹 서버같은 환경에서는 CL이 더욱 유용한 방법이다. 


DAC 장점

- 사용자가 접근 권한을 관리 할 수 있어서 보안 관리자가 업무가 줄어 든다.

- 권한을 수정이 쉽고 새로운 권한을 적용하기 쉽다.


DAC 단점

- 위의 그림처럼 권한을 부여한 사용자의 의도하지 않은 대로 정보가 흘러갈 수 있다.

- 루트의 권한이 너무 강력하다. 

- 주체와 객체의 수가 증가한다면 복잡해지고 크기가 커지는 문제도 있다.

- 소유자를 쉽게 변경이 가능하다는 점을 악용한 프로그램들도 많다 



Unix / Linux의 파일 시스템

리눅스의 파일 시스템의 정보는 inode 저장된다. inode에는 하나의 파일 또는 디렉터리의  UID, GID, 소유권이 명시 되어있으며 12개의 비트로 특수권한 / 읽기 / 쓰기 / 실행 권한을 표기한다. 이 inode들은 디스크 상에 inode 테이블이나 리스트에 저장이 되어진다. 그리고 파일을 열면 해당 파일의 inode는 메인 메모리에 상주하는 inode 테이블에 저장된다. 


DAC 취약점 실습


위 그림은 file1의 소유주 csos가 user1에게 읽기 권한을 부여하는 과정이다.


리눅스에서 setfacl 또는 chacl 명령어로 파일 시스템 상의 ACL을 변경할 수 있고 getfacl 명령어로 해당 파일의 ACL정보를 확인 할 수 있다. 파일의 소유자의 UID, GID 정보와 각 사용자와 그룹에 대하여 권한, 마스크 정보도 추가된 확장된 ACL을 사용한다는 것을 알 수 있다. 파일 시스템에 접근하는 경우 파일의 ACL을 확인하여 소유자 -> 지정한 유저 -> 그룹 -> 외부자로 순으로 연산 처리한다.



user1으로 접속을 변경하고 csos가 읽기 권한을 준 file1을 복사하여 user1의 영역으로 복사하였다. 이제 user1은 file1의 사본 file2의 소유자이므로 file1에 대한 모든 권한을 행사할 수 있다. 이처럼 DAC는 원 소유자 csos의 의도와 다르게 흐름을 변경할 수 있는 취약점이 있다.

,

1. ISAM 트리구조

정적 트리구조

삽입 / 삭제를 하여도 트리의 구조가 변화하지 않는 것이 특징이다. 만일 페이지에 더 이상 데이터를 추가할 수 없다면 오버플로우 페이지를 생성하여 마지막 리프노드에 체인으로 연결하여 추가한다.


오버플로우 페이지

위의 그림 왼쪽 트리 구조에서 7을 삽입하는 연산이 수행한다고 가정하자. 루트에서 시작해서 리프 노드에 도달하면 7이 들어갈 노드가 (6*, 9*)로 채워져 들어갈 공간이 없다. 이때 ISAM 트리 구조는 트리의 구조를 변형시키지 않고 아래에 7이 들어갈 수 있는 공간을 만든 후 다음 7을 삽입한다. 이 때 새로 만들어진 이 공간을 오버플로 페이지라고 한다. 


오버플로 페이지를 생성함에 따라서 얻을 수 있는 이득은 오버플로가 발생하는 경우 삽입 / 삭제 연산이 B+ 트리보다는 빠르다는 점이다. 아래에 설명할 B+트리는 동적 트리구조로 만약 위의 그림과 같은 상황이 발생하면 트리 구조를 변형을 하여 삽입을 시도하기 때문이다. 또한 Lock을 거는 경우 트리의 내용물에는 변화가 없기 때문에 리프 노드에만 Lock을 걸면 된다. 하지만 오버플로 페이지가 많이 발생할수록 위 그림에서 눈치챘을지도 모르지만 데이터를 물리적으로 정렬하지 않기 때문에 데이터를 탐색하는 속도는 늦어어진다는 단점이 있다.


2. B+ 트리구조

적 트리구조

ISAM 트리와 달리 삽입 / 삭제시에 트리구조가 변화가 발생하는  특징이 있다. 또한 Root 노드를 제외한 나머지 노드들은 데이터를 반드시 절반 이상 점유해야 하는 필수 조건이 있다. 



삽입 연산

위의 트리에서 8을 삽입한다고 가정하자. 8은 L2에 삽입되어야 하지만 현재 노드가 꽉 차있어서 삽입을 할 수 없다. B+ 트리는 오버플로 페이지를 생성하지 않고 L2를 쪼개서 새로운 노드를 만드는 방법을 사용한다. 8을 포함한 L2의 구성 요소 (5,6,7,8,9) 중 중간 값인 7은 상위 단계 노드로 Copy up 되고 트리구조의 특징상 기존의 L2(5,6,7,8,9)는 L2(5,6), L3(7,8,9)로 갈라지게 되어 아래 그림과 같이 된다.



따라서 삽입 연산 중 리프 노드에서 오버플로가 발생하면 B+트리는 해당 노드의 중간 값이 상위 노드로 Copy up 하는 과정이 발생한다. 만일 Copy-up 과정에서 상위 레벨 노드마다 오버플로가 발생한다면 어떻게 트리구조가 변화할까?



위의 그림과 같은 트리에서 15가 삽입된다고 가정하자. 15가 삽입될 노드는 L4인데 L4가 가득 차있으니까 아까 위에서 Copy up을 시도한다. L4(13,14,15,16,18) 중에서 중간 값인 15가 상위 단계 노드로 Copy up이 되려는 순간 상위 노드가 꽉 차있다. 따라서 상위 노드도 분열이 되어야 하는데 이 과정에서 분열된 상위 노드들을 연결할 새로운 루트 노드가 필요하다.  상위 노드의 값 (5, 7, 13, 15, 20) 중 중간 값인 13이 한 단계 상승한 Push up 과정이 발생하고 나머지 (5,7) (15,20)이 다음 노드로 연결되는 아래 그림과 같은 트리구조로 변화한다.



기존 트리의 높이는 1이였지만 삽입의 결과로 높이가 2로 변화하였다. B+트리는 이처럼 트리의 구조를 유지하기 위해서 Copy up / Push up 알고리즘을 사용하고 경우에 따라서 트리의 높이가 변화할 수도 있다.


삭제 연산

삭제 연산에서 가장 중요한 키워드는 루트를 제외한 각 노드는 점유율을 항상 50%이상 유지한다는 점이다. 만약 50프로가 안된다면 인접한 노드에서 빌려온다. 그런데 빌려 오니까 인접 노드도 점유율 50%를 만족하지 못한다면 인접 노드와 병합하는 방법으로 점유율 50%를 유지한다.


 

위 그림(아까 삽입한 결과)에서 24*를 삭제하는 연산을 수행한다고 하자. L6은 이제 데이터가 (21*)만 존재하게 되어 데이터의 점유율이 50프로 미만이 되어버렸다. 따라서 인접 노드인 L5에서 데이터를 (18*)를 빌려온다. L5도 (15*, 16*)을 가지고 있고 L6도 (18*, 21*)로 50프로를 유지할 수 있으므로 L6의 최솟값 18*을 상위 노드에 20*으로 Copy up 하는 것으로 마무리한다. 결과는 아래의 그림과 같다.



이번에는 L1의 1을 삭제한다고 가정하자. 1이 삭제된 L1은 (2*)만 남아 50프로를 유지할 수 없고 인접 노드 L2의 5*를 빌려서 채울려고 하지만 그렇게 진행하면 L2도 (6*)만 남아 점유율 50프로를 만족하지 못한다. 따라서 L1과 L2는 합병이 된다.



합병을 하고 상위 노드 I1의 5*를 삭제하니 I1도 50프로를 만족하지 못한다. 인접 노드 L2에서 데이터를 하나 빌릴려고 하니 연산 결과 L2도 50%를 만족하지 못한다. 따라서 합병을 해야하는 상황이 발생한다. 



합병을 할 때 상위 노드의 값은 한 단계 내려와 Pull down되어 I1, I2와 함께 합쳐진다.  그 외 여기서 다루진 못했지만 만약 I1이 데이터가 4개 있고 삭제당한 I2의 데이터가 1개 있다면 합병하게 되면 5개가 돼버려서 오버플로가 발생하는 경우가 있다. 이 경우에는 재분산(Re-distribution)을 사용하여 2개 / 3개로 다시 나누어 주는 과정이 있다. 기존의 트리의 높이는 2였지만 모든 삭제 연산을 수행한 후에 보면 높이 1의 트리로 변화하였다. 


트리 구조를 유지하면서 이득 / 손해

우선 이득은 데이터 탐색 속도가 ISAM보다 빠르다. 오버플로 페이지가 발생하지 않고 데이터가 삽입되면 그 데이터를 물리적으로 정렬하기 때문이다. 대신 삭제나 삽입의 경우 트리 구조를 유지해야 하는 면에서 ISAM에 비하면 복잡한 과정이 있기 때문에 비용이 좀 더 드는 손해는 있다. 하지만 인덱스를 사용하는 이유는 탐색 속도의 향상이므로 대부분 상용 DBMS에서는 B+ 트리를 사용하여 인덱스를 만든다.

,