前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GPU加速04:将CUDA应用于金融领域,使用Python Numba加速B-S期权估值模型

GPU加速04:将CUDA应用于金融领域,使用Python Numba加速B-S期权估值模型

作者头像
PP鲁
发布2019-12-26 14:28:50
1.8K0
发布2019-12-26 14:28:50
举报
文章被收录于专栏:皮皮鲁的AI星球

很多领域尤其是机器学习场景对GPU计算力高度依赖,所幸一些成熟的软件或框架已经对GPU调用做了封装,使用者无需使用CUDA重写一遍,但仍需要对GPU计算的基本原理有所了解。对于一些无法调用框架的场景,当数据量增大时,非常有必要进行GPU优化。量化金融是一个非常好的应用GPU并行编程的领域。

本文为英伟达GPU计算加速系列的第四篇,主要基于前三篇文章的内容,以金融领域期权估值案例来进行实战练习。前三篇文章为:

  1. AI时代人人都应该了解的GPU知识:主要介绍了CPU与GPU的区别、GPU架构、CUDA软件栈简介。
  2. 超详细Python Cuda零基础入门教程:主要介绍了CUDA核函数,Thread、Block和Grid概念,内存分配,并使用Python Numba进行简单的并行计算。
  3. 让Cuda程序如虎添翼的优化技巧:主要从并行度和内存控制两个方向介绍了多流和共享内存两个优化技术。

阅读完以上文章后,相信读者已经对英伟达GPU编程有了初步的认识,这篇文章将谈谈如何将GPU编程应用到实际问题上,并使用Python Numba给出具体的B-S模型实现。

2017年 于瑞士

应用场景

我在本系列开篇就曾提到目前GPU的应用场景非常广泛:金融建模、自动驾驶、智能机器人、新材料发现、神经科学、医学影像...不同学科一般都有相应的软件,比如分子动力学模拟软件AMBER 16在英伟达的GPU上的运行速度比仅使用CPU的系统快15倍;金融领域则需要使用GPU加速的机器学习来对各类金融产品做分析和预测。

GPU计算加速使用最广泛的领域要数机器学习和深度学习了。各行各业(包括金融量化)都可以将本领域的问题转化为机器学习问题。幸运的是,一些大神帮我们做了封装,做成了框架供我们直接调用,省去了自己编写机器学习算法的时间,并且对GPU的支持非常强,无论是Google家的TensorFlow,还是Facebook家的PyTorch,以及XGBoost都对英伟达GPU有了非常不错的支持,程序员几乎不需要了解太多CUDA编程技术。虽然机器学习工程师不需要了解编程知识,但还是需要了解一些GPU的基础知识(详见本系列第一篇文章),非常有助于其深度学习项目的落地。关于TensorFlow等框架如何调用GPU,大家可先参考这些框架各自的官方文档。

还有很多问题是与具体场景高度相关的,并不能直接用这些框架和库,需要编程人员针对具体问题来编程。例如量化金融领域常常使用蒙特卡洛模拟,而CUDA对蒙特卡洛模拟也有非常好的支持,当数据量增大时,CUDA的优势非常明显。本文以金融领域著名的Black-Scholes模型为案例来展示如何使用Python Numba进行CUDA并行加速。B-S模型为Python Numba官方提供的样例程序,我在原来基础上做了一些简单修改。

Black-Scholes模型简介

Black-Scholes模型,简称B-S模型,是一种对金融产品估价的数学模型。该模型由Fischer Black和Myron Scholes提出,后由Robert Merton完善,这几位学者凭借该模型荣获1997年荣获诺贝尔经济学奖。金融主要是在研究现在的钱与未来的钱的价值问题,B-S模型就是一种对期权产品初始价格和交割价格估值的方法。模型的公式如下。

B-S模型

使用上面这个公式,给定期权现价S、交割价格K,期权时间t,可以计算出看涨期权(Call Option)和看跌期权(Put Option)的价格。我本人并不是金融科班出身,就不在此班门弄斧解释这个模型的金融含义了。对于程序员来说,一个重要的能力就是不需要对业务有太深入理解,也能使用代码实现需求。

B-S模型的Python实现

这里我随机生成了一组数据,包括期权现价S、交割价格K和期权时间t,数据维度分别为1000、100000, 1000000, 4000000。分别用"Python + Numpy"和"CUDA"方式实现,在高性能的Intel E5-2690 v4 CPU和Telsa V100 PCI-E版上运行,运行耗时如下图所示。数据量越小,Python和Numpy在CPU上运行的程序越有优势,随着数据量增大,CPU程序耗时急速上升,GPU并行计算的优势凸显。当数据量为400万时,CUDA程序可以获得30+倍速度提升!试想,如果一个程序之前需要在CPU上跑一天,改成CUDA并行计算后,可能只需要一个小时,这是何等程度的生产力提升啊!

耗时对比

在实现B-S模型时,编写了一个正态分布的累计概率分布函数(Cumulative Distribution Function):cnd。关于概率密度函数和累计概率分布函数我这里不做赘述,本科的概率论课程都会涉及,网络上也有很多详细介绍。我随机初始化了一些数据,并保存在了numpy向量中。注意,在CPU上使用numpy时,尽量不要用for对数组中每个数据处理,而要使用numpy的向量化函数。对于CPU程序来说,numpy向量尽量使用numpy.log()numpy.sqrt()numpy.where()等函数,因为numpy在CPU上做了大量针对向量的计算优化。但是对于标量,numpy可能比math库慢。还需要注意的是,Numba的CUDA有可能不支持部分numpy向量操作。其他CPU的Python加速技巧,我会在后续文章中分享。

Python Numba库支持的Numpy特性:https://numba.pydata.org/numba-doc/dev/reference/numpysupported.html

整个程序如下:

代码语言:javascript
复制
import numpy as np
import math
from time import time
from numba import cuda
from numba import jit
import matplotlib
# 使用无显示器的服务器进行计算时,需加上下面这行,否则matplot报错
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def timeit(func ,number_of_iterations, input_args):

    # 计时函数
    start = time()
    for i in range(number_of_iterations):
        func(*input_args)
    stop = time()
    return stop - start

def randfloat(rand_var, low, high):

    # 随机函数
    return (1.0 - rand_var) * low + rand_var * high


RISKFREE = 0.02

VOLATILITY = 0.30

def cnd(d):
    # 正态分布累计概率分布函数
    A1 = 0.31938153
    A2 = -0.356563782
    A3 = 1.781477937
    A4 = -1.821255978
    A5 = 1.330274429
    RSQRT2PI = 0.39894228040143267793994605993438
    K = 1.0 / (1.0 + 0.2316419 * np.abs(d))
    ret_val = (RSQRT2PI * np.exp(-0.5 * d * d) *
               (K * (A1 + K * (A2 + K * (A3 + K * (A4 + K * A5))))))
    return np.where(d > 0, 1.0 - ret_val, ret_val)
    # 上面的Numpy函数比下面使用循环效率高很多
    # for i in range(len(d)):
    #     if d[i] > 0:
    #         ret_val[i] = 1.0 - ret_val[i]
    # return ret_val

def black_scholes(stockPrice, optionStrike, optionYears, riskFree, volatility):

    # Python + Numpy 实现B-S模型
    S = stockPrice
    K = optionStrike
    T = optionYears
    r = riskFree
    V = volatility
    sqrtT = np.sqrt(T)
    d1 = (np.log(S / K) + (r + 0.5 * V * V) * T) / (V * sqrtT)
    d2 = d1 - V * sqrtT
    cndd1 = cnd(d1)
    cndd2 = cnd(d2)
    expRT = np.exp(- r * T)
    callResult = S * cndd1 - K * expRT * cndd2
    putResult = K * expRT * (1.0 - cndd2) - S * (1.0 - cndd1)
    return callResult, putResult

@cuda.jit(device=True)

def cnd_cuda(d):
    # 正态分布累计概率分布函数
    # CUDA设备端函数
    A1 = 0.31938153
    A2 = -0.356563782
    A3 = 1.781477937
    A4 = -1.821255978
    A5 = 1.330274429
    RSQRT2PI = 0.39894228040143267793994605993438
    K = 1.0 / (1.0 + 0.2316419 * math.fabs(d))
    ret_val = (RSQRT2PI * math.exp(-0.5 * d * d) *
               (K * (A1 + K * (A2 + K * (A3 + K * (A4 + K * A5))))))
    if d > 0:
        ret_val = 1.0 - ret_val
    return ret_val

@cuda.jit

def black_scholes_cuda_kernel(callResult, putResult, S, K,

                       T, r, V):
    i = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x
    if i >= S.shape[0]:
        return
    sqrtT = math.sqrt(T[i])
    d1 = (math.log(S[i] / K[i]) + (r + 0.5 * V * V) * T[i]) / (V * sqrtT)
    d2 = d1 - V * sqrtT
    cndd1 = cnd_cuda(d1)
    cndd2 = cnd_cuda(d2)
    expRT = math.exp((-1. * r) * T[i])
    callResult[i] = (S[i] * cndd1 - K[i] * expRT * cndd2)
    putResult[i] = (K[i] * expRT * (1.0 - cndd2) - S[i] * (1.0 - cndd1))

def black_scholes_cuda(stockPrice, optionStrike,

                        optionYears, riskFree, volatility):
    # CUDA实现B-S模型
    blockdim = 1024
    griddim = int(math.ceil(float(len(stockPrice))/blockdim))
    device_callResult = cuda.device_array_like(stockPrice)
    device_putResult = cuda.device_array_like(stockPrice)
    device_stockPrice = cuda.to_device(stockPrice)
    device_optionStrike = cuda.to_device(optionStrike)
    device_optionYears = cuda.to_device(optionYears)
    black_scholes_cuda_kernel[griddim, blockdim](
            device_callResult, device_putResult, device_stockPrice, device_optionStrike,
            device_optionYears, riskFree, volatility)
    callResult = device_callResult.copy_to_host()
    putResult= device_putResult.copy_to_host()
    cuda.synchronize()
    return callResult, putResult


def main():

    pure_time_list = []
    cuda_time_list = []
    dtype = np.float32
    data_size = [10, 1000, 100000, 1000000, 4000000]
    for OPT_N in data_size:
        print("data size :" + str(OPT_N))
        stockPrice = randfloat(np.random.random(OPT_N), 5.0, 30.0).astype(dtype)
        optionStrike = randfloat(np.random.random(OPT_N), 1.0, 100.0).astype(dtype)
        optionYears = randfloat(np.random.random(OPT_N), 0.25, 10.0).astype(dtype)
        input_args=(stockPrice, optionStrike, optionYears, RISKFREE, VOLATILITY)
        pure_duration = timeit(black_scholes, 20, input_args)
        pure_time_list.append(pure_duration)
        cuda_duration = timeit(black_scholes_cuda, 20, input_args)
        cuda_time_list.append(cuda_duration)
    print(pure_time_list)
    print(cuda_time_list)
    plt.plot(pure_time_list[1:], label='pure python')
    plt.plot(cuda_time_list[1:], label='cuda')
    plt.legend(loc='upper right')
    plt.xticks(np.arange(5), ('1000', '100000', '1000000', '4000000'))
    #设置坐标轴名称
    plt.ylabel('duration (second)')
    plt.xlabel('option number')
    plt.savefig("benchmark.png")
    plt.show()

if __name__ == "__main__":

    main()

程序都是非常基础的CUDA使用技巧,在我的第二篇文章中都有提到,并没有使用太多优化技巧。其中,cnd_cuda函数使用了@cuda.jit(device=True)修饰,表示这个函数只是GPU端做计算的设备函数。

小结

很多领域尤其是机器学习场景对GPU计算力高度依赖,所幸一些成熟的软件或框架已经对GPU调用做了封装,使用者无需使用CUDA重写一遍,但仍需要对GPU计算的基本原理有所了解。对于一些无法调用框架的场景,当数据量增大时,非常有必要进行GPU优化。量化金融中经常使用蒙特卡洛模拟和机器学习等技术,是一个非常好的应用GPU并行编程的领域。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 皮皮鲁的AI星球 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 应用场景
  • Black-Scholes模型简介
  • B-S模型的Python实现
  • 小结
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档