20、分布式处理与大型数据集处理

基于Redis与execnet的分布式数据处理

分布式处理与大型数据集处理

在自然语言处理(NLP)中,处理大量数据时,单处理器的内存和计算能力往往会成为瓶颈。为了充分利用多核CPU、多台计算机的资源,我们可以使用分布式处理技术。本文将介绍如何使用 execnet 进行分布式处理,以及如何使用 Redis 存储频率分布等数据。

1. 使用execnet进行分布式标注

execnet 是一个用于Python的分布式执行库,它允许创建网关和通道来执行远程代码。网关是从调用进程到远程环境的连接,远程环境可以是本地子进程或通过SSH连接到的远程节点。通道则用于在通道创建者和远程代码之间进行通信。

1.1 准备工作

需要安装 execnet ,可以使用以下命令:

sudo pip install execnet

或者

sudo easy_install execnet

当前版本是1.2,官方文档和示例可参考 http://codespeak.net/execnet/

1.2 操作步骤

以下是使用 execnet 进行分布式标注的示例代码:

import execnet, remote_tag, nltk.tag, nltk.data
from nltk.corpus import treebank
import pickle

# 序列化标注器
pickled_tagger = pickle.dumps(nltk.data.load(nltk.tag._POS_TAGGER))

# 创建网关
gw = execnet.makegateway()

# 创建通道
channel = gw.remote_exec(remote_tag)

# 发送序列化的标注器
channel.send(pickled_tagger)

# 发送第一个分词后的句子
channel.send(treebank.sents()[0])

# 接收标注后的句子
tagged_sentence = channel.receive()

# 验证标注结果
print(tagged_sentence == treebank.tagged_sents()[0])

# 关闭网关
gw.exit()
1.3 工作原理

remote_tag.py 模块的代码如下:

import pickle

if __name__ == '__channelexec__':
    tagger = pickle.loads(channel.receive())

    for sentence in channel:
        channel.send(tagger.tag(sentence))

__name__ 等于 '__channelexec__' 时,表示该模块正在被 execnet 用于创建远程通道。首先接收序列化的标注器,然后对每个接收到的句子进行标注并发送回结果。

1.4 更多操作
  • 创建多个通道 :可以创建多个通道来增加并行处理能力。示例代码如下:
import itertools
gw1 = execnet.makegateway()
gw2 = execnet.makegateway()
ch1 = gw1.remote_exec(remote_tag)
ch1.send(pickled_tagger)
ch2 = gw2.remote_exec(remote_tag)
ch2.send(pickled_tagger)
mch = execnet.MultiChannel([ch1, ch2])
queue = mch.make_receive_queue()
channels = itertools.cycle(mch)
for sentence in treebank.sents()[:4]:
    channel = next(channels)
    channel.send(sentence)
tagged_sentences = []
for i in range(4):
    channel, tagged_sentence = queue.get()
    tagged_sentences.append(tagged_sentence)
print(len(tagged_sentences))
gw1.exit()
gw2.exit()
  • 本地与远程网关 :默认网关是 popen ,会在本地机器上创建Python子进程。如果有免密码的SSH访问权限,可以使用 execnet.makegateway('ssh=remotehost') 创建远程网关。
2. 使用execnet进行分布式组块提取

此操作与分布式标注类似,但需要发送两个对象(标注器和组块器),并接收一个 Tree 对象,需要进行序列化和反序列化。

2.1 准备工作

同样需要安装 execnet

2.2 操作步骤
import execnet, remote_chunk
import nltk.data, nltk.tag, nltk.chunk
import pickle
from nltk.corpus import treebank_chunk

# 序列化标注器
tagger = pickle.dumps(nltk.data.load(nltk.tag._POS_TAGGER))

# 序列化组块器
chunker = pickle.dumps(nltk.data.load(nltk.chunk._MULTICLASS_NE_CHUNKER))

# 创建网关
gw = execnet.makegateway()

# 创建通道
channel = gw.remote_exec(remote_chunk)

# 发送标注器和组块器
channel.send(tagger)
channel.send(chunker)

# 发送第一个分词后的句子
channel.send(treebank_chunk.sents()[0])

# 接收并反序列化组块树
chunk_tree = pickle.loads(channel.receive())

# 打印组块树
print(chunk_tree)

# 关闭网关
gw.exit()
2.3 工作原理

remote_chunk.py 模块的代码如下:

import pickle

if __name__ == '__channelexec__':
    tagger = pickle.loads(channel.receive())
    chunker = pickle.loads(channel.receive())

    for sentence in channel:
        chunk_tree = chunker.parse(tagger.tag(sentence))
        channel.send(pickle.dumps(chunk_tree))

首先接收序列化的标注器和组块器,然后对每个接收到的句子进行标注和组块提取,将结果序列化为 Tree 对象并发送回。

3. 使用execnet进行并行列表处理

此操作是一种使用 execnet 并行处理列表的模式,将列表中的每个元素映射到一个新值。

3.1 操作步骤

remote_double.py 模块的代码如下:

if __name__ == '__channelexec__':
    for (i, arg) in channel:
        channel.send((i, arg * 2))

使用 plists.map() 函数进行并行处理:

import plists, remote_double
print(plists.map(remote_double, range(10)))
3.2 工作原理

plists.py map() 函数的代码如下:

import itertools, execnet

def map(mod, args, specs=[('popen', 2)]):
    gateways = []
    channels = []

    for spec, count in specs:
        for i in range(count):
            gw = execnet.makegateway(spec)
            gateways.append(gw)
            channels.append(gw.remote_exec(mod))

    cyc = itertools.cycle(channels)

    for i, arg in enumerate(args):
        channel = next(cyc)
        channel.send((i, arg))

    mch = execnet.MultiChannel(channels)
    queue = mch.make_receive_queue()
    l = len(args)
    results = [None] * l

    for i in range(l):
        channel, (i, result) = queue.get()
        results[i] = result

    for gw in gateways:
        gw.exit()

    return results

首先根据指定的规格创建网关和通道,然后将每个参数发送到通道进行处理。由于结果返回的顺序不确定,使用索引 i 来确保结果按原始顺序组合。

4. 在Redis中存储频率分布

nltk.probability.FreqDist 类用于存储和管理频率分布,但它是内存中的,不支持数据持久化,也不能被多个进程同时访问。可以基于Redis构建一个持久化的 FreqDist

4.1 准备工作

需要安装 Redis redis-py 。可以从 http://redis.io/ 下载 Redis ,使用以下命令安装 redis-py

pip install redis

或者

easy_install redis

确保 redis-server 在本地的6379端口运行。

4.2 操作步骤

以下是在Redis中存储频率分布的示例代码:

from redis import Redis
from redisprob import RedisHashFreqDist

# 创建Redis连接
r = Redis()

# 创建RedisHashFreqDist对象
rhfd = RedisHashFreqDist(r, 'test')

# 验证初始长度
print(len(rhfd))

# 增加一个元素的频率
rhfd['foo'] += 1

# 验证元素频率
print(rhfd['foo'])

# 验证长度
print(len(rhfd))
4.3 工作原理

RedisHashFreqDist 类的代码如下:

from rediscollections import RedisHashMap

class RedisHashFreqDist(RedisHashMap):
    def N(self):
        return int(sum(self.values()))

    def __missing__(self, key):
        return 0

    def __getitem__(self, key):
        return int(RedisHashMap.__getitem__(self, key) or 0)

    def values(self):
        return [int(v) for v in RedisHashMap.values(self)]

    def items(self):
        return [(k, int(v)) for (k, v) in RedisHashMap.items(self)]

RedisHashMap 类的代码如下:

import collections, re

white = re.compile('[\s&]+')

def encode_key(key):
    return white.sub('_', key.strip())

class RedisHashMap(collections.MutableMapping):
    def __init__(self, r, name):
        self._r = r
        self._name = encode_key(name)

    def __iter__(self):
        return self.items()

    def __len__(self):
        return self._r.hlen(self._name)

    def __contains__(self, key):
        return self._r.hexists(self._name, encode_key(key))

    def __getitem__(self, key):
        return self._r.hget(self._name, encode_key(key))

    def __setitem__(self, key, val):
        self._r.hset(self._name, encode_key(key), val)

    def __delitem__(self, key):
        self._r.hdel(self._name, encode_key(key))

    def keys(self):
        return self._r.hkeys(self._name)

    def values(self):
        return self._r.hvals(self._name)

    def items(self):
        return self._r.hgetall(self._name).items()

    def get(self, key, default=0):
        return self[key] or default

    def clear(self):
        self._r.delete(self._name)

RedisHashMap 类使用Redis的哈希映射来存储数据,并重写了一些方法以确保值始终为整数。 RedisHashFreqDist 类继承自 RedisHashMap ,并实现了 N() 方法来计算频率分布的总和。

总结

通过使用 execnet 进行分布式处理和在Redis中存储数据,可以充分利用多核CPU和多台计算机的资源,提高处理大量数据的效率。同时,Redis的持久化和多进程访问能力也为数据存储和管理提供了便利。

流程图

graph LR
    A[开始] --> B[安装execnet]
    B --> C[序列化标注器]
    C --> D[创建网关]
    D --> E[创建通道]
    E --> F[发送标注器和句子]
    F --> G[接收标注结果]
    G --> H[验证结果]
    H --> I[关闭网关]
    I --> J[结束]

表格

操作 描述
分布式标注 使用execnet进行分布式词性标注
分布式组块提取 使用execnet进行分布式组块提取
并行列表处理 使用execnet并行处理列表
存储频率分布 在Redis中存储频率分布

分布式处理与大型数据集处理

5. 在Redis中存储条件频率分布

在自然语言处理中,条件频率分布(Conditional Frequency Distribution)是一种重要的数据结构,用于记录在特定条件下某个事件发生的频率。在本部分,我们将介绍如何在 Redis 中存储条件频率分布。

5.1 准备工作

和之前存储频率分布一样,需要安装 Redis redis-py ,并确保 redis-server 在本地的 6379 端口运行。

5.2 操作步骤

以下是在 Redis 中存储条件频率分布的示例代码:

from redis import Redis
from redisprob import RedisHashConditionalFreqDist

# 创建 Redis 连接
r = Redis()

# 创建 RedisHashConditionalFreqDist 对象
rhcfd = RedisHashConditionalFreqDist(r, 'test_cfd')

# 添加条件频率数据
condition = 'condition1'
sample = 'sample1'
rhcfd[condition][sample] += 1

# 获取条件频率
print(rhcfd[condition][sample])
5.3 工作原理

RedisHashConditionalFreqDist 类的代码如下:

from rediscollections import RedisHashMap

class RedisHashConditionalFreqDist:
    def __init__(self, r, name):
        self._r = r
        self._name = name
        self._cfd = {}

    def __getitem__(self, condition):
        if condition not in self._cfd:
            hash_name = f'{self._name}:{condition}'
            self._cfd[condition] = RedisHashMap(self._r, hash_name)
        return self._cfd[condition]

    def conditions(self):
        keys = self._r.keys(f'{self._name}:*')
        return [key.decode().split(':')[1] for key in keys]

RedisHashConditionalFreqDist 类使用 Redis 的哈希映射来存储条件频率分布。对于每个条件,会创建一个独立的哈希映射来存储该条件下的样本频率。

6. 在 Redis 中存储有序字典

有序字典(Ordered Dictionary)在需要保持元素插入顺序的场景中非常有用。我们可以在 Redis 中存储有序字典。

6.1 准备工作

同样需要安装 Redis redis-py ,并确保 redis-server 正常运行。

6.2 操作步骤

以下是在 Redis 中存储有序字典的示例代码:

from redis import Redis
from rediscollections import RedisOrderedDict

# 创建 Redis 连接
r = Redis()

# 创建 RedisOrderedDict 对象
rod = RedisOrderedDict(r, 'test_od')

# 添加元素
rod['key1'] = 'value1'
rod['key2'] = 'value2'

# 获取元素
print(rod['key1'])
6.3 工作原理

RedisOrderedDict 类的代码如下:

import collections
from rediscollections import RedisHashMap

class RedisOrderedDict(collections.MutableMapping):
    def __init__(self, r, name):
        self._r = r
        self._name = name
        self._order_key = f'{name}:order'
        self._data = RedisHashMap(r, name)

    def __setitem__(self, key, value):
        if key not in self._data:
            self._r.rpush(self._order_key, key)
        self._data[key] = value

    def __delitem__(self, key):
        if key in self._data:
            del self._data[key]
            self._r.lrem(self._order_key, 0, key)

    def __getitem__(self, key):
        return self._data[key]

    def __iter__(self):
        keys = self._r.lrange(self._order_key, 0, -1)
        return (key.decode() for key in keys)

    def __len__(self):
        return self._r.llen(self._order_key)

RedisOrderedDict 类使用 Redis 的列表来存储元素的顺序,使用哈希映射来存储元素的键值对。

7. 使用 Redis 和 execnet 进行分布式单词评分

在处理大量文本数据时,我们可能需要对单词进行评分。可以结合 Redis 和 execnet 进行分布式单词评分。

7.1 准备工作

安装 execnet Redis redis-py ,并确保 redis-server 运行。

7.2 操作步骤

以下是使用 Redis 和 execnet 进行分布式单词评分的示例代码:

import execnet, remote_score
import redis
from nltk.corpus import words

# 创建 Redis 连接
r = redis.Redis()

# 序列化评分函数相关数据
# 这里假设评分函数需要的一些数据已经准备好
data = {'param1': 'value1'}
import pickle
pickled_data = pickle.dumps(data)

# 创建网关
gw = execnet.makegateway()

# 创建通道
channel = gw.remote_exec(remote_score)

# 发送评分数据
channel.send(pickled_data)

# 发送单词列表
word_list = words.words()[:10]
for word in word_list:
    channel.send(word)

# 接收评分结果
scores = []
for _ in range(len(word_list)):
    score = channel.receive()
    scores.append(score)

# 打印评分结果
print(scores)

# 关闭网关
gw.exit()
7.3 工作原理

remote_score.py 模块的代码如下:

import pickle

if __name__ == '__channelexec__':
    data = pickle.loads(channel.receive())
    for word in channel:
        # 这里实现具体的评分逻辑
        score = len(word)  # 简单示例,根据单词长度评分
        channel.send(score)

首先,将评分函数需要的数据序列化并发送到远程节点。然后,将单词列表发送到远程节点进行评分。远程节点接收到数据和单词后,进行评分并将结果发送回本地。

总结

通过在 Redis 中存储条件频率分布、有序字典,以及结合 Redis 和 execnet 进行分布式单词评分,我们可以更好地处理大型数据集。Redis 的持久化和多进程访问能力,以及 execnet 的分布式处理能力,为自然语言处理中的数据存储和计算提供了强大的支持。

流程图

graph LR
    A[开始] --> B[安装 Redis 和 redis-py]
    B --> C[创建 Redis 连接]
    C --> D[创建存储对象(如 CFD、OD)]
    D --> E[添加数据]
    E --> F[获取数据]
    F --> G[结束]

表格

操作 描述
存储条件频率分布 在 Redis 中存储条件频率分布
存储有序字典 在 Redis 中存储有序字典
分布式单词评分 使用 Redis 和 execnet 进行分布式单词评分
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值