
在YouScan ,我们每天处理大约1亿条消息,其中应用了许多规则和各种智能功能。 对于它们的正确工作,有必要正确地确定语言,因为并非所有功能都可以与该语言无关。 在本文中,我们将简要讨论我们对该问题的研究,并在社交网络数据集上显示质量评估。 网络。
文章大纲
- 语言定义问题
- 经济实惠的公共解决方案
- 质量评估
- 结论
1.语言定义问题
语言的定义是一个相当老的问题,许多人试图在其产品的多语种框架内解决它。 较旧的方法使用基于n-gram的解决方案,当考虑某个n-gram的出现次数,并以此为基础,计算每种语言的“速度”,然后根据我们的模型选择最可能的语言。 这些模型的主要缺点是绝对不考虑上下文,因此很难为类似的语言组定义语言。 但是由于模型的简单性,我们最终获得了很高的确定速度,从而节省了高负载系统的资源。 另一个选择,一种更现代的选择,是递归神经网络的解决方案。 该解决方案不仅已经基于n-gram,而且还考虑了上下文,这应该提高工作质量。
创建自己的解决方案的复杂性在于收集培训数据和学习过程本身。 最明显的解决方案是在Wikipedia文章上训练模型,因为我们可以肯定该语言,并且有相当高质量的相对容易编译的经过验证的文本。 为了训练模型,您需要花费大量时间来组装数据集,对其进行处理,然后选择最佳架构。 很可能有人已经在我们之前这样做了。 在下一个方框中,我们将研究现有的解决方案。
2.可用的公共解决方案
紧凑型语言检测器2
CLD2是基于机器学习的概率模型(朴素贝叶斯分类器),可以为UTF-8或html / xml格式的文本定义83种不同的语言。 对于混合语言,该模型返回前3种语言,其中概率的计算方式是占文本总数的大约百分比。 如果模型不确定其答案,则返回标签“ unc”。
该模型的准确性和完整性处于相当不错的水平,但是主要优势是速度。 创建者声称在1毫秒内大约30 kb,在我们对Python包装器的测试中,我们在1毫秒内从21 kb接收到26 kb(每秒70,000-85,000条消息,平均大小为0.8 kb,中位数为0.3 kb)。
该解决方案非常易于使用。 首先,您需要安装其python包装器或使用我们的docker 。
要进行预测,只需导入pycld2
库并编写另一行代码:
使用cld2定义语言 import pycld2 as cld2 cld2.detect("Bonjour, Habr!")
检测器响应是一个三元素元组:
- 语言是否已定义;
- 字符数;
- 三种最有可能的语言的元组,全名在前,
第二个是ISO 3166代码的缩写,第三个是属于该语言的字符的百分比,第四个是字节数。
快速文字
FastText是Facebook编写的一个库,用于有效地学习和分类文本。 在该项目的框架内,Facebook Research提供了157种语言的嵌入,这些嵌入显示了各种任务的最新结果,以及确定语言和其他管理任务的模型。
对于语言定义模型,他们使用了Wikipedia,Tatoeba和SETimes的数据,并且作为分类器,他们使用了快速文本解决方案。
Facebook研究的开发人员提供了两种模型:
要在python中使用这些模型,您首先需要为fasttext安装python包装器 。 安装它可能很困难,因此您需要仔细遵循github上的说明或使用我们的docker 。 还需要从上面的链接下载模型。 我们将在本文中使用原始版本。
使用来自Facebook的模型对语言进行分类要复杂一些,为此,我们需要三行代码:
使用FastText模型定义语言 from pyfasttext import FastText model = FastText('../model/lid.176.bin') model.predict_proba(["Bonjour, Habr!"], 3)
FastText'a模型可以预测n种语言的概率,默认情况下n = 1,但是在本示例中,我们推导出了前3种语言的结果。 对于此模型,这已经是对文本进行语言预测的一般概率,而不是像cld2模型那样属于特定语言的字符数。 速度也很高-每秒超过60,000条消息。
3.质量评估
我们将评估从YouScan系统获取的随机时间(约50万参考)的社交网络数据算法的质量,因此,该样本将使用更多的俄语和英语,分别为乌克兰语,西班牙语和葡萄牙语的43%和32%-每种语言中的2%不到1%。 对于正确的目标,我们将通过Google翻译进行标记,因为目前Google非常擅长管理翻译以及文本语言的定义。 当然,它的标记不是理想的,但是在大多数情况下它是可以信任的。
评估语言定义质量的指标是准确性,完整性和f1。 让我们计算它们并显示在表格中:
两种算法质量的比较 with open("../data/lang_data.txt", "r") as f: text_l, cld2_l, ft_l, g_l = [], [], [], [] s = '' for i in f: s += i if ' |end\n' in s: text, cld2, ft, g = s.strip().rsplit(" ||| ", 3) text_l.append(text) cld2_l.append(cld2) ft_l.append(ft) g_l.append(g.replace(" |end", "")) s='' data = pd.DataFrame({"text": text_l, "cld2": cld2_l, "ft": ft_l, "google": g_l}) def lang_summary(lang, col): prec = (data.loc[data[col] == lang, "google"] == data.loc[data[col] == lang, col]).mean() rec = (data.loc[data["google"] == lang, "google"] == data.loc[data["google"] == lang, col]).mean() return round(prec, 3), round(rec, 3), round(2*prec*rec / (prec + rec),3) results = {} for approach in ["cld2", "ft"]: results[approach] = {} for l in data["google"].value_counts().index[:20]: results[approach][l] = lang_summary(l, approach) res = pd.DataFrame.from_dict(results) res["cld2_prec"], res["cld2_rec"], res["cld2_f1"] = res["cld2"].apply(lambda x: [x[0], x[1], x[2]]).str res["ft_prec"], res["ft_rec"], res["ft_f1"] = res["ft"].apply(lambda x: [x[0], x[1], x[2]]).str res.drop(columns=["cld2", "ft"], inplace=True) arrays = [['cld2', 'cld2', 'cld2', 'ft', 'ft', 'ft'], ['precision', 'recall', 'f1_score', 'precision', 'recall', 'f1_score']] tuples = list(zip(*arrays)) res.columns = pd.MultiIndex.from_tuples(tuples, names=["approach", "metrics"])
型号 | | cld2 | | | 英尺 | | | 安斯 | |
---|
指标 | 精确 | 记录 | f1 | 精确 | 记录 | f1 | 精确 | 记录 | f1 |
AR | 0.992 | 0.725 | 0.838 | 0.918 | 0.697 | 0.793 | 0.968 | 0.788 | 0.869 |
z | 0.95 | 0.752 | 0.839 | 0.888 | 0.547 | 0.677 | 0.914 | 0.787 | 0.845 |
g | 0.529 | 0.136 | 0.217 | 0.286 | 0.178 | 0.219 | 0.408 | 0.214 | 0.281 |
恩 | 0.949 | 0.844 | 0.894 | 0.885 | 0.869 | 0.877 | 0.912 | 0.925 | 0.918 |
es | 0.987 | 0.653 | 0.786 | 0.709 | 0.814 | 0.758 | 0.828 | 0.834 | 0.831 |
fr | 0.991 | 0.713 | 0.829 | 0.53 | 0.803 | 0.638 | 0.713 | 0.81 | 0.758 |
编号 | 0.763 | 0.543 | 0.634 | 0.481 | 0.404 | 0.439 | 0.659 | 0.603 | 0.63 |
它 | 0.975 | 0.466 | 0.631 | 0.519 | 0.778 | 0.622 | 0.666 | 0.752 | 0.706 |
JA | 0.994 | 0.899 | 0.944 | 0.602 | 0.842 | 0.702 | 0.847 | 0.905 | 0.875 |
KA | 0.962 | 0.995 | 0.979 | 0.959 | 0.905 | 0.931 | 0.958 | 0.995 | 0.976 |
kk | 0.908 | 0.653 | 0.759 | 0.804 | 0.584 | 0.677 | 0.831 | 0.713 | 0.767 |
KO | 0.984 | 0.886 | 0.933 | 0.94 | 0.704 | 0.805 | 0.966 | 0.91 | 0.937 |
毫秒 | 0.801 | 0.578 | 0.672 | 0.369 | 0.101 | 0.159 | 0.73 | 0.586 | 0.65 |
pt | 0.968 | 0.753 | 0.847 | 0.805 | 0.771 | 0.788 | 0.867 | 0.864 | 0.865 |
RU | 0.987 | 0.809 | 0.889 | 0.936 | 0.933 | 0.935 | 0.953 | 0.948 | 0.95 |
sr | 0.093 | 0.114 | 0.103 | 0.174 | 0.103 | 0.13 | 0.106 | 0.16 | 0.128 |
日 | 0.989 | 0.986 | 0.987 | 0.973 | 0.927 | 0.95 | 0.979 | 0.986 | 0.983 |
TR | 0.961 | 0.639 | 0.768 | 0.607 | 0.73 | 0.663 | 0.769 | 0.764 | 0.767 |
英国 | 0.949 | 0.671 | 0.786 | 0.615 | 0.733 | 0.669 | 0.774 | 0.777 | 0.775 |
uz | 0.666 | 0.512 | 0.579 | 0.77 | 0.169 | 0.278 | 0.655 | 0.541 | 0.592 |
结果清楚地表明,cld2方法在确定语言方面具有非常高的准确性,仅对于不受欢迎的语言而言,它降至90%以下,并且在90%的情况下,其结果比fasttext好。 在两种方法的完整性大致相同的情况下,f1在cld2处更快。
cld2模型的独特之处在于,它仅对具有足够信心的消息提供预测,这说明了准确性。 快速文本模型为大多数消息提供了答案,因此准确性显着降低,但奇怪的是完整性没有显着提高,有一半情况下降低了。 但是,如果您“扭曲”快速文本模型的阈值,则可以提高准确性。
4.结论
通常,两种模型都可以提供良好的结果,并且可以用来解决在不同领域中确定语言的问题。 它们的主要优点是高速,这使得创建所谓的“合奏”并添加必要的预处理以提高质量成为可能。
您可以在我们的存储库中找到用于再现实验和测试上述方法的所有代码。
您还可以在另一篇文章中看到这些解决方案的测试,该文章比较了6种西欧语言的准确性和速度。