Apache Kafka'da makine öğrenimi ve gerçek zamanlı analiz uygulaması

Tam metin 8336 Kelimeler, tahmini öğrenme süresi yirmi dört dakika

Kaynak: Pexels

Apache Kafka ile makine öğrenimi arasındaki ilişki inceliklidir.

Bu makale, bir makine öğrenimi çerçevesi oluşturmanın belirli bir bölümünü tartışmayı amaçlamaktadır: gerçek zamanlı tahmin için bir Kafka uygulamasında analitik bir model dağıtmak.

Kalıp eğitimi ve model dağıtımı iki bağımsız süreç olabilir. Ancak aynı adımlar veri entegrasyonu ve veri ön işlemeye de uygulanabilir çünkü model eğitimi ve model türetmenin aynı veri entegrasyonunu, filtrelemeyi, zenginleştirmeyi ve toplamayı sunması gerekir.

Bu makale, iki modelin farklı dağıtım seçeneklerini tartışacak ve karşılaştıracaktır: RPC'lerle (RPC'ler) sunucu tarafı modelleri ve yerel olarak katıştırılmış Kafka istemci uygulamaları içeren modeller. Bu makaledeki örnekler özellikle TensorFlow kullanır, ancak ilgili ilkeler diğer makine öğrenimi / derin öğrenme çerçeveleri veya ürünleri için geçerlidir. Bu çerçeveler ve ürünler arasında H2O.ai, Deeplearning4j, Google Cloud Machine Learning Engine ve Statistical Analysis System (SAS) bulunur.

Makine öğrenimi / derin öğrenme için TensorFlow-açık kaynak yazılım kitaplığı

Tensorflow, verimli bilgi işlem için oluşturulmuş açık kaynaklı bir yazılım kitaplığıdır.Esnek mimarisi, masaüstü bilgisayarlardan sunucu kümelerine, mobil cihazlara ve uç cihazlara kadar çeşitli uygulamalarla birden fazla platform (cpu, gpu, TPU'lar vb.) Arasında bilgi işlem dağıtmayı kolaylaştırır . Yazılım, Google yapay zeka kuruluşunun araştırma ve geliştirme ekibindeki araştırmacılar ve mühendisler tarafından geliştirilmiştir. Makine öğrenimi ve derin öğrenme için güçlü bir destek olan Tensorflow, birden fazla alanda kullanılır ve izole bir bileşen yerine eksiksiz bir ekosistemdir.

Bu makale model hizmetlerine odaklandığından, ağırlıklı olarak modelleri kaydetme ve yükleme ile ilgileniyorum. Modeli kaydetmek ve yüklemek, eğitim modelini depolamak ve model sunucusu olarak Tensorflow'u kullanmaktır.

Depolama modeli, esasen bir protokol tamponu (Protobuf) kullanılarak serileştirilmiş bir ikili dosyadır. Ardından model verileri sınıflandırır, yükler, verileri C, Python, Java ve diğer yazılımlarda depolar ve işler. Dosya formatı okunabilir bir metin formatı (.pbtxt) veya sıkıştırılmış ikili protokol arabelleğidir (.pb). Grafik nesnesi, TensorFlow'daki hesaplamanın temelidir. Ağırlıklar ayrı bir TensorFlow denetim noktası dosyasında saklanır.

Bu makale TensorFlow'un model dağıtımına odaklandığından, modelin nasıl önceden eğitileceği önemli değildir. Bulut makine öğrenimi motorları ve Google Cloud Platformu (GCP) gibi bulut hizmetlerini ve entegre ardışık düzenleri kullanabilir veya kendi model eğitim yolunuzu oluşturabilirsiniz. Kafka yalnızca model dağıtımında önemli değil, aynı zamanda veri entegrasyonu, veri ön işleme ve veri izlemede de önemli bir rol oynar.

Model sunucusu ve RPC kullanarak akış işleme

Model sunucusu kendi kendine yönetilebilir veya bir analiz veya bulut sağlayıcısı tarafından barındırılabilir. Model sunucusu yalnızca modelleri türetmez, dağıtmaz ve depolamaz, aynı zamanda sürüm kontrolü veya A / B testi gibi ek işlevler de sağlar. Uygulamadan model sunucusuna iletişim genellikle istek-yanıt protokolü (HTTP) veya Google RPC (gRPC) gibi RPC çerçeveleri aracılığıyla yapılır. Proje her çalıştığında, Kafka uygulaması ile model sunucusu arasında bu istek-yanıt iletişimi gerçekleşir.

Aralarından seçim yapabileceğiniz birçok model sunucu var. Seldon Server, PredictionIO, Hydrosphere.io gibi açık kaynaklı model sunuculardan seçim yapabilir veya H2O.ai, DataRobot, International Business Machines Corporation (IBM) ve Statistical Analysis System (SAS) gibi analiz satıcılarının model sunucularını kullanabilirsiniz.

Bu makale, TensorFlow'un model sunucusu olan TensorFlow tarafından sağlanan hizmeti kullanır. Model sunucusu, kendi kendine yönetimi gerçekleştirebilir veya bulut makine öğrenimi motoru hizmetlerini kullanabilir. TensorFlow hizmeti aşağıdaki özelliklere sahiptir.

· Google RPC (gRPC) ve istek-yanıt protokol terminalini (HTTP) dahil edin

· Müşteri kodunu değiştirmeden model sürümünü sunun

· Taleplerin ortak yürütülmesi için grup tek model türetme talepleri

Gecikmeyi en aza indirmek için model türetme süresini optimize edin

· Servis verilebilir birçok öğeyi destekleyin (servis verilebilir öğe, modelle birlikte veri sağlayan bir model veya görevdir)

o TensorFlow modeli

o Gömülü işlev

o Kelime arama tablosu

o Özellik dönüştürme

o TensorFlow'a dayalı olmayan modeller

· Kanarya tahliyesi ve A / B testi yapabilme

Aşağıdaki şekil, Kafka uygulaması ile model sunucu arasındaki iletişim sürecini göstermektedir.

Kafka uygulamalarını yürütme süreci basittir. Aşağıda, Kafka uygulama veri akışının ve TensorFlow sunucusundaki RPC kod parçacıkları verilmiştir.

1. Kafka ve TensorFlow hizmet API'sini girin

  • import org.apache.kafka.common.serialization.Serdes;
  • import org.apache.kafka.streams.KafkaStreams;
  • import org.apache.kafka.streams.StreamsBuilder;
  • import org.apache.kafka.streams.StreamsConfig;
  • import org.apache.kafka.streams.kstream.KStream;
  • import com.github.megachucky.kafka.streams.machinelearning.TensorflowObjectRecogniser;
  • 2. Kafka veri akışı uygulamasını yapılandırın

  • // Kafka Akış Uygulamasını Yapılandırın
  • finalString bootstrapServers = args.length > 0? Args: "localhost: 9092";
  • final Özellikler streamsConfiguration = new Özellikler ();
  • // Streams uygulamasına benzersiz bir ad verin. Ad benzersiz olmalıdır
  • // uygulamanın çalıştırıldığı Kafka kümesinde.
  • streamsConfiguration.put (StreamsConfig.APPLICATION_ID_CONFIG, "kafka-streams-tensorflow-hizmet-gRPC-örneği");
  • // Kafka komisyoncuları nerede bulunur?
  • streamsConfiguration.put (StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
  • 3. RPC'yi TensorFlow sunucusuna sunun (RPC başarısız olursa, anormal durumu bildirin)

  • KStream < Dize, Nesne > transformedMessage = imageInputLines.mapValues (değer- > {
  • System.out.println ("Görüntü yolu:" + değer);
  • imagePath = değer;
  • TensorflowObjectRecogniser tanıyıcı = yeni TensorflowObjectRecogniser (sunucu, bağlantı noktası);
  • System.out.println ("Image =" + imagePath);
  • InputStream jpegStream;
  • Deneyin {
  • jpegStream = new FileInputStream (imagePath);
  • // TensorFlow Görüntü Tanıma modelinin tahmini:
  • Liste < Harita Giriş < Dize, Çift > > list =cogniser.recognise (jpegStream);
  • Dize tahmini = list.toString ();
  • System.out.println ("Tahmin:" + tahmin);
  • cogniser.close ();
  • jpegStream.close ();
  • dönüş tahmini;
  • } catch (İstisna e) {
  • e.printStackTrace ();
  • return Collections.emptyList (). toString ();
  • }
  • });
  • 4. Kafka uygulamasını başlatın

  • // Giriş Konusundan yeni gelen görüntüleri işlemek için Kafka Akış Uygulamasını başlatın
  • final KafkaStreams akışları = yeni KafkaStreams (builder.build (), streamsConfiguration);
  • streams.start ();
  • Gömülü modeller için akış işleme

    Modeli, model sunucusu ve RPC ile iletişim kurmadan doğrudan Kafka uygulamasına yerleştirebilirsiniz. Gömülü model, Kafka veri akışını kaldıraç olarak kullanarak Kafka tarafından yerel olarak işlenen veri akışı aracılığıyla uygulanabilir. Model ayrıca KSQL (bir SQL lehçesi) veya Java, Scala, Python, Go gibi Kafka istemci uygulamaları ile arayüz oluşturabilir.

    Bu durumda Kafka uygulamaları harici model sunuculara güvenemez. Model, Kafka uygulamasında yüklenir, örneğin, Kafka veri akışı uygulamasında TensorFlow'un Java API'si kullanılır.

    Benzer şekilde, Kafka uygulamalarını çalıştırmak basittir. İşte TensorFlow modelini Kafka veri akışı uygulamasına gerçek zamanlı bir tahmin olarak yerleştiren bir kod parçacığı:

    1. Kafka ve TensorFlowAPI girin

  • import org.apache.kafka.streams.KafkaStreams;
  • import org.apache.kafka.streams.KeyValue;
  • import org.apache.kafka.streams.StreamsBuilder;
  • import org.apache.kafka.streams.StreamsConfig;
  • import org.apache.kafka.streams.integration.utils.EmbeddedKafkaCluster;
  • import org.apache.kafka.streams.integration.utils.IntegrationTestUtils;
  • import org.apache.kafka.streams.kstream.KStream;
  • import org.deeplearning4j.nn.modelimport.keras.KerasModelImport;
  • import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
  • 2. TensorFlow modelini veri depolamasından (Amazon S3 bağlantısı gibi) veya veri belleğinden (Kafkatopic düzey parametresini kabul etmek gibi) yükleyin.

  • // 1. Adım: DeepLearning4J API kullanarak Keras TensorFlow Modelini yükleyin
  • String simpleMlp = new ClassPathResource ("generatedModels / Keras / simple_mlp.h5"). GetFile (). GetPath ();
  • System.out.println (simpleMlp.toString ());
  • MultiLayerNetwork modeli = KerasModelImport.importKerasSequentialModelAndWeights (simpleMlp);
  • 3. Kafka veri akışı uygulamasını yapılandırın

  • // Kafka Akış Uygulamasını Yapılandırın
  • Özellikler streamsConfiguration = new Özellikler ();
  • streamsConfiguration.put (StreamsConfig.APPLICATION_ID_CONFIG, "kafka-streams-tensorflow-keras-entegrasyon-testi");
  • streamsConfiguration.put (StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, CLUSTER.bootstrapServers ());
  • // Kayıt anahtarları ve kayıt değerleri için varsayılan (de) serileştiricileri belirtin
  • streamsConfiguration.put (StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String (). getClass (). getName ());
  • streamsConfiguration.put (StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String (). getClass (). getName ());
  • 4. Veri akışında TensorFlow modelini uygulayın

  • final KStream < Dize, Dize > inputEvents = builder.stream (inputTopic);
  • inputEvents.foreach ((anahtar, değer) - > {
  • // Giriş değerlerini (Dizeler listesi) beklenen DL4J parametrelerine (iki Tamsayı değeri) dönüştürün:
  • Dize değerleriAsArray = value.split (",");
  • INDArray input = Nd4j.create (Integer.parseInt (valuesAsArray), Integer.parseInt (valuesAsArray));
  • // Gerçek zamanlı model çıkarımı:
  • output = model.output (input);
  • tahmin = output.toString ();
  • });
  • 5. Kafka uygulamasını başlatın

  • final KafkaStreams akışları = yeni TestKafkaStreams (builder.build (), streamsConfiguration);
  • streams.cleanUp ();
  • streams.start ();
  • Kafka veri akışında TensorFlow, H2O ve derin öğrenme 4j'nin diğer uygulama örnekleri için lütfen GitHub'a bakın.

    JUnit ve Kafka veri akışı testi kitaplıkları tarafından kullanılanlar gibi iyi bilinen test kitaplıklarını kullanarak bir birim testi çerçevesi (birim testi) bile yazabilirsiniz.

    Aşağıda, KSQL özel işlevleri tarafından kullanılan bir model dağıtımı örneği verilmiştir:

    Yapılması gereken, KSQL özel işlevinin Java arayüzünü çalıştırmak ve özel işlevi KSQL sunucusuna dağıtmaktır. Geçmiş blog gönderileri, kendi KSQL özel işlevlerinizi nasıl oluşturacağınızı ayrıntılı olarak içerir. Bu şekilde, son kullanıcılar analiz modellerini gerçek zamanlı olarak uygulamak için SQL dil sorguları yazmaktadır.

    Hangi model doğrudan uygulamaya gömülüdür?

    Tüm modeller gömülü uygulamalar için ideal modeller değildir. Modelin yerleştirilip yerleştirilmeyeceği değerlendirilirken aşağıdaki noktaların dahil edilmesi gerekir:

    · Model performansı: ne kadar hızlıysa o kadar iyidir

    · Model ikili biçimi: tercihen Java bayt kodu

    · Model boyutu: daha az bayt, daha az depolama daha iyidir

    · Model sunucu özellikleri: yükleme ve kullanma, kendiniz yükleme veya gereksiz yükleme

    Python'da yazılan kodun yavaş çalışmasının nedeni, dinamik dillerin birçok değişkeni ve isteği çalışma zamanında çevirmesi gerektiğidir.

    H2O Java sınıflandırmaları (karar ağaçları gibi) çok hızlı çalışabilir. Çalışma süresi mikrosaniye cinsinden hesaplanır.

    Yalnızca birkaç megabayt ve daha az belleğe sahip bir TensorFlow Protobuf protokolü sinir ağı hızlı bir şekilde yüklenebilir.

    Büyük bir TensorFlow Protobuf protokolü sinir ağı (yaklaşık 100 megabayt) çok fazla bellek gerektirir ve nispeten yavaş çalışır.

    Standartlara dayalı modeller (tahmini model biçimlendirme diline veya açık sinir ağı değişimine dayalı XML / JSON format verileri gibi), model işlemeye (veri ön işleme gibi) ek olarak diğer adımları içerir. Bu model, bu standartları kullanmanın organizasyonel zorluklarını ve teknik sınırlamalarını sunar ve performansı, TensorFlow'un Kaydedilen Modeli gibi yerel olarak yüklenmiş modellerden çok daha düşüktür.

    Nihayetinde, modelin doğrudan uygulamaya gömülü olup olmadığına bakılmaksızın, modelin kendisine, donanım olanaklarına ve proje gereksinimlerine bağlıdır.

    Kafka uygulamalarında model sunucunun özelliklerini yeniden yapılandırmak zor değil

    Modeli uygulamaya gömmek, model özelliklerinin hemen kullanılabileceği anlamına gelmez. Kullanıcı modeli kendisi yürütmelidir. Önce kendinize ilk soruyu sorun: Model sunucunun özelliklerine ihtiyacım var mı? Modeli dinamik olarak güncellemem gerekiyor mu? Model versiyonu? A / B testi? Kanarya testi?

    İyi haber şu ki, model özelliklerini uygulamak zor değil. Kullanıcının gereksinimlerine ve araç yapılandırmasına bağlı olarak şunları yapabilirsiniz:

    · Uygulamanın yeni bir sürümünü başlatın (Kubernetes kapsayıcı gibi)

    Kafka konularıyla model veya ağırlık gönderin ve kullanın

    · Hizmet ağlarını kullanın (Envoy, Linkerd veya Istio sunucuları gibi)

    Kafka uygulamalarında analiz modellerini kullanmanın çeşitli yöntemlerini değerlendirip tartalım.

    Kaynak: Pexels

    Takas --- model sunucu ve gömülü model

    Model sunucusunda bir analiz modeli dağıtabilir ve RPC kullanarak iletişim kurabilirsiniz. Ya da modeli doğrudan uygulamaya yerleştirebilirsiniz. Burada en iyi seçenek yoktur çünkü kullanıcının tesislerine, gereksinimlerine ve yeteneklerine bağlıdır.

    Olay akışı uygulamalarıyla birlikte model sunucusu ve RPC neden kullanılmalı?

    · Olay akışı hakkında hiçbir şey bilmiyorsanız, modelin anlaşılması kolaydır

    · Daha sonra gerçek zamanlı akışa geçmek mümkündür

    Yerleşik model yönetimine farklı modeller, sürümler ve A / B testleri koyun

    Dahili izleme

    Modelleri neden olay akışı uygulamalarına yerleştirmelisiniz?

    Uzaktan aramalara gerek kalmadan daha iyi gecikme elde etmek için yerel muhakeme kullanın

    Çevrimdışı çıkarım (ekipman, kenar işleme vb.)

    Kafka Streams uygulamalarının kullanılabilirliği, ölçeklenebilirliği ve gecikme / aktarım hızı ile RPC arabiriminin SLA'sı arasında bağlantı yoktur

    · Yan etki yok (arıza riski gibi) -Kafka başvuru işlemleri çeşitli faktörleri içeriyor (belirli bir faktör gibi)

    Her iki seçeneğin de kendi avantajları ve dezavantajları vardır ve farklı durumlar için önerilirler.

    Kubernetes (K8s) bulut yerel model dağıtımı

    Bulut tabanlı çerçevede, her iki yöntem de fayda sağlayabilir. Diğer bulut yerel teknolojileri benzer özelliklere sahip olsa bile, Kubernetes hala aşağıdaki bulut yerel ortamı olarak kullanılmaktadır.

    Modeli Kafka uygulamasına gömmek, bağımsız pod veri yapısının tüm avantajlarını elde edebilir. Bağımsız bölme veri yapısı, akışlı veri işleme ve model türetme için bir kaptır ve harici bir model sunucusuna dayanmaz.

    Aşağıdaki örnekte, modele gömülü Kafka veri akışı uygulamasını bağımsız olarak ölçebilir, yeni bir sürüm başlatabilir, A / B testi veya başka yollar ekleyebilir ve istisnaları işlemek için Envoy veya Linkerd gibi bir bulut yerel proxy sunucusu kullanabilirsiniz.

    Yine de model sunucunun avantajlarından ve özelliklerinden yararlanmak istiyorsanız sepet tasarım modelini kullanabilirsiniz. Kubernetes, Kapsüle belirli görevler içeren başka kapsayıcılar eklemeyi destekler. Aşağıdaki örnekte, Kafka Streams uygulaması bir kapsayıcıda konuşlandırılır ve model sunucusu, aynı bölmedeki başka bir kapta bir sepet olarak konuşlandırılır.

    Bu şekilde, model sunucunun işlevselliğinden ve tek bir konteynerin sağlamlığından ve ölçeklenebilirliğinden yararlanabilirsiniz. Yine de her bir konteyner arasında RPC kullanmanın dezavantajına sahiptir. Aynı konteynere iki konteyner yerleştirilerek bekleme süresi ve olası hatalar en aza indirilebilir.

    Edge modeli dağıtımı

    Model genellikle bulutta veya veri merkezinde konuşlandırılmaz. Bazı durumlarda modeller uçta konuşlandırılabilir. Uç dağıtımı şu anlama gelir:

    · Kenar veri merkezi veya kenar ekipmanı / makinesi

    · Edge'in bir Kafka uygulama kümesi, bir aracı ve bir Kafka uygulama istemcisi vardır.

    · Güçlü bir istemci (KSQL veya Java gibi) veya hafif bir istemci (C veya JavaScript gibi)

    · Gömülü bir model veya RPC modeli türetme

    · Yerel veya uzaktan eğitim

    · Kanun ve yönetmeliklere etkisi

    Bazı telekomünikasyon sağlayıcıları için, uç bilişimin tanımı ultra düşük gecikmedir ve terminaller arasındaki iletişim süresi 100 milisaniyeden azdır. Bu, eksiksiz bir OpenStack ve Kubernetes kümesi ve nesne depolaması gerektiren açık kaynak bulut altyapısı yazılım yığını StarlingX gibi çerçevelerle elde edilir. Diğer nesneler için "kenar", çok küçük hafif C uygulamalarının ve mobil cihaz modellerinin konuşlandırılabildiği mobil cihazlar, hafif panolar veya sensörler anlamına gelir.

    Kafka'nın bakış açısına göre birçok seçenek var. Confluent tarafından tam olarak desteklenen librdkafka (yerel Kafka C / C ++ istemci kitaplığı) kullanılarak hafif uç uygulamaları tamamen oluşturulabilir. Modeli bir mobil uygulamaya yerleştirmek için JavaScript kullanabilir ve Kafka iletişimi için REST proxy veya WebSocket entegrasyonunu kullanabilirsiniz.

    Kafka'nın bağımsız teknik model dağıtımı

    Model dağıtımı, süreç ve teknoloji konusunda model eğitiminden tamamen ayrılabilir. Dağıtım altyapısı farklı modelleri yönetebilir ve hatta farklı makine öğrenimi çerçevelerini kullanarak modelleri eğitebilir. Kafka ayrıca, altyapının teknik izlenmesi ve performans veya model doğruluğu gibi modele özgü izleme dahil olmak üzere makine öğrenimi izlemesi oluşturmak için iyi bir temel sağlar.

    İster veri entegrasyonu, ön işleme, model dağıtımı ve izleme dahil tüm işlevleri uygulamak için Kafka'yı kullanmak isteyin, ister modelleri gerçek zamanlı Kafka istemcilerine yerleştirmek için yalnızca Kafka istemcilerini kullanın, Kafka makine öğrenimi altyapısı için tamamlayıcı bir araçtır ( Veri ön işleme ve model eğitiminden tamamen ayrı) Kafka.

    Model dağıtımı için iki seçenek vardır: model sunucusu (RPC) ve gömülü model. Her yöntemin artılarını ve eksilerini anlamak, proje için doğru kararın alınmasına yardımcı olacaktır. Aslında analiz modellerini Kafka uygulamalarına gömmek basit ve çok pratiktir.

    Kaynak: Pexels

    Yorum Beğen Takip Et

    Yapay zeka öğrenme ve geliştirmenin kuru mallarını paylaşalım

    Yeniden yazdırıyorsanız, lütfen arka planda bir mesaj bırakın ve yeniden yazdırma şartnamelerine uyun

    Python'da bir sözlüğün değerini almak için köşeli parantez kullanmayı bırakın, bu yöntemi deneyin
    önceki
    Core Voice Today | Wuhan Viroloji Enstitüsü, 21 Ocak'ta bir Çin buluş patenti için Remdesivir'i açıkladı
    Sonraki
    Geliştirici görmeli! KISS, DRY ve izlenecek kodlama ilkeleri
    2020'de veri biliminde çığır açan dört adım
    Bugün Core Voice | "Zorlu" Salgın Önleme! Sakinler topluluğa girmek için 5 metre uzunluğundaki bir dezenfeksiyon kanalından geçmelidir.
    Çin Tüketiciler Derneği, kişisel mülkiyet güvenliğini ve kişisel gizliliği korumak için Bahar Festivali sırasında tüketime yönelik yedi ipucu yayınladı
    Yaşamın geçişini açmak için ülke çapında bir çaba gösterildi ve birçok yerde itfaiye departmanları, itfaiye aracı geçitlerini işgal etmenin gizli tehlikelerini araştırdı.
    Yeni Zelanda'nın Kuzey Adası'nda orman yangını çıktı
    Çin Yeni Yılı Kültür Merkezi ücretsiz olarak açılarak size Yeni Yılı tattırıyor
    Salgının altındaki üç zihniyet uykusuzluğa neden olabilir mi? Uzman: Bu, güvensizlikle ilgili
    Guangzhou'dan trenle 29 işe dönüş bugün Hubei'ye gidiyor! 541 kurumsal çalışanı noktadan noktaya toplayın
    Kurumsal yanıt verenlerin% 70'inden fazlası gelecekteki işlerine güveniyor ve sağlık sektörü en çok güven duyuyor
    İtalya'nın en çok etkilenen bölgesinde Çinli bir adam duvara bir maske asarak komşulara dağıttı ama kızın kalbini aldı
    58 gün boyunca Hubei'de "engellendikten" sonra, çok sayıda "kontrol noktasından" geçtim ve sonunda Guangzhou'daki evime döndüm.
    To Top