分布式处理与大型数据集处理
在自然语言处理(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 进行分布式单词评分 |
基于Redis与execnet的分布式数据处理
超级会员免费看
898

被折叠的 条评论
为什么被折叠?



