Διαδρομές
/
Julia
Julia
/
Ύλη
/
Τυχαιότητα
Τυ

Τυχαιότητα σε Julia

2 ασκήσεις

Σχετικά με την έννοια Τυχαιότητα

Πολλά προγράμματα χρειάζονται (φαινομενικά) τυχαίες τιμές για να προσομοιώσουν γεγονότα του πραγματικού κόσμου.

Συνήθη και οικεία παραδείγματα περιλαμβάνουν:

  • Ρίψη κέρματος: μια τυχαία τιμή από ('H', 'T').
  • Ρίψη ζαριού: έναν τυχαίο ακέραιο από το 1 έως το 6.
  • Ανακάτεμα μιας τράπουλας: μια τυχαία διάταξη μιας λίστας καρτών.

Η παραγωγή πραγματικά τυχαίων τιμών με έναν υπολογιστή είναι μια προκλητικά δύσκολη τεχνική πρόκληση, γι' αυτό μπορεί να δεις αυτά τα αποτελέσματα να αναφέρονται ως "ψευδοτυχαία".

Σημαντικό: Αυτή η Έννοια δεν καλύπτει τους κρυπτογραφικά ασφαλείς τυχαίους αριθμούς, οι οποίοι αποτελούν μια πολύ πιο δύσκολη πρόκληση.

Ωστόσο, καλά σχεδιασμένες βιβλιοθήκες όπως η ενότητα Random στην τυπική βιβλιοθήκη της Julia είναι γρήγορες, ευέλικτες και δίνουν αποτελέσματα που είναι υπεραρκετά καλά για τις περισσότερες εφαρμογές στη μοντελοποίηση, την προσομοίωση και τα παιχνίδια.

Η Julia χωρίζει τη λειτουργικότητα των τυχαίων αριθμών σε πολλά σημεία:

  • Μόνο λίγες βασικές αλλά πολύ ευέλικτες συναρτήσεις στο Base, οι οποίες είναι πάντα διαθέσιμες.
  • Μια ευρύτερη γκάμα επιλογών στην ενότητα Random.
  • Πιο εξειδικευμένη λειτουργικότητα σε πακέτα που πρέπει να εγκατασταθούν πριν από τη χρήση (και δεν είναι διαθέσιμα στο Exercism).

Η Random είναι μέρος της τυπικής βιβλιοθήκης και πιθανότατα είναι προεγκατεστημένη, αλλά θα χρειαστεί να προσθέσεις using Random στην αρχή του προγράμματός σου για να φέρεις τα περιεχόμενά της στον χώρο ονομάτων.

Η συνάρτηση rand()

Το τι κάνει αυτή η συνάρτηση εξαρτάται από τα ορίσματα που της δίνεις. Υπάρχουν πολλές επιλογές.

Χωρίς ορίσματα, παράγει έναν αριθμό κινητής υποδιαστολής μεταξύ του 0 (συμπεριλαμβανομένου) και του 1. Αυτή είναι μια κατανομή uniform όπου όλες οι τιμές είναι εξίσου πιθανές, όπως εξηγείται στην ενότητα Εργασία με κατανομές, παρακάτω.

Ένα μόνο ακέραιο όρισμα παράγει ένα διάνυσμα αυτού του μήκους.

julia> rand()
0.10261774967264703

julia> rand(5)
5-element Vector{Float64}:
 0.24134501977563894
 0.5664193284851202
 0.9804412082089355
 0.6229551330613335
 0.47589221741904664

Για ένα διαφορετικό εύρος, απλώς μετατόπισε και κλιμάκωσε ανάλογα το αποτέλεσμα.

Το παρακάτω παράδειγμα χρησιμοποιεί μετάδοση για την αφαίρεση, η οποία καλύπτεται στην Έννοια Vector Operations. Το .- απλώς εφαρμόζει αυτή την αριθμητική σε κάθε στοιχείο του διανύσματος.

# numbers between -1.0 and +1.0
julia> (rand(5) .- 0.5) * 2
5-element Vector{Float64}:
 -0.5303906759076336
  0.9635682226775855
 -0.048823697086981754
  0.465842804648374
  0.9880834344780736

Με έναν τύπο ως μοναδικό όρισμα, η rand θα χρησιμοποιήσει τα typemin και typemax ως όρια. Αυτό μάλλον δεν είναι αυτό που θέλεις!

Για τυχαίους ακεραίους, μπορούμε να δώσουμε ένα εύρος και προαιρετικά πόσες τιμές να παραχθούν.

julia> rand(Int64)
-9159538335234594326 # not very useful

julia> rand(1:10, 5)
5-element Vector{Int64}:
 1
 1
 1
 4
 7

Στο παραπάνω παράδειγμα rand(1:10, 5), πρόσεξε ότι υπάρχουν (τυχαία) επαναλαμβανόμενες τιμές, επειδή κάθε επιλογή είναι ανεξάρτητη. Αυτή είναι η "δειγματοληψία με επανατοποθέτηση", που εξηγείται αναλυτικότερα παρακάτω.

Για τιμές κινητής υποδιαστολής μέσα σε ένα εύρος, συνήθως θα χρειαστεί να ορίσεις ένα βήμα. Διαφορετικά, το βήμα θα είναι 1.0 από προεπιλογή, κάτι που σπάνια είναι χρήσιμο.

julia> rand(2.4:0.01:3.2, 4)
4-element Vector{Float64}:
 3.19
 2.53
 3.14
 3.13

Εναλλακτικά, δώσε έναν πίνακα ή μια πλειάδα και η rand θα επιστρέψει ένα τυχαίο στοιχείο:

julia> rand([4, 9, 16, 25])
16

# coin flip
julia> rand(['H', 'T'])
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

# mixed types in tuple
julia> rand( (1, 3.2, "name"), 2 )
2-element Vector{Any}:
 1
  "name"

Δειγματοληψία με ή χωρίς επανατοποθέτηση

Φαντάσου ότι έχουμε ένα σακούλι που περιέχει 3 κόκκινες μπάλες και 4 πράσινες μπάλες και τραβάμε τυχαία μια μπάλα από το σακούλι. Για να πάρουμε και δεύτερη μπάλα, υπάρχουν δύο δυνατότητες:

  1. Βάζεις την πρώτη μπάλα πίσω στο σακούλι και το κουνάς καλά πριν τραβήξεις άλλη. Ο αριθμός των μπαλών είναι τώρα ίδιος όπως πριν (7) και η αναλογία κόκκινων προς πράσινες είναι επίσης ίδια.
  2. Αφήνεις την πρώτη μπάλα στο τραπέζι πριν τραβήξεις δεύτερη. Τώρα υπάρχουν μόνο 6 μπάλες στο σακούλι και η αναλογία κόκκινων:πράσινων εξαρτάται από το χρώμα της πρώτης μπάλας.

Το σενάριο 1 είναι με επανατοποθέτηση, το σενάριο 2 είναι χωρίς, και δίνουν διαφορετικά αποτελέσματα.

Για να προσομοιώσεις δειγματοληψία χωρίς επανατοποθέτηση στην Julia, υπάρχουν δύο επιλογές.

Η απλούστερη (και εντός του Exercism η μόνη επιλογή), χρησιμοποίησε τη Random.shuffle() για να βάλεις τα στοιχεία σε τυχαία σειρά και μετά πάρε τα πρώτα n στοιχεία. Αυτό είναι εντάξει για μικρά προβλήματα αλλά μπορεί να μην κλιμακώνεται καλά σε μεγάλες συλλογές: η shuffle χρειάζεται να δημιουργήσει ολόκληρο τον πίνακα, ακόμα κι αν θέλεις μόνο ένα μικρό κλάσμα του.

Για να κάνεις δειγματοληψία με επανατοποθέτηση "σωστά", εγκατέστησε το πακέτο StatsBase.jl. Αυτό παρέχει τη συνάρτηση sample() με μια πλήρη γκάμα επιλογών.

Μπορούμε εύλογα να ελπίζουμε ότι παρόμοια λειτουργικότητα θα προστεθεί στη Random σε κάποια μελλοντική έκδοση, ώστε να γίνει μέρος της τυπικής βιβλιοθήκης (τα δείγματα κώδικα σε αυτό το έγγραφο δοκιμάστηκαν με Julia 1.11).

Εργασία με κατανομές

Μέχρι τώρα, έχουμε επικεντρωθεί σε περιπτώσεις όπου όλα τα ενδεχόμενα είναι εξίσου πιθανά. Για παράδειγμα, η rand(1:100) έχει την ίδια πιθανότητα να δώσει οποιονδήποτε ακέραιο από το 1 έως το 100.

Πολλές καταστάσεις του πραγματικού κόσμου είναι πολύ λιγότερο απλές από αυτό. Ως αποτέλεσμα, οι στατιστικολόγοι έχουν δημιουργήσει μια μεγάλη ποικιλία από distributions για να περιγράψουν μαθηματικά τα αποτελέσματα του "πραγματικού κόσμου".

Ομοιόμορφες κατανομές

Η συνάρτηση rand() που περιγράφηκε παραπάνω χρησιμοποιείται όταν όλες οι πιθανότητες είναι ίσες. Αυτή ονομάζεται κατανομή [uniform][uniform-distribution].

Κατανομή Γκάους

Επίσης γνωστή ως "κανονική" κατανομή ή "κωδωνοειδής" καμπύλη, είναι ένας πολύ συνηθισμένος τρόπος να περιγράφουμε την ανακρίβεια σε μετρημένες τιμές.

Για παράδειγμα, υπόθεσε ότι το εργοστάσιο όπου δουλεύεις μόλις αγόρασε 10.000 μπουλόνια που υποτίθεται ότι είναι πανομοιότυπα. Θέλεις να ρυθμίσεις το ρομπότ του εργοστασίου ώστε να τα χειρίζεται, οπότε ζυγίζεις ένα δείγμα 100 τεμαχίων και βρίσκεις ότι έχουν μέσο (ή mean) βάρος 4,731g. Είναι εξαιρετικά απίθανο αυτό να σημαίνει ότι όλα ζυγίζουν ακριβώς 4,731g. Ίσως βρεις ότι οι τιμές κυμαίνονται από 4,627 έως 4,794g αλλά συγκεντρώνονται γύρω από τα 4,731g.

Αυτή είναι η Gaussian distribution, στην οποία οι πιθανότητες κορυφώνονται στη μέση τιμή και μειώνονται συμμετρικά και προς τις δύο πλευρές (εξ ου και "κωδωνοειδής"). Για να το προσομοιώσουμε σε λογισμικό, χρειαζόμαστε κάποιον τρόπο να προσδιορίσουμε το πλάτος της καμπύλης (συνήθως, τα ακριβά μπουλόνια συγκεντρώνονται πιο σφιχτά γύρω από τη μέση τιμή από τα φθηνά!).

Κατά σύμβαση, αυτό γίνεται με την standard deviation: μικρές τιμές για μια οξεία, στενή καμπύλη, μεγάλες για μια χαμηλή, πλατιά καμπύλη. Οι μαθηματικοί λατρεύουν τα ελληνικά γράμματα, γι' αυτό χρησιμοποιούμε το μ ('mu') για τη μέση τιμή και το σ ('sigma') για την τυπική απόκλιση. Έτσι, αν διαβάσεις ότι "το 95% των τιμών βρίσκονται εντός 2σ από το μ" ή ότι "το μποζόνιο Higgs ανιχνεύτηκε με βεβαιότητα 5 σίγμα", τέτοια σχόλια σχετίζονται με την τυπική απόκλιση.

Θα υπάρχουν περισσότερα να πούμε γι' αυτό στην Έννοια Statistics.

Η συνάρτηση randn()

Συντομογραφία του "random normal", μοιάζει με την παραλλαγή κινητής υποδιαστολής της rand(), με τη διαφορά ότι οι τιμές κατανέμονται ως Γκάους με μέση τιμή 0 και τυπική απόκλιση 1.

Και πάλι, μπορεί να θέλεις να κλιμακώσεις την ακατέργαστη έξοδο της randn για την τυπική απόκλιση και να τη μετατοπίσεις για τη μέση τιμή. Το παρακάτω παράδειγμα μετατρέπει σε μέση τιμή 30 και τυπική απόκλιση 5.

julia> raw = randn(5)
5-element Vector{Float64}:
  3.0762588867281475
  1.5101100620253902
 -0.5914858221637778
  0.684175554069735
 -0.8416433926114673

julia> raw * 5 .+ 30
5-element Vector{Float64}:
 45.38129443364074
 37.55055031012695
 27.04257088918111
 33.420877770348675
 25.791783036942665

Είναι δύσκολο να καταλάβεις κοιτάζοντας την έξοδο ότι η ακατέργαστη έξοδος συγκεντρώνεται πιο κοντά στο μηδέν απ' ό,τι σε μια ομοιόμορφη κατανομή. Αν αμφιβάλλεις, δημιούργησε 1000 ή περισσότερες και σχεδίασέ τις για να γίνει πιο προφανές.

Η ενότητα Random

Η ενότητα αυτή περιέχει το επόμενο επίπεδο λειτουργικότητας, το οποίο παραλείφθηκε από το Base για να βοηθήσει στην ελαχιστοποίηση του μεγέθους της προεπιλεγμένης διαμόρφωσης της Julia.

Η Random συμπληρώνει τις rand και randn του Base με παραλλαγές που μεταβάλλουν τα δεδομένα, τις rand! και randn!.

Μια χρήσιμη προσθήκη είναι η randstring, η οποία παράγει μια συμβολοσειρά δεδομένου μήκους. Από προεπιλογή, χρησιμοποιεί κεφαλαία και πεζά γράμματα καθώς και τα ψηφία 0 έως 9, αλλά μπορούν να οριστούν και άλλες επιλογές.

julia> using Random

julia> randstring(20)
"BoJnIxrS33pJiWggXZQV"

Επιπλέον, υπάρχει μια συνάρτηση bitrand για να παράγει έναν τυχαίο BitArray δεδομένου μήκους.

julia> bitrand(5)
julia> bitrand(5)
5-element BitVector:
 1
 1
 0
 0
 1

Ανακατέματα και μεταθέσεις

Για να ανακατέψουμε τυχαία τα στοιχεία ενός Vector, έχουμε τη shuffle; επίσης τη shuffle! για να μεταβάλει το διάνυσμα εισόδου επί τόπου.

julia> v = ['A', '1', '2', 'J', 'Q', 'K'];

julia> shuffle(v)
6-element Vector{Char}:
 'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
 '1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
 '2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
 'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)

# shuffles are random:
julia> shuffle(v)
6-element Vector{Char}:
 '2': ASCII/Unicode U+0032 (category Nd: Number, decimal digit)
 'K': ASCII/Unicode U+004B (category Lu: Letter, uppercase)
 'A': ASCII/Unicode U+0041 (category Lu: Letter, uppercase)
 'Q': ASCII/Unicode U+0051 (category Lu: Letter, uppercase)
 'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase)
 '1': ASCII/Unicode U+0031 (category Nd: Number, decimal digit)

Μερικές φορές είναι χρήσιμο να έχεις αντ' αυτού τις ανακατεμένες θέσεις. Για αυτό, χρησιμοποίησε τη randperm(n), όπου n είναι το μήκος της ακολουθίας.

julia> randperm(6)
6-element Vector{Int64}:
 6
 2
 4
 1
 3
 5

Στην πράξη, το παραπάνω παράδειγμα δίνει τα ίδια αποτελέσματα με το shuffle(1:6).

Σχετικές συναρτήσεις περιλαμβάνουν τη randsubseq για την εξαγωγή στοιχείων με σταθερή πιθανότητα και τη randcycle για κυκλικές μεταθέσεις. Αυτές απαιτούν κάποιες εξειδικευμένες γνώσεις, οπότε συμβουλέψου την τεκμηρίωση αν σε ενδιαφέρουν.

Σπόροι και αλγόριθμοι

Αρκετοί αλγόριθμοι γεννήτριας τυχαίων αριθμών (RNG) είναι ενσωματωμένοι στη Random ως στάνταρ, και οποιοσδήποτε έχει τις κατάλληλες μαθηματικές ικανότητες μπορεί να προσθέσει κι άλλους. Τέτοια πράγματα ξεφεύγουν πολύ από το πεδίο αυτού του εγγράφου!

Ένας πιο συνηθισμένος λόγος για να δουλεύεις με RNG είναι να ορίσεις ένα seed, το οποίο έχει ως αποτέλεσμα να γίνεται η ακολουθία των "τυχαίων" εξόδων αναπαραγώγιμη από τη μια εκτέλεση στην επόμενη.

Μια τέτοια αναπαραγωγιμότητα δεν είναι κατάλληλη σε κώδικα παραγωγής, αλλά μπορεί να βοηθήσει στις δοκιμές και την αποσφαλμάτωση.

Άλλα πακέτα

Έξω από το Exercism, υπάρχουν πολλά εγκαταστάσιμα πακέτα που σχετίζονται με την τυχαιότητα, την πιθανότητα και τη στατιστική. Για περισσότερες πληροφορίες, δες την Έννοια Statistics.

Το πακέτο StatsBase.jl

Οι περισσότερες από τις συναρτήσεις του StatsBase είναι αρκετά τεχνικές και δεν σχετίζονται με αυτό το έγγραφο.

Η εξαίρεση είναι η StatsBase.sample, η οποία παρέχει μια πλήρη υλοποίηση δειγματοληψίας με ή χωρίς επανατοποθέτηση (δες μια προηγούμενη ενότητα, παραπάνω). Υπάρχουν επίσης συναρτήσεις για σταθμισμένη (μη ομοιόμορφη) δειγματοληψία.

Το πακέτο Distributions.jl

Οι κατανομές uniform και normal (ή Γκάους) περιγράφηκαν παραπάνω.

Η ενότητα Random περιέχει επίσης τη randexp για δειγματοληψία από την εκθετική κατανομή, η οποία σχετίζεται με την (πολύ συνηθισμένη) κατανομή Poisson.

Για μια πολύ ευρύτερη γκάμα επιλογών, υπάρχει το πακέτο Distributions.jl για όσους έχουν κατάλληλο υπόβαθρο στη στατιστική.

Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα

Μάθε την έννοια Τυχαιότητα

Η εξάσκηση είναι κλειδωμένη

Ξεκλείδωσε 1 ακόμη άσκηση για να εξασκηθείς στην έννοια Τυχαιότητα