C기반 언어를 사용하는 이유중 개인적인 생각으로 가장 중요한 부분이 포인터라고 생각한다 . 

포인터를 이용하여 메모리 주소값에 직접 접근하여 프로그램의 효율을 상승시킬 수 있다. 

 

포인터란? 

  • 포인터는 값 자체가 아니라 값의 주소를 저장하는 변수이다. 
  • 주소연산자(&)를 변수 앞에 붙이면 그 변수의 주소를 알아낼 수 있다.
  • cout을 사용할 때 주소값은 컴파일러에 따라 16진수 또는 10진수로 표현된다 
  • * 기호는 곱하기를 뜻하기도 하지만 포인터에서는 해당 메모리 주소에 들어있는 값을 의미한다 
    *메모리주소변수 = 100 
  • & 기호는 and를 뜻하기도 하지만 해당 변수의 메모리 주소를 의미한다  
    &값변수 = 00000079CA8FF534
int main() {
	using namespace std;

	int donuts = 8;
	double cups = 4.5; 

	// 변수의 주소를 알고싶다면 해당 변수앞에 &를 붙여라 
	cout << "donuts : " << donuts; 
	cout << " donuts의 주소 : " << &donuts << endl; // 000000B526CFF7C4
	cout << " donuts의 사이즈 : " << sizeof(donuts) << endl; // 4 Byte

	cout << "cups: " << cups;
	cout << " cups의 주소 : " << &cups << endl; // 000000B526CFF7E8
	cout << " cups의 사이즈 : " << sizeof(cups) << endl; // 8 Byte
		
	return 0;
}
int main() {
	using namespace std;
	int updates = 6;
	int* p_updates; // int형을 지시하는 포인터 선언

	p_updates = &updates; // int형의 주소를 포인터에 대입 

	cout << "값 : updates = " << updates; // 해당 변수에 대입된 값 
	cout << ", p_updates = " << p_updates << endl; // 해당 주소의 값 ( 포인터 )

	cout << "주소: &updates = " << &updates; // 변수가 저장된 메모리의 주소 
	cout << ", *p_updates = " << *p_updates << endl; // 메모리 주소에 저장된 값 

	// 포인터를 사용하여 값 변경 
	*p_updates = *p_updates + 1; // 메모리주소에 저장된 값에 더하기 1 을 함 
	cout << "변경된 updates = " << updates << endl;  // updates 의 메모리에 접근하여 값을 + 1 했기 때문에 7을 출력 

	return 0;
}
  • int* a 에서 a는 int형을 가르키는 포인터이다  
  • 포인터 변수는 그냥 단순한 포인터가 아니라, 항상 어떤 데이터형을 지시하는 포인터라고 해야한다 
  • char형의 주소와 double형의 주소는 크기가 같다. 그렇기에 특정 주소의 크기나 값만 가지고는 그 주소에 저장되있는 변수의 크기나 종류를 알 수 없다 
int main() {
	using namespace std; 

	int* ptr; 
	ptr = (int*)0xB80000000; // 포인터에 직접 주소를 매핑하려면 이렇게 int형을 가르키는 주소라고 데이터타입 형변환이 필요하다 
}
int main() {
	using namespace std; 

    // ptr이라는 메모리주소 변수에 int형이 필요할 때 
    // 이렇게 초기화를 하면 시스템에서 알아서 int형에 맞는 사이즈의 메모리를 찾아서 ptr에 대입해준다 
	int* ptr1 = new int; // ptr1은 데이터객체를(data object)를 지시한다 
    // 00000153F2AF6590
	cout << ptr1 << endl; 

	int higgens; 
	int* ptr2 = &higgens; // ptr2에 이런식으로 주소값을 대입할 수 있다. 

    // 위의 두 방식이 다른 점은 ptr1의 경우 
    // 오직 ptr1을 통해서만 (변수가없기때문에) 해당 값에 접근할 수 있다 
    // 하지만 higgens를 이용한 경우 
    // 해당 값에 접근할 수 있는 방식이 higgens를 이용하는 법과 ptr2를 이용하는 방법 두가지가 있게된다

	return 0;

}
int main() {
	using namespace std; 

	int nights = 1001; 
	int* pt = new int; 
	*pt = 1001; 

	double* pd = new double; 
	*pd = 10000001.0;

	cout << "nights의 값 : " << nights;
	cout << " nights의 메모리 위치 : " << &nights << endl;;
	cout << "int형 값 : " << *pt << " : 메모리의 위치 : " << pt << endl; 

	cout << "double형의 값 : " << *pd;
	cout << "메모리 위치 : " << pd << endl;
	cout << "포인터 pd의 메모리 위치 : " << &pd << endl; 

	cout << "pt의 크기 : " << sizeof(pt);// 8을 나타낸다 - 데이터 객체의 사이즈이기 떄문
	cout << " *pt의 크기 : " << sizeof(*pt) << endl; // 4를 나타낸다 - int형의 사이즈이기 때문
	cout << "pd의 크기 : " << sizeof(pd); // 8을 나타낸다 - 데이터 객체의 사이즈이기 떄문
	cout << " *pd의 크기 : " << sizeof(*pd) << endl; // 8을 나타낸다 - double형의 사이즈이기 때문

	return 0;
}

new로 생성한 메모리를 해제하는 delete 

int* ps = new int; 
delete ps ; // 정상 
delete ps ; // 이미 해제되었기 때문에 비정상 에러

int jugs = 5; 
int * pi = &jugs; 
delete pi; // new로 생성한 데이터 객체가아니기 때문에 비정상 에러

C++ 에서 코드의 가독성과 효율을 높이기 위한 구조체와 공용체, 열거체에 대해서 정리한다. 

 

구조체란 ? (struct)

  • 사용자가 정의할 수 있는 데이터 타입 
  •  하나의 변수안에 여러개의 변수를 저장 할 수 있다 ( python의 dictionary와 비슷)
struct MyStruct{
    int type : 4; // :뒤에 있는 숫자는 해당 데이터 타입의 bit수를 제한한다 
    int null_space : 4; // 이렇게 아무 값을 넣지 않을 공간을 미리 만들 수도 있다
    char name[20] : 21; 
    unsigned int price : 4;
}

 

공용체란 ? (union)

  • 서로 다른 데이터형을 한 번에 한 가지만 보관할 수 있는 데이터 형식 
  • 여러개의 데이터형을 선언 했을 때 하나의 데이터형만 사용이 가능하다 
  • 차지하는 메모리 공간은 선언된 데이터 타입중에 가장 큰 데이터타입의 크기로 설정된다 
union MyUnion{
    int int_val;
    float float_val;
    double double_val;
    long long_val;
}

// ...... // 
MyUnion test1; 

test1.int_val = 3; // int_val에 값 저장 
test2.float_val = 3.3; // int_val에 값이 소실되고 float_val이 저장된다

열거체란 ? (enum : enumeration)

  • 기호 상수를 만들 수 있다
  • 상수에 매칭되는 정수는 상수의 순서대로 0부터 시작하여 1씩 증가한다    
enum spectruum { red, orange, yellow, green, blue, violet, indigo, ultraviolet};

cout << red << endl;  // 0 출력
enum spectruum { red=100, orange, yellow, green, blue, violet, indigo, ultraviolet};

cout << red << endl;  // 100 출력
cout << orange << endl;  // 101 출력 (상수는 이전 상수의 값 + 1 이 된다)

 

최근 이직을 생각하며 내가 무엇을 잘못하고 있는지, 이직을 하는게 맞는지, 이 회사가 맞는지, 책임을 져버리는게 아닌지 고민이 많았다. 그러던 중 친구에게 책을 추천받았다 제목을 보자마자 나에게 필요한 책일 까? 라는 생각이 들었다. 의구심이 들었던 이유 자체가 내가 이직에 대한 확신이 없는 상태에서 이런 이직을 권하는 책을 읽는게 맞을까 라는 생각이 들어서였다.

그러던 중 주말에도 일을 하고있는 나의 모습과 일에대한 생각이 다른 대표의 행동을 보고 이직을 준비해야겠다 라는 쪽에 마음이 더 기울었고 이 책을 읽게 되었다. 

읽고나니 정말 나에게 필요한 책이였다.

많은 생각을 가지게 되었고 나의 현재 위치에서 판단해야하는 것들에 대해서 생각하게 되는 계기가 되었다. 

 

먼저 책의 내용에 대해서 감명깊었던 부분을 정리를 하고 후에 책을 읽으며 들었던 나의 생각에 대해서 정리하고자 한다. 

 

감명깊었던 내용

  • 이직을 준비하기에 앞서 나의 현재 상태에 대해서 기술할 수 있어야한다. 내가 무엇을 하고싶은지 나의 능력은 무엇인지 
    • 내가 가진 전문성 ( 전문성이란 내가 능력있다 생각하는 것 뿐만이 아니라 능력이 없더라도 하고있는 일에대해서 작성해야한다) 
    • 내가 해본 경험 

나의 시장가치 측정법 : 이 상자의 크기가 자신의 시장가치를 뜻한다

위의 내용을 보고 나의 시장가치를 측정해보자 

  • 업계(일인당) 생산성 - 나의 생산성은 어떠한가? 
    1. 내가 속한 시장의 향후 '성장 가능성'이 있는가? 
    2. 내가 속한 시장의 '일인당 생산성'은 얼마나 되는가? 
    3. 향후 '내 시장가치'의 성장 가능성은 얼마나 되겠는가? 
    4. 애초에 업계를 잘못 골랐다면 시장가치를 높일 수 없다 
    5. 올라가는 에스컬레이터에 탄 시장에서 일하면 아무것도 하지 않아도 시장가치가 올라간다  
      • 인적자산
        1. 회사를 옮겨도 기꺼이 나를 도울 사람이 현재 회사에 몇명인가?, 그들에게 의사결정권이 있는가? 
        2. 일에 있어서 나를 기꺼이 도울 사람이 회사 밖에서는 몇명인가?, 그들에게 의사결정권이 있는가? 
        3.  나이기 때문에 일을 주는 사람과 회사의 담당자이기 때문에 일을 주는 사람을 구분해야한다. 회사의 담당자이기 때문에 일을 주는 사람밖에 없다면 자신의 시장가치가 위기에 처했다고 봐야한다 
      • 전문적 자산 
        1. 다른 회사에서도 가치 있을 만한 전문성 얼마나 보유하고 있는가? 
        2. 그 전문성의 유통기한은 언제까지인가? 
        3. 다른 회사에서도 통용될 만한 '흔치 않은 경험'을 얼마나 했는가? 
        4. 그 경험에 대한 가치는 얼마나 높은가? 
        5. 전문성이 있는 사람만이 남들이 할 수 없는 특별한 경험을 할 수 있다. 
      • 20대에는 전문성을 30대에는 경험을 쌓아라. 
        • 시장가치는 상대적인 개념이다. 업무에 희귀성을 확보하기 위해서는 20대에 전문성을 확보하고 30대에는 그 전문성을 가지고 남들이 할 수 없는 특별한 경험을 하여 자신의 시장가치를 올려라 
        • 상사가 정한 목표를 달성하려고 노력하는 것은 자신의 시장가치를 높이려는 목적과는 거리가 있다. 
        • 전문성을 무기로 높은 자리로 올라가려면 "재능"이 필요하다 하지만 "경험"은 어느 자리에 있는지(포지셔닝)의 영향을 크게 받는다. 평범한 사람일 수록 포지셔닝에 신경을 써서 경험을 쌓아야한다. 

일의 라이프 사이클 

  • 모든 일에는 유통기한이 있다
  • 틈새 산업에서 시작해서 -> 2스타산업 , 3일상산업을 거쳐 4. 사양산업으로 일이 소멸된다 
  • 틈새 산업에서 돈을 많이 벌 수 있다는것을 알면 일하는 사람이 점점 증가하게 되고 금세 일반화 되어 어느새 누구나 모방할 수 있는 수준으로 세분화되고 해내기 쉬운 형태로 체계가 갖춰진다.그 후 기업은 인건비를 줄이기 위하여 기계화를 추진하고 사양산업으로 진행되어 사람의 일거리가 줄어든다( 산업이 소멸된다는것은 아니다) 
  • 3. 일상산업부터 에스컬레이터가 내려가고 있다고 생각해야한다 
  • 시장가치가 없는 사람일수록 기존 회사에 남아 있으려고 애를 쓰기 때문에 3단계가 되고나서는 줄어드는 의자를 서로 빼앗으려고 사내에서도 격렬한 경쟁이 벌어진다 ( 사내 정치가 많아지고 뒷말이 많아진다)
  • 자신의 회사 뿐만이 아니라 경쟁사 또한 이익이 줄어든다면 이것은 시장이 축소된다는 신호이다 

일의 라이프 사이클

 

성장하는 시장을 찾는 방법 

  • 어떤 한 회사가 곧 문을 닫는다. 그 회사에 가보니 직원들이 스마트폰을 하며 놀고있다. 회사를 망하게 하는건 사장일까 직원일까? 
    - 직원이 열심히 일하지 않아서?
    - 사장이 운영을 잘 하지 못해서? 

  • 10년 전과 똑같은 서비스를 제공하는 회사는 피하라
    - 커리어 선회가 필요하다. 
    - 몰려오는 파도에 올라 탔다가 그 파도가 사라지기 전에 다음 파도로 갈아타는것. 이것이 평생 먹고살 수 있는 일을 하기 위한 최강의 방법론이다 

  • 스타산업에 이직을 하고 스타산업이 일상산업이 되려 할 때 틈새산업으로 이직해라 
  • 성장하고 있는 시장은 어떻게 찾을 수 있나? 
    1. 여러 벤처가 진입해 다 함께 성장 중인 서비스는 무엇인가? 
    2. 기존 업계의 비효율을 타파하는 이념을 내건 회사는 어디인가? 

 

회사를 고르는 기준

  • "자신을 성장시킬 수 있는 환경"의 중요성을 강조하는 것은 20대는 이해할 수 있어도 30대에게는 좋지 안다
  • 면접을 볼 때 면접자의 입장에서 회사에 물어보면 좋은 질문 
    1. 귀사는 어떤 인재를 필요로 하며 그에게 어떤 역할을 기대하는가? 
    2. 귀사에서 가장 큰 두각을 드러내며 높은 평가를 받는 직원은 누구인가? 두각을 드러내는 이유는 무엇인가? 
    3. 자신처럼 경력직으로 입사해 현재 가장 활약하는 이는 사내에서 어떤 부서를 거쳤고 어떤 업무를 담당했는가? 
  • 위의 질문에 대한 대답을 듣고 자신이 해당 회사에서 활약하는 모습을 상상할 수 있다면 합격이다( 갈만하다) 
  • 회사를 고르는 3가지 기준 
    1. 시장가치
    2. 업무 환경
    3. 활약 가능성 
  •  

좋은 벤처를 판단하는 세 가지 포인트 

  1. 경쟁상는 어느 회사인가? 그 경쟁사도 성장하고 있는가? 
  2. 현장 직원들은 우수한가? 경영진은 당연히 우수하겠지만 그들이 뽑은 다른 직원들도 우수한가? 
  3. 동종업계 타사로부터의 평판은 어떤가? 

좋은 헤드헌터의 특징 

  1. 면접 때 어떤 점에서 높은 평가를 받았는지, 입사 시 주의 사항을 알려준다( 의뢰인이 먼저 주의사항이 있느냐고 물어볼것) 
  2. 소개하는 회사의 특징만 전달하는 것이 아니라 고객의 커리어에 그 회사가 어떤 가치가 있을지 조언한다 
  3. 입사여부 결정 시한을 늘리거나 연봉 교섭을 할 때 원만하게 처리한다. 
  4. 다른 좋은 회사를 소개해 달라는 요청에 지속적으로 응답한다. 
  5. 사장, 임원, 인사 책임자와 관계가 두터워서 면접 등을 자유롭게 주선해 준다. 
  6. 해드헌터가 좋아하는 기업은 이직률이 높고 채용 기준이 낮은 회사이니 좋지 못한 회사일 가능성이 크다 
  7. 어떤 인재로든 잘 돌아가는 회사라면 개인의 시장가치 성장을 기대하기 어렵다 

경력으로 들어갈 회사와 신입으로 들어갈 회사 

  • 그 회사에 경력직을 귀하게 여기는 문화가 있나? 
  • 경력직 혹은 신입만 선호하는 회사는 경력직이 활약할 수 있는 범위가 상당히 제한적일 것이다 
     - "사외 이사를 제와한 임원 여덟 명 중 일곱 명이 000에 대졸 신입으로 입사" 
  • 이직해서 성공하고 싶으면 그 회사에 경력으로 입사한 직원이 활약하는 문화가 있는지 살펴보라 
  • 창립한지 10~ 15년 이상의 회사라면 경력직으로 입사해 임원이 된 직원이 얼마나 되는지 확인하라 
  • 자신의 직종이 회사의 장점과 일치하느냐? 
    1. 회사마다 "엔진"(핵심) 분야가 있다. 영업 위주로 돌아가는지, 개발 위주로 돌아가는지  개발자라면 개발이 핵심인 회사에 들어가야한다 
    2. 어떻게 핵심 분야를 알 수 있나? - 그 회사가 하고있는 서비스를 경험해봐라 
      - 배달의 민족은 개발이 주를 이뤘지만 현재는 프로모션, 광고가 주를 이루는 회사가 되었다 (할인이나 프로모션에 공을 들인다) = 마케팅이나 광고 관련 부서가 강하다 
    3. 직접 경험할 수 없을 때는 경영진과 핵심 직원들의 배경을 알아봐라 - 어떤 부서를 거쳐 출세했는지, 어떤 회사 출신이 많은지 

이직할 회사를 찾는 방법 

  1. 헤드헌터의 연락 - 가장 단가가 비쌈- 일반적으로 인재를 찾기 어려운 특수 직종이거나 중요한 인물을 뽑아야 한다면 헤드헌터나 헤드헌팅 업체를 통하는게 좋음 - 애초에 이런 인재는 재직중인 회사에서도 신망이 두터워 이직 시장에 나오지 않는다 
  2. 헤드헌팅 업체에 등록
  3. 구인구직 사이트 등록 
  4. SNS 추천 기능
  5. 직접 지원, 지인 소개 

 

이직을 결심하기 어려운 이유

  • '이 회사를 그만둘 수 없다'는 생각에 갇히면 누구든 자신을 조금씩 속이게 되는 법이다 
  • 선택지를 잃을수록 구구절절해진다 
  • 자신이 회사에 당당해지면 점점 회사는 당신에게 매력적인것들을 제안하며 유혹한다. 
    • 하지만 회사는 그렇게 쉽게 변하지 않는다. 잠깐의 눈속임일 뿐이다 
    • 조직에 의존하지 않고 스스로 살아갈 힘을 기르기 위한 회사가 지금의 회사인가? 
    • 시장가치보다 연봉이 많이 받는것이 좋은것은 아니다 
    • 마지막 순간에 이직이 망설여진다면 처음의 목적을 되새겨라 
    • 자신의 시장가치와 연봉 사이에 격차가 있다는 사실을 40대 후반까지 아무도 말해 주지 않는다 
    • 당신이 회사에서 나간다 해도 회사의 다른 사람들이 충분히 당신의 빈자리를 매꿀 수 있다 
    • 내가 나간다고 해도 회사는 변함없이 잘 돌아갈 것이다 
  • 이직 이야기를 나눌 때 지킬 세가지 
    1. 설득에 논리가 있어야한다 
    2. 상대방의 공감을 이끌어내야한다 
    3. 상대방의 믿음을 얻어야한다 

 

조직논리가 사람을 갉아먹는다 

  • 벤처기업의 특징상 퇴사할 때까지 다닐 수 있다는 보장이 없기 때문에 오히려 사원들에게 적극적인 성장 기회를 제공한다. 그러다 보니 역설적으로 훌륭한 인재들이 모이게 되고 이직률도 낮아진다 
     - 언제든 이직할 수 있는 사람들이 이직하지 않는 회사 
     
  • 자신이 가치있다고 생각하는 상품을 판매하는 일 그 자체가 천직이다 
  • 실적이 좋지 않은 회사일수록 사람들의 관심이 내부를 향해서 근거 없는 소문과 사내 정치, 부정에 대한 암묵적인 동조나 압력이 판을 치게된다 회사가 점점 사람의 영혼을 갉아먹는 방향으로 가게된다 
  • 자신을 속이지 않고 일해야한다 

여태껏 싫은 일만 해 왔던 사람이 어느 날 갑자기 좋아하는 일을 찾기가 쉬울까? 

진심으로 즐길 수 있는 일이냐의 여부는 크게 의미가 없다 그보다 진심으로 즐길 수 있는 상태가 중요하다 

  • To-Do(일)을 중시하는 사람 : 항상 '무엇을 이룰지' 생각한다 , 꿈이나 목표가 명확하다 
    • 전에 없던 혁신적인 상품을 개발하는 것을 좋아한다 
    • 회사를 키워나가는 것이 즐겁다 
  • Being(상태)를 중시하는 사람 : 어떤 사람이고 싶은지, 어떤 상태이고 싶은지를 중시한다 
    • 존경하는 이들과 함께 하는 것을 좋아한다 
    • 세상에 일을 통해 좋은 영향을 끼치는 것을 좋아한다 
    • Being형 인간은 자신이 외부 환경에 비해 적당한 실력을 갖췄을 때 재미를 느낀다 ( 게임을 예로 들어 너무 강한 적만 있다면 재미가 없다) 
    • Being형 인간이 일을 즐기기 위해서는
      • 자신의 시장가치를 높여야한다 
      • 되도록 자신을 속이지 않아야한다 내가 나를 좋아하지 않으면 아무리 실력을 키워도 게임을 즐길 수 없다
      • 인생이 너무 느슨해 졌거나 빡빡해 졌다면 다른 게임으로 갈아탈 때가 됐다는 뜻이다 
        • 약한 적만 보면 지루해지고 강한적만 보면 피폐해진다 
        • 지난 6개월간 회사에서 좋은 긴장이 세번 이하라면 직무를 바꾸는게 좋다 
        • 지난 6개월간 회사에서 나쁜 긴장이 10번 20번이 넘으면 직장을 바꾸는게 좋다 
          - 긴장이 사내에서 생겼는지 사외에서 생겼는지 
          - 나쁜 긴장 : 상사가 사내의 평가, 인정에만 주목해서 매출 실적에 대한 압박한다. 
          - 좋은 긴장 : 경쟁사와의 대결, 고객 앞에서의 프리젠테이션, 복잡한 교섭을 동원하는 영업활동 
        • 나쁜 긴장이 줄어도 좋은 긴장이 부족하다면 이직을 하는것이 좋다 
    • Being형 인간이 좋아하는 일을 찾는 방법 
      • 남들은 잘한다고 칭찬하는데 스스로는 썩 잘한다고 생각하지 않는 일을 떠올려 본다 
      • 평소에 전혀 스트레스 없이 할 수 있는 일을 떠올려 본다

고유한 라벨로 대체불가능한 사람이 돼라 

자신만의 라벨을 만들어라 - 대기업에 다닌다 한들 언제 방출될 지 모른다. 그럴 때 고유한 '라벨'이 있는 사람은 살아남을 수 있다 

"신규 개척의 괴물", "기존 고객 욕구 예측 분야의 에이스", "프로젝트의 위험 요소 제거자" 

일용품 신세를 벗어나고 싶다면 자신이 좋아하는 일, 힘들지 않은 일을 '라벨'로 만들어라 그 라벨에 바라는 이상이나 잘하고 싶은 일을 포함해도 괜찮다 

라벨을 붙였다면 그 라벨이 더욱 확고해지는 방향으로 판단해라 

이직의 장애물을 대개 현실적인 위험성이 아니라 남들의 시선 혹은 막연한 공포다. 

선택의 성공 여부는 나중에야 판단할 수 있다. 유일하게 나중에 확실히 후회할 선택은 '각오해야 할 때 각오하지 않은 것이다' 

이직이 당연한 사회가 되면 다른 선택지가 생긴 개인은 보다 자유로워지고 직원을 끌어당겨야 하는 회사는 더 매력적으로 변할 것이다 

마치며 - 우수한 인재는 회사에 있을 동안 필사적으로 짊어지고 전진했을 것이다. 그만두기 전까지는 회사를 진심으로 맡아줄 인재이다. 우수한 인재는 3년 후에는 회사에 없을지라도 회사를 짊어지고 전진하는 직원이다.

이직은 선(善)이다. 성장하는 시장을 찾고 자신을 믿어라 

 

이전 CPU의 명령어 처리 주기에 이어서 CPU가 기존 1 core에서 Multi-core로의 발전과 process 단위 실행에 대한 문제점을 개선하기 위해 나온 threading에 대해서 설명한다 

  • Process 
    • process는 하나의 코어에 하나만 동시에 작업될 수 있고 여러개의 process를 맡길 수 있는데 이때 동시에라는 말은 시간에 대한 개념으로 여러개의 process를 맡기더라도 정해진 스케쥴의 프로세스들을 시분할로 몇 ms씩 번갈아가면서 작업을 한다 
    • process에는 5가지 상태가 있다 -
      • create - 프로세스가 생성되는 중이다 
      • running - 프로세스가 CPU를 차지하여 명령어들이 실행되고 있다 
      • ready - 프로세스가 CPU를 사용하고 있지는 않지만 언제든 사용할 수 있는 상태로, CPU가 할당되기를 기다리고 있다. ready상태의 프로세스들은 정해진 schedule에 따라 우선순위를 가지고 있고 우선순위가 높은 프로세스가 먼저 CPU를 할당받는다 
      • waiting(block) - 프로세스가 입출력 완료, 시그널 수신 등 어떤 사건을 기다리고 있는 상태
      • terminated - 프로세스의 실행이 종료되었다 
    • 프로세스는 OS kernel에 설정된 Timeout에 따라 running, ready, waiting 상태를 계속 반복하게 된다
    • 이렇게 계속해서 프로세스를 중지시키고 다시 시작하고 할 때마다 계속해서 메모리의 특정 영역에 이전 프로세스의 정보를 저장하고 이번에 작업해야하는 프로세스의 정보를 읽어오는데 이러한 작업을 Context Switching 이라 한다 
    • Process context switching에 포함되는 정보는 다음과 같다
      • Process ID 
      • Process state 
      • program counter ( 이 프로세스가 다음에 실행할 명령어의 주소를 가지고있음)
      • registers
      • scheduling 정보 
      • memory 관리 정보 
      • process 계정 정보 
      • I/O state 정보 
    • 프로세스가 너무 자주 변경되면 context switching overhead가 커져 효율적이지 못하다 
    • 하지만 사용자는 더 많은 작업들을 동시에 실행시키고 싶어하고 이를 위해 사용하는 기술이 threading이다 
  • thread 
    •  어떤 프로세스내에서 실행되는 흐름의 단위를 말한다.
    • 일반적으로 하나의 프로그램은 하나의 스레드를 가지고 있지만 프로그램의 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다 
    • multi process의 경우 각 process는 독립적으로 실행되며 별개의 메모리를 차지하고 있는 것과 달리 multi thread는 프로세스 내부에서는 메모리를 공유해 사용할 수 있다. 또한 context switching overhead가 작아 프로세스간의 전환보다 스레드간의 전환속도가 더 빠르다 
    • 하지만 하나의 메모리를 둘이상의 흐름이 동시에 접근 할 수 있어 값을 읽고 쓸때 동시에 같은 메모리 주소에 접근하지 못하게 프로그래머의 계획이 필요로 하게 된다 
    • thread는 또 2가지로 나뉘는데 
      • User-Level Thread 
        • 커널 영역의 상위에서 지원되며 일반적으로 사용자 레별의 라이브러리를 통해 구현됨 
        • 프로세스내부의 하나의 스레드가 중단되면 다른 모든 스레드도 중단된다 . 커널이 프로세스 내부의 스레드를 인식하지 못하며 해당 프로세스를 대기상태로 전환하기 때문이다 
        • 일반적으로 프로그래밍을 할 때 사용되는 스레드가 사용자 레별 스레드와 비슷하게 실행된다 
      • Kernel-Level Thread
        • OS가 지원하는 스레드 기능으로 구현되고 커널이 스레드의 생성과 스케줄링을 관리한다 
        • 하나의 스레드가 중지되더라도 같은 프로세스 내부의 다른 스레드를 중단시키지 않는다

thread의 흐름 출처 : https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A0%88%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85), https://m.blog.naver.com/three_letter/220333796848
thread level

  • multi-threding
    • TMT ( Temporal Multi-Threading) - 일시적 멀티스레딩
      • 하나의 코어에서 여러개의 스레드를 시분할로 번갈아가면서 작동시킴 
    • SMT ( Simultaneous Multi-Threading) - 동시 멀티스레딩
      • 여러개의 스레드를 동시에 작동시킴 
      • 하드웨어 상에서 1개 이상의 명령어를 issue하고 dispatch 할 수 있다면 동시 멀티스레딩을 위해서는 각 스레드의 context를 저장할 추가적인 레지스터 정도의 하드웨어 모듈이 추가되면 구현이 가능하다 
      • 현재는 2-way, 4-way, 8-way SMT들이 있고  양방향(2-way) 동시 멀티스레딩을 주로 사용하는데 Intel에서 개발한 2-way SMT를 Hyper threading이라 한다 

*programing에서의 thread - 일반적인 프로그래밍에서 thread를 사용하면 여러개의 코어에 스레드를 적절하게 분배하지만 python의 경우 GIL(Global Interpreter Lock)로 인해 하나의 프로세스에서만 동작하게 되고 결국 하나의 프로세스에서 시분할로 여러개의 스레드가 작동되게 된다 그렇기에 완전한 병렬이라고 볼 수 없다.

 

  • Multi-processing 
    • Multi processing은 여러개의 프로세스들끼리 자원을 공유하며 동시에 작업을 진행하는 기술을 말한다 - multi threading의 경우 하나의 프로세스안에서 여러개의 스레드가 시분할로 작동된다면 multi processing은 다수의 프로세스들끼리 통신하여 한개 이상의 코어에서 동시에 프로세스에서 작업을 진행하는 것이다. 여기서 여러개의 코어로 말하지 않는 이유는 특정 코어에 고정적으로 프로그램을 동작시키지 않는다면 OS의 코어 할당 우선순위에 따라 같은 코어에 2개의 프로세스가  시분할로 작업될 수 있기 때문이다 
    • 하지만 process들간에는 메모리 공유가 되지 않고 각각의 독립된 공간에서 작업을 진행하기 때문에 같이 사용하는 데이터가 있을 경우 IPC(Inter Process Communication)을 사용하여 자원을 공유하여야 한다. 여기서 공유된 자원에 대하여 동시에 여러 프로세스들이 접근하는 것은 매우 위험하기에 권장되지 않는다 
    • IPC의 종류 
      • Pipe 방식 
        하나의 프로세스는 데이터를 쓰기만 하고 다른 하나는 데이터를 읽기만 한다 
        한쪽으로만 통신하기 때문에 Half-Duplex(반이중) 통신이라 부르기도 한다 
        만약 Full-Duplex(전이중) 통신을 원한다면 하나의 Pipe를 더 생성하여 Cycle을 만들면 된다 
        프로세스간에 PPID를 알고 있어야 정보의 전달이 가능하다 - 주로 자식, 부모 프로세스간 사용
      • Named Pipe (FIFO) 
        프로세스의 통신을 위해 이름이 있는 파일을 이용한다 
        위의 pipe와 다르게 부모 프로세스와 무관하게 전혀 다른 모든 프로세스들 사이에서 통신이 가능하다 
        하지만 위와 마찬가지로 전이중 통신을 위해서는 두개의 FIFO파일이 필요로 하게 된다 
      • Message Queue 
        프로세스간에 공유하는 Queue를 생성한다 선입선출의 자료구조를 가지며 커널에서 관리한다 
        메모리공간에 위치하기 때문에 어느 프로세스에서던 데이터를 이용할 수 있다. 
      • Socket 
        네트워크를 이용하여 socket으로 데이터를 공유한다 
        127.0.0.1:50000 이런 IP:PORT로 접근 할 수 있기 때문에 물리적으로 다른 위치에 있는 하드웨어의 프로세스에서도 접근할 수 있다 
      • Shared memory 
        메모리의 일정공간(주소)를 공유한다 
        일반적으로 프로세스는 자신만의 메모리 공간을 가지고 있는데 kernel상에서 공유할 메모리공간을 따로 할당하여 프로세스에서 사용할 수 있게 한다 
        공유메모리 방식은 중간에 broker가 없기 때문에 IPC 방식들 중에 가장 빠르게 작동할 수 있다 
        공유메모리를 두개이상의 프로세스가 동시에 접근할 시 문제가 될 수 있기에 semaphore같은 관리장치를 이용하여 동시접근을 예방할 수 있다 
      • Memory map 
        Shared memory와 비슷하지만 열린 파일을 메모리에 올려 공유한다는 점이 다르다 
        파일은 시스템의 전역적인 자원이므로 서로다른 프로세스들 끼리 공유하는데 문제가 없다 

shared memory와 socket 방식
Pipe 방식

 

IPC Socket : https://github.com/dldidfh/distributed_process

 

GitHub - dldidfh/distributed_process

Contribute to dldidfh/distributed_process development by creating an account on GitHub.

github.com

공유 메모리 : https://github.com/dldidfh/tistory_code/tree/master/%EA%B3%B5%EC%9C%A0%EB%A9%94%EB%AA%A8%EB%A6%AC

'IT기초' 카테고리의 다른 글

(2) CPU Instruction Cycle  (0) 2022.11.09
(1) Linux 기초 - 리눅스란?  (0) 2021.11.22
[네트워크] 개발자용 네트워크 기초  (0) 2021.09.28

CPU는 사람의 두뇌라고 볼 수 있다. 모든 컴퓨터 장비들을 통제하고 실질적으로 모든 작업에 대해서 main으로 처리하는 장비이다

 

매우 좋은 자료 : https://www.youtube.com/watch?v=Fg00LN30Ezg 

 

CPU가 처리하는 작업으로는 대표적으로 4가지가 있다 

Fetch, Decode, Execute, Writeback 

  • Fetch 
    1. PC(Program Counter)에 저장되어있는 주소를 MAR(Memory Address Register)에 복사한다 
    2. MAR에 저장되어있는 주소를 address bus*에 배치시킨다 
    3. 제어장치가 read signal을 발생시키기 위해 control bus에 read state를 배치시킨다 
    4. address bus에 있는 주소를 참조하여 메모리에서 해당 주소에 해당하는 값을 읽어 Data bus(Buffer bus)에 배치시킨다 
    5. Data bus에 배치되어있는 명령 또는 데이터를 MDR(Memory Data(Buffer) Register)에 불러들인다 
    6. MDR의 명령어가 IR(Instruction Register)에 복사된다 
    7. Program Counter에 메모리 주소가 다음 주소로 증가한다 ( CPU의 처리 단위 bit에 따라 주소 증가 값이 다름*)
  • Decodes
    1. CU(Control Unit)이 IR에 저장된 명령어를 decodes 한다 - CPU 제조사에 따라 opcode가 다를 수 있음
      명령어 : 00000001 -> opcode 리스트를 확인하여 -> 저장명령어구나 ! 한다. 
  • Execute 
    1. 해석된 명령어에 따라 데이터에 대한 명령을 수행한다 
  • Writeback
    • 처리가 완료된 캐시에 있는 데이터를 메모리에 기록한다. 결과값을 기다리는 명령어가 있다면 결괏값이 생겼다 알려준다

*address bus - 일정한 메모리 번지를 찾을 때 사용되는 신호를 운반하는 컴퓨터 내의 배선 버스 

*CPU의 처리단위 bit에 따라 주소 증가값 : RAM에 주소 하나에는 8bit씩 저장되어 OS 64bit process의 경우 8씩 증가한다
(64bit = 8bit의 묶음 8개) 

 

위의 CPU의 4가지 작업은 간단한 순차적 처리의 경우의 단순한 예시이다. 

만약 특정 메모리 주소의 데이터를 참조해야하는 명령어가 진행 중인데 특정 메모리 주소의 값이 아직 처리되기 전이라면 가만히 기다릴 것인가? 

  • Out-of-Order Execution OoOE - 비순차적 명령어 처리
    1. 만약 어떤 결과값을 받아야 다음이 실행되어야 하는 명령어가 있을 때 결괏값을 받을 때까지 계속 대기한다면 Idle time이 길어지게 된다
    2. 이때 현재까지 진행하던 명령어 처리를 잠시 뒤로하고 다음 명령어를 실행하면 Idle time을 줄일 수 있다
    3. 그러면 이전 명령어는 어떻게 다시 불러올까? 
    4. 이전 명령어의 진행단계를 특정 저장장소에 저장해놓자 
    5. 그렇게 하기위해서는 일정 명령어들끼리 미리 의존성(이전 결괏값을 기다려야 하는 명령어가 있는가?)을 조사하여 의존성이 해결된 명령어들을 먼저 실행하자! 
    6. 그렇기 위해서는 위에서 설명한 Decode 단계를 Issue, read-operand 2단계로 나눈다. 바로실행할 수 없는 명령어는 대기상태(stall)로 변경된다 
      • issue - 다음 명령어가 실행가능한지 검증한다 
      • read-operand - 실행가능한 명령어의 opcode를 불러온다 
    7. 비순차적 명령어 처리로 인해 발생할 수 있는 문제 
      1. WAR - Write after Read - 변경된 값을 읽어옴
      2. WAW - Write after Write - 변경하고 또 변경함 
      3. 아래 SMT를 설명할 때 추가설명

* 분기 예측 (Branch Prediction): 특정 조건에 맞는 상황에 다음에 실행할 명령어의 주소를 예측한다

* 추측 실행 (Speculative Execution): 어떤 명령어가 특정 단계에서 필요한 정보가 없어 진행이 막혔을 때 필요한 정보를 예측해서 높은 확률로 맞춘다면 틀렸을 때의 다소 큰 손해를 넘어서는 큰 이익을 취할 수 있다. 

 

비순차적 명령어 처리에서 이전 명령어의 진행단계는 어디에 어떻게 저장하나? 

기존 CPU의 명령 cycle이 4단계에서 8단계로 세분화된다

  • Rename 
    1. 실제 프로그래머가 보는 레지스터 주소는 0x0001 이런 논리적 주소(Architecture Register file )이다 이를 프로그래머가 실제로 보지 못하는 물리 레지스터 파일(Physical Register File )과 메핑 한다 이러한 메핑에 대한 정보는 RAT(Register Alias table)에 저장된다 

Renaming 출처 : https://en.wikipedia.org/wiki/Register_renaming

 

  • Dispatch
    • 명령어가 실행하기 위해 대기하는 대기열 - 순서를 기다리는 Reservation Station : ROB(Reorder Buffer)
  • Issue
    • 대기열에 있는 명령어가 실행될 수 있는지 검증하고 실행하기 위한 장치 
  • Commit 
    • 명령어를 수행하며 사용한 자원을 반환

 

그럼 또 생각나는 것은 브라우저를 이용하면서 카카오톡도 쓰고 싶다. 

초창기에는 하나의 작업을 빨리 끝내고 다음 작업을 시작하자!라고 생각하여 CPU의 성능(clock, 면적)을 올리는 것에 집중했다. 하지만 한계에 다 달았고 그로 인해 Dual, Octa core 등의 다수의 core를 장착한 CPU가 사용되어 보편화되기 시작한다

다음 장에서는 멀티태스킹의 발전에 대해서 설명하기 위해 Multi-process, Multi-Threading에 대해서 설명하겠다

 

 

 

 

 

 

 

 

(2022-07-20) 현재에도 많이 사용하고 있는 방법인 CSP에 대한 논문이다 

CSP는 계속돼서 사용되어 지금은 Modified CSPv6까지 나왔다 

개인적인 생각으로는 기존 residual shortcut을 몇 개의(2~3개의) convolution마다 진행했다면 CSP shortcut은 그런 residual shortcut 몇 개마다 진행한다. 

 

Abstract 

  • CSP : Cross Stage Partial Network 
  • 기존의 CNN들의 computation이 많이 소모되는 이유는 네트워크 최적화 내의 중복 기울기 정보(Duplicated gradient information) 때문이다 
  • CSPNet은 시작과 끝에서 feature map을 통합하여 기울기의 variability를 respect 한다
  • 결과적으로, ImageNet dataset에서 동등한 정확도로 20% computations를 감소시켰다 
  • CSPNet은 쉽게 적용할 수 있고 기존 shortcut 방식들을 대체할 수 있는 일반적인 구조이다. 
    - transition layer : BN + 1x1 conv + 2x2 avg pooling 
    - growth rate : Dense Block을 지날 때마다 증가되는 feature map의 사이즈 

Dense connection

 

1. Introduction 

  • Neural Network가 더 깊고 넓어질 수 록 더 강력함을 보여줬다
  • 그러나 이런 확장 구조는 매우 많은 computations를 필요로 한다 
  • 이전의 몇몇 접근법에서는 모바일 GPU를 기준으로 설계되었다 ( depth-wise separable convolution) 
    depth-wise separable conv는 ASIC(Application-Specific Integrated Circuit)과 호환되지 않는다. 
  • 이 제안의 초점은 기본 layer의 featuer map을 두 부분으로 나누어 제안하는 cross-stage hierarchy에 따라 mergeing 한다 
  • 중요 개념은 gradient의 흐름을 다른 두 개의 네트워크 경로에 나누는 것이다 
  • CSPNet에서 주로 다루는 문제는 3가지이다 
    1. CNN의 학습능력(learning ability) 강화 
        - 경량화하면서도 충분한 정확도를 유지 
        - ResNet, ResNeXt, DenseNet등에 쉽게 적용 가능하고 computation effort를 10~20%가량 줄이지만 정확도는 거의 떨어지지 않거나 outperform 
    2. Removing computational bottlenecks 
        - 너무 많은 computational bottleneck은 inference process에서 더 많은 cycles이 필요하게 된다 
        - utilization을 더 효율적으로 높이고 필요 없는 에너지 소모를 줄인다 ( 저자 중에 College of Artificial Intelligence and Green Energy National Chiao Tung Univ가 있는데 친환경에너지 쪽이어서 그런지 이런 이야기가 좀 있다)
        - YOLOv3 기반 모델에서 computational bottleneck을 80%가량 감소시킨다 
    3. Reducing memory costs 
        - DRAN은 비싸고 크기도 큰데 DRAM사용량을 줄이면 ASIC의 비용을 감소시킬 수 있을 것이다 
        - 메모리 사용량을 줄이기 위해 cross-channel pooling을 이용 - feature pyramid 생성 과정에서 feature map의 compress를 수행한다 
        - PeleeNet에서 feature pyramids를 생성할 때 memory 사용량을 75% 감소할 수 있었다 
  • CSPNet은 CNN에서 학습 용량을 증가시킬 수 있고 작은 모델에서 더 좋은 정확도를 얻을 수 있다. 
    GTX 1080ti에서 109 FPS, 50%의 AP50을 얻었다 
    - 메모리 대역폭을 줄일 수 있었고 Nvidia jetson TX2에서 42% AP50, 49 FPS를 얻었다 

 

2. Related work 

  • CNN architectures design 
    ResNeXt 저자가 width, depth의 channel보다 cardinality(gradient의 다양성)를 늘리는 게 더 효과적일 수 있다 했다
    a. DenseNet 
        - 출력의 결과물을 그대로 concatenates 하여 다음 레이어의 인풋으로 사용했다 이러한 방식은 cardinality를 극대화할 수 있다
    b. SparseNet
        - dense connection은 exponentially spaced connection을 하여 parameter의 utilization을 효과적으로 개선할 수 있다 또한 높은 cardinality와 sparse connection이 gradient combination의 concept으로 왜 네트워크의 학습능력을 향상할 수 있는지 이유를 설명하고 partial ResNet(PRN)을 개발했다 

 

3. Method 

  • Cross Stage Partial Network 
    • DenseNet 
      아래 수식을 보면 DenseNet의 경우 역전파에서  $$ g_0, g_1,... g_{k-1} $$ 가 중복되어 사용됨을 볼 수 있다 
      이런 중복은 다른 레이어에서 같은 값을 중복해서 사용함을 의미한다 

 DenseNet에서의 순전파와 역전파 

 

  • 저자가 제안하는 CSPDenseNet은 입력 채널을 두 개의 파트로 분할한다 
    $$ x_0 = [x_0`, x_0``] $$ 두 개로 나누어진 파트는 스테이지의 마지막에서 직접적으로 연결된다 그리고 $$ x_0``$$ 는 dense block을 통과한다 
  • Dense layers의 출력 $$ [x_0``, x_1, ..., x_k]$$는 바로 transition layer를 통과하고 transition layer의 output $$x_t$$는 $$x_0``$$와 결합되고 바로 다른 transition layer를 통과하여 $$x_U$$를 생성한다. 

  • 위 공식을 보면 dense layer에서 들어오는 gradient 가 별도로 합쳐져 있음을 볼 수 있다 (gt) 
    또한 feature map x0`이 직접적으로 dense layers를 통과하지 않고 분리되어 통합된다 
    가중치 업데이트를 보면 두 갈래의 기울기 정보가 서로 중복되지 않는다. 
  • CSPDenseNet은 DenseNet의 feature reuse장점은 유지하면서 동시에 gradient flow를 truncation 하여 중복되는 gradient info의 과도한 양을 방지한다 
  • Partial Dense Blcok 
    - Incerease gradient path : feature map복사를 사용하여 gradient paths가 doubled 되는 것을 완화 
    - balance computations of each layer : 일반적으로 base layer의 채널의 개수는 growth rate보다 크다(input C 64, growth rate 32, 64,...) CSP는 base layer의 반절만 사용하기 때문에 계산 병목 현상의 절반 가까이를 효과적으로 해결할 수 있다 
    - reduce memory traffic : 총 m개의 dense layer가 있을 때
    $$ w\, \times \,h\, \times \, c,\, grouth\, rate\, :\, d, total \,m\, dense\, layers$$
    dense layer의 CIO(Convolution Input, Output)는
    $$ (c \times m ) + ((m^2 + m) \times d)/2 $$
    이고 partial dense block의 CIO는
    $$((c \times m)+(m^2 +m) \times d)/2$$
    일반적으로 m과 d는 C보다 매우 작기 때문에 partial block이 반절 가까이 네트워크 메모리 traffic을 절약할 수 있다 

 

 

  • 저자는 3가지 다른 방식의 Feature fusion 방식을 소개한다 
    1.  CSPDenseNet에 사용한 방식 두 갈래로 나누어진 경로에 하나는 Dense Block과 Transition layer를 지나고 그 뒤 두 갈래를 concatenate 한 후 다시 transition layer를 통과한다
    2. Fusion First는 두 갈래 중 하는 Dense Block을 통과하고 그 뒤 두 갈래를 합친 후 transition layer를 통과한다
    3. Fusion Last는 CSPDenseNet와 다르게 마지막에 두 갈래를 합치기만 하고 transition layer을 다시 지나가지 않는다
  • 위의 Fusion First와 Fusion Last는 다른 영향을 보여주는데 
    Fusion First는 두 갈래로 나뉜 기울기를 먼저 합친 다음 transition layer로 들어가기 때문에 많은 양의 기울기가 재사용된다 
    Fusion Last는 한 방향만 transition layer를 지나가고 그 뒤 두 갈래를 합치기 때문에 기울기의 흐름이 잘리기 때문에 기울기 정보가 재사용되지 않는다 
  • Fusion first는 실험 결과 Imagenet 데이터셋에서 computations는 효과적으로 감소되었고 정확도는 0.1% 밖에 감소되지 않았다
    Fusion Last 또한 computations는 감소되었지만 정확도가 1.5% 감소되었다 

일반 CNN에서의 CIO 계산 (출처 : HarDNet: A Low Memory Traffic Network )

  • 가정 1 
    - c 입력 채널 : 32 
    - m 몇개의 Dense layer : 3 
    - g growth rate : 32 
    $$ Dense \, layer :  (32 \times 3) + ((3^2 + 3) \times 32)/2 = (96 + (12 \times 32)/2 = 96 + 384 / 2 = 288 $$
    $$ partial \, dense \, block :  ((32 \times 3) + ((3^2 + 3) \times 32))/2 = ((96 + (12 \times 32))/2 = (96 + 384) / 2 =  240 $$
  • 가정 2 
    - c  : 4
    - m : 3 
    - g : 62
    $$ Dense \, layer :  (4 \times 3) + ((3^2 + 3) \times 64)/2 = (12 + (12 \times 64)/2 = 12 + 768 / 2 = 396 $$
    $$ partial \, dense \, block :  ((4 \times 3) + ((3^2 + 3) \times 64))/2 = ((12 + (12 \times 64))/2 = (12 + 768) / 2 =  390 $$
    - 채널과 growth rate의 차이가 매우 커야 Dense layer의 CIO가 더 커진다

ResNeXt에서 bottleneck 을 제거하고 transition layer을 사용하여 병목을 줄였다

 

4. Experiments 

  • Computational Bottleneck 
    기존의 ResXBlock에서 bottleneck layers를 제거하여 채널의 크기 변경을 감소시킴으로 인해 22%가량 computations를 감소시켰다 

PeleeNet에서 병목구간을 80% 가량 감소시켰다
ResNeXt에서 bottleneck layer를 제거함으로써 중간중간 아웃풋 채널의 개수가 일정하게 유지된다 ( computations 22% 감소 )

논문 : https://arxiv.org/pdf/1911.11929.pdf

 

최신 SOTA 모델인 YOLOX 논문이다 

기존 누구나 생각했던 YOLO의 문제점? 불편함이었던 사항들을 전부 수정한듯한 느낌이다 

기존 불편한점 : Anchor based model , Anchor 최적, ancho는 그저 mAP를 끌어올리기 위한 수단, 과연 coupled head는 regression loss와 classification loss, obj loss 중 어떤 게 더 많이 잘못됐는지 알고 있을까? end-to-end 학습을 위해 NMS를 없애고 싶다 

Abstract 

  • 기존의 YOLO 시리즈를 anchor-free로 변경하고 다른 디텍션 테크닉을 사용 
  • Decoupled head 
  • Leading label assignment strategy - SimOTA 
  • COCO 데이터셋에서 기존보다 3%정도 뛰어난 47.3%의 AP를 얻음 

Introduction 

  • 지난 2년간 객체인식 분야의 주된 관점은 anchor-free detectors와 end-to-end(NMS-free) detectors이다. 이런 기술들은 아직 YOLO시리즈에 접목되지 않았고 SOTA였던 YOLOv4와 YOLOv5 또한 anchor-based detector이어서 학습 시 hand-craft로 하이퍼 파라미터들을 수정해야 했다 
  • 저자는 기존의  YOLOv4와 5는 anchor-based에 과하게 최적화 되어있어 YOLOv3을 베이스 모델로 선택하였다 

YOLOX 

  • YOLOX-DarkNet53
    • YOLOv3 + DarkNet53을 베이스 라인으로 사용 
    • 300 epochs 학습 진행, 5 epochs는 COCO train2017로 warm-up 진행 
    • SGD momentum 0.9 사용 
    • Learning rate는 lr X BatchSize/64 ( linear scaling ), 초기 lr = 0.01
    • cosine scheduler 사용 
    • weight decay 0.0005 
    • batch size 128 ( 8-GPU) 
    • Input size = 448 ~ 832 ( 32 strides)
    • 모든 실험 FPS, latency는 모두 FP-16, batchsize 1 , Tesla V100으로 진행 
  • YOLOv3 baseline 
    • DarkNet53, SPP layer ( YOLOv3-SPP) 
    • 추가로 EMA weights updating 사용 ( EMA : Exponential Moving Average)
    • Cosine lr schedule 
    • IoU loss, IoU-aware branch
    • 학습때regression 브런치에 대해 IoU loss를 사용한 것이 큰 성능 향상을 불러왔다 한다 
    • Augmentations 
      • RamdomHorizontalFlip
      • ColorJitter
      • Multi-scale for data augmentation
      • Random Resized Crop은 Mosaic과 상충한다 생각되어 사용하지 않음 
  • Decoupled head 
    • 기존 객체인식에서 분류와 회귀의 잘 알려진 conflict가 있었다. 대부분의 decoupled head는 one-stage, two-stage에서 많이 사용되었지만 YOLO시리즈는 백본 및 feature pyramids가 지속적으로 변함에 따라 detection head는 coupled로 유지되었다 
    • 저자는 두가지 분석 실험을 통해 coupled detection head는 성능에 문제가 있단 걸 알았다 
    • YOLO의 head를 decoupled 로 변경하니 높은 성능 향상이 있었다 
      또한 decoupled head는 end-to-end 학습에 필수적이다 
    • Coupled head와 decoupled head를 비교한 결과 Coupled head에서는 4.2%의 성능 감소가 있었고 decoupled head에서는 0.8%의 성능 감소가 있어 YOLO detect head를 가벼운(lite) decoupled head로 대체하였다
      - 구체적으로 1x1 conv로 채널차원을 감소시키고 각각 두 개의 병렬 3x3 conv가 따른다

  • Strong data augmentation 
    • Mosaic과 MixUp 이용 – YOLOX의 성능을 boost함 
    • MixUp은 classification을 위해 design됐지만 객체 검출 학습에서 BoF(Bag of Freebies)로 수정되었다
    • MixUp과 Mosaic을 모델에 적용했고 마지막 15epochs에서는 close 하였다
    • Data augmentation을 사용한 후 ImageNet pre-training은 더 이상 효과를 보지 못했다 – 그래서 뒤의 모든 학습은 스크레치부터 학습하였다
  • Anchor-free
    • 기존의 YOLOv4, v5는 anchor-base 파이프라인으로 엥커 메커니즘은 많은 문제들이 있었다
      1. 검출 성능을 최적화하기 위해서는 엥커의 크기를 찾기 위해 클러스트링을 진행해야 한다. 이런 클러스트링은 학습 때마다 도메인을 특정(domain-specific)하게 되고 일반화에 좋지 않다
      2. anchor 메커니즘은 detection head의 복잡도와 이미지마다 예측하는 숫자를 증가시킨다. 이러한 메커니즘은 edge AI system에서 병목현상을 야기한다
    • Anchor-free detector는 디자인 파라미터의 수를 감소시키고 heuristic한 튜닝과 많은 트릭들(clustering, grid sensitive)이 필요하지 않게 된다
    • 기존의 YOLO를 anchor-free로 변경하는 것은 그렇게 어렵지 않다
      각 로케이션마다 3개씩 예측하던 것을 1개로 변경하고 직접 4개의 값을 예측하도록 하였다 (left-top corner, height, width)
    • location of each object as the positive sample and pre-define a scale range, as done in [29], to designate the FPN level for each object. – 이러한 수정이 파라미터와 GFLOPs를 감소시키고 성능을 증가시켰다
  • Multi positives 
    • 기존 YOLOv3의 할당 rule과 같게 anchor-free 버전 또한 하나의 positive sample을 할당하였다 ( 각 예측에 대해 다른 비슷하게 잘 예측한 예측은 무시)
      하지만 이러한 좋은 (할당하지 않는) 예측들은 가중치 update에 좋은 이득을 가져온다
      이러한 좋은 예측은 positive와 negetive와의 극심한 불균형을 완화시킨다
      저자는 간단하게 center에서 3x3 area를 positives로 정했다 – 이름은 center sampling이라고 했다
    • 결과적으로 45%로 성능이 향상됐고 ultralytics(YOLOv5 저자)의 yolov3의 성능을 이미 넘었다
  • SimOTA
    • Advanced label assignmetn는 객체 검출에 중요한 과정이다. 기존 저자의 연구인 OTA를 기준으로 4가지 insights를 정했다
      1. Loss/Quality aware 
      2. Center prior
      3. Dynamic number of positive anchors for each ground-truth
      4. Global view - 특히 OTA는 레이블 할당에 global 관점을 가지게 한다 
    • Optimal transport problem을 공식화할 수 있게 한다
    • 저자는 Sinkhorn-Knopp algorithm을 이용하여 OT problem을 해결한다.
      하지만 25%의 추가적인 학습 시간이 필요하다
      이것은 quite expensive 하기 때문에 OT(Optimal transport)를 간단히 한 SimOTA를 제안한다
    • Dynamic top-k starategy를 사용한다
    • SimOTA는 먼저 각 예측-GT 쌍에 대해 cost 또는 quality로 표시되는 pair 매칭 정도를 계산한다
    • Gt gi와 prediction pj와의 cost 계산은 다음과 같다 cij=LijclsLijreg 
      λ는 balancing coefficient
      LijclsLijreggt와 predictions의 classification loss와 regression loss를 뜻한다 
      그러고 나서 gt에 대해서 고정 중앙 영역 내에서 비용이 가장 적게 드는 Top-k 예측을 positive sample로 선택한다 
      마지막으로 이러한 positive prediction의 해당 grid는 positive로 할당된다
      k 값은 gt의 개수에 따라 다른 값을 가진다
    • SimOTA는 학습 시간만 감소시키는 것이 아닌 Simkhorn-Knopp algorithm의 solver 하이퍼파라미터들을 방지한다

  • End-to-End YOLO 
    • (Qiang Zhou, Chaohui Yu, Chunhua Shen, Zhibin Wang, and Hao Li. Object detection made simpler by eliminating heuristic nms. arXiv preprint arXiv:2101.11782, 2021)를 따라 두 개의 추가적인 conv, one-to-one label assignment, stop gradient를 추가한다
      1. 이러한 적용은 end-to-end 학습이 가능하게 하지만 약간의 성능 감소와 추론 속도 추가를 발생시킨다

  • Model size and data augmentation
    • 대부분의 모델에서 같은 룰을 적용했지만 augmentation 전략에서는 모델의 사이즈에 따라 몇 개의 차이가 있다 
    • 작은 모델의 경우(YOLOX-nano) 강한 data augmentation은 성능 감소가 따랐다 그렇기 때문에 MixUP의 경우 삭제하고 mosaic의 경우 scale range를 감소시켰다 [0.1,2.0] 🡪 [0.5,1.5]
    • 하지만 큰 모델의 경우 강한 augmentation이 성능 증가가 따랐다
      MixUP의 경우 기존의 버전보다 더 강하게 적용하였고
      Copypaste에서 영감을 받아 두 이미지를 혼합하기 전에 무작위로 샘플링된 scale factor로 두 이미지를 jittered 하였다
      MixUP에서 Scale jittering의 효과를 비교하기 위해 COPYpaste(YOLOX-L)와 비교하였다 ( Copypaste는 instance mask(segmentation)가 필요하지만 MixUP은 필요하지 않다)

 

이번 장에서는 openCV에서 어떤 타겟을 마치 정면에서 본 듯 만들 수 있는 투영 변환에 대한 글을 작성하겠다

이전 장에서도 이용했던 mouse_events를 이용한다 

import cv2 

class MouseEvents:
    def __init__(self) -> None:
        self.points = []

    def left_button_down(self, x, y, frame):
        self.points.append([x,y])  
        cv2.circle(frame, (x,y),5,(0,0,255),-1)
        cv2.imshow('origin',frame)
        print(x,y)      

    def right_button_down(self,x,y, frame):
        self.points.pop()
        print(x,y)      

    def onMouse(self, event, x, y, flags, img):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.left_button_down(x,y, img)
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.right_button_down(x,y,img)

1. 원근(투영) 변환 - perspective (projection) transform 

  • 원근  
     일반적으로 사람들이 2차원 화면에서 어떤것이 더 멀리 있는지 인식하는 방법은 원근법을 이용한 것이다 가까운 것은 크게, 멀리 있는 것은 작게, 자신의 초점이 있는 곳을 기준으로 한 점으로 모인다 
  • 투영이란 
    도형이나 입체를 다른 평면에 옮기는 일
  • 우리가 진행할 문제는 이미지에서 원하는 부분의 원근감을 없에는 문제이다. 

 

2. opencv perspective transform 

import cv2 
import numpy as np 

from mouse_events import MouseEvents

POINTS_CHECK = False
# 좌표점은 좌상->좌하-> 우상 -> 우하 
# 변경할 좌표를 4x2 행렬로 전환 
# 이동할 좌표를 설정
mouse_events = MouseEvents()
mouse_events2 = MouseEvents() 

path = 'images.jpg'
frame = cv2.imread(path)
height, width  = frame.shape[:2]
cv2.imshow('origin', frame)
if POINTS_CHECK != True:
    copy_frame = frame.copy()
    cv2.setMouseCallback('origin',mouse_events.onMouse, copy_frame)
    cv2.waitKey(0)
    # 원본 이미지에서 원하는 지점의 좌표
    source_coord = np.float32( mouse_events.points )
    # H와 W 는 새로 생성될 이미지(destination_coord)의 크기를 선택한 지점의 상하좌우 최대값으로 넣기 위해 구한다
    H = max(mouse_events.points[2][0],mouse_events.points[3][0]) - min(mouse_events.points[0][0],mouse_events.points[1][0])
    W = max(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] ) - min(mouse_events.points[0][1], mouse_events.points[1][1], mouse_events.points[2][1], mouse_events.points[3][1] )
    # 변환 이미지에서 원하는 지점의 좌표 ( (0,0) ~ (W,H)로 변환 )
    destination_coord = np.float32( [ [0,0], [0,H], [W,0], [W,H] ] )
    # 원본 -> 변환 미지수 8개를 구한다
    M = cv2.getPerspectiveTransform(source_coord, destination_coord)
    # 변환 -> 원본 미지수 8개를 구한다
    M2 = cv2.getPerspectiveTransform(destination_coord, source_coord)
	# 원본 -> 변환의 역행렬을 구한다 
    INV_M = np.linalg.pinv(M)
    
    
transformed = cv2.warpPerspective(frame, M, (W,H))
cv2.imshow('transformed', transformed)
  • opecv에서는 간단하게 원근변환을 할 수 있게 해주는 getPerspectiveTransform 함수와 warpPerspective 함수를 제공한다 
    getPerspectiveTransform은 이용을 하자면 미지수를 구하는 단순한 수학 계산이다. 사용하기 위해서는 해당 점이 이동할 위치를 알고 있어야 한다 또한 사용상 원하는 지점의 좌표를 선택할 때 좌상, 좌하, 우상, 우하 순서대로 입력해야 한다.

  • cv2.warpPerspective(frame, M, (W, H)) 
    첫 번째 파라미터로는 사용될 이미지의 데이터가 들어간다 
    두 번째 파라미터 M 은 getPerspectiveTranform을 통해 구한 변환 행렬이 들어간다 ( 아래 행렬식에서 P들이 M이 된다)
    세 번째 파라미터는 결과로 나오는 이미지의 높이, 넓이이다 

다음과 같이 M은 3x3의 행렬이다
미지수는 P11 ~ P32 8개

  • MouseCallback을 이용하여 원하는 영역의 꼭짓점 4개를 선택한 후 아무 키나 눌러 진행한다 

 

3. 역변환

  • 만약 변환된 이미지에서 특정 좌표가 원본 이미지에서 어느 지점인지 알고 싶다면 어떻게 할 수 있을까? 

  • 위 사진과 같이 변환된 이미지에서 파란 선의 좌표가 원본 이미지에서 어느 지점인지 알기 위해서는 이전에 구한 변환에 사용한 행렬의 역행렬을 구하여 변환 이미지에서 원하는 지점의 좌표와 행렬 곱해주면 된다 
if POINTS_CHECK != True:
    cv2.setMouseCallback('transformed',mouse_events2.onMouse, transformed)
    cv2.waitKey(0)
    line_coord = np.float32(mouse_events2.points)
    One = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[0], 1), axis=1))
    Two = np.squeeze(INV_M @ np.expand_dims(np.append(line_coord[1], 1), axis=1))
    One = One[:2] / One[2]
    Two = Two[:2] / Two[2]

    POINTS_CHECK = True

cv2.line(transformed, line_coord[0].astype(int), line_coord[1].astype(int), (255,0,0),2,1)
cv2.line(frame, One.astype(int), Two.astype(int), (255,0,0),2,1)

dst2 = cv2.warpPerspective(transformed, M2, (height, width))
cv2.imshow('transform2', dst2)

cv2.imshow('origin', frame)
cv2.imshow('transformed', transformed)
cv2.waitKey(0)
  • 행렬곱은 간단하게 @를 이용하여 구할 수 있다. 
  • 행렬의 길이가 서로 다르니 맞추기 위해 np.append로 값 1을 넣었다 그 후 하나의 차원을 추가해 3x1의 차원을 가지도록 만들었다 ( 3x3 @ 3x1 ) 
  • 위의 결과로 나온 One 은 wx, wy, w 형태이다 ( 위의 공식 참고 ) 그렇기 때문에 원본의 x, y를 구하기 위해서는 w를 구하여 나누어줘야 한다. 
  • M = P11 ~ P 32 
  • 원하는 지점 = x, y를 x, y, 1로 변경 
  • One = (wx`, wy`, w) 

위의 공식을 잘 이해하고 있다면 무리 없이 진행할 수 있다. 복잡한 수학 계산은 컴퓨터가 해주니 공식을 가지고 사용하면 된다! 

 

+ Recent posts