Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 中文字幕免费在线看,女bbbbxxxx另类亚洲,最近中文字幕免费完整版

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          利用 JS 實(shí)現(xiàn)多種圖片相似度算法



          搜索領(lǐng)域,早已出現(xiàn)了“查找相似圖片/相似商品”的相關(guān)功能,如 Google 搜圖,百度搜圖,淘寶的拍照搜商品等。要實(shí)現(xiàn)類似的計(jì)算圖片相似度的功能,除了使用聽(tīng)起來(lái)高大上的“人工智能”以外,其實(shí)通過(guò) js 和幾種簡(jiǎn)單的算法,也能八九不離十地實(shí)現(xiàn)類似的效果。

          在閱讀本文之前,強(qiáng)烈建議先閱讀完阮一峰于多年所撰寫的《相似圖片搜索的原理》相關(guān)文章,本文所涉及的算法也來(lái)源于其中。

          體驗(yàn)地址:img-compare.netlify.com/



          特征提取算法

          為了便于理解,每種算法都會(huì)經(jīng)過(guò)“特征提取”和“特征比對(duì)”兩個(gè)步驟進(jìn)行。接下來(lái)將著重對(duì)每種算法的“特征提取”步驟進(jìn)行詳細(xì)解讀,而“特征比對(duì)”則單獨(dú)進(jìn)行闡述。

          平均哈希算法

          參考阮大的文章,“平均哈希算法”主要由以下幾步組成:

          第一步,縮小尺寸為8×8,以去除圖片的細(xì)節(jié),只保留結(jié)構(gòu)、明暗等基本信息,摒棄不同尺寸、比例帶來(lái)的圖片差異。

          第二步,簡(jiǎn)化色彩。將縮小后的圖片轉(zhuǎn)為灰度圖像。

          第三步,計(jì)算平均值。計(jì)算所有像素的灰度平均值。

          第四步,比較像素的灰度。將64個(gè)像素的灰度,與平均值進(jìn)行比較。大于或等于平均值,記為1;小于平均值,記為0。

          第五步,計(jì)算哈希值。將上一步的比較結(jié)果,組合在一起,就構(gòu)成了一個(gè)64位的整數(shù),這就是這張圖片的指紋。

          第六步,計(jì)算哈希值的差異,得出相似度(漢明距離或者余弦值)。

          明白了“平均哈希算法”的原理及步驟以后,就可以開(kāi)始編碼工作了。為了讓代碼可讀性更高,本文的所有例子我都將使用 typescript 來(lái)實(shí)現(xiàn)。

          圖片壓縮:

          我們采用 canvas 的 drawImage() 方法實(shí)現(xiàn)圖片壓縮,后使用 getImageData() 方法獲取 ImageData 對(duì)象。

          export function compressImg (imgSrc: string, imgWidth: number = 8): Promise<ImageData> {
            return new Promise((resolve, reject) => {
              if (!imgSrc) {
                reject('imgSrc can not be empty!')
              }
              const canvas = document.createElement('canvas')
              const ctx = canvas.getContext('2d')
              const img = new Image()
              img.crossOrigin = 'Anonymous'
              img.onload = function () {
                canvas.width = imgWidth
                canvas.height = imgWidth
                ctx?.drawImage(img, 0, 0, imgWidth, imgWidth)
                const data = ctx?.getImageData(0, 0, imgWidth, imgWidth) as ImageData
                resolve(data)
              }
              img.src = imgSrc
            })
          }
          復(fù)制代碼

          可能有讀者會(huì)問(wèn),為什么使用 canvas 可以實(shí)現(xiàn)圖片壓縮呢?簡(jiǎn)單來(lái)說(shuō),為了把“大圖片”繪制到“小畫布”上,一些相鄰且顏色相近的像素往往會(huì)被刪減掉,從而有效減少了圖片的信息量,因此能夠?qū)崿F(xiàn)壓縮的效果:


          在上面的 compressImg() 函數(shù)中,我們利用 new Image() 加載圖片,然后設(shè)定一個(gè)預(yù)設(shè)的圖片寬高值讓圖片壓縮到指定的大小,最后獲取到壓縮后的圖片的 ImageData 數(shù)據(jù)——這也意味著我們能獲取到圖片的每一個(gè)像素的信息。

          關(guān)于 ImageData,可以參考 MDN 的文檔介紹。

          圖片灰度化

          為了把彩色的圖片轉(zhuǎn)化成灰度圖,我們首先要明白“灰度圖”的概念。在維基百科里是這么描述灰度圖像的:

          在計(jì)算機(jī)領(lǐng)域中,灰度(Gray scale)數(shù)字圖像是每個(gè)像素只有一個(gè)采樣顏色的圖像。

          大部分情況下,任何的顏色都可以通過(guò)三種顏色通道(R, G, B)的亮度以及一個(gè)色彩空間(A)來(lái)組成,而一個(gè)像素只顯示一種顏色,因此可以得到“像素 => RGBA”的對(duì)應(yīng)關(guān)系。而“每個(gè)像素只有一個(gè)采樣顏色”,則意味著組成這個(gè)像素的三原色通道亮度相等,因此只需要算出 RGB 的平均值即可:

          // 根據(jù) RGBA 數(shù)組生成 ImageData
          export function createImgData (dataDetail: number[]) {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            const imgWidth = Math.sqrt(dataDetail.length / 4)
            const newImageData = ctx?.createImageData(imgWidth, imgWidth) as ImageData
            for (let i = 0; i < dataDetail.length; i += 4) {
              let R = dataDetail[i]
              let G = dataDetail[i + 1]
              let B = dataDetail[i + 2]
              let Alpha = dataDetail[i + 3]
          
              newImageData.data[i] = R
              newImageData.data[i + 1] = G
              newImageData.data[i + 2] = B
              newImageData.data[i + 3] = Alpha
            }
            return newImageData
          }
          
          export function createGrayscale (imgData: ImageData) {
            const newData: number[] = Array(imgData.data.length)
            newData.fill(0)
            imgData.data.forEach((_data, index) => {
              if ((index + 1) % 4 === 0) {
                const R = imgData.data[index - 3]
                const G = imgData.data[index - 2]
                const B = imgData.data[index - 1]
          
                const gray = ~~((R + G + B) / 3)
                newData[index - 3] = gray
                newData[index - 2] = gray
                newData[index - 1] = gray
                newData[index] = 255 // Alpha 值固定為255
              }
            })
            return createImgData(newData)
          }
          復(fù)制代碼

          ImageData.data 是一個(gè) Uint8ClampedArray 數(shù)組,可以理解為“RGBA數(shù)組”,數(shù)組中的每個(gè)數(shù)字取值為0~255,每4個(gè)數(shù)字為一組,表示一個(gè)像素的 RGBA 值。由于ImageData 為只讀對(duì)象,所以要另外寫一個(gè) creaetImageData() 方法,利用 context.createImageData() 來(lái)創(chuàng)建新的 ImageData 對(duì)象。

          拿到灰度圖像以后,就可以進(jìn)行指紋提取的操作了。

          指紋提取

          在“平均哈希算法”中,若灰度圖的某個(gè)像素的灰度值大于平均值,則視為1,否則為0。把這部分信息組合起來(lái)就是圖片的指紋。由于我們已經(jīng)拿到了灰度圖的 ImageData 對(duì)象,要提取指紋也就變得很容易了:

          export function getHashFingerprint (imgData: ImageData) {
            const grayList = imgData.data.reduce((pre: number[], cur, index) => {
              if ((index + 1) % 4 === 0) {
                pre.push(imgData.data[index - 1])
              }
              return pre
            }, [])
            const length = grayList.length
            const grayAverage = grayList.reduce((pre, next) => (pre + next), 0) / length
            return grayList.map(gray => (gray >= grayAverage ? 1 : 0)).join('')
          }
          復(fù)制代碼




          通過(guò)上述一連串的步驟,我們便可以通過(guò)“平均哈希算法”獲取到一張圖片的指紋信息(示例是大小為8×8的灰度圖):


          感知哈希算法

          關(guān)于“感知哈希算法”的詳細(xì)介紹,可以參考這篇文章:《基于感知哈希算法的視覺(jué)目標(biāo)跟蹤》。


          簡(jiǎn)單來(lái)說(shuō),該算法經(jīng)過(guò)離散余弦變換以后,把圖像從像素域轉(zhuǎn)化到了頻率域,而攜帶了有效信息的低頻成分會(huì)集中在 DCT 矩陣的左上角,因此我們可以利用這個(gè)特性提取圖片的特征。

          該算法的步驟如下:

          縮小尺寸:pHash以小圖片開(kāi)始,但圖片大于88,3232是最好的。這樣做的目的是簡(jiǎn)化了DCT的計(jì)算,而不是減小頻率。 簡(jiǎn)化色彩:將圖片轉(zhuǎn)化成灰度圖像,進(jìn)一步簡(jiǎn)化計(jì)算量。 計(jì)算DCT:計(jì)算圖片的DCT變換,得到32*32的DCT系數(shù)矩陣。 縮小DCT:雖然DCT的結(jié)果是3232大小的矩陣,但我們只要保留左上角的88的矩陣,這部分呈現(xiàn)了圖片中的最低頻率。 計(jì)算平均值:如同均值哈希一樣,計(jì)算DCT的均值。 計(jì)算hash值:這是最主要的一步,根據(jù)8*8的DCT矩陣,設(shè)置0或1的64位的hash值,大于等于DCT均值的設(shè)為”1”,小于DCT均值的設(shè)為“0”。組合在一起,就構(gòu)成了一個(gè)64位的整數(shù),這就是這張圖片的指紋。

          回到代碼中,首先添加一個(gè) DCT 方法:

          function memoizeCosines (N: number, cosMap: any) {
            cosMap = cosMap || {}
            cosMap[N] = new Array(N * N)
          
            let PI_N = Math.PI / N
          
            for (let k = 0; k < N; k++) {
              for (let n = 0; n < N; n++) {
                cosMap[N][n + (k * N)] = Math.cos(PI_N * (n + 0.5) * k)
              }
            }
            return cosMap
          }
          
          function dct (signal: number[], scale: number = 2) {
            let L = signal.length
            let cosMap: any = null
          
            if (!cosMap || !cosMap[L]) {
              cosMap = memoizeCosines(L, cosMap)
            }
          
            let coefficients = signal.map(function () { return 0 })
          
            return coefficients.map(function (_, ix) {
              return scale * signal.reduce(function (prev, cur, index) {
                return prev + (cur * cosMap[L][index + (ix * L)])
              }, 0)
            })
          }
          復(fù)制代碼

          然后添加兩個(gè)矩陣處理方法,分別是把經(jīng)過(guò) DCT 方法生成的一維數(shù)組升維成二維數(shù)組(矩陣),以及從矩陣中獲取其“左上角”內(nèi)容。

          // 一維數(shù)組升維
          function createMatrix (arr: number[]) {
            const length = arr.length
            const matrixWidth = Math.sqrt(length)
            const matrix = []
            for (let i = 0; i < matrixWidth; i++) {
              const _temp = arr.slice(i * matrixWidth, i * matrixWidth + matrixWidth)
              matrix.push(_temp)
            }
            return matrix
          }
          
          // 從矩陣中獲取其“左上角”大小為 range × range 的內(nèi)容
          function getMatrixRange (matrix: number[][], range: number = 1) {
            const rangeMatrix = []
            for (let i = 0; i < range; i++) {
              for (let j = 0; j < range; j++) {
                rangeMatrix.push(matrix[i][j])
              }
            }
            return rangeMatrix
          }
          復(fù)制代碼

          復(fù)用之前在“平均哈希算法”中所寫的灰度圖轉(zhuǎn)化函數(shù)createGrayscale(),我們可以獲取“感知哈希算法”的特征值:

          export function getPHashFingerprint (imgData: ImageData) {
            const dctData = dct(imgData.data as any)
            const dctMatrix = createMatrix(dctData)
            const rangeMatrix = getMatrixRange(dctMatrix, dctMatrix.length / 8)
            const rangeAve = rangeMatrix.reduce((pre, cur) => pre + cur, 0) / rangeMatrix.length
            return rangeMatrix.map(val => (val >= rangeAve ? 1 : 0)).join('')
          }
          復(fù)制代碼


          顏色分布法

          首先摘抄一段阮大關(guān)于“顏色分布法“的描述:


          阮大把256種顏色取值簡(jiǎn)化成了4種。基于這個(gè)原理,我們?cè)谶M(jìn)行顏色分布法的算法設(shè)計(jì)時(shí),可以把這個(gè)區(qū)間的劃分設(shè)置為可修改的,唯一的要求就是區(qū)間的數(shù)量必須能夠被256整除。算法如下:

          // 劃分顏色區(qū)間,默認(rèn)區(qū)間數(shù)目為4個(gè)
          // 把256種顏色取值簡(jiǎn)化為4種
          export function simplifyColorData (imgData: ImageData, zoneAmount: number = 4) {
            const colorZoneDataList: number[] = []
            const zoneStep = 256 / zoneAmount
            const zoneBorder = [0] // 區(qū)間邊界
            for (let i = 1; i <= zoneAmount; i++) {
              zoneBorder.push(zoneStep * i - 1)
            }
            imgData.data.forEach((data, index) => {
              if ((index + 1) % 4 !== 0) {
                for (let i = 0; i < zoneBorder.length; i++) {
                  if (data > zoneBorder[i] && data <= zoneBorder[i + 1]) {
                    data = i
                  }
                }
              }
              colorZoneDataList.push(data)
            })
            return colorZoneDataList
          }
          復(fù)制代碼



          把顏色取值進(jìn)行簡(jiǎn)化以后,就可以把它們歸類到不同的分組里面去:

          export function seperateListToColorZone (simplifiedDataList: number[]) {
            const zonedList: string[] = []
            let tempZone: number[] = []
            simplifiedDataList.forEach((data, index) => {
              if ((index + 1) % 4 !== 0) {
                tempZone.push(data)
              } else {
                zonedList.push(JSON.stringify(tempZone))
                tempZone = []
              }
            })
            return zonedList
          }
          復(fù)制代碼



          最后只需要統(tǒng)計(jì)每個(gè)相同的分組的總數(shù)即可:

          export function getFingerprint (zonedList: string[], zoneAmount: number = 16) {
            const colorSeperateMap: {
              [key: string]: number
            } = {}
            for (let i = 0; i < zoneAmount; i++) {
              for (let j = 0; j < zoneAmount; j++) {
                for (let k = 0; k < zoneAmount; k++) {
                  colorSeperateMap[JSON.stringify([i, j, k])] = 0
                }
              }
            }
            zonedList.forEach(zone => {
              colorSeperateMap[zone]++
            })
            return Object.values(colorSeperateMap)
          }
          復(fù)制代碼



          內(nèi)容特征法

          ”內(nèi)容特征法“是指把圖片轉(zhuǎn)化為灰度圖后再轉(zhuǎn)化為”二值圖“,然后根據(jù)像素的取值(黑或白)形成指紋后進(jìn)行比對(duì)的方法。這種算法的核心是找到一個(gè)“閾值”去生成二值圖。


          對(duì)于生成灰度圖,有別于在“平均哈希算法”中提到的取 RGB 均值的辦法,在這里我們使用加權(quán)的方式去實(shí)現(xiàn)。為什么要這么做呢?這里涉及到顏色學(xué)的一些概念。

          具體可以參考這篇《Grayscale to RGB Conversion》,下面簡(jiǎn)單梳理一下。

          采用 RGB 均值的灰度圖是最簡(jiǎn)單的一種辦法,但是它忽略了紅、綠、藍(lán)三種顏色的波長(zhǎng)以及對(duì)整體圖像的影響。以下面圖為示例,如果直接取得 RGB 的均值作為灰度,那么處理后的灰度圖整體來(lái)說(shuō)會(huì)偏暗,對(duì)后續(xù)生成二值圖會(huì)產(chǎn)生較大的干擾。



          那么怎么改善這種情況呢?答案就是為 RGB 三種顏色添加不同的權(quán)重。鑒于紅光有著更長(zhǎng)的波長(zhǎng),而綠光波長(zhǎng)更短且對(duì)視覺(jué)的刺激相對(duì)更小,所以我們要有意地減小紅光的權(quán)重而提升綠光的權(quán)重。經(jīng)過(guò)統(tǒng)計(jì),比較好的權(quán)重配比是 R:G:B = 0.299:0.587:0.114。



          于是我們可以得到灰度處理函數(shù):

          enum GrayscaleWeight {
            R = .299,
            G = .587,
            B = .114
          }
          
          function toGray (imgData: ImageData) {
            const grayData = []
            const data = imgData.data
          
            for (let i = 0; i < data.length; i += 4) {
              const gray = ~~(data[i] * GrayscaleWeight.R + data[i + 1] * GrayscaleWeight.G + data[i + 2] * GrayscaleWeight.B)
              data[i] = data[i + 1] = data[i + 2] = gray
              grayData.push(gray)
            }
          
            return grayData
          }
          復(fù)制代碼

          上述函數(shù)返回一個(gè) grayData 數(shù)組,里面每個(gè)元素代表一個(gè)像素的灰度值(因?yàn)?RBG 取值相同,所以只需要一個(gè)值即可)。接下來(lái)則使用“大津法”(Otsu's method)去計(jì)算二值圖的閾值。關(guān)于“大津法”,阮大的文章已經(jīng)說(shuō)得很詳細(xì),在這里就不展開(kāi)了。我在這個(gè)地方找到了“大津法”的 Java 實(shí)現(xiàn),后來(lái)稍作修改,把它改為了 js 版本:

          / OTSU algorithm
          // rewrite from http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html
          export function OTSUAlgorithm (imgData: ImageData) {
            const grayData = toGray(imgData)
            let ptr = 0
            let histData = Array(256).fill(0)
            let total = grayData.length
          
            while (ptr < total) {
              let h = 0xFF & grayData[ptr++]
              histData[h]++
            }
          
            let sum = 0
            for (let i = 0; i < 256; i++) {
              sum += i * histData[i]
            }
          
            let wB = 0
            let wF = 0
            let sumB = 0
            let varMax = 0
            let threshold = 0
          
            for (let t = 0; t < 256; t++) {
              wB += histData[t]
              if (wB === 0) continue
              wF = total - wB
              if (wF === 0) break
          
              sumB += t * histData[t]
          
              let mB = sumB / wB
              let mF = (sum - sumB) / wF
          
              let varBetween = wB * wF * (mB - mF) ** 2
          
              if (varBetween > varMax) {
                varMax = varBetween
                threshold = t
              }
            }
          
            return threshold
          }
          復(fù)制代碼

          OTSUAlgorithm() 函數(shù)接收一個(gè) ImageData 對(duì)象,經(jīng)過(guò)上一步的 toGray() 方法獲取到灰度值列表以后,根據(jù)“大津法”算出最佳閾值然后返回。接下來(lái)使用這個(gè)閾值對(duì)原圖進(jìn)行處理,即可獲取二值圖。

          export function binaryzation (imgData: ImageData, threshold: number) {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            const imgWidth = Math.sqrt(imgData.data.length / 4)
            const newImageData = ctx?.createImageData(imgWidth, imgWidth) as ImageData
            for (let i = 0; i < imgData.data.length; i += 4) {
              let R = imgData.data[i]
              let G = imgData.data[i + 1]
              let B = imgData.data[i + 2]
              let Alpha = imgData.data[i + 3]
              let sum = (R + G + B) / 3
          
              newImageData.data[i] = sum > threshold ? 255 : 0
              newImageData.data[i + 1] = sum > threshold ? 255 : 0
              newImageData.data[i + 2] = sum > threshold ? 255 : 0
              newImageData.data[i + 3] = Alpha
            }
            return newImageData
          }
          復(fù)制代碼



          若圖片大小為 N×N,根據(jù)二值圖“非黑即白”的特性,我們便可以得到一個(gè) N×N 的 0-1 矩陣,也就是指紋:



          特征比對(duì)算法

          經(jīng)過(guò)不同的方式取得不同類型的圖片指紋(特征)以后,應(yīng)該怎么去比對(duì)呢?這里將介紹三種比對(duì)算法,然后分析這幾種算法都適用于哪些情況。

          漢明距離

          摘一段維基百科關(guān)于“漢明距離”的描述:

          在信息論中,兩個(gè)等長(zhǎng)字符串之間的漢明距離(英語(yǔ):Hamming distance)是兩個(gè)字符串對(duì)應(yīng)位置的不同字符的個(gè)數(shù)。換句話說(shuō),它就是將一個(gè)字符串變換成另外一個(gè)字符串所需要替換的字符個(gè)數(shù)。

          例如:

          1011101與1001001之間的漢明距離是2。

          2143896與2233796之間的漢明距離是3。

          "toned"與"roses"之間的漢明距離是3。

          明白了含義以后,我們可以寫出計(jì)算漢明距離的方法:

          export function hammingDistance (str1: string, str2: string) {
            let distance = 0
            const str1Arr = str1.split('')
            const str2Arr = str2.split('')
            str1Arr.forEach((letter, index) => {
              if (letter !== str2Arr[index]) {
                distance++
              }
            })
            return distance
          }
          復(fù)制代碼

          使用這個(gè) hammingDistance() 方法,來(lái)驗(yàn)證下維基百科上的例子:


          驗(yàn)證結(jié)果符合預(yù)期。

          知道了漢明距離,也就可以知道兩個(gè)等長(zhǎng)字符串之間的相似度了(漢明距離越小,相似度越大):

          相似度 = (字符串長(zhǎng)度 - 漢明距離) / 字符串長(zhǎng)度
          復(fù)制代碼

          余弦相似度

          從維基百科中我們可以了解到關(guān)于余弦相似度的定義:

          余弦相似性通過(guò)測(cè)量?jī)蓚€(gè)向量的夾角的余弦值來(lái)度量它們之間的相似性。0度角的余弦值是1,而其他任何角度的余弦值都不大于1;并且其最小值是-1。從而兩個(gè)向量之間的角度的余弦值確定兩個(gè)向量是否大致指向相同的方向。兩個(gè)向量有相同的指向時(shí),余弦相似度的值為1;兩個(gè)向量夾角為90°時(shí),余弦相似度的值為0;兩個(gè)向量指向完全相反的方向時(shí),余弦相似度的值為-1。這結(jié)果是與向量的長(zhǎng)度無(wú)關(guān)的,僅僅與向量的指向方向相關(guān)。余弦相似度通常用于正空間,因此給出的值為0到1之間。

          注意這上下界對(duì)任何維度的向量空間中都適用,而且余弦相似性最常用于高維正空間。


          余弦相似度可以計(jì)算出兩個(gè)向量之間的夾角,從而很直觀地表示兩個(gè)向量在方向上是否相似,這對(duì)于計(jì)算兩個(gè) N×N 的 0-1 矩陣的相似度來(lái)說(shuō)非常有用。根據(jù)余弦相似度的公式,我們可以把它的 js 實(shí)現(xiàn)寫出來(lái):

          export function cosineSimilarity (sampleFingerprint: number[], targetFingerprint: number[]) {
            // cosθ = ∑n, i=1(Ai × Bi) / (√∑n, i=1(Ai)^2) × (√∑n, i=1(Bi)^2) = A · B / |A| × |B|
            const length = sampleFingerprint.length
            let innerProduct = 0
            for (let i = 0; i < length; i++) {
              innerProduct += sampleFingerprint[i] * targetFingerprint[i]
            }
            let vecA = 0
            let vecB = 0
            for (let i = 0; i < length; i++) {
              vecA += sampleFingerprint[i] ** 2
              vecB += targetFingerprint[i] ** 2
            }
            const outerProduct = Math.sqrt(vecA) * Math.sqrt(vecB)
            return innerProduct / outerProduct
          }
          
          復(fù)制代碼

          兩種比對(duì)算法的適用場(chǎng)景

          明白了“漢明距離”和“余弦相似度”這兩種特征比對(duì)算法以后,我們就要去看看它們分別適用于哪些特征提取算法的場(chǎng)景。

          首先來(lái)看“顏色分布法”。在“顏色分布法”里面,我們把一張圖的顏色進(jìn)行區(qū)間劃分,通過(guò)統(tǒng)計(jì)不同顏色區(qū)間的數(shù)量來(lái)獲取特征,那么這里的特征值就和“數(shù)量”有關(guān),也就是非 0-1 矩陣。



          顯然,要比較兩個(gè)“顏色分布法”特征的相似度,“漢明距離”是不適用的,只能通過(guò)“余弦相似度”來(lái)進(jìn)行計(jì)算。

          接下來(lái)看“平均哈希算法”和“內(nèi)容特征法”。從結(jié)果來(lái)說(shuō),這兩種特征提取算法都能獲得一個(gè) N×N 的 0-1 矩陣,且矩陣內(nèi)元素的值和“數(shù)量”無(wú)關(guān),只有 0-1 之分。所以它們同時(shí)適用于通過(guò)“漢明距離”和“余弦相似度”來(lái)計(jì)算相似度。



          計(jì)算精度

          明白了如何提取圖片的特征以及如何進(jìn)行比對(duì)以后,最重要的就是要了解它們對(duì)于相似度的計(jì)算精度。

          本文所講的相似度僅僅是通過(guò)客觀的算法來(lái)實(shí)現(xiàn),而判斷兩張圖片“像不像”卻是一個(gè)很主觀的問(wèn)題。于是我寫了一個(gè)簡(jiǎn)單的服務(wù),可以自行把兩張圖按照不同的算法和精度去計(jì)算相似度:

          img-compare.netlify.com/

          經(jīng)過(guò)對(duì)不同素材的多方比對(duì),我得出了下列幾個(gè)非常主觀的結(jié)論。

          • 對(duì)于兩張顏色較為豐富,細(xì)節(jié)較多的圖片來(lái)說(shuō),“顏色分布法”的計(jì)算結(jié)果是最符合直覺(jué)的。
          • 對(duì)于兩張內(nèi)容相近但顏色差異較大的圖片來(lái)說(shuō),“內(nèi)容特征法”和“平均/感知哈希算法”都能得到符合直覺(jué)的結(jié)果。
          • 針對(duì)“顏色分布法“,區(qū)間的劃分?jǐn)?shù)量對(duì)計(jì)算結(jié)果影響較大,選擇合適的區(qū)間很重要。

          總結(jié)一下,三種特征提取算法和兩種特征比對(duì)算法各有優(yōu)劣,在實(shí)際應(yīng)用中應(yīng)該針對(duì)不同的情況靈活選用。

          總結(jié)

          本文是在拜讀阮一峰的兩篇《相似圖片搜索的原理》之后,經(jīng)過(guò)自己的實(shí)踐總結(jié)以后而成。由于對(duì)色彩、數(shù)學(xué)等領(lǐng)域的了解只停留在淺顯的層面,文章難免有謬誤之處,如果有發(fā)現(xiàn)表述得不正確的地方,歡迎留言指出,我會(huì)及時(shí)予以更正。


          原鏈接:https://juejin.im/post/5dedf50d518825121b4364ec

          用 CSS 最困難的部分之一是處理CSS的權(quán)重值,它可以決定到底哪條規(guī)則會(huì)最終被應(yīng)用,尤其是如果你想在 Bootstrap 這樣的框架中覆蓋其已有樣式,更加顯得麻煩。不過(guò)隨著 CSS 層的引入,這一切都發(fā)生了變化。 這個(gè)新功能允許您創(chuàng)建自己的自定義 CSS 層,這是有史以來(lái)第一次確定所有 CSS 代碼權(quán)重的層次結(jié)構(gòu)。 在本文中,我將剖析這對(duì)您意味著什么,它是如何工作的,以及您今天如何開(kāi)始使用它。

          什么是層(Layers)

          創(chuàng)建您自己的自定義圖層是 CSS 的新功能,但圖層從一開(kāi)始就存在于 CSS 中。 CSS 中有 3 個(gè)不同的層來(lái)管理所有樣式的工作方式。

          瀏覽器(也稱為用戶代理)樣式 - user agent style
          用戶樣式 - User Styles
          作者樣式 - Author Styles

          瀏覽器樣式是應(yīng)用于瀏覽器的默認(rèn)樣式。這就是為什么 Chrome 和 Safari 中的按鈕看起來(lái)不同的原因。在瀏覽器層中找到的樣式在瀏覽器之間是不同的,并且給每個(gè)瀏覽器一個(gè)獨(dú)特的外觀。

          下一層是用戶樣式,這并不是您真正需要擔(dān)心的事情。這些通常是用戶可以編寫并注入瀏覽器的自定義樣式,但瀏覽器不再真正支持這些樣式。用戶可能會(huì)更改一些瀏覽器設(shè)置,這些設(shè)置會(huì)向該圖層添加樣式,但在大多數(shù)情況下,可以完全忽略該層。

          最后,我們來(lái)到作者層。這是您最熟悉的層,因?yàn)槟帉懙拿恳欢?CSS 代碼都屬于這一層。

          這些層分開(kāi)的原因是因?yàn)樗梢院苋菀椎馗采w瀏覽器樣式和用戶樣式中定義的代碼,因?yàn)閷佣x了自己的層次結(jié)構(gòu),完全忽略了權(quán)重的影響。

          這 3 個(gè) CSS 層是有序的(瀏覽器樣式、用戶樣式、然后是作者樣式),后面層中的每個(gè)樣式都將覆蓋前一層的任何樣式。這意味著即使瀏覽器樣式定義了一個(gè)超級(jí)特定的選擇器,例如#button.btn.super-specific,并且您的作者樣式定義了一個(gè)超級(jí)通用的選擇器,例如按鈕,您的作者樣式仍然會(huì)覆蓋瀏覽器樣式。

          這實(shí)際上已經(jīng)是您可能一直在使用而沒(méi)有意識(shí)到的東西。

          * {
            box-sizing: border-box;
          }

          上面的選擇器沒(méi)有權(quán)重,因?yàn)?* 符號(hào)對(duì)權(quán)重沒(méi)有貢獻(xiàn)。 這意味著例如使用 p 作為選擇器的 p 標(biāo)簽的瀏覽器樣式在技術(shù)上比 * 選擇器更具體,權(quán)重更高。 但是,這一切并不重要,因?yàn)樽髡邩邮轿挥诒葹g覽器樣式層晚的層中,因此您的代碼將始終覆蓋瀏覽器樣式。

          理解這一點(diǎn)至關(guān)重要,因?yàn)槭褂眠@個(gè)新的圖層 API,您可以在作者圖層中創(chuàng)建自己的圖層,從而更輕松地處理特定性。

          如何創(chuàng)建你自己的層

          下面來(lái)看個(gè)例子:

          很明顯,這是我們正常理解的CSS, ID設(shè)置的顏色權(quán)重更高,所以按鈕顯示為紅色。讓我們使用@layer給它們加上兩個(gè)層,看看是什么效果:

          按鈕變成藍(lán)色。為什么會(huì)這樣?

          我們給兩條CSS分別建立了base和utilities層,很明顯,后面創(chuàng)建的層的樣式覆蓋了前面層的樣式,盡管前面層的樣式有更高的權(quán)重。這就是層的默認(rèn)工作原理。當(dāng)然層的順序是可以指定的,

          @layer utilities, base;

          @layer utilities, base;

          您需要做的就是編寫@layer 關(guān)鍵字,后跟以逗號(hào)分隔的層列表。 這將按從左到右的順序定義所有層,其中列出的第一層到最后一層的權(quán)重是依次增加的。 然后,您可以稍后使用普通的@layer 語(yǔ)法向每個(gè)層添加代碼,而不必?fù)?dān)心定義層的順序,因?yàn)樗鼈兌荚谶@一行中定義。 需要注意的是,這行代碼必須在定義任何層之前出現(xiàn),所以我通常將它作為我的 CSS 文件中的第一行。如上圖,通過(guò)指定層的順序,我們讓base層應(yīng)用在utilities層之后,所以按鈕又顯示為紅色。

          導(dǎo)入層

          上面這兩種方式都是導(dǎo)入bootstrap框架的CSS,并且把他們放在framework層中,這樣你如果想要覆蓋它已有的樣式,只需要新建一個(gè)自己的層,放置在framework層后面就行。像下面這樣。

          匿名層

          匿名層不常用,但它寫在后面可以覆蓋其他層的樣式,像下面可以把按鈕設(shè)為橙色。

          不在層里的樣式

          不在層里的樣式會(huì)有更高的權(quán)重,下面這個(gè)列表會(huì)讓你看得更清楚覆蓋是怎么發(fā)生的

          層還可以重疊設(shè)置,不過(guò)很少用。具體的用法可以查閱相關(guān)文檔。

          瀏覽器支持

          自從IE死了以后,所有主流瀏覽器都已支持這一特性。大家請(qǐng)放心使用。

          型壓縮可減少受訓(xùn)神經(jīng)網(wǎng)絡(luò)的冗余,由于幾乎沒(méi)有 BERT 或者 BERT-Large 模型可直接在 GPU 及智能手機(jī)上應(yīng)用,因此模型壓縮方法對(duì)于 BERT 的未來(lái)的應(yīng)用前景而言,非常有價(jià)值。

          軟件工程師 Mitchell A. Gordon 在本文中總結(jié)了所有的 BERT 壓縮模型的方法,并對(duì)該領(lǐng)域的論文進(jìn)行羅列及分類,我們下面來(lái)看:

          一、壓縮方法

          1、剪枝——即訓(xùn)練后從網(wǎng)絡(luò)中去掉不必要的部分。

          這包括權(quán)重大小剪枝、注意力頭剪枝、網(wǎng)絡(luò)層以及其他部分的剪枝等。還有一些方法也通過(guò)在訓(xùn)練期間采用正則化的方式來(lái)提升剪枝能力(layer dropout)。

          2、權(quán)重因子分解——通過(guò)將參數(shù)矩陣分解成兩個(gè)較小矩陣的乘積來(lái)逼近原始參數(shù)矩陣。

          這給矩陣施加了低秩約束。權(quán)重因子分解既可以應(yīng)用于輸入嵌入層(這節(jié)省了大量磁盤內(nèi)存),也可以應(yīng)用于前饋/自注意力層的參數(shù)(為了提高速度)。

          3、知識(shí)蒸餾——又名「Student Teacher」。

          在預(yù)訓(xùn)練/下游數(shù)據(jù)上從頭開(kāi)始訓(xùn)練一個(gè)小得多的 Transformer,正常情況下,這可能會(huì)失敗,但是由于未知的原因,利用完整大小的模型中的軟標(biāo)簽可以改進(jìn)優(yōu)化。

          一些方法還將BERT 蒸餾成如LSTMS 等其他各種推理速度更快的架構(gòu)。另外還有一些其他方法不僅在輸出上,還在權(quán)重矩陣和隱藏的激活層上對(duì) Teacher 知識(shí)進(jìn)行更深入的挖掘。

          4、權(quán)重共享——模型中的一些權(quán)重與模型中的其他參數(shù)共享相同的值。

          例如,ALBERT 對(duì) BERT 中的每個(gè)自注意力層使用相同的權(quán)重矩陣。

          5、量化——截?cái)喔↑c(diǎn)數(shù),使其僅使用幾個(gè)比特(這會(huì)導(dǎo)致舍入誤差)。

          模型可以在訓(xùn)練期間,也可以在訓(xùn)練之后學(xué)習(xí)量化值。

          6、預(yù)訓(xùn)練和下游任務(wù)—一些方法僅僅在涉及到特定的下游任務(wù)時(shí)才壓縮 BERT,也有一些方法以任務(wù)無(wú)關(guān)的方式來(lái)壓縮 BERT。

          二、論文一覽

          (原英文標(biāo)題見(jiàn)文章尾部)

          三、結(jié)果比較

          在這里將盡我所能的對(duì)這些論文的觀點(diǎn)進(jìn)行解讀,同時(shí)主要關(guān)注以下指標(biāo):參數(shù)縮減,推理加速 1和準(zhǔn)確性 2,3

          若需要選一個(gè)贏家,我認(rèn)為是 ALBERT,DistilBERT,MobileBERT,Q-BERT,LayerDrop和RPP。你也可以將其中一些方法疊加使用 4,但是有些剪枝相關(guān)的論文,它們的科學(xué)性要高于實(shí)用性,所以我們不妨也來(lái)驗(yàn)證一番:

          四、相關(guān)論文和博文推薦

          • 《稀疏 Transformer:通過(guò)顯式選擇集中注意力》(Sparse Transformer: Concentrated Attention Through Explicit Selection),論文鏈接:https://openreview.net/forum?id=Hye87grYDH)

          • 《使用四元數(shù)網(wǎng)絡(luò)進(jìn)行輕量級(jí)和高效的神經(jīng)自然語(yǔ)言處理》(Lightweight and Efficient Neural Natural Language Processing with Quaternion Networks,論文鏈接:http://arxiv.org/abs/1906.04393)

          • 《自適應(yīng)稀疏 Transformer》(Adaptively Sparse Transformers,論文鏈接:https://www.semanticscholar.org/paper/f6390beca54411b06f3bde424fb983a451789733)

          • 《壓縮 BERT 以獲得更快的預(yù)測(cè)結(jié)果》(Compressing BERT for Faster Prediction,博文鏈接:https://blog.rasa.com/compressing-bert-for-faster-prediction-2/amp/)

          最后的話:

          1、請(qǐng)注意,并非所有壓縮方法都能使模型更快。眾所周知,非結(jié)構(gòu)化剪枝很難通過(guò) GPU 并行來(lái)加速。其中一篇論文認(rèn)為,在 Transformers 中,計(jì)算時(shí)間主要由 Softmax 計(jì)算決定,而不是矩陣乘法。

          2、如果我們能拿出一個(gè)數(shù)字來(lái)記錄我們真正關(guān)心的事情,那將會(huì)很棒,就像 F1。

          3、其中一些百分比是根據(jù) BERT-Large 而不是 BERT-Base 衡量的,僅供參考。

          4、不同的壓縮方法如何交互,是一個(gè)開(kāi)放的研究問(wèn)題。

          相關(guān)論文列表:

          [1] Compressing BERT: Studying the Effects of Weight Pruning on Transfer Learning

          [2] Are Sixteen Heads Really Better than One?

          [3] Pruning a BERT-based Question Answering Model

          [4] Reducing Transformer Depth on Demand with Structured Dropout

          [5] Reweighted Proximal Pruning for Large-Scale Language Representation

          [6] Structured Pruning of Large Language Models

          [7] ALBERT: A Lite BERT for Self-supervised Learning of Language Representations

          [8] Extreme Language Model Compression with Optimal Subwords and Shared Projections

          [9] DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter

          [10] Distilling Task-Specific Knowledge from BERT into Simple Neural Networks

          [11] Distilling Transformers into Simple Neural Networks with Unlabeled Transfer Data

          [12] Attentive Student Meets Multi-Task Teacher: Improved Knowledge Distillation for Pretrained Models

          [13] Patient Knowledge Distillation for BERT Model Compression

          [14] TinyBERT: Distilling BERT for Natural Language Understanding

          [15] MobileBERT: Task-Agnostic Compression of BERT by Progressive Knowledge Transfer

          [16] Q8BERT: Quantized 8Bit BERT

          [17] Q-BERT: Hessian Based Ultra Low Precision Quantization of BERT 雷鋒網(wǎng)雷鋒網(wǎng)雷鋒網(wǎng)

          Via http://mitchgordon.me/machine/learning/2019/11/18/all-the-ways-to-compress-BERT.html


          主站蜘蛛池模板: 成人毛片一区二区| 亚洲一区二区电影| 毛片无码一区二区三区a片视频| 国产在线视频一区二区三区| 亚洲电影一区二区| 91国在线啪精品一区| 国产aⅴ精品一区二区三区久久| 日韩人妻无码一区二区三区久久 | 在线日产精品一区| 中文乱码精品一区二区三区| 日韩美一区二区三区| 无码精品一区二区三区| 亚洲天堂一区二区三区四区| 久久久久久免费一区二区三区| 亚洲日本中文字幕一区二区三区| 精品日韩一区二区三区视频| 色窝窝免费一区二区三区 | 男人的天堂av亚洲一区2区| 国产高清视频一区三区| 久久精品国产一区| 免费精品一区二区三区第35| 中文字幕精品亚洲无线码一区| 日韩有码一区二区| 国产激情一区二区三区 | 夜夜爽一区二区三区精品| 无码人妻精品一区二区蜜桃百度| 国产一区二区精品久久岳√| 国产主播一区二区三区在线观看| 国内精品视频一区二区三区| 国产大秀视频一区二区三区| 一区二区三区电影网| 国产成人精品一区在线| 任你躁国产自任一区二区三区| 一本AV高清一区二区三区| 国精产品一区一区三区| 无码人妻AⅤ一区二区三区 | 久久久人妻精品无码一区| 久久久久国产一区二区三区| 无码人妻视频一区二区三区| 精品一区二区三区在线成人| 亚洲高清毛片一区二区|