乾物丨時系列データベースのDolphinDB一般コンピューティングチュートリアル

DolphinDBは、分散データを保存できるだけでなく、分散コンピューティングもサポートしています。DolphinDBでは、ユーザーはシステムが提供する一般的な分散コンピューティングフレームワークを使用して、特定の基盤となる実装に注意を払うことなく、スクリプトを介して効率的な分散アルゴリズムを実装できます。この記事では、DolphinDBの一般的なコンピューティングフレームワークの重要な概念と関連機能について詳しく説明し、具体的な使用シナリオと例を豊富に提供します。


1.データソース

データソースは、DolphinDBデータベースの一般的なコンピューティングフレームワークの基本概念です。これは、データのメタ記述である特殊なタイプのデータオブジェクトです。データソースを実行することにより、ユーザーはテーブル、行列、ベクトルなどのデータエンティティを取得できます。DolphinDBの分散コンピューティングフレームワークでは、巨大なデータエンティティの代わりに軽量のデータソースオブジェクトが後続の計算のためにリモートノードに送信されるため、ネットワークトラフィックが大幅に削減されます。

DolphinDBでは、ユーザーはSQLDS関数を使用してSQL式に基づいてデータソースを生成することがよくあります。この関数はテーブルを直接クエリしませんが、1つ以上のSQLサブクエリ、つまりデータソースのメタステートメントを返します。その後、ユーザーはMap-Reduceフレームワークを使用して、データソースと計算関数を入力し、各データソースに対応するノードにタスクを分散し、計算を並行して完了して、結果を要約できます。

データソースを取得するために一般的に使用されるいくつかの方法については、この記事のセクション3.1、3.2、3.3、および3.4​​で詳しく説明します。


2.Map-Reduceフレームワーク

Map-Reduce関数は、DolphinDBの一般的な分散コンピューティングフレームワークのコア関数です。

2.1mr関数

DolphinDBのMap-Reduce関数mrの構文はmr(ds、mapFunc、[reduceFunc]、[finalFunc]、[parallel = true])であり、データソースのセットとmapFunc関数をパラメーターとして受け入れます。各データソースが配置されているノードにコンピューティングタスクを分散し、mapFuncを介して各データソースのデータを処理します。オプションのパラメーターreduceFuncは、mapFuncの戻り値をペアで計算し、得られた結果は3番目のmapFuncの戻り値で計算されるため、累積計算ではmapFuncの結果が要約されます。M個のマップ呼び出しがある場合、reduce関数はM-1回呼び出されます。オプションのパラメーターfinalFuncは、reduceFuncの戻り値をさらに処理します。

公式文書には、mrを介した分散最小二乗線形回帰の例がありますこの記事では、次の例を使用して、mr呼び出しを使用して、分散テーブルの各パーティションのデータの10分の1をランダムにサンプリングする方法を示します。

// 创建数据库和DFS表
db = database("dfs://sampleDB", VALUE, `a`b`c`d)
t = db.createPartitionedTable(table(100000:0, `sym`val, [SYMBOL,DOUBLE]), `tb, `sym)
n = 3000000
t.append!(table(rand(`a`b`c`d, n) as sym, rand(100.0, n) as val))

// 定义map函数
def sampleMap(t) {
    sampleRate = 0.1
    rowNum = t.rows()
    sampleIndex = (0..(rowNum - 1)).shuffle()[0:int(rowNum * sampleRate)]
    return t[sampleIndex]
}

ds = sqlDS(<select * from t>)              // 创建数据源
res = mr(ds, sampleMap, , unionAll)        // 执行计算

上記の例では、ユーザー定義のsampleMap関数は、テーブル(つまり、データソース内のデータ)をパラメーターとして受け取り、行の10分の1をランダムに返します。この例のmr関数にはreduceFuncパラメーターがないため、各マップ関数の戻り値はタプルに配置され、finalFunc、つまりunionAllに渡されます。unionAllは、map関数によって返された複数のテーブルを、順次パーティションを持つ分散テーブルにマージします。

2.2imr関数

DolphinDBデータベースは、Map-Reduceメソッドに基づく反復計算関数imrを提供します。mrと比較すると、反復計算をサポートでき、各反復は前の反復の結果と入力データセットを使用するため、より複雑なアルゴリズムの実現をサポートできます。反復計算には、モデルパラメータの初期値と終了基準が必要です。その構文はimr(ds、initValue、mapFunc、[reduceFunc]、[finalFunc]、terminateFunc、[carryover = false])です。ここで、initValueパラメーターは最初の反復の初期値であり、mapFuncパラメーターは関数であり、受け入れられます。パラメータには、データソースエンティティ、および前の反復での最終関数の出力が含まれます。最初の反復では、これはユーザーが指定した初期値です。imrのパラメーターは、mr関数に似ています。finalFunc関数は2つのパラメーターを受け入れます。最初のパラメーターは、前の反復での最後の関数の出力です。最初の反復では、これはユーザーが指定した初期値です。2番目のパラメーターは、reduce関数を呼び出した後の出力です。terminalFuncパラメーターは、反復が終了するかどうかを判別するために使用されます。2つのパラメータを受け入れます。1つ目は、前の反復でのreduce関数の出力であり、2つ目は、現在の反復でのreduce関数の出力です。trueが返された場合、反復は中止されます。キャリーオーバーパラメーターは、map関数呼び出しが次のmap関数呼び出しに渡されるオブジェクトを生成するかどうかを示します。キャリーオーバーがtrueの場合、map関数には3つのパラメーターがあり、最後のパラメーターはキャリーオブジェクトであり、map関数の出力結果はタプルであり、最後の要素はキャリーオブジェクトです。最初の反復では、運ばれるオブジェクトはNULLです。

公式文書には、imrを介して分散データの中央値を計算する例がありますこの記事では、より複雑な例、つまり、ニュートン法を使用したロジスティック回帰の計算を提供し、機械学習アルゴリズムでのimrの適用を示します。

def myLrMap(t, lastFinal, yColName, xColNames, intercept) {
	placeholder, placeholder, theta = lastFinal
    if (intercept)
        x = matrix(t[xColNames], take(1.0, t.rows()))
    else
        x = matrix(t[xColNames])
    xt = x.transpose()
    y = t[yColName]
    scores = dot(x, theta)
    p = 1.0 \ (1.0 + exp(-scores))
    err = y - p
    w = p * (1.0 - p)
    logLik = (y * log(p) + (1.0 - y) * log(1.0 - p)).flatten().sum()
    grad = xt.dot(err)                   // 计算梯度向量
    wx = each(mul{w}, x)
    hessian = xt.dot(wx)                 // 计算Hessian矩阵
    return [logLik, grad, hessian]
}

def myLrFinal(lastFinal, reduceRes) {
    placeholder, placeholder, theta = lastFinal
    logLik, grad, hessian = reduceRes
    deltaTheta = solve(hessian, grad)    // deltaTheta等于hessian^-1 * grad,相当于解方程hessian * deltaTheta = grad
    return [logLik, grad, theta + deltaTheta]
}

def myLrTerm(prev, curr, tol) {
	placeholder, grad, placeholder = curr
	return grad.flatten().abs().max() <= tol
}

def myLr(ds, yColName, xColNames, intercept, initTheta, tol) {
    logLik, grad, theta = imr(ds, [0, 0, initTheta], myLrMap{, , yColName, xColNames, intercept}, +, myLrFinal, myLrTerm{, , tol})
    return theta
}

上記の例では、map関数は、データソース内のデータの現在の係数の下で勾配ベクトルとヘッセ行列を計算します。reduce関数は、マップの結果を追加します。これは、の勾配ベクトルとヘッセ行列を見つけることと同じです。データセット全体; final関数は、最終勾配ベクトルとヘッセ行列を介して係数を最適化し、反復ラウンドを完了します。終了関数の基準は、この反復ラウンドでの勾配ベクトルの最大成分の絶対値がパラメータtolより大きい。

この例は、データソース変換操作を通じてパフォーマンスを向上させるためにさらに最適化することもできます。詳細については、セクション3.6を参照してください。

頻繁に使用される分析ツールとして、分散ロジスティック回帰がDolphinDBの組み込み関数として実装されています。組み込みバージョン(logisticRegression)は、より多くの機能を提供します。


3.データソース関連の機能

DolphinDBは、データソースを取得するために一般的に使用される次の方法を提供します。

3.1sqlDS関数

sqlDS関数は、入力SQLメタコードに基づいてデータソースリストを作成します。SQLクエリのデータテーブルにn個のパーティションがある場合、sqlDSはn個のデータソースを生成します。SQLクエリにパーティションテーブルが含まれていない場合、sqlDSは1つのデータソースのみを含むタプルを返します。

sqlDSは、SQL式をデータソースに変換する効率的な方法です。ユーザーは、特定のデータ分散に注意を払わずにSQL式を提供するだけでよく、返されたデータソースを使用して分散アルゴリズムを実行できます。以下に示す例は、sqlDSを使用して、DFSテーブルのデータに対してolsEx分散最小二乗回帰を実行する方法を示しています。

// 创建数据库和DFS表
db = database("dfs://olsDB", VALUE, `a`b`c`d)
t = db.createPartitionedTable(table(100000:0, `sym`x`y, [SYMBOL,DOUBLE,DOUBLE]), `tb, `sym)
n = 3000000
t.append!(table(rand(`a`b`c`d, n) as sym, 1..n + norm(0.0, 1.0, n) as x, 1..n + norm(0.0, 1.0, n) as y))

ds = sqlDS(<select x, y from t>)    // 创建数据源
olsEx(ds, `y, `x)                   // 执行计算


3.2repartitionDS機能

sqlDSのデータソースは、データのパーティションに従ってシステムによって自動的に生成されます。ユーザーは、データを取得するとき、計算量を減らすためにデータパーティションを再指定するとき、またはパーティションデータの一部のみが必要なときなど、データソースにいくつかの制限を加える必要がある場合があります。repartitionDS関数は、データソースを再パーティション化する機能を提供します。

関数repartitionDSは、入力SQLメタコードと列名、パーティションタイプ、パーティションスキームなどに従って、メタコードの再パーティション化された新しいデータソースを生成します。

次のコードは、repartitionDSの例を示しています。この例では、DFSテーブルtには、それぞれシンボル、日時、およびdoubleのフィールドdeviceId、time、temporerがあります。データベースは2層パーティション化を採用しています。最初の層は時間のVALUEによって分割され、1日あたり1パーティションです。レイヤーはdeviceId用ですHASHに従って20の領域に分割されます。

次に、deviceIdフィールドで95パーセンタイル温度を集計してクエリする必要があります。クエリselectpercentile(temperature、95)from t group by deviceIDを直接記述した場合、パーセンタイル関数はMap-Reduceによって実装されていないため、このクエリは完了しません。

1つの解決策は、すべての必須フィールドをローカルにロードして95パーセンタイルを計算することですが、データ量が多すぎると、計算リソースが不十分になる可能性があります。repartitionDSは解決策を提供します。元のパーティションスキームHASHに従ってdeviceIdに基づいてテーブルを再パーティション化し、新しい各パーティションは元のテーブルのHASHパーティションのすべてのデータに対応します。mr関数を使用して、新しい各パーティションの95パーセンタイル温度を計算し、最後に結果をマージします。

// 创建数据库
deviceId = "device" + string(1..100000)
db1 = database("", VALUE, 2019.06.01..2019.06.30)
db2 = database("", HASH, INT:20)
db = database("dfs://repartitionExample", COMPO, [db1, db2])

// 创建DFS表
t = db.createPartitionedTable(table(100000:0, `deviceId`time`temperature, [SYMBOL,DATETIME,DOUBLE]), `tb, `deviceId`time)
n = 3000000
t.append!(table(rand(deviceId, n) as deviceId, 2019.06.01T00:00:00 + rand(86400 * 10, n) as time, 60 + norm(0.0, 5.0, n) as temperature))

// 重新分区
ds = repartitionDS(<select deviceId, temperature from t>, `deviceId)
// 执行计算
res = mr(ds, def(t) { return select percentile(temperature, 95) from t group by deviceId}, , unionAll{, false})

この計算結果の正確性を保証できます。repartitionDSによって生成された新しいパーティションはdeviceIdの元のパーティションに基づいているため、各データソースのdeviceIdが重複しないようにすることができます。したがって、正しい結果を得ることができます。各パーティションの計算結果を組み合わせることにより。


3.3textChunkDS関数

textChunkDS関数は、テキストファイルで表されるデータに対して分散計算を実行するために、テキストファイルを複数のデータソースに分割できます。その構文は次のとおりです。textChunkDS(filename、chunkSize、[delimiter = '、']、[schema])。その中で、filename、delimiter、schemaのパラメータはloadText関数のパラメータと同じです。chunkSizeパラメーターは、各データソースのデータのサイズをMB単位で示します。これは、1から2047までの整数にすることができます。

次の例は公式ドキュメントのolsEx例の別の実装ですtextChunkDS関数を使用してテキストファイルから複数のデータソースを生成します。各データソースのサイズは100MBです。生成されたデータソースが変換された後、olsEx関数を実行して最小二乗パラメーターを計算します。

ds = textChunkDS("c:/DolphinDB/Data/USPrices.csv", 100)
ds.transDS!(USPrices -> select VOL\SHROUT as VS, abs(RET) as ABS_RET, RET, log(SHROUT*(BID+ASK)\2) as SBA from USPrices where VOL>0)
rs=olsEx(ds, `VS, `ABS_RET`SBA, true, 2)

データソース変換操作transDS!については、セクション3.6を参照してください。


3.4サードパーティのデータソースによって提供されるデータソースインターフェイス

HDF5などのサードパーティデータをロードする一部のプラグインは、データソースを生成するためのインターフェイスを提供します。ユーザーは、最初にサードパーティのデータをメモリにインポートしたり、ディスクや分散テーブルとして保存したりすることなく、返されたデータソースに対して分散アルゴリズムを直接実行できます。

DolphinDBのHDF5プラグインはhdf5DS関数を提供します。ユーザーは、dsNumパラメーターを設定することにより、生成する必要のあるデータソースの数を指定できます。次の例では、HDF5ファイルから10個のデータソースを生成し、Map-Reduceフレームワークを使用して結果の最初の列のサンプル分散を計算します。

ds = hdf5::hdf5DS("large_file.h5", "large_table", , 10)

def varMap(t) {
    column = t.col(0)
    return [column.sum(), column.sum2(), column.count()]
}

def varFinal(result) {
    sum, sum2, count = result
    mu = sum \ count
    populationVar = sum2 \ count - mu * mu
    sampleVar = populationVar * count \ (count - 1)
    return sampleVar
}

sampleVar = mr(ds, varMap, +, varFinal)


3.5データソースキャッシュ

データソースには、0、1、または複数の位置を指定できます。位置0のデータソースがローカルデータソースです。複数の場所の場合、これらの場所は相互バックアップです。システムは、分散コンピューティングを実行する場所をランダムに選択します。データソースがデータオブジェクトをキャッシュするように指示されると、システムは前回データを正常に取得した場所を選択します。

ユーザーは、データソースをキャッシュするか、キャッシュをクリアするようにシステムに指示できます。反復コンピューティングアルゴリズム(機械学習アルゴリズムなど)の場合、データキャッシングによりコンピューティングパフォーマンスを大幅に向上させることができます。システムメモリが不足している場合、キャッシュされたデータはクリアされます。これが発生した場合、データソースにはすべてのメタ記述とデータ変換関数が含まれているため、システムはデータを回復できます。

データソースのキャッシュに関連する関数は次のとおりです。

  • cacheDS!:データソースをキャッシュするようにシステムに指示します
  • clearcacheDS!:データソースの次の実行後にキャッシュをクリアするようにシステムに指示します
  • cacheDSNow:データソースをすぐに実行してキャッシュし、キャッシュラインの総数を返します
  • clearCacheDSNow:データソースとキャッシュをすぐにクリアします


3.6データソースの変換

データソースオブジェクトには、取得したデータをさらに処理するための複数のデータ変換関数を含めることもできます。システムはこれらのデータ変換関数を順番に実行し、1つの関数の出力を次の関数の入力(および唯一の入力)として使用します。

データソースにデータ変換関数を含めることは、通常、コアコンピューティング操作でデータソースを変換するよりも効果的です(つまり、マップ関数)。取得したデータを1回だけ計算する必要がある場合、パフォーマンスの違いはありませんが、キャッシュされたデータオブジェクトを使用したデータソースの反復計算に大きな違いが生じます。変換操作がコアコンピューティング操作にある場合、変換は反復ごとに実行する必要があります。変換操作がデータソースにある場合、それらは1回だけ実行されます。transDS!関数は、データソースを変換する機能を提供します。

たとえば、反復的な機械学習関数randomForestRegressorを実行する前に、ユーザーはデータに欠落している値を手動で入力する必要がある場合があります(もちろん、DolphinDBのランダムフォレストアルゴリズムには欠落している値の処理が組み込まれています)。この時点で、transDS!を使用して、次のようにデータソースを処理できます。各フィーチャ列について、列の平均値を使用して不足している値を入力します。表の列x0、x1、x2、x3が独立変数であり、列yが従属変数であるとすると、実装方法は次のとおりです。

ds = sqlDS(<select x0, x1, x2, x3, y from t>)
ds.transDS!(def (mutable t) {
    update t set x0 = nullFill(x0, avg(x0)), x1 = nullFill(x1, avg(x1)), x2 = nullFill(x2, avg(x2)), x3 = nullFill(x3, avg(x3))
    return t
})

randomForestRegressor(ds, `y, `x0`x1`x2`x3)

データソースを変換する別の例は、セクション2.2で説明したロジスティック回帰のスクリプト実装です。セクション2.2の実装では、map関数呼び出しには、データソースのテーブルから対応する列をフェッチして行列に変換する操作が含まれます。これは、これらの操作が各反復で発生することを意味します。実際、各反復は同じ入力行列を使用します。この変換ステップは1回だけ呼び出す必要があります。したがって、transDS!を使用して、データソースをx、xt、およびy行列を含むトリプルに変換できます。

def myLrTrans(t, yColName, xColNames, intercept) {
    if (intercept)
        x = matrix(t[xColNames], take(1.0, t.rows()))
    else
        x = matrix(t[xColNames])
    xt = x.transpose()
    y = t[yColName]
    return [x, xt, y]
}

def myLrMap(input, lastFinal) {
    x, xt, y = input
    placeholder, placeholder, theta = lastFinal
    // 之后的计算和2.2节相同
}

// myLrFinal和myLrTerm函数和2.2节相同

def myLr(mutable ds, yColName, xColNames, intercept, initTheta, tol) {
    ds.transDS!(myLrTrans{, yColName, xColNames, intercept})
    logLik, grad, theta = imr(ds, [0, 0, initTheta], myLrMap, +, myLrFinal, myLrTerm{, , tol})
    return theta
}

おすすめ

転載: blog.csdn.net/qq_41996852/article/details/112599763