Postgresのstored functionで2次元配列を引数にし,Pythonから呼び出す

How to call a Postgres stored function with an matrix as an argument from Python

あまりサンプルがなかったので,簡単なものを作成してみた。その2。
ここを参考にした。

  • Postgres 16
  • Python 12

二次元配列で数字のリストを渡して,ストアドファンクションで合計を計算し,返す

Postgresql


drop function if exists matfunc;
create function matfunc(mat integer[][])
returns integer as
$$
declare 
  res integer := 0;
  i integer;
  j integer;
begin

for i in array_lower(mat,1)..array_upper(mat,1) loop
  for j in array_lower(mat,2)..array_upper(mat,2) loop
    res := res + mat[i][j];
  end loop;
end loop;
return res;
end;
$$
language plpgsql;

select matfunc(ARRAY[ARRAY[1,2,3], ARRAY[4,5,6]]);

Python

import psycopg2

try:
    ps_connection = psycopg2.connect(user="username",
                                     password="password",
                                     host="127.0.0.1",
                                     port="5432",
                                     database="testdatabase")

    cursor = ps_connection.cursor()

    lst =[ [1,2,3],[4,5,6]]
    # call stored procedure
    cursor.execute('select matfunc(%s)', (lst,))

    result = cursor.fetchall()
    for row in result:
        print("sum = ", row[0], )

except (Exception, psycopg2.DatabaseError) as error:
    print("Error while connecting to PostgreSQL", error)

finally:
    # closing database connection.
    if ps_connection:
        cursor.close()
        ps_connection.close()
        print("PostgreSQL connection is closed")

Postgresのstored functionで配列を引数にし,Pythonから呼び出す

How to call a Postgres stored function with an array as an argument from Python

あまりサンプルがなかったので,簡単なものを作成してみた。

  • Postgres 16
  • Python 12

配列で数字のリストを渡して,ストアドファンクションで合計を計算し,返す

Postgresql


drop function if exists testfunc;
create function testfunc(ary integer[])
returns integer as
$$
declare tmp integer;
begin
select sum(s) into tmp from unnest(ary) as s;
return tmp;
end;
$$
language plpgsql;

select testfunc(ARRAY[1,2,3]);

Python

import psycopg2

try:
    ps_connection = psycopg2.connect(user="username",
                                     password="password",
                                     host="127.0.0.1",
                                     port="5432",
                                     database="testdatabase")

    cursor = ps_connection.cursor()

    lst = [1,2,3]
    # call stored procedure
    cursor.execute('select testfunc(%s)', (lst,))

    result = cursor.fetchall()
    for row in result:
        print("sum = ", row[0], )

except (Exception, psycopg2.DatabaseError) as error:
    print("Error while connecting to PostgreSQL", error)

finally:
    # closing database connection.
    if ps_connection:
        cursor.close()
        ps_connection.close()
        print("PostgreSQL connection is closed")

ジョブスケジューラのエラー

大学のスパコンはジョブ管理システム PBSによってジョブスケジュールを行なっているのですが,
最近変わったのか,Pythonで並列処理をしようとするとなぜかエラーが出るようになりました。

エラー

=>> PBS: job killed: ncpus 20.8 exceeded limit 16

コード


import os
from mpi4py import MPI
comm = MPI.COMM_WORLD
size = comm.Get_size()
rank = comm.Get_rank()
name = MPI.Get_processor_name()

import csv
source=[]
target=[]
with open("test.csv","r",encoding="utf-8") as f:
    cdata=csv.reader(f,delimiter=",")
    for i,items in enumerate(cdata):
        if i==0:
            continue
        source.append(items[0])
        target.append(items[1])

data_lst=[]
for src,tgt in zip(source,target):
    data_lst.append((src,tgt))

# http://muscle199x.blog.fc2.com/blog-entry-70.html?sp
def split_list(lst, n):
    list_size = len(lst)
    a = list_size // n
    b = list_size % n
    return [lst[i * a + (i if i < b else b):(i + 1) * a + (i + 1 if i < b else b)] for i in range(n)]

from transformers import MT5ForConditionalGeneration, MT5Model, MT5EncoderModel, T5Tokenizer

tokenizer = T5Tokenizer.from_pretrained("google/mt5-base")
model = MT5ForConditionalGeneration.from_pretrained("google/mt5-base")
if rank == 0:
    data = split_list(data_lst, size)
else:
    data = None

def calc(my_data):
    res=[]

    for src,tgt in my_data:
        inputs = tokenizer.encode("summarize: " + src, return_tensors='pt', max_length=512, truncation=True)
        summary_ids = model.generate(inputs, max_length=50, min_length=10, length_penalty=5., num_beams=2)
        summary = tokenizer.decode(summary_ids[0])
        res.append((src,tgt,summary))
    return res

my_data = comm.scatter(data, root=0)

for r in range(comm.size):
    if rank == r:
        print("scatter.[%d] %d" % (rank, len(my_data)))
    comm.Barrier()

my_res = calc(my_data)
def flatten(l):
    try:
        return ([item for sublist in l for item in sublist])
    except:
        import traceback
        traceback.print_exc()
        print(l)

# gather
res = MPI.COMM_WORLD.gather(my_res, root=0)

if rank == 0:
    res=flatten(res)
    for src,tgt,summary in res:
        print(f"source:{src}")
        print(f"target:{tgt}")
        print(f"summary:{summary}")
        print("-----")


これで実行スクリプトはこんな感じ

#!/usr/bin/bash
#PBS -N sample
#PBS -j oe 
#PBS -l select=1:ncpus=64:mpiprocs=64
#PBS -q SINGLE
NPROCS=`cat $PBS_NODEFILE|wc -l`
cd ${PBS_O_WORKDIR}
mpirun -np ${NPROCS} python mt5.py

解決方法

ここにありました。

簡単に解決するためには,Pythonコードの一番上に


import os
os.environ["MKL_NUM_THREADS"] = "1"
os.environ["NUMEXPR_NUM_THREADS"] = "1"
os.environ["OMP_NUM_THREADS"] = "1"

これを入れるだけ。Numpyを呼ぶ前に呼ぶ必要があるので,シェルスクリプトで呼んでもいいですし,Pythonの一番上で環境変数に設定してやってもいいです。

Mac OS 11.4でpip pdftotext

M1Macで何故か素直に入らなかったのでメモ

環境

  • M1 Mac book Air
  • OS: 11.4
  • python: Intel miniconda3
  • python version:3.8.5

手順

brewでPdftotextを入れる

brew install pdftotext

~/.zshrcに追加

export C_INCLUDE_PATH=/opt/homebrew/Cellar/poppler/21.05.0/include:$C_INCLUDE_PATH
export CPATH=$C_INCLUDE_PATH
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/opt/homebrew/Cellar/poppler/21.05.0/lib # どうもこれは効かないらしい

pipでpdftotextライブラリを入れる 

pip install pdftotext --global-option=build_ext --global-option="-L/opt/homebrew/Cellar/poppler/21.05.0/lib"

M1 MacでのPython

M1 Mac book Air 購入したのですが,なかなか使いこなすにはハードルが高そうです。
特に,Pythonのモジュール類がうまく動かないものが多いです。

いろいろ書かれていますが,結論としては,Rosetta2を使うのが一番という結論。

brewのPythonを使うパターン

これは,ディープラーニング系のライブラリが全く入りません。
自分でGitHubからソースコード持ってきてインストールできるものもあるのですが
かなり厳しいです。

pyenvを使うパターン

brewと同様

miniforgeを使うパターン

これはarm用のライブラリもあるのですが,全部はない模様。。

rosetta2でminiconda

これが簡単ですね

wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O ~/miniconda.sh
./miniconda.sh

これでIntelと同様に使えます。多少遅くなっているのでしょうが気にしない

VSCodeのjupyter notebookが起動しない

最近VSCodeをアップデートしたところ、PythonのNotebookが起動しなくなりました。

Connection To IPython kernel: Connecting to kernel.

これでずーと待ったのちに、タイムアウト。

こちらに解決方法が!!

どうやら、Pythonのモジュールと干渉しているようですので、バージョンダウン。

python -m pip install 'traitlets==4.3.3' --force-reinstall

これで無事起動できました。

KLDivergenceとJSDivergence

Pythonのコード作成したのでメモ

KLダイバージェンス

$$D_{KL}(P||Q) = \sum_x p(x) \, \log \frac{P(x)}{Q(x)}$$

JSダイバージェンス

$$D_{JS}(P||Q)=\frac{1}{2}\bigg\{ D_{KL}(P \, || \, R)+D_{KL}(Q \, || \, R) \bigg\}$$
$$R = \frac{P + Q}{2}$$

プログラム

ここを参考に

データ

まずはデータの準備


import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

np.random.seed(12345)
a=np.random.normal(40,10, size=200)
b=np.random.normal(70,15, size=100)
c=np.random.normal(30,2, size=70)
d=np.random.normal(150,10, size=150)

plt.figure()
plt.hist(a,alpha=0.3,bins=20,histtype="stepfilled",color="r",label="a")
plt.hist(b,alpha=0.3,bins=20,histtype="stepfilled",color="b",label="b")
plt.hist(c,alpha=0.3,bins=20,histtype="stepfilled",color="black",label="c")
plt.hist(d,alpha=0.3,bins=20,histtype="stepfilled",color="y",label="d")
plt.legend()
plt.show()

ヒストグラム

KLダイバージェンス


def KLDivergence(a, b, bins=20, epsilon=.00001):
    min_value=min(min(a),min(b))
    max_value=max(max(a),max(b))
    # サンプルをヒストグラムに, 共に同じ数のビンで区切る
    a_hist, _ = np.histogram(a, range=(min_value,max_value),bins=bins) 
    b_hist, _ = np.histogram(b, range=(min_value,max_value),bins=bins)
    
    # 合計を1にするために全合計で割る
    a_hist = (a_hist+epsilon)/np.sum(a_hist)
    b_hist = (b_hist+epsilon)/np.sum(b_hist)
    
    # 本来なら a の分布に0が含まれているなら0, bの分布に0が含まれているなら inf にする
    return np.sum([ai * np.log(ai / bi) for ai, bi in zip(a_hist, b_hist)])

JSダイバージェンス


def JSDivergence(a, b, bins=20, epsilon=.00001):
    min_value=min(min(a),min(b))
    max_value=max(max(a),max(b))
    # サンプルをヒストグラムに, 共に同じ数のビンで区切る
    a_hist, _ = np.histogram(a, range=(min_value,max_value),bins=bins) 
    b_hist, _ = np.histogram(b, range=(min_value,max_value),bins=bins)

    # 合計を1にするために全合計で割る
    a_hist = (a_hist+epsilon)/np.sum(a_hist)
    b_hist = (b_hist+epsilon)/np.sum(b_hist)
    
    r_hist = (a_hist + b_hist)/2.0
    
    ar= np.sum([ai * np.log(ai / ri) for ai, ri in zip(a_hist, r_hist)])
    br= np.sum([bi * np.log(bi / ri) for bi, ri in zip(b_hist, r_hist)])
    return (ar+br)/2.0

実験

まずはaとbの分布を調べます。


kl1=KLDivergence(a,b)
js1=JSDivergence(a,b)

kl2=KLDivergence(b,a)
js2=JSDivergence(b,a)
print("kl1=",kl1,",kl2=",kl2)
print("js1=",js1,",js2=",js2)

kl1= 4.804620164702722 ,kl2= 8.66117091460137
js1= 0.44568757334874426 ,js2= 0.44568757334874426

KLダイバージェンスはaとbを入れ替えると値が変わりますが、JSダイバージェンスでは同じです。

つぎに、分布間でのKLダイバージェンスとJSダイバージェンスの値を調べます


# 一致 
kl1=KLDivergence(a,a)
kl2=KLDivergence(a,a)
js=JSDivergence(a,a)
print("一致:kl1=",kl1,",kl2=",kl2,",js=",js)

# 含まれる
kl1=KLDivergence(a,c)
kl2=KLDivergence(c,a)
js=JSDivergence(a,c)
print("含有:kl1=",kl1,",kl2=",kl2,",js=",js)

# 重なる
kl1=KLDivergence(a,b)
kl2=KLDivergence(b,a)
js=JSDivergence(a,b)
print("重複:kl1=",kl1,",kl2=",kl2,",js=",js)

# 小別離
kl1=KLDivergence(b,d)
kl2=KLDivergence(d,b)
js=JSDivergence(b,d)
print("小離:kl1=",kl1,",kl2=",kl2,",js=",js)

# 大別離
kl1=KLDivergence(a,d)
kl2=KLDivergence(d,a)
js=JSDivergence(a,d)
print("大離:kl1=",kl1,",kl2=",kl2,",js=",js)

一致:kl1= 0.0 ,kl2= 0.0 ,js= 0.0
含有:kl1= 8.982185089568121 ,kl2= 1.5818474236167488 ,js= 0.3961382475750016
重複:kl1= 4.804620164702722 ,kl2= 8.66117091460137 ,js= 0.44568757334874426
小離:kl1= 14.585645733703917 ,kl2= 14.484008742932685 ,js= 0.6931379306807828
大離:kl1= 14.9325435215302 ,kl2= 15.170878470071264 ,js= 0.6931412821202988

結果を見ると、距離の遠い順にKLダイバージェンス、JSダイバージェンスの値が大きくなっています。KLダイバージェンスの場合には、どちらを基準にするかによって変わってくるので注意が必要みたいですね。

BERTモデルをPytorchのモデルへ変換

Tensorflowで作成したBERTモデルを、Pytorchへ変換します。

こちらに方法が書いています。

モデル

モデルを自分で計算して作ってもいいのですが、公開しているものがあるので、そちらを使います。

KNP

こちらはKNPを使って分かち書きしたWikipedia日本語版を元に作成しています。
あまりKNPを使ったことはないのですが、実際に使うときにはMeCabで分かち書きをしても大丈夫でしょう。

SentencePiece

こちらのものは同じく日本語Wikipediaを用いて、SentencePieceによる分かち書きをしたものです。

Transformers

Pytorchで用いるのライブラリです。以下のコマンドでインストールしておきます。

pip install transformers

変換

SentencePieceで作成したモデルにはPytorch用のモデルがついていませんので、変換してみます。
こちらには以下のファイルが含まれています。

model.ckpt-1400000.meta
bert_config.json                       
model.ckpt-1400000.data-00000-of-00001 
wiki-ja.model
model.ckpt-1400000.index               
wiki-ja.vocab

このコマンドでpytorch_model.binというファイルへ変換します。

transformers bert  model.ckpt-1400000 bert_config.json pytorch_model.bin

numpyのエラー

tensorflowのサンプルを動かそうとしたときのエラー

4$ python fully_connected_feed.py 
RuntimeError: module compiled against API version 0xc but this version of numpy is 0xb
ImportError: numpy.core.multiarray failed to import
ImportError: numpy.core.umath failed to import
ImportError: numpy.core.umath failed to import
2019-11-15 09:55:15.959592: F tensorflow/python/lib/core/bfloat16.cc:675] Check failed: PyBfloat16_Type.tp_base != nullptr 
中止 (コアダンプ)

どうやら、numpyが壊れているらしい?

$ pip install -U numpy 

これで解決

VAEを用いたなりすまし検知を書き直してみる

こちらの記事「VAEを用いたUNIXセッションのなりすまし検出」はソースコードが完全に公開されていないので、補完してみました。

環境

  • macOS 10.14
  • python 3.7

データ準備

まず、データを準備します。こちらからダウンロードします。ダウンロードしたら、解凍し、Fasttext用にデータを結合します。各ユーザの前半の5000は訓練用のデータで、なりすましがないデータですので、これをすべてのユーザから抽出します。ユーザごとに一行にコマンドをスペースでつないで作成します。全部で五十行になります。

for f in masquerade-data/*;do head -n 5000 $f|perl -pe 's/\n/ /g'|perl -pe 's/$/\n/'>> train.txt; done

Fasttextによるベクトル化

単語をfasttextによりベクトル化します。50次元を使っています。fasttextはpipでインストールしたものでもOKです。


import numpy as np
import os
import fasttext

model = fasttext.train_unsupervised("train.txt", "skipgram", dim=50, minCount=1)

UMAPによる可視化

元記事では出現頻度の高いコマンドから200取り出して、可視化していますが、面倒なので、modelの単語列から上位50単語を抜き出して可視化します。


%matplotlib inline
import matplotlib.pyplot as plt

import umap
import matplotlib.cm as cm
np.random.seed(12345)
x_word=[model[w] for w in model.words]
y_word=model.words
embedding = umap.UMAP().fit_transform(x_word[:50])
for emb,lbl in zip(embedding,y_word[:50]):
    plt.plot(emb[0],emb[1],marker=".")
    plt.annotate(lbl,(emb[0],emb[1]))
# https://teratail.com/questions/110775
plt.show()

モデル

元記事のものではそのままでは動かなかったので多少修正しています。


import numpy as np
import keras
from keras import layers
from keras import backend as K
from keras.models import Model
from keras.layers.core import Lambda
from keras.optimizers import Adam
 
#K.clear_session()
 
shape = (100, 50, 1)
epochs = 120
batch_size = 16
latent_dim = 2
 
input_cmd = keras.Input(shape=shape)
 
x = layers.Conv2D(32, 3,
                  padding='same', activation='relu')(input_cmd)
x = layers.Conv2D(64, 3,
                  padding='same', activation='relu',
                  strides=(2, 2))(x)
x = layers.Dropout(0.1)(x)
x = layers.Conv2D(64, 3,
                  padding='same', activation='relu')(x)
x = layers.Dropout(0.2)(x)
x = layers.Conv2D(64, 3,
                  padding='same', activation='relu')(x)
x = layers.Dropout(0.2)(x)
 
shape_before_flattening = K.int_shape(x)
 
x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)
 
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)
 
def sampling(args):
    z_mean, z_log_var = args
    epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim),
                              mean=0., stddev=1.)
    return z_mean + K.exp(z_log_var) * epsilon
 
z = Lambda(sampling)([z_mean, z_log_var])
 
decoder_input = layers.Input(K.int_shape(z)[1:])
 
x = layers.Dense(np.prod(shape_before_flattening[1:]),
                 activation='relu')(decoder_input)
 
x = layers.Reshape(shape_before_flattening[1:])(x)
 
x = layers.Conv2DTranspose(32, 3, strides=(2, 2),
                           padding='same', activation='relu')(x)
x = layers.Conv2D(1, 3,
                  padding='same', activation='sigmoid')(x)
 
decoder = Model(decoder_input, x)
 
z_decoded = decoder(z)
 
class CustomVariationalLayer(keras.layers.Layer):
 
    def vae_loss(self, x, z_decoded):
        x = K.flatten(x)
        z_decoded = K.flatten(z_decoded)
        xent_loss = keras.metrics.binary_crossentropy(x, z_decoded)
        kl_loss = -5e-4 * K.mean(
            1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
        return K.mean(xent_loss + kl_loss)
 
    def call(self, inputs):
        x = inputs[0]
        z_decoded = inputs[1]
        loss = self.vae_loss(x, z_decoded)
        self.add_loss(loss, inputs=inputs)
        return x
 
y = CustomVariationalLayer()([input_cmd, z_decoded])


vae = Model(input_cmd, y)
vae.compile(optimizer=Adam(), loss=None)
vae.summary()

入力データ

入力データを作成します。ファイルから再度読み込んで、訓練データを上位4000コマンド、テストデータを4001から5000コマンドで作成します。また、元記事と同様にUser2を使っています。


with open("masquerade-data/User2") as f:
    cmds=[l.rstrip() for l in f.readlines()]

x=[model[cmd] for cmd in cmds]
x_train=np.reshape(np.array(x[:4000]),(40,100,50,1))
x_valid=np.reshape(np.array(x[4000:5000]),(10,100,50,1))

訓練

Earlystoppingを入れました。訓練は100コマンドをひとかたまりとして訓練しています。


from keras.callbacks import EarlyStopping
es_cb = EarlyStopping(monitor='val_loss', patience=50, verbose=1, mode='auto')
history = vae.fit(x=x_train, y=None,
        shuffle=True,
        epochs=epochs,
        batch_size=batch_size,
        validation_data=(x_valid, None),
        callbacks=[ es_cb])

結果の可視化

訓練データでのLossを可視化します


%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(history.history["loss"],label="training loss")
plt.plot(history.history["val_loss"],label="validation loss")
plt.title("model loss")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend(loc="upper right")
plt.show()

テスト

最後に、1%の確率でなりすましが入っているという各ユーザの5001行目からのデータをつかって、Lossを可視化します。同様に100コマンドをひとかたまりとして、100個分をテストしています


x_test=np.reshape(np.array(x[5000:]),(100,100,50,1))
loss_test=[]
for i in range(len(x_test)):
    loss_test.append((vae.evaluate(x=np.reshape(x_test[i],(1,100,50,1)),y=None)))
    

%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(loss_test,label="test loss")
plt.title("test loss")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend(loc="upper right")
plt.show()