바이너리의 단점

마지막 업데이트: 2022년 5월 8일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
CHML

바이너리의 단점

검색에 대한 알고리즘 중 선형검색과 이진검색에 대하여 알아보도록 하겠습니다.

선형 검색(Linear Search)

다른이름으로 순차 검색(Sequential Search) 이라고도 하는 선형검색에 대하여 먼저 알아보겠습니다.

선형 검색은 데이터가 모인 집합(배열, 링크드리스트 등)의 처음부터 끝까지 하나씩 순서대로 비교하며 원하는 값을 찾아내는 알고리즘입니다.

데이터를 정렬하거나 따로 건드릴 필요가 없고, 난이도가 쉬운 편이나, 데이터의 양이 많아지면 검색에 소요되는 시간도 비례하여 많아지고, 하나씩 일일이 비교하기 때문에 비효율적이라는 단점이 있습니다. 예를들어 위와 같은 데이터의 집합이 있을경우 4를 찾으려면 10번의 비교를 거쳐야 합니다. 100만개의 데이터가 있고 찾고자 하는 데이터가 100만번째에 있다면 100만번의 비교를 해야 한다는 뜻 입니다. 이와 같은 상황을 Worst Case라고 합니다. 관련된 용어로 평균적인 상황을 Average Case 좋은 상황을 Best Case라고 합니다.

코드를 통하여 살펴보도록 하겠습니다.

반복문을 size만큼 실행시킬텐데, 배열의 0번째 방부터 찾는 값이 있는지 훑어 나갑니다. 위 표를 예시로 삼으면 size는 10이고, target은 4가 되겠습니다. 반복문을 통해 arr[0]부터 i의 값을 증가시키면서 진행이 되겠죠. arr[9]에서 target이 되는 4라는 값을 찾았으니, 방의 번호 9를 리턴합니다.

이진검색(Binary Search)

이진검색은 다른말로 이분 (二分) 검색이라고도 합니다. 반으로 나누어서 연산하기 때문이죠. 선형검색의 경우 데이터 집합의 처음에서 시작하여 끝까지 탐색하는 알고리즘 이지만 이진검색은 중간값부터 탐색을 합니다. 선형검색은 링크드리스트에서 자주 쓰이는 반면에 이진검색은 트리구조에서 자주 쓰이는 형식입니다.

이진검색은 데이터를 계속 반으로 나누면서 연산하기 때문에 처리속도가 매우 빠르다는 장점을 가지고 있습니다. 그러나 이진검색을 하기 위해서 데이터의 집합이 반드시 정렬(Sort)되어야 한다는 단점이 있습니다. 이렇게 이야기하면 얼마나 빠른지 감이 잘 안올텐데, 예를들어 70억명의 사람중 한사람을 찾으려고 한다면, 선형검색은 Worst Case의 경우 70억번, 대략적으로 생각해도 35억번은 비교연산을 해야 하는데, 이진 검색은 최대 33번의 비교만으로도 원하는 데이터를 찾을 수 있습니다.


선형검색과 마찬가지로 10개의 방에 0부터 9까지의 데이터가 담겨 있는 배열입니다. 이야기 했다시피 이진검색의 조건은 데이터가 정렬이 되어있어야 된다는 점이기 때문에, 정렬을 진행했다 가정하고 0부터 9까지 순차적으로 적었습니다. 9라는 데이터를 찾기위해 어떤 연산과정을 거치는지 한번 알아보도록 하겠습니다.

일단 선택된 중간값인 5와 타겟인 9의 값을 비교합니다. 9가 더 큰 숫자이기 때문에 오른쪽으로 진행됩니다. 5보다 작은수인 0부터 4까지는 비교연산을 할 필요가 없습니다. 이제 5부터 9까지의 중간값을 다시 선택합니다.

함수 내부에서는 로컬변수로 first와 last, mid를 각각 선언 및 초기화 합니다. first는 arr의 첫번째 방을 뜻하는 0으로, last는 arr의 마지막 방인 size - 1 (예를들어 배열의 크기가 10이면 방의 번호는 0부터 9까지 이므로 9가 되겠음), mid는 담을 값이 아직 없으므로 선언만 하고 초기화는 하지 않았습니다.

이제 first가 last가 될때까지 반복문을 실행합니다. 첫줄에 mid를 중간값으로 정의합니다. 이때 arr의 mid번째 방에 있는 데이터가 target과 일치한다면 mid를 리턴하면 종료합니다. 그렇지 않으면 target이 arr[mid]보다 클때와 작을때로 나누어서 비교를 하며, 값을 찾을때까지 범위를 줄여 나갑니다. 선형검색과 마찬가지로 데이터를 찾지 못하면 -1을 리턴하면 되겠습니다.

바이너리의 단점

Binary Log(binlog) 사용하기

binlog(쉽게 빈로그라 읽고 쓰자..)는 서버내에서 발생되는 모든 변경내역이 기록되는 파일이다.

PIT(시점복구)와 리플리케이션에 필수 요소이며 이 2가지 기능을 쓰지 않는다면 굳이 활성화 할 필요는 없다.

빈로그를 쓰는것 자체가 미세하지만 IO 부담을 가중시키기 때문이다.

빈로그를 활성화 할 수 있는 방법은 2가지다.

1. 서버 구동시에 --log-bin 옵션을 준다.

[[email protected]]# mysqld_safe --log-bin=systemv-bin-log &

--log-bin=VALUE 과 같은 형식으로 사용할수 있는데 VALUE에는 파일경로/파일명을 쓸수 있다.

VALUE값을 생략하면 data_dir 디렉토리에 hostname -bin 이라는 이름으로 생성된다.

(라고 모든 MySQL 공식 도큐먼테이션에서 밝히지만 5.5버전 이후에서 테스트 했을시 mysqld-bin 으로 생성되더라.

hostname 변경시 발생될 수 있는 변수 상황을 피하기 위함이라 생각한다.)

2. my.cnf 에 작성

my.cnf의 [mysqld] 섹션에 log_bin 옵션을 설정해준다.

log_bin=VALUE 형식으로 작성할수 있으며 VALUE에 대한 내용은 위에 구동시 내용과 동일하다.

어떤방식으로 활성화를 하든 생성되는 빈로그에는 시퀀셜한 넘버링이 자동으로 붙게된다. (mysqld-bin.000001)

빈로그 활성화 여부는 show 로 확인 가능하다.

mysql> show variables like 'log_bin';

1 row in set (0.00 sec)

show 로 확인되는 log_bin 값과 my.cnf 에 작성하는 log_bin 은 전혀 다르므로 헷갈리면 안된다.

show 의 log_bin 은 현재 빈로그의 활성화 유무만을 보여주는 변수값일 뿐이고

my.cnf 에 작성되는 log_bin 은 빈로그를 활성화 시키면서 경로까지 지정해주는 설정 옵션이다.

어떤 방식으로 활성화 해도 결과는 동일하지만 서버 구동시마다 잊지 않고 옵션을 넣어 주는게 힘들기 때문에

my.cnf 설정법이 주로 쓰인다.

빈로그 사용시 기본적으로 같이 쓰는 옵션이 몇개 더 있으니 넘어가듯 살펴보자.

- log-bin=bin-log

data_dir에 bin-log.000001 같은 형식으로 빈로그를 남기고.

- binlog_format=row

빈로그 데이터 포맷을 row 형식으로 지정하고.

- binlog-do-db=test-db

test-db 라는 DB에 대한 내용을 남기는데.

- max_binlog_size=256M

빈로그 파일이 256M가 넘어가면 bin-log.000002 로 자동 로테이트 하면서 .

- expire_logs_days=4

4일 이상 오래된 빈로그는 삭제하도록 한다.

MySQL 5.7.6 까지는 기본값이 statement 이고 그 이후 버전부터는 row 가 기본값이다. (예외적으로 클러스터는 mix)

빈로그 포맷은 동적변수이므로 DB가 가동중일때도 set 으로 변경할 수 있지만 리플리케이션일 경우에는

마스터/슬레이브를 내리고 변경하는게 좋다.

빈로그 포맷 형식에는 3가지가 있는데 상황에 맞게 적절하게 써야한다.

쿼리문으로 기록되기 때문에 용량을 적게 차지하고 버전특성을 타지 않는다.

하지만 복구시 일관된 데이터에 대한 보장이 적으며(sysdate()와 now()의 다른 결과등..)

쿼리기반이기 때문에 복구(동기화)시 굉장히 느려질 수 있다.

statement 형식과 반대 개념으로 쿼리문이 아닌 변경된 데이터 기반으로 기록된다.

장단점 역시 statement 과 정반대로 용량이 커지고 복구시 일관성을 보장받을수 있으며 빠른 복구(동기화)가 가능하다.

statement 와 row 방식의 장점을 취합한 형태로 기본적으로 statement 방식을 취하나 필요에 따라(일관성 보장등..)

데이터 일관성을 위해 mixed 나 row 방식을 선택하는게 백번 맘 편하다.

isolation level이 read-committed 일 경우 현재 트랜젝션이 종료되지 않았더라도 다른 트랜젝션에서 동일한 데이터의

commit 이 일어나면 현재 트랜젝션에서도 변경된 값이 보이게 된다. 이런 환경에서 statement 방식을 사용하게 되면

트랜젝션 단위로 순서대로 로깅하기 때문에 복구나 슬레이브동기화시 원하는 바와 달리 다른 결과가 나타날수 있다.

그렇기 때문에 read-committed 에서는 row 방식이나 이런 상황에서 자동으로 row 형태로 변경해주는 mixed 방식을

PIT(Point In Time)복구

보통 사용량이 낮은 새벽시간에 디비백업을 하는데 그 이후 장애가 발생했다면 백업과 장애발생 시점 사이의

데이터를 잃을수 밖에 없다. 그래서 백업된 데이터를 활용해 1차로 복구하고 빈로그를 통해 PIT 복구를 추가로

xtrabackup 을 이용해 백업된 데이터를 먼저 복구하고

innobackupex --apply-log /backup_data

innobackupex --copy-back /backup_data

백업된 데이터의 마지막 포지션이 빈로그와 겹치는 파일부터 포지션을 지정해 먼저 적용하고

mysqlbinlog --start-position=222222 mysqld-bin.000003 | mysql -u root -p

그 이후 부터 현재까지 생성된 빈로그도 전부 적용시키면 PIT 복구까지 완료된다.

mysqlbinlog mysqld-bin.000004 | mysql -u root -p

mysqlbinlog mysqld-bin.000005 | mysql -u root -p

mysqlbinlog mysqld-bin.000006 | mysql -u root -p

* start-position 대신 start-datetime 을 쓸수도 있으나 동일한 시간대에 실행된 원치않은 다른 쿼리나 이벤트가

재실행 될수도 있기 때문에 position을 쓰는걸 권장한다.

PIT복구등에 반드시 필요한 빈로그이지만 예전에는 로컬에만 저장되었기 때문에 디스크나 서버자체에 장애가 생기게 되면

복구하는데 어려움이 많았다. 하지만 MySQL5.6 이상, MariaDB 10.0 이상부터 빈로그를 원격에 저장할 수 있는 기능이

생겼다. 물론 이전에도 rsync나 scp, ftp 등을 이용해 원격에서도 백업할수 있었지만 지금 쓰고 있는 빈로그는 백업할

수가 없었다. 맥스사이즈를 넘어서거나 DB재가동 등으로 스위칭 바이너리의 단점 된 빈로그만 백업이 가능했기때문에 스위칭되는 간격동안의

데이터는 유실될수밖에 없다는 단점이 있었다.

새로 추가된 원격저장은 지원버전 이상의 mysqlbinlog(예전부터 빈로그를 관리하던 커맨드에 옵션만 추가됨)를 사용해

실행할 수 있다. 기존에 사용하던 실행파일을 그대로 쓰는건 좋은거 같다.

실행문을 보기 전에 기본적으로 필요한 옵션 몇가지만 정리해보자.

--read-from-remote-server

빈로그를 원격에서 저장하기 위한 핵심옵션으로 이 옵션이 사용되면 당연히 --host(DB주소), --user(접속계정),

--password(패스워드), --port(접속포트) 등의 옵션도 함께 사용해야한다.

이 옵션이 없으면 원격서버에서 빈로그를 텍스트 형태로 가져오게 된다. 일반적으로 실서버에 있는 빈로그 그대로를

백업하기 원하기 때문에 이 옵션을 반드시 쓰도록 한다.

빈로그가 존재하는 실서버의 디비가 내려가거나 커넥션이 끊어지지 않는한 용량이 커지건 파일이 바뀌건 죽지않고

계속 빈로그를 가져오게 된다.

--stop-never-slave-server-id

stop-never 옵션을 쓰게 되면 백업서버가 리플리케이션의 슬레이브처럼 동작하게 된다. 리플리케이션 내에서는

server_id가 중복될수 없기때문에 해당 옵션으로 server_id를 지정해주는 것이다. 물론 생략도 가능하며

생략하면 65535로 셋팅된다.

가져온 빈로그 파일명에 Prefix를 붙여준다.

--result-file=orig-binlog- 라면 orig-binlog-mysqld-bin.000001 처럼 저장된다.

원본과 동일하게 저장하고자 하면 잴끝에 "/" 를 포함해 디렉토리명까지만 지정해주면 된다.

이 옵션을 쓰면 지정된 빈로그 뿐만 아니라 그 이후에 생성된 모든 빈로그를 가지고 온다. --stop-never 옵션을 쓰면

자동으로 활성화되는 옵션이므로 굳이 지정해 쓸 일은 없다.

위 옵션들을 참고해 실행문을 만들어 보면 아래와 같다.

mysqlbinlog --read-from-remote-server --raw --stop-never --host=192.168.0.100 \

--user=root --password=pwpw mysqld-bin.000001

원격 빈로그백업의 단점이 원서버가 재시작되거나 다른이유로 커넥션이 한번 끊기면 자동 재접속을 하지 않는다는 것이다.

게다가 실행문 마지막에 백업할 빈로그 파일명을 하나 지정해줘야하는데 그때마다 파일명을 확인하는게 굉장히 번거롭다.

그래서 스크립트로 만들어 쓰는게 좋다.

password='dbpassword'

mkdir -p $backup_dir

last_file=`ls -1 $backup_dir | grep -v orig | tail -n 1`

if [ -z "$last_file" ] ; then

last_file=`echo "show binary logs" | mysql -Ns -u $user -p$password -h $mysql_server | awk ''`

file_size=`stat -c%s $backup_dir/$last_file`

if [ "$file_size" -gt "0" ] ; then

mv $backup_dir/$last_file $backup_dir/$last_file.orig_$now

$mysqlbinlog --raw --read-from-remote-server --stop-never --host=$mysql_server --user=$user --password=$password --result-file=$backup_dir/ $last_file

Untitled

프로필사진

CHML

« 2022/07 »
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
    (1) (1) (1) (1)

Untitled

[파일구조] - 레코드 검색 본문

[파일구조] - 레코드 검색

검색은 파일에서 가장 바이너리의 단점 빈번하게 발생하는 연산이다. 따라서, 파일구조를 설계할 때는 파일 검색 연산을 신중히 구현해야 한다. 파일을 검색하기 위한 방법에는 크게 순차 탐색(sequential search)과 이진 탐색(binary search)이 있다. 순차 탐색의 시간 복잡도는 $O(n)$이며, 이진 탐색 트리에서 이진 탐색을 수행할 때의 시간 복잡도는 $log_2(n)$이다.

일반적으로 순차 탐색보다 이진 탐색의 효율이 더 좋지만 이진 탐색은 데이터가 정렬되어 있어야 한다는 제약 조건이 존재한다. 또한, 가변 길이 레코드 파일에서 이진 탐색을 수행하기 위해서는 파일의 중간 지점을 찾는 방법에 대한 구현도 필요하다.

이진 탐색을 이용하기 위해서는 반드시 데이터를 정렬해야한다. 파일의 경우에는 일반적으로 메모리에서 다루어지는 자료보다 데이터의 양이 많기 때문에 효율적인 정렬 알고리즘이 매우 중요하다.

파일은 데이터의 양이 매우 많기 때문에 메모리에 데이터를 모두 적재할 수 없는 경우가 존재한다. 이러한 경우에는 external sorting을 수행해야 하는데, external sorting은 전체 데이터 중에서 일부만 메모리에 적재하여 정렬하고 정렬이 끝나면 디스크에 접근하여 다시 데이터를 메모리에 적재하여 정렬을 수행하기 때문에 메모리에서 정렬을 수행하는 internal sorting에 비해 속도가 매우 떨어진다. 파일 정렬 시, 정렬을 위해 필요한 데이터의 양을 최소화하여 internal sorting을 수행하는 방법으로 key sorting이 있다.

Key sorting의 기본 개념은 데이터 파일 자체를 정렬하는 것이 아니라, 데이터 파일에 대해 키-상대 레코드 번호(RRN)으로 구성된 KEYNODE array를 정렬하는 것이다. 일반적으로 데이터 파일에 대한 KEYNODE array는 모든 데이터가 충분히 메모리에 적재될 수 있기 때문에 external sorting 대신 internal sorting을 이용할 수 있다. Key sorting의 구현은 아래와 같다.

위의 코드에서 알 수 있듯이 key sorting을 위해서는 입력 파일을 두 번 읽어야 한다. 하나의 입력 파일을 읽기 위해 디스크에 두 번 접근하는 것은 바람직하지 않다. 또한, 정렬된 KENODE를 참조하여 파일을 정렬된 형태로 다시 쓸 때 입력 파일을 임의 접근(random access) 하기 때문에 정렬 알고리즘의 성능이 떨어지는 단점이 있다.

Key sorting의 이러한 단점을 극복하기 위한 방법으로는 인덱스 파일(index file)을 이용하는 방법이 있다. 인덱스 파일을 이용하여 key sorting의 단점을 극복하는 방법은 매우 단순하다. 바로 인덱스 파일만 정렬하고 원래 데이터 파일은 정렬되지 않은 그대로 놔두는 것이다.

바이너리의 단점

다음 사이트에는 앞서 대충 살펴본 직렬화에 대한 좀 더 자세한 설명이 나온다.

위 사이트의 내용 중 Serialization을 이해하는데 도움이 될만한 내용만 추렸다.

직렬화의 장단점

  • DOM을 이용하지 않고 XML 문서를 수정할 수 있다.
  • 어떤 어플리케이션으로부터 다른 어플리케이션으로 객체를 전달할 수 있다.
  • 어떤 도메인으로부터 다른 도메인으로 객체를 전달할 수 있다.
  • 객체를 XML 문자열의 형태로 방화벽을 거쳐(통과해) 전달할 수 있다(방화벽에 제약을 받지 않고 객체를 전달할 수 있다).

반면 직렬화의 주요한 단점은

  • 직렬화와 역직렬화 과정에서 자원(CPU와 IO 장치)의 소모가 많다.
  • 네트워크를 통해 객체를 전달할 경우 지연 문제가 발생할 수 있다(직렬화 과정은 상당히 느리다).
  • XML로 직렬화하는 것은 안전하지 않으며(XML 문서의 형태로 저장할 경우 다른 프로그램이나 사용자가 실수나 고의로 문서의 내용을 변경할 수도 있다.), 많은 저장 공간을 차지하고, public 클래스에 대해서만 직렬화가 이루어질 수 있으므로 private 클래스 또는 internal 클래스는 직렬화할 수 없다(프로그래머로 하여금 직렬화를 지원하는 클래스를 바이너리의 단점 만들기 위해 불필요한 public 클래스를 양산할 수도 있다).

SerializableAttribute 특성

Part II에서 직렬화 가능한 클래스를 만들기 위해서는 클래스에 SerializableAttribute 특성을 적용해야 한다고 했다. 이 특성이 적용된 클래스는 직렬화가 가능하다. 하지만, 그 클래스의 멤버 중 NonSerializableAttribute 특성이 적용된 멤버를 제외한 나머지 모든 멤버가 직렬화가 가능해야 한다. NonSerializableAttribute 특성이 적용된 멤버는 직렬화할 때 무시된다. SerializableAttrbute 특성이 적용된 클래스의 private 멤버와 public 멤버는 기본적으로 직렬화된다.

[Serializable]
public class Employee
public int empCode;
public string empName;
>

직렬화의 유형

직렬화는 다음과 같은 유형으로 나뉜다.

  • Binary Seralization
  • SOAP Serialization
  • XML Serialization
  • Custom Serialization

Binary Serialization

Binary Serialization은 출력 스트림에 데이터를 기록하는 메커니즘으로 이 출력 스트림은 객체를 자동으로 재구성하는데 사용할 수 있다. binary라는 용어가 저장매체에 저장되어 있는 객체와 동일한 복사본을 생성하는데 필요한 필수적인 정보라는 뜻을 가지고 있다. binary serialization과 XML serialization간의 주목할만한 차이는 binary serialization이 인스턴스의 identity(instance identity)를 가지고 있는 반면에 XML serialization은 그렇지 않다는 점이다. 다시 말하자면, binary serialization은 객체의 모든 상태값을 저장하고 있는 반면, XML serialization은 객체가 가지고 있는 데이터의 일부분만을 저장하고 있다는 것이다. binary serialization은 동일한 객체를 여러 곳에서 참조하는 그래프(graphs-object graphs; 역자주: object graphs의 정확한 의미를 모르겠다. 다만, 여기서는 순환참조와 같이 복잡한 참조 관계를 의미한다고 보면 이해하는데 큰 무리가 없을 것 같다)를 다룰 수 있다; XML serialization은 각 참조를 어떤 유니크한 객체에 대한 참조로 바꾼다.(?? XML serialization will turn each reference into a reference to a unique object).

관리되는 환경(managed environment)에서의 Binary 바이너리의 단점 Serialization의 주요한 장점은 객체를 직렬화한 그대로 역직렬화할 수 있다는 점이다(객체가 원래 가지고 있던 값들을 그대로 복원할 수 있다). 더구나, 속도가 빠른만큼 더 나은 성능을 보일 뿐만 아니라 복합적인 객체를 지원하고, 읽기전용(readonly) 속성과 순환참조(circular references)도 지원하는 등 더 강력하다. 하지만, 다른 플랫폼으로 이식하는 것이 쉽지 않다는 단점이 있다.

다음 코드는 Binary Serialization의 예이다.

public void BinarySerialize(string filename, Employee emp)
FileStream fileStreamObject;

fileStreamObject = new FileStream(filename, FileMode.Create);
BinaryFormatter binaryFormatter 바이너리의 단점 바이너리의 단점 = new BinaryFormatter();
binaryFormatter.Serialize(fileStreamObject, emp);
>


public static object BinaryDeserialize(string filename)
FileStream fileStreamObject;

try
fileStreamObject = new FileStream(filename, FileMode.Open);
BinaryFormatter binaryFormatter = new BinaryFormatter();
return (binaryFormatter.Deserialize(fileStreamObject));
>

SOAP Serialization

SOAP 프로토콜은 서로 다른(heterogeneous) 아키텍쳐를 가지는 어플리케이션 간의 통신에 이상적이다. .NET에서 SOAP Serialization을 이용하려면 어플리케이션이 System.Runtime.Serialization.Formatters.Soap을 참조해야 한다. SOAP Serializaion의 기본적인 장점은

이식성이다. SoapFormatter는 객체를 직렬화해서 SOAP 메시지로 바꾸거나, SOAP 메시지로를 파싱해서 직렬화된 객체를 추출해낸다.

다음은 SOAP Serialization의 예제이다.

public void SOAPSerialize(string filename, Employee employeeObject)
FileStream fileStreamObject = new FileStream(filename, FileMode.Create);
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.Serialize(fileStreamObject, employeeObject);
fileStreamObject.Close();
>

public void SOAPSerialize(string filename, Employee employeeObject)
FileStream fileStreamObject = new FileStream(filename, FileMode.Create);
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.Serialize(fileStreamObject, employeeObject);
fileStreamObject.Close();
>

XML Serialization

MSDN에 따르면, "XML serialization은 어떤 오브젝트의 공용 필드와 속성 또는 메서드의 매개변수와 반환값을 명시적인 XML 스키마 정의 언어 문서(XSD; XML Schema definition language)에 맞는 XML 스트림으로 변환(직렬화)한다. XML serialization의 결과로 전송이나 저장을 위해 순차적인 포맷(여기서는 XML)으로 변환된, 공용 속성과 필드들을 가진 강력한 형식의 클래스(strongly typed classes)바이너리의 단점 가 만들어진다. XML은 개방형 표준이기 때문에, 그 XML 스트림은 어떤 플랫폼이 어떤 프로그램에서도 처리될 수 있다."라고 되어 있다.

.NET에서 XML Serialization을 구현하는 것은 아주 쉽다. serializatin과 de-serialization을 위해 필요한 기본 클래스는 XmlSerializer이다. Web 서비스는 통신에 SOAP 프로토콜을 사용하는데, 이 XmlSerializer 클래스를 이용하여 반환형과 매개변수를 모두 직렬화한다. XML Serialization은 Binary Serialization과 비교하면 상당히 느리다.

[XmlAttribute("empName")] //클래스의 멤버에 대해 개별적으로 XmlAttribute 특징을 적용할 수 있다.
public string EmpName
get
return empName;
>

public void XMLSerialize(Employee emp, String filename)
XmlSerializer serializer = null;
FileStream stream = null;

try
serializer = new XmlSerializer(typeof(Employee));
stream = new FileStream(filename, FileMode.Create, FileAccess.Write);
serializer.Serialize(stream, emp); 바이너리의 단점
>


public static Employee XMLDeserialize(String filename)
XmlSerializer serializer = null;
FileStream stream = null;
Employee emp = new Employee();

try
serializer = new XmlSerializer(typeof(Employee));
stream = new FileStream(filename, FileMode.Open);
emp = (Employee)serializer.Deserialize(stream);
>

finally
if (stream != null)
stream.Close();
>
return emp;
>

Formatters 활용하기

formatter는 객체의 직렬화 포맷을 결정한다. 다시 말하자면, 객체를 스트림으로 직렬화하거나 스트림을 역직렬화하여 객체를 재생성해내는 것을 제어한다. formatter는 객체를 네트워크를 통해 전송하기 전에 암호화하고 적합한 포맷으로 직렬화하는데 사용된다. formatter는 IFormatter라는 인터페이스를 노출시킨다(구현하고 있다). IFormatter의 중요한 메서드로는 실제 직렬화와 역직렬화를 수행하는 Serialize와 De-Seialize 메서드가 있다. .NET은 BinaryFormatter와 SoapFormatter라는 2개의 formatter 클래스를 제공한다. 이 두 클래스 모두 IFormatter 인터페이스를 바이너리의 단점 구현하고 있다.

Binary Formatter

Binary formatter는 이진(binary) 인코딩을 이용한 직렬화하는 방법을 제공한다. BinaryFormater 클래스는 .NET의 Remoting기술에서 흔히 사용되는 이진 직렬화(binary serialziation)를 책임지고 있다. 이 클래스는 데이터가 방화벽을 통해 전달되어야 할 경우에는 적합하지 않다.

SOAP Formatter

SOAP formatter는 SOAP 프로토콜을 이용하는 객체의 직렬화에 사용된다. SOAP formatter는 Soap envelop을 생성는데 사용되고 결과를 생성하기 위해 객체 그래프 (object graph; 인스턴스들 간의 참조 관계)이용한다(?? It is used to create a Soap envelop and it uses an object graph to generate the result). SOAP formatter는 객체를 직렬화해서 SOAP 메시지로 바꾸거나 SOAP 메시지를 파싱해 직렬화된 객체를 추출해내는 역할을 한다. SOAP formatter는 .NET에서 WebServices에 널리 사용된다.

기억해야 할 것

  • 어떤 형(클래스)에 SerializableAttribute 특성을 적용하면, 그 클래스의 모든 인스턴스 필드(private, public, protected, 기타)가 자동적으로 직렬화된다.
  • XMLSerializer는 ISerializable 인터페이스를 사용하지 않으며, 대신 IXmlSerializable 인터페이스를 사용한다. XmlSerializer 클래스는 BinaryFormatter클래스가 ISerializable 인터페이스를 이용하여 private 필드도 직렬화하는 것에 반하여, 해당 클래스의 public 속성들만 직렬화할 수 있다.
  • SerializableAttribute 특성은 어떤 클래스를 직렬화할 수 있게 만들기 위해서는 반드시 필요하다. 그 클래스가 ISerializable 인터페이스를 구현하든 하지 않든 상관없이.
  • 어떤 클래스를 직렬화할 때, 다른 클래스를 참조하는 객체가 그 클래스에 포함되어 있어도 그 객체가 직렬화가 가능하다고 표시되어 있으면 같이 직렬화된다. public, private 또는 protected 멤버를 포함하여 모든 멤버들이 직렬화된다.
  • 심지어 binary serialization을 이용하면 순환참조(circular references)도 직렬화할 수 있다.
  • 읽기전용 속성은 그 속성이 컬렉션 클래스의 객체인 경우를 제외하고는 직렬화되지 않는다는 점을 주의해야 한다(Note that read only properties are not serialized except the collection class objects). binary serialization을 이용하면 읽기전용 속성도 직렬화 할 수 있다.
  • XmlSerializer를 이용하여 어떤 클래스를 직렬화할 때, 그 클래스의 특정한 속성을 직렬화하지 않을 필요가 있다면, 그 속성에 XmlIgnoreAttribute 특성을 적용하여야만 한다. SoapFormatter를 사용하는 경우에는 SoapIgnoreAttribute 특성을 사용하여 직렬화하지 않도록 처리할 수 있다.
  • XmlSerializer를 Web Service에 이용할 경우에 XmlSerializer는 각 타입(형)에 대해 최초로 호출될 때마다 최적화된 in-memory 어셈블리를 생성한다. 이 작업은 많은 시간이 걸린다. 이 문제를 해결하기 위해서 sgen.exe 툴을 이용하여 그 serialization 어셈블리를 미리 생성해 놓는 방법을 사용할 수 있다.

※ 객체 그래프(Object Graph)란?

Object Graph는 Wikipedia에 다음과 같이 설명되어 있다.

An Object Graph is a view of an object system at a particular point in time. Whereas a normal data model details the relationships between objects, the object graph relates the instances.

객체 그래프는 어떤 특정 시점의 객체 시스템에 대한 뷰이다. 보통 Data Model이 객체들 간의 관계를 상세히 설명하는데 비해, 객체 그래프는 인스턴스들 간의 관계를 설명한다.

바이너리의 단점

오늘은 정형 데이터를 다룰 때면 흔히 마주치는 문제인 변수 인코딩(Feature Encoding) 에 대해서 알아보겠습니다. 모든 인코딩 방법을 알아야 하는 건 아니기 때문에, 쉽게 쓸 수 있는 인코딩 방법 몇개만 소개하도록 하겠습니다.

가정.

범주형 변수는 데이터가 대부분 String으로 되어 있기 때문에 모델들은 이해하지 못합니다. 따라서 숫자로 인코딩을 해주어야 하는데, 인코딩 방식에 따라 알고리즘이 받아들이는 기준이 다릅니다. 인간은 정보를 보고 단번에 알아차릴 수 있지만, 컴퓨터는 일일이 다 알려줘야만 알아 먹죠. 정보 손실을 최소화하기 위해서는 데이터에 맞는 인코딩 방법을 써야 합니다. 그래야만 학습도 잘 되고, 설명력도 좋아집니다.

Nominal & Ordinal.

범주형 변수는 크게 Nominal(순서가 없는) 형식과 Ordinal(순서가 있는) 형식으로 나뉩니다. 아래 그림에서도 볼 수 있듯이 소와 개, 고양이는 특정한 개체가 우위에 있지 않습니다. 즉 순서가 없는 Nominal 데이터입니다. 반면에 매우 좋음, 좋음, 나쁨 등과 같은 범주는 우위가 있습니다. 따라서 Ordinal 데이터라고 할 수 있습니다.

Decision Tree가 숫자로 된 변수를 받아들였을 때.

트리 계열의 알고리즘들은 숫자로 된 변수를 받아들였을 때, 그것을 연속형 변수로 인식 합니다. 예를 들어 [소, 개, 고양이]를 Label Encoding을 사용해서 [1, 2, 3] 으로 학습시킨다면, 트리 계열의 알고리즘들은 소보다 개가 크고, 개보다 고양이가 큰 개체라고 인식하게 됩니다. 이것은 트리 계열의 특징입니다. 따라서 트리 계열의 알고리즘에서 순서가 없는 Nominal 데이터를 라벨 인코딩(Label Encoding)을 통해 인코딩 하고 학습시킨다면, 성능이 잘 나오더라도(실험적으로는 잘 나올 때가 많습니다.) 제대로 정보를 학습하고 있다고는 볼 수 없습니다. 따라서 해당 범주형 변수가 Ordinal인지, Nominal인지 잘 살펴야 합니다.

Decision Tree가 원핫 인코딩(One-hot Encoding)을 받아들였을 때.

트리 계열의 알고리즘은 가지를 분기할 때마다 하나의 변수를 선택하여 정보 이득을 계산합니다. 반대로 선형 모델은 하나의 회귀 식에서 모든 변수의 영향을 동시에 계산하여 기울기를 줄여나갑니다. 원핫 인코딩은 하나의 변수를 여러개로 찢는 방식 입니다. 변수 안에 카테고리가 10개면 1개의 변수가 10개의 변수로 늘어나는 것이죠. 그렇게 되면 하나의 변수에 대한 정보 이득이 10개의 변수로 찢어지기 때문에 해당 변수가 높은 정보 이득을 가질 가능성은 카테고리의 개수가 증가하는 만큼 줄어듭니다. 결국 알고리즘에 의해 잘 선택되지 않고 중요도도 낮아지게 되죠.

참여했던 경진대회 중, 출력한 변수 중요도 그래프

위 그래프는 제가 참여했던 한 경진대회에서 썼던 모델의 변수 중요도를 뽑은 그래프입니다. 상위 15개의 변수가 전부 연속형(수치형) 변수입니다. 범주형 변수 중에서 가장 높은 중요도를 냈던 변수는 16번째에 있는 'car_N' 입니다. 그 이유는 모든 범주형 변수들이 원핫 인코딩으로 변환이 되었고, 그만큼 정보 이득이 분산되어 중요한 변수로 작용하지 못했기 때문입니다. (해당 데이터는 변수가 무려 100개가 넘었습니다.)

선형 모델도 물론 변수가 많아질수록 과대적합 될 가능성이 높아진다는 단점이 있지만 이 문제는 규제(Regularization)를 통해 완화할 수 있습니다. 그러나 트리 계열의 문제는 과대적합을 넘어서 해당 변수가 제외되는 문제를 가져옵니다 . 그렇기 때문에 트리 계열 알고리즘에서는 원핫 인코딩을 사용하는 것이 그다지 좋지 않게 받아들여지고 있습니다.

Binary Encoding, Ordinal Encoding, Hashing Encoding

그래서 준비한 인코딩 방법은 바로 이 3가지 입니다.

Binary Encoding이진법을 활용해 변수를 나누는 것 으로 원핫 인코딩과 유사하지만 늘어나는 변수의 개수가 훨씬 적습니다.

Ordinal Encoding 은 Ordinal 데이터에 쓸 수 있는 인코딩 방법으로, 순서를 지정해주는 것 입니다.

마지막으로 Hashing Encoding 은 변수를 더 높은 차원의 정수 공간으로 맵핑 시킵니다. 또한 관찰된 카테고리의 범주를 유지하지 않고 새롭게 맵핑하게 됩니다.(무슨 말인지 모르겠습니다.) 이또한 Binary Encoding과 비슷하게 바이너리의 단점 원핫 인코딩보다 낮은 차원으로 인코딩할 수 있습니다.

바이너리 인코딩(Binary Encoding)

바이너리 인코딩의 방식은 아주 간단합니다. 카테고리에 대한 오더를 부여한 다음(바이너리로 바꾸기 때문에 오더는 의미가 없습니다.) 바이너리로 바꾸고, 이를 다시 원핫 인코딩의 방식으로 찢는 것 입니다. 아래 그림의 순서로 작업이 이루어집니다.

바이너리 인코딩의 장점은 원핫 인코딩의 방식을 취하면서 늘어나는 변수의 개수를 줄일 수 있다는 점 입니다. 보통 카테고리가 15개가 넘어가면 바이너리 인코딩이 훨씬 효율적이라고 합니다. 그러나 제 생각에는 카테고리가 4개만 넘어가더라도 원핫 인코딩보다 바이너리 인코딩의 변수가 작아지기 때문에 4개 이상만 되어도 바이너리 인코딩을 사용하는 것이 나을 수 있겠다고 판단하고 있습니다.

오디널 인코딩(Ordinal Encoding)

오디널 인코딩은 해당 범주형 변수가 Ordinal 데이터일 때 가능한 인코딩 방식 입니다. 라벨 인코딩과는 다르게 순서 정보를 담을 수 있다는 것이 장점 입니다.

해싱 인코딩(Hashing Encoding)

해싱 인코딩은 하나의 변수만을 인코딩하지 않습니다. 결과물만 살펴보면 PCA와 매우 비슷하게 느껴질 수 있습니다. 다수의 변수를 한 번에 다차원 공간에 맵핑시켜서 인코딩하는 방식 입니다. 그렇기 때문에 단점이 명확히 존재합니다. 바로 해석의 용이성이 떨어진다 는 점입니다.

그 외에도 많은 인코딩 방식이 있습니다.

여기서 다루지 않은 원핫 인코딩(One Hot Encoding) , 라벨 인코딩(Label Encoding) 도 있고, 헬멋 인코딩(Helmert Encoding) 이나 민 인코딩(Mean Encoding) , 백워드 디퍼런스 이코딩(Backward Difference Encoding) 등등. 찾아보면 끝도 없는 곳이 바로 인코딩인 것 같습니다.

아래 글들을 매우 많이 참고했습니다. 다른 인코딩 방식 또한 서술되어 있으니 참고하세요! :)

All about Categorical Variable Encoding

Most of the Machine learning algorithms can not handle categorical variables unless they are converted to numerical values and many…

Category Encoders — Category Encoders 2.2.2 documentation

Category Encoders A set of scikit-learn-style transformers for encoding categorical variables into numeric with different techniques. While ordinal, one-hot, and hashing encoders have similar equivalents in the existing scikit-learn version, the transforme

Encode Smarter: How to Easily Integrate Categorical Encoding into Your Machine Learning Pipeline

Automated feature engineering solves one of the biggest problems in applied machine learning by streamlining a critical, yet manually intensive step in the ML pipeline. However, even after feature engineering, handling non-numeric data for use by machine l


0 개 댓글

답장을 남겨주세요