我有点怀疑你在刷屏

elasticsearch totalhits过高影响性能问题

byx313 回复了问题 • 2 人关注 • 1 个回复 • 2412 次浏览 • 2020-03-15 23:01 • 来自相关话题

ES7.X压缩索引体积的方法还有哪些

byx313 回复了问题 • 2 人关注 • 1 个回复 • 1233 次浏览 • 2020-03-15 22:59 • 来自相关话题

关于es集群扩充节点的问题

byx313 回复了问题 • 4 人关注 • 3 个回复 • 6041 次浏览 • 2020-03-15 22:47 • 来自相关话题

重启es之后,之前创建的template中的mappings变成空了

byx313 回复了问题 • 2 人关注 • 1 个回复 • 1482 次浏览 • 2020-03-15 22:37 • 来自相关话题

基于ES的aliyun-knn插件,开发的以图搜图搜索引擎

发表了文章 • 1 个评论 • 8721 次浏览 • 2020-03-15 12:47 • 来自相关话题

基于ES的aliyun-knn插件,开发的以图搜图搜索引擎

<br /> 本例是基于Elasticsearch6.7 版本, 安装了aliyun-knn插件;设计的图片向量特征为512维度.<br /> 如果自建ES,是无法使用aliyun-knn插件的,自建建议使用ES7.x版本,并按照fast-elasticsearch-vector-scoring插件(<a href="https://github.com/lior-k/fast-elasticsearch-vector-scoring" rel="nofollow" target="_blank">https://github.com/lior-k/fast ... oring</a>/)<br />

由于我的python水平有限,文中设计到的图片特征提取,使用了yongyuan.name的VGGNet库,再此表示感谢!

一、 ES设计

1.1 索引结构

```json

创建一个图片索引

PUT images_v2
{
"aliases": {
"images": {}
},
"settings": {
"index.codec": "proxima",
"index.vector.algorithm": "hnsw",
"index.number_of_replicas":1,
"index.number_of_shards":3
},
"mappings": {
"_doc": {
"properties": {
"feature": {
"type": "proxima_vector",
"dim": 512
},
"relation_id": {
"type": "keyword"
},
"image_path": {
"type": "keyword"
}
}
}
}
}
```

1.2 DSL语句

<br /> GET images/_search<br /> {<br /> "query": {<br /> "hnsw": {<br /> "feature": {<br /> "vector": [255,....255],<br /> "size": 3,<br /> "ef": 1<br /> }<br /> }<br /> },<br /> "from": 0,<br /> "size": 20, <br /> "sort": [<br /> {<br /> "_score": {<br /> "order": "desc"<br /> }<br /> }<br /> ], <br /> "collapse": {<br /> "field": "relation_id"<br /> },<br /> "_source": {<br /> "includes": [<br /> "relation_id",<br /> "image_path"<br /> ]<br /> }<br /> }<br />


二、图片特征

extract_cnn_vgg16_keras.py
```python

-- coding: utf-8 --

Author: yongyuan.name

import numpy as np
from numpy import linalg as LA
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
class VGGNet:
def init(self):

weights: 'imagenet'

    # pooling: 'max' or 'avg'<br />
    # input_shape: (width, height, 3), width and height should >= 48<br />
    self.input_shape = (224, 224, 3)<br />
    self.weight = 'imagenet'<br />
    self.pooling = 'max'<br />
    self.model = VGG16(weights = self.weight, input_shape = (self.input_shape[0], self.input_shape[1], self.input_shape[2]), pooling = self.pooling, include_top = False)<br />
    self.model.predict(np.zeros((1, 224, 224 , 3)))<br />
'''<br />
Use vgg16 model to extract features<br />
Output normalized feature vector<br />
'''<br />
def extract_feat(self, img_path):<br />
    img = image.load_img(img_path, target_size=(self.input_shape[0], self.input_shape[1]))<br />
    img = image.img_to_array(img)<br />
    img = np.expand_dims(img, axis=0)<br />
    img = preprocess_input(img)<br />
    feat = self.model.predict(img)<br />
    norm_feat = feat[0]/LA.norm(feat[0])<br />
    return norm_feat<br />

<br /> <br /> <br /> python

获取图片特征

from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
file_path = "./demo.jpg"
queryVec = model.extract_feat(file_path)
feature = queryVec.tolist()
```


三、将图片特征写入ES

helper.py
python<br /> import re<br /> import urllib.request<br /> def strip(path):<br /> """<br /> 需要清洗的文件夹名字<br /> 清洗掉Windows系统非法文件夹名字的字符串<br /> :param path:<br /> :return:<br /> """<br /> path = re.sub(r'[?\\*|“<>:/]', '', str(path))<br /> return path<br /> <br /> def getfilename(url):<br /> """<br /> 通过url获取最后的文件名<br /> :param url:<br /> :return:<br /> """<br /> filename = url.split('/')[-1]<br /> filename = strip(filename)<br /> return filename<br /> <br /> def urllib_download(url, filename):<br /> """<br /> 下载<br /> :param url:<br /> :param filename:<br /> :return:<br /> """<br /> return urllib.request.urlretrieve(url, filename)<br />


train.py
```python

coding=utf-8

import mysql.connector
import os
from helper import urllib_download, getfilename
from elasticsearch5 import Elasticsearch, helpers
from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
http_auth = ("elastic", "123455")
es = Elasticsearch("<a href="http://127.0.0.1:9200"" rel="nofollow" target="_blank">http://127.0.0.1:9200", http_auth=http_auth)
mydb = mysql.connector.connect(
host="127.0.0.1", # 数据库主机地址
user="root", # 数据库用户名
passwd="123456", # 数据库密码
database="images"
)
mycursor = mydb.cursor()
imgae_path = "./images/"
def get_data(page=1):
page_size = 20
offset = (page - 1) * page_size
sql = """
SELECT id, relation_id, photo FROM images LIMIT {0},{1}
"""
mycursor.execute(sql.format(offset, page_size))
myresult = mycursor.fetchall()
return myresult

def train_image_feature(myresult):
indexName = "images"
photo_path = "http://域名/{0}"
actions = []
for x in myresult:
id = str(x[0])
relation_id = x[1]

photo = x[2].decode(encoding="utf-8")

photo = x[2]<br />
full_photo = photo_path.format(photo)<br />
filename = imgae_path + getfilename(full_photo)<br />
if not os.path.exists(filename):<br />
    try:<br />
        urllib_download(full_photo, filename)<br />
    except BaseException as e:<br />
        print("gid:{0}的图片{1}未能下载成功".format(gid, full_photo))<br />
        continue<br />
if not os.path.exists(filename):<br />
     continue<br />
try:<br />
    feature = model.extract_feat(filename).tolist()<br />
    action = {<br />
    "_op_type": "index",<br />
    "_index": indexName,<br />
    "_type": "_doc",<br />
    "_id": id,<br />
    "_source": {<br />
                        "relation_id": relation_id,<br />
                        "feature": feature,<br />
                        "image_path": photo<br />
    }<br />
    }<br />
    actions.append(action)<br />
except BaseException as e:<br />
    print("id:{0}的图片{1}未能获取到特征".format(id, full_photo))<br />
    continue<br />
# print(actions)<br />
succeed_num = 0<br />
for ok, response in helpers.streaming_bulk(es, actions):<br />
    if not ok:<br />
        print(ok)<br />
        print(response)<br />
    else:<br />
        succeed_num += 1<br />
        print("本次更新了{0}条数据".format(succeed_num))<br />
        es.indices.refresh(indexName)<br />


page = 1
while True:
print("当前第{0}页".format(page))
myresult = get_data(page=page)
if not myresult:
print("没有获取到数据了,退出")
break
train_image_feature(myresult)
page += 1
```

四、搜索图片

```python
import requests
import json
import os
import time
from elasticsearch5 import Elasticsearch
from extract_cnn_vgg16_keras import VGGNet
model = VGGNet()
http_auth = ("elastic", "123455")
es = Elasticsearch("<a href="http://127.0.0.1:9200"" rel="nofollow" target="_blank">http://127.0.0.1:9200", http_auth=http_auth)

上传图片保存

upload_image_path = "./runtime/"
upload_image = request.files.get("image")
upload_image_type = upload_image.content_type.split('/')[-1]
file_name = str(time.time())[:10] + '.' + upload_image_type
file_path = upload_image_path + file_name
upload_image.save(file_path)

计算图片特征向量

queryVec = model.extract_feat(file_path)
feature = queryVec.tolist()

删除图片

os.remove(file_path)

根据特征向量去ES中搜索

body = {
"query": {
"hnsw": {
"feature": {
"vector": feature,
"size": 5,
"ef": 10
}
}
},

"collapse": {

# "field": "relation_id"<br />
# },<br />
"_source": {"includes": ["relation_id", "image_path"]},<br />
"from": 0,<br />
"size": 40<br />

}
indexName = "images"
res = es.search(indexName, body=body)

返回的结果,最好根据自身情况,将得分低的过滤掉...经过测试, 得分在0.65及其以上的,比较符合要求

```

五、依赖的包

```
mysql_connector_repackaged
elasticsearch
Pillow
tensorflow
requests
pandas
Keras
numpy

elasticsearch对于内存使用的一个小疑惑

hapjin 回复了问题 • 6 人关注 • 2 个回复 • 5332 次浏览 • 2020-03-14 23:12 • 来自相关话题

es开启_all字段,但是结果不是写入所有字段的内容值

回复

xiao 发起了问题 • 2 人关注 • 0 个回复 • 1590 次浏览 • 2020-03-14 20:14 • 来自相关话题

Elastic SIEM - 适用于家庭和企业的安全防护 ( 一)

回复

liuxg 发起了问题 • 2 人关注 • 0 个回复 • 1550 次浏览 • 2020-03-14 16:34 • 来自相关话题

索引管理,一个好的建议(有认识官方的,可以反馈一下)

medcl 回复了问题 • 2 人关注 • 1 个回复 • 1760 次浏览 • 2020-03-14 12:06 • 来自相关话题

es7.1更改index的字段类型

yangruideyang 回复了问题 • 3 人关注 • 3 个回复 • 5074 次浏览 • 2020-03-13 15:33 • 来自相关话题

es索引带科学记数法的double类型报错

回复

soulzhouxu 发起了问题 • 1 人关注 • 0 个回复 • 3410 次浏览 • 2020-03-12 22:47 • 来自相关话题

java 调用.startScroll(),为什么无法排序,求解啊

laoyang360 回复了问题 • 2 人关注 • 1 个回复 • 1465 次浏览 • 2020-03-12 13:02 • 来自相关话题

如何实现将mysql 1对多数据导入到es 的nested 嵌套字段中

zwy_ 回复了问题 • 7 人关注 • 6 个回复 • 6184 次浏览 • 2020-03-11 16:43 • 来自相关话题

一种处理Elasticsearch对象数组类型的方式

trycatchfinal 发表了文章 • 0 个评论 • 6901 次浏览 • 2020-03-10 20:27 • 来自相关话题

目前情况

Elasticsearch中处理对象数组有两种格式array和nested,但这两种都有一定的不足。
以下面的文档为例:
<br /> {<br /> "user": [<br /> {<br /> "first": "John",<br /> "last": "Smith"<br /> },<br /> {<br /> "first": "Alice",<br /> "last": "White"<br /> }<br /> ]<br /> }<br />
如果在mapping中以array存储,那么实际存储为:
<br /> user.first:["John","Alice"]<br /> user.last:["Smith","White"]<br />
如果以must的方式查询user.first:Johnuser.last:White,那么这篇文档也会命中,这不是我们期望的。

如果在mapping中以array存储,Elasticsearch将每个对象视为一个doc,这例子会存储3个doc,会严重影响ES写入和查询的效率。

Flatten格式

我想到的存储方式很简单,就是将对象数组打平保存为一个keyword类型的字符串数组,故起名Flatten格式。
以上面文档为例,数组对象需要转换为下面的格式

<br /> "user.flatten": [<br /> "first:John",<br /> "last:Smith",<br /> "first:John&last:Smith",<br /> "first:Alice",<br /> "last:White",<br /> "first:Alice&last:White"<br /> ]<br /> <br />

这样以must的方式查询user.first:Johnuser.last:White,可以转换为term查询first:John&last:White,并不会命中文档。
同时,这种方式还是保存1个doc,避免了nested的缺点。

对于flatten格式有几点说明

user.flatten数组的大小

如果user对象个数为M,user属性个数为N,那么其数组大小为(2^N-1)*M

对象为空怎么处理

建议以null方式保存,例如:

<br /> {<br /> "first": "John",<br /> "last": null<br /> }<br />
转换后的格式

<br /> [<br /> "first:John",<br /> "last:null",<br /> "first:John&last:null",<br /> ]<br />

保存和查询对于对象属性的处理顺序要保持一致

上述例子都是按first&last顺序存储的,那么以must的方式查询user.first:Johnuser.last:White也要以first:John&last:White方式查询,不能用last:White&first:John

不足

  • 需要自己编码将JSON对象转换为字符串数组
  • 需要自己编码转换查询语句
  • 只支持term查询

Solutions:网站搜索 - Elastic Site Search

回复

liuxg 发起了问题 • 1 人关注 • 0 个回复 • 1126 次浏览 • 2020-03-10 14:10 • 来自相关话题