判断语音识别结果好坏的指标——python实现:
- WER字错率
- SER句错率
杰卡德系数
- TF 相似度
- TF-IDF 相似度
- Word2Vec词向量比较相似性
素材的下载:
下载地址:链接:https://pan.baidu.com/s/1cTjob0fic0wN16krePThxA
提取码:269s
result.txt 是按照train.txt 中内容录得音频,经过语音识别的文字结果。
train.txt 为录音内容文本。
字错率是一种常用的且计算速度比较快的评价方法,根本原理在于统计原句中的文字,和识别录音之后的字的不同之处从而断定语音识别模型好坏。在一般性的识别率的计算上常常使用。“WER(词错误率,Word Error Rate)”。
Substitution——替换
Deletion——删除
Insertion——插入
N——单词数目
其中S+D+I的计算过程也叫:编辑距离计算
编辑距离计算:
编辑距离,英文叫做 Edit Distance,又称 Levenshtein 距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数,如果它们的距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
例如我们有两个字符串:string 和 setting,如果我们想要把 string 转化为 setting,需要这么两步:
第一步,在 s 和 t 之间加入字符 e。
第二步,把 r 替换成 t。
所以它们的编辑距离差就是 2,这就对应着二者要进行转化所要改变(添加、替换、删除)的最小步数。
因为有插入词,所以理论上WER有可能大于100%,但实际中、特别是大样本量的时候,是不可能的,否则就太差了,不可能被商用。(代码如下)
import distance
def edit_distance(s1, s2):
return distance.levenshtein(s1, s2)
计算总WER():代码如下
# 计算总WER
def calculate_WER():
text1_list = [i[11:].strip("\n") for i in read_result("result.txt")]
text2_orgin_list = [i[11:].strip("\n") for i in read_result("train.txt")]
# print(text2_orgin_list)
total_distance = 0
total_length = 0
for i in range(len(text1_list)):
text1 = text1_list[i]
text2_orgin = text2_orgin_list[i]
res, length = text_distance(text1, text2_orgin)
# print(res, length)
total_distance += res
total_length += length
print("总距离:", total_distance)
print("总长度:", total_length)
WER = total_distance / total_length
print("总WER:", WER.__format__('0.2%'))
结果输出:
总距离: 538
总长度: 5213
总WER: 10.32%
SER表述为句子中如果有一个词识别错误,那么这个句子被认为识别错误,句子识别错误的的个数,除以总的句子个数即为SER。
站在纯产品体验角度,很多人会以为识别率应该等于“句子识别正确的个数/总的句子个数”,即“识别(正确)率等于96%”这种,实际工作中,这个应该指向“SER(句错误率,Sentence Error Rate)”,即“句子识别错误的个数/总的句子个数”。不过据说在实际工作中,句错率对结果太苛刻了,一般字错误率的2~3倍,所以可能就不太怎么看了。代码如下
# 计算SER句错率
def calculate_SER():
text1_list = [i[11:].strip("\n") for i in read_result("result.txt")]
text2_orgin_list = [i[11:].strip("\n") for i in read_result("train.txt")]
total_distance = 0
for i in range(len(text1_list)):
text1 = text1_list[i]
text2_orgin = text2_orgin_list[i]
text2_orgin = remove_punctuation(text2_orgin)
# 去除句子中的空格
text1 = text1.replace(' ', '')
# print(text1,"\n",text2_orgin)
if text2_orgin != text1:
total_distance += 1
SER = total_distance / len(text1_list)
print("总句子数量:", len(text1_list))
print("总不相同句子数量:", total_distance)
print("总SER:", SER.__format__('0.2%'))
输出结果:
总句子数量: 100
总不相同句子数量: 59
总SER: 59.00%
杰卡德系数,英文叫做 Jaccard index, 又称为 Jaccard 相似系数,用于比较有限样本集之间的相似性与差异性。Jaccard 系数值越大,样本相似度越高。 实际上它的计算方式非常简单,就是两个样本的交集除以并集得到的数值,当两个样本完全一致时,结果为 1,当两个样本完全不同时,结果为 0。 算法非常简单,就是交集除以并集。(代码如下)
# Jaccard 相似度
def jaccard_similarity(s1, s2):
s1, s2 = set(s1), set(s2)
return len(s1 & s2) / len(s1 | s2)
TF相似度计算就是,计算 TF(词频向量化) 矩阵中两个向量的相似度了,实际上就是求解两个向量夹角的余弦值,就是点乘积除以二者的模长,公式如下
cosθ=a·b/|a|*|b|
代码如下:
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer
# TF 相似度
def tf_similarity(s1, s2):
def add_space(s):
return ' '.join(list(s))
# 将字中间加入空格
s1, s2 = add_space(s1), add_space(s2)
# 转化为TF矩阵
cv = CountVectorizer(tokenizer=lambda s: s.split())
corpus = [s1, s2]
vectors = cv.fit_transform(corpus).toarray()
print(vectors)
return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1]))
另外除了计算 TF 系数我们还可以计算 TFIDF 系数,TFIDF 实际上就是在词频 TF 的基础上再加入 IDF 的信息,IDF 称为逆文档频率。
TF-IDF与余弦相似性的应用(一):自动提取关键词 - 阮一峰的网络日志 (TFIDF讲解)
代码如下:
from sklearn.feature_extraction.text import TfidfVectorizer
# TF-IDF 相似度
def tfidf_similarity(s1, s2):
def add_space(s):
return ' '.join(list(s))
# 将字中间加入空格
s1, s2 = add_space(s1), add_space(s2)
# 转化为TF-IDF矩阵
cv = TfidfVectorizer(tokenizer=lambda s: s.split())
corpus = [s1, s2]
vectors = cv.fit_transform(corpus).toarray()
print(vectors)
return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1]))
将词映射成词向量在通过计算夹角比较相似性,可以通过训练好的Word2Vec 模型,加载模型,将词转化为向量。
这里使用的模型是:使用新闻、百度百科、小说数据来训练的 64 维的 Word2Vec 模型,数据量很大,整体效果还不错,我们可以直接下载下来使用,这里我们使用的是 news_12g_baidubaike_20g_novel_90g_embedding_64.bin 数据,然后实现 Sentence2Vec。
下载地址:链接:https://pan.baidu.com/s/1cTjob0fic0wN16krePThxA
提取码:269s
代码如下:
model_file = 'news_12g_baidubaike_20g_novel_90g_embedding_64.bin'
model = gensim.models.KeyedVectors.load_word2vec_format(model_file, binary=True)
def vector_similarity(s1, s2):
def sentence_vector(s):
words = jieba.lcut(s)
v = np.zeros(64)
for word in words:
v += model[word]
v /= len(words)
return v
v1, v2 = sentence_vector(s1), sentence_vector(s2)
return np.dot(v1, v2) / (norm(v1) * norm(v2))