Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Saturday, February 14, 2026

Numpy Tingkat Lanjut

Ada beberapa perintah untuk menaikkan skill pemrograman Python Numpy (dan Scipy) ke level lanjut. Jika Anda (termasuk saya) paham dan bisa menginterpretasikan output perintah tersebut, maka kita sudah siap naik ke level Numpy selanjutnya.

1. Blok Memory (np.nbytes)

Sering kali kita hanya fokus pada bentuk (shape) array, namun melupakan "berat"nya. Perintah `np.nbytes` menginformasikan berapa banyak memori RAM yang sebenarnya kita pakai. Seseorang yang paham bahwa mengubah tipe data (dtype) dari float64 ke float32 bisa memangkas konsumsi memori hingga 50%, yang berarti mempercepat proses komputasi secara drastis. Di sini, kita belajar bahwa array bukan sekadar wadah angka, tetapi blok memori yang harus dikelola secara efisien. Contoh:
import numpy as np

a = np.zeros((1000, 1000), dtype=np.float64)
print(a.nbytes)
# Karena float64 = 8 byte, maka total memori:
# 1000 x 1000 x 8 byte = 8.000.000 byte (~8 MB)
Konsep penting:
  • Ukuran memori = jumlah elemen × ukuran tipe data.
  • Mengganti float64 ke float32 langsung mengurangi memori setengahnya.
  • Pada skala besar (misalnya deep learning atau audio processing), ini sangat krusial.
Jika kita tidak sadar ukuran memori, kita akan mudah "membunuh" RAM sendiri.

2. Stride

Jika nbytes adalah beratnya, maka stride adalah cara kita melangkah di atas memori tersebut. Stride menentukan berapa byte yang harus "diloncati" komputer untuk menuju ke elemen berikutnya di setiap dimensi. Stride adalah jarak (dalam byte) yang harus dilompati untuk berpindah ke elemen berikutnya pada setiap dimensi. Konsep inilah yang menjawab misteri mengapa operasi transpose pada array besar bisa sangat cepat. Karena Numpy tidak benar-benar memindahkan data di memori; ia hanya mengubah pola langkah (stride) tersebut. Memahami stride berarti memahami bagaimana komputer "melihat" data array secara linear di dalam RAM. Contoh:
a = np.arange(12).reshape(3, 4)
print(a.strides)
# Jika dtype=int64 (8 byte), kemungkinan output:
# (32, 8)
Artinya:
  • Untuk pindah ke baris berikutnya → lompat 32 byte (4 kolom × 8 byte).
  • Untuk pindah ke kolom berikutnya → lompat 8 byte.
Ini penting karena:
  • Operasi transpose (a.T) sering tidak membuat copy data, hanya mengubah stride.
  • Pemahaman stride membantu kita tahu kapan NumPy membuat copy atau hanya view.
  • Dalam optimasi performa, akses memori yang kontigu jauh lebih cepat.
Stride adalah jembatan antara konsep matematis array dan realitas fisik memori.

3. Broadcasting

Broadcasting adalah seni "mengada-ada" secara efisien. Broadcasting memungkinkan operasi antara array dengan ukuran yang berbeda tanpa perlu menyalin data secara fisik untuk menyamakan ukurannya. Misalnya, menjumlahkan array (3,3) dengan vektor (3,) secara otomatis. Seorang programmer biasa mungkin akan melakukan looping atau padding manual, tetapi level "Dewa" membiarkan Numpy melakukan broadcasting internal yang jauh lebih cepat karena terjadi di level C. Kita hemat memori, kita hemat waktu. Contoh:
a = np.array([[1], [2], [3]])
b = np.array([10, 20, 30])

c = a + b
Secara mental:
  • `a` bentuk (3,1)
  • `b` bentuk (3,)
  • NumPy "membentangkan" dimensi agar cocok → hasil (3,3)
Tanpa broadcasting, kita harus membuat array besar secara manual. Dengan broadcasting:
  • Kode lebih ringkas
  • Lebih hemat memori
  • Lebih cepat
Kunci memahami broadcasting:
  • Dimensi dibandingkan dari kanan ke kiri
  • Ukuran cocok jika sama atau salah satunya 1
Kalau ini sudah bisa kita prediksi tanpa menjalankan kode, berarti otak kita sudah mulai "NumPy-native".

4. Fancy Indexing

Fancy indexing adalah indexing menggunakan array atau list indeks. Berbeda dengan slicing biasa yang menghasilkan view (tampilan tanpa menyalin data), Fancy Indexing selalu menghasilkan copy (data baru). Memahami perbedaan ini krusial agar kita tidak membludakkan memori secara tidak sengaja saat memproses data besar. Contoh:
a = np.array([10, 20, 30, 40])
idx = [0, 2]
print(a[idx])
# Output: [10 30]
Perbedaan penting:
  • Slice (a[1:3]) → biasanya view (tidak copy)
  • Fancy indexing → selalu membuat copy
Ini sangat penting untuk:
  • Mengontrol penggunaan memori
  • Menghindari bug akibat modifikasi data yang tidak disengaja
Misalnya:
b = a[idx]
b[0] = 999
Di sini, a tidak akan berubah karena b adalah copy. Kalau kita tidak memahami ini, kita bisa salah kaprah saat debugging.

5. Vectorization

Vectorization adalah teknik menghilangkan loop eksplisit (perulangan for di Python) dan menggantinya dengan operasi array tunggal. Loop Python lambat karena interpretasi per-baris, namun operasi vektor Numpy terjadi di level kompilasi C/Fortran yang sangat cepat. Ketika kita memahami Broadcasting, Stride, dan Memory, Vectorization menjadi senjata utama untuk membuat kode berjalan 10x hingga 100x lebih kencang. Perulangan for (lambat):
result = []
for x in a:
    result.append(x * 2)
Vektorisasi (cepat):
result = a * 2
Kenapa jauh lebih cepat?
  • Loop Python berjalan di level interpreter (lambat)
  • Operasi NumPy berjalan di C (cepat)
  • Akses memori lebih efisien
Vectorization bukan sekadar "gaya", tapi filosofi berpikir: Berpikir dalam bentuk operasi matriks, bukan iterasi elemen.

Pesan Penting

Perlu diingat, bagian terpenting adalah memahami konsep array dengan perintah di atas pada tataran level otak kita, bukan simulasi komputer. Artinya, kita bisa menebak output dari komputer (simulasi/Python Numpy) dengan otak kita dan memahaminya. Bukan asal mensimulasikan array, meskipun bisa menebak hasilnya. Inilah yang terpenting: ilmu sebelum beramal.

Referensi

  1. https://shihchinw.github.io/2019/03/performance-tips-of-numpy-ndarray.html
  2. https://pythonspeed.com/articles/numpy-memory-footprint/

Wednesday, November 19, 2025

Filter emboss (dari PIL): kenapa bagian yang berwarna gelap tidak di-emboss

Iseng-iseng, saya mencoba menggunakan filter emboss pada ijasah S1 (versi Inggris) saya. Di luar dugaaan, filter embos tidak muncul pada jas yang saya pakai.


Kode yang saya gunakan:
from PIL import Image, ImageFilter
import matplotlib.pyplot as plt

img = Image.open('/mnt/data/ijasah_s1_bta_eng.png')
emboss = img.filter(ImageFilter.EMBOSS)

plt.imshow(emboss)
plt.axis('off')
Hasil: 

filter emboss tidak tampak pada jas




Alasan kenapa emboss tidak muncul:
  • Tinta berwarna (misalnya merah atau biru) dan warna jas memiliki nilai luminance yang hampir sama (fenomena color blending by luminance contrast);
  • Perbedaan kontrasnya sangat kecil (tinta menyerap cahaya secara berbeda, stempel tampak memudar atau bahkan “hilang”);
  • Emboss tidak dapat menonjolkannya;
  • Menunjukkan fenomena optik normal pada stempel di atas area gelap (low optical visibility).


Wednesday, March 06, 2024

Menggunakan (Mini) Conda Untuk Komersial (Institusi Riset, dll)

Per 30 September 2020, term of service Anaconda berubah; lisensi berbayar dibutuhkan untuk menggunakan Anaconda untuk keperluan komersial (gratis untuk skala kecil, perusahaan kurang 200 orang pekerja). Miniconda dapat digunakan secara gratis, namun setting default dapat menyebabkan akses ke repository berbayarnya (meskipun gratis).

Solusi singkatnya adalah menambahkan `conda-forge` pada channel di `.condarc` untuk menggantikan konfigurasi default. Cek dengan `conda config --show channels` sebelum dan sesudah mengganti channel default (tidak tertulis di `.condarc`) dengan `conda-forge`.

Cek setting default channel:

$ conda config --show channels
channels:
  - defaults

Ganti channel default dengan menambahkan conda-forge di fail `.condarc` (default di direktori home)

 auto_activate_base: false
  channels: 
    - conda-forge

Cek kembali config conda:

$ conda config --show channels
channels:
  - conda-forge
Dengan cara ini, Miniconda dapat digunakan secara sah dan halal. Saya menggunakan Miniconda (daripada Anaconda) hanya untuk membuat virtual environment dengan versi python yang berbeda (misal dengan python3.8, python3.12, dll). Instalasi paket dalam python (seperti Numpy, Pandas) tetap saya lakukan dengan pip, bukan dengan Conda.

Friday, November 25, 2022

Konversi Fail Stereo ke Mono Dari Direktori Berisi Banyak Fail Suara

Skrip berikut dapat merubah fail suara stereo ke mono dari suatu direktori (termasuk subdirektori di dalamnya) yang diberikan.
Input: Direktori/folder
Output: Fail berakhiran "_mono" dengan direktori yang sama terhadap input (termasuk subdirektori)
#!/usr/bin/env python3
import os
import argparse
import glob
from pydub import AudioSegment

def stereo2mono(files):
    """Convert all files from stereo to mono.
    Note: this would effectively also create a copy of files that were already in a mono format
    Parameters
    ----------
    files : iterable
        Sequence of files
    Example use:
    ```
    $ python3 stereo2mono.py -f /path/to/audio/files/
    ```
    Then you may remove the files that do not contain the '_mono' tag with: 
    $ find . -name "*-??.wav" -delete # for emovo
    """
    for f in files:
        print(f"Converting {f}")
        # Load audio
        sound = AudioSegment.from_wav(f)
        # Convert to mono
        sound = sound.set_channels(1)
        # Save file
        stem, ext = os.path.splitext(f)
        sound.export(f'{stem}_mono{ext}', format='wav')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Convert stereo to mono')
    parser.add_argument('-f', '--folder', 
        type=str, help='Path to wavfiles')
    args = parser.parse_args()
    files = glob.glob(args.folder + '**/*.wav', recursive=True)
    stereo2mono(files)
Contoh penggunaan
# Konversi ke mono
python3 stereo2mono.py -f tensorflow_datasets/downloads/extracted/ZIP.emovo.zip/EMOVO/
# menghapus file yang bukan mono
$ pwd
/home/bagus/tensorflow_datasets/downloads/extracted/ZIP.emovo.zip/EMOVO/
$ find . -name "*-??.wav" -delete
Referensi gist:
1. https://gist.github.com/bagustris/40b406d99820207bc804a020db169f7e

Friday, June 24, 2022

Membuka dan menyimpan file json

MENYIMPAN file JSON
Studi kasus
Misalkan kita ingin menyimpan dataset berikut (RAVDESS Speech) dalam format json yang berisi file dan labelnya (data speech emotion recognition). Untuk keperluan tersebut kita ingin memisahkan antara data training ('train_meta_data.json') dan data test ('test_meta_data.json'). Skrip berikut memenuhi tujuan tersebut.
import os
import glob
import json

data_dir = '/data/Audio_Speech_Actors_01-24/'
files = glob.glob(os.path.join(data_dir, 'Actor_??', '*.wav'))
files.sort()

data_train = []
data_test = []

for file in files:
    lab = os.path.basename(file).split('-')[2]
    if int(file[-6:-4]) < 20: # speaker 1-19 for training
        data_train.append({
            'path': file,
            'label': lab
        })
    else:                     # speaker 20-24 for test
        data_test.append({
            'path': file,
            'label': lab
        })

with open("train_meta_data.json", 'w') as f:
        json.dump(data_train, f)

with open("test_meta_data.json", 'w') as f:
        json.dump(data_test, f)

MEMBUKA file JSON
import json
filepath = '/data/Audio_Speech_Actors_01-24/train_meta_data.json'
with open(filepath, 'r') as f:
     data_train = json.load(f)

Wednesday, March 30, 2022

Mencoba Docker

Setelah sekian lama ingin mencoba docker, kali ini saya benar-benar mencobanya. Selama ini saya bisa menghindari docker karena venv dan conda tidak pernah gagal. Karena kali ini conda gagal mendukung GPU di tensorflow 1.15, maka mau tidak mau Docker menjadi solusi terbaik. 

Docker
Gambar 1. Diagram alir ketika venv gagal, berlanjut ke conda, berlanjut ke docker. Selama venv berhasil membuat environment yang diinginkan saya akan memakainya sebagai pilihan pertama, jika tidak baru mencoba conda dan docker, secara bertahap. 

Docker Untuk Menginstall Software (Paket/Library) Baru

Sampai saat ini saya masih memakai Ubuntu 16.04 untuk laptop-laptop pribadi dan Ubuntu 20.04 untuk laptop-laptop kantor. Permasalahan pada OS lama adalah kegagalan untuk menginstall software versi baru. Kasus saya adalah menginstall paket opensmile python versi terbaru (versi 2.4.1) yang membutuhkan library glibc di atas versi 2.31. Library glibc ini tidak bisa diupate. Pernah sekali saya mengupdatenya dan system saya (Centos 7) langsung rusak dan harus install ulang. Docker adalah solusinya.
 
# asumsi docker sudah terinstall
$ docker run -it ubuntu bash
# jika ada masalah permission, lakukan: sudo chmod 666 /var/run/docker.sock
$ apt update
$ apt install python3
$ apt install python3-pip
$ python3 -m pip install opensmile
$ apt install libsndfile1 libsndfile1-dev
$ apt install sox
$ apt insalll ffmpeg
root@6e7dd3cbfee1:/# python3
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import opensmile
>>> opensmile.__version__
'2.4.1'
>>> exit()
root@6e7dd3cbfee1:/# ldd --version ldd
ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

Menyimpan file docker dengan commit

Docker tidak didesain untuk menyimpan file, untuk menyimpan file (dan perubahan/update) dari docker yang sedang berjalan dan docker-docker sebelumnya gunakan perintah commit berikut.
Format:
docker commit CONTAINER_ID IMAGE
$ docker ps -a # bisa juga dengan ps -l untuk docker yang sedang berjalan
$ dokcer commit ef7d7090e3c7 ubuntu:opensmile

Docker Untuk Menginstall Software Lama (Tensorflow 1.15 dengan GPU support) 


Skenario: Saya ingin menjalankan kode di repository berikut: Efficient Bigan, dengan GPU RTX 3090, yang saat itu (saat repo itu dibuat dengan tensorflow 1.15) belum diproduksi.
Solusi:
# asumsi docker belum terinstall
$ sudo apt install docker.io
$ curl https://get.docker.com #install curl jika belum ada
$ sudo systemctl --now enable docker
  
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
      && curl -s -L https://nvidia.github.io/libnvidia-container/gpgkey | sudo apt-key add - \
      && curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
$ sudo apt update
$ docker images      # list images docker 	 	
REPOSITORY                  TAG             IMAGE ID       CREATED       SIZE
nvcr.io/nvidia/tensorflow   22.04-tf1-py3   8b2abbd886f0   2 years ago   9.51GB
$ docker run --gpus all -it --rm nvcr.io/nvidia/tensorflow:22.04-tf1-py3
$ git clone https://github.com/houssamzenati/Efficient-GAN-Anomaly-Detection.git
$ cd Efficient-GAN-Anomaly-Detection
$ python3 main.py gan mnist run --nb_epochs=10 --label=0 
______   _____       _____       ____                                                                                                                   
|_     `.|_   _|     / ___ `.   .'    '.                                                                                                                 
  | | `. \ | |      |_/___) |  |  .--.  |                                                                                                                
  | |  | | | |   _   .'____.'  | |    | |                                                                                                                
 _| |_.' /_| |__/ | / /_____  _|  `--'  |                                                                                                                
|______.'|________| |_______|(_)'.____.'                                                                                                                 
                                                                                                                                                         
                                                                                                                                                         
[09:48:37 INFO @AnomalyDetection] Running script at gan.run_mnist                                                                                        
2022-03-30 09:48:37.916727: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudart.so.11.0        
[09:48:37 WARNING @tensorflow] Deprecation warnings have been disabled. Set TF_ENABLE_DEPRECATION_WARNINGS=1 to re-enable them.                          
[09:48:38 WARNING @tensorflow] From /TA/demo/Efficient-GAN-Anomaly-Detection/gan/run_mnist.py:4: The name tf.ConfigProto is deprecated. Please use tf.com
pat.v1.ConfigProto instead.                                                     
...
2022-03-30 09:48:44.229713: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcublas.so.11
2022-03-30 09:48:44.683684: I tensorflow/stream_executor/platform/default/dso_loader.cc:49] Successfully opened dynamic library libcudnn.so.8
[09:48:51 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 0 | time = 7s | loss gen = 0.7218 | loss dis = 1.3530 
[09:48:54 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 1 | time = 3s | loss gen = 0.7747 | loss dis = 1.2383 
[09:48:57 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 2 | time = 3s | loss gen = 0.8793 | loss dis = 1.0877 
[09:49:01 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 3 | time = 3s | loss gen = 1.0170 | loss dis = 0.9235 
[09:49:04 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 4 | time = 3s | loss gen = 1.1724 | loss dis = 0.7746 
[09:49:07 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 5 | time = 3s | loss gen = 1.3378 | loss dis = 0.6493 
[09:49:11 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 6 | time = 3s | loss gen = 1.5056 | loss dis = 0.5522 
[09:49:14 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 7 | time = 3s | loss gen = 1.6597 | loss dis = 0.4744 
[09:49:17 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 8 | time = 3s | loss gen = 1.7992 | loss dis = 0.4116 
[09:49:21 INFO @GAN.train.mnist.fm.0] Epoch terminated
Epoch 9 | time = 3s | loss gen = 1.8616 | loss dis = 0.3848 
[09:49:21 WARNING @GAN.train.mnist.fm.0] Testing evaluation...
[09:54:19 INFO @GAN.train.mnist.fm.0] Testing : mean inference time is 1.5292
Testing | PRC AUC = 0.7183

Me-mount direktori 

Alih-alih membuat commit setiap kali kita mengedit, kita bisa memount direktori saat memanggil docker.
Skenario: Mengedit file diluar docker, mengeksekusi file (misal python) didalam docker. Caranya adalah sebagai berikut.
$ docker run --gpus all -it --mount type=bind,source=/TA,target=/TA tf1.15:py3
Dimana `source` adalah sumber direktori, dan `target` adalah target direktori. Image docker sebelumnya sudah saya commit menjadi `tf1.15` dengan tag `py3`. 

Menambahkan docker ke sudoers

Ketika membuka docker, terlihat bahwa tanda di baris kiri terminal adalah sharp #, ini menandakan kita masuk sebagai super user (sudo). Karenanya ada error saat membuka docker pertama kali (harus chmod 666 dulu). Solusi ini hanya berjalan sementara, untuk solusi permanen kita bisa menambahkan docker ke grup sudoers sebagai berikut.
# menambahkan grup baru bernama docker
$ sudo groupadd docker
# menambahkan $USER ke grup docker
$ sudo usermod -aG docker $USER
# merefresh grup
$ newgrp docker
Demikian tutorial singkat docker ini, semoga bermanfaat.

Referensi;
[1] https://docs.docker.com/engine/install/linux-postinstall/

Thursday, January 27, 2022

Ekstraksi Fitur Akustik dengan Torchaudio

Kode di bawah ini mengekstrak tiga fitur akustik -- spectrogram, melspectrogram, dan mfcc -- dari sebuah file audio "filename" (wav, mp3, ogg, flacc, dll). Ketiga fitur akustik tersebut merupakan fitur-fitur akustik terpenting dalam pemrosesan sinyal wicara. Keterangan singkat ada di dalam badan kode. Hasil plot ada di bawah kode.

###
#import torch
import torchaudio
from matplotlib import pyplot as plt
import librosa

# show torchaudio version
# torch.__version__
print(torchaudio.__version__)


def plot_spectrogram(spec, title=None, ylabel="freq_bin", aspect="auto",
                     xmax=None):
    fig, axs = plt.subplots(1, 1)
    axs.set_title(title or "Spectrogram (db)")
    axs.set_ylabel(ylabel)
    axs.set_xlabel("frame")
    im = axs.imshow(librosa.power_to_db(spec), origin="lower", aspect=aspect)
    if xmax:
        axs.set_xlim((0, xmax))
    fig.colorbar(im, ax=axs)
    plt.show(block=False)


filename = "/home/bagus/train_001.wav"  # change with your file
waveform, sample_rate = torchaudio.load(filename)

# Konfigurasi untuk spectrogam, melspectrogram, dan MFCC
n_fft = 1024
win_length = None  # jika None maka sama dengan n_fft
hop_length = 512   # y-axis in spec plot
n_mels = 64  # y-axis in melspec plot
fmin = 50
fmax = 8000
n_mfcc = 40  # must be smaller than n_mels, will be y-axis in plot

# definisi kelas untuk ekstraksi spektrogram
spectrogram = torchaudio.transforms.Spectrogram(
    n_fft=n_fft,
    win_length=win_length,
    hop_length=hop_length,
    center=True,
    pad_mode="reflect",
    power=2.0,
)

# Show plot of spectrogram
spec = spectrogram(waveform)
print(spec.shape)  # torch.Size([1, 513, 426])
plot_spectrogram(spec[0], title=f"Spectrogram - {str(filename)}")


## kelas untuk ekstraksi melspectrogram
melspectogtram = torchaudio.transforms.MelSpectrogram(
    sample_rate=sample_rate,
    n_fft=n_fft,
    win_length=win_length,
    hop_length=hop_length,
    n_mels=n_mels,
    f_min=fmin,
    f_max=fmax,
)

# Calculate melspec
melspec = melspectogtram(waveform)
melspec.shape # torch.Size([1, 513, 426])
plot_spectrogram(melspec[0], title=f"Melspectrogam - {str(filename)}")

## kelas untuk ekstraksi MFCC
mfcc_transform = torchaudio.transforms.MFCC(
    sample_rate=sample_rate,
    n_mfcc=n_mfcc,
    melkwargs={
      'n_fft': n_fft,
      'n_mels': n_mels,
      'hop_length': hop_length,
      'mel_scale': 'htk',
    }
)

# plot mfcc
mfcc = mfcc_transform(waveform)
print(mfcc.shape) # torch.Size([1, 40, 426])
plot_spectrogram(mfcc[0], title=f"MFCC - {str(filename)}")
###

Plot 


Thursday, October 21, 2021

Argumen "self" pada Python OOP (temasuk __init__ dan instance variable)

Saat pertama kali mengetahui banyaknya kata "self" pada bahasa pemrograman Python, saya terhenyak. Teknik ini banyak sekali dipakai (a must!), dan saya sama sekali tidak memahaminya. Dua tahun lebih berlalu sejak saya ingin mengenal "self" pada Python (2019-02-02), dan kali ini saya ingin serius berkenalan dengannya.

"Kelas" dan OOP

Bahasa pemrograman python diadopsi secara meluas karena keluwesannya, baik secara prosedural maupun object-oriented programming (OOP). Pada kasus pertama, yakni prosedural, cukup sederhana dan intuitif. Misal:

def kali(x, y):
    return x*y

Secara singkat kita bisa paham bahwa "kali" adalah sebuah fungsi untuk mengalikan dua variabel, x dan y. Hal ini berbeda dengan kelas berikut

class kaliX (object):
    def __init__(self, data):
        self.data = int(data)
    def kali(self, other):
        return self.data * other.data
Disinilah saya kebingungan memahami apa itu self. Kembali ke dua potongan kode di atas. Kalau kita run di IPython, akan terlihat hasilnya sama sebagai berikut (hanya terlihat ketika mengakses blog ini via PC).



Jadi apa itu self?

Sebelum masuk ke self, kita masuk ke __init__ dulu karena __init__ disebut lebih dulu pada OOP, pada contoh di atas. __init__ adalah inititalization method pada badan OOP atau kelas. Fungsi pertama kelas ini, yakni didefinisikand dengan def __init__ akan dipanggil setiap instance dari kelas dibuat. Instance sederhananya ya kelas itu sendiri. Nah self adalah variabel pertama dari fungsi __init__. Variabel kedua fungsi __init__ adalah `data`. Berbeda dengan bahasa pemrograman lain (yang saya juga tidak mengerti), Python mengizinkan satu __init__ saja pada satu kelas [3].


Instance Variable

Sebagai tambahan dari self dan __init__ adalah `instance variable`. Instance variable adalah DASAR dari OOP dalam Python. Dalam contoh kelas "kaliX" di atas, instance variable-nya adalah sebagai berikut: `data` pada `self.data`. Maknanya, variabel data sebagai input kelas kaliX akan diubah menjadi integer dan menempati tempat `data` pada `self.data`. Pada fungsi `kali` yang menjadi bagian dari kelas `kaliX`, variabel `other` akan menempati `data` pada `other.data` melalui fungsi __init__ tadi. Begitu seterusnya jika ada variabel lain. Beginilah secara sederhana OOP bekerja. Instance variable bisa diisi secara eksplisit lewat `Namakelas.NamaInstanceVariable  = <nilai>`. Misalnya `my_circle.radius = 5` pada contoh berikut.

>>> class Circle:
         def __init__(self):
             self.radius = 1
    
>>> my_circle = Circle()
>>> print(my_circle.radius)
>>> 1
>>> my_circle.radius = 5
>>> print(my_circle.radius)
>>> 5


Kesimpulan

Telah dikelaskan apa itu __init__  -- sebagai fungsi initial di kelas --, self --sebagai argumen pertama __init__ --, dan instance variable -- sebagai dasar OOP di Python --.


Referensi:

  1. https://www.programiz.com/article/python-self-why
  2. https://www.digitalocean.com/community/tutorials/how-to-construct-classes-and-define-objects-in-python-3
  3. Naomi Ceder, The Quick Python Book, 2nd ed. Manning Publishing, 2018.

Monday, August 23, 2021

How-to: Install jedi-vim in python 2.7

Rationale:

  • Although nowadays python3 becomes standard, in some servers the default python is still python2.7 (mostly RHEL servers).
  • Using vim (or emacs) in remote works is a must. You may use GUI, but the setup is more complicated than CLI.
  • Using vim without plugins is hard. We should use a minimum number of plugins. The most important plugin is code completion.
  • Humans make errors inevitably. Code completion prevents typos.

Based on those rationales, this is one line command to install jedi-vim (including jedi itself!) in python 2.7.

git clone --recursive https://github.com/davidhalter/jedi-vim.git --branch 0.9.0 ~/.vim/bundle/jedi-vim
Note: you need to install vim-pathogen first to allow plugin installation via "bundle" directory.

Monday, August 16, 2021

Install sox locally in cluster without root

This time, I can't install homebrew in (AIST) cluster as I previously did in JAIST cluster. Here are steps to documents how to install SoX, one of the most important library in sound processing, locally in cluster. I tried these steps in abci.ai.

# download sox package, in this case
wget https://nchc.dl.sourceforge.net/project/sox/sox/14.4.2/sox-14.4.2.tar.gz

# extract sox package
tar xvfz sox-14.4.2.tar.gz

# change to extracted sox directory
cd sox-14.4.2

# configure in local directory, I used $HOME
./configure --prefix=$HOME

# make
make

# make install
make install
Then check it by its version
[user13432@es2 ~]$ sox --version
sox:      SoX v14.4.2
You may also need to update $PATH to make it works in your enviroment.

Wednesday, August 04, 2021

Extracting Emobase Feature Using Python-Opensmile under Windows (WSL)

This article documents my steps to extract acoustic features with "emobase" configuration on opensmile-python under Windows. I used WSL (Window Sub-System for Linux) with Ubuntu Latest (20.04). Click each image for larger size and clarity.

0. Windows Version

Here is my Windows version in which I experimented with. Other versions may give errors. To show your version, simply press the Windows button and type "about PC".
Edition	        Windows 10 Pro
Version	        20H2
Installed on	‎4/‎2/‎2021
OS build        19042.1083
Experience      Windows Feature Experience Pack 120.2212.3530.0

1. Activate WSL2

Here are the steps to activate WSL2 on Windows 10. WSL2 only works on Windows 10 version 1903 or higher, with Build 18362 or higher. For the older version, you can use WSL instead of WSL2.
a. Activate WSL using PowerShell. Press the Windows key, and enter the following.
 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart 
b. Install Linux kernel update package. Download from here.
https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi
Double click and install that .msi package.
Select WSL2 as default.
 wsl --set-default-version 2 

You need to ensure the wsl version after installing Ubuntu distro below.

2. Install Ubuntu

Press windows key and type "Microsoft Store". I choose Ubuntu (latest) instead of Ubuntu 20.04 or other versions. See image below; I already installed it.


Ensure that Ubuntu uses WSL2 as default. Check-in PowerShell with the following command (wsl -l -v).

Then click launch Ubuntu from the previous image/step, or you can type "Ubuntu" di search dash.
When launching Ubuntu for the first time, you will be prompted for the user name and password. Remember this credential. See the image below for example.

3. Install Python and pip

In Ubuntu do/type
 sudo apt update && sudo apt -y upgrade 
Enter your password. Type "y" when it is prompted.
Install Python using apt. I chose python3.7 as follows.
 sudo apt install python3.7-full 
Type "y" when it asked. See the image below for reference.

Test if the installation is successful. Type "python3.7" in Ubuntu to enter python3.7 console.

Next, we need pip to install python packages. Hence, we need to install pip first as follows.
 python3.7 -m ensurepip --upgrade 

4. Install Python-Opensmile

Since this version of python in Ubuntu is already equipped with pip, we can directly use it to install opensmile.
 python3.7 -m pip install opensmile 
See the image below for a reference.

Same as previous step, I installed IPython for my convenience. You may also need to install numpy, scipy, and matplotlib.
 python3.7 -m pip install ipython numpy scipy audb

We also need to install sox since it is required by opensmile
 sudo apt install sox 

5. Extract Emobase Feature

Now is the time to use opensmile. First, open IPython console for this python3.7.
 python3.7 -m IPython 
Import Opensmile and download emodb dataset with a specific configuration.
See the image below for your reference. Skip the parts with red cross since they contain errors (I forgot to add a comma between arguments).

Configure opensmile to extract EMOBASE feature.
smile = opensmile.Smile(
    feature_set=opensmile.FeatureSet.emobase,
    feature_level=opensmile.FeatureLevel.Functionals,
)
smile.feature_names
See image below for your reference. You can change feature_level value to "opensmile.FeatureLevel.LowLevelDescriptors" if you want LLD (LowLevelDescriptors, extracted per frame) instead of functionals (statistics of LLD). The number of emobase functional is 988 features [len(smile.features_names)].

Finally, we extract acoustic features based on these configuration.
smile.process_signal(
    signal,
    sampling_rate
)
See below image for your reference.


That's all. Usually, I save the extracted acoustic features in other format like numpy .npy files or .csv files. From my experience, this is my first extraction of emobase feature set. Previously I used gemaps, egemaps, compare2016, and emo_large configuration. Let see if this kind of feature set has advantages over others. Although intended for Windows 10, this configuration may also works for other distribution. Still, I prefer to use Ubuntu since the process is simple and straightforward. No need to set WSL2 and other things just pip and pip.

The full script to extract emobase functional features from all utterances in emodb dataset is given below. Please note that it takes a long time to process since it will download all utterances in emodb dataset according to "audb" format and extract acoustic features from them.

Example 1: Extract emobase feature from an excerpt of emodb dataset and save it as an .npy file.

import os
import time

import numpy as np
import pandas as pd

import audb
import audiofile
import opensmile

sr = 16000

# if you change code below, it will download the dataset again 
db = audb.load(
    'emodb',
    version='1.1.1',
    format='wav',
    mixdown=True,
    sampling_rate=sr,
    full_path=False,
    verbose=True,
)

smile = opensmile.Smile(
    feature_set=opensmile.FeatureSet.emobase,
    feature_level=opensmile.FeatureLevel.Functionals,
)

# If you run this program for the second time
# comment the whole db above and change db.root and db.files to (uncomment)
# db_root = audb.cached().index[0]
# db_files = pd.read_csv('/home/bagus/audb/emodb/1.1.1/fe182b91/db.files.csv')['file']

feats = []
for i in db.files:
    file = os.path.join(db.root, db.files[i])
    signal, _ = audiofile.read(
            file,
            always_2d=True,
            )
    feat = smile.process_signal(
            signal,
            sr
            )
    feats.append(feat.to_numpy().reshape(-1))

# this will save all emodb emobase feature in a single npy file
# make sure you have 'data' dir first
np.save('data/emodb_emobase.npy', feats)
  

Example 2: Extract emobase features from files under a directory ("ang") and save it in a csv file.
import os
import opensmile
import numpy as np
import glob
#from scipy.io import wavfile

# jtes angry path, 50 files
data_path ="/data/jtes_v1.1/wav/f01/ang/"
files = glob.glob(os.path.join(data_path, "*.wav"))
files.sort()

# initiate opensmile with emobase feature set
smile = opensmile.Smile(
    feature_set=opensmile.FeatureSet.emobase,
    feature_level=opensmile.FeatureLevel.Functionals,
)
smile.feature_names

# read wav files and extract emobase features on that file
feat = []

for file in files:
    print("processing file ... ", file)
    #sr, data = wavfile.read(file)
    #feat_i = smile.process_signal(data, sr)
    feat_i = smile.process_file(file)
    feat.append(feat_i.to_numpy().flatten())

# save feature as a csv file, per line, with comma
np.savetxt("jtes_f01_ang.csv", feat, delimiter=",")

  


If you face problems during following this article, let me see in comments below.

Reference:
[1] https://docs.microsoft.com/en-us/windows/wsl/install-win10
[2] https://audeering.github.io/opensmile-python/usage.html

Monday, August 02, 2021

Python: memanggil nama variabel secara dinamis dalam loop for

Misalkan kita punya data seperti ini:
a_1 = 1
a_2 = 2
a_3 = 3
Kemudian kita ingin memanggi variabel tersebut secara berurutan dalam loop for. Karena nama-nama variabel tersebut mirip dan hanya berbeda karakter terakhir saj, maka pemanggilan variabel tersebut bisa kita permudah, misalnya dengan cara "a_[i}", dimana "i" adalah indeks mulai dari i. Saya menggunakan kurung kurawal karena implementasi pada teknik yang dipakai juga seperti itu. Pemanggilan nama variabel secara dinamis seperti ini dalam Python bisa menggunakan fungsi "globals()" seperti berikut.
for i in range(1,4):
    print(globals()[f"a_{i}"])
Outputnya adalah nilai a_1, a_2, dan a_3 secara berurutan.
1
2
3

Perbedaan dengan list biasa 

Untuk menampilkan output di atas bisa saja dengan teknik berikut.
for i in [a_1, a_2, a_3]:
    print(i)
Namun tujuan saya bukan output, tapi proses pemanggilannya. Dalam banyak hal, kita butuh memanggil nama variabelnya saja dan mengakses anggota kelasnya. Misal jika variabel "a_" memiliki child (anak) .panjang, .lebar, .tinggi, maka metode tsb (methods dalam pengertian pemrograman) bisa dipanggil dengan teknik nama variabel dinamis di atas. Akan sulit jika memakai list biasa.

Thursday, July 08, 2021

Japanese sentiment analysis

Make virtual environment:
 python3.6 -m venv hf-venv # hf-venv: huggingface-virtualenv 
Activate virtualenv:
 source hf-ven/bin/activate 
Install Huggingface's Transformer with torch:
 python3.6 -m pip install transormer[torch] 
Install other dependencies (required if you don't have them)
 python3.6 -m pip install --user -U fugashi ipadic pillow 
Try to make Japanese sentiment analysis as follows.
>>> from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
>>> model = "daigo/bert-base-japanese-sentiment"
>>> tokenizer = "daigo/bert-base-japanese-sentiment"
Run it! Output:
>>> print(pipeline("sentiment-analysis", model=model, tokenizer=tokenizer, return_all_scores=True)("家で見守ってます")) 
[[{'label': 'ポジティブ', 'score': 0.9886383414268494},
  {'label': 'ネガティブ', 'score': 0.01136170607060194}]]
List of installed packages
(hf-venv) bagus@qc2gtr3:~$ pip freeze
certifi==2021.5.30
chardet==4.0.0
click==8.0.1
dataclasses==0.8
filelock==3.0.12
fugashi==1.1.0
huggingface-hub==0.0.12
idna==2.10
importlib-metadata==4.6.1
ipadic==1.0.0
joblib==1.0.1
numpy==1.19.5
packaging==21.0
Pillow==8.3.1
pyparsing==2.4.7
PyYAML==5.4.1
regex==2021.7.6
requests==2.25.1
sacremoses==0.0.45
six==1.16.0
tokenizers==0.10.3
torch==1.9.0
tqdm==4.61.2
transformers==4.8.2
typing-extensions==3.10.0.0
urllib3==1.26.6
zipp==3.5.0

Troubleshooting when there is not enough (ram) memory to install torch

Since torch is big enough (831 MB for v1.9) there is not enough memory in my home directory to install. As an alternative, I made another temporary directory for the cache and pointed it out to that directory when installing torch.
$ pip cache dir # works on pip==21.1.3
/home/bagustris/.cache/pip
$ mkdir -p /media/bagustris/atmaja/tmp
$ TMPDIR=/media/bagustris/atmaja/tmp/ pip install --cache-dir=/media/bagustris/atmaja/tmp/ transformers[torch]

Tuesday, June 22, 2021

Sympy: Menyelesaikan Persamaan Matrik

Sympy melakukan proses komputasi yang tidak bisa dilakukan oleh Numpy: simbolik. Misal kita punya persamaan berikut

$$2.10^{6}\left[\begin{array}{crr} 1 & -1 & 0 \\ -1 & 2 & -1 \\ 0 & -1 & 1 \end{array}\right]\left\{\begin{array}{l} 0 \\ u_{2} \\ u_{3} \end{array}\right\}=\left[\begin{array}{c} F_{1}+1500 \\ 9000 \\ 7500 \end{array}\right] $$
Dalam bentuk gambar, persamaan di atas dapat ditampilkan di bawah ini (rendering kode Latex/mathjax di atas kadang Luama....).
Untuk menyelesaikan persamaan di atas (u1=0), maka solusi dengan Sympy adalah seperti kode di bawah ini:
from sympy import *

# buat symbol
u2, u3, F1 = symbols('u2 u3 F1')

# matrik sebelah kiri (left-hand)
K = Matrix([[ 1, -1, 0], [-1, 2, -1], [0, -1, 1]]) 
Ke1 = 2e6*K
U = Matrix([0, u2, u3]).reshape(3,1)

# matrik sebelah kanan
F = Matrix([F1+1500, 9000, 7500])

# selesaikan: cari u2, u3, dan F1
sol = solve(Ke1*U-F, (u2, u3, F1))


Jika dijalankan, hasilnya adalah sebagai berikut.
print(sol)
{u2: 0.00825000000000000, u3: 0.0120000000000000, F1: -18000.0000000000}

Monday, June 21, 2021

Numpy: Broadcasting

Konsep python yang agak susah dipahami adalah operasi broadcasting. Operasi broadcasting merupakan "menyebar" pesan ke seluruh elemen matrik. Contoh sederhana, matrik c saya tambahkan dengan '5', maka semua elemen dalam matrik c akan ditambah dengan nilai 5. Operasi ini, sesuai konsep vektorisasi Numpy, akan jauh lebih cepat dari loop `for'. Syarat operasi broadcasting ini adalah setidaknya harus ada kompatibilitas antar aksisnya, bisa kolomnya, bisa barisnya, atau skalar. Misalnya matrik c  dengan ukuran (3, 3) bisa kita tambah dengan [1, 2, 3] tapi tidak dengan [1, 2, 3, 4].
>>> c = np.array([[ 5,  8, 11],
      [ 6,  9, 12],
      [ 7, 10, 13]])
>>> c +  [1, 2, 3]  # bisa juga dengan list langsung
array([[1,  5,  9],
      [ 2,  6, 10],
      [ 3,  7, 11]])
>>> c + np.array([1, 2, 3, 4])                
------------------------------------------
ValueError   Traceback (most recent call last)
< ipython-input-42-d334d3cf449e> in < module >
----> 1 c + np.array([1, 2, 3, 4])

ValueError: operands could not be broadcast together
with shapes (3,3) (4,)
Contoh lain adalah penjumlahan vektor kolom [1, 2, 3] dengan vektor baris [1, 2, 3]. Contoh ini menunjukkan betapa menariknya operasi broadcasting, kolom dijumlah dengan baris. Pada kolom pertama hasilnya menjadi [2, 3, 4], tiap elemen vektor kolom ditambah 1. Pada kolom 2 tiap elemen vektor kolom ditambah 2, dan pada elemen 3 tiap elemen vektor kolom ditambah dengan 3.
>>> np.array([1, 2, 3])[:, np.newaxis]+np.array([1, 2, 3])                                                                            
array([[2, 3, 4],
       [3, 4, 5],
       [4, 5, 6]])
Tulisan ini melengkapi tulisan ini.

Friday, June 04, 2021

Python: List Comprehension

Salah satu fitur di pemrograman Python yang saya suka dan sering pakai adalah list comprehension. Fitur ini menyederhanakan "for" loop dalam satu baris. Berikut contohnya.

Saya punya dua vektor A dan B. Saya ingin mencari dimana kemunculan vektor B dalam vektor A. 

a = np.array([1, 2, 3, 4, 5, 6])
b = np.array([2, 3, 4])
Dengan contoh di atas, jawaban atas pertanyaan saya adalah [1, 2, 3]; posisi/indeks dimana vektor B muncul di vektor A. Solusi pertama dengan "for" loop sebagai berikut:
import numpy as np
for i in b: 
    print(np.where(a == i)) 
Solusi kedua dengan List comprehension seperti berikut (baris kedua merupakan output).
In [26]: [np.where(a==x) for x in b]                                            
Out[26]: [(array([1]),), (array([2]),), (array([3]),)]
Cukup simpel dan intuitif.

Tuesday, April 27, 2021

Perkalian waktu di Python

Ada kalanya kita ingin mengalikan waktu agar kita mengetahui jumlah jam yang kita gunakan untuk mengerjakan sesuatu, misalnya jam kerja. Dengan cara biasa, jam:detik tidak bisa dikalikan. Cara berikut dapat digunakan untuk mengetahui berapa lama kita seharusnya menggunakan waktu tersebut. Misalnya, jika perhari kita "hanya" dialokasikan waktu tujuh jam empat puluh lima menit (ditulis "7:45"), berapa waktu (lagi "jam:menit") yang boleh dan harus kita gunakan untuk berkerja?
import datetime

# waktu kerja per hari, 7 jam 45 menit
t1 = datetime.timedelta(hours=7, minutes=45) 

# waktu kerja sebulan, 21 hari
t2 = t1 * 21


# fungsi untuk konversi jam dan menit
def sec2hourmin(t2_datetime):
	hours seconds = divmod(t2_datetime.total_seconds(), 3600)
    minutes, x  = divmod(seconds, 60)
    return hours, minutes

print(sec2hourmin(t2))
Dengan cara ini, kita bisa mendapatkan jumlah jam yang harus kita kerjakan untuk mengerjakan sesuatu (kerja) dalam sebulan. Agar lebih elegan, teknik di atas dapat kita konversi menjadi skrip python dengan argumen jumah hari untuk menghasilkan output "jam:menit" perbulannya.
# function to calculate hours and minutes per month for work
# input argument: # days (integer)
# example python3 days_to_hours.py 21

import sys
import datetime

def days2hours(days):
    tpday = datetime.timedelta(hours=7, minutes=45)
    tpmonth = tpday * days
    hours, seconds = divmod(tpmonth.total_seconds(), 3600)
    minutes, x  = divmod(seconds, 60)
    return int(hours), int(minutes)

hours, minutes = days2hours(int(sys.argv[1])) 
print('{}:{}'.format(str(hours), str(minutes)))
Ketik skrip di atas di editor dan simpan sebagai day_to_hour.py. Cara menggunakan skrip di atas adalah sebagai berikut.
# Jika sebulan ada 21 hari kerja
$ python3 day_to_hour.py 21
162:45
Artinya, dalam bulan itu ada 21 hari kerja; kita hanya diperbolehkan dan diharuskan bekerja selama 162 jam dan 45 menit.

Monday, April 26, 2021

Running IPython in multiple GPUs

Here is a simple configuration to run IPython on different GPUs on a single PC.

1. Check available GPU in PC via terminal, "$ sudo lshw -C display"

Checking number of GPUs

2. Launch "$ CUDA_AVAILABLE_DEVICES=0 ipython3" to use IPython using first GPU

Set IPython to use the first GPU (on OS level)

3. Launch "$ CUDA_AVAILABLE_DEVICES=1 ipython" to use IPython using second GPU

Set IPython to use the second GPU

4. Check "$ nvidia-smi" to confirm both GPUs are running simultaneously

Output of nvidia-smi when two GPUs are used simultaneously

5. For comparison, here is the output of 2 GPUs used in IPython without commands above

IPython without configuration resulting two GPUs output

Ouput of nvidia-smi without configuration

It can be seen from both IPython outputs and nvidia-smi that the configuration works. Each IPython window outputted a single GPU, while without configuration (without adding "CUDA_VISIBLE_DEVICES=X") the output of IPython is two GPUs. Also, the nvidia-smi output shows two GPUs work simultaneously with the given configs, while without config it shows only one GPU is running (the consumed memory of the second GPU only 416MiB meaning idle condition).

 

Update  2021/05/13

I checked again today, the above-mentioned steps sometimes still failed to choose GPU on multiple GPUs. The following workaround works for me.

Choose GPU 0

import os
os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"]="0"

# check with
import tensorflow as tf
tf.test.is_gpu_available()
The same steps apply to GPU 1. These steps also can be applied using the shell command "export".
export CUDA_DEVICE_ORDER="PCI_BUS_ID"
export CUDA_VISIBLE_DEVICES="1" 
Please be sure not to make typo (E.g, VISIBLE -> VISBLE), otherwise, the configuration won't work. There is no error message when we make string typos in a shell.

Monday, September 07, 2020

Python check if file/directory exist

This is a documentation for myself. To check if a file or directory already exists, use the following ways.
 
Check file or directory
import os
os.path.exists('filename')
Check file only
import os
os.path.isfile('filename')
Create the file if not exist
if not os.path.exists('file'): 
 os.mknod('file') 
Create a directory if not exist
if not os.path.exists('file'): 
 os.mkdir('file') 
Please note that the last commands will complain (shows error) if the file or directory 'file' exists. To throw complain, it is better to use `os.makedirs` with argument `exist_ok=True`. It also enables creation of subdirectories with their parent (like bash `mkdir -p`).
os.makedirs('parent-dir/sub-dir', exist_ok=True)
The last one is my favourite since it will create folder if not exist and don't throw error if it is exist.

Monday, July 13, 2020

Menginstall static library di (JAIST) cluster

Salah satu kelemahan (sekaligus keunggulan) menggunakan superkomputer atau komputer cluster adalah ketiadaan akses root. Akses root ditiadakan agar user tidak bisa mengutak-atik system. Apa jadinya kalau user bisa masuk /usr, /lib, /etc, dll...? Sistem akan dengan sangat mudah dilumpuhkan, pun tanpa serangan (ketidak sengajaan, ketidaktahuan, dll).

Tidak punya akses ke root artinya tidak bisa menginstall program atau library di sistem. Hanya admin yang bisa melakukannya. Permasalahan yang sering muncul adalah menghadapi admin yang bandel alias strict. Contoh: saya minta di-installkan library A, "Dear Pak Admin, tolong install library A di server/superkomputer." Alih-alih memberi jawaban, biasanya admin ngeles seperti ini, "maaf saya tidak terbiasa menggunakan library A, di kluster sudah tersedia library B." Tamat sudah kalau begini jawabannya. Cari cara lainnya.

Kalau anda sering menggunakan Linux atau Unix, berita baiknya, anda bisa menggunakan library atau program tanpa perlu meningstallnya ke system (/usr/bin). Ini dinamakan library statik. Kita bisa menginstall library di home direktory dan memerintahkan sistem untuk mencari di library home tersebut. Singkatnya begini: Hai sistem, cari librari ini ditempat ini. Berikut dua contoh instalasi library statik: ffmpeg dan libsndfile. Dua-duanya dipakai untuk pemrosesan suara.

ffmpeg

Jika menggunakan komputer biasa, library ini dengan mudah bisa di-install dengan `sudo apt intall ffmpeg`. Di superkomputer atau kluster hal ini tidak bisa dilakukan. Untung ada pre-built statik library ffmpeg yang disediakan oleh https://johnvansickle.com/. Download file-nya, uncompress, dan update $PATH seperti berikut.


wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz
tar -xvf ffmpeg-git-amd64-static.tar.xz

Hasilnya, kita akan memiliki direktory yang berisi program ffmpeg. Misalnya direktori dengan versi berikut: "ffmpeg-git-20200130-amd64-static". Di dalamnya ada direktori bin dan didalam bin ada file program ffmpeg. Jadi kita arahkan $PATH pada lokasi tersebut. Ketik perintah berikut pada terminal.
export PATH="/home/$USER/ffmpeg-git-20200130-amd64-static:$PATH"

Untuk mempermamenkannya, kita bisa letakkan perintah tersebut di .bashrc atau .bash_profile dan kita `source` untuk mengupdate file config bash tersebut. Sangat simpel dan cepat (daripada meminta tolong admin kluster).

libsndfile

Pada contoh kedua saya menggunakan linuxbrew. Brew adalah program seperti apt/apt-get yang didesain untuk Unix (Mac dan Linux). Pertama kita harus menginstall linuxbrew di home direktori cluster.
wget https://raw.githubusercontent.com/Homebrew/install/master/install.sh
bash ./install.sh
Kadang kita perlu mengganti akses permisi dari file install.sh. Jika dibutuhkan, coba dengan `chmod +x install.sh`.
Brew akan memberi pilihan dimana kita menginstall. By default akan diinstall di system (root). Lagi, karena kita tidak punya akses ke root, maka installasi brew kita tempatkan di home direktori (pilih opsi ini dengan Ctrl-D). Nah setelah terinstall, seperti halnya ffmpeg, kita akan memiliki program brew di .linuxbrew/bin. Ini contohnya pada kasus saya.

Singularity tensorflow_1.14.0-gpu-py3.sif:~> ls .linuxbrew/bin/brew 
.linuxbrew/bin/brew
Sekarang kita bisa menginstall program yang tersedia di brew, termasuk ffmpeg dan libsndfile. Libsndfile kita install dengan perintah berikut,

Singularity tensorflow_1.14.0-gpu-py3.sif:~> .linuxbrew/bin/brew install libsndfile 
/home/s1820002/.linuxbrew/Homebrew/Library/Homebrew/brew.sh: line 4: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
/home/s1820002/.linuxbrew/Homebrew/Library/Homebrew/brew.sh: line 4: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
Updating Homebrew...
/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
/home/s1820002/.linuxbrew/Homebrew/Library/Homebrew/brew.sh: line 4: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
No changes to formulae.

/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
/home/s1820002/.linuxbrew/Homebrew/Library/Homebrew/brew.sh: line 4: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
Warning: libsndfile 1.0.28 is already installed and up-to-date
Karena saya sudah menginstallnya maka brew mengabari kalau libsndfile sudah terinstall dan update. Langkah terakhir adalalah mengupdate #LD_LIBRARY_PATH. Semua library yang terinstall dengan brew akan terinstall di .linuxbrew/lib. Karenanya kita perintahkan LD_LIBRARY_PATH untuk mencari library di situ.

export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/.linuxbrew/lib"

Bisa juga perintah tersebut ditambahkan di .bashrc. Saya juga perlu menambahkan ini dalam .bashrc,
eval $(~/.linuxbrew/bin/brew shellenv)

Seleasai. Sekarang kita bisa menggunakan container tensorflow dengan library LibROSA yang membutuhkan libsndfile untuk membaca file suara.

Studi case di JAIST cluster
Di JAIST cluster, saat kita masuk ke vpcc (cluster yg memiliki gpu) telah tersedia tensorflow-gpu via container (singularity). By default, python yang diload adalah python bawaan Anaconda yang menyebabkan library tidak dapat ditemukan (karena perbedaan path, kemungkinan). Solusinya adalah dengan tidak menggunakan python bawaan anaconda, dan menggunakan python bawaan container/singularity dengan perintah berikut.
module remove py35

Cara tersebut cukup ampuh daripada meminta admin untuk menginstall library (sndfile) di cluster.