블랙록의 투자를 분석하는 주요 팩터 6가지; 투자를 위한 새로운 렌즈

투자를 평가하기 위한 새로운 렌즈인 The Factor Box.

 

투자자들은 The Factor Box를 다양한 펀드에서 factor를 비교하고,

포트폴리오를 구축하고 리스크 매니지 먼트하는데 도움이 되는 툴로 사용할 수 있다.

 

 

  • Value (가치): stocks that are cheap relative to their fundamentals
  • Low Size (소형주): Smaller more nimble companies
  • Momentum (모멘텀): Stocks in an upswing
  • Quality (퀄리티): Companies with healthy balance sheets
  • Dividend Yield (고배당): Higher yielding companies
  • Low Volatility (저변동): Stocks that have collectively exhibited lower volatility

 

 

 


Reference

 

[1] www.ishares.com/us/resources/tools/factor-box

 

The Factor Box | iShares - BlackRock

 

www.ishares.com

 

5. F-Score

  • 조셉 피오트로스키 (Joseph Piotroski)가 2000년 논문에서 제안
  • 저 PBR 기업 중 고 퀄리티 기업에만 투자하고자 제안

F-score

  • 한 개 지표만 사용하지 않고, 주식별로 9개 항목을 검토해 각 사항이 맞으면 1점, 틀리면 0점을 부여

수익성

  • 전년 당기순이익: 0이상
  • 전년 영업현금흐름: 0이상
  • ROA: 전년 대비 증가
  • 전년 영업현금흐름: 순이익보다 높음

재무 건전성

  • 부채비율: 전년 대비 감소
  • 유동비율: 전년 대비 증가
  • 신규 주식 발행(유상증자): 전년 없음

효율성

  • 매출총이익률(=매출총이익/매출액): 전년 대비 증가
  • 자산회전율(=매출/자산): 전년 대비 증가

신 F-score

  • 9개가 아닌 3개의 지표만 사용하여, 맞으면 1점, 틀리면 9점을 부여

  • 신규 주식 발행(유상증자): 전년 없음
  • 전년 당기순이익: 0이상
  • 전년 영업현금흐름: 0이상

1. ROE (자기자본이익률; Return On Equity)

  • 당기순이익을 순자산(자기자본)으로 나눈 값
ROE=당기순이익순자산=시가총액순자산시가총액당기순이익=PBRPERROE = {당기순이익 \over 순자산} = {{시가총액 \over 순자산} \over {시가총액 \over 당기순이익} } = {PBR \over PER}
  • PBR과 PER을 알고 있으면 ROE를 계산할 수 있음
  • POE/PER > 3 일 경우, 투자가치가 있따고 판단하는 경우도 있음(브라운스톤공식)
  • PBR이 같아도 ROE와 PER에 따라 다른 의미를 가짐
    • 투입한 자기자본이 얼마만큼의 순이익을 냈는지를 나타내는 지표
    • ROE와 PER이 각각 (20%, 5배), (5%, 20배)인 두 기업이 있으면 PBR은 1로 같지만 ROE가 높고 PER이 낮은 기업이 현재 재무구조상 더 좋다고 볼 수 있음
  • 워렌 버핏이 중요하게 여기는 지표로 알려져 있음
  • 단독으로는 크게 유용한 지표는 아님

2. ROA (총자산수익률; Return On Assets)

  • 당기순이익을 자산총액으로 나눈 값
ROA=당기순이익자산총액ROA = {당기순이익 \over 자산총액}
  • 특정 기업이 자산을 얼마나 효율적으로 운영했는지 보여주는 지표
  • 일반적으로 ROE의 경우 업계 평균 ROE, 동종기업 ROE와 비교하여 기업 실적을 판단하는데 사용됨
  • 하지만 ROE로는 부채로 발생한 손익여부를 확인할 수 없기 때문에 부채 비중이 큰 업종에는 적합하지 않을 수 있음. 이 경우에는 ROE보다 ROA를 사용하는 것이 적합

3. GP/A

  • ROE, ROA와 달리 미래 수익 예측에 비교적 큰 도움이 되는 지표임
  • 노비 마르크스(Robert Novy Marx)가 제안
    • <The Other side of Value: The gross Profit-ability Premium>, 2013

GP/A=매출액매출원가자산총액=매출총이익자산총액GP/A = {매출액-매출원가 \over 자산총액} = {매출총이익 \over 자산총액}
  • 지표와 기업의 실제 수익성과 연관성이 떨어진다.
  • 영업이익이나 당기순이익보다는 GP(매출총이익)이 더 우수하다고 설명
    • 영업이익, 당기순이익은 투자자들이 관심 있게 살펴보는 지표인 만큼 회계 조작이 많음
    • GP/A는 변질될 가능성이 거의 없어, 기업의 수익성을 대표하는 우수한 지표라고 봄

4. ROC (자기자본이익률; Return on Capital)

  • 투자한 자본 대비 어느 정도의 수익을 냈는지 측정하는 지표
  • 순이익을 총자산으로 나눈 ROA와 비슷한 개념

ROC=EBIT(영업이익)투자자본=EBIT고정자산+유동자산유동부채ROC = {EBIT (영업이익) \over 투자자본} = {EBIT \over 고정자산+유동자산-유동부채}
  • 그린블라트가 선호한 지표

5. 분기 PER

  • PER 대신 분기 PER로 계산
  • 시가총액을 최근 분기수익을 4를 곱한 수로 나눈 지표

분기PER=시가총액최근분기영업이익×4분기 PER = {시가총액 \over 최근분기영업이익 \times 4}

  • 분기 순이익이 연간 순이익 보다 정보가치가 높다고 볼 수도 있음

6. PFCR

  • 시가총액/잉여현금흐름

PFCR=시가총액잉여현금흐름(FCFF)=시가총액영업현금흐름투자금=시가총액자유현금흐름PFCR = {시가총액 \over 잉여현금흐름(FCFF)} = {시가총액 \over 영업현금흐름 - 투자금} ={시가총액 \over 자유현금흐름}

  • 영업현금흐름에서 자본지출(설비투자 등)금을 차감한 현금흐름
  • 영업현금흐름이 많아도, 기업의 성장을 위해 지속적인 설비투자 등으로 돈을 쓴다면, 좋지 않을 수도 있다는 관점
  • 워렌버핏이 중요시함

7. POR

  • PER, PCR의 사촌 (시가총액/영업이익)

POR=시가총액영업이익POR = {시가총액 \over 영업이익}

  • 순이익에는 비영업이익이 포함되니, 영업이익을 반영하는 PER보다 정보가치가 높다고 볼수도 있음

8. EV/EBITDA

  • 몇 년 만에 내가 투자한 비용을 회수할 수 있는가?
    • EV/EBITDA가 4라면, 만약 200억원을 주고 A회사의 주식 전부를 인수한다면, 4년 만에 회수가 가능하다는 의미.
  • PER과 다르게, 기업 자기자본 뿐만 아니라 부채까지 모두 인수한다고 가정하기에, 부채비율이 상이한 기업들을 비교할 때 좋음
  • M&A에서 잘 쓰이는 지표
  • 알파 아키텍트에서 강조 (퀀트로 가치투자하라)

8.1. EV (Enterprise Value)

  • 순차입금(차입금-현금)과 자본(주가x주식수)의 합으로 계산

EV=순차입금(차입금현금)+시가총액EV = {순차입금(차입금-현금) + 시가총액 }
  • A회사의 갚아야할 차입금이 100억원, 보유한 현금이 20억원, 그리고 시가총액이 200억원이라면, EV는 280억원으로 계산 됨

8.2. EBITDA (Earnings Before Interest, Taxes, Depreciation and Amortization)

  • 영업이익(EBIT) + 감가상각비(Depreciation) + 무형자산상각비(Amortization)
EBITDA=영업이익(EBIT)+감가상각비(Depreciation)+무형자산상각비(Amortization)EBITDA = {영업이익(EBIT) + 감가상각비(Depreciation) + 무형자산상각비(Amortization) }
  • A회사의 영업이익은 20억원인데, 상각비(감가상각비, 무형자산상각비) 합이 50억원이라고 가정하면, 회사의 EBITDA는 70억원

1. PBR (주가 순자산 비율; Price Book-value Ratio)

  • 주가를 1주당 순자산 가치로 나눈 값
  • 시가총액을 순자산 가치로 나눈 값
PBR=주가주당순자산가치(BPS)=주가순자산총발행주식수=주가×총발행주식수순자산=시가총액순자산PBR = {주가 \over 주당순자산가치(BPS)} = {주가 \over {순자산 \over 총발행주식수} } = {주가 \times 총발행주식수 \over 순자산} = {시가총액 \over 순자산}
  • PBR이 1이면 시가총액과 기업의 순자산가치가 같다는 의미
    • 회사를 청산한다고 가정하면, 청산비가 시장 가격보다 비쌈 → 투자하기 좋다!
  • 낮을 수록 저평가 된 기업
  • 우리나라에선 은행주가 1 이하에 속함
  • PBR가 너무 낮으면 주의 (0.2 이하?)
  • 벤자민 그레이엄, 유진 파마 강조!

2. PER; P/E (주가수익률; Price Earnings Ratio)

  • 주가를 1주당 순이익(=EPS)로 나눈 값
  • 시가 총액을 순이익으로 나눈 값
PER=주가주당순이익(EPS)=주가순이익총발행주식수=시가총액순이익PER = {주가 \over 주당순이익(EPS)} = {주가 \over {순이익 \over 총발행주식수} } = {시가총액 \over 순이익}
  • PER 년 후에, 투자금을 회수할 수 있다는 의미
  • 낮을 수록 저평가된 기업
  • PER가 너무 낮으면 주의 (2 이하?)

3. PCR; P/CF (주가현금흐름비율; Price Cash Flow Ratio)

  • 주식 1주당 주식 가격과 현금흐름비율간의 비율을 나타낸 주가 평가 지표
    • 현금 흐름비율은 주식 1주당 기업이 창출한 현금의 가치
  • 특정 기업이 얻은 영업현금흐름 1원을 증권시장이 얼마의 가격으로 평가하고 있는지 나타내는 수치
  • PCR이 높으면 당장 벌어들이는 현금은 적지만 미래 수익 전망이 높아서 시장의 기대가 높음

PCR=주가주당영업현금흐름(CPS)=주가당기순이익+감가상각비주식수=시가총액영업현금흐름PCR = {주가 \over 주당영업현금흐름(CPS)} = {주가 \over { 당기순이익 + 감가상각비 \over 주식수}} = {시가총액 \over 영업현금흐름}
  • 낮을 수록 저평가된 기업
  • PER과 비슷한 지표(사촌동생 정도?) 이지만, PER의 순이익은 회계 조작 가능성이 높은 반면에 조작이 덜 되는 영업현금 흐름을 보는 투자자들을 위한 지표

4. PSR (주가매출액비율; Price Selling Ratio)

PSR=주가주당매출액(SPS)=주가매출액총발행주식수=시가총액매출액PSR = {주가 \over 주당매출액(SPS)} = {주가 \over {매출액 \over 총발행주식수}} = {시가총액 \over 매출액}
  • 아직 수익성 평가하기 힘든 회사의 가치분석을 위한 지표
  • 순이익을 활용한 PER과 달리, PSR은 이익이 아직 나지 않은 스타트업/초창기 기업/이익의 변동이 큰 경기 민감형 기업 들의 가치를 평가할 때 사용
  • 당장의 수입성보다는 미래가치와 성장성을 고려할 수 있다는 점이 장점
  • 낮을 수록 성장 잠재력에 비해 주가가 저평가 되어 있음
  • 켄 피셔가 만들어서 1980년대 센세이션 몰고온 지표!
  • PSR 너무 낮으면 이상함? (0.21 이하?)

Intro

 

딥러닝 모델을 설계하고 개발할 때 중요한 부분 중 하나인 데이터 로더 만드는 방법에 대해서 정리하고자 한다.

 

특별히, 음성이나 음악 등 연속적인 데이터를 이용하는 모델을 구축하고자 한다.

 

신호를 가지고 할 수 있는 것들이 많이 있지만, 우선 Keyword Spotting 알고리즘을 만드는 것을 목표로 놓고, 그에 맞는 데이터 로더를 만들어 가보도록 하자.

 

먼저 shell 환경에서 다음과 같이 tensorflow speech command dataset을 다운로드 받자. 

!wget https://storage.cloud.google.com/download.tensorflow.org/data/speech_commands_v0.02.tar.gz

 

torch.utils.data.Dataset 은 데이터셋을 나타내는 추상 클래스이다. 우리가 만드는 Custom Dataset Class 는 torch.utils.data.Dataset 을 상속하고, 다음 3가지 멤버함수들을 오버라이드 해야 한다. 

import torch

class CustomDataset(torch.utils.data.Dataset):

  def __init__(self, ...):
  
  def __len__(self):
  
  def __getitem__(self, idx):

멤버함수 __init__() 는 클래스 인스턴스 생성시 파라미터로 들어오는 정보로 원하는 데이터셋 정보를 초기화 해야 한다.

len(dataset) 에서 호출되는, 멤버함수 __len__ 은 데이터 셋의 크기를 return 해야 한다.

dataset[i] 에서 호출되는, __getitem__ 은 i 번째 샘플을 찾는데 사용된다.

 

그럼 Custom Dataset Class인 SpeechCommandsDataset 를 만들어 보자.

import torch
import os
import numpy as np
import librosa

CLASSES = 'unknown, silence, yes, no, up, down, left, right, on, off, stop, go'.split(', ')

class SpeechCommandsDataset(torch.utils.data.Dataset):
    """Google speech commands dataset. Only 'yes', 'no', 'up', 'down', 'left',
    'right', 'on', 'off', 'stop' and 'go' are treated as known classes.
    All other classes are used as 'unknown' samples.
    """

    def __init__(self, folder, transform=None, classes=CLASSES, silence_percentage=0.1):
        """
          Args:
          folder (string): Path folder.
          transform (callable, optional): Optional transform to be applied
          on a sample.
          class (string): list

        """
        all_classes = [d for d in os.listdir(folder) if os.path.isdir(os.path.join(folder, d)) and not d.startswith('_')]
        
        class_to_idx = {classes[i]: i for i in range(len(classes))}
        for c in all_classes:
            if c not in class_to_idx:
                class_to_idx[c] = 0

        data = []
        for c in all_classes:
            d = os.path.join(folder, c)
            target = class_to_idx[c]
            for f in os.listdir(d):
                path = os.path.join(d, f)
                data.append((path, target))

        # add silence
        target = class_to_idx['silence']
        data += [('', target)] * int(len(data) * silence_percentage)

        self.classes = classes
        self.data = data
        self.transform = transform

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        path, target = self.data[index]
        data = {'path_wave': path, 'target': target}

        if self.transform is not None:
            data = self.transform(data)
        return data

 

__init__ 을 사용해서 폴더 안에 있는 데이터들의 Path 를  읽지만, __getitem__ 을 이용해서 그 path에 해당하는 waveform 데이터를 읽어드린다 . 이 방법은 모든 파일을 메모리에 저장하지 않고 필요할때마다 읽기 때문에 메모리를 효율적으로 사용한다.

 

데이터셋의 샘플은 {'path': path_wave, 'target': label} 의 사전 형태를 갖는다. 선택적 인자인 transform 을 통해 필요한 전처리 과정을 샘플에 적용할 수 있다. transform 에 대해서는 뒷부분에서 조금 더 자세히 살펴보기로 한다.

 

클래스를 인스턴스화 하고, 데이터 샘플을 통해서 반복한다. 첫번째 4개의 크기를 출력하고, 샘플들의 wave와 target을 보여준 것이다.

path_dataset = "~/data_speech_commands_v0.02"

dataset = SpeechCommandsDataset(path_dataset)
                                         
for i in range(len(dataset)):
    sample = dataset[i]

    print(i, sample['path_wave'], sample['target'])

Out:

0 /Users/Downloads/data_speech_commands_v0.02/right/8e523821_nohash_2.wav 7
1 /Users/Downloads/data_speech_commands_v0.02/right/bb05582b_nohash_3.wav 7
2 /Users/Downloads/data_speech_commands_v0.02/right/988e2f9a_nohash_0.wav 7
3 /Users/Downloads/data_speech_commands_v0.02/right/a69b9b3e_nohash_0.wav 7
...

 

Transform

 

뉴럴 네트워크 학습을 위해서 우리는 다양한 형태의 데이터 변환이 필요할 수 있다. 예를들어, 음성 신호처리에서는 다음과 같은 다양한 transforms이 필요하다.

 

  1. 음성 신호를 time domain 혹은 frequency domain 에서 분석해야 한다.
  2. 모든 wave 파일의 길이가 상이한 특성 때문에 파일들의 길이를 Fix 하여 데이터를 재구성 한 후, 원하는 뉴럴네트워크 모델을 학습해야 한다.
  3. Data augmentation 적용 ( Time Streching / Shift / Add Noise )

모든 transform 은 클래스로 작성하여 클래스가 호출될 때마다 Transform의 매개변수가 전달 되지 않아도 되게 만드는 것이 좋다. 이를 위해 __call__ 함수와 __init__ 함수를 포함한 목적에 맞는 클래스를 구현한다.

 

이 페이지에서는 4가지 Transform 클래스를 구현한다.

  1. LoadAudio : Audio를 librosa library를 사용하여 time domain data로 로드한다.
  2. FixAudioLength : time domain audio 신호를 1초를 기준으로 zero-padding 하거나, truncates 시켜서 fixed length로 변환한다.
  3. ToMelSpectrogram : time domain 신호로부터 freqency domain log Mel filterbank 특징벡터로 변경한다.
  4. ToTensor : numpy 벡터를 torch tensor type 으로 변경한다.
import torch
import librosa
import numpy as np

class LoadAudio(object):
    """Loads an audio into a numpy array."""

    def __init__(self, sample_rate=16000):
        self.sample_rate = sample_rate

    def __call__(self, data):
        path = data['path']
        if path:
            samples, sample_rate = librosa.load(path, self.sample_rate)
        else:
            # silence
            sample_rate = self.sample_rate
            samples = np.zeros(sample_rate, dtype=np.float32)
        data['samples'] = samples
        data['sample_rate'] = sample_rate
        return data

class FixAudioLength(object):
    """Either pads or truncates an audio into a fixed length."""

    def __init__(self, time=1):
        self.time = time

    def __call__(self, data):
        samples = data['samples']
        sample_rate = data['sample_rate']
        length = int(self.time * sample_rate)
        if length < len(samples):
            data['samples'] = samples[:length]
        elif length > len(samples):
            data['samples'] = np.pad(samples, (0, length - len(samples)), "constant")
        return data
        
class ToMelSpectrogram(object):
    """Creates the mel spectrogram from an audio. The result is a 32x32 matrix."""

    def __init__(self, n_mels=32):
        self.n_mels = n_mels

    def __call__(self, data):
        samples = data['samples']
        sample_rate = data['sample_rate']
        s = librosa.feature.melspectrogram(samples, sr=sample_rate, n_mels=self.n_mels)
        data['mel_spectrogram'] = librosa.power_to_db(s, ref=np.max)
        return data

class ToTensor(object):
    """Converts into a tensor."""

    def __init__(self, np_name, tensor_name, normalize=None):
        self.np_name = np_name
        self.tensor_name = tensor_name
        self.normalize = normalize

    def __call__(self, data):
        tensor = torch.FloatTensor(data[self.np_name])
        if self.normalize is not None:
            mean, std = self.normalize
            tensor -= mean
            tensor /= std
        data[self.tensor_name] = tensor
        return data

 

Transform 구성(Compose)

Trasform 들이 잘 작성되었나 확인해보자.

 

위에 정의한 클래스들을 사용하여, path_wave로부터 raw audio data를 메모리로 load 하고 1초 기준으로 zero-padding/truncate 하고, MelSpectrogram으로 변환 후 Torch Tensor 타입으로 변경한다.

 

torchvision.transforms.Compose 는 위의 클래스에 정의 일을 하도록 호출할 수 있는 클래스이다.

from torchvision import transforms

n_mels=40
batch_size=4
use_gpu=False
num_dataloader_workers_=1

path_dataset = "/Users/Downloads/data_speech_commands_v0.02"

dataset = SpeechCommandsDataset(path_dataset)

_loadaudio = LoadAudio()
_fixaudiolength = FixAudioLength()
_melSpec = ToMelSpectrogram()

composed = transforms.Compose([LoadAudio(),
                                FixAudioLength(),
                                ToMelSpectrogram(n_mels=n_mels), 
                                ToTensor('mel_spectrogram', 'input')])


sample = dataset[0]

transformed_sample = _loadaudio(sample)
print(transformed_sample['samples'])

transformed_sample = _fixaudiolength(sample)
print(transformed_sample['samples'])

transformed_sample = _melSpec(sample)
print(transformed_sample['mel_spectrogram'])

transformed_sample = composed(sample)
print(transformed_sample['path_wave'], transformed_sample['input'].size(), transformed_sample['target'])

Out:

[ 0.0000000e+00  0.0000000e+00 -3.0517578e-05 ... -6.1035156e-05
 -6.1035156e-05 -6.1035156e-05]
[ 0.0000000e+00  0.0000000e+00 -3.0517578e-05 ... -6.1035156e-05
 -6.1035156e-05 -6.1035156e-05]
[[-80.      -80.      -80.      ... -80.      -80.      -80.     ]
 [-76.06169 -78.95192 -80.      ... -80.      -80.      -80.     ]
 [-80.      -80.      -80.      ... -80.      -80.      -80.     ]
 ...
 [-80.      -80.      -80.      ... -80.      -80.      -80.     ]
 [-80.      -80.      -80.      ... -80.      -80.      -80.     ]
 [-80.      -80.      -80.      ... -80.      -80.      -80.     ]]
/Users/Downloads/data_speech_commands_v0.02/right/8e523821_nohash_2.wav torch.Size([40, 32]) 7

 

Custom dataset에 적용

 

이제 Transform 클래스들을 기본 custom dataset 클래스에 적용해보자.

 

from torchvision import transforms
from tqdm import tqdm

n_mels=40
batch_size=4
use_gpu=False
num_dataloader_workers=1

# path_dataset = "~/data_speech_commands_v0.02"
path_dataset = "/Users/Downloads/data_speech_commands_v0.02"

dataset = SpeechCommandsDataset(path_dataset,
                                transforms.Compose([LoadAudio(),
                                         		FixAudioLength(),
                                         		ToMelSpectrogram(n_mels=n_mels), 
                                         		ToTensor('mel_spectrogram', 'input')]))
                                         

dataloader = torch.utils.data.DataLoader(dataset,
			batch_size=batch_size,
            		shuffle=False,
           		pin_memory=use_gpu, 
            		num_workers=num_dataloader_workers)
                    

for batch in tqdm(dataloader, unit="audios", unit_scale=dataloader.batch_size):
    inputs = batch['input']
    targets = batch['target']
    print(inputs.size(), targets.size())

 

요약

wave 파일 전체를 메모리에 올리지 않고 필요할 때마다 로드 시키고, 원하는 형태의 데이터로 transform을 메모리상에서 할 수 있다.

 

전체 코드 공유

import torch
import os
import librosa
import numpy as np
from torchvision import transforms
from tqdm import tqdm

CLASSES = 'unknown, silence, yes, no, up, down, left, right, on, off, stop, go'.split(', ')

class SpeechCommandsDataset(torch.utils.data.Dataset):
    """Google speech commands dataset. Only 'yes', 'no', 'up', 'down', 'left',
    'right', 'on', 'off', 'stop' and 'go' are treated as known classes.
    All other classes are used as 'unknown' samples.
    """

    def __init__(self, folder, transform=None, classes=CLASSES, silence_percentage=0.1):
        """
          Args:
          folder (string): Path folder.
          transform (callable, optional): Optional transform to be applied
          on a sample.
          class (string): list

        """
        all_classes = [d for d in os.listdir(folder) if os.path.isdir(os.path.join(folder, d)) and not d.startswith('_')]
        
        class_to_idx = {classes[i]: i for i in range(len(classes))}
        for c in all_classes:
            if c not in class_to_idx:
                class_to_idx[c] = 0

        data = []
        for c in all_classes:
            d = os.path.join(folder, c)
            target = class_to_idx[c]
            for f in os.listdir(d):
                path = os.path.join(d, f)
                data.append((path, target))

        # add silence
        target = class_to_idx['silence']
        data += [('', target)] * int(len(data) * silence_percentage)

        self.classes = classes
        self.data = data
        self.transform = transform

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index):
        path, target = self.data[index]
        data = {'path_wave': path, 'target': target}

        if self.transform is not None:
            data = self.transform(data)
        return data
  
class LoadAudio(object):
    """Loads an audio into a numpy array."""

    def __init__(self, sample_rate=16000):
        self.sample_rate = sample_rate

    def __call__(self, data):
        path = data['path_wave']
        if path:
            samples, sample_rate = librosa.load(path, self.sample_rate)
        else:
            # silence
            sample_rate = self.sample_rate
            samples = np.zeros(sample_rate, dtype=np.float32)
        data['samples'] = samples
        data['sample_rate'] = sample_rate
        return data

class FixAudioLength(object):
    """Either pads or truncates an audio into a fixed length."""

    def __init__(self, time=1):
        self.time = time

    def __call__(self, data):
        samples = data['samples']
        sample_rate = data['sample_rate']
        length = int(self.time * sample_rate)
        if length < len(samples):
            data['samples'] = samples[:length]
        elif length > len(samples):
            data['samples'] = np.pad(samples, (0, length - len(samples)), "constant")
        return data
        
class ToMelSpectrogram(object):
    """Creates the mel spectrogram from an audio. The result is a 32x32 matrix."""

    def __init__(self, n_mels=32):
        self.n_mels = n_mels

    def __call__(self, data):
        samples = data['samples']
        sample_rate = data['sample_rate']
        s = librosa.feature.melspectrogram(samples, sr=sample_rate, n_mels=self.n_mels)
        data['mel_spectrogram'] = librosa.power_to_db(s, ref=np.max)
        return data

class ToTensor(object):
    """Converts into a tensor."""

    def __init__(self, np_name, tensor_name, normalize=None):
        self.np_name = np_name
        self.tensor_name = tensor_name
        self.normalize = normalize

    def __call__(self, data):
        tensor = torch.FloatTensor(data[self.np_name])
        if self.normalize is not None:
            mean, std = self.normalize
            tensor -= mean
            tensor /= std
        data[self.tensor_name] = tensor
        return data
        
        
        
        


n_mels=40
batch_size=4
use_gpu=False
num_dataloader_workers=1

path_dataset = "/Users/Downloads/data_speech_commands_v0.02"

dataset = SpeechCommandsDataset(path_dataset,
                                transforms.Compose([LoadAudio(),
                                         		FixAudioLength(),
                                         		ToMelSpectrogram(n_mels=n_mels), 
                                         		ToTensor('mel_spectrogram', 'input')]))
                                         

dataloader = torch.utils.data.DataLoader(dataset,
			batch_size=batch_size,
            		shuffle=False,
           		pin_memory=use_gpu, 
            		num_workers=num_dataloader_workers)
                    

for batch in tqdm(dataloader, unit="audios", unit_scale=dataloader.batch_size):
    inputs = batch['input']
    targets = batch['target']
    print(inputs.size(), targets.size())
    
    

Pytorch 라이브러리 개요

파이토치는 딥러닝 프로젝트를 빌드(build)하는 데 도움을 주는 파이썬 프로그램용 라이브러리/프레임워크 이다.

 

파이토치는 대부분 C++과 CUDA 언어를 기반으로 만들어졌다.

 

파이토치는 수학적 연산을 가속화 하고자 코어 데이터 구조인 텐서(Tensor)를 제공한다. 이는 NumPy 배열(array)과 비슷한 다차원 배열이고, CPU 또는 GPU에서 연산이 가능하다.

 

파이토치에서 모든 연산은 텐서에서 기초적으로 제공되고, torch.autograd에서 정제된다.

 

파이토치에서 신경망을 구성하기 위한 대부분의 모듈은 코어 모듈(torch.nn)의 하위 모듈로 [nn.Conv1d/nn. ReLU/nn.MSELoss] 등과 같이 여러 Layers/Activation/Loss 를 지원한다. 모델의 최적화를 위해서는 torch.optim을 지원한다.

 

효율적인 학습을 위한 데이터 핸들링에 필요한 기능들 torch.utils.data.dataloader 를 통해 지원한다.

 

Multi-Node / Multi-GPU 머신을 활용한 분산/병렬 처리를 데이터 로딩과 학습 연산에 사용할 수 있도록 torch.nn.DataParallel torch.distributed를 지원한다. 관련하여 다른 글에서 정리해보고 싶다!! [관련 내용]

 

파이토치에서는 torch.utils.tensorboard 를 통해 Tensorboard를 지원한다.

 

또한 각각 도메인에서 활용가능한 PyTorch Library들이 존재 한다.

파이토치는 딥러닝 모델 성능 향상을 위해서 여러가지 Quantization 방법을 지원한다.

 

마지막으로 파이토치는 배포 환경을 고려하여 high performance inference 위한 TorchScript를 제공한다. TorchScript는 Python 인터프리터의 비용을 줄이고 Python 런타임으로부터 독립적으로 모델을 실행시키기 위한 방법이다. 효율적 연산을 위해 Just in Time(JIT)을 지원한다.


Rerference

 

[1] www.pytorch.org/

[2] www.yytorch.org/assets/deep-learning/Deep-Learning-with-PyTorch.pdf

 

개요

 

음성 인식, 음성 합성, 오디오 신호 처리 등의 기술을 익히기 위해서는 신호에 대한 기본적인 지식과 컴퓨터 상에서 다룰 수 있는 능력이 있어야 합니다. 신호는 시간 영역과 주파수 영역에서 분석할 수 있으며 파이썬으로 Librosa 라이브러리를 사용하여 손쉽게 분석이 가능합니다.

 

오디오 파일 Load

 

오디오 파일은 일반적으로 wav, pcm 등의 형식으로 저장되며 sampling 개념을 사용하여 디지털화 됩니다. 오디오 데이터 처리를 위해 대표적으로 사용하는 Python 라이브러리는 librosa 입니다. librosa를 사용하여, 오디오 파일을 로드 해보겠습니다.

 

import librosa

audio_data = 'example.wav'

# 오디오 파일은 명시된 특정 샘플 속도 (sr)로 샘플링 된 후 NumPy 배열로 load 된다.
x = librosa.load(audio_data, sr=16000)

 

The sampling rate (sr) 는 sound의 초당 sample (data points) 수 입니다. 예를 들어 sampling frequency가 44kHz 인 경우 60 초 길이의 파일은 2,646,000(44000*60) 개의 샘플로 구성되어 있습니다.

 

 

시간 영역(TIme-domain) 에서 분석

 

일반적으로 음성/오디오 신호는 시간영역과 주파수영역에서 분석을 할 수 있습니다. 위에서 메모리 상에 load한 샘플링된 신호(데이터)를 시간영역에서 시간에 따른 파형의 진폭을 표현 해보겠습니다.

 

신호를 시각화하고 plotting 하기 위해 Python 시각화 대표 라이브러리인 Matplotlib 를 사용합니다.

 

import matplotlib.pyplot as plt
import librosa.display

plt.figure(figsize=(14, 5))

librosa.display.waveplot(x, sr=16000)

 

주파수 영역(Frequency-domain) 에서 분석

 

 

다음으로 주파수 영역에서 신호를 그려보겠습니다.

spectrogram을 사용하면 시간에 따라 주파수 스펙트럼이 어떻게 변화하는지 확인할 수 있습니다.

 

spectrogram : 시간에 따라 변화하는 신호의 주파수 스펙트럼의 크기를 시각적으로 표현한 것

 

librosa 라이브러리에 포함되어 있는 "Short Time Fourier Transform (STFT)" 를 사용하여 시간영역의 값을 주파수 영역에서 표현한 것입니다. STFT를 통해 구할 수 있는 값은 magnitude, phase로 나눌 수 있고, spectrogram은 magnitude만 가지고, 시간축에 대해서 표현하는 것입니다. abs()함수를 통해 magnitude를 구한후 db scale로 변경하여 다음과 같이 시간에 따른 스펙트럼의 크기를 확인할 수 있습니다.

 

X = librosa.stft(x)

X_mag = abs(X)

# energy levels(dB) 로 변경
Xdb = librosa.amplitude_to_db(X_mag)

plt.figure(figsize=(20, 5))
librosa.display.specshow(Xdb, sr=16000, x_axis='time', y_axis='hz')
plt.colorbar()

+ Recent posts