◆版權聲明:本文出自胖喵~的博客,轉載必須注明出處。
轉載請注明出處:http://www.cnblogs.com/by-dream/p/7091315.html
前言
最近打算把翻譯質量的人工評測好好的做一做。
首先廢話幾句,介紹下我這邊翻譯質量的人工評測怎么做。先找一批句子,然后使用不同的引擎對其進行翻譯,然后將原文和譯文用下面的方式進行呈現,把這些交給專業的人士去進行打分,打完分之后,對結果進行統計,得出評測結果。

看似流程很順利,且結果也有參考價值。然而實際操作的過程中發現如果一個用戶的能力或者態度有問題的話,就會影響一個打分的效果。因此評測人員究竟是否靠譜也成了我們需要考慮的一項因素。
通過向專業人士請教,得知了kappa系數可以進行一致性的校驗且可用來衡量分類精度。因此我決定試試它。
好了先看看kappa系數的概念和計算公式。
kappa系數概念
它是通過把所有地表真實分類中的像元總數(N)乘以混淆矩陣對角線(Xkk)的和,再減去某一類地表真實像元總數與被誤分成該類像元總數之積對所有類別求和的結果,再除以總像元數的平方減去某一類中地表真實像元總數與該類中被誤分成該類像元總數之積對所有類別求和的結果所得到的。
——來自百科
kappa計算結果為-1~1,但通常kappa是落在 0~1 間,可分為五組來表示不同級別的一致性:0.0~0.20極低的一致性(slight)、0.21~0.40一般的一致性(fair)、0.41~0.60 中等的一致性(moderate)、0.61~0.80 高度的一致性(substantial)和0.81~1幾乎完全一致(almost perfect)。
計算公式:

po是每一類正確分類的樣本數量之和除以總樣本數,也就是總體分類精度
假設每一類的真實樣本個數分別為a1,a2,...,aC
而預測出來的每一類的樣本個數分別為b1,b2,...,bC
總樣本個數為n
則有:pe=a1×b1+a2×b2+...+aC×bC / n×n
運算舉例
為了更好的理解上述運算的過程,這里舉例說明一下:
學生考試的作文成績,由兩個老師給出 好、中、差三檔的打分,現在已知兩位老師的打分結果,需要計算兩位老師打分之間的相關性kappa系數:
Po = (10+35+15) / 87 = 0.689
a1 = 10+2+8 = 20; a2 = 5+35+5 = 45; a3 = 5+2+15 = 22;
b1 = 10+5+5 = 20; b2 = 2+35+2 = 39; b3 = 8+5+15 = 28;
Pe = (a1*b1 + a2*b2 + a3*b3) / (87*87) = 0.455
K = (Po-Pe) / (1-Pe) = 0.4293578
這樣我們就得到了kappa系數。
實際應用
像開頭說的一樣,真實的問卷回收回來后,我一般都會對用戶的結果進行kappa系數計算之后才會發放獎勵,因為我的獎勵價格不低,也算是為了公司節省成本吧。
一般一個問卷我會讓5個人去做,當然人越多越准確,但是為了考慮成本且就能得到有效的結果,我這里選了5個人,起初我的想法是用5個人的平均分做為標准答案,然后讓每個人的打分去和平均分算kappa,后來思考后發現這樣有些不太合理,如果有一個人亂答,那么他的結果就會影響平均分,從而影響到整個結果。於是最終換成了一個人和所有人直接計算kappa,然后再求平均。這樣當一個人亂作答的時候,我們在算出兩兩kappa的時候就可以發現這個人,然后在最終計算平均kappa的時候,去掉這個所有人和這個人之間的值即可。
剛開始我用python實現了kappa系數計算的代碼,直接算出了一組結果,然后發現大家相互之前的kappa系數都非常的低,大概在0.1-0.2左右,后來分析是由於5分制導致數據太離散,因此針對翻譯引擎的評測,我將用戶打分的5分制換算成了3分制,1、2分歸為一類,2為一類,4、5為一類。
當然在完成了這些之后,為了再多一輪保險,每一份問卷中的5個人中,有一個我非常信任的專業評測者,因此我還會計算所有人和她直接的kappa,這樣更加的保證每一個打分的結果合理性和相關性都竟在掌握之中。
下面是我實現的python腳本。
#-*- coding:utf-8 -*- # 腳本用途:算出所有人的kappa 和 第一個人和所有人的kappa默認第一個人是 優質用戶 # 腳本使用方法: # get**.py 優秀用戶文件 其他用戶文件。。。。 import sys reload(sys) sys.setdefaultencoding("utf-8") # (用戶1) #avg_file = sys.argv[1] avglist = [] # (用戶2) #user_file = sys.argv[2] userlist = [] # 二維矩陣 matrix = [[0 for col in range(4)] for i in range(4)] # 數據讀取到list中 def initList(file1, file2): #print 'avg file: ' + file1 + '; user file: ' + file2 global avglist global userlist avglist = [] userlist = [] for line in open(file1): line = line.strip() avglist.append(float(line)) for line in open(file2): line = line.strip() userlist.append(float(line)) #print avglist, userlist # 處理平均分為標准分 def avgFormat(): global avglist global userlist for i in range(0, len(avglist)): num = avglist[i] if num < 2.499: avglist[i] = 1 elif num > 2.499 and num < 3.499: avglist[i] = 2 elif num > 3.499 : avglist[i] = 3 else: print 'num is error! ', num for i in range(0, len(userlist)): num = userlist[i] if num < 2.499: userlist[i] = 1 elif num > 2.499 and num < 3.499: userlist[i] = 2 elif num > 3.499 : userlist[i] = 3 else: print 'num is error! ', num #print avglist, userlist # 輸出,調試用 def printlist(listname): for l in listname: print l # 計算矩陣 def getMatrix(): num1 = len(avglist) num2 = len(userlist) if num1 != num2: print 'two list num is not same!' return global matrix matrix = [[0 for col in range(4)] for i in range(4)] for i in range(0, num1): x = int(avglist[i]) y = int(userlist[i]) matrix[x][y] = matrix[x][y] + 1 # 輸出矩陣 def printmatrix(): #print '\nmatrix is:' for num in range(1, 4): print matrix[num][1], matrix[num][2],matrix[num][3] #print '\n' # 得到kappa值 def getkappa(): # 對角線之和 dsum = 0.0 # 總數 allsum = float(len(avglist)) global matrix # 計算對角線的和 for i in range(1, 4): dsum = dsum + matrix[i][i] #print dsum #print 'diagonal sum is :', dsum p0 = float(dsum)/allsum #print 'p0 is :', p0 , '\n' # 計算a1*b1+a2*b2+...ac*bc的和(a1 等於 matrix[1][x]的和 b1等於matrix[x][1]的和) a = 0 al = [] b = 0 bl = [] for j in range(1, 4): a = 0 b = 0 for k in range(1, 4): a = a + matrix[j][k] b = b + matrix[k][j] al.append(a) bl.append(b) #print 'a list is ', al #print 'b list is ', bl tmpsum = 0 for l in range (0, 3): tmpsum = tmpsum + al[l]*bl[l] #print 'pe fenzi sum is: ', tmpsum pe = float(tmpsum)/float(allsum*allsum) #print 'pe is: ',pe , '\n' kappa = (p0-pe)/(1-pe) #print 'kappa is : ', kappa return kappa def gettwokappa(file_one, file_two): initList(file_one, file_two) avgFormat() #printlist(avglist) getMatrix() #printmatrix() return getkappa() file_list = sys.argv[1:] i = 0 for user_file in file_list: allkappa = 0 for other_file in file_list: if user_file != other_file: #print user_file, other_file,':' kapp = gettwokappa(user_file, other_file) allkappa = allkappa + kapp # 第一個優質用戶 需要輸出一下別人和她的kappa if i == 0: print user_file, other_file, ':', kapp i= i+1 print user_file, ' avg kappa: ', allkappa/(len(file_list)-1)
運行的時候,需要 python getallKappa_ref_3level.py 用戶1的文件 用戶2的文件 .. (默認認為用戶1是優秀的用戶)
這里用戶文件中就是用戶的打分信息:

好,我們運行一下看下真實的運行結果。
下面是其中一次問卷,我計算的 “所有人之間kappa的平均分” 和 “所有人和優秀評測者之間的kappa” 不言而喻,很明顯下圖中標紅的這位用戶的打分就不合格,無論是他和優秀者之間的kappa還是他和別人之間kappa的平均值,都是非常低的。經過我人工篩查,果然這個用戶的打分的確非常的不合理。 這樣就被我們過濾掉了。

有了kappa系數的計算規則后,對於一些類似這樣的打分規則,我們就有了更多的把握以及更了解我們的評測結果是否准確可靠。
算法優化
按照上面的步驟,確實可以濾除一些不好的用戶,但是我又仔細思考了一下整個過程。
首先我的做法是:我在已經有了這些翻譯的的平均得分的情況下,將平均分首先四舍五入,然后和新用戶的打分結果計算kappa,這里我選擇了“10個新的用戶的數據”和“四舍五入后的數據”進行kappa計算:


我們可以看到,人員之間的差距其實比較明顯了,但是有一個致命的問題就是:kappa系數普遍較低,難度沒有一個人做的和正確答案比較的相似嗎?經過仔細分析以及向有經驗的人請教得知:由於5分制導致數據太離散,因此結果會很低,其實應該將4分5分歸為同樣的結果,3分一個結果,1分2分看做為同樣的結果,因此我將代碼修改了將用戶打分的5分制換算成了3分制。具體可以看看代碼,最終計算的結果:


可以看到,kappa系數的分值明顯提高了,但是新的問題來了:改用3分制后,曲線的部分趨勢和5分值不一致了。那么到底哪個更趨近於真實呢?造成的誤差在哪里呢?我自己的想了一下,將平均分(一般都是小數)轉換為整數的時候,勢必會丟掉一些信息,而將五分制轉換為三分制的時候同樣也會面臨這樣的問題,並且丟失的信息更多。那么我們回到最初的那個問題上,如何在不損失以后數據信息的情況下,計算kappa。其實還是剛才那個問題,舉個簡單例子:一道題平均分4.4,一個用戶打了5分,一個打了4分,按照5分制計算的話,4.4會被轉換為4,因此4分會被加到P0的分子里,但是5分的不會,但其實5分的也應該算很高相似度的,因為正是有了很多人打5分,平均值才能在4分之上,那么我們是否可以不轉4.4,而是用用戶打分和平均分之間的差值來決定是否要被加到P0的分子中,那么新的問題就來了,這個值我應該選多少合適,這里我做了一組實驗,將這個值在0.1~0.9的結果都進行了計算:

圖中可以看到差值越小,得到的kappa值越小,差值越大,kappa值就越大,之前的方案都是因為只抓了一個點,而實際上這些數據我們都需要考慮,差值越小,說明和平均分越接近,如果在越接近的情況下,kappa值高,那就說明相關性更高,因此我們需要給差值小的更大的權重。
0.1~0.9 我們都給分配不同的權重,暫時按照:1x+2x+3x+4x+5x+6x+7x+8x+9x = 1 解得 x = 0.0222..
所以最終公式:=F:F*0.199+G:G*0.177+H:H*0.155+I:I*0.133+J:J*0.11+K:K*0.088+L:L*0.066+M:M*0.044+N:N*0.022 (excel公式,從F列開始的),得到:


相關資料:這里

由於0.1的值小於0,所以這里我們將這個值忽略,
0.021689*8x + 0.378608*7x + 0.6713388*6x + 1*5x + 1*4x + 1*3x + 1*2x + 1*1x = 1,得:x=0.045
所以每個差值(0.1的差值我們就不取了)對應的權重值為:8x、7x、6x、5x、4x、3x、2x、1x
即 0.36、0.315、0.27、0.225、0.18、0.135、0.09、0.045
