问题集 3:文档间距离
介绍
目标
• 介绍 Python 中字典(dictionary)的概念
• 编写和调用 Python 中的辅助函数
合作
• 学生可以一起合作,但每个学生都应该单独编写和提交作业。学生不能提交完全相同的代码。
• 学生不允许查看或复制彼此的代码或代码结构。
• 在每个文件的开头的注释中包含你合作伙伴的名字。
尽管这个手册很长,但这里的信息是为了给你提供背景、实用的例子和提示,所以一定要仔细阅读。
A)文件设置
下载文件 1_ps3.zip 并将所有文件提取到同一目录中。包含的文件有:document_distance.py、test_ps3_student.py 和 tests/student_tests 目录中
的各种文本和歌词文档。当你完成后,确保运行测试器文件 test_ps3_student.py 来检查你的代码是否符合我们的某些测试用例。
你只需修改 document_distance.py。
B)文档距离概述
针对给定的两个单词或文档,你将计算一个介于 0 和 1 之间的分数,以表示它们的相似程度。如果单词或文档相同,则得分为 1。如果文档完全不同,则
得分为 0。你将以两种不同的方式计算分数,并观察哪种方式效果更好。第一种方法将使用两个文本中的单个单词频率。第二种方法将使用 TF-IDF(词
频-逆文档频率) 来处理文件中的单词。
注意,在整个 pset 中你不需要担心大小写敏感性。所有输入都将是小写的。
1)文本转列表
在任何数据分析问题中,第一步是准备数据。我们提供了一个名为 load_file 的函数来读取文本文件,并将文件中的所有文本输出到一个字符串中。此函
数接受一个名为 filename 的变量,该变量是要加载的文件名的字符串,包括扩展名。
该函数删除所有标点符号,并将文本保存为字符串。不要修改此函数。
这是一个示例用法:
# hello_world.txt looks like this: 'hello world, hello'
>>> text = load_file("tests/student_tests/hello_world.txt")
>>> text
'hello world hello'
接下来,你需要将字符串转换为文本的列表表示形式。基于上面给定的示例,期望得到如下的效果:
>>> text_to_list('hello world hello')
[‘hello’, ‘world’, ‘hello’]
按照给定的说明和文档字符串在 document_distance.py 中实现 text_to_list。除了运行测试文件外,你还可以通过取消注释 document_distance.py
底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 0: Prep Data
test_directory = "tests/student_tests/"
hello_world, hello_friend = load_file(test_directory + 'hello_world.txt'), load_file(test_directory + 'hello_friend.txt')
world, friend = text_to_list(hello_world), text_to_list(hello_friend)
print(world) # should print ['hello', 'world', 'hello']
print(friend) # should print ['hello', 'friends']
1
注意:你可以假设我们提供的文本文件中唯一的空白是换行符或单词之间的空格(即没有制表符)
2)获取频率
让我们开始计算给定列表中每个元素的频率。目标是返回一个字典,其中每个唯一元素作为键,元素在列表中出现的次数作为值。
参考以下示例:
示例 1:
>>> get_frequencies(['h', 'e', 'l', 'l', 'o'])
{'h': 1, 'e': 1, 'l': 2, 'o': 1}
示例 2:
>>> get_frequencies(['hello', 'world', 'hello'])
{'hello': 2, 'world': 1}
根据以上说明和提供的文档字符串在 document_distance.py 中实现 get_frequencies。除了运行测试文件外,你还可以通过取消注释
document_distance.py 底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 1: Get Frequencies
test_directory = "tests/student_tests/"
hello_world, hello_friend = load_file(test_directory + 'hello_world.txt'), load_file(test_directory + 'hello_friend.txt')
world, friend = text_to_list(hello_world), text_to_list(hello_friend)
world_word_freq = get_frequencies(world)
friend_word_freq = get_frequencies(friend)
print(world_word_freq) # should print {'hello': 2, 'world': 1}
print(friend_word_freq) # should print {'hello': 1, 'friends': 1}
3)字母频率
现在,给定一个字符串形式的单词,让我们创建一个字典,其中每个字母作为键,每个字母在单词中出现的次数作为值。这听起来和 get_frequencies 非
常相似…
提示:你必须在 get_letter_frequencies 中调用 get_frequencies 。
示例 1:
>>> get_letter_frequencies('hello')
{'h': 1, 'e': 1, 'l': 2, 'o': 1}
示例 2:
>>> get_letter_frequencies('that')
{'t': 2, 'h': 1, 'a': 1}
根据以上说明和提供的文档字符串在 document_distance.py 中实现 get_letter_frequencies。除了运行测试文件外,你还可以通过取消注释
document_distance.py 底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 2: Get Letter Frequencies
freq1 = get_letter_frequencies('hello')
freq2 = get_letter_frequencies('that')
print(freq1) # should print {'h': 1, 'e': 1, 'l': 2, 'o': 1}
print(freq2) # should print {'t': 2, 'h': 1, 'a': 1}
4)相似性
终于到了计算相似度的时候!根据下一个段落中给出的 相似性 定义,完成函数 calculate_similarity_score。你的函数应该能够与 get_frequencies 或
get_letter_frequencies 的输出一起使用。
考虑两个列表 𝐿1 和 𝐿2 。设 𝑈 是由 𝐿1 、 𝐿2 中的所有元素组成的一个列表,但不重复(例如,如果 𝐿1 = ['a', 'b'],𝐿2 = ['b', 'c'],则 𝑈 = ['a', 'b', 'c'])。
对于 𝐿1 或 𝐿2 中的元素 𝑒,令
count(𝑒,𝐿𝑖
) = {
𝑒 出现在 𝐿𝑖 中的次数 如果 𝑒 在 𝐿𝑖 中
0 如果 𝑒 不在 𝐿𝑖 中
我们可以定义:
• 𝜎(𝑒) = |count(𝑒,𝐿1
) + count(𝑒,𝐿2
)| . (其中竖线表示绝对值),以及
2
• 𝜎(𝑒) = count(𝑒,𝐿1
) + count(𝑒,𝐿2
) .
相似性 定义为:
1 −
𝛿(𝑢1
) + 𝛿(𝑢2
) + 𝛿(𝑢3
) + …
𝜎(𝑢1
) + 𝜎(𝑢2
) + 𝜎(𝑢3
) + …
其中求和是对 𝑈 中所有元素 𝑢1
, 𝑢2
, 𝑢3
, … 进行的,结果四舍五入到小数点后两位。
示例(元素是单词):
• 假设
‣ 𝐿1 = ['hello', 'world', 'hello'],
‣ 𝐿2 = ['hello', 'friends']。
• 去掉重复元素后的列表 𝑈 = ['hello', 'world', 'friends']。
• 频率差 𝛿(𝑢) 是
‣ 𝛿('hello') = 2 + 1 = 3
‣ 𝛿('world') = 1 + 0 = 1
‣ 𝛿('friends') = 0 + 1 = 1
• 因此,相似性 是 1 −
1
3
+
+
1
1
+
+
1
1 = 1 −
3
5 = 0.4
你可以在 calculate_similarity_score 的文档字符串中找到相同的计算和另一种(等效的)解释。
重要提示:确保将最终的相似度计算结果四舍五入到小数点后两位。
根据给定的说明和文档字符串在 document_distance.py 中实现函数 calculate_similarity_score。除了运行测试文件外,你还可以通过取消注释
document_distance.py 底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 3: Similarity
test_directory = "tests/student_tests/"
hello_world, hello_friend = load_file(test_directory + 'hello_world.txt'), load_file(test_directory + 'h
world, friend = text_to_list(hello_world), text_to_list(hello_friend)
world_word_freq = get_frequencies(world)
friend_word_freq = get_frequencies(friend)
word1_freq = get_letter_frequencies('toes')
word2_freq = get_letter_frequencies('that')
word3_freq = get_frequencies('nah')
word_similarity1 = calculate_similarity_score(word1_freq, word1_freq)
word_similarity2 = calculate_similarity_score(word1_freq, word2_freq)
word_similarity3 = calculate_similarity_score(word1_freq, word3_freq)
word_similarity4 = calculate_similarity_score(world_word_freq, friend_word_freq)
print(word_similarity1) # should print 1.0
print(word_similarity2) # should print 0.25
print(word_similarity3) # should print 0.0
print(word_similarity4) # should print 0.4
5)出现频次最高的单词
接下来,你将找出在两个字典中哪个单词出现的频率最高。你将计算每个单词在两个文本中出现的次数,并返回出现频率最高的单词列表。出现频率最高
的单词不必要在两个字典中都有。它是基于两个字典中所有单词频率的总和。如果一个单词在两个字典中都出现,则将频率之和视为该单词的总频率。如
果多个单词并列(即具有相同的最高频率),则返回按字母顺序排列的所有这些单词的列表。
举例,参考以下用例:
>>> freq1 = {"hello": 5, "world": 1}
>>> freq2 = {"hello": 1, "world": 5}
>>> get_most_frequent_words(freq1, freq2)
["hello", "world"]
根据给定的说明和文档字符串在 document_distance.py 中实现函数 get_most_frequent_words。除了运行测试文件外,你还可以通过取消注释
document_distance.py 底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 4: Most Frequent Word(s)
freq_dict1, freq_dict2 = {"hello": 5, "world": 1}, {"hello": 1, "world": 5}
most_frequent = get_most_frequent_words(freq_dict1, freq_dict2)
print(most_frequent)
# should print ["hello", "world"]
6)词频-逆文档频率(TF-IDF)
在这一部分,你将计算词频-逆文档频率,这是一个数值度量,表示单词在文档中的重要性。你将首先计算词频和逆文档频率,然后将两者结合起来得到
TF-IDF。
词频 (TF) 的计算公式为:
3
TF(𝑤) =
number of times word
total number of words in the document
𝑤 appears in the document
TF(𝑤) =
𝑤 在文档中出现的次数
文档中的总单词数
逆文档频率 (IDF) 的计算公式为:
IDF(𝑤) = log10(
number of documents with word
total number of documents
𝑤 in it)
IDF(𝑤) = log10(含 𝑤
文档数量
的文档数量)
其中 log10 是以 10 为底的对数,可以调用 math.log10 完成计算。
结合 TF 和 IDF,我们可以得到 TF-IDF(𝑤) = TF(𝑤) × IDF(𝑤),其中值越高,单词越稀有,反之亦然。在本题目集中,我们只处理单个单词,但 TF-IDF
也适用于更大的单词组(例如 二元组、三元组等)。
你需要实现 get_tf 函数,它的输入参数 text_file 表示一个文件名,你需要加载对应文件,准备数据,并计算 text_file 中每个单词的 TF 值。输出应该
是一个字典,将每个单词映射到其 TF。想想你如何可以利用之前的函数。
你需要实现 get_idf 函数,它的输入参数 text_files 表示一个文件名列表,你需要加载每个文件,准备数据,并计算 text_files 中所有单词的 IDF 值。
输出应该是一个字典,将每个单词映射到其 IDF。
你需要实现 get_tfidf 函数,它的输入参数 text_file 和 text_files 分别表示一个文件名和一个文件名列表,你需要加载对应文件,准备数据,并计算
text_file 中所有单词的 TF-IDF 值。输出应该是一个排序后的元组列表(按 TF-IDF 分数递增),其中每个元组的形式为 (word, TF-IDF) 。如果多个单词
具有相同的 TF-IDF,则按字母顺序递增排序。
以下是一些用例:
>>> text_file = "tests/student_tests/hello_world.txt"
>>> get_tf(text_file)
{"hello": 0.6666666666666666, "world": 0.3333333333333333}
# Explanation: There are 3 total words in "hello_world.txt". 2 of the three total words are "hello", giving
>>> text_files = ["tests/student_tests/hello_world.txt", "tests/student_tests/hello_friends.txt"]
>>> get_idf(text_files)
{"hello": 0.0, "world": 0.3010299956639812, "friends": 0.3010299956639812}
# Explanation: There are a total of 2 documents in this example. "hello" is in both documents, giving "hello
>>> text_file = "tests/student_tests/hello_world.txt"
>>> text_files = ["tests/student_tests/hello_world.txt", "tests/student_tests/hello_friends.txt"]
>>> get_tfidf(text_file, text_files)
[('hello', 0.0), ('world', 0.10034333188799373)]
# Explanation: We multiply the corresponding TF and IDF values for each word in "hello_world.txt" and get
根据给定的说明在 document_distance.py 中实现函数 get_tf、get_idf 和 get_tfidf。除了运行测试文件外,你还可以通过取消注释
document_distance.py 底部提供的相关行来快速检查你的实现:
if __name__ == "__main__":
# Tests Problem 5: Find TF-IDF
tf_text_file = 'tests/student_tests/hello_world.txt'
idf_text_files = ['tests/student_tests/hello_world.txt', 'tests/student_tests/hello_friends.txt']
tf = get_tf(tf_text_file)
idf = get_idf(idf_text_files)
tf_idf = get_tfidf(tf_text_file, idf_text_files)
print(tf) # should print {'hello': 0.6666666666666666, 'world': 0.3333333333333333}
print(idf) # should print {'hello': 0.0, 'world': 0.3010299956639812, 'friends': 0.3010299956639812}
print(tf_idf) # should print [('hello', 0.0), ('world', 0.10034333188799373)]
完成后,请确保运行测试文件 test_ps3_student.py 以检查你的代码是否符合预设的测试用例。
7)提交程序
7.1)命名文件
将你的所有实现直接保存在原始的源代码文件 document_distance.py 中, 请不要忽略此步骤或使用其他名称保存文件!
7.2)时间和协作信息
在每个文件的开头,在注释中写下你在该部分问题上花费的时间(大致),以及你的合作伙伴的名字。例如:
# Problem Set 3
# Name: Jane Lee
# Collaborators: John Doe
4
7.3)中期提交
所有学生都应该在中期截止日期(最终截止日期前 1 周)之前提交他们的进展。此提交将占问题集成绩的一部分分数,并且不会根据正确性进行评分。目
的是确保你在问题集上稳步推进,而不是在截止日期前的最后几天才开始工作。
7.4)最终提交
确保运行学生测试文件并确保所有测试都通过。
文档相似性 补充阅读材料
这个问题集是信息检索中一个非常相关的问题的极大简化版本。文档相似性的应用范围从检索搜索引擎结果到比较基因和蛋白质,再到改善机器翻译。
计算文档距离的进阶技术包括将文本转换为向量空间,并计算余弦相似度、Jaccard 指数或向量的其他度量。
56-1 Problem Set 3
分数 60
作者 樊志伟
单位 许昌学院
Problem Set 3 - Document Distance
下载 实验三材料.zip 文件,解压后根据其中的 ps3_cn.pdf 文件完成实验。
实验三材料.zip
注意:把你的 document_distance.py 文件中的所有代码全部复制到提交框内后:
把 if __name__ == '__main__': 及其之后的代码 删掉或者注释掉,否则会影响此题的正常判题
输入样例:
Tests Problem 0: Prep Data
hello world, hello
===END===
输出样例:
['hello', 'world', 'hello']
最新发布