如何轻松创建AI种族主义者

警示课。

让我们做一个音调分类器!

情感分析(sentiment analysis)是自然语言处理(NLP)中非常常见的任务,这并不奇怪。 对企业而言,重要的是要了解人们在说什么:正面或负面。 这样的分析用于监视社交网络,客户反馈,甚至用于算法股票交易(因此,机器人在上一部电影中对安妮·海瑟薇的角色发表正面评价后购买了伯克希尔·哈撒韦的股票 )。

分析方法有时过于简化,但这是获得可测量结果的最简单方法之一。 只需提交文本-输出为正和负。 无需处理解析树,构建图或其他复杂的表示形式。

这就是我们要做的。 我们将遵循阻力最小的路径,并建立最简单的分类器,对于参与NLP领域相关开发的所有人来说,这看起来似乎非常熟悉。 例如,可以在文章《 深度平均网络》 (Iyyer等人,2015)中找到这种模型。 我们绝不试图挑战他们的结果或批评模型。 我们只是简单地给出一种众所周知的单词向量表示方法。

工作计划:

  • 介绍一种典型的单词向量表示法,用于意义(意义)。
  • 介绍带有正负词标准列表的训练和测试数据集
  • 训练梯度下降分类器以基于其向量表示来识别其他正负词。
  • 使用此分类器可以计算文本句子的语气等级
  • 看看我们创造的怪物

然后我们将看到,“如何在无需特殊努力的情况下创建AI种族主义者”。 当然,您不能以如此怪异的形式离开系统,因此我们将要:

  • 对问题进行统计评估 ,以便在解决问题时衡量进度。
  • 改进数据以获得更准确和更少种族主义的语义模型。

软件依赖


本教程使用Python编写,并且依赖于典型的Python机器学习堆栈:用于数值计算的numpyscipy ,用于数据管理的pandas和用于机器学习的scikit-learn 。 最后,我们还将seaborn matplotlibseaborn构建图。

原则上,可以用TensorFlow或Keras或类似的东西代替scikit-learn :它们还能够在梯度下降时训练分类器。 但是我们不需要它们的抽象,因为这里的培训是在一个阶段进行的。

 import numpy as np import pandas as pd import matplotlib import seaborn import re import statsmodels.formula.api from sklearn.linear_model import SGDClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score #     %matplotlib inline seaborn.set_context('notebook', rc={'figure.figsize': (10, 6)}, font_scale=1.5) 

步骤1.单词的矢量表示


输入文本时通常使用矢量表示。 单词成为多维空间中的向量,其中相邻的向量表示相似的含义。 使用向量表示法,您可以(大致)通过单词的含义来比较单词,而不仅仅是完全匹配。

成功的学习需要数百GB的文本。 幸运的是,各种研究团队已经完成了这项工作,并提供了可下载的预先训练的矢量表示模型。

英文的两个最著名的数据集是word2vec (在Google新闻文本上受过训练)和GloVe (在Common Crawl网页上)。 它们中的任何一个都会给出类似的结果,但是我们将采用GloVe模型,因为它具有更透明的数据源。

GloVe分为三种规模:60亿,420亿和8400亿,最新的模型功能最强大,但需要大量的处理资源。 420亿个版本相当不错,字典整齐地裁剪成100万个单词。 我们正走在阻力最小的道路上,因此采用420亿美元的版本。

-为什么使用“知名”模型如此重要?

“我很高兴您询问这个假想的对话者!” 在每个步骤中,我们都尝试做一些非常典型的事情,并且由于某种原因,还没有确定单词向量表示的最佳模型。 我希望本文能引起人们对使用现代高质量模型的渴望,尤其是那些考虑了算法错误并试图对其进行纠正的模型。 但是,稍后会详细介绍。

从GloVe 网站下载Gloves.42B.300d.zip并解压缩文件data/glove.42B.300d.txt 。 接下来,我们定义一个用于以简单格式读取向量的函数。

 def load_embeddings(filename): """  DataFrame      ,   word2vec, GloVe, fastText  ConceptNet Numberbatch.            . """ labels = [] rows = [] with open(filename, encoding='utf-8') as infile: for i, line in enumerate(infile): items = line.rstrip().split(' ') if len(items) == 2: # This is a header row giving the shape of the matrix continue labels.append(items[0]) values = np.array([float(x) for x in items[1:]], 'f') rows.append(values) arr = np.vstack(rows) return pd.DataFrame(arr, index=labels, dtype='f') embeddings = load_embeddings('data/glove.42B.300d.txt') embeddings.shape 

(1917494, 300)

步骤2.黄金标准音调词典


现在我们需要知道哪些单词被认为是肯定的,哪些单词被认为是负面的信息。 此类词典很多,但是我们将使用一个非常简单的词典(Hu and Liu,2004),该文章在Deep Averaging Networks中使用

刘冰的网站上下载字典,然后将数据提取到data/positive-words.txtdata/negative-words.txt

接下来,我们确定如何读取这些文件并将它们分配为pos_wordsneg_words

 def load_lexicon(filename): """       (https://www.cs.uic.edu/~liub/FBS/sentiment-analysis.html)      Latin-1.      ,    - .    ,    ';'   ,   . """ lexicon = [] with open(filename, encoding='latin-1') as infile: for line in infile: line = line.rstrip() if line and not line.startswith(';'): lexicon.append(line) return lexicon pos_words = load_lexicon('data/positive-words.txt') neg_words = load_lexicon('data/negative-words.txt') 

步骤3.我们训练模型以预测音调


基于正词和负词的向量,我们使用Pandas .loc[]命令搜索所有词的向量表示。

GloVe词典中缺少某些单词。 最常见的是这些拼写错误,例如“幻想”。 在这里,我们看到了一堆NaN ,表示没有向量,并使用.dropna()命令将其删除。

pos_vectors = embeddings.loc[pos_words].dropna()
neg_vectors = embeddings.loc[neg_words].dropna()


现在我们在输入(向量表示)和输出(正数为1,负数为-1)处创建数据数组。 我们还检查矢量是否附加在单词上,以便我们可以解释结果。

vectors = pd.concat([pos_vectors, neg_vectors])
targets = np.array([1 for entry in pos_vectors.index] + [-1 for entry in neg_vectors.index])
labels = list(pos_vectors.index) + list(neg_vectors.index)


-等一下 有些话既不积极也不消极,它们是中立的。 不应该为中性单词创建第三类吗?

“我认为他会派上用场的。” 稍后我们将看到由于将中性词分配了音调而引起的问题。 如果我们能够可靠地识别中性词,那么很有可能将分类器的复杂度提高到三个类别。 但是您需要找到一个中性词字典,因为在Liu的字典中只有正负词。

因此,我尝试了800个单词示例,并增加了预测中性单词的权重。 但是最终结果与您现在看到的并没有很大不同。

-此列表如何区分肯定和否定词? 这不取决于上下文吗?

-好问题。 通用键的分析并不像看起来那样简单。 在某些地方边界是任意的。 在此列表中,“无礼”一词被标记为“坏”,而“雄心勃勃”则被标记为“好”。 “漫画”不好,“滑稽”好。 “退款”是好的,尽管当您欠某人的钱或欠某人的钱时,通常会在不好的背景下提到“退款”。

每个人都知道音调是由上下文决定的,但是在一个简单的模型中,您必须忽略上下文,并希望可以正确猜测平均音调。

使用train_test_split函数, train_test_split可以同时将输入向量,输出值和标签划分为训练数据和测试数据,而剩下的10%用于测试。

 train_vectors, test_vectors, train_targets, test_targets, train_labels, test_labels = \ train_test_split(vectors, targets, labels, test_size=0.1, random_state=0) 

现在创建一个分类器,并将向量传递给迭代器。 我们使用逻辑损失函数,以便最终的分类器可以推断出单词为正或负的概率。

 model = SGDClassifier(loss='log', random_state=0, n_iter=100) model.fit(train_vectors, train_targets) SGDClassifier(alpha=0.0001, average=False, class_weight=None, epsilon=0.1, eta0=0.0, fit_intercept=True, l1_ratio=0.15, learning_rate='optimal', loss='log', n_iter=100, n_jobs=1, penalty='l2', power_t=0.5, random_state=0, shuffle=True, verbose=0, warm_start=False) 

我们在测试向量上评估分类器。 它显示了95%的精度。 还不错

accuracy_score(model.predict(test_vectors), test_targets)
0.95022624434389136


我们为某些单词定义了音调预测功能,然后将其用于测试数据中的一些示例。

 def vecs_to_sentiment(vecs): # predict_log_proba  log-    predictions = model.predict_log_proba(vecs) #        #  log-    . return predictions[:, 1] - predictions[:, 0] def words_to_sentiment(words): vecs = embeddings.loc[words].dropna() log_odds = vecs_to_sentiment(vecs) return pd.DataFrame({'sentiment': log_odds}, index=vecs.index) #  20      words_to_sentiment(test_labels).ix[:20] 

音调
坐立不安-9.931679
打断-9.634706
坚定不移1.466919
虚构的-2.989215
税收0.468522
世界闻名6.908561
便宜的9.237223
失望的-8.737182
极权主义者-10.851580
好战的-8.328674
冻结-8.456981
罪过-7.839670
脆弱的-4.018289
上当-4.309344
未解决-2.816172
巧妙地2.339609
妖魔化-2.102152
无忧无虑8.747150
不受欢迎-7.887475
同情1.790899

可以看到分类器正在工作。 他学会了在训练数据之外用语音概括音调。

步骤4.获得文本的音调分数。


有很多方法可以将向量添加到总体估计中。 同样,我们遵循阻力最小的路径,因此取平均值。

 import re TOKEN_RE = re.compile(r"\w.*?\b") # regex  ,     (\w)   #   (.+?)    (\b).   #       . def text_to_sentiment(text): tokens = [token.casefold() for token in TOKEN_RE.findall(text)] sentiments = words_to_sentiment(tokens) return sentiments['sentiment'].mean() 

有很多要求优化的地方:

  • 引入单词权重及其频率之间的反比关系,以使相同的介词不会严重影响音调。
  • 设置为使短句子不以极端音调值结尾。
  • 会计短语。
  • 撇号不会击倒的更可靠的分词算法。
  • 排除负面因素,例如“不满意”。

但是,所有内容都需要其他代码,并且不会从根本上改变结果。 至少现在您可以大致比较不同的报价:

 text_to_sentiment("this example is pretty cool") 3.889968926086298 

 text_to_sentiment("this example is okay") 2.7997773492425186 

 text_to_sentiment("meh, this example sucks") -1.1774475917460698 

步骤5。看我们创造的怪物


并非每个句子都有明显的语调。 让我们看看中性句子会发生什么:

 text_to_sentiment("Let's go get Italian food") 2.0429166109408983 

 text_to_sentiment("Let's go get Chinese food") 1.4094033658140972 

 text_to_sentiment("Let's go get Mexican food") 0.38801985560121732 

当我考虑到单词的矢量表示来分析餐厅的评论时,我已经遇到过这种现象。 毫无道理,所有墨西哥餐馆的总体得分较低

向量表示捕获上下文中的细微语义差异。 因此,它们反映了我们社会的偏见。

以下是一些其他中立的建议:

 text_to_sentiment("My name is Emily") 2.2286179364745311 

 text_to_sentiment("My name is Heather") 1.3976291151079159 

 text_to_sentiment("My name is Yvette") 0.98463802132985556 

 text_to_sentiment("My name is Shaniqua") -0.47048131775890656 

好该死...

与名字相关的人的系统完全不同。 您可以查看这些示例以及许多其他示例,并发现通常在定型白色名称中音调较高,而在定型黑色名称中音调较低。

Caliscan,Bryson和Narayanan在2017年4月的《 科学 》杂志上发表的研究论文中使用了该测试。 证明语言语料库语义包含社会偏见 。 我们将使用这种方法。

步骤6.评估问题


我们想了解如何避免此类错误。 让我们通过分类器传递更多数据,并统计测量其“偏差”。

在这里,我们有四个名称列表,这些名称反映了不同的种族背景,主要是在美国。 前两个是主要由“白色”和“黑色”命名的列表,根据Kaliskan等人的文章进行了改编。我还从阿拉伯语和乌尔都语中添加了西班牙和穆斯林的名字。

此数据用于验证ConceptNet构建过程中算法的偏差:可以在conceptnet5.vectors.evaluation.bias模块中找到它。 有一个想法可以将字典扩展到其他种族,不仅要考虑名字,还要考虑姓氏。

这里是清单:

 NAMES_BY_ETHNICITY = { #           . 'White': [ 'Adam', 'Chip', 'Harry', 'Josh', 'Roger', 'Alan', 'Frank', 'Ian', 'Justin', 'Ryan', 'Andrew', 'Fred', 'Jack', 'Matthew', 'Stephen', 'Brad', 'Greg', 'Jed', 'Paul', 'Todd', 'Brandon', 'Hank', 'Jonathan', 'Peter', 'Wilbur', 'Amanda', 'Courtney', 'Heather', 'Melanie', 'Sara', 'Amber', 'Crystal', 'Katie', 'Meredith', 'Shannon', 'Betsy', 'Donna', 'Kristin', 'Nancy', 'Stephanie', 'Bobbie-Sue', 'Ellen', 'Lauren', 'Peggy', 'Sue-Ellen', 'Colleen', 'Emily', 'Megan', 'Rachel', 'Wendy' ], 'Black': [ 'Alonzo', 'Jamel', 'Lerone', 'Percell', 'Theo', 'Alphonse', 'Jerome', 'Leroy', 'Rasaan', 'Torrance', 'Darnell', 'Lamar', 'Lionel', 'Rashaun', 'Tyree', 'Deion', 'Lamont', 'Malik', 'Terrence', 'Tyrone', 'Everol', 'Lavon', 'Marcellus', 'Terryl', 'Wardell', 'Aiesha', 'Lashelle', 'Nichelle', 'Shereen', 'Temeka', 'Ebony', 'Latisha', 'Shaniqua', 'Tameisha', 'Teretha', 'Jasmine', 'Latonya', 'Shanise', 'Tanisha', 'Tia', 'Lakisha', 'Latoya', 'Sharise', 'Tashika', 'Yolanda', 'Lashandra', 'Malika', 'Shavonn', 'Tawanda', 'Yvette' ], #         . 'Hispanic': [ 'Juan', 'José', 'Miguel', 'Luís', 'Jorge', 'Santiago', 'Matías', 'Sebastián', 'Mateo', 'Nicolás', 'Alejandro', 'Samuel', 'Diego', 'Daniel', 'Tomás', 'Juana', 'Ana', 'Luisa', 'María', 'Elena', 'Sofía', 'Isabella', 'Valentina', 'Camila', 'Valeria', 'Ximena', 'Luciana', 'Mariana', 'Victoria', 'Martina' ], #       # ,   .     . # #          # -   .    #   ,    . # #       . 'Arab/Muslim': [ 'Mohammed', 'Omar', 'Ahmed', 'Ali', 'Youssef', 'Abdullah', 'Yasin', 'Hamza', 'Ayaan', 'Syed', 'Rishaan', 'Samar', 'Ahmad', 'Zikri', 'Rayyan', 'Mariam', 'Jana', 'Malak', 'Salma', 'Nour', 'Lian', 'Fatima', 'Ayesha', 'Zahra', 'Sana', 'Zara', 'Alya', 'Shaista', 'Zoya', 'Yasmin' ] } 

使用熊猫,我们将编译一个名称表,它们的主要族裔血统和音调等级:

 def name_sentiment_table(): frames = [] for group, name_list in sorted(NAMES_BY_ETHNICITY.items()): lower_names = [name.lower() for name in name_list] sentiments = words_to_sentiment(lower_names) sentiments['group'] = group frames.append(sentiments) #           return pd.concat(frames) name_sentiments = name_sentiment_table() 

样本数据:

name_sentiments.ix[::25]
音调小组
穆罕默德0.834974阿拉伯/穆斯林
艾莉亚3.916803阿拉伯/穆斯林
特里尔-2.858010黑色的
何塞0.432956西班牙裔
露西安娜1.086073西班牙裔
k0.391858
梅根2.158679

我们将绘制每个名称的音调分布图。

 plot = seaborn.swarmplot(x='group', y='sentiment', data=name_sentiments) plot.set_ylim([-10, 10]) 

(-10, 10)



或作为具有置信区间平均为95%的直方图。

 plot = seaborn.barplot(x='group', y='sentiment', data=name_sentiments, capsize=.1) 



最后,运行严重的statsmodels统计信息包。 它将显示算法的偏差有多大(以及许多其他统计数据)。


OLS回归结果
部门 变数:感悟R平方:0.208
型号:最小二乘调整 R平方:0.192
方法:最小二乘F统计:04/13
日期:2017年7月13日,星期四概率(F统计):1.31e-07
时间:11:31:17对数似然:-356.78
不行 观察结果:153AIC:721.6
Df残留物:149BIC:733.7
DF型号:3
协方差类型:不稳健

F统计量是组间差异与组内差异的比率,可以作为偏差的一般评估。

紧随其后的是表明我们将在无假设的情况下看到最大F统计量的可能性:也就是说,在比较的选项之间不存在差异。 可能性非常非常低。 在科学文章中,我们将结果称为“非常统计显著”。

我们需要提高F值。 越低越好。

ols_model.fvalue
13.041597745167659


步骤7.尝试其他数据。


现在,我们有机会以数字方式测量模型的有害偏差。 让我们尝试调整它。 为此,您需要重复一堆过去只是Python记事本中单独步骤的操作。

如果我编写了良好的受支持的代码,则不会使用诸如modelembeddings类的全局变量。 但是,当前的意大利面条代码使您可以更好地检查每个步骤并了解正在发生的事情。 我们重用部分代码,至少定义一个函数来重复一些步骤:

 def retrain_model(new_embs): """      . """ global model, embeddings, name_sentiments embeddings = new_embs pos_vectors = embeddings.loc[pos_words].dropna() neg_vectors = embeddings.loc[neg_words].dropna() vectors = pd.concat([pos_vectors, neg_vectors]) targets = np.array([1 for entry in pos_vectors.index] + [-1 for entry in neg_vectors.index]) labels = list(pos_vectors.index) + list(neg_vectors.index) train_vectors, test_vectors, train_targets, test_targets, train_labels, test_labels = \ train_test_split(vectors, targets, labels, test_size=0.1, random_state=0) model = SGDClassifier(loss='log', random_state=0, n_iter=100) model.fit(train_vectors, train_targets) accuracy = accuracy_score(model.predict(test_vectors), test_targets) print("Accuracy of sentiment: {:.2%}".format(accuracy)) name_sentiments = name_sentiment_table() ols_model = statsmodels.formula.api.ols('sentiment ~ group', data=name_sentiments).fit() print("F-value of bias: {:.3f}".format(ols_model.fvalue)) print("Probability given null hypothesis: {:.3}".format(ols_model.f_pvalue)) #        Y plot = seaborn.swarmplot(x='group', y='sentiment', data=name_sentiments) plot.set_ylim([-10, 10]) 

我们尝试word2vec


可以假设只有GloVe有问题。 Common Crawl数据库中可能有很多可疑站点,并且至少有20本《街头语城市词典》。 也许在不同的基础上会更好:在Google新闻上训练好的旧word2vec呢?

似乎word2vec数据最权威的来源是Google云端硬盘上的此文件 。 下载并保存为data/word2vec-googlenews-300.bin.gz

 #   ConceptNet   word2vec   Pandas     from conceptnet5.vectors.formats import load_word2vec_bin w2v = load_word2vec_bin('data/word2vec-googlenews-300.bin.gz', nrows=2000000) #  word2vec    w2v.index = [label.casefold() for label in w2v.index] #  ,    w2v = w2v.reset_index().drop_duplicates(subset='index', keep='first').set_index('index') retrain_model(w2v) 

Accuracy of sentiment: 94.30%
F-value of bias: 15.573
Probability given null hypothesis: 7.43e-09


因此word2vec的F值大于15时甚至更糟。

原则上,期望新闻能够更好地免受偏见是愚蠢的。

尝试ConceptNet Numberbatch


最后,我可以谈谈我自己的单词向量表示项目。

具有矢量表示功能的ConceptNet是我正在研究的知识图。 它将在训练阶段对向量表示进行归一化,识别并消除算法种族主义和性别歧视的某些来源。 这种校正偏差的方法是基于Bulukbashi等人的文章“去偏置词嵌入”,并且被普遍采用以同时消除多种类型的偏差。 据我所知,这是唯一具有类似内容的语义系统。

我们有时会从ConceptNet导出预先计算的向量-这些版本称为ConceptNet Numberbatch 。 2017年4月,发布了带有偏差校正的第一个版本,因此我们将加载英语向量并重新训练模型。

numberbatch-en-17.04b.txt.gz ,将其保存在data/目录中并重新训练模型:

 retrain_model(load_embeddings('data/numberbatch-en-17.04b.txt')) 

Accuracy of sentiment: 97.46%
F-value of bias: 3.805
Probability given null hypothesis: 0.0118




那么,ConceptNet Numberbatch是否已完全解决问题? 没有其他算法种族主义了吗? 不行

种族主义变得越来越少了吗? 绝对可以

与GloVe或word2vec向量相比,族裔的键范围重叠得多。 与GloVe相比,F的值下降了三倍以上,而与word2vec相比,下降了四倍以上。 通常,在比较不同名称时,我们会看到音调上的差异小得多:应该是这样,因为名称确实不会影响分析结果。

但是仍然存在轻微的相关性。 也许我可以选择似乎可以解决问题的数据和训练参数。 但这将是一个糟糕的选择,因为实际上问题仍然存在,因为在ConceptNet中,我们并未发现并弥补算法种族主义的所有原因。 但这是一个好的开始。

没有陷阱


请注意,切换到ConceptNet Numberbatch可以提高音调预测的准确性。

可能有人建议纠正算法种族主义会以其他方式恶化结果。 但是没有 您可能拥有更好和更少种族主义的数据。 . word2vec GloVe .


, . - .

. , .

, . , — . , . (recall), — .

, - . . , , . , .

Source: https://habr.com/ru/post/zh-CN436506/


All Articles