Background:由于针对的场景是小学数学,学生做完试题,拍照,上传,OCR做试题的图片识别,再传到NLP这块做快速题目切分。小学数学题内容相对简单,基本都是加减乘除的计算,内容长度相对较短,所以属于短文本相似度比较。
相似度比对流程对输入OCR试题列表,进行正则过滤,去掉哪些明显不是文章题目,例如题目内容或者是试卷头部和尾部的内容;
与原题进行比对,这里涉及相似度比较
筛选可确定的题目
这里主要做的是同试卷的比较,所以文本信息比较短。
最好的办法其实还是做语义分析,通过理解语义做相似度计算;由于我们的OCR识别结果一般,对试题做分词的时候有许多问题,所以没有选择分词的算法。
选取的相似度比对算法
def cosine_similarity(s1='', s2=''):
return float(np.dot(s1, s2) / (np.linalg.norm(s1) * np.linalg.norm(s2)))
def convert_to_vector(self, s1, s2):
l = len(s1)
s2 = s2[: l + 2] if l < 10 else s2[: l + 4]
word = list(set([o for o in s1] + [o for o in s2]))
total = len(word)
word_vector1 = np.zeros(total)
word_vector2 = np.zeros(total)
for i in range(total):
for o in s1:
if o == word[i]: word_vector1[i] += 1
for o in s2:
if o == word[i]: word_vector2[i] += 1
return word_vector1, word_vector2
input1 = '列竖式计算。(12分)'
input2 = '列竖式计算。(12分)260+480=570-190=560+370=900-580=840-460=160+680='
s1, s2 = convert_to_vector(input1, input2)
res = cosine_similarity(s1, s2)
print(f'cosine_similarity:{res}')
# 结果
cosine_similarity: 0.8775269089525567
Jaccard相似指数用来度量两个集合之间的相似性,它被定义为两个集合交集的元素个数除以并集的元素个数。
# jaccard 相似度
def jaccard(s1='', s2='', mode='strict'):
if mode == 'strict':
s1 = re.sub(r'^\d+\.', '', s1)
s2 = re.sub(r'^\d+\.', '', s2)
l1 = len(s1)
s2 = s2[: l1 + 1]
l2 = len(s2)
else:
l1 = len(s1)
s2 = s2[: l1 + 2] if l1 < 10 else s2[: l1 + 4]
l2 = len(s2)
temp = 0
for o in s1:
if o in s2:
temp += 1
total = l1 + l2 - temp
return float(temp / total)
input1 = '列竖式计算。(12分)'
input2 = '列竖式计算。(12分)260+480=570-190=560+370=900-580=840-460=160+680='
res = jaccard(input1, input2)
print(f'jaccard_similarity: {res}')
#结果
jaccard_similarity: 0.9166666666666666
Jaro–Winkler distance最后得分越高说明相似度越大。Jaro–Winkler distance 是适合于串比如名字这样较短的字符之间计算相似度。0分表示没有任何相似度,1分则代表完全匹配。
import jaro
jaro.jaro_winkler_metric(input1, input1)
#结果
0.8372881355932204
由于jaro相似度对句首的匹配程度要求比较高,这里我么你可以三个算法一起用,求平均值
0.8771605704041479 作为最终的相似度结果。