[PYTHON] Eine Geschichte über einen Anfänger im Deep Learning, der versucht, Gitarren mit CNN zu klassifizieren

Überblick

Einige Leute haben es bereits mit Qiita versucht, aber es dient auch als eigene Studie. Ich habe versucht, Gitarrenbilder mit CNN (ResNet) zu klassifizieren, also habe ich es dabei versucht, Hier sind einige Dinge, die hilfreich sein können. (Da es nicht zusammengefasst ist, ist es ein wenig schmutzig, aber ich werde auch den Code posten)

Inhaltsverzeichnis

Informationen zur spezifischen Klassifizierungsmethode

Das Gitarrenbild wird durch Schaben erhalten und vorverarbeitet, um das Bild aufzublasen. Durch Feinabstimmung von ResNet, einer CNN-Methode, unter Verwendung aufgeblähter Bilder Ich werde versuchen, maschinelles Lernen zu machen, ohne zu viel Lernkosten auszugeben.

Über Etiketten

Ich habe die folgenden Modelle ausgewählt, die relativ einfach zu sammeln scheinen.

Über die Vorbehandlung

Das erste ist, Bilder zu sammeln. Dieses Mal habe ich es mit iCrawler gesammelt. Im Allgemeinen werden die meisten von ihnen über die Google-Bildsuche erfasst, jedoch ab dem 12. März 2020 aufgrund von Änderungen der Spezifikationen auf der Google-Seite. Diesmal habe ich Bilder von Bing gesammelt, weil das Tool nicht in Ordnung zu sein scheint.

crawling.py


import os

from icrawler.builtin import BingImageCrawler

searching_words = [
                    "Fender Stratocaster",
                    "Fender Telecaster",
                    "Fender Jazzmaster",
                    "Fender Jaguar",
                    "Fender Mustang",
                    "Gibson LesPaul",
                    "Gibson SG",
                    "Gibson FlyingV",
                    "Gibson ES-335",
                    "Acoustic guitar"
                ]
if __name__ == "__main__":
    for word in searching_words:
        if not os.path.isdir('./searched_image/' + word):
            os.makedirs('./searched_image/' + word)
        bing_crawler = BingImageCrawler(storage={ 'root_dir': './searched_image/' + word })
        bing_crawler.crawl(keyword=word, max_num=1000)

Nach dem Sammeln habe ich manuell Bilder weggelassen, die nicht verwendet werden konnten (solche, die nicht den gesamten Körper der Gitarre zeigen, solche, die Buchstaben enthalten, solche, die Reflexionen wie Hände usw. haben). Infolgedessen konnten wir für jedes Etikett etwa 100 bis 160 Bilder sammeln. (Ich habe max_num = 1000 in der Crawl-Methode angegeben, aber es wurden nur etwa 400 Blätter gesammelt.)

Als nächstes werden wir die gesammelten Bilder vorverarbeiten. Dieses Mal wurde das Bild um 45 ° gedreht und invertiert. Das Ergebnis stieg also 16-mal auf etwa 1600-2000 Bilder für jedes Etikett.

image_preprocessing.py


import os
import glob

from PIL import Image
import numpy as np
from sklearn.model_selection import train_test_split 

#Die Größe des zu komprimierenden Bildes
image_size = 224
#Anzahl der Trainingsdaten
traindata = 1000
#Anzahl der Testdaten
testdata = 300

#Ordnernamen eingeben
src_dir = './searched_image'
#Name des Ausgabeordners
dst_dir = './input_guitar_data'

#Markenname zu identifizieren
labels = [
                    "Fender Stratocaster",
                    "Fender Telecaster",
                    "Fender Jazzmaster",
                    "Fender Jaguar",
                    "Fender Mustang",
                    "Gibson LesPaul",
                    "Gibson SG",
                    "Gibson FlyingV",
                    "Gibson ES-335",
                    "Acoustic guitar"
                ]
#Bilder laden
for index, label in enumerate(labels):
    files =glob.glob("{}/{}/all/*.jpg ".format(src_dir, label))
        
    #Bild konvertierte Daten
    X = []
    #Etikette
    Y = []

    for file in files:
        #Bild öffnen
        img = Image.open(file)
        img = img.convert("RGB")
        
        #===================#In Quadrat konvertieren#===================#
        width, height = img.size
        #Wenn es vertikal lang ist, erweitern Sie es horizontal
        if width < height:
            result = Image.new(img.mode,(height, height),(255, 255, 255))
            result.paste(img, ((height - width) // 2, 0))
        #Wenn es horizontal lang ist, erweitern Sie es vertikal
        elif width > height:
            result = Image.new(img.mode,(width, width),(255, 255, 255))
            result.paste(img, (0, (width - height) // 2))
        else:
            result = img

        #Richten Sie die Bildgröße auf 224 x 224 aus
        result.resize((image_size, image_size))

        data = np.asarray(result)
        X.append(data)
        Y.append(index)

        #===================#Aufgeblasene Daten#===================#
        for angle in range(0, 360, 45):
            #Drehung
            img_r = result.rotate(angle)
            data = np.asarray(img_r)
            X.append(data)
            Y.append(index)

            #Umkehren
            img_t = img_r.transpose(Image.FLIP_LEFT_RIGHT)
            data = np.asarray(img_t)
            X.append(data)
            Y.append(index)
    
    #Normalisierung(0~255->0~1)
    X = np.array(X,dtype='float32') / 255.0
    Y = np.array(Y)


    #Daten für die Kreuzungsüberprüfung aufteilen
    X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=testdata, train_size=traindata)
    xy = (X_train, X_test, y_train, y_test)
    np.save("{}/{}_{}.npy".format(dst_dir, label, index), xy)

Speichern Sie die vorverarbeiteten Ergebnisse in einer npy-Datei für jedes Etikett.

Über die Lernmethode

Dieses Mal werde ich versuchen, ResNet zu lernen, eine typische Methode von CNN. Da der PC, den ich besitze, keine NVIDIA-GPU hat, dauert es sehr lange, wenn ich versuche, ihn so zu trainieren, da er nur von der CPU berechnet wird. Lassen Sie uns also den folgenden Code in der GPGPU-Umgebung mit Google Colab ausführen und lernen. Ich tat. (Wie man Colab benutzt, wie man Dateien hochlädt usw. wird weggelassen)

import gc

import keras
from keras.applications.resnet50 import ResNet50
from keras.models import Sequential, Model
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense, Input
from keras.callbacks import EarlyStopping 
from keras.utils import np_utils
from keras import optimizers

from sklearn.metrics import confusion_matrix

import numpy as np
import matplotlib.pyplot as plt

#Definition der Klassenbezeichnung
classes = [
                    "Fender Stratocaster",
                    "Fender Telecaster",
                    "Fender Jazzmaster",
                    "Fender Jaguar",
                    "Fender Mustang",
                    "Gibson LesPaul",
                    "Gibson SG",
                    "Gibson FlyingV",
                    "Gibson ES-335",
                    "Acoustic guitar"
                ]
num_classes = len(classes)

#Bildgröße zum Laden
ScaleTo = 224

#Definition der Hauptfunktion
def main():
    #Trainingsdaten lesen
    src_dir = '/content/drive/My Drive/Maschinelles Lernen/input_guitar_data'

    train_Xs = []
    test_Xs = []
    train_ys = []
    test_ys = []

    for index, class_name in enumerate(classes):
        file = "{}/{}_{}.npy".format(src_dir, class_name, index)
        #Bringen Sie eine separate Lerndatei mit
        train_X, test_X, train_y, test_y = np.load(file, allow_pickle=True)

        #Kombinieren Sie Daten zu einem
        train_Xs.append(train_X)
        test_Xs.append(test_X)
        train_ys.append(train_y)
        test_ys.append(test_y)

    #Kombinieren Sie die kombinierten Daten
    X_train = np.concatenate(train_Xs, 0)
    X_test = np.concatenate(test_Xs, 0)
    y_train = np.concatenate(train_ys, 0)
    y_test = np.concatenate(test_ys, 0)

    #Etikette
    y_train = np_utils.to_categorical(y_train, num_classes)
    y_test = np_utils.to_categorical(y_test, num_classes)


    #Generierung eines maschinellen Lernmodells
    model, history = model_train(X_train, y_train, X_test, y_test)
    model_eval(model, X_test, y_test)
    #Lernverlauf anzeigen
    model_visualization(history)

def model_train(X_train, y_train, X_test, y_test):
    #ResNet 50 laden. Einschließen, da keine vollständig verbundene Ebene erforderlich ist_top=False
    input_tensor = Input(shape=(ScaleTo, ScaleTo, 3))
    resnet50 = ResNet50(include_top=False, weights='imagenet', input_tensor=input_tensor)

    #Erstellen einer vollständig verbundenen Ebene
    top_model = Sequential()
    top_model.add(Flatten(input_shape=resnet50.output_shape[1:]))
    top_model.add(Dense(256, activation='relu'))
    top_model.add(Dropout(0.5))
    top_model.add(Dense(num_classes, activation='softmax'))

    #Erstellen Sie ein Modell, indem Sie ResNet50 und eine vollständig verbundene Ebene kombinieren
    resnet50_model = Model(input=resnet50.input, output=top_model(resnet50.output))

    """
    #Einige Gewichte von ResNet50 wurden behoben
    for layer in resnet50_model.layers[:100]:
        layer.trainable = False
    """

    #Geben Sie eine Klassifizierung für mehrere Klassen an
    resnet50_model.compile(loss='categorical_crossentropy',
            optimizer=optimizers.SGD(lr=1e-3, momentum=0.9),
            metrics=['accuracy'])
    resnet50_model.summary()

    #Ausführung des Lernens
    early_stopping = EarlyStopping(monitor='val_loss', patience=0, verbose=1) 
    history = resnet50_model.fit(X_train, y_train,
                        batch_size=75,
                        epochs=25, validation_data=(X_test, y_test),
                        callbacks=[early_stopping])
    #Modell speichern
    resnet50_model.save("/content/drive/My Drive/Maschinelles Lernen/guitar_cnn_resnet50.h5")
    
    return resnet50_model, history

def model_eval(model, X_test, y_test):
    scores = model.evaluate(X_test, y_test, verbose=1)
    print("test Loss", scores[0])
    print("test Accuracy", scores[1])
    #Berechnung der Verwirrungsmatrix
    predict_classes = model.predict(X_test)
    predict_classes = np.argmax(predict_classes, 1)
    true_classes = np.argmax(y_test, 1)
    print(predict_classes)
    print(true_classes)
    cmx = confusion_matrix(true_classes, predict_classes)
    print(cmx)
    #Löschen Sie das Modell, wenn die Inferenz abgeschlossen ist
    del model
    keras.backend.clear_session() #← Das ist
    gc.collect()

def model_visualization(history):
    #Grafische Anzeige des Verlustwertes
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('model loss')
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'test'], loc='upper left')
    plt.show()

    #Grafische Anzeige der richtigen Antwortrate
    plt.plot(history.history['acc'])
    plt.plot(history.history['val_acc'])
    plt.title('model accuracy')
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(['train', 'test'], loc='upper left')
    plt.show()
    
if __name__ == "__main__":
    main()

Dieses Mal war das Ergebnis von val acc usw. besser, wenn das Gewicht nicht festgelegt wurde, sodass auch das Gewicht jeder Schicht erneut gelernt wird. Im Code werden 100 Epochen trainiert, aber in Wirklichkeit beendete Early Stopping das Lernen in der 5. Epoche.

Über Lernergebnisse

Das Ergebnis ist wie folgt.

test Loss 0.09369107168481061
test Accuracy 0.9744

Ich werde auch eine Verwirrungsmatrix herausgeben.

[[199   0   1   0   0   0   0   0   0   0]
 [  0 200   0   0   0   0   0   0   0   0]
 [  2   5 191   2   0   0   0   0   0   0]
 [  1   0  11 180   6   0   2   0   0   0]
 [  0   2   0   0 198   0   0   0   0   0]
 [  0   0   0   0   0 288   4   0   6   2]
 [  0   2   0   0   0   0 296   0   2   0]
 [  0   0   0   0   0   0   0 300   0   0]
 [  0   0   0   0   0   0   0   0 300   0]
 [  0   0   0   0   0   0   0   1   0 299]]

ダウンロード2.png ダウンロード.png

Am Ende einer Epoche können Sie sehen, dass das Lernen erheblich fortgeschritten ist.

Versuche und spiele

Ich werde versuchen, auf dem gespeicherten Modell zu schließen. Dieses Mal habe ich versucht, mit Flask, das ich zum ersten Mal berührt habe, eine sehr rudimentäre Webanwendung zu erstellen.

graphing.py


import matplotlib.pyplot as plt
from PIL import Image
import numpy as np

def to_graph(image, labels, predicted):
    #=======#Plotten und speichern#=======#
    fig = plt.figure(figsize=(10.24, 5.12))
    fig.subplots_adjust(left=0.2)

    #=======#Schreiben Sie ein horizontales Balkendiagramm#=======#
    ax1 = fig.add_subplot(1,2,1)
    ax1.barh(labels, predicted, color='c', align="center")
    ax1.set_yticks(labels)#y-Achsenbeschriftung
    ax1.set_xticks([])#Entfernen Sie die Beschriftung der x-Achse

    #Schreiben Sie Zahlen in Balkendiagramme
    for interval, value in zip(range(0,len(labels)), predicted):
        ax1.text(0.02, interval, value, ha='left', va='center')

    #=======#Fügen Sie das identifizierte Bild ein#=======#
    ax2 = fig.add_subplot(1,2,2)
    ax2.imshow(image)
    ax2.axis('off')

    return fig

def expand_to_square(input_file):
    """Konvertieren Sie ein rechteckiges Bild in ein Quadrat
    input_file:Zu konvertierender Dateiname
Rückgabewert:Konvertiertes Bild
    """
    img = Image.open(input_file)
    img = img.convert("RGB")
    
    width, height = img.size
    #Wenn es vertikal lang ist, erweitern Sie es horizontal
    if width < height:
        result = Image.new(img.mode,(height, height),(255, 255, 255))
        result.paste(img, ((height - width) // 2, 0))
    #Wenn es horizontal lang ist, erweitern Sie es vertikal
    elif width > height:
        result = Image.new(img.mode,(width, width),(255, 255, 255))
        result.paste(img, (0, (width - height) // 2))
    else:
        result = img
    
    return result 

predict_file.py


predict_file.py
import io
import gc

from flask import Flask, request, redirect, url_for
from flask import flash, render_template, make_response

from keras.models import Sequential, load_model
from keras.applications.resnet50 import decode_predictions
import keras

import numpy as np
from PIL import Image
from matplotlib.backends.backend_agg import FigureCanvasAgg

import graphing

classes = [
            "Fender Stratocaster",
            "Fender Telecaster",
            "Fender Jazzmaster",
            "Fender Jaguar",
            "Fender Mustang",
            "Gibson LesPaul",
            "Gibson SG",
            "Gibson FlyingV",
            "Gibson ES-335",
            "Acoustic guitar"
            ]
num_classes = len(classes)
image_size = 224
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'gif'])


app = Flask(__name__)

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.',1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('Keine Datei')
            return redirect(request.url)
        file = request.files['file']

        if file.filename == '':
            flash('Keine Datei')
            return redirect(request.url)

        if file and allowed_file(file.filename):
            virtual_output = io.BytesIO()
            file.save(virtual_output)
            filepath = virtual_output

            model = load_model('./cnn_model/guitar_cnn_resnet50.h5')

            #Bild in Quadrat konvertieren
            image = graphing.expand_to_square(filepath)
            image = image.convert('RGB')
            #Richten Sie die Bildgröße auf 224 x 224 aus
            image = image.resize((image_size, image_size))
            #Wechseln Sie von Bild zu Numpy-Array und führen Sie eine Normalisierung durch
            data = np.asarray(image) / 255.0
            #Erhöhen Sie die Abmessungen des Arrays(3D->4 Dimensionen)
            data = np.expand_dims(data, axis=0)
            #Machen Sie Schlussfolgerungen mit dem erlernten Modell
            result = model.predict(data)[0]
            
            #Zeichnen Sie das Inferenzergebnis und das abgeleitete Bild in einem Diagramm
            fig = graphing.to_graph(image, classes, result)
            canvas = FigureCanvasAgg(fig)
            png_output = io.BytesIO()
            canvas.print_png(png_output)
            data = png_output.getvalue()

            response = make_response(data)
            response.headers['Content-Type'] = 'image/png'
            response.headers['Content-Length'] = len(data)

            #Löschen Sie das Modell, wenn die Inferenz abgeschlossen ist
            del model
            keras.backend.clear_session()
            gc.collect()

            return response
    return '''
    <!doctype html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>Laden wir die Datei hoch und beurteilen</title>
        </head>
        <body>
            <h1>Laden Sie die Datei hoch und beurteilen Sie!</h1>
            <form method = post enctype = multipart/form-data>
                <p><input type=file name=file>
                <input type=submit value=Upload>
            </form>
        </body>
    </html>
    '''

Übrigens, wenn Sie das Lernen und Inferenzieren auf Keras viele Male wiederholen, scheinen die Daten im Speicher überzulaufen, und es scheint, dass Sie sie explizit im Code löschen müssen. (Ähnlich auf Colab)

Referenz-URL ↓ Das Problem, dass die Speichernutzung beim wiederholten Lernen mit Keras zunimmt, wurde behoben

Außerdem werde ich den Quellcode der Webanwendung veröffentlichen, die ich tatsächlich erstellt habe. ↓ Guitar Classification Web App

Versuche und spiele

Ich habe es tatsächlich mit meinem eigenen Instrument versucht.

Zuerst vom Jazzmeister ジャズマスター判定.png Es reagiert auch auf Jaguar, der viele Ähnlichkeiten aufweist. Wenn es sich jedoch um ein anderes Bild handelt, das aus einem anderen Netz stammt, kann es als 99% Jazz Master beurteilt werden, sodass nicht gesagt werden kann, dass die Klassifizierungsgenauigkeit schlecht ist.

Dann Stratocaster ストラトキャスター判定.png Es war mit ziemlicher Sicherheit entschlossen, eine Stratocaster zu sein. Es scheint, dass es kein besonderes Problem gibt, selbst wenn der Kontrast etwas dunkel ist.

Was passiert also, wenn Sie sie bestimmen lassen, welche Basis sie nicht trainiert haben? Ich habe es mit meinem Jazzbass versucht. ジャズベース判定.png Es ist nicht unklar, ob es als Mustang beurteilt wird, aber ich bin besorgt, dass die Wahrscheinlichkeit von SG ebenfalls hoch ist. Es scheint, dass der Tsuno-Teil nicht ähnlich ist ...?

Zusammenfassung

Dieses Mal konnten wir durch Feinabstimmung von ResNet, einer Methode von CNN, einen Klassifikator erstellen, der relativ einfach zu erstellen ist, aber eine hohe Genauigkeit aufweist. Einige maschinelle Lernmethoden wie CNN sind jedoch schwer zu erklären, warum die Ergebnisse so waren. Wenn ich also Zeit habe, werde ich in Zukunft Visualisierungsmethoden wie Grad-CAM ausprobieren.

das ist alles.

Recommended Posts

Eine Geschichte über einen Anfänger im Deep Learning, der versucht, Gitarren mit CNN zu klassifizieren
Einführung in Deep Learning ~ CNN Experiment ~
[Windows] Die Geschichte eines Anfängers, der über die Einstellung von Anacondas PFAD stolpert.
Die Geschichte des Versuchs, den Client wieder zu verbinden
Eine Geschichte, die mich süchtig nach dem Versuch machte, LightFM unter Amazon Linux zu installieren
Eine Geschichte über einen Anfänger, der sich bemüht, CentOS 8 einzurichten (Verfahrensnotiz)
Ich habe die übliche Geschichte ausprobiert, Deep Learning zu verwenden, um den Nikkei-Durchschnitt vorherzusagen
Die Geschichte des Versuchs, SSH_AUTH_SOCK mit LD_PRELOAD auf dem Bildschirm veraltet zu halten
Eine Geschichte, in der ein Anfänger beim Versuch, eine Vim 8.2 + Python 3.8.2 + Lua-Plug-In-Umgebung unter Ubuntu 18.04.4 LTS zu erstellen, nicht weiterkommt
Eine Geschichte über einen Versuch, uwsgi auf einer fehlgeschlagenen EC2-Instanz zu installieren
Die Geschichte des tiefen Lernens mit TPU
Von nichts unter Ubuntu 18.04 bis zum Einrichten einer Deep Learning-Umgebung auf Tensor
Ein Memorandum zum Studieren und Implementieren von Deep Learning
Die Geschichte des Versuchs, Tensorboard mit Pytorch zu verwenden
Erstellen Sie eine Python-Umgebung, um die Theorie und Implementierung von Deep Learning zu erlernen
Die Geschichte eines hochrangigen Technikers, der versucht, das Überleben der Titanic vorherzusagen
Eine Geschichte, die beim Versuch, die Python-Version mit GCE zu aktualisieren, hängen blieb
Ich kann die Uhrenquelle tsc nicht finden! ?? Die Geschichte des Versuchs, einen Kernel-Patch zu schreiben
Eine Geschichte von Versuch und Irrtum beim Versuch, eine dynamische Benutzergruppe in Slack zu erstellen
Eine Geschichte über einen Python-Anfänger, der versucht, Google-Suchergebnisse mithilfe der API abzurufen
Schritte zum schnellen Erstellen einer umfassenden Lernumgebung auf einem Mac mit TensorFlow und OpenCV
Eine Geschichte über den Versuch, Linter mitten in einem Python (Flask) -Projekt vorzustellen
Erstellen Sie einen Datensatz mit Bildern, die für das Training verwendet werden sollen
Eine Geschichte über die Vorhersage des Wechselkurses mit Deep Learning
Eine Geschichte über das Ausprobieren von pyenv, virtualenv und virtualenvwrapper
Learning Deep Forest, ein neues Lerngerät, das mit DNN vergleichbar ist
Ein Hinweis zum Ausprobieren eines einfachen MCMC-Tutorials auf PyMC3
Eine Geschichte über einen Linux-Anfänger, der Linux auf ein Windows-Tablet bringt
Deep Learning 1 Übung des Deep Learning
Versuchen Sie, ein Deep Learning / Neuronales Netzwerk mit Scratch aufzubauen
Aufzeichnung der Höllenstunden, die Python-Anfängern auferlegt wurden
[Einführung in AWS] Memorandum zum Erstellen eines Webservers auf AWS
So registrieren Sie ein Paket in PyPI (Stand September 2017)
Klassifizieren Sie CIFAR-10-Bilddatensätze mithilfe verschiedener Modelle des Deep Learning
Technologie, die Jupiter unterstützt: Traitlets (Geschichte des Entschlüsselungsversuchs)
Eine Geschichte über einen 40-jährigen Ingenieurmanager, der "Deep Learning for ENGINEER" bestanden hat
Eine Geschichte über den Versuch, private Variablen in Python zu implementieren.
Ich habe versucht, in einem tief erlernten Sprachmodell zu schreiben
Eine Geschichte über einen GCP-Anfänger, der versucht, mit GCE einen Micra-Server aufzubauen
Eine Geschichte, in der der Algorithmus zu einem lächerlichen Ergebnis kam, als er versuchte, das Problem der reisenden Verkäufer richtig zu lösen
<Kurs> Tiefes Lernen: Day2 CNN
Deep Running 2 Tuning von Deep Learning
Einführung in Deep Learning ~ Lernregeln ~
Tiefe Stärkung des Lernens 1 Einführung in die Stärkung des Lernens
Tiefes Lernen der Verstärkung 2 Implementierung des Lernens der Verstärkung
Einführung in Deep Learning ~ Backpropagation ~
Eine Geschichte, die mit der Installation der maschinellen Lernbibliothek JAX zusammenhängt
Eine Geschichte über den Versuch, einen Chot zu automatisieren, wenn Sie selbst kochen
Ich suchte mit Deep Learning nach einer ähnlichen Karte von Hearthstone
Eine Geschichte über den Versuch, mehrere Python-Versionen auszuführen (Mac Edition)
Ein Befehl zum einfachen Überprüfen der Netzwerkgeschwindigkeit auf der Konsole
Versuchen Sie, eine Blackjack-Strategie zu entwickeln, indem Sie das Lernen stärken ((1) Implementierung von Blackjack)
Eine Geschichte darüber, wie man in GAE / P über verstümmelte Charaktere nachdenken möchte
Die Geschichte, dass "calendar.day_abbr" auf dem Admin-Bildschirm von django nicht aktualisiert werden konnte
Eine Geschichte über den Versuch, den Testprozess eines 20 Jahre alten Systems in C zu verbessern