未验证 提交 6408cbfe 编写于 作者: J Jeff Wang 提交者: GitHub

Embedding visualization connect (#356)

* Make the frontend to fetch the data from the flask server.

* Provide loading animation.

* Fixed format style
Fix the issue where the display label not working.

* Provide a 2D 3D switcher.
Display the chart in 3D.

* Include PCA method to do dimension reduction

* Fix the style.

* Fix typo
Use === to compare type as well.
上级 5a077745
...@@ -6,31 +6,86 @@ ...@@ -6,31 +6,86 @@
* @param {Object} postParam post params * @param {Object} postParam post params
* @return {Object} * @return {Object}
*/ */
module.exports = function (path, queryParam, postParam) { module.exports = function(path, queryParam, postParam) {
return { if (queryParam.dimension === '3') {
// moock delay return {
_timeout: 0, // moock delay
// mock http status _timeout: 0,
_status: 200, // mock http status
// mock response data _status: 200,
_data: { // mock response data
status: 0, _data: {
msg: 'SUCCESS', status: 0,
data: { msg: 'SUCCESS',
"embedding": [ data: {
[10.0, 8.04, "yellow"], "embedding": [
[8.0, 6.95, "blue"], [10.0, 8.04, 3],
[13.0, 7.58, "red"], [8.0, 6.95, 4],
[9.0, 8.81, "king"], [13.0, 7.58, 1],
[11.0, 8.33, "queen"], [9.0, 8.81, 3],
[14.0, 9.96, "man"], [11.0, 8.33, 5],
[6.0, 7.24, "women"], [14.0, 9.96, 6],
[4.0, 4.26, "kid"], [6.0, 7.24, 1],
[12.0, 10.84, "adult"], [4.0, 4.26, 2],
[7.0, 4.82, "light"], [12.0, 10.84, 6],
[5.0, 5.68, "dark"] [7.0, 4.8, 3],
] [5.0, 5.68, 3]
],
"labels": [
"yellow",
"blue",
"red",
"king",
"queen",
"man",
"women",
"kid",
"adult",
"light",
"dark"
]
}
} }
} };
}; } else {
return {
// moock delay
_timeout: 0,
// mock http status
_status: 200,
// mock response data
_data: {
status: 0,
msg: 'SUCCESS',
data: {
"embedding": [
[10.0, 8.04],
[8.0, 6.95],
[13.0, 7.58],
[9.0, 8.81],
[11.0, 8.33],
[14.0, 9.96],
[6.0, 7.24],
[4.0, 4.26],
[12.0, 10.84],
[7.0, 4.8],
[5.0, 5.68]
],
"labels": [
"yellow",
"blue",
"red",
"king",
"queen",
"man",
"women",
"kid",
"adult",
"light",
"dark"
]
}
}
};
}
}; };
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
"d3-format": "^1.2.1", "d3-format": "^1.2.1",
"dagre": "^0.8.2", "dagre": "^0.8.2",
"dagre-d3": "^0.6.1", "dagre-d3": "^0.6.1",
"echarts": "^3.8.5", "echarts": "^4.0.0",
"echarts-gl": "^1.1.0",
"file-saver": "^1.3.3", "file-saver": "^1.3.3",
"graphlib": "^1.0.5", "graphlib": "^1.0.5",
"htmlcs": "^0.4.1", "htmlcs": "^0.4.1",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
:config="config" :config="config"
:displayWordLabel="config.displayWordLabel" :displayWordLabel="config.displayWordLabel"
:searchText="config.searchText" :searchText="config.searchText"
:dimension="config.dimension"
:embedding_data="embedding_data" :embedding_data="embedding_data"
></ui-chart> ></ui-chart>
</div> </div>
...@@ -35,20 +36,45 @@ export default { ...@@ -35,20 +36,45 @@ export default {
config: { config: {
searchText: '', searchText: '',
displayWordLabel: true, displayWordLabel: true,
dimension: "2",
reduction: "tsne",
running: true running: true
}, },
embedding_data: [] embedding_data: []
} }
}, },
created() { created() {
getHighDimensionalDatasets().then(({errno, data}) => { this.fetchDatasets()
this.embedding_data = data.embedding; },
}); watch: {
'config.dimension': function(val) {
this.fetchDatasets()
},
'config.reduction': function(val) {
this.fetchDatasets()
}
}, },
mounted() { mounted() {
autoAdjustHeight(); autoAdjustHeight();
}, },
methods: { methods: {
fetchDatasets() {
// Fetch the data from the server. Passing dimension and reduction method
let params = {
dimension: this.config.dimension,
reduction: this.config.reduction
};
getHighDimensionalDatasets(params).then(({errno, data}) => {
var vector_data = data.embedding;
var labels = data.labels;
for ( var i = 0; i < vector_data.length; i ++) {
vector_data[i].push(labels[i])
}
this.embedding_data = vector_data
});
},
} }
}; };
......
...@@ -7,9 +7,10 @@ ...@@ -7,9 +7,10 @@
<script> <script>
import echarts from 'echarts'; import echarts from 'echarts';
import 'echarts-gl';
export default { export default {
props: ['config', 'displayWordLabel', 'searchText', 'embedding_data'], props: ['config', 'displayWordLabel', 'searchText', 'embedding_data', 'dimension'],
data() { data() {
return { return {
width: 900, width: 900,
...@@ -18,20 +19,21 @@ export default { ...@@ -18,20 +19,21 @@ export default {
}, },
computed: { computed: {
computedStyle() { computedStyle() {
return 'height:' + this.height + 'px;' return 'height:' + this.height + 'px;' +
+ 'width:' + this.width + 'px;'; 'width:' + this.width + 'px;';
} }
}, },
created() { created() {},
},
mounted() { mounted() {
this.createChart(); this.createChart();
this.setChartsOptions(); this.myChart.showLoading()
this.set2DChartOptions();
this.setDisplayWordLabel(); this.setDisplayWordLabel();
}, },
watch: { watch: {
embedding_data: function(val) { embedding_data: function(val) {
this.myChart.hideLoading();
this.myChart.setOption({ this.myChart.setOption({
series: [{ series: [{
// Grab the 'matched' series data // Grab the 'matched' series data
...@@ -43,13 +45,25 @@ export default { ...@@ -43,13 +45,25 @@ export default {
displayWordLabel: function(val) { displayWordLabel: function(val) {
this.setDisplayWordLabel() this.setDisplayWordLabel()
}, },
dimension: function(val) {
this.myChart.clear()
this.myChart.showLoading()
if (val === "2") {
this.set2DChartOptions();
this.setDisplayWordLabel();
} else {
this.set3DChartOptions();
this.setDisplayWordLabel();
}
},
searchText: function(val) { searchText: function(val) {
// Filter the data that has the hasPrefix
var matched_words = [] var matched_words = []
if (val != '') { if (val != '') {
val = val.toLowerCase() val = val.toLowerCase()
function hasPrefix(value) { function hasPrefix(value) {
var word = value[2] var word = value[value.length - 1]
return (typeof word == "string" && word.toLowerCase().startsWith(val)) return (typeof word == "string" && word.toLowerCase().startsWith(val))
} }
...@@ -72,13 +86,12 @@ export default { ...@@ -72,13 +86,12 @@ export default {
let el = this.$refs.chartBox; let el = this.$refs.chartBox;
this.myChart = echarts.init(el); this.myChart = echarts.init(el);
}, },
setChartsOptions(){ set2DChartOptions() {
var typeD = "normal"; var typeD = "normal";
var option = { var option = {
xAxis: {}, xAxis: {},
yAxis: {}, yAxis: {},
series: [ series: [{
{
name: "all", name: "all",
symbolSize: 10, symbolSize: 10,
data: this.embedding_data, data: this.embedding_data,
...@@ -87,7 +100,7 @@ export default { ...@@ -87,7 +100,7 @@ export default {
{ {
name: "matched", name: "matched",
animation: false, animation: false,
symbolSize:10, symbolSize: 10,
data: [], data: [],
itemStyle: { itemStyle: {
normal: { normal: {
...@@ -95,37 +108,80 @@ export default { ...@@ -95,37 +108,80 @@ export default {
} }
}, },
label: { label: {
normal: {
show: true,
formatter: function(param) {
return param.data[param.data.length - 1];
},
position: 'top'
}
},
type: 'scatter'
}
]
};
this.myChart.setOption(option);
},
set3DChartOptions() {
var symbolSize = 2.5;
var option3d = {
grid3D: {},
xAxis3D: {
type: 'category'
},
yAxis3D: {},
xAxis3D: {},
zAxis3D: {},
dataset: {
source: this.embedding_data
},
series: [
{
name: "all",
type: 'scatter3D',
symbolSize: symbolSize,
data: []
},
{
name: "matched",
animation: false,
symbolSize: symbolSize,
data: [],
label: {
normal: { normal: {
show: true, show: true,
formatter: function (param) { formatter: function(param) {
return param.data[2]; return param.data[param.data.length - 1];
}, },
position: 'top' position: 'top'
} }
}, },
type: 'scatter' type: 'scatter3D'
} }
] ]
}; }
this.myChart.setOption(option); this.myChart.setOption(option3d);
}, },
setDisplayWordLabel() { setDisplayWordLabel() {
this.myChart.setOption({ this.myChart.setOption({
series: [{
// Grab the 'all' series data
name: 'all',
label: { label: {
normal: { normal: {
show: this.displayWordLabel, show: this.displayWordLabel,
formatter: function (param) { formatter: function(param) {
return param.data[2]; return param.data[param.data.length - 1];
},
position: 'top'
}, },
emphasis: { position: 'top'
show: true, },
} emphasis: {
show: true,
} }
}) }
}]
});
}, },
} }
}; };
...@@ -136,7 +192,4 @@ export default { ...@@ -136,7 +192,4 @@ export default {
float left float left
padding 10px padding 10px
position relative position relative
</style> </style>
...@@ -13,6 +13,16 @@ ...@@ -13,6 +13,16 @@
label="Display All Labels" label="Display All Labels"
v-model="config.displayWordLabel" dark></v-checkbox> v-model="config.displayWordLabel" dark></v-checkbox>
<v-radio-group label="Dimension" v-model="config.dimension" dark>
<v-radio label="2D" value="2"></v-radio>
<v-radio label="3D" value="3"></v-radio>
</v-radio-group>
<v-radio-group label="Reduction Method" v-model="config.reduction" dark>
<v-radio label="T-SNE" value="tsne"></v-radio>
<v-radio label="PCA" value="pca"></v-radio>
</v-radio-group>
<v-btn :color="config.running ? 'primary' : 'error'" <v-btn :color="config.running ? 'primary' : 'error'"
v-model="config.running" v-model="config.running"
@click="toggleAllRuns" @click="toggleAllRuns"
...@@ -64,5 +74,3 @@ export default { ...@@ -64,5 +74,3 @@ export default {
margin-bottom 10px margin-bottom 10px
</style> </style>
...@@ -196,6 +196,32 @@ def get_texts(storage, mode, tag, num_records=100): ...@@ -196,6 +196,32 @@ def get_texts(storage, mode, tag, num_records=100):
return res return res
def get_embeddings(storage,
mode,
tag,
reduction,
dimension=2,
num_records=5000):
with storage.mode(mode) as reader:
embedding = reader.embedding(tag)
labels = embedding.get_all_labels()
high_dimensional_vectors = embedding.get_all_embeddings()
# TODO: Move away from sklearn
if reduction == 'tsne':
from sklearn.manifold import TSNE
tsne = TSNE(
perplexity=30, n_components=dimension, init='pca', n_iter=5000)
low_dim_embs = tsne.fit_transform(high_dimensional_vectors)
elif reduction == 'pca':
from sklearn.decomposition import PCA
pca = PCA(n_components=3)
low_dim_embs = pca.fit_transform(high_dimensional_vectors)
return {"embedding": low_dim_embs.tolist(), "labels": labels}
def get_histogram(storage, mode, tag): def get_histogram(storage, mode, tag):
with storage.mode(mode) as reader: with storage.mode(mode) as reader:
histogram = reader.histogram(tag) histogram = reader.histogram(tag)
......
...@@ -236,6 +236,14 @@ def texts(): ...@@ -236,6 +236,14 @@ def texts():
result = gen_result(0, "", data) result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json') return Response(json.dumps(result), mimetype='application/json')
@app.route('/data/plugin/embeddings/embeddings')
def embeddings():
dimension = request.args.get('dimension')
reduction = request.args.get('reduction')
key = os.path.join('/data/plugin/embeddings/embeddings', dimension, reduction)
data = cache_get(key, try_call, lib.get_embeddings, log_reader, 'train', 'scratch/embedding', reduction, int(dimension))
result = gen_result(0, "", data)
return Response(json.dumps(result), mimetype='application/json')
@app.route('/data/plugin/graphs/graph') @app.route('/data/plugin/graphs/graph')
def graph(): def graph():
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册