Productos secos 丨 Tutorial de computación general de DolphinDB

DolphinDB no solo puede almacenar datos distribuidos, sino que también tiene un buen soporte para la computación distribuida. En DolphinDB, los usuarios pueden utilizar el marco de computación distribuida general proporcionado por el sistema para implementar algoritmos distribuidos eficientes a través de scripts sin prestar atención a implementaciones subyacentes específicas. Este artículo proporcionará una explicación detallada de los conceptos importantes y las funciones relacionadas en el marco de trabajo informático general de DolphinDB, y proporcionará una gran cantidad de escenarios y ejemplos de uso específicos.


1. Fuente de datos

Data Source es el concepto básico en el marco informático general de DolphinDB. Es un tipo especial de objeto de datos, que es una meta descripción de datos. Al ejecutar la fuente de datos, los usuarios pueden obtener entidades de datos como tablas, matrices y vectores. En el marco de computación distribuida de DolphinDB, se transmiten objetos de origen de datos livianos en lugar de entidades de datos enormes a nodos remotos para cálculos posteriores, lo que reduce en gran medida el tráfico de red.

En DolphinDB, los usuarios suelen utilizar la función sqlDS para generar una fuente de datos basada en una expresión SQL. Esta función no consulta directamente la tabla, sino que devuelve la metadeclaración de una o más subconsultas SQL, es decir, la fuente de datos. Después de eso, los usuarios pueden usar el marco Map-Reduce para pasar fuentes de datos y funciones de cálculo, distribuir tareas a los nodos correspondientes a cada fuente de datos, completar los cálculos en paralelo y luego resumir los resultados.

En las Secciones 3.1, 3.2, 3.3 y 3.4 de este artículo se describirán en detalle varios métodos comúnmente utilizados para obtener fuentes de datos.


2. Marco Map-Reduce

La función Map-Reduce es la función principal del marco de computación distribuida general de DolphinDB.

2.1 función mr

La sintaxis de la función Map-Reduce mr de DolphinDB es mr (ds, mapFunc, [reduceFunc], [finalFunc], [paralelo = true]), que acepta un conjunto de fuentes de datos y una función mapFunc como parámetros. Distribuye las tareas informáticas a los nodos donde se encuentra cada fuente de datos y procesa los datos en cada fuente de datos a través de mapFunc. El parámetro opcional reduceFunc calculará el valor de retorno de mapFunc por pares, y el resultado obtenido se calculará con el valor de retorno del tercer mapFunc, de modo que el cálculo acumulativo resumirá los resultados de mapFunc. Si hay M llamadas al mapa, la función de reducción se llamará M-1 veces. El parámetro opcional finalFunc procesa aún más el valor de retorno de reduceFunc.

Hay un ejemplo de ejecución de regresión lineal de mínimos cuadrados distribuidos mediante mr en el documento oficial . Este artículo usa el siguiente ejemplo para mostrar cómo usar una llamada mr para muestrear aleatoriamente una décima parte de los datos en cada partición de la tabla distribuida:

// Crear una base de datos y una tabla 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)) 

// definir mapa Función 
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>) // Crear fuente de datos 
res = mr (ds, sampleMap ,, unionAll) // Realizar cálculo

En el ejemplo anterior, la función sampleMap definida por el usuario acepta una tabla (es decir, los datos de la fuente de datos) como parámetro y devuelve aleatoriamente una décima parte de las filas. La función mr en este ejemplo no tiene parámetro reduceFunc, por lo que el valor de retorno de cada función de mapa se coloca en una tupla y se pasa a finalFunc, es decir, unionAll. unionAll fusiona las múltiples tablas devueltas por la función de mapa en una tabla distribuida con particiones secuenciales.

2.2 función imr

La base de datos DolphinDB proporciona una función de cálculo iterativo imr basada en el método Map-Reduce. En comparación con mr, puede admitir cálculos iterativos. Cada iteración utiliza el resultado de la iteración anterior y el conjunto de datos de entrada, por lo que puede admitir la implementación de algoritmos más complejos. El cálculo iterativo requiere los valores iniciales y los criterios de terminación de los parámetros del modelo. Su sintaxis es imr (ds, initValue, mapFunc, [reduceFunc], [finalFunc], terminateFunc, [carryover = false]) donde el parámetro initValue es el valor inicial de la primera iteración, el parámetro mapFunc es una función y el valor aceptado los parámetros incluyen la entidad de origen de datos y la salida de la función final en la iteración anterior. Para la primera iteración, es el valor inicial dado por el usuario. Los parámetros en imr son similares a la función mr. La función finalFunc acepta dos parámetros, el primer parámetro es la salida de la función final en la iteración anterior. Para la primera iteración, es el valor inicial dado por el usuario. El segundo parámetro es la salida después de llamar a la función de reducción. El parámetro terminateFunc se utiliza para determinar si la iteración finaliza. Acepta dos parámetros. El primero es el resultado de la función de reducción en la iteración anterior y el segundo es el resultado de la función de reducción en la iteración actual. Si devuelve verdadero, la iteración se cancelará. El parámetro de transferencia indica si la llamada a la función de mapa genera un objeto que se pasará a la siguiente llamada a la función de mapa. Si el arrastre es verdadero, entonces la función de mapa tiene 3 parámetros y el último parámetro es el objeto transportado, y el resultado de salida de la función de mapa es una tupla, y el último elemento es el objeto transportado. En la primera iteración, el objeto transportado es NULL.

Hay un ejemplo de cálculo de la mediana de datos distribuidos a través de imr en el documento oficial . Este artículo proporcionará un ejemplo más complejo, es decir, utilizando el método de Newton para lograr el cálculo de regresión logística (regresión logística), para mostrar la aplicación de imr en algoritmos de aprendizaje automático.

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函数为数据源中的数据计算在当前的系数下的梯度向量和Hessian矩阵;reduce函数将map的结果相加,相当于求出整个数据集的梯度向量和Hessian矩阵;final函数通过最终的梯度向量和Hessian矩阵对系数进行优化,完成一轮迭代;terminate函数的判断标准是本轮迭代中梯度向量中最大分量的绝对值是否大于参数tol。

这个例子还可以通过数据源转换操作,进一步优化以提高性能,具体参见3.6节。

作为经常使用的分析工具,分布式逻辑回归已经在DolphinDB中作为内置函数实现。内置版本(logisticRegression)提供更多功能。


3. 数据源相关函数

DolphinDB提供了以下常用的方法获取数据源:

3.1 sqlDS函数

sqlDS函数根据输入的SQL元代码创建一个数据源列表。 如果SQL查询中的数据表有n个分区,sqlDS会生成n个数据源。 如果SQL查询不包含任何分区表,sqlDS将返回只包含一个数据源的元组。

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.2 repartitionDS函数

sqlDS的数据源是系统自动根据数据的分区而生成的。有时用户需要对数据源做一些限制,例如,在获取数据时,重新指定数据的分区以减少计算量,或者,只需要一部分分区的数据。repartitionDS函数就提供了重新划分数据源的功能。

函数repartitionDS根据输入的SQL元代码和列名、分区类型、分区方案等,为元代码生成经过重新分区的新数据源。

以下代码提供了一个repartitionDS的例子。在这个例子中,DFS表t中有字段deviceId, time, temperature,分别为symbol, datetime和double类型,数据库采用双层分区,第一层对time按VALUE分区,一天一个分区;第二层对deviceId按HASH分成20个区。

现需要按deviceId字段聚合查询95百分位的temperature。如果直接写查询select percentile(temperature,95) from t group by deviceID,由于percentile函数没有Map-Reduce实现,这个查询将无法完成。

一个方案是将所需字段全部加载到本地,计算95百分位,但当数据量过大时,计算资源可能不足。repartitionDS提供了一个解决方案:将表基于deviceId按其原有分区方案HASH重新分区,每个新的分区对应原始表中一个HASH分区的所有数据。通过mr函数在每个新的分区中计算95百分位的temperature,最后将结果合并汇总。

// 创建数据库
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.3 textChunkDS函数

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框架对结果的第1列求样本方差:

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 数据源转换

一个数据源对象还可以包含多个数据转换函数,用以进一步处理所检索到的数据。系统会依次执行这些数据转换函数,一个函数的输出作为下一个函数的输入(和唯一的输入)。

将数据转换函数包含在数据源中,通常比在核心计算操作(即map函数)中对数据源进行转换更有效。如果检索到的数据仅需要一次计算时,没有性能差异,但它对于具有缓存数据对象的数据源的迭代计算会造成巨大的差异。如果转换操作在核心计算操作中,则每次迭代都需要执行转换; 如果转换操作在数据源中,则它们只被执行一次。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函数调用中包含了从数据源的表中取出对应列,转换成矩阵的操作,这意味着每一轮迭代都会发生这些操作。而实际上,每轮迭代都会使用同样的输入矩阵,这个转换步骤只需要调用一次。因此,可以用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
}


Supongo que te gusta

Origin blog.51cto.com/15022783/2677988
Recomendado
Clasificación