-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
feed.xml
6055 lines (4892 loc) · 671 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.1.1">Jekyll</generator><link href="https://old.tacosdedatos.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://old.tacosdedatos.com/" rel="alternate" type="text/html" /><updated>2021-08-01T20:46:16+00:00</updated><id>https://old.tacosdedatos.com/feed.xml</id><title type="html">🌮 tacos de datos | Aprende visualización de datos en español.</title><subtitle>Tu sitio para aprender de visualización y ciencia de datos en español. Consejos, recursos y mejores prácticas para tus proyectos de tecnología, periodismo de datos y análisis estadísticos.</subtitle><entry><title type="html">Mejorando nuestros gráficos de barras</title><link href="https://old.tacosdedatos.com/mejorando-barplots" rel="alternate" type="text/html" title="Mejorando nuestros gráficos de barras" /><published>2020-09-25T10:00:00+00:00</published><updated>2020-09-25T10:00:00+00:00</updated><id>https://old.tacosdedatos.com/mejorando-barplots</id><content type="html" xml:base="https://old.tacosdedatos.com/mejorando-barplots"><p>En esta ocasión les quiero hablar de cómo mejorar sus gráficos de barras con colores y fuentes personalizadas… aunque me gustaría que esta fuera una lección sobre cómo es que podemos sacar más provecho de Matplotlib y herramientas (como Seaborn) que se construyen al rededor de esta poderosa librería.</p>
<p>Puedes usar este <a href="https://www.kaggle.com/ioexception/las-cartas-m-s-usadas-de-yu-gi-oh">notebook de Kaggle</a> como referencia o como punto de inicio para tu experimentación.</p>
<h2 id="los-datos">Los datos</h2>
<p>Ultimamente he estado obsesionado con Yu-Gi-Oh! un juego de cartas en donde cada jugador elije cartas para su baraja, y encontré un dataset que contiene muchos, muchos decks… en fin, no es necesario que sepas nada sobre el juego para seguir este tutorial. Lo importante es que me hice de un dataset de barajas, y quise averiguar cuáles eran las cartas más usadas del juego, supongamos que tenemos un par de listas <code class="language-plaintext highlighter-rouge">card_ids</code> y <code class="language-plaintext highlighter-rouge">counts</code> con los identificadores de las cartas más comunes y la cantidad en la que aparecen en todas las barajas que encontré.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">card_ids</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">counts</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>['83764719', '14558127', '24224830', '81439173', '73628505', '10045474', '32807846', '97268402', '24094653', '2295440']
[4649, 4406, 3634, 3169, 3076, 2739, 1695, 1682, 1676, 1539]
</code></pre></div></div>
<p>Que indica que el id <code class="language-plaintext highlighter-rouge">83764719</code> aparece <code class="language-plaintext highlighter-rouge">4649</code> veces, el id <code class="language-plaintext highlighter-rouge">14558127</code> lo hace <code class="language-plaintext highlighter-rouge">4406</code> veces y así sucesivamente…</p>
<p>Además, tengo un diccionario que me permite obtener más información sobre determinada carta a partir de su identificador:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">card_by_id</span><span class="p">[</span><span class="s">'83764719'</span><span class="p">]</span>
</code></pre></div></div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"83764719"</span><span class="p">,</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Monster Reborn"</span><span class="p">,</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Spell Card"</span><span class="p">,</span><span class="w">
</span><span class="nl">"desc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Target 1 monster in either GY; Special Summon it."</span><span class="p">,</span><span class="w">
</span><span class="nl">"image_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://storage.googleapis.com/ygoprodeck.com/pics/83764719.jpg"</span><span class="p">,</span><span class="w">
</span><span class="nl">"image_url_small"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://storage.googleapis.com/ygoprodeck.com/pics_small/83764719.jpg"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Tenemos cosas como el nombre y el tipo de carta, esos atributos nos serán de utilidad más adelante.</p>
<h2 id="ahora-sí-a-graficar">Ahora sí, a graficar</h2>
<p>Tenemos ids de cartas y la cantidad de veces que aparecen, suena a un trabajo para… ¡una gráfica de barras! así es como la harías usando <em>matplotlib</em>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span><span class="n">card_ids</span><span class="p">,</span> <span class="n">counts</span><span class="p">)</span>
</code></pre></div></div>
<p><img src="assets/blogposts/mejorando-barplots/ugly.png" alt="An ugly plot" /></p>
<h3 id="tamaño-de-la-gráfica">Tamaño de la gráfica</h3>
<p>Además de las críticas obvias (gráfica sin título, no hay etiquetas en los ejes…) ¡la gráfica es horriblemente pequeña! para arreglar esto, podemos jugar con los argumentos <code class="language-plaintext highlighter-rouge">dpi</code> y <code class="language-plaintext highlighter-rouge">figsize</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span><span class="n">card_ids</span><span class="p">,</span> <span class="n">counts</span><span class="p">)</span>
</code></pre></div></div>
<p><img src="assets/blogposts/mejorando-barplots/ugly-but-bigger.png" alt="An ugly, but bigger plot" /></p>
<p>Un poquito mejor, ¿cierto? los argumentos que usamos nos ayudan a controlar dos cosas:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">figsize</code> controla el tamaño de la gráfica en pulgadas; el primer valor de la tupla es el valor horizontal y el segundo es vertical</li>
<li><code class="language-plaintext highlighter-rouge">dpi</code> controla los <em>puntos por pulgada</em> en nuestra gráfica, va de la mano con <code class="language-plaintext highlighter-rouge">figsize</code>. El usar este argumento nos facilitará en demasía el controlar el tamaño del texto en nuestra gráfica.</li>
</ul>
<h3 id="la-api-orientada-a-objetos">La API orientada a objetos</h3>
<p>Para permitirnos aún más personalización, tendremos que aventurarnos a usar la no tan conocida API orientada a objetos de <em>matplotlib</em>, pero como verás no es nada del otro mundo:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">ax</span> <span class="o">=</span> <span class="n">fig</span><span class="p">.</span><span class="n">gca</span><span class="p">()</span>
<span class="n">ax</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span><span class="n">card_ids</span><span class="p">,</span> <span class="n">counts</span><span class="p">)</span>
</code></pre></div></div>
<p>La gran diferencia es que no tenemos ya más referencias a <code class="language-plaintext highlighter-rouge">plt</code> más allá de la primera que nos ayuda a crear una figura. La novedad, también radica en que usamos el método <code class="language-plaintext highlighter-rouge">gca</code> (que proviene de <em>get current axes</em>) para obtener el <em>axes</em> que reside dentro de nuestra figura. Una vez hecho esto, ya podemos empezar a personalizar la gráfica:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ax</span><span class="p">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">"Card id"</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">"Occurrences"</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_title</span><span class="p">(</span><span class="sa">f</span><span class="s">"Most used cards across </span><span class="si">{</span><span class="n">deck_count</span><span class="si">}</span><span class="s"> Yu-Gi-Oh! decks"</span><span class="p">)</span>
</code></pre></div></div>
<p><img src="assets/blogposts/mejorando-barplots/ugly-but-better.png" alt="An ugly, but better plot" /></p>
<h3 id="entra-seaborn">¡Entra <em>Seaborn</em>!</h3>
<p>Muchas veces, si bien podemos lograr todos los resultados que queramos usando solamente matplotlib, hay ocasiones en las que podemos delegar este trabajo a otras librerías como <em>Seaborn</em>. Digamos que queremos mejorar aún más nuestra gráfica usando barras horizontales y los nombres de las cartas:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">card_names</span> <span class="o">=</span> <span class="p">[</span><span class="n">card_by_id</span><span class="p">[</span><span class="n">card_id</span><span class="p">][</span><span class="s">"name"</span><span class="p">]</span> <span class="k">for</span> <span class="n">card_id</span> <span class="ow">in</span> <span class="n">card_ids</span><span class="p">]</span>
<span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">dpi</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">5</span><span class="p">))</span>
<span class="n">ax</span> <span class="o">=</span> <span class="n">fig</span><span class="p">.</span><span class="n">gca</span><span class="p">()</span>
<span class="n">sns</span><span class="p">.</span><span class="n">barplot</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">counts</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="n">card_names</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">"h"</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">"Card name"</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">"Occurrences"</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_title</span><span class="p">(</span><span class="sa">f</span><span class="s">"Most used cards across </span><span class="si">{</span><span class="n">deck_count</span><span class="si">}</span><span class="s"> Yu-Gi-Oh! decks"</span><span class="p">)</span>
</code></pre></div></div>
<p>La novedad es que estamos usando <em>seaborn</em> (lo renombramos como <code class="language-plaintext highlighter-rouge">sns</code> en nuestro script) y, algo muy importante, le estamos pasando el argumento <code class="language-plaintext highlighter-rouge">ax</code> para que haga uso del axis que nosotros queremos. El resultado es el siguiente:</p>
<p><img src="assets/blogposts/mejorando-barplots/improved.png" alt="An improved, bigger plot" /></p>
<p>Se ve un poco mejor, pero la podemos hacer aún más atractiva.</p>
<h3 id="personalización-máxima">Personalización máxima</h3>
<p>Ahora podemos comenzar a crear nuestra gráfica:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span><span class="mi">7</span><span class="p">),</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">300</span><span class="p">)</span>
<span class="n">ax</span> <span class="o">=</span> <span class="n">fig</span><span class="p">.</span><span class="n">gca</span><span class="p">()</span>
<span class="n">sns</span><span class="p">.</span><span class="n">barplot</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="n">counts</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="n">card_names</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">ax</span><span class="p">,</span> <span class="n">orient</span><span class="o">=</span><span class="s">"h"</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="tipografías-personalizadas">Tipografías personalizadas</h4>
<p>Como se trata de una gráfica sobre cartas de Yu-Gi-Oh! decidí usar las tipografías que se usan para las cartas:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># fm es matplotlib.font_manager
</span><span class="n">card_name_prop</span> <span class="o">=</span> <span class="n">fm</span><span class="p">.</span><span class="n">FontProperties</span><span class="p">(</span><span class="n">fname</span><span class="o">=</span><span class="s">"card_name.ttf"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
<span class="n">card_effect_prop</span> <span class="o">=</span> <span class="n">fm</span><span class="p">.</span><span class="n">FontProperties</span><span class="p">(</span><span class="n">fname</span><span class="o">=</span><span class="s">"card_effect.ttf"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">40</span><span class="p">)</span>
<span class="n">card_effect_prop_sm</span> <span class="o">=</span> <span class="n">fm</span><span class="p">.</span><span class="n">FontProperties</span><span class="p">(</span><span class="n">fname</span><span class="o">=</span><span class="s">"card_effect.ttf"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
<span class="n">card_number_prop</span> <span class="o">=</span> <span class="n">fm</span><span class="p">.</span><span class="n">FontProperties</span><span class="p">(</span><span class="n">fname</span><span class="o">=</span><span class="s">"card_number.ttf"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">fname</code> es la ruta de la tipografía que vamos a usar, y los parámetros <code class="language-plaintext highlighter-rouge">size</code> tuve que averiguarlos a prueba y error (como casi todo en <em>matplotlib</em>). Mantengamos las referencias ahí, más adelante las vamos a usar.</p>
<h4 id="etiquetas-de-los-ejes">Etiquetas de los ejes</h4>
<p>Tengo la idea de poner los nombres de las cartas dentro de las barras, así que las podemos quitar las etiquetas del eje de las <em>y</em>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#ax.axes.get_yaxis().set_visible(False)
</span><span class="n">ax</span><span class="p">.</span><span class="n">axes</span><span class="p">.</span><span class="n">get_yaxis</span><span class="p">().</span><span class="n">set_ticks</span><span class="p">([])</span>
</code></pre></div></div>
<p>Agreguemos las etiquetas, esta vez usando las tipografías personalizadas que definimos anteriormente. También vamos a cambiarle la tipografía a los valores en el eje de las <em>x</em> usando el método <code class="language-plaintext highlighter-rouge">ax.get_xticklabels</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ax</span><span class="p">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s">"Card"</span><span class="p">,</span> <span class="n">fontproperties</span><span class="o">=</span><span class="n">card_number_prop</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s">"Occurrences"</span><span class="p">,</span> <span class="n">fontproperties</span><span class="o">=</span><span class="n">card_number_prop</span><span class="p">)</span>
<span class="k">for</span> <span class="n">label</span> <span class="ow">in</span> <span class="n">ax</span><span class="p">.</span><span class="n">get_xticklabels</span><span class="p">()</span> <span class="p">:</span>
<span class="n">label</span><span class="p">.</span><span class="n">set_fontproperties</span><span class="p">(</span><span class="n">card_number_prop</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="texto-dentro-de-las-barras">Texto dentro de las barras</h4>
<p>Cada una de las barras dentro de nuestra gráfica no es más que una instancia de una clase llamada <code class="language-plaintext highlighter-rouge">matplotlib.patches.Rectangle</code>, a los cuales podemos acceder mediante la propiedad <code class="language-plaintext highlighter-rouge">patches</code> de nuestros axes. Cada uno de estos <em>patches</em> tiene diversas propiedades como su posición en [<em>x</em>,<em>y</em>], el color de relleno, entre otras.</p>
<p>Nosotros nos aprovecharemos de tener acceso a estos <em>patches</em> poner los nombres dentro de las barras usando el método <code class="language-plaintext highlighter-rouge">text</code> que ofrece nuestro <code class="language-plaintext highlighter-rouge">ax</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">card_id</span><span class="p">,</span> <span class="n">rect</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">card_ids</span><span class="p">,</span> <span class="n">ax</span><span class="p">.</span><span class="n">patches</span><span class="p">):</span>
<span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">=</span> <span class="n">rect</span><span class="p">.</span><span class="n">xy</span>
<span class="n">card</span> <span class="o">=</span> <span class="n">card_by_id</span><span class="p">[</span><span class="n">card_id</span><span class="p">]</span>
<span class="n">rect_color</span><span class="p">,</span> <span class="n">font_color</span> <span class="o">=</span> <span class="n">find_colors</span><span class="p">(</span><span class="n">card</span><span class="p">[</span><span class="s">"type"</span><span class="p">])</span>
<span class="n">rect</span><span class="p">.</span><span class="n">set_facecolor</span><span class="p">(</span><span class="n">rect_color</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">text</span><span class="p">(</span><span class="mi">30</span> <span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="mf">0.6</span><span class="p">,</span> <span class="n">card</span><span class="p">[</span><span class="s">"name"</span><span class="p">],</span> <span class="n">color</span><span class="o">=</span><span class="n">font_color</span><span class="p">,</span> <span class="n">fontproperties</span><span class="o">=</span><span class="n">card_name_prop</span><span class="p">)</span>
</code></pre></div></div>
<h4 id="pie-de-página">Pie de página</h4>
<p>A la hora de compartir una gráfica siempre es importante informar de dónde obtuvimos la información, o tal vez una pequeña nota de <em>copyright</em> indicando que nosotros fuimos los creadores de esta, para este propósito podemos usar la clase <code class="language-plaintext highlighter-rouge">AnchoredText</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">text</span> <span class="o">=</span> <span class="n">AnchoredText</span><span class="p">(</span><span class="s">"Data from decks built at ygoprodeck.com"</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
<span class="n">prop</span><span class="o">=</span><span class="p">{</span><span class="s">'size'</span><span class="p">:</span> <span class="mi">10</span><span class="p">,},</span> <span class="n">frameon</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">ax</span><span class="p">.</span><span class="n">add_artist</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</code></pre></div></div>
<p>Y el esperado resultado final es:</p>
<p><img src="assets/blogposts/mejorando-barplots/beautiful.png" alt="Beautiful" /></p>
<p>Y <em>voilá</em>, nuestra gráfica está lista para ser compartida con el mundo.</p>
<p>Como siempre, comentarios y dudas son bienvenidos en mi cuenta de Twitter <a href="https://twitter.com/io_exception">@io_exception</a>. Esta vez todo el código para reproducir lo que hicimos aquí está en este <a href="https://www.kaggle.com/ioexception/las-cartas-m-s-usadas-de-yu-gi-oh">notebook de Kaggle</a> (si no sabes qué es Kaggle, puedes ver <a href="https://www.youtube.com/watch?v=5yF-VeivtgU">mi video sobre el tema</a>).</p></content><author><name>Antonio Feregrino Bolaños</name></author><category term="python" /><category term="matplotlib" /><category term="seaborn" /><summary type="html">En esta ocasión les quiero hablar de cómo mejorar sus gráficos de barras con colores y fuentes personalizadas… aunque me gustaría que esta fuera una lección sobre cómo es que podemos sacar más provecho de Matplotlib y herramientas (como Seaborn) que se construyen al rededor de esta poderosa librería.</summary></entry><entry><title type="html">Creación de Mapas con Tableau</title><link href="https://old.tacosdedatos.com/tableau-mapas" rel="alternate" type="text/html" title="Creación de Mapas con Tableau" /><published>2020-09-24T06:00:00+00:00</published><updated>2020-09-24T06:00:00+00:00</updated><id>https://old.tacosdedatos.com/tableau-mapas</id><content type="html" xml:base="https://old.tacosdedatos.com/tableau-mapas"><p>¿Has querido presentar información en un mapa, pero no sabes por dónde empezar?</p>
<p>Existen diversas herramientas para realizarlo, en esta ocasión te presento el software Tableau, que no requiere conocimientos de programación para generar mapas intuitivos y atractivos para cualquier tipo de público. Con este tutorial aprenderás a elaborar un mapa de México a nivel entidad a través de la identificación y uso del Marco Geoestadístico del Instituto Nacional de Estadística y Geografía (INEGI). En este podrás mostrar la información que requieras con su respectiva simbología y etiquetado de forma que puedas transmitir un mensaje claro a tu audiencia.</p>
<p>Antes de comenzar, necesitas tres cosas: Tableau Public, el Marco Geoestadístico del INEGI y los datos desagregados por entidad que desees mostrar.
Para instalar Tableau Public da click en <a href="https://public.tableau.com/en-us/s/">este enlace</a> e introduce tu correo electrónico para comenzar con la descarga.
El Marco Geoestadístico del INEGI es necesario para garantizar que tu información esté georreferenciada adecuadamente, lo encuentras en distintas categorías territoriales, pero en esta ocasión será a nivel entidad. Para obtenerlo dirígete al sitio oficial del INEGI como se indica a continuación.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_01.gif" alt="GIF_01" /></p>
<p>Una vez que haya finalizado la descarga, descomprime la carpeta “mg_sep2019_integrado” y guárdala en tu directorio de trabajo.
Por último, necesitas los datos que quieras representar para cada una de las entidades del país. Estos deben estar en un archivo Excel de forma que cada fila corresponda a una observación y cada columna sea una variable. En este sentido, una columna contendrá la clave numérica perteneciente a cada entidad, otra su nombre de acuerdo con el Marco Geoestadístico; esto lo encuentras en la carpeta “catálogos” del archivo que descargaste de INEGI. En una tercera columna agrega el nombre abreviado de la entidad pues será útil si quieres etiquetar los valores en tu mapa. Y, por último, en otra columna debe estar la información que requieras para cada entidad. Para realizar este ejemplo, usaré los datos sobre la percepción de inseguridad de las mujeres en las calles contenidos en la Encuesta Nacional de Victimización y Percepción sobre Seguridad Pública (ENVIPE) 2019 del INEGI. La estructura de tu base de datos debe ser como se ilustra en la siguiente imagen.</p>
<p><img src="assets/blogposts/tableau-mapas/IMA_001.png" alt="IMA_01" /></p>
<p>Ahora sí abré Tableau y carga los datos con los que vas a trabajar. En la pantalla de inicio de Tableau del lado izquierdo encontrarás un panel con distintos formatos de archivos, da click en Microsoft Excel y selecciona el archivo que contiene tu base de datos. Si este contiene una solo hoja se abrirá automáticamente, de lo contrario arrastra la hoja con tu información a la parte superior de la página. A continuación, une estos datos con el Marco Geoestadístico del INEGI como se muestra, da click en Añadir, selecciona Archivo espacial y elige el archivo “00ent.shp” contenido en la carpeta “conjunto de datos”. Para realizar la unión, ambos archivos deben contener una columna con los mismos datos para que estos sean identificados, puedes utilizar las claves numéricas de la entidad o sus nombres.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_02.gif" alt="GIF_02" /></p>
<p>Con los datos en la plataforma irás a la hoja de trabajo de Tableau que aparece en la esquina inferior izquierda, Hoja 1. Una vez que estés ahí, es necesario que identifiques del lado izquierdo los datos con los que vas a trabajar y enseguida encontrarás una serie de recuadros con los títulos Páginas, Filtros y Marcas; este último es el más relevante pues aquí puedes editar la simbología y etiquetado del mapa. En la barra de herramientas superior se encuentran otras opciones que permiten modificar o configurar algunas funciones. Por ahora ubica en Archivo la opción Configuración regional del libro de trabajo y selecciona español (México) para que las unidades que utilices se muestren con la notación adecuada.</p>
<p><img src="assets/blogposts/tableau-mapas/IMA_002.png" alt="IMA_02" /></p>
<p>Es momento de comenzar con tu mapa. Arrastra el campo “Geometría” a Detalle ubicado en el recuadro Marcas. Tu pantalla ahora debe mostrarte el mapa de México.</p>
<p><img src="assets/blogposts/tableau-mapas/IMA_003.png" alt="IMA_03.PNG" /></p>
<p>Para que el mapa se vea de forma más clara, ubica en la barra superior la etiqueta Mapa, da click en Capas de mapas y deselecciona todas las opciones.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_03.gif" alt="GIF_03" /></p>
<p>Es tiempo de agregar tus datos al mapa, para este ejemplo usaré el porcentaje de mujeres que perciben inseguridad en las calles. Para realizarlo debes identificar cada entidad con su clave numérica y nombre llevando los campos “CVE_ENT” y “NOMGEO” a Detalle y posteriormente el campo que contiene tus datos a Color.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_04.gif" alt="GIF_04" /></p>
<p>Por default Tableau asigna una paleta de colores, esta puede modificarse al hacer click en Color del recuadro Marcas; puedes dejarla de forma desvanecida o establecer un color escalonado. Cuando elijas los colores, considera que estos son de ayuda para que tu auditorio comprenda de forma más rápida y clara tu mensaje. En esta ocasión las entidades de color más oscuro son aquellas en donde la percepción de inseguridad es más alta, mientras que en las entidades en tono más claro ocurrirá la situación contraria. También puedes editar el nivel de opacidad de los colores, así como los límites y las sombras que quieras darle a tu mapa.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_05.gif" alt="GIF_05" /></p>
<p>Las etiquetas serán necesarias para conocer el valor exacto de cada entidad. Para esto debes colocar los campos donde guardaste las abreviaturas de las entidades y tus datos en Etiqueta del ya conocido recuadro Marcas. Al dar click en Etiqueta accedes a las opciones de edición.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_06.gif" alt="GIF_06" /></p>
<p>Como te habrás dado cuenta, no aparecen todas las etiquetas pues el espacio que hay en el mapa es limitado, para solucionarlo hay dos opciones. La primera es entrar a las opciones de edición de Etiqueta y seleccionar la opción Permitir que las etiquetas se superpongan a otras marcas, para posteriormente acomodarlas. O, segunda, dar click derecho en la entidad sin etiqueta, seleccionar la opción Una marca y se abrirá un panel donde puedes editar el contenido de tu etiqueta. También es posible modificar la anotación que acabas de crear dando click derecho en esta y seleccionando Formatear.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_07.gif" alt="GIF_07" /></p>
<p>Además de editar el tipo y tamaño de fuente, y la alineación para las etiquetas, puedes indicar qué unidad estás representando en tu mapa dando click derecho en el campo de tus datos y seleccionando Formatear.</p>
<p><img src="assets/blogposts/tableau-mapas/GIF_08.gif" alt="GIF_08" /></p>
<p>¡Estás por terminar! Dirígete a Dashboard en la barra de herramientas superior y selecciona Nuevo Dashboard para darle la presentación final a tu mapa. Del lado izquierdo puedes indicar el tamaño que tendrá tu hoja. A continuación, arrastra tu hoja de trabajo al dashboard y comienza a editar el título, subtítulo, fuente y la ubicación de la simbología. Recuerda que al dar click derecho en estos objetos puedes acceder a las opciones de edición.</p>
<p><img src="assets/blogposts/tableau-mapas/IMA_004.png" alt="IMA_04" /></p>
<p>Ahora solo queda guardarlo, para esto dirígete a Archivo en la barra superior y selecciona Guardar en Tableau Public. ¡Listo! Podrás descargar el resultado final en distintos formatos.</p>
<p><img src="assets/blogposts/tableau-mapas/IMA_005.png" alt="IMA_05" /></p></content><author><name>Karen Santoyo Tapia</name></author><category term="tableau" /><category term="mapas" /><summary type="html">¿Has querido presentar información en un mapa, pero no sabes por dónde empezar?</summary></entry><entry><title type="html">Word2vec ilustrado</title><link href="https://old.tacosdedatos.com/word-to-vec-ilustrado" rel="alternate" type="text/html" title="Word2vec ilustrado" /><published>2020-09-04T10:00:00+00:00</published><updated>2020-09-04T10:00:00+00:00</updated><id>https://old.tacosdedatos.com/word-to-vec-ilustrado</id><content type="html" xml:base="https://old.tacosdedatos.com/word-to-vec-ilustrado"><p>En esta ocasión les quiero hablar de otra forma de convertir texto a vectores, esta es distinta a las que hemos visto previamente ya que nos da como resultado un vector por cada token y cada uno de estos vectores es un vector denso.</p>
<p>Esta vez les traigo no un post original, sino más bien una traducción de un artículo que me parece vale mucho la pena. El artículo original es de Jay Alammar y se llama <a href="http://jalammar.github.io/illustrated-word2vec/">The Illustrated Word2vec</a>:</p>
<hr />
<blockquote>
<p>“Hay en todas las cosas un ritmo que es parte de nuestro universo. Hay simetría, elegancia y gracia… esas cualidades a las que se acoge el verdadero artista. Uno puede encontrar este ritmo en la sucesión de las estaciones, en la forma en que la arena modela una cresta, en las ramas de un arbusto creosota o en el diseño de sus hojas.
Intentamos copiar este ritmo en nuestras vidas y en nuestra sociedad, buscando la medida y la cadencia que reconfortan. Y sin embargo, es posible ver un peligro en el descubrimiento de la perfección última. Está claro que el último esquema contiene en sí mismo su propia fijeza. En esta perfección, todo conduce hacia la muerte” ~ Frank Herbert. “Dune” (1965)</p>
</blockquote>
<p>Encuentro la idea de <em>embeddings</em>* una de las más fascinantes dentro del aprendizaje automático. Si alguna vez has usado <em>Siri</em>, <em>Google Assistant</em>, <em>Alexa</em> o <em>Google Translate</em>, o inclusive un teléfono con teclado que predice tu siguiente palabra, entonces seguramente te has beneficiado de esta idea que se a convertido en la clave de los modelos de Procesamiento de Lenguaje Natural (<em>NLP</em>). En las últimas décadas ha existido mucho desarrollo respecto a usar <em>embeddings</em> para modelos neuronales (investigaciones recientes incluyen <em>embeddings</em> contextualizados que llevan a modelos vanguardistas como <a href="https://jalammar.github.io/illustrated-bert/">BERT</a> o GPT2).</p>
<p><strong>Word2vec</strong> es un método para crear <em>embeddings</em> de forma eficiente que ha existido desde 2013. pero además de su utilidad para la creación de estos <em>embeddings</em>, algunos de sus conceptos han sido exitosamente empleados para crear modelos de recomendación y para hacer sentido de datos secuenciales, inclusive en aplicaciones comerciales no relacionadas con lenguajes. Compañías como <a href="https://www.kdd.org/kdd2018/accepted-papers/view/real-time-personalization-using-embeddings-for-search-ranking-at-airbnb">Airbnb</a>, <a href="https://www.kdd.org/kdd2018/accepted-papers/view/billion-scale-commodity-embedding-for-e-commerce-recommendation-in-alibaba">Alibaba</a>, <a href="https://www.slideshare.net/AndySloane/machine-learning-spotify-madison-big-data-meetup">Spotify</a>, y <a href="https://towardsdatascience.com/using-word2vec-for-music-recommendations-bb9649ac2484">Anghami</a> le han sacado provecho a esta brillante pieza del mundo del <em>NLP</em> y la están usando para potenciar una nueva clase de modelos de recomendación.</p>
<p>En este post, vamos a revisar el concepto de <em>embeddings</em>, y cómo es que se generan estos con la técnica de <em>word2vec</em>. Pero comencemos con un ejemplo para familiarizarnos con el uso de vectores para representar cosas. ¿Sabías que una lista de cinco números (es decir, un vector) puede representar mucho sobre tu personalidad?</p>
<h2 id="embeddings-de-personalidad-cómo-eres"><em>Embeddings</em> de personalidad: ¿cómo eres?</h2>
<blockquote>
<p>“Te doy el camaleón del desierto, cuya habilidad para mezclarse con el fondo te dice todo lo que necesitas saber sobre las raíces de la ecología y las bases de la identidad personal” ~ Hijos de Dune</p>
</blockquote>
<p>En una escala de 0 a 100, ¿qué tan introvertido/extrovertido eres tu (donde 0 es introvertido, y 100 es extrovertido)? ¿alguna vez has tomado un test de personalidad como MBTI – o, mejor aún, una prueba del <a href="https://es.wikipedia.org/wiki/Modelo_de_los_cinco_grandes">modelo de los cinco grandes</a>? si no lo has hecho, este tipo de pruebas te hace una serie de preguntas y te califica en diferentes ejes, siendo la introversión o extraversión uno de ellos.</p>
<p><img src="http://jalammar.github.io/images/word2vec/big-five-personality-traits-score.png" alt="" /></p>
<p><small>Ejemplo de un resultado en una prueba del modelo de los cinco grandes. Este te puede decir mucho sobre ti mismo y ha mostrado tener habilidades predictivas en cuanto a éxito <a href="http://psychology.okstate.edu/faculty/jgrice/psyc4333/FiveFactor_GPAPaper.pdf">académico</a>, <a href="https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1744-6570.1999.tb00174.x">personal</a> y <a href="https://www.massgeneral.org/psychiatry/assets/published_papers/soldz-1999.pdf">profesional</a>. <a href="https://projects.fivethirtyeight.com/personality-quiz/">Aquí</a> puedes calcular tus valores.</small></p>
<p>Imagina que obtuve 38/100 en el eje de introversión/extroversión. Lo podemos graficar así:</p>
<p><img src="http://jalammar.github.io/images/word2vec/introversion-extraversion-100.png" alt="" /></p>
<p>Si colocamos los valores de -1 a 1:</p>
<p><img src="http://jalammar.github.io/images/word2vec/introversion-extraversion-1.png" alt="" /></p>
<p>¿Qué tan bien crees conocer a una persona si sabes solo esta información sobre ella? No mucho, las personas son complejas. Añadamos otra dimensión, la calificación de otra de las características del test:</p>
<p><img src="http://jalammar.github.io/images/word2vec/two-traits-vector.png" alt="" /></p>
<p><small>Podemos representar dos dimensiones como un punto en la gráfica, o mejor aún, como un vector desde el origen a ese punto. Tenemos herramientas increíbles que para trabajar con vectores que nos resultarán útiles más adelante.</small></p>
<p>He ocultado qué características estamos graficando solamente para que nos acostumbremos a no saber qué representa cada dimensión – aun asi, estamos obteniendo mucha información de la representación vectorial de cada una de las personalidades.</p>
<p>Ahora podemos decir que este vector representa parcialmente mi personalidad. La usabilidad de esta representación es útil cuando quieres comparar otras dos personas conmigo. Digamos que me atropella un autobús y debo ser reemplazado por alguien con una personalidad similar. Dada la siguiente figura, ¿cuál de las dos personas es más similar a mi?</p>
<p><img src="http://jalammar.github.io/images/word2vec/personality-two-persons.png" alt="" /></p>
<p>Cuando estamos trabajando con vectores, una forma común de calcular una medida de similitud es la <a href="https://es.wikipedia.org/wiki/Similitud_coseno">similitud coseno</a> (o <em>cosine similarity</em> que es su nombre en inglés).</p>
<p><img src="http://jalammar.github.io/images/word2vec/cosine-similarity.png" alt="" /></p>
<p><small><span style="color: #70BF41;">Person #1</span> es más similar a mi en cuanto a personalidad. Vectores que apuntan a la misma dirección (aunque la longitud también tiene que ver) tienen una similitud coseno más grande.</small></p>
<p>Aun asi, dos dimensiones no son suficientes para capturar información suficiente sobre qué tan diferentes dos personas son. Décadas de investigación psicológica han llevado a que existen 5 características (y muchas sub-características). Así que vamos a usar todas en nuestras comparaciones:</p>
<p><img src="http://jalammar.github.io/images/word2vec/big-five-vectors.png" alt="" /></p>
<p>El problema con estas cinco dimensiones es que hemos perdido la habilidad de graficar esas flechitas tan bonitas en dos dimensiones. Este es un obstáculo común en el aprendizaje automático en el que constantemente tenemos que pensar en espacios de grandes dimensiones. La ventaja que teneos es que la similitud coseno aún funciona sin importar las dimensiones:</p>
<p><img src="http://jalammar.github.io/images/word2vec/embeddings-cosine-personality.png" alt="" /></p>
<p><small>La similitud coseno funciona sin importar el número de dimensiones. Estas son mejores calificaciones porque han sido calculadas en una representación con mayor resolución de las cosas que están siendo comparadas.</small></p>
<p>Para concluir esta sección, quiero que nos quedemos con dos ideas principales:</p>
<ol>
<li>Podemos representar personas (y cosas) como vectores de números (lo que es perfecto para las computadoras).</li>
<li>Podemos comparar fácilmente qué tan similares son los vectores entre sí.</li>
</ol>
<p><img src="http://jalammar.github.io/images/word2vec/section-1-takeaway-vectors-cosine.png" alt="" /></p>
<h2 id="embeddings-de-palabras"><em>Embeddings</em> de palabras</h2>
<blockquote>
<p>“El don de las palabras es el don del engaño y la ilusión” ~ Hijos de Dune</p>
</blockquote>
<p>Entendiendo esto, podemos proceder a ver ejemplos de vectores-palabra entrenados (también conocidos como <em>embeddings</em>) y ver algunas de sus propiedades interesantes.</p>
<p>Este es un <em>embedding</em> para la palabra <em>“king”</em> –rey, en inglés– (GloVe vector entrenado en Wikipedia):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 0.50451 , 0.68607 , -0.59517 , -0.022801, 0.60046 , -0.13498 , -0.08813 , 0.47377 , -0.61798 , -0.31012 , -0.076666, 1.493 , -0.034189, -0.98173 , 0.68229 , 0.81722 , -0.51874 , -0.31503 , -0.55809 , 0.66421 , 0.1961 , -0.13495 , -0.11476 , -0.30344 , 0.41177 , -2.223 , -1.0756 , -1.0783 , -0.34354 , 0.33505 , 1.9927 , -0.04234 , -0.64319 , 0.71125 , 0.49159 , 0.16754 , 0.34344 , -0.25663 , -0.8523 , 0.1661 , 0.40102 , 1.1685 , -1.0137 , -0.21585 , -0.15155 , 0.78321 , -0.91241 , -1.6106 , -0.64426 , -0.51042 ]
</code></pre></div></div>
<p>Es una lista de 50 números. No podemos decir mucho si solamente vemos estos números.</p>
<p><img src="http://jalammar.github.io/images/word2vec/king-white-embedding.png" alt="" /></p>
<p>Añadamos colores a las celdas basados en sus valores (rojo si están cerca de 2, blanco si están cerca de 0 y azul si están cerca de -2):</p>
<p><img src="http://jalammar.github.io/images/word2vec/king-colored-embedding.png" alt="" /></p>
<p>De ahora en adelante, vamos a ignorar los números y concentrarnos solo en los colores que indican los valores de las celdas. Comparemos <em>“king”</em> contra otras palabras:</p>
<p><img src="http://jalammar.github.io/images/word2vec/king-man-woman-embedding.png" alt="" /></p>
<p>¿Ves cómo las palabras <em>“man”</em> y <em>“woman”</em> son más similares entre sí que cualquiera de ellas con <em>“king”</em>? Esto nos dice algo. Estas representaciones vectoriales capturan un poco la información/significado/asociaciones de estas palabras.</p>
<p>Aquí hay otros ejemplos (compara las columnas verticalmente, buscando columas con colores similares):</p>
<p><img src="https://jalammar.github.io/images/word2vec/queen-woman-girl-embeddings.png" alt="" /></p>
<p>Algunas cosas para destacar:</p>
<ol>
<li>Hay una columna roja que coincide en todas las palabras. Las palabras son similares en esa dimensión (recuerda que no sabemos lo que significa cada dimensión).</li>
<li>Puedes ver cómo <em>“woman”</em> y <em>“girl”</em> son similares en un montón de lugares. Lo mismo sucede con <em>“man”</em> y <em>“boy”</em>.</li>
<li><em>“boy”</em> y <em>“girl”</em> también tienen lugares en donde coinciden, pero son lugares diferentes a <em>“woman”</em> o <em>“man”</em>. ¿Será que estas estén codificando una vaga definición de juventud? es posible.</li>
<li>Todas las palabras, excepto la última representan personas. Agregué, por ejemplo, un objeto (<em>“water”</em>) para mostrar las diferencias entre categorías. Por ejemplo, ¿ves esa columna azul fuerte a la derecha que se atenúa cuando llegamos al <em>embedding</em> de <em>“water”</em>?</li>
<li>Hay otros lugares en donde <em>“king”</em> y <em>“queen”</em> son diferentes de todas las demás, ¿será que estas diferencias codifiquen un concepto vago de realeza?</li>
</ol>
<h3 id="analogías">Analogías</h3>
<blockquote>
<p>“Las palabras pueden llevar todo el peso que queramos. Todo lo que se requiere es un acuerdo tradición a partir de la cual construir” ~ Dios emperador de Dune</p>
</blockquote>
<p>Los ejemplos más usados que muestran una de las características más increíbles de los <em>embeddings</em> es el concepto de analogías. Podemos sumar y restar <em>embeddings</em> y llegar a resultados interesantes. El ejemplo más famoso es la fórmula <em>“king”</em> - <em>“man”</em> + <em>“woman”</em>:</p>
<p><img src="https://jalammar.github.io/images/word2vec/king-man+woman-gensim.png" alt="" /></p>
<p><small>Usando la biblioteca <a href="https://radimrehurek.com/gensim/">Gensim</a> de Python, podemos sumar y restar vectores, y encontrar las palabras más similares al vector resultante. La imagen muestra una lista de las palabras más similares, cada una con su similitud coseno.</small></p>
<p>Podemos visualizar esta analogía como lo hemos hecho anteriormente:</p>
<p><img src="https://jalammar.github.io/images/word2vec/king-analogy-viz.png" alt="" /></p>
<p><small>El vector resultante de <em>“king-man+woman”</em> no coincide exactamente con <em>“queen”</em> es la más cercana de las 400,000 que la contiene este <em>dataset</em>.</small></p>
<p>Ahora que hemos revisado los <em>embeddings</em>, aprendamos más acerca del proceso para obtenerlos. Pero antes de que lleguemos a <em>word2vec</em>, necesitamos conocer a su padre conceptual: los modelos de lenguaje neuronales.</p>
<h2 id="modelando-lenguajes">Modelando lenguajes</h2>
<blockquote>
<p>“El profeta no se distrae con ilusiones del pasado, presente y futuro. <strong>La fijeza del lenguaje determina tales distinciones lineales.</strong> Los profetas sostienen la llave de la cerradura en un idioma.
Este no es un universo mecánico. La progresión lineal de los eventos la impone el observador. ¿Causa y efecto? No es eso para nada. <strong>El profeta pronuncia palabras fatídicas.</strong> Vislumbras algo “destinado a ocurrir” pero el instante profético libera algo de portento y poder infinitos. El universo sufre un cambio fantasmal” ~ Dios emperador de Dune</p>
</blockquote>
<p>Si uno quisiera un ejemplo de una aplicación que usa <em>NLP</em>, uno de los mejores sería la predicción de la próxima palabra en el teclado de un teléfono. Es una característica que miles de millones de personas usan cientos de veces al día.</p>
<p><img src="http://jalammar.github.io/images/word2vec/swiftkey-keyboard.png" alt="" /></p>
<p>La predicción de la próxima palabra es una tarea que puede llevarse a cabo usando un <em>modelo de lenguage</em> (<em>language model</em>, en inglés). Un modelo de lenguaje puede tomar una lista de palabras (digamos, dos), y tratar de predecir cuál es la que le seguiría.</p>
<p>En la captura de pantalla de arriba, podemos pensar que el modelo tomó estas dos palabras en color verde (<span style="color: #70BF41;">Thou</span>, <span style="color: #70BF41;">shalt</span>) y regresa un conjunto de sugerencias (“not” es la que tenía la mayor probabilidad):</p>
<p><img src="http://jalammar.github.io/images/word2vec/thou-shalt-_.png" alt="" /></p>
<p>Puedes pensar en el modelo como una caja negra:</p>
<p><img src="http://jalammar.github.io/images/word2vec/language_model_blackbox.png" alt="" /></p>
<p>Pero en la práctica, el modelo no solamente regresa como resultado una sola palabra. En realidad, entrega las probabilidades para todas las palabras que “conoce” (el conjunto de todas las palabras que conoce se llama vocabulario, que pueden ir desde unas cuantas miles hasta millones de palabras). Es la responsabilidad del teclado encontrar las palabras con mayor probabilidad y presentarlas al usuario.</p>
<p><img src="http://jalammar.github.io/images/word2vec/language_model_blackbox_output_vector.png" alt="" /></p>
<p><small>Los resultados de un modelo de lenguaje son probabilidades sobre todas las palabras que el modelo “conoce”. En la imagen nos referimos a la probabilidad como porcentaje, pero ese 40% en realidad vale 0.4 en nuestro vector de salida.</small></p>
<p>Después de ser entrenado, los primeros modelos de lenguaje (<a href="http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf">Bengio 2003</a>) calculaban la predicción en tres pasos:</p>
<p><img src="http://jalammar.github.io/images/word2vec/neural-language-model-prediction.png" alt="" /></p>
<p>El primer paso es el más relevante para nosotros porque en este post estamos hablando sobre los <em>embeddings</em>. Uno de los resultados del proceso de entrenamiento es una matriz que contiene un <em>embedding</em> por cada uno de los tokens en nuestro vocabulario. Cuando estamos prediciendo, simplemente buscamos los <em>embeddings</em> de los tokens de entrad ay calculamos la predicción:</p>
<p><img src="http://jalammar.github.io/images/word2vec/neural-language-model-embedding.png" alt="" /></p>
<p>Ahora vamos a aprender cómo es que esta matriz de <em>embeddings</em> es creada.</p>
<h2 id="entrenamiento-de-modelos-de-lenguaje">Entrenamiento de modelos de lenguaje</h2>
<blockquote>
<p>“Un proceso no se puede entender deteniéndolo. La comprensión debe moverse con el flujo del proceso, debe unirse y fluir con él.” ~Dune</p>
</blockquote>
<p>Los modelos de lenguaje tienen una gran ventaja sobre otros modelos de <em>machine learning</em>. Esa ventaja es que podemos entrenarlos usando texto – del cual tenemos mucho. Piensa en todos los libros, artículos, y otras formas de textos a nuestro al rededor. En contraste con otros modelos de aprendizaje automático que necesitan que la los datos sean preparados (y a veces obtenidos) específicamente para ellos.</p>
<blockquote>
<p>“Conocerás una palabra por sus la compañía que mantiene alrededor” ~ J.R. Firth</p>
</blockquote>
<p>Las palabras obtienen sus <em>embeddings</em> a partir de las palabras que aparecen a su alrededor. Esto funciona de la siguiente manera:</p>
<ol>
<li>Obtenemos un montón de texto (digamos, todos los artículos de Wikipedia), luego</li>
<li>tomamos una ventana (digamos, de tres palabras) que movemos sobre todo el texto,</li>
<li>Esta ventana genera nuestros ejemplos para el entrenamiento del modelo:</li>
</ol>
<p><img src="http://jalammar.github.io/images/word2vec/wikipedia-sliding-window.png" alt="" /></p>
<p>En tanto esta ventana se desliza, nosotros (virtualmente) generamos un <em>dataset</em> que usaremos para entrenar el modelo. Para ver cómo es que esto funciona, veamos cómo funciona el proceso para la siguiente frase:</p>
<blockquote>
<p>“Thou shalt not make a machine in the likeness of a human mind” ~Dune</p>
</blockquote>
<p>Cuando empezamos, la ventana está en las tres primeras palabras de la oración:</p>
<p><img src="http://jalammar.github.io/images/word2vec/lm-sliding-window.png" alt="" /></p>
<p>Tomamos las dos palabras como <em>features</em>, y la tercera como la etiqueta a predecir:</p>
<p><img src="http://jalammar.github.io/images/word2vec/lm-sliding-window-2.png" alt="" /></p>
<p>Luego entonces deslizamos la ventana a la siguiente posición para generar un segundo ejemplo de entrenamiento:</p>
<p><img src="http://jalammar.github.io/images/word2vec/lm-sliding-window-3.png" alt="" /></p>
<p>Y de pronto tendremos un gran <em>dataset</em> de palabras que suelen aparecer después de otro par:</p>
<p><img src="http://jalammar.github.io/images/word2vec/lm-sliding-window-4.png" alt="" /></p>
<p>En la práctica, los modelos suelen ser entrenados mientras esta ventana se va deslizando, sin embargo, siento que es más claro separar lógicamente la etapa de generación del <em>dataset</em> de la etapa de entrenamiento. Además de modelos basados en redes neuronales, existe una técnica conocida como <em>n-grams</em> que es también usada comúnmente para entrenar modelos (mira el capítulo 3 de <a href="http://web.stanford.edu/~jurafsky/slp3/"><em>Speech and Language Processing</em></a>). para ver cómo es que este cambio de <em>n-grams</em> a modelos neuronales se refleja en productos reales, revisa <a href="https://blog.swiftkey.com/neural-networks-a-meaningful-leap-for-mobile-typing/">este post de Swiftkey</a> mi teclado favorito para Android, introduciendo su modelo neuronal de lenguaje y comparándolo con su previo modelo basado en <em>n-grams</em>. me gusta este ejemplo porque muestra cómo las propiedades algorítmicas de los <em>embeddings</em> se pueden describir en lenguaje de marketing.</p>
<h3 id="mira-hacia-ambos-lados">Mira hacia ambos lados</h3>
<blockquote>
<p>“La paradoja es un indicador que te dice que mires más allá. Si las paradojas te molestan, eso delata tu profundo deseo de absolutos. El relativista trata una paradoja simplemente como interesante, quizás divertida o incluso, un pensamiento terrible, educativo” ~ Dios emperador de Dune</p>
</blockquote>
<p>Sabiendo de lo que hablamos anteriormente en este post, completa la oración</p>
<p><img src="http://jalammar.github.io/images/word2vec/jay_was_hit_by_a_.png" alt="" /></p>
<p>El contexto que te he dado aquí son 5 palabras antes del espacio en blanco. Estoy seguro que la mayoría de las personas elegirían la palabra <em>“bus”</em> como solución. Pero qué sucedería si te doy un poco más de información – una palabra después del espacio en blanco, ¿cambiaría tu respuesta?</p>
<p><img src="http://jalammar.github.io/images/word2vec/jay_was_hit_by_a_.png" alt="" /></p>
<p>Esto cambia completamente lo que debería ir en el espacio en blanco, la palabra <em>“red”</em> es ahora la que tiene mayor sentido de ser elegida. Lo que hemos aprendido de esto es que tanto las palabras previas como las siguientes a una palabra determinada contienen un alto valor sobre esta palabra determinada. Resulta que tomar en cuenta ambas direcciones (palabras a la izquierda y derecha de la que estamos adivinando) nos lleva a tener mejor <em>embeddings</em>. Veamos cómo es que podemos tomar en cuenta esto al momento de entrenar nuestro modelo.</p>
<h2 id="skipgram"><em>Skipgram</em></h2>
<blockquote>
<p>“La inteligencia se arriesga con datos limitados en un campo donde los errores no solo son posibles sino también necesarios.” ~Chapterhouse: Dune</p>
</blockquote>
<p>En lugar de solamente mirar dos palabras antes de nuestra palabra objetivo, podemos también ver dos palabras después:</p>
<p><img src="http://jalammar.github.io/images/word2vec/continuous-bag-of-words-example.png" alt="" /></p>
<p>Si hacemos esto, el <em>dataset</em> que estamos construyendo virtualmente para entrenar el modelo se vería así:</p>
<p><img src="http://jalammar.github.io/images/word2vec/continuous-bag-of-words-dataset.png" alt="" /></p>
<p>Esta arquitectura es conocida como <em>Continuous Bag of Words</em> y es descrita en uno de los <a href="https://arxiv.org/pdf/1301.3781.pdf">artículos de word2vec</a>. Hay otra arquitectura que entregó grandes resultados haciendo las cosas un poco diferente.</p>
<p>El lugar de “adivinar” una palabra a partir de su contexto (las palabras antes y después de ella), esta otra arquitectura trata de predecir las palabras vecinas a partir de una palabra determinada. Podemos imaginar que la ventana se desliza sobre el texto de entrenamiento así:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window.png" alt="" /></p>
<p><small>La palabra en la casilla verde será tratada como la palabra de entrada y cada una de las casillas rosas serán tratadas como posibles resultados.</small></p>
<p>Las casillas rosas están marcadas con diferentes tonalidades porque la ventana deslizante en realidad crea cuatro ejemplos diferentes en nuestro dataset:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-samples.png" alt="" /></p>
<p>Este método es conocido como la arquitectura <em>skipgram</em>. Podemos visualizar la ventana deslizante haciendo algo así:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-1.png" alt="" /></p>
<p>Esto añadiría cuatro ejemplos a nuestro dataset de entrenamiento:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-2.png" alt="" /></p>
<p>Luego entonces podemos deslizar la ventana a su siguiente posición:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-3.png" alt="" /></p>
<p>Esto genera otros cuatro ejemplos:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-4.png" alt="" /></p>
<p>Un par de posiciones más adelante tenemos muchos más ejemplos:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-sliding-window-5.png" alt="" /></p>
<h2 id="revisando-el-proceso-de-entrenamiento">Revisando el proceso de entrenamiento</h2>
<blockquote>
<p>“Muad’Dib aprendió rápidamente porque su primer entrenamiento fue sobre cómo aprender. Y la primera lección de todas fue la confianza básica que podía aprender. Es impactante descubrir cuántas personas no creen que puedan aprender y cuántas más creen que aprender es difícil.” ~ Dune</p>
</blockquote>
<p>Ahora que ya tenemos nuestro <em>dataset</em> creado a partir del modelo <em>skipgram</em>, echemos un vistazo a cómo lo podemos usar para entrenar un modelo neuronal básico de lenguaje que predice las palabras vecinas a otra.</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-language-model-training.png" alt="" /></p>
<p>Comencemos por el primer ejemplo en nuestro <em>dataset</em>. Tomando la primer entrada y dándosela al modelo que aún no está entrenado pidiéndole su predicción para la siguiente palabra.</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-language-model-training-2.png" alt="" /></p>
<p>El modelo ejecuta los tres pasos definidos arriba y entrega un vector de predicción (en donde cada palabra en su vocabulario recibe una probabilidad). Dado que el modelo no está entrenado aún, sus predicciones son incorrectas en esta etapa; eso está bien. Nosotros sabemos qué palabra debió haber predicho – la palabra (o “etiqueta”) en la fila que estamos usando para entrenar el modelo:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-language-model-training-3.png" alt="" /></p>
<p><small>El vector objetivo (<em>“target vector”</em>) es aquel en donde la verdadera palabra esperada tiene una probabilidad de 1 mientras que cualquier otra tienen probabilidad 0.</small></p>
<p>¿Qué tan lejos estuvo el modelo? para saber esto, restamos los dos vectores (el valor esperado menos el valor predecido) lo cual nos va a dar un vector “error”:</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-language-model-training-4.png" alt="" /></p>
<p>Este vector “error” puede ser usado para actualizar el modelo para que, la siguiente vez que se le pregunte, sea más probable que “adivine” <span style="color: #e91e63;">thou</span> cuando recibe <span style="color: #4caf50;">not</span> como entrada.</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-language-model-training-5.png" alt="" /></p>
<p>Y así concluye el primer paso del entrenamiento. Procedemos a ejecutar el mismo proceso con el siguiente ejemplo en nuestro dataset, y el siguiente, y el siguiente y así hasta que hayamos terminado de cubrir todos los ejemplos de nuestro <em>dataset</em>, con eso se cubre lo que en <em>machine learning</em> se conoce como una <strong>época de entrenamiento</strong>. Después repetimos el mismo proceso por un número de épocas y finalmente podemos extraer la matriz de <em>embeddings</em> (que son los parámetros internos de nuestro modelo neuronal) y usarla para cualquier otra aplicación.</p>
<p>Mientras qué hemos logrado entender el proceso, aún no llegamos a cómo es que <em>word2vec</em> fue entrenado en realidad. Nos faltan un par de ideas claves.</p>
<h2 id="sampleo-negativo">Sampleo negativo</h2>
<blockquote>
<p>“Intentar comprender a Muad’Dib sin comprender a sus enemigos mortales, los Harkonnen, es intentar ver la Verdad sin conocer la Falsedad. Es el intento de ver la Luz sin conocer la Oscuridad. No puede ser.” ~ Dune</p>
</blockquote>
<p>Recordemos los tres pasos de cómo es que este modelo neuronal calcula su predicción:</p>
<p><img src="http://jalammar.github.io/images/word2vec/language-model-expensive.png" alt="" /></p>
<p>Este tercer paso es muy costoso desde un punto de vista computacional – especialmente sabiendo que tenemos que hacerlo una vez por cada ejemplo en nuestro dataset (que fácilmente será millones de veces). Necesitamos hacer algo para mejorar el desempeño.</p>
<p>Una forma de hacerlo es dividiendo nuestro objetivo en dos etapas:</p>
<ol>
<li>Generar <em>embeddings</em> de alta calidad (sin preocuparnos por predecir la siguiente palabra)</li>
<li>Usar estos <em>embeddings</em> para entrenar un modelo de lenguaje (para ahora si, predecir la siguiente palabra)</li>
</ol>
<p>Nos vamos a enfocar en el paso 1, ya que este post se trata de <em>embeddings</em>. Para generar unos de alta calidad mientras que usamos un modelo de alto desempeño podemos cambiar la funcionalidad del modelo de predecir la siguiente palabra:</p>
<p><img src="http://jalammar.github.io/images/word2vec/predict-neighboring-word.png" alt="" /></p>
<p>A uno que tome las dos palabras (la de entrada y la que sería de salida) y regrese una medida indicando si estas palabras son vecinas o no (0 para “no vecinas”, 1 para “vecinas”):</p>
<p><img src="http://jalammar.github.io/images/word2vec/are-the-words-neighbors.png" alt="" /></p>
<p>Este simple cambio también significa que podemos cambiar nuestro modelo de salidas de múltiples salidas a un modelo de regresión lineal – que se convierte en uno más sencillo y fácil de entrenar.</p>
<p>Este cambio también requiere que nosotros cambiemos la estructura de nuestro <em>dataset</em> – lo que era antes nuestra etiqueta ahora es otra entrada al modelo, y el valor a predecir es 0 o 1. Por ahora todos serán 1 puesto que todas nuestras palabras son “vecinas”.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-dataset.png" alt="" /></p>
<p>Este problema puede ser resuelto a una velocidad impresionante – procesando millones de ejemplos en minutos; sin embargo, existe un pequeño problema que debemos solucionar. Si todos nuestros ejemplos son positivos (es decir, etiqueta 1), nos estamos exponiendo a que nuestro modelo se pase de listillo y siempre regrese 1 como respuesta – logrando 100% de precisión pero sin aprender nada (y en el proceso generar <em>embeddings</em> que no sirven).</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-smartass-model.png" alt="" /></p>
<p>Para resolver esto, necesitamos incluir <em>ejemplos negativos</em> en nuestro <em>dataset</em> – ejemplos de palabras que no tienen relación y para las cuales nuestro modelo debe regresar 0 como predicción. Con eso tenemos ahora un verdadero reto para el cual nuestro modelo tiene que trabajar para resolver, sin embargo este proceso sigue siendo rápido.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-negative-sampling.png" alt="" /></p>
<p><small>Para cada ejemplo en nuestro <em>dataset</em>, añadimos un ejemplo negativo. Estos tienen la misma palabra de “entrada” y 0 como etiqueta.</small></p>
<p>Pero, ¿qué colocamos como palabras de “salida”? pues podemos elegir palabras de nuestro vocabulario aleatoriamente:</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-negative-sampling-2.png" alt="" /></p>
<p>Esta idea está inspirada por <a href="http://proceedings.mlr.press/v9/gutmann10a/gutmann10a.pdf"><em>noise-constrative estimation</em></a>. Nosotros estamos contrastando la verdadera señal (los ejemplos positivos de palabras vecinas) con ruido (los ejemplos de palabras que no son vecinas elegidos aleatoriamente). Esto representa un excelente balance entre eficiencia computacional y estadística.</p>
<h2 id="skipgram-con-sampleo-negativo"><em>Skipgram</em> con sampleo negativo</h2>
<p>Ahora hemos cubierto dos de las ideas centrales en <em>word2vec</em>: en conjunto son llamadas <em>skipgram</em> con sampleo negativo (*Skipgram with Negative Sampling, SGNS)</p>
<p><img src="http://jalammar.github.io/images/word2vec/skipgram-with-negative-sampling.png" alt="" /></p>
<h2 id="proceso-de-entrenamiento-de-word2vec">Proceso de entrenamiento de <em>Word2vec</em></h2>
<blockquote>
<p>“La computadora no puede anticipar todos los problemas de importancia para los humanos. Es la diferencia entre bits en serie y un continuo ininterrumpido. Nosotros tenemos este; las máquinas se limitan al otro.” ~ Dune</p>
</blockquote>
<p>Antes de que el proceso de entrenamiento comience, tenemos que pre-procesar el texto que vamos a usar para entrenar el modelo. Por ejemplo, determinamos el tamaño de nuestro vocabulario (llamaremos a este valor <span style="color:#ffa000;">vocab_size</span>, y digamos que su valor es 10,000) y qué palabras pertenecen a este.</p>
<p>Al principio de la face de entrenamiento creamos dos matrices – una de <span style="color: #4caf50;">Embedding</span> y otra de <span style="color: #9c27b0;">Contexto</span>. Estas dos matrices tienen un vector (<em>embedding</em>) para cada palabra en el vocabulario, es decir <span style="color:#ffa000;">vocab_size</span> es el tamaño de una de sus dimensiones. La segunda dimensión es qué tan largo queremos que el <em>embedding</em> sea (llamemos a este parámetro <span style="color: #ff6f00;">embedding_size</span>), 300 es un valor común, aunque anteriormente vimos un ejemplo de 50.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-embedding-context-matrix.png" alt="" /></p>
<p>Cuando el proceso de entrenamiento comienza, rellenamos estas matrices con valores aleatorios. En cada paso de entrenamiento, tomamos un ejemplo positivo y su correspondiente negativo. Veámoslo con el primer grupo:</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-example.png" alt="" /></p>
<p>Ahora tenemos cuatro palabras: la de entrada <span style="color: #4caf50;">not</span> y la de salida o contexto <span style="color: #9c27b0;">thou</span> (que es su vecina). Además, tenemos <span style="color: #9c27b0;">aaron</span> y <span style="color: #9c27b0;">taco</span> como ejemplos negativos. Con esto en mente, ubicamos sus <em>embeddings</em>: para la palabra de entrada, buscamos en la matriz <span style="color: #4caf50;">Embedding</span> mientras que para las palabras “contexto” los buscamos en la matriz <span style="color: #9c27b0;">Contexto</span> (a pesar de que ambas matrices tienen un <em>embedding</em> para cada palabra en nuestro vocabulario).</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-lookup-embeddings.png" alt="" /></p>
<p>Después, tomamos el producto punto (<em>dot product</em>) del <em>embedding</em> de entrada con los <em>embeddings</em> contexto. En cada caso, este producto punto resultará en un número que indica la similtud entre estas dos palabras:</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-dot-product.png" alt="" /></p>
<p>Necesitamos una forma de convertir estos valores en algo que parezca probabilidades – necesitamos que todas sean positivas y con valores entre 0 y 1. Este es un buen lugar para usar <a href="https://jalammar.github.io/feedforward-neural-networks-visual-interactive/#sigmoid-visualization"><em>sigmoid</em></a>.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-dot-product-sigmoid.png" alt="" /></p>
<p>Ahora podemos tratar la salida de la operación <em>sigmoid</em> como la salida del modelo para estos ejemplos. Puedes ver que <span style="color: #9c27b0;">taco</span> tiene el valor mayor mientras que <span style="color: #9c27b0;">aaron</span> el menor, ambos antes y después de la operación <em>sigmoid</em>.</p>
<p>Una vez que el modelo no entrenado ha hecho una predicción, y sabiendo que tenemos un resultado correcto contra el cual comparar, vamos a calcular cuál fue el error en las predicciones del modelo. Para hacer eso, basta con restar los valores obtenidos de <em>sigmoid</em> contra el valor de salida verdadero (<em>target</em>):</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-error.png" alt="" /></p>
<p>Aquí es donde viene la parte del <em>learning</em> en <em>machine learning</em>. Una vez que calculamos el error, podemos usarlo para ajustar los embeddings de nuestrass palabras, para que la próxima vez que necesitemos una predicción, los valores predecidos sean más parecidos a los valores esperados.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-update.png" alt="" /></p>
<p>Con esto concluye un primer paso de entrenamiento. Una vez concluido, terminamos con <em>embeddings</em> un poquito mejores para las palabras involucradas en él (<span style="color: #4caf50;">not</span>, <span style="color: #9c27b0;">thou</span>, <span style="color: #9c27b0;">aaron</span> y <span style="color: #9c27b0;">taco</span>). Ahora si, podemos pasar al siguiente paso, es decir, el siguiente ejemplo positivo y sus correspondientes negativos para ejecutar el proceso nuevamente.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-training-example-2.png" alt="" /></p>
<p>Los <em>embeddings</em> seguirán mejorando mientras iteramos sobre todo nuestro <em>dataset</em>. En cualquier momento podemos detener el proceso de entrenamiento, descartar la matriz <span style="color: #9c27b0;">Contexto</span> y usar los <em>embeddings</em> en la matriz <span style="color: #4caf50;">Embedding</span> para cualquier otra tarea.</p>
<h2 id="tamaño-de-ventana-y-número-de-ejemplos-negativos">Tamaño de ventana y número de ejemplos negativos</h2>
<p>Hay dos híper parámetros claves en el proceso de entrenamiento de <em>word2vec</em>, estos son el tamaño de la ventana y el número de ejemplos negativos.</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-window-size.png" alt="" /></p>
<p>Diferentes tareas se benefician de diferentes tamaños de ventana. Una <a href="https://youtu.be/tAxrlAVw-Tk?t=648">heurística</a> que podemos emplear es que tamaños de ventana pequeños (2-15) llevan a <em>embeddings</em> donde altos valores de similitud indican que las palabras son intercambiables (toma en cuenta que los antónimos suelen ser intercambiables si solo nos fijamos en las palabras que los rodean – por ejemplo, “bueno” y “malo” suelen aparecer en contextos similares). Los tamaños de ventana más grandes nos llevana a <em>embeddings</em> en donde la similitud es más bien una medida del nivel de relación entre dos palabras. En la práctica, probablemente tengas que proveer <a href="https://youtu.be/ao52o9l6KGw?t=287">anotaciones</a> que guíen el proceso de generación de <em>embeddings</em> y entreguen un sentido mejor de similitud. El tamaño de ventana por default en <em>Gensim</em> es 5 (dos palabras antes y dos palabras después de la palabra de entrada).</p>
<p><img src="http://jalammar.github.io/images/word2vec/word2vec-negative-samples.png" alt="" /></p>
<p>El número de ejemplos negativos es otro factor a considerar en el proceso de entrenamiento, el <em>paper</em> original recomienda de 5 a 20 como un buen número de ejemplos negativos. También menciona que de 2 a 5 suele ser suficiente cuando se tiene un <em>dataset</em> de buen tamaño. El default en Gensim es 5 ejemplos negativos.</p>
<h2 id="conclusión">Conclusión</h2>
<blockquote>
<p>“Si sale de tu dominio, entonces estás interactuando con inteligencia, no con automatización.” ~ Dios Emperador de Dune</p>
</blockquote>
<p>Espero que ahora ya comprendas qué son los <em>embeddings</em> y el algoritmo detrás de <em>word2vec</em>. También espero que cuando leas un artículo científico mencionando <em>“skipgram with negative sampling”</em> (como los que mencioné al principio), entiendas lo que significan estos conceptos. Como siempre, cualquier comentario es bien apreciado <a href="https://twitter.com/JayAlammar">@JayAlammar</a></p>
<h2 id="referencias-y-recursos-para-seguir-leyendo">Referencias y recursos para seguir leyendo</h2>
<ul>
<li><a href="https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf">Distributed Representations of Words and Phrases and their Compositionality</a> [pdf]</li>
<li><a href="https://arxiv.org/pdf/1301.3781.pdf">Efficient Estimation of Word Representations in Vector Space</a> [pdf]</li>
<li><a href="http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf">A Neural Probabilistic Language Model</a> [pdf]</li>
<li><a href="https://web.stanford.edu/~jurafsky/slp3/">Speech and Language Processing</a> by Dan Jurafsky and James H. Martin is a leading resource for NLP. Word2vec is tackled in Chapter 6.</li>
<li><a href="https://www.amazon.com/Language-Processing-Synthesis-Lectures-Technologies/dp/1627052984">Neural Network Methods in Natural Language Processing</a> by <a href="https://twitter.com/yoavgo">Yoav Goldberg</a> is a great read for neural NLP topics.</li>
<li><a href="http://mccormickml.com/">Chris McCormick</a> has written some great blog posts about Word2vec. He also just released <a href="https://www.preview.nearist.ai/paid-ebook-and-tutorial">The Inner Workings of word2vec</a>, an E-book focused on the internals of word2vec.</li>
<li>Want to read the code? Here are two options:
<ul>
<li>Gensim’s <a href="https://github.com/RaRe-Technologies/gensim/blob/develop/gensim/models/word2vec.py">python implementation</a> of word2vec</li>
<li>Mikolov’s original <a href="https://github.com/tmikolov/word2vec/blob/master/word2vec.c">implementation in C</a> – better yet, this <a href="https://github.com/chrisjmccormick/word2vec_commented/blob/master/word2vec.c">version with detailed comments</a> from Chris McCormick.</li>
</ul>
</li>
<li><a href="http://sro.sussex.ac.uk/id/eprint/61062/1/Batchkarov,%20Miroslav%20Manov.pdf">Evaluating distributional models of compositional semantics</a></li>
<li><a href="http://ruder.io/word-embeddings-1/index.html">On word embeddings</a>, <a href="http://ruder.io/word-embeddings-softmax/">part 2</a></li>
<li><a href="https://www.amazon.com/Dune-Frank-Herbert/dp/0441172717/">Dune</a></li>
</ul>
<p><small>Alammar, Jay (2019). The Illustrated Word2vec [Blog post]. Retrieved from <a href="http://jalammar.github.io/illustrated-word2vec/">http://jalammar.github.io/illustrated-word2vec/</a> </small></p>
<hr />
<p>Sin más les recuerdo que como siempre, quedo atento a sus dudas y comentarios en mi cuenta de Twitter <a href="https://twitter.com/io_exception">@io_exception</a>, en donde me pueden con contactar para hablar sobre ciencia de datos, ingeniería de software y muchas cosas más.</p></content><author><name>Antonio Feregrino Bolaños</name></author><category term="python" /><category term="skelarn" /><category term="texto" /><category term="word2vec" /><summary type="html">En esta ocasión les quiero hablar de otra forma de convertir texto a vectores, esta es distinta a las que hemos visto previamente ya que nos da como resultado un vector por cada token y cada uno de estos vectores es un vector denso.</summary></entry><entry><title type="html">De texto a vectores (parte 1)</title><link href="https://old.tacosdedatos.com/texto-vectores" rel="alternate" type="text/html" title="De texto a vectores (parte 1)" /><published>2020-08-24T10:00:00+00:00</published><updated>2020-08-24T10:00:00+00:00</updated><id>https://old.tacosdedatos.com/texto-vectores</id><content type="html" xml:base="https://old.tacosdedatos.com/texto-vectores"><p>Tener nuestro texto convertido en tokens es un paso importante en el uso de texto para aplicaciones de machine learning, existe una transformación que debemos realizar para facilitarle la tarea a nuestros modelos. Esta transformación es conocida como vectorización.</p>
<p>En el <a href="https://tacosdedatos.com/analisis-texto">post pasado</a> nos quedamos con nuestro texto ya tokenizado, sin embargo los modelos de machine learning operan con valores numéricos organizados en arreglos llamados vectores, y hasta el momento nuestros tokens son solo secuencias de caracteres. Nuestra tarea, y de lo que les voy a hablar en el siguiente post, es convertir esta secuencias a vectores.</p>
<h2 id="un-poco-de-nomenclatura">Un poco de nomenclatura</h2>
<p>Cuando se habla de texto en el contexto de la ciencia de datos hay algunas palabras que debemos entender ya que son comúnmente usadas:</p>
<ul>
<li><strong>Token</strong>, conjunto de caracteres que representa la mínima unidad en el análisis de texto.</li>
<li><strong>Documento</strong>, la representación escrita de una idea, concepto o diálogo, un documento está compuesto por varios tokens. Como ejemplos de documentos podemos tener un <em>tweet</em>, un diálogo en una película o un <em>paper</em> de una publicación científica.</li>
<li><strong>Corpus</strong>, todo el conjunto de documentos sobre el que estamos realizando el análisis.</li>
<li><strong>Vocabulario</strong>, el conjunto de tokens únicos que obtenemos como resultado al tokenizar nuestro <em>corpus</em> completo.</li>
</ul>
<p>Por ejemplo, debajo tenemos tres documentos, que en conjunto forman nuestro corpus.</p>
<blockquote>
<p>Fui a comer tacos de suadero. Juro que es el suadero más delicioso de mi vida. #suadero</p>
</blockquote>
<blockquote>
<p>Taco de delicioso suadero con bolsa de plástico para que no ensucie el plato.</p>
</blockquote>
<blockquote>
<p>Tengo ganas de comprarme unos tacos de fútbol, ir a la cancha y jugar hasta la noche</p>
</blockquote>
<p>Luego, una vez tokenizado con la función que desarrollamos en el post anterior, nos queda el siguiente vocabulario: <strong>bolsa</strong>, <strong>cancha</strong>, <strong>comer</strong>, <strong>comprarme</strong>, <strong>delicioso</strong>, <strong>ensuciar</strong>, <strong>fútbol</strong>, <strong>ganar</strong>, <strong>jugar</strong>, <strong>juro</strong>, <strong>noche</strong>, <strong>plato</strong>, <strong>plástico</strong>, <strong>suadero</strong>, <strong>taco</strong> y <strong>vida</strong>.</p>
<p>Como referencia, aquí está la función tokenizadora que llamamos <code class="language-plaintext highlighter-rouge">tokenize_phrase</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">tokenize_phrase</span><span class="p">(</span><span class="n">phrase</span><span class="p">):</span>
<span class="n">parsed_phrase</span> <span class="o">=</span> <span class="n">nlp</span><span class="p">(</span><span class="n">phrase</span><span class="p">)</span>
<span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">parsed_phrase</span><span class="p">:</span>
<span class="k">if</span> <span class="n">token</span><span class="p">.</span><span class="n">is_punct</span> <span class="ow">or</span> <span class="n">token</span><span class="p">.</span><span class="n">is_stop</span> <span class="ow">or</span> <span class="n">token</span><span class="p">.</span><span class="n">text</span><span class="p">.</span><span class="n">lower</span><span class="p">()</span> <span class="ow">in</span> <span class="n">spanish_stopwords</span><span class="p">:</span>
<span class="k">continue</span>
<span class="k">yield</span> <span class="n">token</span><span class="p">.</span><span class="n">lemma_</span><span class="p">.</span><span class="n">lower</span><span class="p">()</span>
</code></pre></div></div>
<blockquote>
<p><strong>SciKit-Learn</strong>: Si no estás familiarizado con la interfaz de SciKit-Learn te invito a <a href="https://www.youtube.com/watch?v=XgXPxrEg0rA">ver mi video sobre el tema</a>. En resumen, <em>SciKit-Learn</em> tiene unas clases que se conocen como <em>transformers</em>, estos, como el nombre nos indica, son usados para transformar datos entre dos dominios. Los transformadores que vamos a usar poseen dos métodos que estamos usando: <code class="language-plaintext highlighter-rouge">fit</code>, que nos ayuda preparar nuestro vectorizador, <code class="language-plaintext highlighter-rouge">transform</code> que nos ayuda a convertir nuestros datos a vectores.</p>
</blockquote>
<h2 id="bolsa-de-palabras-bag-of-words">Bolsa de palabras (<em>Bag of words</em>)</h2>
<p>Una de las primeras ideas que se nos puede venir a la mente es la de generar una pequeña tabla, en donde cada documento es un renglón y cada token es una columna:</p>
<p><img src="assets/detrasdelavis/004_empty.png" alt="" /></p>
<blockquote>
<p><strong><code class="language-plaintext highlighter-rouge">CountVectorizer</code> y <code class="language-plaintext highlighter-rouge">analyzer</code></strong>: Al especificar el argumento <code class="language-plaintext highlighter-rouge">analyzer</code> y asignarlo a nuestra función para tokenizar (<code class="language-plaintext highlighter-rouge">tokenize_phrase</code>) le estamos indicando a nuestro transformador que cuando sea el momento de tokenizar nuestros documentos, use esa función en lugar del tokenizador por default que viene en <code class="language-plaintext highlighter-rouge">sklearn</code>.</p>
</blockquote>
<h3 id="vectores-de-frecuencia">Vectores de frecuencia</h3>
<p>Para rellenar la tabla anterior tenemos varias opciones, por ejemplo, podríamos simplemente la cantidad de veces que un determinado token aparece en cada uno de los documentos:</p>
<p><img src="assets/detrasdelavis/004_frequency.png" alt="" /></p>
<p>Como, en nuestro caso, la palabra “suadero” aparece tres veces en el primer documento.</p>
<p>Para lograr esto, vamos a hacer uso de <em>SciKit-Learn</em>, que si bien no es un framework dedicado a trabajar con texto, tiene bastantes utilidades que nos permiten hacerlo de manera sencilla y reproducible.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">CountVectorizer</span>
<span class="n">frequency_vectorizer</span> <span class="o">=</span> <span class="n">CountVectorizer</span><span class="p">(</span><span class="n">analyzer</span><span class="o">=</span><span class="n">tokenize_phrase</span><span class="p">)</span>
<span class="n">frequency_vectorizer</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
<span class="n">frequency_vectors</span> <span class="o">=</span> <span class="n">frequency_vectorizer</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
</code></pre></div></div>
<h3 id="one-hot-encoding-bag-of-words">One-hot-encoding (<em>Bag of words</em>)</h3>
<p>Otra opción que tenemos, si es que solamente queremos saber si una palabra existe en un documento o no, es la de usar la codificación <em>one-hot</em>, que simplemente consiste en colocar verdadero o falso (<code class="language-plaintext highlighter-rouge">1</code> o <code class="language-plaintext highlighter-rouge">0</code>) dependiendo de si la palabra existe o no:</p>
<p><img src="assets/detrasdelavis/004_onehot.png" alt="" /></p>
<p>Como, en nuestro caso, a pesar de que la palabra “suadero” aparece tres veces en el primer documento, solamente hay un uno en la columna correspondiente. Para lograr esto con Python, el siguiente código es útil:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">CountVectorizer</span>
<span class="n">one_hot_vectorizer</span> <span class="o">=</span> <span class="n">CountVectorizer</span><span class="p">(</span><span class="n">analyzer</span><span class="o">=</span><span class="n">tokenize_phrase</span><span class="p">,</span> <span class="n">binary</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">one_hot_vectorizer</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
<span class="n">one_hot_vectors</span> <span class="o">=</span> <span class="n">one_hot_vectorizer</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
</code></pre></div></div>
<blockquote>
<p><strong><code class="language-plaintext highlighter-rouge">CountVectorizer</code> y <code class="language-plaintext highlighter-rouge">binary</code></strong>: A pesar de usar la misma clase <code class="language-plaintext highlighter-rouge">CountVectorizer</code>, el especificar el argumento <code class="language-plaintext highlighter-rouge">binary = True</code> le indica a nuestro <em>transformer</em> que simplemente queremos saber si un token existe en un documento, no cuántas veces aparece.</p>
</blockquote>
<h3 id="tf-idf">TF-IDF</h3>
<p>Tanto el vector de frecuencias como el binario tratan cada documento de manera individual. Lo cual es bueno hasta cierto punto, sin embargo, existe una transformación que toma en cuenta la influencia de todo el <em>corpus</em> en cada documento.</p>
<p>La idea detrás del famoso TF-IDF (Term Frequency – Inverse Document Frequency), o por su nombre en español: <em>frecuencia de término – frecuencia inversa de documento</em>, El valor de cada token aumenta proporcionalmente al número de veces que una palabra aparece en el documento, pero es compensada por la frecuencia de la palabra en la colección de documentos, lo que permite manejar el hecho de que algunas palabras son generalmente más comunes que otras.</p>
<h3 id="detalles-matemáticos">Detalles matemáticos…</h3>
<p>Existen diversas maneras de calcular este número <a href="https://es.wikipedia.org/wiki/Tf-idf#Detalles_matem%C3%A1ticos">la wikipedia lista varias</a>, pero esta es una de las más comunes:</p>
<p>Partiendo de que</p>
<ul>
<li>\(t\) es el token para el cual estamos calculando,</li>
<li>\(d\) es el documento de interés y</li>
<li>\(D\) es nuestro corpus</li>
</ul>
<p>Obtenemos el valor de acuerdo a la siguiente formula</p>
\[tfidf(t, d, D) = \color{blue}{tf(t, d)} \times \color{red}{idf(t, D)}\]
<ul>
<li>
<p>\(\color{blue}{tf(t, d) = f_{t,d}}\) es la cantidad de veces que el token \(t\) aparece en el documento \(d\).</p>
</li>
<li>
<p>\(\color{red}{idf(t, D) = \ln{\frac{\vert D \vert + 1}{\vert\{d \in D: t \in d\}\vert + 1} + 1}}\) en donde \(\vert D \vert\) es la cantidad de documentos en nuestro corpus y \(\vert \{d \in D: t \in d\} \vert\) es la cantidad de documentos en la que aparece el token \(t\). Los \(+ 1\) que se encuentran ahí son conocidos como “suavizado” que nos ayudan a evitar divisiones entre \(0\).</p>
</li>
</ul>
<p>Toma por ejemplo, el token <em>suadero</em> en el primer documento (denotado como \(documentos_1\)):</p>
\[tfidf(“suadero”, documentos_1, corpus) = \color{blue}{tf(“suadero”, documentos_1)} \times \color{red}{idf(“suadero”, corpus)}\]
\[tfidf(“suadero”, documentos_1, corpus) = \color{blue}{3} \times \color{red}{\ln{\frac{3 + 1}{2 + 1} + 1}}\]
\[tfidf(“suadero”, documentos_1, corpus) = \color{blue}{3} \times \color{red}{\ln{\frac{4}{3}+1}}\]
\[tfidf(“suadero”, documentos_1, corpus) = \color{blue}{3} \times \color{red}{\ln{2.333}}\]
\[tfidf(“suadero”, documentos_1, corpus) = 3.8630\]
<p>Como se puede observar en la siguiente tabla:</p>
<p><img src="assets/detrasdelavis/004_tfidf_no_norm.png" alt="" /></p>
<p>Usualmente los algoritmos lineares de <em>machine learning</em> otorgan mejores resultados cuando nuestras variables de entrada están normalizadas, así que se recomienda que nosotros hagamos lo mismo.</p>
<p>Para nuestra suerte, dentro de <code class="language-plaintext highlighter-rouge">sklearn</code> también existe un <em>transformer</em> que nos permite convertir nuestros vectores a TF-IDF (incluyendo la parta de la normalización):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sklearn.feature_extraction.text</span> <span class="kn">import</span> <span class="n">TfidfVectorizer</span>
<span class="n">tfidf_vectorizer</span> <span class="o">=</span> <span class="n">TfidfVectorizer</span><span class="p">(</span><span class="n">analyzer</span><span class="o">=</span><span class="n">tokenize_phrase</span><span class="p">)</span>
<span class="n">tfidf_vectorizer</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
<span class="n">tfidf_vectors</span> <span class="o">=</span> <span class="n">tfidf_vectorizer</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">corpus</span><span class="p">)</span>
</code></pre></div></div>
<p>Que nos dará como resultado una matriz más o menos así:</p>
<p><img src="assets/detrasdelavis/004_tfidf.png" alt="" /></p>
<h2 id="sobre-las-representaciones-vectoriales">Sobre las representaciones vectoriales…</h2>
<h3 id="representaciones-dispersas">Representaciones dispersas</h3>
<p>Si de algo nos dimos cuenta con las matrices anteriores, es que en muchas ocasiones, tenemos más <code class="language-plaintext highlighter-rouge">0</code> (valores en blanco) que valores reales. En otras palabras, nuestros documentos-vectores son dispersos, o en ingles <em>sparse vectors</em>. Esto podría llegar a ser un problema cuando tenemos un vocabulario de tamaño considerable.</p>
<h3 id="no-reflejan-el-orden-de-los-términos">No reflejan el orden de los términos</h3>
<p>Otra cosa a considerar es que con solo ver la representación vectorizada de determinado documento, no podemos reconstruir el documento original, de entrada por todo el proceso de tokenización, pero también porque <strong>ninguna de las técnicas que vimos, preserva el orden original de los tokens en el documento</strong>.</p>
<h3 id="existen-todavía-más-opciones">Existen todavía más opciones</h3>
<p>Si bien estas representaciones son buenas u útiles, hay casos en los que necesitamos obtener más detalle de nuestros documentos, para esos casos podemos usar técnicas como <a href="http://jalammar.github.io/illustrated-word2vec/">Word2vec</a> o <a href="https://medium.com/wisio/a-gentle-introduction-to-doc2vec-db3e8c0cce5e">Doc2vec</a>, de las cuales espero escribir en un post futuro.</p>
<h3 id="guarda-tus-vectorizadores">Guarda tus vectorizadores</h3>
<p>Sin importar qué método de transformación usaste, siempre debes usar el mismo para transformaciones subsecuentes. Por ejemplo, si usaste <code class="language-plaintext highlighter-rouge">fit</code> con un transformador para los datos de entrenamiento de un algoritmo de clasificación, debes usar el mismo transformador (ya entrenado) para obtener los vectores al momento de realizar predicciones en nuevos datos.</p>
<p>Mientras tanto, te invito a que me en Twitter en <a href="https://twitter.com/io_exception">@io_exception</a>, por allá podemos conversar sobre el tema.</p></content><author><name>Antonio Feregrino Bolaños</name></author><category term="python" /><category term="skelarn" /><category term="texto" /><summary type="html">Tener nuestro texto convertido en tokens es un paso importante en el uso de texto para aplicaciones de machine learning, existe una transformación que debemos realizar para facilitarle la tarea a nuestros modelos. Esta transformación es conocida como vectorización.</summary></entry><entry><title type="html">Introducción al análisis de texto</title><link href="https://old.tacosdedatos.com/analisis-texto" rel="alternate" type="text/html" title="Introducción al análisis de texto" /><published>2020-08-16T10:00:00+00:00</published><updated>2020-08-16T10:00:00+00:00</updated><id>https://old.tacosdedatos.com/analisis-texto</id><content type="html" xml:base="https://old.tacosdedatos.com/analisis-texto"><p>¿Alguna vez has querido analizar texto? en este post voy a tratar de explicar cuáles son los primeros pasos recomendados para comenzar cualquier proyecto que involucre texto y ciencia de datos.</p>
<h2 id="texto-como-datos">Texto como datos</h2>
<p>Seguramente ya habrás escuchado algunas veces, o tu mismo has dicho “Hey Google” o “Hey Siri”, o tal vez te le has echado un ojo a tu filtro de spam… pero ¿te has preguntado cómo es que funcionan estos sistemas? como ya te imaginarás, la mayoría son aplicaciones de aprendizaje automático (o <em>machine learning</em>) que son posibles gracias a muchos de los algoritmos tradicionales del aprendizaje máquina.</p>
<p>Sin embargo, no podemos nosotros simplemente agarrar un montón de texto y dárselo a un algoritmo y esperar a que haga su magia… antes de todo esto existe un proceso que les voy a describir a continuación.</p>
<h2 id="spacy"><em>spaCy</em></h2>
<p>Aquí es donde entra <em>spaCy</em>, que es un paquete de Python que podríamos comparar con una navaja suiza para el procesamiento de texto. Esta es una herramienta muy poderosa, y aquí solamente vamos a tocar apenas la superficie de lo que ofrece. Para comenzar, hay que instalarlo:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>spacy
</code></pre></div></div>
<p>Instalar <em>spaCy</em> es solo la primera parte del rompecabezas, puesto que su correcto funcionamiento depende de usar el modelo adecuado para la tarea (y el idioma) que vamos a realizar, aquí puedes encontrar una <a href="https://spacy.io/models">descripción de los modelos</a>, pero para este post, podemos usar el más simple <code class="language-plaintext highlighter-rouge">es_core_news_sm</code>, se descarga con estos comandos en la consola:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python <span class="nt">-m</span> spacy download es_core_news_sm
python <span class="nt">-m</span> spacy <span class="nb">link </span>es_core_news_sm es
</code></pre></div></div>
<p>Para acceder dentro de Python a todas las bondades de nuestra nueva herramienta, es necesario cargar el modelo, es convención cargar el modelo en una variable llamada <code class="language-plaintext highlighter-rouge">nlp</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">spacy</span>
<span class="n">nlp</span> <span class="o">=</span> <span class="n">spacy</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="s">"es"</span><span class="p">)</span>
</code></pre></div></div>
<p>Y listo, por el momento podemos seguir.</p>
<h2 id="tokenización">Tokenización</h2>
<p>El primer paso es segmentar nuestro texto en tokens. Un token es un conjunto de caracteres que representan texto. También podemos decir que el token es la únidad análisis de texto, así como un número es la unidad del análisis matemático. Es fácil para nosotros pensar que un token es igual a una palabra, sin embargo esto no es correcto, puesto que la “palabra” es un elemento del lenguaje que posee significado por sí misma, mientras que el token se supone es un elemento abstracto. Dependiendo de la tarea que estemos afrontando, el token puede ser alguna de las siguientes:</p>
<ul>
<li>Una sola palabra, como: “jóvenes”, “nivel” o “superior”,</li>
<li>Un número, como: “1”, “0”, o “10”,</li>
<li>Un solo caracter, como: “j”, “ó” o “v”,</li>
<li>Un símbolo, como “¿”, “?” o “#”,</li>
<li>Un conjunto de caracteres, como “nivel superior” o “escuela técnica”</li>
</ul>
<p>La forma de elegir los tokens en nuestro texto va a depender muchísimo del problema que estemos afrontando, habrá ocasiones en las que una simple tokenización, como la de dividir nuestro texto por los espacios, bastará:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">text</span> <span class="o">=</span> <span class="s">"Seguramente ya habrás escuchado algunas veces, o tu mismo has dicho </span><span class="se">\"</span><span class="s">Hey Google</span><span class="se">\"</span><span class="s"> o </span><span class="se">\"</span><span class="s">Hey Siri</span><span class="se">\"</span><span class="s">, o tal vez le has echado un ojo a tu filtro de spam..."</span>
<span class="n">simple_tokens</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">" "</span><span class="p">)</span>
<span class="k">print</span><span class="p">([</span><span class="n">token</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">simple_tokens</span><span class="p">])</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>['Seguramente', 'ya', 'habrás', 'escuchado', 'algunas', 'veces,', 'o', 'tu', 'mismo', 'has', 'dicho', '"Hey', 'Google"', 'o', '"Hey', 'Siri",', 'o', 'tal', 'vez', 'le', 'has', 'echado', 'un', 'ojo', 'a', 'tu', 'filtro', 'de', 'spam...']
</code></pre></div></div>
<p>Pero habrá otras ocasiones en las que tengamos que echar mano de otras formas de tokenizar el texto, por ejemplo en el paquete <em>spaCy</em> podemos echar mano de algunas herramientas.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">document</span> <span class="o">=</span> <span class="n">nlp</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="k">print</span><span class="p">([</span><span class="n">token</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">document</span><span class="p">])</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Seguramente, ya, habrás, escuchado, algunas, veces, ,, o, tu, mismo, has, dicho, ", Hey, Google, ", o, ", Hey, Siri, ", ,, o, tal, vez, le, has, echado, un, ojo, a, tu, filtro, de, spam, ...]
</code></pre></div></div>
<p>La gran diferencia es que en la segunda tokenización los símbolos de puntuación están separadas de las palabras en el texto.</p>
<blockquote>
<p><em>spaCy</em>: Cuando llamamos <code class="language-plaintext highlighter-rouge">nlp("algún texto")</code> obtenemos como retorno un valor del tipo <code class="language-plaintext highlighter-rouge">Document</code>, que, a su vez está compuesto de valores del tipo <code class="language-plaintext highlighter-rouge">Token</code>, es por eso que podemos iterar nuestro documento con un ciclo <em>for</em>.</p>
</blockquote>
<h2 id="reducción-de-tokens">Reducción de tokens</h2>
<h3 id="minúsculas-mayúsculas">¿Minúsculas? ¿mayúsculas?</h3>
<p>Hasta este punto todo bien con nuestros tokens, pero piensa en una oración como:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">oracion</span> <span class="o">=</span> <span class="s">"El ciclo escolar comienza en las escuelas. Escuelas de todo el país comenzarán clases este lunes."</span>
</code></pre></div></div>
<p>Los tokens únicos de esta oración son:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">oracion_parsed</span> <span class="o">=</span> <span class="n">nlp</span><span class="p">(</span><span class="n">oracion</span><span class="p">)</span>
<span class="nb">set</span><span class="p">((</span><span class="n">token</span><span class="p">.</span><span class="n">text</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion</span> <span class="p">))</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{'clases', 'El', 'las', 'en', 'todo', 'comenzarán', 'este', 'ciclo', 'escolar', 'comienza', 'escuelas', '.', 'el', 'lunes', 'Escuelas', 'país', 'de'}
</code></pre></div></div>
<p>Si te das cuenta, “<em>Escuelas</em>” y “<em>escuelas</em>” son dos tokens distintos, (recuerda, los tokens no son palabras); sin embargo, para muchas aplicaciones, estos dos tokens pueden, simple y sencillamente ser tratados como el mismo, simplemente con transformar todos nuestros tokens a minúsculas. Usamos la propiedad <code class="language-plaintext highlighter-rouge">lower_</code> de <code class="language-plaintext highlighter-rouge">Token</code> para acceder a la versión en minúscula de la palabra:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">set</span><span class="p">((</span><span class="n">token</span><span class="p">.</span><span class="n">lower_</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion_parsed</span><span class="p">))</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{'país', 'lunes', 'comenzarán', 'en', '.', 'el', 'comienza', 'de', 'ciclo', 'las', 'escolar', 'clases', 'este', 'todo', 'escuelas'}
</code></pre></div></div>
<blockquote>
<p><em>spaCy</em>: como ya sabemos, en nuestro ejemplo de código anterior, cuando iteramos sobre <code class="language-plaintext highlighter-rouge">oracion_parsed</code>, obtenemos uno a uno los tokens que forman nuestro documento original. Cada uno de estos elementos de la clase <code class="language-plaintext highlighter-rouge">Token</code> posee <a href="https://spacy.io/api/token">muchas propiedades</a>, de entre ellas <code class="language-plaintext highlighter-rouge">lower_</code> es una.</p>
</blockquote>
<h3 id="stopwords-signos-de-puntuación">¿<em>Stopwords</em>? ¿Signos de puntuación?</h3>
<p>Como resultado de la tokenización, en el ejemplo anterior vemos que existen muchos <em>tokens</em> que, dependiendo del análisis que vayamos a realizar, podemos considerar “irrelevantes” para nuestro análisis, por ejemplo, las palabras “en”, “este”, “el” y “las” ya que son muy comunes en el español y están destinadas a aparecer en todas las oraciones, sin importar el tema al que estas hagan referencia. Estas palabras en inglés son conocidas como “<em>stopwords</em>”. Para encontrarlas con <em>spaCy</em> podemos usar la propiedad <code class="language-plaintext highlighter-rouge">is_stop</code> de cada token:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">token</span><span class="p">.</span><span class="n">lower_</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">token</span><span class="p">.</span><span class="n">is_stop</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion_parsed</span><span class="p">]</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>['el: True', 'ciclo: False', 'escolar: False', 'comienza: False', 'en: True', 'las: True', 'escuelas: False', '.: False', 'escuelas: False', 'de: True', 'todo: True', 'el: True', 'país: False', 'comenzarán: False', 'clases: False', 'este: True', 'lunes: False', '.: False']
</code></pre></div></div>
<p>Al igual que las stopwords puede ser que los símbolos carezcan de relevancia en nuestro análisis, nuevamente, por ser considerados como una ocurrencia común en el español. Para encontrarlos, podemos hacer uso de la propiedad <code class="language-plaintext highlighter-rouge">is_punct</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">token</span><span class="p">.</span><span class="n">lower_</span><span class="si">}</span><span class="s">: </span><span class="si">{</span><span class="n">token</span><span class="p">.</span><span class="n">is_punct</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion_parsed</span><span class="p">]</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>['el: False', 'ciclo: False', 'escolar: False', 'comienza: False', 'en: False', 'las: False', 'escuelas: False', '.: True', 'escuelas: False', 'de: False', 'todo: False', 'el: False', 'país: False', 'comenzarán: False', 'clases: False', 'este: False', 'lunes: False', '.: True']
</code></pre></div></div>
<h3 id="lematización">Lematización</h3>
<p>Otro procedimiento que podemos usar para reducir la cantidad de tokens únicos es el proceso de lematización, que es un proceso lingüístico que consiste en, dada una forma flexionada, hallar el lema correspondiente. El lema es la forma que por convenio se acepta como representante de todas las formas flexionadas de una misma palabra… pero creo que es mejor con algunos ejemplos:</p>
<ul>
<li>comienza -&gt; comenzar</li>
<li>comenzarán -&gt; comenzar</li>
<li>clases -&gt; clase</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">set</span><span class="p">((</span><span class="n">token</span><span class="p">.</span><span class="n">lemma_</span><span class="p">.</span><span class="n">lower</span><span class="p">()</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion</span><span class="p">))</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{'país', 'lunes', 'en', '.', 'el', 'los', 'de', 'escolar', 'clase', 'este', 'ciclar', 'comenzar', 'escuela', 'todo', 'escuelas'}
</code></pre></div></div>
<h3 id="stemming---acortamiento-">Stemming - Acortamiento (?)</h3>
<p>Existe otra forma de reducir el número de tokens. Y es conocida como <em>stemming</em> en inglés, en español yo lo traduciría como “acortamiento” o “poda (de podar el césped)”. Este proceso consiste en simple y llanamente recortar las palabras para reduciras a una base común:</p>
<ul>
<li>comienza -&gt; comienz</li>
<li>comenzarán -&gt; comenz</li>
<li>clases -&gt; clas</li>
</ul>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">nltk.stem.snowball</span> <span class="kn">import</span> <span class="n">SnowballStemmer</span>
<span class="n">stemmer</span> <span class="o">=</span> <span class="n">SnowballStemmer</span><span class="p">(</span><span class="n">language</span><span class="o">=</span><span class="s">'spanish'</span><span class="p">)</span>
<span class="nb">set</span><span class="p">((</span><span class="n">stemmer</span><span class="p">.</span><span class="n">stem</span><span class="p">(</span><span class="n">token</span><span class="p">.</span><span class="n">text</span><span class="p">)</span> <span class="k">for</span> <span class="n">token</span> <span class="ow">in</span> <span class="n">oracion</span><span class="p">)))</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{'escol', 'en', 'comienz', 'el', '.', 'de', 'comenz', 'las', 'lun', 'pais', 'este', 'clas', 'tod', 'cicl', 'escuel'}
</code></pre></div></div>
<blockquote>
<p><em>spaCy</em>: por increíble que parezca, esta poderosa librería no cuenta con la opción de ejecutar <em>stemming</em> por default, las <a href="https://github.com/explosion/spaCy/issues/327#issuecomment-208658745">razones son varias</a> y trataré de hablar sobre ellas más adelante, pero por ahora, si quieres realizar <em>stemming</em>, el paquete NLTK es tu aliado.</p>
</blockquote>
<h3 id="preguntas-más-comunes">Preguntas más comunes</h3>
<h4 id="lematización-o-stemming">¿Lematización o <em>stemming</em>?</h4>
<p>Estas dos técnicas son consideradas mutuamente exclusivas, puesto que o aplicas una o aplicas la otra, nunca las dos. Pero, ¿cuál es la más recomendada?</p>
<p>En general siempre se prefiere la lematización, puesto que es un buen compromiso entre reducir la cantidad de tokens y preservar un poco más la composición original de estos. El <em>stemming</em> al ser más agresivo tiende a conllevar una pérdida de información más grande.</p>
<h4 id="cuál-es-el-orden-en-que-se-aplican-los-pasos">¿Cuál es el orden en que se aplican los pasos?</h4>
<p>Es común que después de tokenizar el texto, los pasos se apliquen en el orden presentado:</p>
<ol>
<li>Conversión a minúsculas</li>
<li>Eliminación de <em>stopwords</em> y símbolos de puntuación</li>
<li>Lematización o <em>stemming</em></li>
</ol>