아나콘다(anaconda)는 유명한 파이썬(python) 가상환경 제공 및 기타 패키지 의존 관리 자동설치 툴이다. 아나콘다가 쿠다 툴킷(cuda toolkit)도 알아서 맞추어 설치해 가상환경을 제공하기에 나는 자주 사용하는 편이다. 그런데 우분투만 사용하다가 갑자기 윈도우에서 사용하려니 좀 앞뒤가 안맞는게 많았다.

 

윈도우에 아나콘다를 설치하고 파워 셸(powershell 5.1 버전, 나는 windows terminal 이라는 윈도우 앱을 사용하는데 이게 일반 윈도우 프롬프트가 아니라 파워 셸로 켜진다.)에서 환경을 만들고 활성화를 해보았다.

그런데 아무 반응이 없고 활성화가 안된다.

 

다행이 해결법을 아나콘다 깃 저장소 이슈에서 찾을 수 있었고 원인은 파워 셸 실행 정책에 의해서 아나콘다 스크립트 실행이 막힌것이 원인이다[1].

 

파워 셸을 관리자 권한으로 실행해서 다음 명령어를 입력한다.

> Set-ExecutionPolicy -ExecutionPolicy Unrestricted

다음 아나콘다를 다시 초기화 해준 후 창을 닫는다.

> conda init

그리고 다시 파워 셸을 열고 아나콘다를 활성화 하면 잘 된다.

 

*추가로 자신의 파워 셸 버전별 set-executionpolicy는 여기서 확인이 가능하다.

참고문헌

1. "conda environment activation not working in powershell", Mar 2019, github.com/conda/conda/issues/8428

나는 살면서 한번도 웹을 건드릴 일이 없을줄 알았는데, 지인의 부탁으로 어쩔 수 없이 떠맞게 되었다.

디자인은 bootstrap(부트스트랩)으로 하였는데 중요한 데이터 베이스도 슬슬 구축할 필요성이 보여서 구축을 해보려고 한다. 참고로 이 분야는 내 전공이 아니다, 그래서 잘못된 내용이 있을수도 있으니 이점 양해를 구하고 시작한다.

 

MySQL은 오픈소스 데이터 베이스로 유명하다. 나는 학부때 MSSQL을 배워서 그나마 MSSQL를 사용하면 빨리 구축할수 있을거라 예상했으나, 지인의 부탁으로 플랫폼(windows server에 ASP를 예상했으나 ubuntu로 바뀜)이 바뀌어서 MySQL을 사용한다.

 

먼저 패키지 업데이트부터 진행후 설치를 진행한다.

$ sudo apt update
$ apt install mysql-server

그냥 mysql-server 라고 치면 기본적으로 5.7.x 버전이 설치가 된다.

 

* 만약 8 버전을 설치하고 싶다면 다음 저장소(Repository)설정 데비안 파일을 다운로드하고 패키지파일을 실행후 설정을 마치고 $ sudo apt install mysql-server 를 쳐주면 설치가 된다.

 

다음 보안 설정을 해준다.(5.7.x 버전)

$ sudo mysql_secure_installation

참고로 나는 다음과 같이 진행하였다.

$ sudo mysql_secure_installation

Securing the MySQL server deployment.

Connecting to MySQL using a blank password.

VALIDATE PASSWORD PLUGIN can be used to test passwords
and improve security. It checks the strength of password
and allows the users to set only those passwords which are
secure enough. Would you like to setup VALIDATE PASSWORD plugin?

Press y|Y for Yes, any other key for No: y

There are three levels of password validation policy:

LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary                  file

Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 2
Please set the password for root here.

New password:

Re-enter new password:

Estimated strength of the password: 100
Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!

위 처럼 데비안 패키지 저장소로부터 설치를 진행했다면 보통 데이터 디렉토리(data directory)는 자동으로 초기화 된다고 한다[1].

 

만약 다음처럼 데이터 디렉토리가 조회되지 않는다면 mysqld --initialize 를 쳐서 초기화를 해준다[1, 2].

$ sudo mysql -uroot -p -e 'SHOW VARIABLES WHERE Variable_Name LIKE "%dir"'

Enter password:
+---------------------------+----------------------------+
| Variable_name             | Value                      |
+---------------------------+----------------------------+
| basedir                   | /usr/                      |
| character_sets_dir        | /usr/share/mysql/charsets/ |
| datadir                   | /var/lib/mysql/            |
| innodb_data_home_dir      |                            |
| innodb_log_group_home_dir | ./                         |
| innodb_tmpdir             |                            |
| lc_messages_dir           | /usr/share/mysql/          |
| plugin_dir                | /usr/lib/mysql/plugin/     |
| slave_load_tmpdir         | /tmp                       |
| tmpdir                    | /tmp                       |
+---------------------------+----------------------------+

그리고 다음과 같이 데이터 베이스를 만든다음 유저를 만들고 권한을 주고, 그 다음에 만든 유저로 테스트용 테이블을 만들었다.

$ sudo mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.31-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database test;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| test               |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'Pass12!@#';
Query OK, 0 row affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON test.* TO 'testuser'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> exit
Bye

$ mysql -utestuser -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.7.31-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> use test
Database changed

mysql> CREATE TABLE test(
    -> id INT(5) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    -> title VARCHAR(255))CHARSET=utf8;
Query OK, 0 rows affected (0.01 sec)

test 테이블이 잘 되는지 확인해본다.

mysql> INSERT INTO test (title) VALUES ('스바루'), ('에밀리아'), ('렘');
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM test;
+----+--------------+
| id | title        |
+----+--------------+
|  1 | 스바루       |
|  2 | 에밀리아     |
|  3 | 렘           |
+----+--------------+
3 rows in set (0.00 sec)

mysql> exit
Bye

자 이제 마지막으로 제일 중요한 python과의 연동을 확인해본다.

먼저 다음과 같이 MySQL connector(커넥터)를 설치해주어야 한다.(나는 테스트중인 python 버전이 3.6.9 이다.)

$ pip3 install mysql-connector-python

* mysql-connector-python 버전이 8 버전이면 MySQL 경우 8.0, 5.7, 5.6, 5.5 사용이 가능하고 python 경우 3.6, 3.5, 3.4, 2.7이 지원된다[3].

 

설치를 완료했다면 다음과 같이 간단히 db.py 란라는 커넥터를 만들고 실행해본다.

import mysql.connector
from mysql.connector import Error
from datetime import datetime

# configuration for connect to MySQL
host = 'localhost'
database = 'test'
user = 'testuser'
password = 'Pass12!@#'

# logging option
logging = True

# simple connector
def connect():
    conn = mysql.connector.connect(
                host = host,
                database = database,
                user = user,
                password = password
            )

    if logging and conn.is_connected():
        print("Connected to MySQL host: '{}', database: '{}', user: '{}', time: '{}'".format(
            host, database, user, datetime.now()))

    return conn

if __name__ == '__main__':
    connect()

실행해본다.

$ python3 db.py
Connected to MySQL host: 'localhost', database: 'test', user: 'testuser', time: '2020-08-30 00:43:09.697235'

잘 작동됨이 확인이 되었고 다음 test 테이블에 맞는 기본적인 CRUD(create, read, update and delete)를 아래와 같이 만들고 실행해본다.

import mysql.connector
from mysql.connector import Error

from datetime import datetime

host = 'localhost'
database = 'test'
user = 'testuser'
password = 'Pass12!@#'

logging = True

# simple connector
def connect():
    conn = mysql.connector.connect(
                host = host,
                database = database,
                user = user,
                password = password
            )

    if logging and conn.is_connected():
        print("Connected to MySQL host: '{}', database: '{}', user: '{}', time: '{}'".format(
            host, database, user, datetime.now()))

    return conn

# insert datas into test table
def create(table, fields, values):
    # check fields is innumerable or not
    if isinstance(fields, str):
        fields = (fields, )
    # check values is innumerable or not
    if isinstance(values, str):
        values = (values, )

    fields, values = ','.join(fields), "'"+"','".join(values)+"'"
    query = "INSERT INTO {} ({}) VALUES ({})".format(table, fields, values)

    return query

# read datas from test table
def read(table, fields, where=None, orderby=None):
    # check fields is innumerable or not
    if isinstance(fields, str):
        fields = (fields, )

    fields = ','.join(fields)
    if where is None and orderby is None:
        query = "SELECT {} FROM {}".format(fields, table)
    elif where is None:
        query = "SELECT {} FROM {} ORDER BY {}".format(fields, table, orderby)
    elif orderby is None:
        query = "SELECT {} FROM {} WHERE {}".format(fields, table, where)
    else:
        query = "SELECT {} FROM {} WHERE {} ORDER BY {}".format(fields, table, where, orderby)
    return query

# change datas into test table
def update(table, set, where):
    query = "UPDATE {} SET {} WHERE {}".format(table, set, where)
    return query

# delete datas into test table
def delete(table, where):
    query = "DELETE FROM {} WHERE {}".format(table, where)
    return query

if __name__ == '__main__':
    # connect
    conn = connect()
    cursor = conn.cursor()

    # create
    cursor.execute(create('test', ('title'), ('람')))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())

    # read
    cursor.execute(read('test', ('id', 'title')))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), orderby='id DESC'))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), where='id = 2'))
    print(cursor.fetchall())
    cursor.execute(read('test', ('*'), where='id >= 2', orderby='id DESC'))
    print(cursor.fetchall())

    # update
    cursor.execute(update('test', "title = '베아트리스'", 'id = 4'))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())

    # delete
    cursor.execute(delete('test', 'id = 1'))
    cursor.execute(read('test', ('*')))
    print(cursor.fetchall())
    
    conn.commit()
    conn.close()

실행해본다.

$ python3 db.py
Connected to MySQL host: 'localhost', database: 'test', user: 'testuser', time: '2020-08-30 02:57:01.585607'
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '람')]
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '람')]
[(4, '람'), (3, '렘'), (2, '에밀리아'), (1, '스바루')]
[(2, '에밀리아')]
[(4, '람'), (3, '렘'), (2, '에밀리아')]
[(1, '스바루'), (2, '에밀리아'), (3, '렘'), (4, '베아트리스')]
[(2, '에밀리아'), (3, '렘'), (4, '베아트리스')]

잘 됨을 확인할 수 있다.

추가적으로 SQL injection(인젝션) 대비와 태그가 들어가는거 및 몇 이상한 문자가 흘러 들어가는 것 정도만 막아주면 그럭저럭 사용할수 있지 않을까 생각한다.

참고문헌

1. "How To Install MySQL on Ubuntu 18.04", April 2020, www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-18-04

2. "How to find the mysql data directory from command line in windows", Jul 2013, stackoverflow.com/questions/17968287/how-to-find-the-mysql-data-directory-from-command-line-in-windows

3. "Getting Started with MySQL python Connector", www.mysqltutorial.org/getting-started-mysql-python-connector/

큰 용량의 데이터셋을 크롤러(crawler)로 다운받거나 아니면 어떠한 모델을 학습할때 즉시 로그를 확인할 필요는 없지만 학습기간이 오래걸릴때 등등 경우에는 터미널(terminal)을 계속 열어두면 많이 불편하다.

 

특히 ssh(secure shell)로 다른곳에서 열어두고 학습하다가 갑자기 인터넷이라도 끊기면 다시 시작해야하는 번거로움도 발생한다.

 

이러한 경우 터미널이 종료되어도 백그라운드 작업을 nohup로 진행할 수 있다. nohup은 posix 커맨드로 HUP(signal hang up) 신호를 무시한다[1].

 

기본적인 사용은 다음과 같다[2].

$ nohup test.py &

그리고 많은 경우가 로그를 보기를 원하는데 위 같은 간단한 명령은 실행했던 디렉토리에 nohup.out 이라는 로그파일을 남기게 된다. 그러나 이는 모두가 원하는 경우는 아니다.

 

그래서 다음과 같이 사용하면 원하는 디렉토리에 원하는 로그파일을 남길 수 있다.

$ nohup python -u ./test.py > ./test.log &

python의 -u 플래그는 --help 에서도 확인이 가능한데, 강제로 stdout 과 strerr 스트림 버퍼링을 해제하는 옵션이다.(단 stdin에는 적용안됨) 다시 요약하면 실시간으로 로그를 남기고 싶다면 -u를 아니면 -u를 빼도 된다[3].

 

종료는 해당 프로세서가 종료가 되면 자동으로 종료가 되나 강제로 종료를 하고싶다면 실행한 프로세서의 pid를 확인한후 kill 명령어로 프로세서를 강제로 종료하는 방법이 있다.

참고문헌

1. "nohup", May 2020, en.wikipedia.org/wiki/Nohup

2. "리눅스 nohup 사용법", Mar 2019, zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_nohup_%EC%82%AC%EC%9A%A9%EB%B2%95

3. "Nohup is not writing log to output file", Oct 2012, stackoverflow.com/questions/12919980/nohup-is-not-writing-log-to-output-file

일반적인 분류(classification)문제를 다루다보면 항상 마지막에 linear layer가 들어가게 된다.

이는 우리가 생각할수 있는 가장 간단한 방식이고, 이는 말이 된다.

 

그런데 만약 입력 텐서의 크기가 고정되지 않다면 출력 텐서의 크기도 마찬가지로 다르게 나온다, 여기서 문제가 보통 사용하는 linear layer는 입력 크기가 고정되기에 입력 사이즈가 변화되면 에러가 발생한다.

 

그래서 이를 위해서 입력에 관계없이 출력을 고정하도록 설계된 Adaptive Pooling을 사용할 수 있다.[1, 2]

 

단순히 출력을 지정한 튜플값으로 맞춰주는 pooling이라고 생각하면 된다.

 

예로 다음과 같은 텐서(b, c, h, w)를 만들어서 Max pooling을 했다고 가정한다.

>>> a = torch.randn(1, 2, 5, 5)
>>> b = F.max_pool2d(a, stride=1, kernel_size=2)
>>> b.size()
torch.Size([1, 2, 4, 4])
>>> b
tensor([[[[ 1.4379,  1.4379,  1.0259,  1.0259],
          [ 1.8803,  0.7414,  0.0418,  0.8152],
          [ 1.8803,  0.7414,  0.0418, -0.0604],
          [ 1.3686,  0.2596,  1.2160,  1.2160]],

         [[ 1.5899,  0.6822,  1.4687,  1.4687],
          [ 1.5899,  0.6822,  1.4687,  1.4687],
          [-0.1199,  0.7847,  0.7847,  0.7320],
          [-0.2405,  0.7847,  0.7847,  0.6991]]]])

그리고 위와 같은 텐서는 Adaptive Max Pooling을 사용하면 다음과 같이 표현할수 있다.

>>> c = F.adaptive_max_pool2d(a, (4, 4))
>>> c.size()
torch.Size(1, 2, 4, 4)
>>> c
tensor([[[[ 1.4379,  1.4379,  1.0259,  1.0259],
          [ 1.8803,  0.7414,  0.0418,  0.8152],
          [ 1.8803,  0.7414,  0.0418, -0.0604],
          [ 1.3686,  0.2596,  1.2160,  1.2160]],

         [[ 1.5899,  0.6822,  1.4687,  1.4687],
          [ 1.5899,  0.6822,  1.4687,  1.4687],
          [-0.1199,  0.7847,  0.7847,  0.7320],
          [-0.2405,  0.7847,  0.7847,  0.6991]]]])

Adaptive Max Pooling의 매개변수로 위 코드처럼 튜플값이 들어가는데 이는 2d Max pooling을 적용하고 나올 결과의 (h, w)를 의미한다. 위 코드 경우에는 (b, c, 4, 4)로 나오기를 원해서 (4, 4)라는 튜플을 넘겨준 것 이다.

 

*참고로 torchvision의 resnet은 Adaptive Avg Pool2d가 적용되었다. pre-trained weight은 이미지넷 224사이즈로 학습이 된걸로 보인다.[3]

>>> import torchvision.models as models
>>> models.resnet18()
ResNet(
  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU(inplace=True)
  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
  (layer1): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
    (1): BasicBlock(
      (conv1): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(64, 128, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(128, 256, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock(
      (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (downsample): Sequential(
        (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
        (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      )
    )
    (1): BasicBlock(
      (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
      (relu): ReLU(inplace=True)
      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
  (fc): Linear(in_features=512, out_features=1000, bias=True)
)

references

1. "Contiguous vs non-contiguous tensor", Nov 2018, discuss.pytorch.org/t/contigious-vs-non-contigious-tensor/30107

2. "Adaptive_avg_pool2d vs avg_pool2d", Oct 2018, discuss.pytorch.org/t/adaptive-avg-pool2d-vs-avg-pool2d/27011

3. "TORCHVISION.MODELS", pytorch.org/docs/stable/torchvision/models.html

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 호스트 이름에 맞는 호스트 키가 없어서 발생함.

 

자신의 호스트 이름으로 ssh를 한번만 접속해주면 해결됨을 확인함.

$ ssh hyeok@hyeok -p 2200

 

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 JAVA_HOME을 못찾기에 발생함.

 

그러나 JAVA_HOME을 .bashrc에 설정이 되어있고 자바 버전 확인도 잘 작동됨.

 

이는 hadoop이 실행되면서 ssh 접속을 할때 자바 코드에서 접속을 시도하기에 .bashrc 관련 설정 및 현재 터미널에서 사용하던 환경변수는 모두 값을 잃어버린다, 그래서 JAVA_HOME 정보를 전달하기 위해 /etc/hadoop/hadoop-env.sh의 JAVA_HOME을 설정해준다.[1]

$ export JAVA_HOM="/home/hyeok/JAVA/jdk-8"

*참고로 ~/JAVA/jdk-8은 안된다.

references

1. "JAVA_HOME is not set in Hadoop", Dec 2017, stackoverflow.com/questions/20628093/java-home-is-not-set-in-hadoop

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 기존 사용중인 ssh의 포트가 맞지 않기에 발생함.

 

사용중인 ssh포트를 22로 변경하거나, HADOOP_SSH_OPTS 환경변수를 알맞게 변경해주면 됨.[1]

$ export HADOOP_SSH_OPTS="-p 2200"

아니면 /etc/hadoop/hadoop-env.sh 의 HADOOP_SSH_OPTS를 수정해준다.

$ export HADOOP_SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=10s -p 2200"

references

1. "Change ssh default port in hadoop multi cluster [closed]", Feb 2016, stackoverflow.com/questions/35224304/change-ssh-default-port-in-hadoop-multi-cluster

 

hadoop version: 3.2.1

java version: openjdk 1.8.0_41, 64bit

 

본 에러는 pdsh의 설정 문제로 발생함.

=> pdsh is a variant of the rsh command. unlike host, pdsh can run multiple remote commands in parallel.[1]

 

pdsh의 rcmd type은 기본으로 rsh로 되어있다 이를 ssh로 바꾸어 주면 됨.[2]

 

먼저 rsh로 되어있는지 확인한다.

$ pdsh -q -w localhost

Generic options항목의 Rcmd type을 확인후 rsh로 되어있다면 ssh로 바꾸어줌

$ export PDSH_RCMD_TYPE=ssh

references

1. "pdsh(1) - Linux man page", linux.die.net/man/1/pdsh

2. "Permission Denied error while running start-dfs.sh", Mar 2017, stackoverflow.com/questions/42756555/permission-denied-error-while-running-start-dfs-sh

 

pytorch를 사용하다보면 코드속에 .contiguous()가 가끔 보일때가 있다. 이는 연속적인 메모리 텐서를 반환하는 메서드로 만약 어떤 연산을 사용할때 이를 사용하지 않으면 에러가 발생하는 경우가 생긴다.

 

예로 아래와 같이 a라는 텐서를 만들고 텐서의 형태(shape)와 보폭(stride)을 살펴보면 다음과 같다.[1]

>>> a = torch.randn(2, 3, 4)
>>> a.size()
torch.Size([2, 3, 4])
>>> a.stride()
(12, 4, 1)

여기서 보폭은 해당하는 차원의 원소에 접근할때 건너 뛰어야 할 원소들의 수(보폭)를 의미한다. 위의 예로 0차원 에서 건너 다음 원소를 가져올때는 12개의 원소를 뛰어 넘어야 한다는 의미이다.

이는 말이 된다. 하지만 메모리에 연속적으로 값이 들어있지 않는 경우에는 말이좀 달라진다.

 

예로 아래와 같이 a텐서의 차원 0과 1을 바꾸어 보았다. 그리고 텐서의 형태가 바뀐 a와 같은 형태의 b를 만들어서 비교 해보면 다음과 같다.[1]

>>> a = a.transpose(0, 1)
>>> a.size()
torch.Size([3, 2, 4])
>>> a.stride()
(4, 12, 1)
>>> a.is_contiguous()
False
>>> b = torch.randn(3, 2, 4)
>>> b.stride()
(8, 4, 1)
>>> b.is_contiguous()
True

여기서 눈여겨 볼점은 a와 b가 같은 형태로(3, 2, 4) 유지되나 실제 원소에 접근하기 위한 보폭을 살펴보면 값이 다르다는 점(a: [4, 12, 1], b: [8, 4, 1])을 확인할 수 있다.

이런 이유는 텐서 a의 형태가 변화될때 실제로 해당 원소에 접근할때는(메모리에서는 원소들의) 위치가 변화되지 않았고 접근 인덱스만 변화되었기 때문이다. 이는 pytorch에서 일부 텐서 연산을 수행함에 있어서 성능향상을 위한것으로 생각된다.(만약 텐서의 형태가 바뀔때마다 메모리에 있는 그래도 배치하려면 재 할당을 해주어야 하는데 이러한 연산이 잦으면 오히려 성능을 떨어뜨리는 원인이 될수있다.)  그래서 형태가 바뀐 a의 보폭을 해석해보면 차원 0 에서 다음 원소에 접근할때 8개가 아닌 4개의 원소만 건너면 b와 같은 텐서처럼 사용을 할수가 있다는 의미로, 실제 메모리에는 a와 b가 다른 순서로 배열이 되어있으나 메모리 재할당을 할필요 없이 접근 인덱스만 바꾸면 b처럼 사용을 할수가 있다는 의미가 된다.

 

그리고 .contiguous() 메서드는 다음과 같이 이러한 a와 같은 비연속적인 텐서를 연속적으로 만들어주는 역할을 한다.

>>> a = a.contiguous()
>>> a.stride()
(8, 4, 1)

이제 텐서 b랑 같아졌다.

 

아래의 예는 contiguous하지 않은 텐서를 펴서 사용할 때 접할 수 있는 에러이다.

>>> a = a.transpose(0, 1)
>>> a = a.view(-1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: view size is not compatible with input tensor`s size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.

 

이를 해결하려면 텐서를 연속적으로 만들어주면 된다.

>>> a = a.contiguous()
>>> a = a.view(-1)
>>> a.size()
torch.Size([24])

 

또는 .contiguous() 없이 아래와 같이 사용이 가능하다.

>>> a = torch.randn(3, 2, 4)
>>> a = a.transpose(0, 1)
>>> a = a.reshape(-1) # view() 대신 reshape() 사용
>>> a.size()
torch.Size([24])

위의 예 처럼 연속을 고려하는 메서드(reshape)와 고려하지 않는 텐서변환 메서드(view)가 존재하니 알맞게 사용하면 에러를 예방할수 있다.

참고문헌

1. "Contiguous vs non-contiguous tensor", Nov 2018, discuss.pytorch.org/t/contigious-vs-non-contigious-tensor/30107

+ Recent posts