2016/10/29

Ubuntu 16.10 Upgrade 후 발생한 문제들


우분투 16.04에서 16.10으로 Upgrade하고 나서 생긴 몇가지 문제들에 대해서 정리한다. Clean Install을 했으면 발생하지 않았을 지도 모를 문제들도 있다. 참고로, 우분투 Desktop 설치 iso 파일은 우분투 16.04가 1.4GB였는데 16.10은 1.5GB가 되었다. 그런데, 의외로 Upgrade 시간은 예전엔 2시간 정도 걸렸는데 이번엔 1시간 정도 밖에 안걸렸다.

Nvidia 드라이버 문제

우분투 16.10 업그레이드 후 Nvidia Proprietary Driver(v367.57)를 선택해서 재부팅했더니 16.04와 마찬가지로 black screen 문제가 생겼다. 우분투 16.04 설치시와 동일하게 Nvidia Site에서 367.57버전을 내려 받아서 재설치했더니 문제가 해결됐다. 16.04에서 사용하던 Nvidia 드라이버는 커널 모듈 컴파일시 오류가 발생해서 사용할 수 없었다.

fcitx 문제

fcitx process가 뜨지 않아서 한글을 입력할 수가 없다. 언어 설정에서 fcitx로 설정해서 로그 아웃했다가 재로그인 하면 fcitx가 동작하지만 재부팅하면 fcitx 프로세스가 뜨지 않는다. 참고로, ibus를 사용하면 문제가 없다. 그래도 ibus 보다 fcitx를 선호하는 편이라서 임시 방편의 해결책이 필요했기에 시작 프로그램에 fcitx를 넣어 주는 방법으로 해결했다. 즉, Unity Dash에서 Startup Applications의 Add 명령으로 /usr/bin/fcitx command를 추가해 주었다.

gcc/g++ v6.2 문제

g++에서 기본 언어 설정이 -std=gnu++14가 되었다. gcc 6.x부터 바뀐 모양이다. gcc 5.x까지만해도 -std=c++98이었는데 c++14를 써라는 야그. 하긴 Qt도 5.7부터는 c++11을 지원하지 않는 컴파일러는 사용할 수 없단다.

문제는 gcc/g++의 문제라기 보다 NVIDIA CUDA Toolkit에 딸려오는 nvcc가 gcc/g++ 6.x를 지원하지 않는다는 것이다. Tensorflow 등의 Deep Learning에 관심이 있는 사람들은 우분투 16.10을 설치하고나서 후회할 지도 모른다. 하지만, 아래와 같이 해결하면 된다.

$ sudo apt install gcc-5 g++-5

$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 100 --slave /usr/bin/g++ g++ /usr/bin/g++-5
$ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 50 --slave /usr/bin/g++ g++ /usr/bin/g++-6

즉, gcc/g++ 5.x와 6.x를 우분투에 동시에 설치해서 선택해서 사용할 수 있는데 위와 같이하면 우선순위가 높은 gcc/g++ 5.x를 자동으로 사용할 수 있다. 참고로 gcc-5 패키지는 gcc v5.4.1인데 CUDA 7.5/8.0의 nvcc에서 사용할 수 있다.

두 가지 버전의 gcc/g++이 설치돼 있으니 아래 명령으로 버전을 수동 선택할 수도 있다.

$ sudo update-alternatives --config gcc

즉, update-alternatives는 Debian 계열 리눅스 명령으로써 동일한 목적의 패키지들을 사용자가 선택해서 사용할 수 있도록 해준다.

Unity 8/Mir

우분투 16.10부터 Unity 8/Mir 데스크탑이 기본으로 탑재되어 있어서 로그인 화면에서 선택해서 로그인 할 수 있다(?). nouveau 환경에서 로그인이 된다는데 iMac의 문제인지 모르지만 로그인하면 먹통이다. 


기타 참고 사항

간만에 블로그에 와 보니 Syntax Highlighter가 또 다시 동작하지 않고 있다. 구글 드라이브에 올려 놓은 javascript나 css파일들이 동작하지 않는다는 것... 구글링해 보니 구글 이 놈들이 본색을 드러내는 것인지 구글 Cloud 사업하려고 다 막아 버린듯... 그런데, 구글링을 하다가 여기서 github 사이트를 이용하면서 우회하는 방법을 발견했다.

2016/07/08

c++11 Template 및 Move Constructor


c++11에서 기초적인 사항들인데 자꾸 까먹게 돼서 참고용으로 소스를 올려 놓는다. 주요 참고 사항은 아래와 같다.
  • Template에서 friend 함수 선언: Class 안과 밖에 두 번 선언하는 이유는 여기 참고.
  • std::initializer_list를 이용한 객체 초기화
  • Move constructor 호출: move semantics를 아래 main()에서와 같이 사용하는 것은 사실 매우 위험하다. Memory Leak 문제가 생길 수 있다. 객체 사용시 temporary 객체에 대해서만 묵시적으로 동작하도록 하는 것이 좋다. RVO(Return Value Optimization)와 std::move에 대해서는 여기 참고
  • Matrix 연산에서 DMatrix C = A*B와 같은 단순한 binary 연산을 할 경우에는 A와 B가  lvalue이므로 copy constructor가 사용되지만, 이 경우 RVO가 적용되므로(컴파일러가 operator*() 함수에서 생성한 local object를 return하면 바로 C에 할당되도록 함) c++11에 도입된 move semantics가 당장 이득이 되는 건 아니다. 하지만 복잡한 연산을 한 번에 수행할 경우에는 move semantics를 사용하게 된다.
  • Matrix 연산에서 다중 for-loop을 간소화하기 위해 template  meta programming을 사용하는 라이브러리들이 많은데 move semantics를 사용하더라도 그 의미가 줄어들지는 않을듯...
  • 참고로, RVO가 적용되는 경우엔 RVO를 사용하는 것이 가장 빠르고, move semantics는 불필요한 memory 할당과 deep copy를 제거해 주므로 copy semantics만 사용하는 것보다는 훨씬 효율적이다.
  • 그니까 아직 c++03 사용자라면, c++11 이후 환경에서는 단순히 move constructor와 move assignment operator만 기존의 class에 추가해 주면, 표준 STL 라이브러리들이 move semantic를  지원하기 때문에 대부분의 경우 나머지 일은 컴파일러가 알아서 해 준다. 물론, 아래의 예처럼 operator overloading을 사용하고 있다면 move semantics 버전을 추가해 주어야 한다.
#include <vector>
#include <iostream>
#include <initializer_list>
// uncomment to disable assert()
// #define NDEBUG
#include <cassert>

#define LOG(x) std::cout << "[" << __FILE__ << "(" << __LINE__ << "): " \
                         << __FUNCTION__ << "()] " << x << "\n"

// template class declaration
template <typename TD>
struct Matrix;

// template friend fuction declarations for Matrix class 
template <typename TD>
std::ostream& operator<<(std::ostream &os, const Matrix<TD>& m);
template <typename TD>
Matrix<TD> operator*(const Matrix<TD>& m, TD f);
template <typename TD>
Matrix<TD> operator*(Matrix<TD>&& m, TD f);

template <typename TD>
struct Matrix {
  Matrix() { LOG("Default Constructor"); }

  Matrix(std::initializer_list<TD> il) : m_data(il) {
    LOG("Initializer_list Constructor: 1 dimesion - size : " << il.size());
  }

  Matrix(std::initializer_list<std::initializer_list<TD>> mil) {
    LOG("Initializer_list Constructor: 2 dimension");
    size_t row = mil.size();
    size_t col = 0, pcol = 0;
    for(auto il: mil) {
      col = il.size();
      if(pcol && col != pcol) {
        LOG("Fatal Error: no. of columns should be same.");
        assert(col == pcol);
      }
      std::vector<TD> tv = il;
      m_data.insert(m_data.end(), tv.cbegin(), tv.cend());
      pcol = col;
    }
    LOG("row, col : " << row << ", " << col);
  }

  Matrix(const Matrix& m) : m_data(m.m_data) {
    LOG("Copy Constructor");
  }

  Matrix(Matrix&& m) : m_data(std::move(m.m_data)) {
    LOG("Move Constructor");
  }

  Matrix& operator=(Matrix& m) {
    LOG("Copy Assign");
    m_data = m.m_data;
    return *this;
  }

  Matrix& operator=(Matrix&& m) {
    LOG("Move Assign");
    m_data = std::move(m.m_data);
    return *this;
  }

private:
  std::vector<TD> m_data;

  friend std::ostream& operator<<<TD>(std::ostream& os, const Matrix& m);
  friend Matrix operator*<TD>(const Matrix& m, TD f);
  friend Matrix operator*<TD>(Matrix<TD>&& m, TD f);
};

template <typename TD>
std::ostream& operator<<(std::ostream &os, const Matrix<TD>& m)
{
  for(auto v : m.m_data) os << v << " ";
  os << "\n";
  return os;
}

template <typename TD>
Matrix<TD> operator*(const Matrix<TD>& m, TD f)
{
  LOG("copy(m,f)");
  Matrix<TD> mr(m);
  for(auto & v : mr.m_data) v *= f;

  LOG("mr") << mr;
  // return std::move(mr);    // this prevent RVO(copy elision).
  return mr;
}

template <typename TD>
Matrix<TD> operator*(TD f, const Matrix<TD>& m)
{
  LOG("copy(f,m)");
  return operator*(m, f);
}

template <typename TD>
Matrix<TD> operator*(Matrix<TD>&& m, TD f)
{
  LOG("move(m,f)");
  Matrix<TD> mr(std::move(m));
  for(auto & v : mr.m_data) v *= f;

  LOG("mr") << mr;
  // return std::move(mr);    // this prevent RVO(copy elision).
  return mr;
}

template <typename TD>
Matrix<TD> operator*(TD f, Matrix<TD>&& m)
{
  LOG("move(f,m)");
  return operator*(m, f);
}

int main()
{
  using DMatrix = Matrix<double>;

  DMatrix m1 {1., 2., 3.};
  DMatrix m2 = {{1., 2., 3.}, {4., 5., 6.}};
  LOG("m1, m2") << m1 << m2;

  DMatrix m3 = m1;            // copy construct
  LOG("m3, m1") << m3 << m1;

  DMatrix m4;                 // default construct
  m4 = m2;                    // copy assign
  LOG("m4, m2") << m4 << m2;

  m3 = m1 * 2. * 3. * 5.;
  LOG("m3") << m3;
  DMatrix m5 = m1 * 2. * 3. * 5.;
  LOG("m5") << m5;

  m4 = 2. * 3. * m1 * 5.;
  LOG("m4") << m4;
  DMatrix m6 = 2. * 3. * m1 * 5.;
  LOG("m6") << m6;

  DMatrix m7;                 // default construct
  m7 = std::move(m1);         // move assign
  LOG("m7") << m7;
  LOG("m1") << m1;

  DMatrix m8(std::move(m2));  // move construct
  LOG("m8") << m8;
  LOG("m2") << m2;
}

2016/07/02

c++11에서 병렬 연산


Caffe 소스를 보니까 GPU 연산은 병렬인데 CPU 연산은 병렬로 처리하지 않고 있어서 c++에서 multi-core를 사용하는 방법에 대해 호기심이 생겼다. 물론 multi-thread를 사용하면 된다는 건 아는데 CUDA에서 GPU를 사용해서 for loop을 쉽게 병렬로 처리하듯이 multi-core CPU에 대해서도 쉽게 처리할 수 있는 방법이 없나 하는 것이다. Open MP와 같은 오픈소스를 사용해도 되겠지만, 표준 c++ 함수를 사용해서 구현할 수 있으리란 생각이 들었다.

사실, c++ 언어 표준 자체도 c++17까지 가고 있는데 c++11 조차도 그 동안 꺼려왔던게 사실이다. 시스템간 이식성 문제 때문에 오픈 소스들도 c++11을 적극적으로 사용하기 보다는 boost 같은 오픈소스 라이브러리를 사용하는 경우가 많다. 5년 쯤 됐으니 최소한 c++11정도는 사용해도 될 시점이 아닌가 싶기도 하다. 병렬연산에 대해 좀 찾아 보면서 c++11에 쓸만한 기능이 참 많은데 그 간에 활용을 안해왔다는 느낌이 든다. python 언어도 사정이 비슷한데 Deep Learning 오픈 소스들을 돌려 보면 python 2.x에서는 잘 돌아가는데 python 3.x에서는 삐걱대는 경우가 많더라. 포팅하는 것이 어려운 건 아니지만 시간을 들여야 한다.

아무튼 여기서는 multi-core 시스템에서 c++11기반의 multi-thread를 활용해서 병렬연산을 하는 방법에 대해 간단히 정리한다. Deep learning에서 기본적으로 수많은 matrix와 vector data 계산을 해야 하는데 multi-core를 활용하지 않을 이유가 없기 때문이다. 물론 multi-core CPU 연산 보다는 GPGPU 연산을 하는게 훨씬 빠르긴 하다. 하지만 Mobile 기기를 비롯한 실제 응용 환경에서는 GPU 연산을 하기 어렵기 때문에 CPU 기반의 병렬 연산도 당분간은 포기하기 어려울 것이다. Caffe나 Tensorflow 같은 오픈 소스 Deep Leaning Framework에서 CPU 버전과 GPU 버전이 공존할 수 밖에 없는 이유이기도 하다.

for-loop의 문제

Deep Learning과 같이 선형대수를 사용해야 하는 경우 수많은 계산을 for-loop에 의지해야 한다. 단순히 m x n matrix X에 대해서 각 인자들의 pow 값을 구하는 아래의 예와 같이 말이다. 대개는 두번째 예처럼 matrix를 vector로 변환해서 1차원 배열로 처리한다. BLAS 라이브러리들이 matrix를 다루는 방법이기도 하다.
for(i = 0; i < m; ++i) {
  for(j = 0; j < n; ++j) {
    Y[i][j] = pow(X[i][j], 2.0);
  }
}

for( i= 0; i < m*n; ++i) Y[i] = pow(X[i], 2.0);
이 글에서 다루려는 것은 multi-thread를 사용해서 위의 놈을 아래와 비슷하게 동작하도록  하려는 것이다. 물론 아래의 코드는 희망사항일 뿐이다. Open MP에서는 pragma directive를 사용해서 for가 parallel로 동작하도록 하더라.
parallel_for(i = 0; i < m*n; ++i) Y[i] = pow(X[i], 2.0);
아무튼, CPU core 수가 4개 라면 parallel_for가 최소한 4개의 thread를 사용해서 병렬 연산을 하도록 하려는 것이다.

사실, 해결 방법은 간단하다. m*n/(core 수 = thread 수) 만큼씩 배열을 쪼개서 계산을 하는 것이다. 이 경우 두 가지 방법을 생각해 볼 수 있는데 하나는 처음부터 쪼개서 thread를 할당하는 방법과 divide & conquer algorithm을 이용해서 쪼개면서 thread를 할당하는 방법이다.

CPU core 수

일단, c++11을 이용해 구현에 사용할 header 파일 들은 아래의 4개이다.
#include <thread>
#include <future>
#include <vector>
#include <functional>
CPU core 수는 아래와 같이 알아낼 수 있다.
const unsigned int HOST_NUM_THREADS = std::thread::hardware_concurrency();
배열을 쪼개서 thread에  균등 할당하기

실제 구현 방법은 여러가지가 있을 수 있겠으나, 여기서는 Core수만큼 Thread를 생성해서 최대 균등 할당량만큼씩 할당해 준다. 여분의 할당량이 있으면 main thread가 처리한다. 사실, c++11부터 도입된 lambda 함수를 과용하는 측면이 있다.
template<typename TF>
void parallel_for0(unsigned int begin, unsigned int end, const TF& func)
{
  auto length = end - begin;
  if(!length) return;

  auto f = [&func](unsigned int bs, unsigned int be) 
            { for(unsigned int i = bs; i < be; ++i) func(i); };

  unsigned int nthreads = HOST_NUM_THREADS;
  auto const blockSize = (end - begin) / nthreads;
  if(blockSize && (length % nthreads)) ++nthreads;
  std::vector<std::future void>> futures;
  unsigned int blockStart = begin;

  if(blockSize) {
    for(unsigned int i = 0; i < (nthreads - 1); ++i) {
      unsigned int blockEnd = blockStart + blockSize;
      futures.push_back(std::move(std::async(std::launch::async,
        [blockStart, blockEnd, &f]() { f(blockStart, blockEnd); })));
      blockStart = blockEnd;
    }
  }
  f(blockStart, end);
  for(auto & future : futures) future.wait();
}
Divide & Conquer Algorithms 적용

전체 배열 size를 반씩 나눠서 recursive하게 자신을 호출하면서 새로운 thread에 할당해 주고(divide), 각 thread가 자신의 할당량이 최대 균등 할당량보다 작아지면 실제 작업을 마치고 빠져나오게 된다(conquer).
template<typename TF>
void parallel_for(unsigned int begin, unsigned int end, const TF& func)
{
  auto length = end - begin;
  if(length <= 0) return;

  static auto const blockSize = length/HOST_NUM_THREADS;
  if(!blockSize || length <= blockSize) {
    for(unsigned int i = begin; i < end; ++i) func(i);
    return;
  }
 
  unsigned int mid = begin + length/2;
  auto future = std::async(std::launch::async, parallel<TF>, mid, end, func);
  parallel(begin, mid, func);
  future.get();
}
parallel_for 테스트
#include <iostream>
#include <chrono>
#include <ctime>
위의 헤더 파일이 필요하다.
std::vector<double> A(1000000, 1.0);
auto start = std::chrono::steady_clock::now();
//parallel_for0(0, m*k, [&](int i) { C[i] = ::pow(A[i], 2); });
parallel_for(0, m*k, [&](int i) { C[i] = ::pow(A[i], 2); });
auto end = std::chrono::steady_clock::now();
auto diff = end - start;
std::cout << "\nTime elapsed: "
          << std::chrono::duration<double, std::milli>(diff).count()
          << " ms" << "\n"; 
위의 두가지 버전의 parallel_for의 성능은 Divide & Conquer 방식이 쬐금 낫더라. 하지만 for-loop을 사용하는 것보다는 parallel_for를 사용하는 것이 당연히 빠르다. 물론 data size가 작으면 별 차이를 느끼지 못할 수도 있다.

Divide & Conquer를 이용한 parallel_sum 구현

위에서는 배열에 대해서만 언급했지만 c++11의 STL Container들로 위의 개념을 확장시킬 수 있을 것이다. Container에 대해서 iterator를 이용한 parallel_each와 parallel_sum을 생각해 볼 수 있는데, parallel_sum이 더 복잡하므로 parallel_sum만 다룬다. 두 가지 인터페이스가 있는데 하나는 iterator를 사용하는 방식이고, 하나는 container instance를 사용하는 방식이다. parallel_for에서는 int index를 사용했는데 여기서는 iterator pointer를 사용했다는 점만 다르고 Divide & Conquer logic은 동일하다.
template<typename TI, typename TR, typename TF>
TR parallel_sum(TI begin, TI end, TR init, const TF& func)
{
  auto length = end - begin;
  if(length <= 0) return init;
  static auto const blockSize = length/HOST_NUM_THREADS;
  TR result = 0;
  if(!blockSize || length <= blockSize) {
    for(TI it = begin; it < end; ++it) func(*it, result);
    return result;
  }
  TI mid = begin + length/2;
  auto future = std::async(std::launch::async, 
                           parallel_sum&<TI, TR, TF>, mid, end, init, func);
  TR sum = parallel_sum(begin, mid, init, func);
  return (sum + future.get());
}

template<typename TCon, typename TR, typename TF>
TR parallel_sum(TCon con, TR init, const TF& func)
{
  return parallel_sum(con.begin(), con.end(), init, func);
}
얼핏, parallel_for를 사용해서 for-loop에서 sum을 구하듯이 하면 되지 않겠느냐 생각할 수도 있겠지만 sum 자체가 shared variable이므로 parallel_for를 사용하려면 lock을 사용해야 한다. 이는 성능 저하를 의미하기 때문에 수학 계산에서는 lock-free 병렬 계산을 해야만 한다.

parallel_sum 사용
std::vector<double> A(1000000, 1.0);
double sum = 0;
//sum = parallel_sum(A.begin(), A.end(), sum, [](double item, double& r)
//        { r += 3*item + 2; });
sum = parallel_sum(A, sum, [](double item, double& r) { r += 3*item + 2; });
맺음말

흠, 사실 c++11만 해도 깊게 들어가면 배워야 할게 넘 많다. atomic 개념까지 들어가면 lock-free parallel 연산에 대한 도사가 될지도 모른다. 실제로 최근의 NVIDIA CUDA가 성능을 높이기 위해 이걸 파고 들고 있는 느낌이다. 언젠가는 모바일기기까지도 병렬 연산에 GPGPU를 사용하는 날이 올지 모른다.

아무튼, 병렬 계산에 관한  한 GPU를 사용할 수 있으면 GPU를 사용하는 것이 젤로 좋고, 차선책으로써 multi-core CPU를 사용하면 좀 낫다는 게 이 글의 요지다. 물론 데이터 량이나 계산 량이 많을 경우의 얘기다. 참고로, GPU와 CPU 연산을 함에 있어서 배열 size가 1만개 정도에서는 CPU와 GPU간의 성능차이가 별로 나지 않는다. 더구나 c++11의 random number generator와 cuRand를 비교하면 난수 갯수가 1만개 이내 일때는 CPU가 훨씬 빠르더라. 그런데 갯수가 1백만개를 넘어가면 GPU가 훨씬 빠르다.

multi-thread를 사용함에 있어서 CPU core 수와 같거나 조금 많은 정도가 최적의 성능을 준다. Divide & Conquer 방식에서 할당량(blockSize)을 줄이면 thread 수가 늘어나게 되는데 성능이 저하된다.


참고

구 버전의 SyntaxHighlighter를 사용했었는데 구글 site 들이 https 접속 방식으로 바뀌면서 블로그 Template의 http 링크들이 모두 무용지물이 됐다는 걸 알았다. 이 참에 다시 새 버전으로 바꾸면서 google drive에 Javascript를 올려 놓으면 된다는 글을 보게 돼서 신 버전으로 다시 회귀했다. 

2016/06/24

CodeLite에서 cmake plugin 사용


CodeLite은 c++/wxWidgets 기반의 cross-platform 오픈소스 IDE 개발 Tool이다. Code::Blocks가 요즘에도 계속 개발되고 있는지 모르겠는데 중간에 개발이 중단됐었다. Code::Blocks와 유사하면서도 현재까지 명맥을 이어온 몇 안되는 훌륭한 오픈소스 c++ 개발 툴 중의 하나이다. 물론 다른 IDE들 처럼 PHP나 Python 등 다른 언어 개발 Tool로도 사용할 수 있다. Qt Creator가 Qt 기반 프로젝트에 최적화되어 Upgrade 되는데 비해 상대적으로 CodeLite는 wxWidgets 기반 프로젝트에 최적화 되어 발전해 왔다. 하지만 어떤 면에서는 CodeLite가 범용 c++ 프로젝트 개발 Tool로써 더 적합해 보이기도 한다. 머 둘다 훌륭한 개발 Tool이기 때문에 호불호를 따지는게 큰 의미가 있는 건 아니다. 참고로, 우분투 16.04에서 패키지로도 설치할 수 있는데 CodeLite 9.1.0 버전이 설치된다.

clang 기반의 code completion, function/type-info tooltip, debugging, code navigation, code refactoring, reference search, UI theme 같은 기본 기능 외에 막강한 plugin들이 있는데 valgrind, source code formatter, qmake, git, svn, call graph, wxFormbuilder 등의  plugin 외에 cscope과 cmake plugin은 꽤 실용적인 plugin 이다.

여기서는 최근의 Open Source들이 거의 cmake 기반으로 개발되고 있어서 CodeLite에서 cmake plugin을 사용해서 cmake 기반의 Open Source 프로젝트를 import해서 사용하는 방법에 대해서 정리한다. GNU autoconf/automake를 사용하고 있는 오픈소스 프로젝트들은 오래된 프로젝트이거나 한물간 프로젝트일 수도 있는 것이 현실이기 때문이다. cmake plugin은 CodeLite 최신 버전에서 기능이 강화되고 있기도 해서 다른 plugin에 비해 설정이 다소 복잡하다. Qt Creator에서도 cmake 프로젝트를 import할 수 있지만 개인적으로 CodeLite이 소스 분석에 더 도움이 되는 듯하다.

Open Source 프로젝트 import

Workspace Context Menu에서 [Create New Project] 선택 후 나타나는 [New Project Wizard]에서, [Template] > [Custom Makefile] > [New Project]의  [Project name:]을 import 하려는 폴더 명으로 설정하고, [Project path:]는 [...] 버튼을 선택해서 import 하려는 폴더 위치를 찾아서 지정해 주면 된다. 이때 [Create the project under a separate directory] checkbox는 unchecked 상태라야 한다. 이제 [Next >] 버튼을 선택하면 project toolchain을 설정할 수 있는데 [Build System:]에 기본으로 [CMake]로 설정되어 있다. [Finish] 버튼을 선택해서 [New Project Wizard]를 마무리한다.

이제 [Workspace Tree]에 새로운 프로젝트가 추가됐는데 이름만 올라가 있다. 실제 파일들을 import하기 위해서, 새로 생성된 프로젝트 선택 후 마우스 우클릭시 나타나는 Project Context Menu 들 중에 [Import Files From Directory...]를 선택하면 [Import Files] 창이 뜨고 import할 폴더 tree를 보여주고 왼쪽에 checkbox 들이 있는데 tree의 최상위 폴더를 선택하면 하위 폴더들이 모두 선택된다. 창 밑에 확장자들을 보여주는데 이 놈들만 import 한다. [OK] 버튼을 누르면 Workspace에 새로 생성한 프로젝트 밑에 import한 폴더와 파일들을 tree로 확인할 수 있다.

Active Project 설정

Workspace 밑에 여러 개의 프로젝트가 있다면 새로 생성한 프로젝트가 Active Project가 되도록 설정해 주어야 한다. Workspace 탭의 tree에서 새로 import한 프로젝트를 선택해서 Project Context Menu 들 중에 [Make Active(double click)]을 선택하거나, 그냥 프로젝트가 선택된 상태에서 마우스 double click을 해 주면 된다.

CMake Plugin 설정

import한 cmake 기반의 Open Source 프로젝트를 codelite에서 build 하려면 cmake plugin 설정을 해 주어야 한다. 설정은 Project Settings 창에서 해주면 되는데 Workspace tree에서 새로 생성한 프로젝트 선택 후 Project Context Menu 들 중에 [Settings...] 메뉴를 선택하면 된다.

참고로, 아래 설명에서 입력할 실제 값들은 => 후의 [ ] 괄호 안의 것들이다.

1. [Project Settings] 창 > [Customize] > [Custom Build] tab 에서,
1.1 [Working Directory:] => [$(ProjectPath)/build-$(ConfigurationName)] 설정
1.2 [New] 버튼 > [Build Target] 창에서 [Target Name:] => [CMake] 로 하고, [Command:] => [cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=1] 설정
설정 완료 후, [Apply] 버튼 선택,

2. [Project Settings] 창 > [General] tab 선택 후,
2.1 [Build] > [Intermediate Folder] => [$(ProjectPath)/build-$(ConfigurationName)] 설정
2.2 [Execution] > [Executable to Run/Debug] => [$(ProjectPath)/build-$(ConfigurationName)/$(ProjectName)] 설정
설정 완료 후, [Apply] 버튼 > [OK] 버튼 선택하면 설정이 끝난다.

사실 위의 설정은 [Debug] 모드에 대한 것이었고 [Release] 모드에서도 위의 1~2의 과정으로 설정해 주어야 한다. [Release] 모드는 Workspace 탭에서 위쪽에 프로젝트명 옆에 [Debug] 선택 상자를 선택하면 나타난다.

다른 설정은 동일 한데 1.2에서 [Command:] => [cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=1]로 설정해 주면 된다.

CMake Plugin 실행 및 Project Build

이 부분이 좀 직관적이지 않을 수 있는데 CodeLite 최신 버전에서는 프로젝트 선택후 Project Context Menu 들 중에 [Run CMake]가 새로 생겼다. 그런데 이걸 사용하면 안된다. Custom Project로 CMake 프로젝트를 import 한 것이기 때문에 Context Menu 들 중에 [Custom Targets...] > [CMake] 메뉴를 선택해야 한다. 이제 Makefile을 생성하게 되는데 CodeLite의 아랫쪽에 [Output View] > [Build] 탭에 에러가 0개이면 성공한 것이다.

프로젝트를 build 하려면 다시 Project Context Menu 들 중에 [Build]를 선택하면 된다. 성공적으로 build가 됐다면 <Ctrl>+<F5> 단축키나 CodeLite Main Menu의 [Build] > [Run]을 선택해서 실행해 볼 수 있다.

참고 사항

Codelite의 CMake Plugin은 최근에 기능이 강화되고 있는 중이라서 약간의 버그도 있고 설정이 다소 혼란스럽고 직관적이지 않은 면이 있는데, 향후 설정 방법이 달라질 수도 있을 것이다.

2016/05/17

Ubuntu 16.04에 Caffe와 CUDA-7.5 설치


(2016/05/20 추가)

Theano에서 CUDA-7.5 사용

CUDA를 설치한 김에 Theano에서도 GPU를 사용할 수 있게 설정해 보았다. theano-0.8.2가 최신 버전인데 Anaconda에서 theano 패키지를 설치하니까 버전 0.7이었다. theano-0.8.2를 우분투 16.04에 설치하는 방법은 여기를 참고하면 되는데 여기서도 gcc-5.3.1을 구 버전으로 대체하도록 권장하고 있더라. 지금까지 몇가지 시험해 본 바로는 gcc를 새로 설치하지 않아도 CUDA-7.5는 잘 동작하고 있다. 물론, Caffe와 theano-0.7, Python-3.5.1 환경에서만 테스트해 본 것이긴 하다.

아나콘다에서 theano 패키지는 아래와 같이 설치할 수 있다.

$ conda install theano

CUDA-7.5는 아래 글에서 설치했기 때문에 theano가 CUDA를 사용하도록 ~/.theanorc 파일에 아래 내용과 같이 설정해 주기만 하면 된다.

$ gedit ~/.theanorc

[cuda]
root = /usr/local/cuda

[global]
device = gpu
floatX = float32

[nvcc]
flags = -D_FORCE_INLINES

위에서 device=cpu로 바꾸면 theano가 CPU를 사용한다.

참고로, theano 테스트는 아래와 같이 python을 실행해서 theano.test()를 돌려 보라고 하는데 device가 gpu로 되어 있으면 무수한 오류가 발생하더라. cpu로 하면 그나마 나은데 그래도 오류가 좀 생기고 테스트 시간도 엄청 오래 걸려서 중간에 <Ctrl>+<c>로 관뒀다.

$ python
>>> import theano
>>> theano.test()

그런데, theano를 cpu 환경에서 사용하다가 gpu로 처음 설정해서 사용하는 경우에는 cache 폴더를 지우는 것이 좋다. CUDA 컴파일 오류가 생길 수 있다.

$ rm -rf ~/.theano

theano.test()를 돌린 경우에도 엄청난 cache가 쌓여 있을 수 있는데 원래 cache만 지우는 방법은 아래 명령과 같다.

$ theano-cache clear

결론적으로, theano에서 GPU 테스트는 여기의 소스를 돌려 보면 된다. 내 iMac의 CPU는 QuadCore i5-3470 3.20GHz이고, GPU는 NVIDIA GeForce GTX 675MX인데 첫번째 소스를 돌려 봤더니 GPU가 무려 50배 이상 빠른 결과를 보였다(0.5초 : 28초). 물론 어떤 연산을 하느냐에 따라 성능이 달라지겠지만 GPU 연산이 이렇게나 빠를 줄은 몰랐었다.

맺음말

인간들이 python으로 빨리 구현해 볼 수 있어서 python을 애용하는 듯 한데 python은 근본적으로 c/c++ 보다 느리고, process에서는 multi-core를 사용할 수 있지만 thread는 multi-core를 사용할 수 없는 한계가 있다. 더구나, python은 언어 자체가 2.x와 3.x 버전이 호환이 안되고 아직까지도 과도기이기 때문에 처음 python을 접하는 이들에게는 혼란을 줄 수도 있다. 물론, python 언어의 간결함은 성능보다 더 중요한 장점일 수 있다.

아무튼 Tensorflow도 그렇고 theano도 그렇고 python을 사용해서 모델을 빨리 만들고, 정확도가 좀 떨어지더라도 금방 결과를 볼 수 있다는 것이 생산성 측면에서는 큰 도움이 되는 것이 사실이다. 사용자들은 python만 알아도 되겠지만 backend에서는 c/c++을 여전히 사용하고 있다는 점을 간과해선 안될 것이다.

----------------------------

심심풀이로  Berkeley Vision and Learning Center(BVLC)에서 만든 c++ 기반의 오픈소스 Deep Learning Framework인 Caffe를 우분투 16.04에 설치해 보았다. 구글의 Tensorflow도 CPU only 옵션으로 설치해 봤는데 우분투 16.04에 그럭저럭 설치된다. NVIDIA CUDA는 Caffe 설치시 필요하기 때문에 설치했다. Tensorflow에서도 GPU를 연산에 사용하려면 CUDA를 설치해서 소스로 build 해 주어야 하는데 Caffe에 더 관심이 있어서 Tensorflow는 Anaconda 환경에서 binary 버전으로 설치했다. CUDA를 제목에 붙여 놓은 이유는 우분투 16.04에서 cuda-7.5 설치가 안된다는 글들이 보여서 약간의 도움을 주려는 의도이다.

Caffe 설치 절차는 여기를 따라가면 되는데 우분투에서는 이 곳을 참조해도 된다. 우선, 다음과 같이 Caffe framwork에서 필수로 사용하는 놈들과 옵션인 놈들이 있다. 옵션인 놈들 중에서도 OpenCV를 제외하고는 필수라고 보는게 좋을 듯하다. 옵션이라는 놈들을 설치하지 않으면 예제를 돌려 볼 수 없더라. 추가 옵션이 하나 더 있는데 NVIDIA cuDNN이다. 이놈은 NVIDIA site에 등록해야 받을 수 있는데 귀찮아서 제외했다.
  • NVIDIA CUDA 6.5+
  • BLAS: Atlas(기본) 또는 MKL 또는 OpenBLAS
  • Boost 1.55+
  • protobuf, glog, gflags, hdf5
  • (옵션) OpenCV, leveldb, snappy, lmdb
  • (옵션) NVIDIA cuDNN
NVIDIA CUDA/cuDNN을 제외하면 나머지는 모두 우분투 16.04의 패키지로 설치할 수 있다.

1. NVIDIA CUDA 7.5.18 설치

NVIDIA에서 CUDA 7.5를 내려 받아서 설치했다. deb 패키지로 내려 받거나 run 파일을 내려 받을 수 있는데, cuda_7.5.18_linux.run 파일을 내려 받아서 설치했다. deb으로는 안해 봐서 어느 방법이 좋은 지는 모르겠다. 이전 글의 NVIDIA 드라이버 설치 방법을 참고해서 복구 모드나 콘솔 모드로 부팅해서 실행하면 된다.

다만, 설치 문서를 보면 우분투 16.04의 gcc 버전이 5.3.1인데 gcc 4.9 이전 버전까지만 지원한다고 되어 있고 실제로 설치가 안된다. 하지만 무시하고 설치할 수 있는 --override 옵션을 보여 준다. 즉, 아래와 같이 설치하면 된다.

$ sudo sh cuda_7.5.18_linux.run --silent --override

그런데, 주의할 점은 이 놈이 NVIDIA 드라이버를 포함하고 있어서 기존에 설치된 드라이버를 대체해 버린다. iMac에서는 우려했던 대로 CUDA를 설치하고 나서 GUI로 로그인이 안되는 문제가 발생했다. 이전 글에 있는 대로 복구모드에서 NVIDIA-Linux-x86_64-352.63.run으로 드라이버를 다시 설치했다. 드라이버가 CUDA에 같이 들어 있는 이유는 아마 드라이버와 CUDA 라이브러리 버전이 일치해야 하기 때문일 텐데, 352.63이 구 버전이긴 해도 1년도 안된 것이라 호환성에 문제가 없으리라 예상했고 잘 돌아 가더라.

재부팅 후에 ~/.bashrc 파일 끝에 CUDA path를 아래 두 줄과 같이 잡아 준다.

$ gedit ~/.bashrc

export PATH=/usr/local/cuda-7.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-7.5/lib64:$LD_LIBRARY_PATH

설치는 끝났고 잘 동작하는지 위의 설치 문서대로 테스트 해 봐야 한다. 터미널을 새로 열어서 Sample 프로그램을 build 하고 실행해 봐야 한다. 단, 그 전에 아래 명령과 같이 samples 이하 폴더들에게 사용자(aaa라 가정) 접근 권한을 부여해야 한다.

$ sudo chown -R aaa:aaa /usr/local/cuda/samples

$ cd /usr/local/cuda/samples/5_Simulations/nbody
$ make

make를 하면 우분투 16.04의 gcc 버전이 gcc 4.9 초과 버전이라 build가 안된다. 아래와 같이 /usr/local/cuda/include/host_config.h 파일에서 gcc 버전 check하는 부분을 코멘트 처리해 버린다.

$ sudo vi /usr/local/cuda/include/host_config.h
......
/* AAA
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 9)

#error -- unsupported GNU version! gcc versions later than 4.9 are not supported!

#endif
*/
/* __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 9) */
......

이제 다시 make 하면 잘 된다.

$ make
$ ./nbody

nbody를 실행해서 불꽃놀이 창이 뜨면 CUDA가 제대로 동작하는 것이다.

2. Caffe에 필요한 우분투 패키지 설치

NVIDIA CUDA/cuDNN을 제외한 나머지는 아래와 같이 설치하면 된다.

$ sudo apt install libatlas-base-dev

$ sudo apt install --no-install-recommends libboost-all-dev

$ sudo apt install libprotobuf-dev protobuf-compiler libgoogle-glog-dev libgflags-dev libhdf5-serial-dev

$ sudo apt install libopencv-dev libleveldb-dev libsnappy-dev liblmdb-dev

3. Caffe build 및 설치

Caffe 소스는 github에서 받을 수 있다. git가 설치되어 있지 않다면 git를 먼저 설치해야 한다.

$ sudo apt install git
$ git clone https://github.com/BVLC/caffe

Caffe를 build하기에 앞서 Makefile.config 파일을 생성해서 수정해야 한다.
$ cd ./caffe
$ cp Makefile.config.example Makefile.config
$ gedit Makefile.config

OpenCV는 선택 사항인데 예제를 돌려 보려면 leveldb와 lmdb가 필요하다.

USE_OPENCV := 0
USE_LEVELDB := 1
USE_LMDB := 1

이외에도 Python API를 사용하려면 Path를 제대로 잡아 주어야 한다. 이제 Caffe를 make로 build 하는데 CPU core 갯수가 2개 이상이라면 -j<core 수> 옵션을 주는 것이 좋다. 

$ make all -j4
역시 여기서도 오류가 발생했는데 아래와 같이 NVIDIA 컴파일러에서 memcpy 함수를 못찾는 오류다. 소스를 보면 inline 함수를 사용해야 하는 상황인데 이걸 못 찾고 있는 거였다.

NVCC src/caffe/util/math_functions.cu
/usr/include/string.h: In function ‘void* __mempcpy_inline(void*, const void*, size_t)’:
/usr/include/string.h:652:42: error: ‘memcpy’ was not declared in this scope
   return (char *) memcpy (__dest, __src, __n) + __n;
                                          ^
Makefile:585: recipe for target '.build_release/cuda/src/caffe/util/math_functions.o' failed
make: *** [.build_release/cuda/src/caffe/util/math_functions.o] Error 1

Makefile을 수정해서 아래와 같이 NVCC가 inline 함수를 사용하도록 해준다.
$ gedit Makefile

NVCCFLAGS += -D_FORCE_INLINES -ccbin=$(CXX) -Xcompiler -fPIC $(COMMON_FLAGS)

참고로 cmake로 build할 경우에는 CMakeLists.txt 파일의 맨앞에 아래 한 줄을 넣어 주면 된다.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORCE_INLINES")

다시 make 하고 test를 진행한다.

$ make all -j4
$ make test -j4
$ make runtest -j4

runtest에서 다시 오류가 발생했는데 아래와 같이 libhdf5 라이브러리를 못찾겠다는 오류다.

.build_release/tools/caffe: error while loading shared libraries: libhdf5_hl.so.10: cannot open shared object file: No such file or directory
Makefile:523: recipe for target 'runtest' failed
make: *** [runtest] Error 127

실제로 libhdf5_hl.so.10과 libhdf5.so.10 라이브러리는 libhdf5-serial-dev 패키지를 설치할 때 설치되지 않았더라. 소스로 부터 다시 build 하는 게 정석일 수도 있는데 직감으로 아래와 같이 해결했다.

$ cd /usr/lib/x86_64-linux-gnu
$ sudo ln -s libhdf5_serial_hl.so.10 libhdf5_hl.so.10
$ sudo ln -s libhdf5_serial.so.10 libhdf5.so.10

다시 runtest를 돌리니 잘 되더라.

$ make runtest -j4

4. Caffe Test

Caffe 설치 절차에 있는 내용에 따라 여기를 참고하여 MNIST 예제를 돌려 볼 필요가 있다

$ cd $CAFFE_ROOT
$ ./data/mnist/get_mnist.sh
$ ./examples/mnist/create_mnist.sh
$ ./examples/mnist/train_lenet.sh

참고로, 내 iMac에서 기본 GPU 사용 옵션으로 5분 10초 걸렸다(CPU 사용시 13분 47초 - 2.7배 GPU가 빠름). 학습횟수는 기본 설정값인 10,000회였고, 정확도는 99.1%였다.

나머지 설명은 Caffe 홈페이지에 잘 되어 있으니 이만 마쳐야 겠다.

2016/04/23

Ubuntu 16.04 LTS 설치 소감


(2016/07/15 참고사항 추가)

어제 software-updater와 upgrade 명령으로 daily update를 했더니 gcc가 v5.3.2에서 v5.4.0으로, 커널은 v4.4.0-28에서 v4.4.0-31로 upgrade 됐다. 문제는 커널 업그레이드 과정에서 NVIDIA 드라이버 v352.63을 사용중이었는데 커널 모듈 컴파일 에러가 발생했다.

새로운 커널을 계속 사용하려면 NVIDIA 드라이버를 교체하는 수 밖에 없는데 그 과정에서 아래 NVIDIA 관련 삽질을 또다시 반복하는 사태가 벌어졌다. 아무튼 아래의 NVIDIA 설치 방법을 써먹을 기회(?)가 생겼고, 최신 NVIDIA 드라이버 v367.27을 설치해서 문제가 해결됐다. NVIDIA 드라이버 설치 후 로그인이 되지 않는 문제까지 생겨서 dconf db를 지우고 데스크탑 설정을 다시 해주어야만 했다.

그런데 이것이 리눅스 커널 Secure Boot와 관련된 문제인지 아닌지는 확실하지 않다. iMac에서 발생한 상황인지라 Mac은 기본적으로 Secure Boot를 사용하지 않기 때문이다. 일반 PC인 경우에는 BIOS/UEFI firmware Setup에서 Secure Boot를 끄면 문제가 안생기는 것인지 모르겠다.

참고로, Secure Boot를  disable 시키지 않고 부팅하려면 인증된 커널을 사용하면 되는데 여기를 참고하거나 아래 URL 참고... 그런데 너무 복잡하고 커널 업그레이드 될 때마다 인증을 해 주어야 하는 건 아무래도 무리일듯...

https://wiki.ubuntu.com/SecurityTeam/SecureBoot

그리고, 참고 삼아 VirtualBox도 업그레이드 됐는데 홈페이지 갔더니 v5.1도 새로 나왔더라. 빠르고 좋대서 5.1로 설치했다. 아직 제대로 안써봐서 머가 좋은지는...

추가하는 김에, Qt 5.6부터는 수정된 ibus immodule이 탑재되어 있어서 ibus를 기본 입력기로 사용해도 Qt Creator나 Qt App에서 한글입력이 잘된다.

-----------------------------------------------------------------------------

어제 따끈따끈한 우분투 16.04 LTS가 떳길래 반가운 마음에 우분투 설치 iso 이미지를 내려 받아
서 iMac에 설치하였다. LTS 버전이라 Clean Install을 감행했다. HDD의 iso 이미지로 설치하니까 10분도 안걸려 설치가 끝났다. 머 우분투 설치 자체야 쉽지만 예전에 쓰던 상태로 다시 만들기 위해 소프트웨어들을 다시 설치하고 모든 설정을 다시 해 주는게 보통 일은 아니다. 그간에 사용하던게 왜 이리 많은지 환경을 맞추는데만 2시간 넘게 소요된 것 같다.

그런데 문제는 우분투에 기본 탑재된 NVIDIA Proprietary driver(v361.42)를 설치하고 재부팅한 후부터 옛날에 경험했던 삽질들을 다시 하느라 엄청난 시간을 허비해야 했다. 공포의 Black(또는 Blank) Screen 문제가 발생한 것이다. 이 놈이 부팅할 때마다 서너 번에 한 번은 제대로 동작하는데 대부분의 경우에 Black Screen 문제가 생긴다. 우분투 15.04나 15.10에서는 기본 탑재된 NVIDIA 드라이버들이 아무 문제 없었기 때문에 당연히 아무 문제가 없으리라 예상했는데 리눅스에 항상 아쉬운 부분으로 남는다. 물론 nouveau 드라이버는 깔금하게 동작한다.

아무튼 지금은 모든 문제가 해결됐다. 우분투가 전반적으로 빨라진 느낌도 든다. 나중에 우분투 16.04를 설치하는 사람들에게 참고가 되길 바란다. Unity 8과 Mir는 사용할 수는 있지만 아직은 완성도가 떨어지는 모양이다. 기본적으로 NVIDIA 환경에서 동작하지 않는다. 우분투 15.10에서 설치해 봤는데 nouveau 환경에서도 로그인 조차 안돼서 포기했다.

우분투 16.04 LTS의 새로운 것들

이 곳을 참고하거나 Release Note를 참고하면 우분투 16.04에서 달라진 것들을 쉽게 확인할 수 있다.

Linux kernel 4.4, Python 3.5, Golang 1.6, PHP 7, OpenSSH 7.2p2, Apt 1.2, LibreOffice 5.1, Firefox45 등을 비롯한 수많은 S/W가 upgrade 되었다. 참고로, 패키지 관리자인 Apt는 apt-get대신 apt 명령으로 사용할 수 있고, 새로운 기능들을 추가로 사용할 수 있다. 이 곳을 참고하면 주요 배포판의 패키지 명령들을 비교해서 기능을 쉽게 익힐 수 있다.

Unity 7.4 데스크탑 기능도 많이 안정화 됐고 기능도 강화됐는데, 아래의 명령으로 화면 왼쪽의 Launcher를 아랫쪽으로 옮길 수도 있다.

$ gsettings set com.canonical.Unity.Launcher launcher-position Bottom

그 동안에 Privacy 문제로 논란이 됐던 Dash에서의 On-line 검색은 기본 설정에서는 동작하지 않도록 바뀌었다. Overlay Scrollbar는 15.10부터 바뀐 것이다. [System Settings] > [Appearance]와 Unity Tweak Tool을 설치해서 함께 사용하면 대부분의 Unity 데스크탑 환경을 필요한 대로 설정할 수 있게 되었다. HUD의 단축키가 <Alt>로 되어 있어서 국내 사용자들이 한영키 설정시 혼란을 많이 겪었었는데 <Alt>+<Space> 키로 바뀐 것도 고무적이다. 이 곳을 참고하면 유니티 데스크탑 환경 설정에 도움이 된다.

무겁고 느려 터져서 논란이 됐던 Ubuntu Software Center도 Gnome Shell의 Software로 대체되었다. 제목은 Ubuntu Software가 됐고 아이콘이 그대로라 바뀐 줄 모를 수도 있다. 이와 함께 Gnome Shell의 Calendar도 일정관리를 위해 사용할 수 있고, On-line 계정과 연동하여 동기화할 수도 있다.

이외에도, Snap 패키징, LXD(LXC hypervisor), ZFS 파일시스템을 우분투에서 사용할 수 있다. 리눅스에서는 License와 특허 등의 문제로 ZFS 파일시스템 대신 Btrfs 개발에 힘을 실어 왔는데 우분투가 ZFS를 밀기로 한 것인지는 모르겠다. ZFS는 역사가 깊고 대규모 서버 환경에서 검증됐기 때문일 수도 있다. 참고로, Btrfs와 ZFS는 기능적으로는 유사하지만 설계부터 다르고, Btrfs가 리눅스 커널에 포함되어 있는데 반해 ZFS는 라이센스 문제 때문에 kernel module로만 사용할 수 있다.

AMD Radeon 그래픽 카드 사용자들은 당분간 우분투 16.04를 사용하지 말라는 경고도 있다. fglrx 대신 오픈소스 radeon과 amdgpu 드라이버가 개발 중인데 아직 성능이 그닥이란다.

NVIDIA 드라이버 재설치 방법

전에 올렸던 우분투 복구모드에서의 NVIDIA 드라이버 설치 방법으로는 nouveau 모듈이 로딩되는 것을 막지 못해서 NVIDIA 드라이버 설치 후에 로그인이 되지 않는 문제가 생겼다. 그래서 구글링해서 찾은 방법대로 성공했기에 다시 정리한다.

NVIDIA 홈페이지에 가보니 우분투 16.04에 기본 탑재된 v361.42가 최신 stable 버전이더라. 같은 놈이라서 문제가 생길 소지가 있어서(나중에 해봤더니 이 버전은 설치시 오류가 생기더라) 15.10에서 잘 동작했던 NVIDIA-Linux-x86_64-352.63.run을 내려 받아서 설치했다.

우분투 16.04에 기본 탑재된 NVIDIA 드라이버에 문제가 생겨 GUI를 쓸 수 없기 때문에 nouveau 드라이버로 복구하는 일 부터 다시 해야 했다. 참고로, iMac에서는 UEFI 모드에서는 NVIDIA 드라이버가 정상 동작하더라도 <Alt>+<Ctrl>+<F1> ~ <F6> 키로 console을 띄우면 Black Screen이라 복구모드 외에는 작업할 수 있는 방법이 없다. NVIDIA가 아직 지원하지 않는단다. 그런데 복구 모드에서는 Copy & Paste가 안되기 때문에 GUI 환경에서 작업하는게 좋다.

0. 우분투 복구 모드에서 파일 시스템 Check

나의 경우에는 NVIDIA가 동작하는지 확인하느라 재부팅을 몇 번이나 했는데 이 과정에서 Power 버튼으로 Hard 부팅을 해야 했기에 파일 시스템이 좀 깨져 있었다. 혹시 모르니 복구 모드로 부팅해서 fsck를 돌려 볼 필요가 있다.

1. 우분투 복구 모드에서 문제가 있는 NVIDIA 드라이버 제거

복구 모드에서 파일 시스템 check를 하면 자동으로 디스크 파티션을 rw 모드로 mount 해주는데, fsck를 돌리지 않았다면 수동으로 mount해 주어야 한다.

$ mount -o remount,rw /
$ mount -a

우분투 복구 모드 활용에서 다룬대로 복구 모드로 부팅한 후, 아래의 명령으로 NVIDIA 드라이버를 모두 제거한다.

$ apt-get remove --purge nvidia*

만약, 아래 3번에서 이미 blacklist 파일을 생성했었다면 nouveau 드라이버로 부팅하기 위해서, 파일을 삭제하거나 편집기로 아래와 같이 '#' 으로 comment 처리해야 한다.

$ nano /etc/modprobe.d/blacklist-nouveau.conf

#blacklist nouveau
#options nouveau modeset=0

$ reboot

2. 우분투로 정상 부팅 후 nouveau 드라이버 복구

우분투로 부팅하면 nouveau가 아직 복구된게 아니므로 GUI가 뜨긴 하지만 로그인이 안된다. 하지만 이 상태에서는 <Alt>+<Ctrl>+<F2> 키로 정상적인 console 로그인이 가능하다. 아래의 명령으로 필요한 패키지를 재설치 한다.

$ sudo service lightdm stop
$ sudo apt-get install --reinstall xserver-xorg-core libgl1-mesa-glx
$ sudo service lightdm start

참고로, 위의 명령이 우분투 16.04에서도 동작하긴 했지만 우분투 15.04이후 systemd를 사용하고 있기 때문에 아래와 같이 systemctl 명령이 올바른 것일 수 있다.

$ sudo systemctl lightdm.service stop

이제 재부팅하면 nouveau 드라이버로 로그인 할 수 있다. 그런데 만약 재부팅 후에도 로그인이 되지 않을 경우가 발생할 수 있는데(삽질하다 보니 발생하더라...) 아래와 같이 dconf database를 삭제하면 로그인 할 수 있다. 다만, 주의할 점은 dconf database를 삭제하면 Unity Desktop 설정을 모두 다시 해 주어야 한다.

$ mv ~/.config/dconf/user ~/.config/dconf/org.user
$ sudo reboot

3. 새로운 NVIDIA 드라이버 설치 후 nouveau 드라이버가 로딩되지 않도록 blacklist 파일 생성

$ sudo nano /etc/modprobe.d/blacklist-nouveau.conf

nouveau 모듈이 로딩되지 않도록 편집기로 아래의 두 줄을 넣어 저장한다.
blacklist nouveau
options nouveau modeset=0
blacklist lbm-nouveau
alias nouveau off
alias lbm-nouveau off

$ sudo nano /etc/modprobe.d/nouveau-kms.conf

커널의 nouveau가 동작하지 않도록 편집기로 아래의 내용을 넣어 저장한다.
options nouveau modeset=0

아래의 명령으로 수정한 blacklist가 제대로 동작하도록 initramfs를 update해 준다.
$ sudo update-initramfs -u

$ sudo reboot

4. 다시 우분투 복구 모드로 부팅 후, 새로운 NVIDIA 드라이버 설치

$ mount -o remount,rw /
$ mount -a

$ sh NVIDIA-Linux-x86_64-352.63.run
$ reboot

5. 재부팅 후 NVIDIA 드라이버 환경에서 정상 로그인이 되는지 확인

부팅 후 NVIDIA 버전은 아래 명령으로 확인할 수 있다.

$ nvidia-smi

참고 사항

위의 4번처럼 우분투 복구모드로 부팅하지 않고 부팅시 Grub 메뉴엔트리를 수정하여 콘솔 모드로 부팅해서 작업해도 된다. 기본 Ubuntu 부팅 메뉴가 선택된 상태에서 <e> 키를 누르면 Grub edit 모드로 진입하는데 커널 부팅 옵션(linux로 시작하는 부분)의 "quiet splash $vt_handoff" 부분을 지우고 "3 nomodeset"으로 대체한 후 <F10>키를 누르면 수정된 옵션으로 부팅한다. 참고로 3은 Linux runlevel 이다. 이렇게 부팅하면 rw mode로 이미 mount된 상태이므로 mount 명령은 필요 없다.

구글 chrome 설치 오류

구글에서 chrome을 내려 받아 설치하면 오류가 생겨서 처음부터 약간 기분이 나빠진다. 그런데 알고 보니 우분투에서 긴급 패치를 한 것 같다. 구글 chrome을 설치하기 전에 우분투 16.04 daily update부터 해주면 누락된 라이브러리가 먼저 설치되기 때문에 오류가 발생하지 않는다. [System Settings] > [Software & Updates] > [Ubuntu Software] > [Download from] > "Main Server"로 바꿔주고 나서,

$ sudo apt-get update
$ sudo apt-get upgrade

하면 우분투 daily update를 설치할 수 있다. 사실 우분투 설치하고 나서 이걸 항상 가장 먼저 해주는게 좋다.

VirtualBox 설치시 참고 사항

VirtualBox 5.0.18 설치는 오라클 VirtualBox 설치 방법을 따라가면 되는데, /etc/apt/sources.list 파일의 저장소 설정은 아래와 같이 해도 잘 된다.

deb http://download.virtualbox.org/virtualbox/debian xenial contrib

버추얼박스 설치 후에 VirtualBox Extension Pack을 내려 받은 후 설치하기 위해서는 아래의 명령을 실행해 주면 된다.

$ sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-5.0.18-106667.vbox-extpack

언제부터 바뀐 것인지 모르겠는데 Guest Additions iso 이미지가 설치되어 있지 않더라. 가상머신 실행 후, [Devices] > [Insert Guest Additions CD Image]를 선택하면 On-line으로 내려 받아 ~/.VirtualBox 폴더에 저장된다.

기타 참고 사항

iMac에서 이전의 우분투 버전에서는 Broadcom Proprietary WIFI 드라이버를 별도로 수작업으로 설치했어야 했는데 16.04에서는 잘 잡아 주더라.

또한, 한글 입력은 fcitx를 사용하면 잘 된다. Qt5.5에서 Qt Creator나 Qt App에서 한글 입력이 안되던 문제가 생겼었는데 한글 입력도 잘 된다. 다솜 입력기도 잘 된다는데 우분투 패키지에 포함되지 않아서 아쉽다.

다만, 아직 본격적으로 우분투 16.04를 써 본건 아니라서 어떤 문제가 있을지는 두고 볼 일이다.

2016/04/07

구글 chrome에 대한 잡상


지난 번에 블로그에 글을 올리면서 스크린 샷을 블로그에 올리려고 했더니 구글 blogger 가라사대, "Drag & Drop 한번 써 봐라!" 우분투의 Nautilus 파일 관리자에서 스크린 샷 이미지 파일을 구글 chrome에 Drag & Drop 했더니 블로그에 쏙 들어왔다. 이게 지난 3월에 새로 추가된 기능이었다. Firefox에선 당연히 안된다.

물론, 블로그 내에서 이미지를 예전에는 중앙에 밖에 배치할 수 없었는데 좌우 어디든 배치할 수 있는 기능도 추가됐다. 국내 블로그 들은 이미 이런 기능을 제공하고 있었으니 블로그 기능 자체는 구글이 개선해야 할 게 아직도 참 많다. 국내 블로그의 가장 큰 장점인 Category 관리 기능이 구글 블로그에 없다는게 신기할 정도니까...

하지만 chrome만 놓고 보면 구글이 chrome OS를 만들었을 정도니까 내가 모르는 기능들이 더 있을 거란 생각이 들어서 숨겨진 기능들을 구글링해 보았다. 역시나 숨겨진 기능들을 잘 정리해 놓은 글들이 보인다. 시간 있으면 26가지 숨겨진 Chrome의 기능들에 대한 PC 매거진 기사를 읽어 보기를... 이외에도 우분투 Unity 사용자들은 chrome에서 파일 다운로드 시에 진행율을 Launcher에서 확인할 수 있다.

26가지 중에 나에게 실질적인 도움이 되는 한가지 기능을 발견했는데, 동영상을 파일 관리자에서 chrome에 Drag & Drop해서 볼 수 있다는 것이다. 물론 파일 관리자의 Open With... 기능을 종종 사용하긴 했었지만 Drag & Drop까지는 미처 생각못했다. 그러고 보면 우분투에 VLC니 3rd Party CODEC이니 하는 것들을 굳이 설치할 필요도 없겠다 싶다. 걍 chrome으로 보면 되니까... Windows에서도 별개의 동영상 Player를 설치해서 사용했었는데 앞으로는 동영상은 chrome으로 보게 될 듯...

근데, 한편으로는 chrome에 중독되는게 무섭다는 생각도 든다. 알게 모르게 구글에 너무 의존하게 되어가는 내 자신을 발견하게 된다. chrome 하나만 놓고 보더라도 Usage Report를 보내지 않도록 설정해서 사용하고는 있지만 무슨 기능이 숨어 있을지 사용자는 알 수 없기 때문이다. 구글의 창업 모토인 "Don't be evil."을 아직까지는 어느 정도 믿고는 있지만 세상은 변하기 마련이니까...

2016년 1월을 기점으로 구글 chrome이 Windows Internet Explorer 점유율을 앞섰다는 소식도 있는 걸로 보아 나만 중독되어 가는 건 아닌 듯 하니 다행이네. 응?

우분투 사용자들이 chrome에 중독되도록 한가지 소식을 더 전한다. 흠냐... 구글이 2016년 3월부로 리눅스에서 32-bit chrome 지원을 중단했다. 그래서 64-bit 우분투 사용자들은 우분투 daily update시에 google repository를 못 읽어와서 오류가 발생한다.

해결방법은, 아래와 같이 gedit 등의 편집기로 repository 정보 파일을 열어서

$ sudo gedit /etc/apt/sources.list.d/google-chrome.list

아래의 내용과 같이 수정한다.

deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main

그리고 나서 repository 정보를 아래의 명령으로 update해 주면 오류가 사라진다.

$ sudo apt-get update


2016/04/01

Windows 10에 우분투 bash 탑재 소식


Windows 10에 우분투 bash을 탑재할 거란 소식이 있었는데, Microsoft Build 개발자 컴퍼런스에서 구체적인 시연이 있었나 보다. Windows 10의 bash 환경에서 우분투 binary가 실행된다. 기본적인 GNU 유틸리티는 물론, gcc를 통해 새로운 리눅스 binary를 만들어서 Windows에서 돌릴 수도 있다. 가상 머신이나 우분투 컨테이너를 통한 것이 아니라 Windows 커널에서 ELF binary의 System Call을 처리해 주는 방식이다. WINE과는 반대라고 이해하면 될 듯...

흠, Microsoft CEO가 바뀌고 나서 이처럼 어마 무시한 일이 현실이 되다니 정말 놀라운 세상이다. 하루하루 급변하는 세상 속에 살고 있음을 실감하게 해준다. GNU License 문제를 어떻게 해결할지는 잘 모르겠는데 설마 Windows 10도 오픈소스로 풀어 버릴려는 건 아니겠지?

Microsoft는 결코 손해보는 장사는 아닐 것이다. 우분투 가상머신을 사용하는 Cloud 서버 관리자들이 이젠 Windows만 가지고도 모든 작업을 할 수 있을 테니까. 또한, 오픈 소스 개발자들을 Windows 환경으로 끌어 들이는 것까지도 가능할 것이다.

그러면, 우분투가 얻는 것은 무엇일까? 돈 못버는 Cannonical이 푼돈 벌려고 몸을 판 것은 아닐 것인데...  뭐, 여전히 Windows에서도 우분투는 사용자 mode로만 동작하니까 리눅스 서버 시장에 영향을 미치진 않는다. 우분투 패키지 환경을 그대로 제공해 주는 것이니까 우분투 사용자가 늘어나는 셈이라고 볼 수도 있긴 하다.

아무튼 OS 사용자 입장에서는 선택의 폭이 넓어졌고 편의성이 늘어나는 것이니까 환영할 만한 일이다. 뭐 캐노니컬이 우분투 정신을 살려서 돈 보다는 공익적인 목적으로 그리 결정했을 수도 있다. 충분히 그럴 수 있는 회사이다.

웬만해서는 Windows Desktop에 우분투 가상 데스크탑을 설치할 필요도 없게 되었고, cygwin 등을 사용할 일이 없어졌다는 것... 그리고, Linux binary 들을 굳이 Windows로 Porting할 필요 없어졌다는 것... 등이 지금 당장 영향을 줄 일 들이다.

미래에 또 어떤 양상으로 리눅스와 Windows가 통합될지는 모르겠지만 아무튼 OS 통합의 단초가 될지도 모를 사건이 아닐 수 없다.

---

하지만 우리에게 당장 더 중요한 것은 선거다. 젊었을 적에는 선거가 별거 아니라 생각할지 모르지만 선거는 우리의 삶에 직간접적으로 많은 영향을 준다. 영화나 드라마에서 보면 기득권을 가진 자들은 기득권을 지키기 위해 별의별 짓을 다하는데, 힘 없는 자들은 세상을 조금이라도 바꾸기 위해 최소한의 노력도 하지 않더라... 그 나라의 정치적 수준은 국민의 의식 수준과 일치한다는 사실... 누구를 찍든 투표를 꼭 하길...

(2016/4/14 Update)

이번 선거 결과를 예측한 사람은 거의 없었을 것이다. 변화를 갈망하는 국민의 의지가 반영된 것으로 보인다. 젊은 세대들의 투표율이 올라간 것도 상당히 큰 역할을 한 듯하다. 젊은이들이 자신감을 가지고 세상을 바꿔 나가려고 한다면 얼마든지 바꿀 수 있음을 보여준 결과라고 본다.

언론과 여론 조사 결과에 휘둘리지 않았다는 점도 대단하다고 본다. 지역이나 세대, 이념에 의한 분열을 조장하는 세력들이 큰 힘을 발휘하지 못한 것도 긍정적으로 본다. 어느 정치인이 한 얘기 같은데 새는 좌우의 날개로 난다는 말... 진화론적 관점에서도 다양한 생각을 용인하는 기업이나 국가가 미래에 생존할 가능성이 높기 때문이다.

개인적으로는 70세 이상이 된 노인들은 젊었을 때 아무리 뛰어난 업적을 쌓았다고 하더라도 자발적으로 정치나 공직, 기업의 요직에서 물러나야 국가가 발전한다고 본다. 나도 곧 노인이 될 것이고, 고령화 사회가 더욱 심화될지라도 말이다. 조력자 역할은 얼마든지 할 수 있을 테니 말이다. 세상의 변화속도가 그만큼 빠르기 때문에 젊은 세대들이 주도하는 사회에 미래가 있다고 본다.

어쨌든 이제야 시작이겠지만 희망을 보았다.

2016/03/11

딥마인드에 대한 잡상


(2016/3/14 Update)

어젯 밤에 NGC에서 인공지능 특집이라고 된 거 우연히 봤는데 외국에서는 인공 생명체 내지는 Cyborg 관련 과학기술도 꽤나 진전이 있었다. 사람에게 인공 수족을 갖다 붙여도 뇌는 별다른 차이를 못느낀단다. 우주적인 시각에서 보면 Cyborg가 인류가 진화한 모습일 수도 있다나. 어차피 언젠가 인류는 지구를 벗어나야 하니까 거기에 맞게 진화해야 한다는 건데 머 일리 있는 말이긴 하다.

자기 DNA를 확대 재생산하려는게 생명체의 진화 방식이었는데 생명체라는 것이 우주적인 시각에서는 아무 존재 가치도 없게 되나? 네안데르탈인과 호모 사피엔스가 경쟁하다 호모 사피엔스만 남았듯이 미래에 인공지능 로봇만 남게 되더라도 진화의 과정으로 봐야 할지도...

(2016/3/13 Update)

어제와 오늘 3, 4차 이세돌과 알파고의 대국을 지켜봤는데 이세돌의 인간 승리였다. 끝까지 포기하지 않고 고군분투하는 모습... 이세돌 9단에게 존경을 표한다. 마지막까지 최선을 다해주기 바란다.

사실, 더이상 승패는 중요한 것이 아니다. 이세돌 9단이 인간 바둑 대표로서 손색이 없다는 것과 구글 딥마인드가 현존하는 최고의 인공지능이고 새로운 지평을 열었다는 사실에는 변함이 없다.

그리고, 강인공지능이니 약인공지능이니 떠들어 대는 앵무새들은 알파고보다도 못한 인간들이다. 강인공지능이 탄생하는 것을 확인하는 순간 인류는 종말로 접어들 가능성이 무지 높기 때문이다. 그것이 인간에 의해서든 기계에 의해서든 말이다.

이번 대국을 통해 인간의 지능이나 직관, 자아, 감정, 자유의지 등등 인간 만이 갖고 있는 모든 능력들을 컴퓨터의 계산 능력만으로도 극복할 수 있다는 확고한 생각을 갖게 됐다.

----------

이세돌과 알파고의 어제 오늘 대국 결과는 정말 충격적이다. 수많은 CPU와 GPU 연산, Cloud Computing 방식에 의한 분산 처리 방식 등등이 총동원 된다고 하더라도 컴퓨터의 연산 능력만으로 최고수 바둑 기사를 꺽는데는 한계가 있으리라 생각했다. 딥마인드가 단순한 인공신경망이라면 그 모든 연산 능력으로도 이세돌을 이길 수 없었을 것이다. 궁금해서 잠시 구글링을 해보니 평범한 놈은 아니었다. 이걸 만든 데미스 하사비스도 대단한 놈이었다.

흠, 내가 아는 소시적 인공지능은 그래도 뭔가 사람이 최소한의 규칙을 알려 주어야 동작하는 방식이었다. 그런데 이 놈은 아무 규칙을 알려 주지 않아도 스스로 학습할 줄 안다. 1년 전 Nature 지에 스스로 공부해서 벽돌깨기 게임을 마스터 한 것이 실렸는데, 그 후 1년 만에 바둑까지 평정한 것이다. 데미스 하사비스 자신이 어제 표현한대로 인류가 달에 첫발을 내디딘 것에 비견된다는 표현은 절대 과장이 아니다. 고전적인 unsupervised learning 방법이 있긴 했지만 clustering이나 하는 정도였다. 이제, 인공지능 알고리즘이 학습을 통해서 인간 고유의 전문 영역들을 하나씩 정복할 수 있는 가능성이 열린 것이다. 판도라의 상자...

SF 영화 속 얘기들이 현실이 되어 가고 있다. 딥마인드가 터미네이터의 SkyNet으로 진화할 수도 있게 된 것이다. 마이너리티 리포트, 심지어 스타워즈까지 모두 현실이 될 수 있을 것 같다. 양자 컴퓨터가 휴대폰이나 시계 속으로 들어 오는 시대가 되면 인공 지능의 미래는 인간들에게 큰 위협이 될 수도 있을 것이다. 진공관 컴퓨터인 ENIAC이 1946년에 완성되었는데 불과 50~60년 만에 스마트폰 안으로 들어온 걸 생각해 보면 현대 과학기술의 발전속도가 엄청나게 빨라지고 있음을 실감하게 된다. SF 얘기를 한 김에 인공 지능과 함께 경계해야할 과학기술 분야가 유전자 조작 분야가 될 것이다. 쥬라기 공원은 그렇다 치고 가타카, 아일랜드 류들... 지금의 유전자 기술 만으로도 특별한 인간을 만들어 내는 것이 어려운 일은 아닐테니까...

암튼, 혹자들은 인공지능이 사람들에게 유익하게 사용될 수 있다고 얘기하지만, 80~90%의 지구인들에게는 불행의 시작이 될 것이다. 결국은 구글과 같이 대규모 자본을 가진 조직들에 의해 인공지능이 활용되고 수익을 창출하게 될 것이고, 자본과 마찬가지로 정보의 불균등한 분배를 겪을 것이다. 시너지 효과에 의해 빈익빈 부익부는 심화될 것이다. 국가 대 국가 또는 자본 대 자본 간의 경쟁이 치열해 질 것이고, 결국은 SkyNet이 탄생... 누군가 좋은 의도로 인공 지능에게 지구의 환경 문제를 해결하라고 하면 인간들을 몰살시킬 방법을 찾아 낼지도 모른다.

물론, 아직까지의 인공지능 기술 수준이 걱정할 수준이 아닌 것은 분명하지만 과학 기술 발전 속도가 엄청 빠르기 때문에 누구도 미래를 예단해선 안된다는 얘기다. 딥 마인드 개발자가 특이점 내지는 임계점을 얘기하던데 인공 지능이 그걸 돌파하는 순간 스스로 목표를 설정할 확률도 높아질 것이다. 인간의 자아라는 것이 알고 보니 복잡함의 결과였다는 것이 밝혀질 날이 올지 모른다. 지구 상의 생명체 들이 단순한 유기체로부터 진화한 것이라면 인간과 같은 의식이 생겨난 원리가 단순히 유기체가 복잡해졌기 때문이라고도 생각할 수 있을 것이다. 동물들이 지능이나 감정을 갖기 시작한 것도 유기체들의 복잡도가 임계점을 돌파한 순간 발생한 것이라고 볼 수도 있을 것이다. 사회 과학에서 얘기하는 양질전화의 법칙도 비슷한 맥락이다.

특이점이니 양질 전화의 법칙이니 하는 것들은 사실은 우리의 과학 기술 수준이 그것들의 정확한 메카니즘을 규명하지 못했기 때문에 겉으로 드러난 현상들을 가지고 우리가 판단하는 것일 수 밖에 없다. 하지만 그렇다고 그것이 전혀 비과학적인 것도 아니다. Big Bang 이론도 마찬가지다. 태초의 빛과 에너지가 이 세상의 물질을 만들었다는 것에도 특이점이 존재하니까... 인공 지능이란 학문 자체도 그런 것들을 내재하고 있기 때문에 하는 얘기다. 인공 신경망의 노드들의 상태가 무엇을 의미하는지 과학적으로 설명할 수 있는 단계인지 모르겠다. 하지만, 딥마인드는 신경/인지 과학의 힘을 빌어 인간의 기억을 저장하고 꿈을 꾸는 방식을 모방해서 만들었다고 한다. 이와 유사하게 유전자 알고리즘은 미생물의 진화과정을 모사해서 만들어진 것이다. 이들을 수학적으로나 물리적으로 의미있게 분석해 내기가 쉽지는 않다는 야그다. 하지만, 생명 현상 자체가 밝혀지지 않은 게 많지만 유용하게 생명체들을 진화시키고 있듯이 생명 현상을 응용한 인공 지능 기술들도 매우 유용하게 동작하고, 심지어 사람의 생각하는 방식을 따라하기에 이르렀다는 것이다.

그니까, 결론적으로 하고 싶은 말은 딥마인드가 어디로 튈지 아무도 모르니까 조심하라는 야그다. 미래 세대를 위해서...

2016/03/05

Zapary Chart의 시작


이전 글에서 잠깐 언급했던 내 장난감 차트인 Zapary Chart(zapChart)에 대해 역사 관리 차원에서 지금까지 진행된 것들을 정리해 보기로 했다. 시작은 이 글을 올릴 때 즈음이니까 아직은 1년이 안됐다.

하지만 호랑이 담배피던 시절까지 거슬러 올라가면 Chart 프로그램에 대한 나의 역사는 꽤 오래다. DOS 창에 c++로 그래프를 그렸던 시절이 있었기 때문이다. 마침 그 때 Windows 3.1이 나오는 바람에 DOS 창에서 돌아가는 프로그램은 의미를 상실했다. 머 전문 개발자도 아니고 실험실에서 모니터링을 위해서 만든 것이었는데 공부하기도 바빠서 그 프로그램은 당시에 모니터링 프로그램이 필요하다던 중소기업 부장 아자씨에게 주어 버렸다. 그리고, 그 즈음에 더 재미있는 장난감이 나왔는데, 그게 바로 리눅스였다. 초창기 리눅스 시절, 리눅스 가지고 별의별 장난을 하면서 잘 놀았다. 하지만 직장 생활을 하면서부터 리눅스나 프로그램 개발과는 멀어지게 돼 버렸다. 그런데 웃기는 건 세월이 지날 수록 리눅스를 쓰는 회사들이 많아졌다는 것이다. 물론, 대부분은 서버용이었다. 언제부터인가 Window는 VirtualBox에서 쓰고 우분투가 내 메인 데스크탑이 된 지도 오래다.

아무튼 각설하고... 몇 년 전부터 다시 Chart 프로그램을 완성하고 싶은 마음이 불현듯 솟아났다. 그리고 소시적 오픈 소스 갖고 놀던 기억들이 되살아 났다. 그래서 소일 거리로 Chart부터 만들자고 결심하게 됐다. 사실, 요즘은 Web에서도 Chart API 들이 많이 공개돼 있고, 속도도 문제가 되지 않으며, 모바일 차트 앱들도 꽤 있다. 더구나 오픈 소스 차트 프로그램도 많더라. 내 스스로도 머에 써 먹을려고 이걸 이제서야 만들겠다는 거냐고 물어 볼 때가 있다. 지금의 내 대답은 이거다. "의미없다. 걍 내 소중한 장난감일 뿐이다. My precious~!!!"

한 가지 위안을 삼고 싶은게 있다면 내 맘대로 지지고 볶고 할 수 있다는 것이다. 그래서 Zapary Chart에는 내 기본적인 생각들을 담아낼 수 있다. 내가 전문 개발자가 아니다 보니 프로그램을 잘 만들지는 못할 것이다. 하지만, 최소한 c++로 만들어진 기존의 오픈 소스 차트 프로그램들과는 다르게 동작할 것이다. 무엇보다 중요한 것은 장난감에 살을 붙여가는 재미가 꽤 쏠쏠하다는 것이다.

오픈소스로 개발해 볼까하는 맘도 없지 않았는데 당분간은 나 혼자 갖고 놀 것이다. 왜냐하면 더 훌륭한 오픈 소스 차트 프로그램도 많기 때문이다. 기본적으로 예전에는 wxWidgets 기반의 freechart를 개선해 보려고 했던 적도 있었는데 해 보다가 구조적으로 막히는 부분이 생겨 버려서 접었다. 새로 시작하는 김에 아예 Qt5를 배울 겸해서 Qt5로 만들고 있다. Qt 기반의 오픈 소스 차트 프로그램인 QCustomPlot도 훌륭한 편이다. 하지만 남들이 짠 프로그램을 소화해 내면서 내 생각을 집어 넣는 일은 생각보다 어려운 일이다. 더구나 나이를 먹을 수록 그게 힘들더라. 그래서 맘 편히 나만의 장난감을 만들기로 한 것이다.

zapChart는 사각형에서 시작했다. 마우스 이벤트 처리 부분이 생소했기 때문에 사각형에서 마우스 이벤트 처리 방식을 이해할 필요가 있었다. 이제 저 사각형들 안에 차트를 꾸겨 넣을 수 있게 되었다. 아직도 마우스 이벤트 처리부분이 깔끔하진 않다. 아무튼 ZapChart는 장난감이기 때문에 마우스로 장난칠 수 있어야 한다. 마우스가 되면 언젠가는 Touch도 당연히 될 것이다.

기본적인 Chart 골격을 만들었다. Grid 방식의 Layout과 다중 축을 지원한다. 한 화면에 무한대까지는 아니더라도 제한없이 Chart를 꾸겨 넣을 수 있다. 하지만 이제 시작일 뿐이다. 머리 속에는 집어 넣어야 할 기능들이 너무 많아서 어느 세월에 만들까하는 생각도 든다. 티끌모아 태산이다. 하나씩 살을 붙이다 보면 태산이 될 것이다.

그래서 Zapary Chart의 실체가 뭐냐... c++/Qt5 기반의 Chart Widget이다. Qt가 Multi-Platform을 지원하니까 이론상 OS나 기기에 상관없이 동작해야 할 것이다. Qt의 QGraphicsView / QGraphicsScene / QGraphicsItem Framework을 사용할까 하다가 그 자체도 복잡하단 생각이 들어서 내가 보기에 가능한 단순하게 만들고 있다. 우분투 환경에서 개발하고 있는데, 차트를 여러개 그려도 컴퓨터가 빨라서 그런지 금방금방 차트를 그려 낸다. 틈틈이 Chart에 대한 내 기본적인 생각들도 기억관리 차원에서 올릴 예정이다.

2016/02/26

구글 검색 차트에 대한 잡상


구글이 언제부터 검색창에서 함수를 검색했을 때 차트를 보여 주었는지는 정확히 모르지만 대략 1~2년 되지 않았나 싶다. 마침 내가 만드는 장난감도 차트 프로그램(일명 Zapary Chart)이기 때문에 함수를 몇개 검색해 보고, 내 장난감 차트와 비교해 보았다. 물론 내 장난감은 혼자서 틈틈이 깔작대는 수준이라 아직 구글 차트하고 비교하기는 이르다. 더구나 최근에 구글 검색해 보고 놀란 것은 3차원 함수는 3차원 차트로 보여 주더라. 아직, 2차원에 살고 있는 내가 3차원 얘기까지는 하고 싶지 않다.

다만, Mathematica를 만든 회사인 Wolfram에서 제공하는 wolframalpha 사이트에서도 차트나 수식을 비롯한 다양한 서비스를 예전부터 제공해 왔는데, 구글이 이런 분야까지 파고들고 있는게 아닌가 싶기도 하다.

여기서는 2차원 구글 검색 차트에 대한 감상을 몇가지 끄적이려고 한다.  구글 검색에서  1/(x*x*cos(x))를 입력해서 검색하거나 이 링크를 누르면 구글 검색 결과가 차트로 나타난다. 일단, 아래에 올린 내가 만든 장난감 차트 결과와 비교해서 설명하면 좋을 것 같다.


구글 검색 차트에서 마우스 움직이면 그래프 따라가면서 x, y 좌표가 보이는 것도 대단하진 않지만, 구글스러운 자잘한 서비스이다. 물론, 확대/축소도 되고, 3차원의 경우 회전도 지원한다.

여기서는 이런 것들보다 차트 자체에 대한 기본적인 테크닉이랄까 머 그런 것만 얘기하려고 한다.

x 축과 y 축 범위 설정 문제

함수를 검색해서 차트로 보여줄 때 가장 문제가 되는 부분이 자동 범위 설정 문제이다. 사용자가 어떤 함수를 입력할지 모르기 때문에 함수만 입력 받고서 자동으로 범위를 지정해서 보여주는 것은 쉽지 않은 일이다. 가령, 1/x은 쉽게 보여 줄수 있지만 1/(x-1e+20) - 1e30은 어떻게 처리할까? 사용자 입력 내용을 어차피 parsing 할 수 있으니 그 정보를 활용하면 도움이 될 수 있기는 하다. 차트의 중심점과 x 절편, y 절편 등을 알면 범위 지정이 한결 쉬워지니까. 하지만, 1/(x-1e+20*cos(x)*x) - 1e30*sin(x)를 검색한다면 문자열 parsing 하는 것이 해결책이 되기는 어려울 것이다. 몇가지 테스트 해봤더니 기본적인 수학 지식만으로도 범위를 지정할 수는 있겠더라.

범위 자동 Scaling

어느 범위를 보여 줄지 결정하고 나면 사용자가 보기 쉽도록 설정된 범위를 자동으로 Scaling 해 주어야 한다. 이런 기법은 옛날 옛적 Excel 초창기 시절부터 제공해 왔던 기능이기 때문에 새로울 건 없다. 데이터가 정해지면 범위를 적당히 조정해 주는 로직이다. DOS 시절에 Turbo C로 만들어 봤던 기억을 장난감 차트에도 적용했다.

점근선 경계 부분 연결선 처리

구글 검색 차트를 보면 데이터라기 보다 사람이 차트를 그린 것처럼 직선을 이어서 차트를 그렸다. 실제로 두 가지 문제가 발생하는데, 첫째는 위의 차트에서와 같이 +/- 무한대로 뻗어 나가는 부분에서 직선으로 차트를 그리면 선들이 이어져서 보기 흉하게 된다. 내 장난감 차트의 경우에도 데이터는 점으로 표시하고 이 들을 직선으로 연결했는데 이 문제를 해결함으로써 원하는 차트를 얻게 된다. 이 역시 고딩 수학을 제대로 배운사람이면 해결 가능한 문제이다.

그렇지만, 컴퓨터에서는 수학적이라기 보다는 수치적으로 처리하기 때문에 완벽할 수 없는 문제들이 생긴다. 실제로 확대 축소를 몇번 해 보면 구글 차트도 깔끔하게 연결선을 정리하지 못하는 경우를 발견하게 된다. 어쨌든 wolframalpha 사이트에서 위의 차트에 대한 함수를 검색해서 그래프를 비교해 보면 구글이 얼마나 자잘한 부분까지 신경쓰고 있는지를 알게 될 것이다.

무한대 부분 처리

그런데, 두번째 문제는 점근선 부근에서는 급격히 y값이 변하기 때문에 다른 곳보다 더 많은 데이터가 필요하다. 즉, 장난감 차트에서는 모든 선들이 위 아래로 쭉쭉 뻗지 못하고 있는데 구글 검색 차트에서는 자로 그은 듯이 쭉쭉 뻗고 있다. 데이터 량이 많아 지는 것은 성능 문제로 이어지기 때문에 샘플링을 많이 하는 것이 좋은 해결책은 아니다. 사실, 이미 해결책을 제시했다. 내 장난감 차트에서도 사실 구글 검색차트처럼 보여 줄 수 있다.

맺음말

구글 검색 차트가 완벽한 건 아니다. 몇가지 어려운 함수를 검색해 봤더니 차트를 못 보여 주더라. 하지만 구글이 무서운 건 모르는 사이에 조금씩 진화해 있다는 것이다. 이세돌과 알파고의 대국이 3월 초에 있으니 기대된다. 소시적 인공신경망은 학습 능력이 그닥이었는데 딥러닝이 얼마나 발전한 알고리즘인지 궁금하긴하다.  예전의 Backpropagation 망의 경우엔 알고보니 Steepest Descent더라는 것도 이미 소시적에 다 밝혀진 사실들이었다. 알고리즘 자체가 좋아진 것도 있겠지만 컴퓨터 처리 속도나 저장 용량의 발전도 알파고를 똑똑하게 만드는 요인이 되긴 할 것이다.

이세돌 lol

2016/02/16

c/c++ 숫자 연산에 대한 잡상 정리


틈틈이 장난감 프로그램을 만들고 있는데 Crash가 발생해서 들여다 보니 숫자 연산에 대한 문제때문이었다. 소시적에도 겪었던 일들인데 오랫만에 하다 보니 같은 실수를 반복하게 된다. 그래서 몇가지 공돌이들이 숫자 연산을 하다가 자주 실수하는 부분들을 정리해 보기로 했다.

수치해석을 들을 때 기본이 되는 컴퓨터에서의 숫자 연산에 대한 부분은 설명이 없어서 시행착오를 많이 겪었던 기억이 난다. 수치해석이란 학문 자체가 가능한 정확한 수치해를 구하는 것이 목적인데 사소한 숫자 연산 오류 때문에 심각한 수치해 오류가 발생할 수 있다. 제목에 c/c++ 라고 했지만, 사실 프로그램 언어와 관계없이 발생할 수 있는 문제들이기도 하다.

1. 정수형에 대한 나누기 연산

초보적인 상식이지만 실제 프로그램을 하다보면 실수를 하게 되는 부분이다. 형 변환시 정확한 값을 사용하지 않음으로 인해 오류가 증폭된다. 아래의 예와 같이 심지어 round() 함수를 쓰면 만사 땡인 줄 알고 실수하는 경우도 많다.
int m = 15, n = 2;

int i1 = m/n;                // i1 = round(1.0*m/n);
int i2 = round(m/n);         // i2 = round(1.0*m/n);
double x1 = 15/n;            // x1 = 15.0/n;
double x2 = m/n;             // x2 = 1.0*m/n;
2. 숫자 0 으로 나누기

정수형을 사용할 경우 0으로 나누면 프로그램이 바로 죽어 버리기 때문에 오류 위치를 쉽게 확인할 수 있다. 그런데, 실수형인 경우엔 프로그램이 0으로 나누는 부분에서 죽지 않는다. 0으로 어떤 수를 나누면 무한대 인데, 어디선가 무한대를 사용하다가 죽어 버린다. 물론 debugger가 어느 정도 죽는 위치를 알려 주지만 정확한 위치를 찾기 어려운 경우도 많다.
int int_zero = 0;
double real_zero = 0;

int i3 = m/int_zero;         // devide by zero. crash~!!!
double x3 = m/int_zero;      // devide by zero. crash~!!!
double x4 = m/real_zero;     // inf (infinity is OK~!!!) 
3. 엄청 크거나 작은 숫자에 대한 예외 처리

위와 같이 무한대에 가까운 큰 수들은 컴퓨터에서 표현할 수 있는 수치상의 한계를 넘기 때문에 예외처리를 해주어야 하는데 c++ 표준에서는 아래의 극한 값들을 정의하고 있다.
#include <limits>

double INFI = std::numeric_limits<double>::infinity();
double MAXI = std::numeric_limits<double>::max();
double EPSI = std::numeric_limits<double>::epsilon();

bool truth1 = (INFI > MAXI);        // true
bool truth2 = (-INFI < -MAXI);      // true
bool truth3 = (x4 > MAXI);          // true

double zero = 1.0/MAXI;             // positive zero
즉, 무한대는 최대값 보다 더 크며, 최대값과 동일하게 다른 수와 비교하여 예외처리를 할 수는 있으나 데이터 연산에 사용되면 프로그램이 갑자기 죽어 버리는 증상이 나타난다. 때로는 안죽고 이상한 숫자를 간직하고 있는 경우도 있다. 최대값에 가까운 큰 수로 어떤 수를 나누면 엄청 작은 수가 발생하는데, 이 놈들 역시 예외 처리를 해 주어야 한다.

4. 실수형에 대한 숫자 비교

프로그램이 죽어 버리면 상대적으로 문제를 쉽게 해결할 수 있는데, if문에서 실수형을 비교하다가 생기는 논리 오류는 찾기가 무척 어렵다. 미리 NearEqual()이나 NearZero() 같은 함수를 만들어 놓고 비교하는 것이 좋은 습관이 될 것이다. 또한, 가능한 상황일 경우, 아예 정수형으로 변환해서 값을 비교하는 것도 좋은 해결책이 될 수 있다.
int n1 = 3, n2 = 15/5;
double d1 = 3, d2 = 15/5.0;

bool b1 = (n1 == n2);             // always true~!!!
bool b2 = (d1 == d2);             // sometimes false.
bool b3 = (fabs(d1-d2) <= EPSI);  // something better.

int n3 = n2 - n1;
double d3 = d2 - d1;

bool b4 = (n3 == 0);              // always true~!!!
bool b5 = (d3 == 0);              // sometimes false.
bool b6 = (fabs(d3) <= EPSI);     // something better.
위에서 절대 오차나 절대값 자체가 매우 작은 수인 epsilon보다 작으면 숫자가 같거나 0이라고 간주하는 부분은 상황에 맞게 사용해야 한다. 가령, log 함수를 사용해야 할만큼 작은 숫자들을 가지고 논다면 상대오차도 비교해야 할 것이다.

5. 0으로 나누기 방지

의도치 않게 종종 나누기에 사용되는 변수 값이 0이 되어 버리는 경우가 발생할 수 있다. 이를 방지 하기 위해서 아래와 같이 protection을 걸어 두는 게 좋은 습관이 된다.
int m = 10, n = 0;
if(n) x2 = m/n;
else std::cerr << "Fatal Error: divide by zero.\n";

double d1 = 15/3.0, d2 = 5;
double t = 0, v = 20, d = d2 - d1;
if(fabs(d) > EPSI) t = v/d;
else std::cerr << "Fatal Error: divide by zero.\n";
이는 pointer 사용시 nullptr가 아닌지 확인해서 작업을 수행할 때 protection을 거는 것과 유사하다.
int n = 3, *p = n;
if(p) *p += 5;
else std::cerr << "Fatal Error: null pointer operation.\n"; 
6. NaN(Not-A-Number) 값에 대한 예외 처리

NaN 값은 0을 실수형 0으로 나누거나 수학 함수 값에 처리할 수 없는 범위의 입력값을 주었을 때 발생한다.
double d1 = 5*(3-3)/0.0;        // d1 = nan
double d2 = 10*log(-1);         // d2 = nan
NaN 값이 발생되어도 프로그램은 문제없이 동작하는 것처럼 보인다. 하지만 if 문에서 모르고 사용하면 엉뚱한 결과가 나올 수 있다.
bool b1 = (d2 <  0);            // false
bool b2 = (d2 >= 0);            // false
NaN이 발생하리라 예상되는 부분에 예외 처리를 해주는 것이 최선이다. 0으로 나누는 부분을 앞서 다룬 바와 같이 잘 처리하고 있다면 NaN이 발생할 수 있는 경우는 log 함수와 같이 특별한 수학 함수를 사용하는 경우가 아니면 발생할 일이 별로 없기는 하다.
if(std::isnan(d2)) std::cerr << "Warning Error: NaN value operation.";
7. 연산자 우선 순위

이 부분도 내가 자주 실수했던 부분이다. 연산자 우선 순위가 애매하면 괄호치는 습관으로 해결할 수 있다. 이런 종류의 논리 오류도 찾기가 무척 어렵다.
int size = 100, step = 10;

if(step && !size%step) ...;       // if(step && !(size%step)) ...;
8. 음수에 대한 나머지(remainder) 연산자(%)

간혹 음수까지 고려해서 나머지 값을 사용해야 할 경우가 생기는데, 항상 조심해야 하는 부분이다.
int a1 = -5%7, a2 = -5%(-7);      // a1 = a2 = -5
참고로, Python에서는 심지어 a1 = 2가 되더라. 리눅스에서 python으로 수식값을 빨리 확인해 보는 경우가 많은데, 프로그램 언어마다 상식적인 연산 결과가 다르게 나올 수도 있다는...

9. 짝수와 홀수

그래픽 프로그램을 만들고 있다면 짝수를 좀더 짝사랑하게 된다. pixel이 어긋나는 문제를 쉽게 해결할 수 있으니까.
if(n%2) ...
10. 유효숫자, 그리고 소수

과학이나 공학적인 계산에서 유효숫자는 매우 중요하다. 갯수를 헤아리거나 size를 다룰 경우 정확한 값을 사용해야 한다. 미분값을 구하는 경우에는 아주 작은 수치오류도 크게 증폭될 수 있다.
int n = 1000000 + 1;            // n = 1000001
double d1 = 1e+6 + 1;           // d1 = 1e+6
double d2 = MAXI - 1e+300;      // d2 = MAXI = 1.79769e+308 (64-bit system)
한편으로, 숫자의 범위를 다루거나 할 때 백만분의 1이라는 숫자는 무시해도 된다. 위에서 최대값에 아주 큰 수를 뺐지만 최대값의 유효숫자에  영향을 줄 정도로 큰 수는 아니므로 d2값은 MAXI가 된다. 유효 숫자를 제대로 고려하는 것은 간단한 문제는 아니다. 이런 문제는 수치해석에서도 다루는 문제들이기도 하다.

유효숫자 자리 수가 엄청 중요한 수가 소수(prime number)이다. 소수 자체가 유효숫자 덩어리니까. 전에 TV에서 봤더니 외국의 어느 인증회사에서는 금고에 몇 메가 바이트에 달하는 소수를 보관하고 있닥카더라.

11. 난수 구하기

가끔 특정 범위의 난수를 사용해야 할 때가 있다. 아래의 함수를 for loop에서 사용한다면 난수가 아니라 상수를 사용하게 된다.
// return random number in the range of (-max, max)
int Rand(int max)
{
    if(!max) return 0;

    srand(time(0));
    return rand() % (2*max) - max;
}
프로그램 실행시마다 새로운 난수가 발생되도록 현재 시간으로 seed를 주었는데 의도와는 달리 컴퓨터가 워낙 빨라서 for loop 내에서 현재시간이 계속 유지되기 때문이다. seed 주는 부분을 빼면 그나마 난수가 발생되는데 이른바 pseudo-random number이다. 즉, 프로그램을 새로 시작할 때마다 동일한 난수가 발생된다.

약간의 trick을 쓰면 의도대로 프로그램을 새로 시작해도 새로운 난수가 발생하도록 할 수 있을 것이다. 그리고, 위의 함수에 한가지 문제가 더 있는데 max 값이 너무 작으면 균일한 난수가 발생되지 않는다(non-uniform distribution). c++11에서는 새로운 <random> 헤더가 있던데 새로 배워야 할만큼 필요하진 않아서 패스...