Skip to content

Commit 9353cd3

Browse files
authored
Merge pull request #160 from marscher/improvements_nb01
[nb01] Better motiviation why to compare different features.
2 parents b18582f + 3ac9cb5 commit 9353cd3

File tree

1 file changed

+144
-52
lines changed

1 file changed

+144
-52
lines changed

notebooks/01-data-io-and-featurization.ipynb

Lines changed: 144 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"\n",
99
"<a rel=\"license\" href=\"http://creativecommons.org/licenses/by/4.0/\"><img alt=\"Creative Commons Licence\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by/4.0/88x31.png\" title='This work is licensed under a Creative Commons Attribution 4.0 International License.' align=\"right\"/></a>\n",
1010
"\n",
11-
"In this notebook, we will cover how to load (and visualize) data with PyEMMA. \n",
11+
"In this notebook, we will cover how to load (and visualize) data with PyEMMA. We are going to extract different features (collective variables) and compare how well these are suited for Markov state model building. Further we will introduce the concept of streaming data, which is mandatory to work with large data sets.\n",
1212
"\n",
1313
"As with the other notebooks, we employ multiple examples. The idea is, first, to highlight the fundamental ideas with a non-physical test system (diffusion in a 2D double-well potential) and, second, to demonstrate real-world applications with molecular dynamics data.\n",
1414
"\n",
@@ -177,7 +177,7 @@
177177
"cell_type": "markdown",
178178
"metadata": {},
179179
"source": [
180-
"In PyEMMA, the `featurizer` is a central object that incorporates the system's topology. We start by creating it object using the topology file. Features are now easily computed by adding the target feature, e.g., with `featurizer.add_backbone_torsions()`. "
180+
"In PyEMMA, the `featurizer` is a central object that incorporates the system's topology. We start by creating it object using the topology file. Features are now easily computed by adding the target feature. If no feature is added, the featurizer will extract Cartesian coordinates. "
181181
]
182182
},
183183
{
@@ -193,7 +193,9 @@
193193
"cell_type": "markdown",
194194
"metadata": {},
195195
"source": [
196-
"Next, we start adding features which we want to extract from the simulation data. Here, we want to load the backbone torsions:"
196+
"Now we pass the featurizer to the load function to extract the Cartesian coordinates from the trajectory files into memory. For real world examples one would prefer the `source` function, because usually one has more data available than main memory in the workstation.\n",
197+
"\n",
198+
"The warning about **plain coordinates** is triggered, because these coordinates will include diffusion as a dynamical process, which might not be what one is interested in. If the molecule of interest has been aligned to a reference prior the analysis, it is fine to use these coordinates, but we will see that there are better choices. "
197199
]
198200
},
199201
{
@@ -202,16 +204,18 @@
202204
"metadata": {},
203205
"outputs": [],
204206
"source": [
205-
"feat.add_backbone_torsions(periodic=False)"
207+
"data = pyemma.coordinates.load(files, features=feat)\n",
208+
"print('type of data:', type(data))\n",
209+
"print('lengths:', len(data))\n",
210+
"print('shape of elements:', data[0].shape)\n",
211+
"print('n_atoms:', feat.topology.n_atoms)"
206212
]
207213
},
208214
{
209215
"cell_type": "markdown",
210216
"metadata": {},
211217
"source": [
212-
"Please note that the structures have been aligned before. Since in that case we loose track of the periodic box, we have to switch off the `periodic` flag for the torsion angle computations.\n",
213-
"\n",
214-
"We can always call the featurizer's `describe()` method to show which features are requested:"
218+
"Next, we start adding features which we want to extract from the simulation data. Here, we want to load the backbone torsions, because these angles are known to describe all flexibility in the system. Since this feature is two dimensional, it is also easier to visualize. Please note that in complex systems it is not trivial to visualize plain input features."
215219
]
216220
},
217221
{
@@ -220,14 +224,17 @@
220224
"metadata": {},
221225
"outputs": [],
222226
"source": [
223-
"print(feat.describe())"
227+
"feat = pyemma.coordinates.featurizer(pdb)\n",
228+
"feat.add_backbone_torsions(periodic=False)"
224229
]
225230
},
226231
{
227232
"cell_type": "markdown",
228233
"metadata": {},
229234
"source": [
230-
"After we have selected all desired features, we can call the `load()` function to load all features into memory or, alternatively, the `source()` function to create a streamed feature reader. For now, we will use `load()`:"
235+
"Please note that the trajectories have been aligned to a reference structure before. Since in that case we loose track of the periodic box, we have to switch off the `periodic` flag for the torsion angle computations. By default PyEMMA assumes your simulation uses periodic boundary conditions.\n",
236+
"\n",
237+
"We can always call the featurizer's `describe()` method to show which features are requested. You might have noticed that you can combine arbitrary features by having multiple calls to `add_` methods of the featurizer."
231238
]
232239
},
233240
{
@@ -243,6 +250,22 @@
243250
"print('shape of elements:', data[0].shape)"
244251
]
245252
},
253+
{
254+
"cell_type": "markdown",
255+
"metadata": {},
256+
"source": [
257+
"After we have selected all desired features, we can call the `load()` function to load all features into memory or, alternatively, the `source()` function to create a streamed feature reader. For now, we will use `load()`:"
258+
]
259+
},
260+
{
261+
"cell_type": "code",
262+
"execution_count": null,
263+
"metadata": {},
264+
"outputs": [],
265+
"source": [
266+
"print(feat.describe())"
267+
]
268+
},
246269
{
247270
"cell_type": "markdown",
248271
"metadata": {},
@@ -264,9 +287,11 @@
264287
"cell_type": "markdown",
265288
"metadata": {},
266289
"source": [
267-
"We can now measure the quantity of kinetic variance of the just selected feature by computing a VAMP-2 score <a id=\"ref-1\" href=\"#cite-vamp-preprint\">wu-17</a>. This score gives us information on the kinetic variance contained in the feature. The minimum value of this score is 1, which corresponds to the invariant measure or the observed equilibrium.\n",
290+
"We can now measure the quantity of kinetic variance of the just selected feature by computing a VAMP-2 score <a id=\"ref-1\" href=\"#cite-vamp-preprint\">wu-17</a>. This enables us to distinguish features on how well they might be suited for MSM building. The minimum value of this score is 1, which corresponds to the invariant measure or the observed equilibrium.\n",
268291
"\n",
269-
"With the dimension parameter we specify the amount of dynamic processes that we want to score. This is of importance later on, when we want to compare different input features. If we did not fix this number, we would not have an upper bound for the score."
292+
"\n",
293+
"With the dimension parameter we specify the amount of dynamic processes that we want to score. This is of importance later on, when we want to compare different input features. If we did not fix this number, we would not have an upper bound for the score.\n",
294+
"Please also note that we split our available data into training and test sets, where we leave out the last file in training and then use it as test. This is an important aspect in practice to avoid over-fitting the score."
270295
]
271296
},
272297
{
@@ -275,18 +300,17 @@
275300
"metadata": {},
276301
"outputs": [],
277302
"source": [
278-
"score_phi_psi = pyemma.coordinates.vamp(\n",
279-
" data[:-1], dim=2).score(\n",
280-
" test_data=data[-1:],\n",
303+
"score_phi_psi = pyemma.coordinates.vamp(data[:-1], dim=2).score(\n",
304+
" test_data=data[-1],\n",
281305
" score_method='VAMP2')\n",
282-
"print('VAMP2-score backbone torsions: {:f}'.format(score_phi_psi))"
306+
"print('VAMP2-score backbone torsions: {:.2f}'.format(score_phi_psi))"
283307
]
284308
},
285309
{
286310
"cell_type": "markdown",
287311
"metadata": {},
288312
"source": [
289-
"The score of $\\approx1.5$ means that we have the constant of $1$ plus a total contribution of $0.5$ from the other dynamic process.\n",
313+
"The score of $1.5$ means that we have the constant of $1$ plus a total contribution of $0.5$ from the other dynamic processes.\n",
290314
"\n",
291315
"We now use PyEMMA's `plot_density()` and `plot_free_energy()` functions to create Ramachandran plots of our system:"
292316
]
@@ -365,9 +389,8 @@
365389
"metadata": {},
366390
"outputs": [],
367391
"source": [
368-
"score_heavy_atoms = pyemma.coordinates.vamp(\n",
369-
" data[:-1], dim=2).score(\n",
370-
" test_data=data[-1:],\n",
392+
"score_heavy_atoms = pyemma.coordinates.vamp(data[:-1], dim=2).score(\n",
393+
" test_data=data[:-1],\n",
371394
" score_method='VAMP2')\n",
372395
"print('VAMP2-score backbone torsions: {:f}'.format(score_phi_psi))\n",
373396
"print('VAMP2-score xyz: {:f}'.format(score_heavy_atoms))"
@@ -377,9 +400,9 @@
377400
"cell_type": "markdown",
378401
"metadata": {},
379402
"source": [
380-
"As we see, the score for the heavy atom positions is much higher as the one for the $\\phi/\\psi$ torsion angles. The feature with a higher score should be favored for further analysis, because it means that this feature contains more information about slow processes. If you are already digging deeper into your system of interest, you can of course restrict the analysis to a set of features you already know describes your process of interest, regardless of its VAMP score.\n",
403+
"As we see, the score for the heavy atom positions is much higher as the one for the $\\phi/\\psi$ torsion angles. The feature with a higher score should be favored for further analysis, because it means that this feature contains more information about slow processes. If you are already digging deeper into your system of interest, you can of course restrict the analysis to a set of features you already know describes your processes of interest, regardless of its VAMP score.\n",
381404
"\n",
382-
"Another featurization that is interesting especially for proteins is heavy atom distances:"
405+
"Another featurization that is interesting especially for proteins are pairwise heavy atom distances:"
383406
]
384407
},
385408
{
@@ -409,37 +432,22 @@
409432
"metadata": {},
410433
"outputs": [],
411434
"source": [
412-
"score_pair_dists_ca = pyemma.coordinates.vamp(\n",
413-
" data[:-1]).score(\n",
414-
" test_data=data[-1:],\n",
415-
" score_method='VAMP2')\n",
416-
"print('VAMP2-score: {:f}'.format(score_pair_dists_ca))"
435+
"score_pair_heavy_atom_dists = pyemma.coordinates.vamp(data[:-1], dim=2).score(\n",
436+
" test_data=data[-1],\n",
437+
" score_method='VAMP2')\n",
438+
"print('VAMP2-score: {:.2f}'.format(score_pair_heavy_atom_dists))"
417439
]
418440
},
419441
{
420442
"cell_type": "markdown",
421443
"metadata": {},
422444
"source": [
423-
"Apparently, the heavy atom distance pairs cover an amount of kinetic variance which is comparable to the coordinates themselves.\n",
445+
"Apparently, the heavy atom distance pairs cover an amount of kinetic variance which is comparable to the coordinates themselves. However we probably would not use much information by using this internal degree of freedom, while avoiding the need to align our trajectories first.\n",
424446
"\n",
425447
"### `load()` versus `source()`\n",
426448
"\n",
427-
"Using `load()`, we put the full data into memory. This is possible for all examples in this tutorial."
428-
]
429-
},
430-
{
431-
"cell_type": "code",
432-
"execution_count": null,
433-
"metadata": {},
434-
"outputs": [],
435-
"source": [
436-
"print(data)"
437-
]
438-
},
439-
{
440-
"cell_type": "markdown",
441-
"metadata": {},
442-
"source": [
449+
"Using `load()`, we put the full data into memory. This is possible for all examples in this tutorial.\n",
450+
"\n",
443451
"Many real world apllications, though, require more memory than your workstation might provide. For these cases, you should use the `source()` function:"
444452
]
445453
},
@@ -449,15 +457,16 @@
449457
"metadata": {},
450458
"outputs": [],
451459
"source": [
452-
"data = pyemma.coordinates.source(files, features=feat)\n",
453-
"print(data)"
460+
"reader = pyemma.coordinates.source(files, features=feat)\n",
461+
"print(reader)"
454462
]
455463
},
456464
{
457465
"cell_type": "markdown",
458466
"metadata": {},
459467
"source": [
460-
"This function allows to stream the data and work on chunks instead of the full set. Most of the functions in the `coordinates` sub-package accept data in memory as well as streamed feature readers. However, some plotting functions require the data to be in memory. To load a (sub-sampled) subset into memory, we can use the `get_output()` method with a stride parameter:"
468+
"This function creates a reader, wich allows to stream the underlying data in chunks instead of the full set. Most of the functions in the `coordinates` sub-package accept data in memory as well as readers.\n",
469+
"However, some plotting functions require the data to be in memory. To load a (sub-sampled) subset into memory, we can use the `get_output()` method with a stride parameter:"
461470
]
462471
},
463472
{
@@ -466,9 +475,9 @@
466475
"metadata": {},
467476
"outputs": [],
468477
"source": [
469-
"data_output = data.get_output(stride=5)\n",
478+
"data_output = reader.get_output(stride=5)\n",
470479
"len(data_output)\n",
471-
"print('number of frames in first file: {}'.format(data.trajectory_length(0)))\n",
480+
"print('number of frames in first file: {}'.format(reader.trajectory_length(0)))\n",
472481
"print('number of frames after striding: {}'.format(len(data_output[0])))"
473482
]
474483
},
@@ -504,11 +513,11 @@
504513
"\n",
505514
"Exercise cells come with a button (**Show Solution**) to reveal the solution.\n",
506515
"\n",
507-
"#### Exercise 1: heavy atom distances\n",
516+
"#### Exercise 1a: inverse heavy atom distances\n",
508517
"\n",
509-
"Please fix the following code block such that the distances between all heavy atoms are loaded and visualized.\n",
518+
"Please fix the following code block such that the inverse distances between all heavy atoms are loaded and visualized.\n",
510519
"\n",
511-
"**Hint**: you might find the `add_distances()` method of the featurizer object helpful."
520+
"**Hint**: try to use the auto-complete feature on the feat object to gain some insight. Also take a look at the previous demonstrations."
512521
]
513522
},
514523
{
@@ -531,6 +540,89 @@
531540
"fig.tight_layout()"
532541
]
533542
},
543+
{
544+
"cell_type": "code",
545+
"execution_count": null,
546+
"metadata": {
547+
"solution2": "hidden"
548+
},
549+
"outputs": [],
550+
"source": [
551+
"feat = pyemma.coordinates.featurizer(pdb)\n",
552+
"pairs = feat.pairs(feat.select_Heavy())\n",
553+
"feat.add_inverse_distances(pairs)\n",
554+
"\n",
555+
"data = pyemma.coordinates.load(files, features=feat)\n",
556+
"\n",
557+
"fig, ax = plt.subplots(figsize=(10, 7))\n",
558+
"pyemma.plots.plot_feature_histograms(\n",
559+
" np.concatenate(data), feature_labels=feat, ax=ax)\n",
560+
"\n",
561+
"fig.tight_layout()"
562+
]
563+
},
564+
{
565+
"cell_type": "markdown",
566+
"metadata": {},
567+
"source": [
568+
"#### Excercise 1b: compare the inverse distances feature to the previously computed ones\n",
569+
"\n",
570+
"Compute and discuss a cross-validated VAMP score for the inverse pairwise heavy atom distances and plot the result. What do you observe and which feature would you choose for further processing?"
571+
]
572+
},
573+
{
574+
"cell_type": "code",
575+
"execution_count": null,
576+
"metadata": {
577+
"solution2": "hidden",
578+
"solution2_first": true
579+
},
580+
"outputs": [],
581+
"source": [
582+
"score_inv_dist = pyemma.coordinates. #FIXME\n",
583+
"\n",
584+
"fig, ax = plt.subplots(figsize=(10, 7))\n",
585+
"score_mapping = dict(score_heavy_atoms=score_heavy_atoms,\n",
586+
" score_phi_psi=score_phi_psi,\n",
587+
" score_pair_heavy_atom_dists=score_pair_heavy_atom_dists,\n",
588+
" score_inv_dist=score_inv_dist)\n",
589+
"lbl = []\n",
590+
"for i, (key, value) in enumerate(sorted(score_mapping.items(), key=lambda x: x[1])):\n",
591+
" ax.bar(i, height=value)\n",
592+
" lbl.append(key)\n",
593+
"ax.set_xticks(np.arange(0, len(score_mapping), 1))\n",
594+
"ax.set_xticklabels(lbl)\n",
595+
"fig.tight_layout()"
596+
]
597+
},
598+
{
599+
"cell_type": "code",
600+
"execution_count": null,
601+
"metadata": {
602+
"solution2": "hidden"
603+
},
604+
"outputs": [],
605+
"source": [
606+
"score_inv_dist = pyemma.coordinates.vamp(\n",
607+
" data[:-1], dim=2).score(test_data=data[-1])\n",
608+
"\n",
609+
"fig, ax = plt.subplots(figsize=(10, 7))\n",
610+
"score_mapping = dict(score_heavy_atoms=score_heavy_atoms,\n",
611+
" score_phi_psi=score_phi_psi,\n",
612+
" score_pair_heavy_atom_dists=score_pair_heavy_atom_dists,\n",
613+
" score_inv_dist=score_inv_dist)\n",
614+
"lbl = []\n",
615+
"for i, (key, value) in enumerate(sorted(score_mapping.items(), key=lambda x: x[1])):\n",
616+
" ax.bar(i, height=value)\n",
617+
" lbl.append(key)\n",
618+
"ax.set_xticks(np.arange(0, len(score_mapping), 1))\n",
619+
"ax.set_xticklabels(lbl)\n",
620+
"fig.tight_layout()\n",
621+
"\n",
622+
"# inversing the feature preserves the amount of kinetic variance, we should continue\n",
623+
"# with the pairwise heavy atom distances."
624+
]
625+
},
534626
{
535627
"cell_type": "markdown",
536628
"metadata": {
@@ -922,7 +1014,7 @@
9221014
"name": "python",
9231015
"nbconvert_exporter": "python",
9241016
"pygments_lexer": "ipython3",
925-
"version": "3.6.5"
1017+
"version": "3.6.6"
9261018
},
9271019
"toc": {
9281020
"base_numbering": 1,

0 commit comments

Comments
 (0)