使用地址匹配的示例
现在我们已经测量了数值和文本距离,我们将花一些时间学习如何将它们组合起来测量具有文本和数字特征的观察之间的距离。
做好准备
最近邻是一种用于地址匹配的好算法。地址匹配是一种记录匹配,其中我们在多个数据集中具有地址并且想要匹配它们。在地址匹配中,我们可能在地址,不同城市或不同的邮政编码中存在拼写错误,但它们可能都指向相同的地址。在地址的数字和字符组件上使用最近邻居算法可以帮助我们识别实际上相同的地址。
在此示例中,我们将生成两个数据集。每个数据集将包含街道地址和邮政编码。但是,一个数据集在街道地址中存在大量拼写错误。我们将非拼写数据集作为我们的黄金标准,并将为每个拼写错误地址返回一个地址,该地址最接近字符串距离(对于街道)和数字距离(对于邮政编码)的函数。
代码的第一部分将侧重于生成两个数据集。然后,代码的第二部分将运行测试集并返回训练集中最接近的地址。
操作步骤
我们将按如下方式处理秘籍:
- 我们将从加载必要的库开始:
import random
import string
import numpy as np
import tensorflow as tf
- 我们现在将创建参考数据集。为了显示简洁的输出,我们只会使每个数据集由
10
地址组成(但它可以运行更多):
n = 10 street_names = ['abbey', 'baker', 'canal', 'donner', 'elm']
street_types = ['rd', 'st', 'ln', 'pass', 'ave']
rand_zips = [random.randint(65000,65999) for i in range(5)]
numbers = [random.randint(1, 9999) for i in range(n)]
streets = [random.choice(street_names) for i in range(n)]
street_suffs = [random.choice(street_types) for i in range(n)]
zips = [random.choice(rand_zips) for i in range(n)]
full_streets = [str(x) + ' ' + y + ' ' + z for x,y,z in zip(numbers, streets, street_suffs)]
reference_data = [list(x) for x in zip(full_streets,zips)]
- 要创建测试集,我们需要一个函数,它将在字符串中随机创建一个拼写错误并返回结果字符串:
def create_typo(s, prob=0.75):
if random.uniform(0,1) < prob:
rand_ind = random.choice(range(len(s)))
s_list = list(s)
s_list[rand_ind]=random.choice(string.ascii_lowercase)
s = ''.join(s_list)
return s
typo_streets = [create_typo(x) for x in streets]
typo_full_streets = [str(x) + ' ' + y + ' ' + z for x,y,z in zip(numbers, typo_streets, street_suffs)]
test_data = [list(x) for x in zip(typo_full_streets,zips)]
- 现在,我们可以初始化图会话并声明我们需要的占位符。我们在每个测试和参考集中需要四个占位符,我们需要一个地址和邮政编码占位符:
sess = tf.Session()
test_address = tf.sparse_placeholder( dtype=tf.string)
test_zip = tf.placeholder(shape=[None, 1], dtype=tf.float32)
ref_address = tf.sparse_placeholder(dtype=tf.string)
ref_zip = tf.placeholder(shape=[None, n], dtype=tf.float32)
- 现在,我们将声明数字 zip 距离和地址字符串的编辑距离:
zip_dist = tf.square(tf.subtract(ref_zip, test_zip))
address_dist = tf.edit_distance(test_address, ref_address, normalize=True)
- 我们现在将拉链距离和地址距离转换为相似之处。对于相似性,当两个输入完全相同时,我们想要
1
的相似性,当它们非常不同时,我们想要0
附近。对于拉链距离,我们可以通过获取距离,从最大值减去,然后除以距离的范围来实现。对于地址相似性,由于距离已经在0
和1
之间缩放,我们只需从 1 中减去它以获得相似性:
zip_max = tf.gather(tf.squeeze(zip_dist), tf.argmax(zip_dist, 1))
zip_min = tf.gather(tf.squeeze(zip_dist), tf.argmin(zip_dist, 1))
zip_sim = tf.divide(tf.subtract(zip_max, zip_dist), tf.subtract(zip_max, zip_min))
address_sim = tf.subtract(1., address_dist)
- 为了结合两个相似度函数,我们将采用两者的加权平均值。对于这个秘籍,我们对地址和邮政编码给予同等重视。我们可以根据我们对每个特征的信任程度来改变这一点。然后,我们将返回参考集的最高相似度的索引:
address_weight = 0.5
zip_weight = 1\. - address_weight
weighted_sim = tf.add(tf.transpose(tf.multiply(address_weight, address_sim)), tf.multiply(zip_weight, zip_sim))
top_match_index = tf.argmax(weighted_sim, 1)
- 为了在 TensorFlow 中使用编辑距离,我们必须将地址字符串转换为稀疏向量。在本章的先前秘籍中,使用基于文本的距离,我们创建了以下函数,我们也将在此秘籍中使用它:
def sparse_from_word_vec(word_vec):
num_words = len(word_vec)
indices = [[xi, 0, yi] for xi,x in enumerate(word_vec) for yi,y in enumerate(x)]
chars = list(''.join(word_vec))
# Now we return our sparse vector
return tf.SparseTensorValue(indices, chars, [num_words,1,1])
- 我们需要将参考数据集中的地址和邮政编码分开,以便在循环测试集时将它们提供给占位符:
reference_addresses = [x[0] for x in reference_data]
reference_zips = np.array([[x[1] for x in reference_data]])
- 我们需要使用我们在步骤 8 中创建的函数创建稀疏张量参考地址集:
sparse_ref_set = sparse_from_word_vec(reference_addresses)
- 现在,我们可以循环遍历测试集的每个条目,并返回它最接近的引用集的索引。我们将为每个条目打印测试和参考集。如您所见,我们在此生成的数据集中获得了很好的结果:
for i in range(n):
test_address_entry = test_data[i][0]
test_zip_entry = [[test_data[i][1]]]
# Create sparse address vectors
test_address_repeated = [test_address_entry] * n
sparse_test_set = sparse_from_word_vec(test_address_repeated)
feeddict={test_address: sparse_test_set,
test_zip: test_zip_entry,
ref_address: sparse_ref_set,
ref_zip: reference_zips}
best_match = sess.run(top_match_index, feed_dict=feeddict)
best_street = reference_addresses[best_match[0]]
[best_zip] = reference_zips[0][best_match]
[[test_zip_]] = test_zip_entry
print('Address: ' + str(test_address_entry) + ', ' + str(test_zip_))
print('Match : ' + str(best_street) + ', ' + str(best_zip))
我们将得到以下结果:
Address: 8659 beker ln, 65463
Match : 8659 baker ln, 65463
Address: 1048 eanal ln, 65681
Match : 1048 canal ln, 65681
Address: 1756 vaker st, 65983
Match : 1756 baker st, 65983
Address: 900 abbjy pass, 65983
Match : 900 abbey pass, 65983
Address: 5025 canal rd, 65463
Match : 5025 canal rd, 65463
Address: 6814 elh st, 65154
Match : 6814 elm st, 65154
Address: 3057 cagal ave, 65463
Match : 3057 canal ave, 65463
Address: 7776 iaker ln, 65681
Match : 7776 baker ln, 65681
Address: 5167 caker rd, 65154
Match : 5167 baker rd, 65154
Address: 8765 donnor st, 65154
Match : 8765 donner st, 65154
工作原理
在像这样的地址匹配问题中要弄清楚的一个难点是权重的值以及如何缩放距离。这可能需要对数据本身进行一些探索和洞察。此外,在处理地址时,我们应该考虑除此处使用的组件之外的组件。我们可以将街道号码视为街道地址的独立组成部分,甚至可以包含其他组成部分,例如城市和州。
处理数字地址组件时,请注意它们可以被视为数字(具有数字距离)或字符(具有编辑距离)。由您决定选择哪个。请注意,如果我们认为邮政编码中的拼写错误来自人为错误而不是计算机映射错误,我们可以考虑使用邮政编码的编辑距离。
为了了解拼写错误如何影响结果,我们鼓励读者更改拼写错误函数以进行更多拼写错误或更频繁的拼写错误,并增加数据集大小以查看此算法的工作情况。