Skip to content
This repository was archived by the owner on Nov 1, 2021. It is now read-only.

Commit edaccb3

Browse files
Kelton Zhangnan-wang
andauthored
Adapt cross modal search to 2.0 (#631)
* fix: comment out merge_all and discard compound indexer pattern * feat: add merge root executor * feat: adapt most executors and yml to 2.0 * feat: clean index flow with comments * fix: tokenize text and input to CLIP text encoder * fix: fix image reader and normalizer * feat: query flow adapted to 2.0 with comment * fix: change 0:0:0:0 to localhost in Readme * feat: fix the requirements * feat: fix the requirements * feat: persistence * fix: fix the index flow * feat: fix index flow * feat: query flow that supports text input * help to adapt the cross modal search to 2.0 (#657) * fix: fix the query part * feat: fix the bug in the indexing text part * fix: fix the query flow * fix: fix the score for text2image matching * fix: fix the query mode * feat: clean up * fix: fix the workspace * fix: remove hello fashion dependency * chore: clean up * fix: switch to use mime_type for routing * fix: adapt evaluation * feat: remove kv indexer * fix: remove the keyvalue indexer * fix: remove the keyvalue indexer * feat: add tests * fix: revert kvindexer deletion * chore: clean up * chore: clean up * chore: clean up Co-authored-by: Nan Wang <nan.wang@jina.ai>
1 parent 90a311d commit edaccb3

26 files changed

+444
-300
lines changed

__init__.py

Whitespace-only changes.

cross-modal-search/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ python app.py -t query_restful
124124
You should open another terminal window and paste the following command.
125125
126126
```sh
127-
curl --request POST -d '{"top_k": 5, "mode": "search", "data": ["hello world"]}' -H 'Content-Type: application/json' 'http://0.0.0.0:45678/search'
127+
curl --request POST -d '{"top_k": 5, "mode": "search", "data": ["hello world"]}' -H 'Content-Type: application/json' 'http://localhost:45678/search'
128128
```
129129
130130
Once you run this command, you should see a JSON output returned to you. This contains the five most semantically similar images sentences to the text input you provided in the `data` parameter.

cross-modal-search/__init__.py

Whitespace-only changes.

cross-modal-search/app.py

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import sys
66

77
import click
8-
from jina import Flow
9-
from jina.logging import JinaLogger
8+
from jina import Flow, Document
9+
import logging
1010
from jina.logging.profile import TimeContext
1111

1212
from dataset import input_index_data
@@ -15,73 +15,73 @@
1515
cur_dir = os.path.dirname(os.path.abspath(__file__))
1616

1717

18-
def config(model_name):
18+
def config():
1919
os.environ['JINA_PARALLEL'] = os.environ.get('JINA_PARALLEL', '1')
2020
os.environ['JINA_SHARDS'] = os.environ.get('JINA_SHARDS', '1')
2121
os.environ["JINA_WORKSPACE"] = os.environ.get("JINA_WORKSPACE", "workspace")
2222
os.environ['JINA_PORT'] = '45678'
23-
if model_name == 'clip':
24-
os.environ['JINA_IMAGE_ENCODER'] = os.environ.get('JINA_IMAGE_ENCODER', 'docker://jinahub/pod.encoder.clipimageencoder:0.0.2-1.2.0')
25-
os.environ['JINA_TEXT_ENCODER'] = os.environ.get('JINA_TEXT_ENCODER', 'docker://jinahub/pod.encoder.cliptextencoder:0.0.3-1.2.2')
26-
os.environ['JINA_TEXT_ENCODER_INTERNAL'] = 'pods/clip/text-encoder.yml'
27-
elif model_name == 'vse':
28-
os.environ['JINA_IMAGE_ENCODER'] = os.environ.get('JINA_IMAGE_ENCODER', 'docker://jinahub/pod.encoder.vseimageencoder:0.0.5-1.2.0')
29-
os.environ['JINA_TEXT_ENCODER'] = os.environ.get('JINA_TEXT_ENCODER', 'docker://jinahub/pod.encoder.vsetextencoder:0.0.6-1.2.0')
30-
os.environ['JINA_TEXT_ENCODER_INTERNAL'] = 'pods/vse/text-encoder.yml'
31-
32-
33-
def index_restful(num_docs):
34-
f = Flow().load_config('flows/flow-index.yml')
35-
36-
with f:
37-
data_path = os.path.join(os.path.dirname(__file__), os.environ.get('JINA_DATA_FILE', None))
38-
f.logger.info(f'Indexing {data_path}')
39-
url = f'http://0.0.0.0:{f.port_expose}/index'
40-
41-
input_docs = _input_lines(
42-
filepath=data_path,
43-
size=num_docs,
44-
read_mode='r',
45-
)
46-
data_json = {'data': [Document(text=text).dict() for text in input_docs]}
47-
r = requests.post(url, json=data_json)
48-
if r.status_code != 200:
49-
raise Exception(f'api request failed, url: {url}, status: {r.status_code}, content: {r.content}')
23+
24+
25+
def index_restful():
26+
flow = Flow().load_config('flows/flow-index.yml')
27+
flow.use_rest_gateway()
28+
with flow:
29+
flow.block()
30+
31+
32+
def check_index_result(resp):
33+
for doc in resp.data.docs:
34+
_doc = Document(doc)
35+
print(f'{_doc.id[:10]}, buffer: {len(_doc.buffer)}, mime_type: {_doc.mime_type}, modality: {_doc.modality}, embed: {_doc.embedding.shape}, uri: {_doc.uri[:20]}')
36+
37+
38+
def check_query_result(resp):
39+
for doc in resp.data.docs:
40+
_doc = Document(doc)
41+
print(f'{_doc.id[:10]}, buffer: {len(_doc.buffer)}, embed: {_doc.embedding.shape}, uri: {_doc.uri[:20]}, chunks: {len(_doc.chunks)}, matches: {len(_doc.matches)}')
42+
if _doc.matches:
43+
for m in _doc.matches:
44+
print(f'\t+- {m.id[:10]}, score: {m.score.value}, text: {m.text}, modality: {m.modality}, uri: {m.uri[:20]}')
5045

5146

5247
def index(data_set, num_docs, request_size):
53-
f = Flow.load_config('flows/flow-index.yml')
54-
with f:
55-
with TimeContext(f'QPS: indexing {num_docs}', logger=f.logger):
56-
f.index(
48+
flow = Flow.load_config('flows/flow-index.yml')
49+
with flow:
50+
with TimeContext(f'QPS: indexing {num_docs}', logger=flow.logger):
51+
flow.index(
5752
inputs=input_index_data(num_docs, request_size, data_set),
58-
request_size=request_size
53+
request_size=request_size,
54+
on_done=check_index_result
5955
)
6056

6157

62-
def query_restful():
63-
f = Flow().load_config('flows/flow-query.yml')
64-
f.use_rest_gateway()
65-
with f:
66-
f.block()
58+
def query():
59+
flow = Flow().load_config('flows/flow-query.yml')
60+
flow.use_rest_gateway()
61+
with flow:
62+
flow.search(inputs=[
63+
Document(text='a black dog and a spotted dog are fighting', modality='text'),
64+
Document(uri='toy-data/images/1000268201_693b08cb0e.jpg', modality='image')
65+
],
66+
on_done=check_query_result)
6767

6868

69-
def dryrun():
70-
f = Flow().load_config('flows/flow-index.yml')
71-
with f:
72-
pass
69+
def query_restful():
70+
flow = Flow().load_config('flows/flow-query.yml')
71+
flow.use_rest_gateway()
72+
with flow:
73+
flow.block()
7374

7475

7576
@click.command()
76-
@click.option('--task', '-t', type=click.Choice(['index', 'index_restful', 'query_restful', 'dryrun'], case_sensitive=False), default='index')
77+
@click.option('--task', '-t', type=click.Choice(['index', 'index_restful', 'query_restful', 'query']), default='index')
7778
@click.option("--num_docs", "-n", default=MAX_DOCS)
7879
@click.option('--request_size', '-s', default=16)
7980
@click.option('--data_set', '-d', type=click.Choice(['f30k', 'f8k', 'toy-data'], case_sensitive=False), default='toy-data')
80-
@click.option('--model_name', '-m', type=click.Choice(['clip', 'vse'], case_sensitive=False), default='clip')
81-
def main(task, num_docs, request_size, data_set, model_name):
82-
config(model_name)
81+
def main(task, num_docs, request_size, data_set):
82+
config()
8383
workspace = os.environ['JINA_WORKSPACE']
84-
logger = JinaLogger('cross-modal-search')
84+
logger = logging.getLogger('cross-modal-search')
8585
if 'index' in task:
8686
if os.path.exists(workspace):
8787
logger.error(
@@ -100,11 +100,11 @@ def main(task, num_docs, request_size, data_set, model_name):
100100
if task == 'index':
101101
index(data_set, num_docs, request_size)
102102
elif task == 'index_restful':
103-
index_restful(num_docs)
103+
index_restful()
104+
elif task == 'query':
105+
query()
104106
elif task == 'query_restful':
105107
query_restful()
106-
elif task == 'dryrun':
107-
dryrun()
108108

109109

110110
if __name__ == '__main__':

cross-modal-search/evaluate.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,9 @@ def config(model_name):
2121
os.environ['JINA_PORT'] = '45678'
2222
os.environ['JINA_USE_REST_API'] = 'false'
2323
if model_name == 'clip':
24-
os.environ['JINA_IMAGE_ENCODER'] = 'docker://jinahub/pod.encoder.clipimageencoder:0.0.1-1.0.7'
25-
os.environ['JINA_TEXT_ENCODER'] = 'docker://jinahub/pod.encoder.cliptextencoder:0.0.1-1.0.7'
24+
# os.environ['JINA_IMAGE_ENCODER'] = CLIPImageEncoder
25+
# os.environ['JINA_TEXT_ENCODER'] = CLIPTextEncoder
2626
os.environ['JINA_TEXT_ENCODER_INTERNAL'] = 'pods/clip/text-encoder.yml'
27-
elif model_name == 'vse':
28-
os.environ['JINA_IMAGE_ENCODER'] = 'docker://jinahub/pod.encoder.vseimageencoder:0.0.5-1.0.7'
29-
os.environ['JINA_TEXT_ENCODER'] = 'docker://jinahub/pod.encoder.vsetextencoder:0.0.6-1.0.7'
30-
os.environ['JINA_TEXT_ENCODER_INTERNAL'] = 'pods/vse/text-encoder.yml'
3127
else:
3228
msg = f'Unsupported model {model_name}.'
3329
msg += 'Expected `clip` or `vse`.'
@@ -98,13 +94,13 @@ def print_evaluation_score(resp):
9894
def main(index_num_docs, evaluate_num_docs, request_size, data_set, model_name, evaluation_mode):
9995
config(model_name)
10096
if index_num_docs > 0:
101-
with Flow.load_config('flow-index.yml') as f:
97+
with Flow.load_config('flows/flow-index.yml') as f:
10298
f.use_rest_gateway()
10399
f.index(
104100
input_fn=input_index_data(index_num_docs, request_size, data_set),
105101
request_size=request_size
106102
)
107-
with Flow.load_config('flow-query.yml').add(name='evaluator', uses='yaml/evaluate.yml') as flow_eval:
103+
with Flow.load_config('flows/flow-query.yml').add(name='evaluator', uses='pods/evaluate.yml') as flow_eval:
108104
flow_eval.search(
109105
input_fn=evaluation_generator(evaluate_num_docs, request_size, data_set, mode=evaluation_mode),
110106
on_done=print_evaluation_score
Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,43 @@
1-
!Flow
1+
jtype: Flow
22
version: '1'
33
with:
44
prefetch: 10
5+
port_expose: 45678
6+
workspace: $JINA_WORKSPACE
57
pods:
6-
- name: loader
8+
- name: loader # load images from the dataset of image-caption pairs
79
uses: pods/image-load.yml
810
shards: $JINA_PARALLEL
911
read_only: true
10-
- name: normalizer
12+
needs: [gateway]
13+
- name: image_normalizer # normalize the dimension of the images
1114
uses: pods/image-normalize.yml
1215
shards: $JINA_PARALLEL
1316
read_only: true
14-
- name: image_encoder
15-
uses: $JINA_IMAGE_ENCODER
17+
- name: image_encoder # encode images into embeddings with CLIP model
18+
uses: pods/clip/image-encoder.yml
1619
shards: $JINA_PARALLEL
1720
timeout_ready: 600000
1821
read_only: true
19-
- name: image_vector_indexer
22+
- name: image_vector_indexer # store image embeddings
2023
polling: any
2124
uses: pods/index-image-vector.yml
2225
shards: $JINA_SHARDS
23-
- name: image_kv_indexer
26+
- name: image_kv_indexer # store image documents
2427
polling: any
2528
uses: pods/index-image-kv.yml
2629
shards: $JINA_SHARDS
2730
needs: [gateway]
28-
- name: text_encoder
29-
uses: $JINA_TEXT_ENCODER
30-
uses_internal: $JINA_TEXT_ENCODER_INTERNAL
31+
- name: text_encoder # encode text into embeddings with CLIP model
32+
uses: pods/clip/text-encoder.yml
3133
shards: $JINA_PARALLEL
3234
timeout_ready: 600000
3335
read_only: true
3436
needs: [gateway]
35-
- name: text_indexer
37+
- name: text_indexer # index the text into documents
3638
polling: any
37-
uses: pods/index-text.yml
39+
uses: pods/index-text.yml #(numpy + binary pb indexer)
3840
shards: $JINA_SHARDS
39-
- name: join_all
40-
uses: _merge_root
41-
needs: [image_vector_indexer, image_kv_indexer, text_indexer]
42-
read_only: true
41+
needs: text_encoder
42+
- name: join_all # wait on the 3 executors to finish data processing with "needs"
43+
needs: [image_vector_indexer, image_kv_indexer, text_indexer]
Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
1-
!Flow
1+
jtype: Flow
22
version: '1'
33
with:
44
prefetch: 10
55
port_expose: 45678
6+
workspace: $JINA_WORKSPACE
67
pods:
7-
- name: loader
8+
- name: loader # load query image
89
uses: pods/image-load.yml
910
shards: $JINA_PARALLEL
1011
read_only: true
11-
- name: normalizer
12+
needs: gateway
13+
- name: normalizer # normalize query image
1214
uses: pods/image-normalize.yml
1315
shards: $JINA_PARALLEL
1416
read_only: true
15-
- name: image_encoder
17+
needs: loader
18+
- name: image_encoder # encode query image into embeddings with CLIP model
1619
polling: any
17-
uses: $JINA_IMAGE_ENCODER
20+
uses: pods/clip/image-encoder.yml
1821
shards: $JINA_PARALLEL
1922
timeout_ready: 600000
2023
read_only: true
21-
- name: text_indexer
24+
needs: normalizer
25+
- name: text_indexer # index query text
2226
polling: all
2327
uses: pods/index-text.yml
2428
shards: $JINA_SHARDS
25-
uses_after: pods/merge_matches_sort_topk.yml
26-
remove_uses_ba: true
27-
- name: text_encoder
28-
uses: $JINA_TEXT_ENCODER
29-
uses_internal: $JINA_TEXT_ENCODER_INTERNAL
29+
- name: text_encoder # encode query text into embeddings with CLIP model
30+
uses: pods/clip/text-encoder.yml
3031
shards: $JINA_PARALLEL
3132
timeout_ready: 600000
3233
read_only: true
3334
needs: [gateway]
34-
- name: image_vector_indexer
35+
- name: image_vector_indexer # index query image embeddings
3536
polling: all
3637
uses: pods/index-image-vector.yml
3738
shards: $JINA_SHARDS
38-
uses_after: _merge_matches
39-
remove_uses_ba: true
40-
- name: image_kv_indexer
39+
needs: text_encoder
40+
- name: image_kv_indexer # index query image as kv
4141
polling: all
4242
uses: pods/index-image-kv.yml
4343
shards: $JINA_SHARDS
44-
uses_after: pods/merge_matches_sort_topk.yml
45-
remove_uses_ba: true
46-
- name: join_all
47-
uses: _merge_root
44+
needs: image_vector_indexer
45+
- name: join_all # combine text and image queries
4846
needs: [text_indexer, image_kv_indexer]
4947
read_only: true

cross-modal-search/pods/__init__.py

Whitespace-only changes.

cross-modal-search/pods/clip/hub-image-encoder.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

cross-modal-search/pods/clip/hub-text-encoder.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)