StatsBeginner: 初学者の統計学習ノート (2023)

負のオーラを自動検出したい

 前回のエントリで、著作権侵害にあたる違法アップロード動画を自分のTwitterで拡散してしまっている懸念を考えて、YouTube動画のリンクが貼ってあるツイートをまとめて削除しました。
 前回のエントリでも言いましたが、著作権侵害モノ以外にも、「残しておくとまずいツイート」は色々ある可能性があり、たとえば誹謗中傷の類いがあるかと思います。誹謗中傷ツイートを自動抽出する方法はにわかには思いつきませんが、たぶん「クソ」とか「死ね」とか「バカ」とかそういう悪口の辞書が必要になりそうです。

 ところで、言語データの分析手法として、単語ごとに感情特性を評価した辞書というものがあちこちで作られていまして、これを使ってツイートがどのような感情を帯びているか分析するということが、よくやられています。Yahoo!がそういうツールを提供してたりもします(参考リンク)。
 Yahoo!のリアルタイム検索にキーワードを入れると(この場合は「ちきりん」)、そのキーワードに関係するツイートが流れてくると同時に、画面に右の方に、ポジティブなつぶやきが多いかネガティブなつぶやきが多いかのグラフが表示されます(この場合はネガティブが74%、ポジティブが0%、中立が26%)。

StatsBeginner: 初学者の統計学習ノート (1)

 ひょっとしたら、感情分析の手法を用いて、自分のツイートの中から「負のオーラ」が漂っているものを自動的に抽出し、削除するというアプローチがあり得るかもしれません。
 というわけで今日は簡単な感情分析を行ってみました。

 最初に言っておきますが、今回やったような単純な手法では精度が低く、実用は厳しいです。あくまでちょっと試しにやってみました程度のアウトプットになっております。
 しかしそれでも、取っ掛かりとして手を動かして解析してみるというのは、色々知識が広がるものではあります。
 
 

さまざまな辞書

 感情分析に使う辞書ですが、私が今回使ったのは、東工大の高村教授が作って公開されている「PN Table」というやつです。
 PN Table

 この辞書の中身は、
めでたい:めでたい:形容詞:0.999645
賢い:かしこい:形容詞:0.999486
善い:いい:形容詞:0.999314
適す:てきす:動詞:0.999295
過小:かしょう:名詞:-0.942846
塗る:ぬる:動詞:-0.943453
器量負け:きりょうまけ:名詞:-0.943603
固まる:かたまる:動詞:-0.943991

 こんな感じで、単語に対応する極性情報が-1〜+1の間で割り当てられており、-1に近いほどネガティブ、+1に近いほとポジティブということになっています。後述するように、ゼロがニュートラルと言っていいのかはよく分かりません。

 日本語で似たような辞書はこれ以外にもありまして、たとえば東北大の乾・岡崎研究室のページで公開されている「日本語評価極性辞書」というものがあります。
 Open Resources/Japanese Sentiment Polarity Dictionary - 東北大学 乾・岡﨑研究室 / Communication Science Lab, Tohoku University

 他には、Yahoo!JAPAN研究所の鍜治伸裕さんという方が作られた「Polar Phrase Dictionary」というのがあり、東大のサイトにそのページがありますが、ダウンロード可能なものとして公開されてるわけではないようです。
 Polar Phrase Dictionary

 ポジとかネガとかのことを「極性」と呼んでいるわけですが、PN Tableは根性で大量の単語に極性を割り振っていったわけではなく、大部分の単語の極性情報が機械的に導出されています。
 物理学の理論を応用したモデルの詳細は、難しくて理解できねーと思いよく読んでませんが、「各電子のスピンは,上向きと下向きのうちどちらかの値をとり,隣り合ったスピンは同じ値をとりやすい.我々は,各単語を電子と見なし,単語の感情極性をスピンの向きと見なす.関連する単語ペアを連結することにより語彙ネットワークを構築し,これをスピン系と見なす.」そうです(汗)
 その利点として、「我々のモデルでは,平均場近似により語彙ネットワーク上の単語の感情極性が大域的に決定される.このような大域的な最適化を用いるからこそ,語釈文やコーパスのような,シソーラスと比べてノイズが含まれやすい(すなわち,隣り合っていても同じ極性を持たないことが起こりやすい)リソースを取り入れることが可能になるのである.最短距離を利用した手法や単純なブートストラッピングを利用した手法のような既存手法では,そのようなリソースを取り入れることはできない.」らしいです(汗)(汗)

 私にはよく分かりませんが、ともかくそういう理論物理学にヒントを得たモデルによって、まず、辞書・シソーラス・コーパスから「語彙ネットワーク」を形成しておき、そこにgood/badなどすでに判明している極性情報を注入してやることで、ネットワーク内の語彙に極性情報が伝搬されて、自動的に極性辞書ができあがるというプロセスのようです。たぶん。なんか近未来的です。
 論文の冒頭の先行研究レビューの部分をみると、単語の感情特性を求めるためにこれまでどのような手法が提案されてきたかも概観できて参考になります。
 
 

自分のツイートを評価してみる作業

 さて、PN Tableを使って自分のツイートを実際に評価してみます。
 今回は、とりあえず何となく数値っぽいものを計算するところまで行きたかったので、単純な処理だけやりました。

 前回のエントリでも使いましたが、自分のツイート全件を処理するときは、Twitterの公式サイトからダウンロードできる全ツイート履歴のCSVファイルを使うのが良いです。
(Video) BLOX FRUITS COMPLETE BEGINNERS GUIDE-2020-UPDATE 11+すべての作業コード

StatsBeginner: 初学者の統計学習ノート (2)

 これをPandasのデータフレームとして取り込むところから分析がスタートします。各種モジュールも最初にインポートしておきます。
import reimport csvimport timeimport pandas as pdimport matplotlib.pyplot as pltimport MeCabimport randomtw_df = pd.read_csv('tweets.csv', encoding='utf-8')
  • tweet_id # ツイートごとのID(いわゆるstatus_id)
  • in_reply_to_status_id
  • in_reply_to_user_id
  • timestamp # 投稿日時
  • source # 投稿に用いたデバイス
  • text # 本文
  • retweeted_status_id
  • retweeted_status_user_id
  • retweeted_status_timestamp
  • expanded_urls # リンクが貼られている場合の、省略しない形のURL
 という10個のフィールドがありますが、今回必要とするのはtweet_idとtextだけです。
 次に、PN TableもPandasで読み込みます。先ほども例示したように、
めでたい:めでたい:形容詞:0.999645
賢い:かしこい:形容詞:0.999486
善い:いい:形容詞:0.999314
適す:てきす:動詞:0.999295
過小:かしょう:名詞:-0.942846
塗る:ぬる:動詞:-0.943453
器量負け:きりょうまけ:名詞:-0.943603
固まる:かたまる:動詞:-0.943991

 というような内容になっているので、read_csvのオプションで区切り文字を「:」と指定して読み込めばいいかと思います。*1
pn_df = pd.read_csv('dictionary/PN_Table/pn_ja.dic.txt',\ sep=':', encoding='utf-8', names=('Word','Reading','POS', 'PN') )

 まず、個々のツイートをMeCabで形態素解析して、単語に分けるとともにその基本形表記を取得します。MeCabの導入方法等は過去のエントリを参照してください。
 基本形を取得するのは、実際のツイート中に登場する未然形や連用形のように活用された形だと、極性辞書をサーチすることができないからです。

 MeCab Pythonでふつうに形態素解析をする場合、返ってくる情報は以下のような感じになります。
>>> print(m.parse('STAP細胞はあります。'))STAP名詞,固有名詞,組織,*,*,*,*細胞名詞,一般,*,*,*,*,細胞,サイボウ,サイボーは助詞,係助詞,*,*,*,*,は,ハ,ワあり動詞,自立,*,*,五段・ラ行,連用形,ある,アリ,アリます助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス。記号,句点,*,*,*,*,。,。,。EOS
 1行1語になっていることが分かります。最後にEOSという終了記号が付いて、さらに空行が1行ついてきます。
 これを後々どうやって扱うかなのですが、とりあえず私は深く考えずに、各行をdict型のデータに格納して、リストで連結しておくことにしました。
m = MeCab.Tagger('') def get_diclist(text): parsed = m.parse(text) lines = parsed.split('\n') lines = lines[0:-2] diclist = [] for word in lines: l = re.split('\t|,',word) d = {'Surface':l[0], 'POS1':l[1], 'POS2':l[2], 'BaseForm':l[7]} diclist.append(d) return(diclist)
 これによって、1つのツイート本文が以下のような情報に変換されます。見やすくするために改行を入れますが、
[ {'POS': '固有名詞', 'POS1': '名詞', 'BaseForm': '*', 'Surface': 'STAP'}, {'POS': '一般', 'POS1': '名詞', 'BaseForm': '細胞', 'Surface': '細胞'}, {'POS': '係助詞', 'POS1': '助詞', 'BaseForm': 'は', 'Surface': 'は'}, {'POS': '自立', 'POS1': '動詞', 'BaseForm': 'ある', 'Surface': 'あり'}, {'POS': '*', 'POS1': '助動詞', 'BaseForm': 'ます', 'Surface': 'ます'}, {'POS': '句点', 'POS1': '記号', 'BaseForm': '。', 'Surface': '。'}]

 こういう感じのリストです。
 品詞は何かに使うかもと思って一応取得したんですが、結局今回は使いませんでした。しかし修正するのが面倒なのでこのままにしておきます。
 あとで拡張していく時にも使うかもしれません。実際、1つの文章を形態素解析して「辞書のリスト」にすることにしておけば、後で色々使いまわせるような気もします。

 次に、上で得られた単語ごとのdict型データに、PN Tableから取った極性値を項目として追加したいと思います。
 最初にPN TableをPandasデータフレームとして読み込んであったので、ふつうに考えたらこのデータフレームを検索してPN値を取ってくればいいということになります。
 たとえば、
(Video) 初心者として統計の追跡を開始する方法。デイトレーダー、株式市場
pn_df.loc[pn_df.Word == '細胞', 'PN']

 というような処理を繰り返せばPN値は取得してこれるのですが、この方法だと死ぬほど時間がかかります。
 最初、この方法で1万3000件のツイートの解析をやってみたのですが、CPU使用率が98.8%になり、処理がなかなか終わりませんでした。シャドウバースを2試合やっても終わらなかったので、外に出て剣道の素振りをして帰ってきたらようやく終わっていました。なので、たぶん30分以上はかかったと思います。

 そこで、Pandasのデータフレームを文字列で検索するのは非常に時間がかかるので、PN Table自体を{'単語':PN値, '単語':PN値, '単語':PN値...}という形のdict型データに変換した上で、単語をキーとしてアクセスしてPN値を取ってくる方法に変更したら約8秒で終わりました。8分ではなく8秒です。30分→8秒(約200倍速)。
 また、感情辞書をこういう形にしておくことにすれば、他の辞書を使った分析へと拡張するのもやりやすいような気がしました。
word_list = list(pn_df['Word'])pn_list = list(pn_df['PN']) pn_dict = dict(zip(word_list, pn_list))def add_pnvalue(diclist_old): diclist_new = [] for word in diclist_old: base = word['BaseForm'] if base in pn_dict: pn = float(pn_dict[base]) else: pn = 'notfound' word['PN'] = pn diclist_new.append(word) return(diclist_new)
 PN Tableに載っていない語をどうするかなのですが、今回は分析から除くことにしました。ゼロを割り当ててしまうとPN Table上で実際にゼロちょうどと評価されている単語(「週末」「巨体」「セレナーデ」など20語ある)なのか、載ってなかった単語なのかの区別がつかないので、適当にnotfoundと書いておきました。
 これで各ツイートが以下のような形式のデータになります。
>>> test_text = 'STAP細胞はあります。'>>> dl_test = get_diclist(test_text)>>> dl_test = add_pnvalue(dl_test)>>> print(dl_test)[ {'PN': 'notfound', 'POS': '固有名詞', 'POS1': '名詞', 'BaseForm': '*', 'Surface': 'STAP'}, {'PN': -0.746254, 'POS': '一般', 'POS1': '名詞', 'BaseForm': '細胞', 'Surface': '細胞'}, {'PN': 'notfound', 'POS': '係助詞', 'POS1': '助詞', 'BaseForm': 'は', 'Surface': 'は'}, {'PN': 'notfound', 'POS': '自立', 'POS1': '動詞', 'BaseForm': 'ある', 'Surface': 'あり'}, {'PN': 'notfound', 'POS': '*', 'POS1': '助動詞', 'BaseForm': 'ます', 'Surface': 'ます'}, {'PN': 'notfound', 'POS': '句点', 'POS1': '記号', 'BaseForm': '。', 'Surface': '。'}]
 あとはPN値の平均をとるだけ(上記の文だとPN値を持った語が1つしかありませんが)なので、正直こんなゴツい形式のデータにする必要なかったと思いますが、先ほども述べたように「何かに使うかも」と思った経緯からこんな形になっております。
def get_pnmean(diclist): pn_list = [] for word in diclist: pn = word['PN'] if pn != 'notfound': pn_list.append(pn) if len(pn_list) > 0: pnmean = mean(pn_list) else: pnmean = 0 return(pnmean)
 全部notfound、つまりPN Tableに載っている単語を1語も含まないツイートを0点と評価するのはよく考えたら適切ではなく、分析から除外すべきですが、やりなおしが面倒なのでコードはこのままにしておきますw
 ここまでできれば、あとはツイートの1件1件に対して、「形態素解析」「PN値の追加」「PNの平均値の算出」を繰り返していき、1つのリストにまとめます。
start_time = time.time() pnmeans_list = []for tw in tw_df['text']: dl_old = get_diclist(tw) dl_new = add_pnvalue(dl_old) pnmean = get_pnmean(dl_new) pnmeans_list.append(pnmean)print(time.time() - start_time) 
 これを、ツイート全件履歴データフレームの右端に追加して、PN極性値でソートし、CSVで吐き出します。
text_list = list(tw_df['text'])for i in range(len(text_list)): text_list[i] = text_list[i].replace('\n', ' ')aura_df = pd.DataFrame({'tweet_id':tw_df['tweet_id'], 'text':text_list, 'PN':pnmeans_list, }, columns=['tweet_id', 'text', 'PN'] )aura_df = aura_df.sort_values(by='PN', ascending=True)aura_df.to_csv('aura.csv',\ index=None,\ encoding='utf-8',\ quoting=csv.QUOTE_NONNUMERIC\ )

結果をみてみる

 さてどんな結果が得られたのかみてみたいと思います。
 まずは、最もネガティブな方から十数件をみてみますと、

StatsBeginner: 初学者の統計学習ノート (3)

 ベッキーさんの不倫事件を擁護しているツイートが最もネガティブという判定になりました。いきなり誤評価です。
 なんでこんなことになったのか確認してみます。
>>> test_text = 'ベッキーは果たしてそんなに悪いのか'>>> dl_test = get_diclist(test_text)>>> dl_test = add_pnvalue(dl_test)>>> for w in dl_test:... print(w)... {'PN': 'notfound', 'POS': '一般', 'POS1': '名詞', 'BaseForm': '*', 'Surface': 'ベッキー'}{'PN': 'notfound', 'POS': '係助詞', 'POS1': '助詞', 'BaseForm': 'は', 'Surface': 'は'}{'PN': 'notfound', 'POS': '一般', 'POS1': '副詞', 'BaseForm': '果たして', 'Surface': '果たして'}{'PN': 'notfound', 'POS': '一般', 'POS1': '副詞', 'BaseForm': 'そんなに', 'Surface': 'そんなに'}{'PN': -1.0, 'POS': '自立', 'POS1': '形容詞', 'BaseForm': '悪い', 'Surface': '悪い'}{'PN': 'notfound', 'POS': '非自立', 'POS1': '名詞', 'BaseForm': 'の', 'Surface': 'の'}{'PN': 'notfound', 'POS': '副助詞/並立助詞/終助詞', 'POS1': '助詞', 'BaseForm': 'か', 'Surface': 'か'}

 要するに、このツイートのなかでPN Tableに載っていた単語が「悪い」しかなく、「悪い」はPN Table上では最もネガティブな語ということになっているので、最もネガティブなツイートという判定になったわけですね。
 そもそもこの文、「か」を付けた反語になっているわけですが、このように文法構造を無視して単語だけで評価すると無理があるということが、この1例からも分かります。
 「良くない」とか「優れていないというわけでもない」みたいな表現を的確に評価しようと思ったら、「良い」「優れる」の部分だけみるのではなく、例えば係り受け解析というのを行って、これらの表現が打ち消されたりしていないかをきちんと調べないといけません。
 係り受け解析にはCaboChaというツールがありますので、これは後日また使ってみようかと思います。

 また、「オムライスなう」がなぜネガティブな評価になるのかというと、
(Video) 仁王2ビギナーズガイド-クラフト、スキル、鍛冶屋などを構築する
>>> test_text = 'オムライスなう'>>> dl_test = get_diclist(test_text)>>> dl_test = add_pnvalue(dl_test)>>> for w in dl_test:... print(w)... {'PN': 'notfound', 'POS': '一般', 'POS1': '名詞', 'BaseForm': 'オムライス', 'Surface': 'オムライス'}{'PN': -0.9999969999999999, 'POS': '自立', 'POS1': '形容詞', 'BaseForm': 'ない', 'Surface': 'なう'}

 このように、「なう」が形容詞の「ない」として判定されており、ないは否定的な言葉なので、ネガティブな評価となってしまってるわけです。上の出力には出てませんが、別途確認したら「連用ゴザイ接続」という活用形として判定されてました。
 「なう」は一つの典型例ですが、要するにTwitter独自の表現を辞書に取り込まないと、適切に評価できないことが分かります。

 つぎにポジティブなほうから十数件をみてみましょう。

StatsBeginner: 初学者の統計学習ノート (4)

 これはネガティブ側に比べれば比較的当たってる気もしますが、「楽天ソーシャルニュースってどこが面白いんだ」のように、反語的な表現が適切に評価できていないのは先ほどみたのと同じですね。

 「ない」の扱いはややこしいので、ややこしい処理を実装すべきなんですが、試しに逆に「ない」を辞書から削除して分析してみたらネガティブランキングは以下のようになりました。
rem_ix = list(pn_df[pn_df.Word == 'ない'].index) pn_df = pn_df.drop(rem_ix)

StatsBeginner: 初学者の統計学習ノート (5)

 次に、全体として極性値の分布がどうなっているのかをみてみます。
 matplotlibでヒストグラムを描きます。
x1 = list(aura_df['PN'])plt.hist(x1, bins=50)plt.title('P/N Frequency of My Tweets')plt.xlabel("P/N value")plt.ylabel("Frequency")

StatsBeginner: 初学者の統計学習ノート (6)

 全体的に、負の値に偏っています。ゼロのところに山ができているのは、上述のとおりPN Tableに載っている単語を1語も含まないツイートが0点と評価されてるからで、これは適切な処理ではないので無視してください。
 ところでこの結果が、ネガティブなツイートが多いことを意味しているのかというと、そうでもない可能性があります。というのも、PN Table自体のヒストグラムも取ってみると、
x2 = list(pn_df['PN'])plt.hist(x2, bins=50)plt.title('P/N Frequency in PN Table')plt.xlabel("P/N value")plt.ylabel("Frequency")

StatsBeginner: 初学者の統計学習ノート (7)

 こんなふうになっており、そもそも大半が負の値を持つ語であるということが分かります。そういう、辞書のクセなんでしょうが、結局どのへんがニュートラルなのかはよく分かっておりません。
 
 

まとめ

 今回は、負のオーラを発する自分のツイートを発掘するために、感情分析を試みました。結果的には、単にツール(MeCabや極性辞書)の使い方を学んだだけに終わり、精度的に使いものになるような処理はできてないため、「まとめて削除」まではしていません。

 その主な原因は、わざわざ分析してみなくても誰でも分かる当たり前のことですが、
  • 文法構造を考慮に入れていない
  • Twitterの独特の表現を辞書に取り込むことができていない
 といった点になると思われます。
 しかしまぁ、そのあたりに課題があるということを、具体例をもって体験できたので、勉強にはなりました。今後は、処理を少しずつ改善して、納得のいく結果が出るかどうかをまた検証していきたいと思います。

 最後に、感情分析に関する、参考になりそうな研究(日本語のもの)を列挙しておきます。

補記

 あとで気づいたんですが、PN Tableには基本形だけみると同一となる語(形容詞の「ない」と助動詞の「ない」など)がいくつかあるので、基本形だけ見てPN値を取ってきているところの処理には、誤りが含まれる可能性があります。取り急ぎ修正はしてないです。以下は例です。
 こういうのを考えると、上で使わなかった、形態素解析結果の品詞情報が、マッチング精度を上げるのに使えますし、さらに項目を追加して読み方の情報も取っておくべきですね。
22 助ける たすける 動詞 0.998356
487 助ける すける 動詞 0.990702

55117 ない ない 形容詞 -0.999882
55120 ない ない 助動詞 -0.999997

37424 頭 がしら 名詞 -0.466818
40768 頭 あたま 名詞 -0.513451
42098 頭 ず 名詞 -0.534602
42411 頭 つむり 名詞 -0.539412
43175 頭 かぶり 名詞 -0.551798
45300 頭 つぶり 名詞 -0.591628
50473 頭 かしら 名詞 -0.777183
52463 頭 とう 名詞 -0.980576

1781 人気 にんき 名詞 0.967650
3272 人気 じんき 名詞 0.213135
3851 人気 ひとけ 名詞 0.114632
10822 人気 ひとげ 名詞 -0.141334

2303 縁 えん 名詞 0.887527
38778 縁 ふち 名詞 -0.485352
41377 縁 へり 名詞 -0.523025
43027 縁 えにし 名詞 -0.549426
43872 縁 ゆかり 名詞 -0.564371
50448 縁 よすが 名詞 -0.775915

37424 頭 がしら 名詞 -0.466818
40768 頭 あたま 名詞 -0.513451
42098 頭 ず 名詞 -0.534602
42411 頭 つむり 名詞 -0.539412
43175 頭 かぶり 名詞 -0.551798
45300 頭 つぶり 名詞 -0.591628
50473 頭 かしら 名詞 -0.777183
52463 頭 とう 名詞 -0.980576
 また、その件を掘っていて、PN Table内に不可解な情報をみつけました。
15907 ホーム ホームラン 名詞 -0.199562
19438 ホーム ホームスパン 名詞 -0.238954
21561 ホーム ホーム 名詞 -0.263255
21588 ホーム ホームドクター 名詞 -0.263565
21936 ホーム ホームステイ 名詞 -0.267942
23736 ホーム ホームドラマ 名詞 -0.289906
23854 ホーム ホームシック 名詞 -0.291620
26676 ホーム ホームグラウンド 名詞 -0.327826
28151 ホーム ホームルーム 名詞 -0.347714
28695 ホーム ホームストレッチ 名詞 -0.354835
32726 ホーム ホームヘルパー 名詞 -0.408128

11151 太刀 たちうち 名詞 -0.145596
21081 太刀 たちさばき 名詞 -0.257552
21521 太刀 たちうお 名詞 -0.262799
21820 太刀 たちすじ 名詞 -0.266431
22494 太刀 たちかぜ 名詞 -0.274693
28816 太刀 たちとり 名詞 -0.356338
33218 太刀 たち 名詞 -0.414187
36886 太刀 たちさき 名詞 -0.459479
48426 太刀 たちもち 名詞 -0.673302

1063 大人 たいじん 名詞 0.982811
2714 大人 だいにん 名詞 0.397852
3153 大人 おとなしい 形容詞 0.243448
3471 大人 おとな 名詞 0.178366
3786 大人 うし 名詞 0.124680
30217 大人 おとなびる 動詞 -0.375421
52038 大人 おとなげない 形容詞 -0.960539

2277 トップ トップ 名詞 0.911679
32105 トップ トップダウン 名詞 -0.400048
32413 トップ トップニュース 名詞 -0.404087
34165 トップ トップコート 名詞 -0.426013
37862 トップ トップマネージメント 名詞 -0.472922

1509 キング キング 名詞 0.974291
7688 キング キングメーカー 名詞 -0.092334
15140 キング キングサイズ 名詞 -0.191609

 自動生成された辞書なので、こういう誤りみたいなものも含まれてるんでしょうね。
 また、「人気」とか「大人」に関して実際にはほとんど使わない読み方が登録されているあたりに、このテーブルが、コーパスよりは日本語辞書を土台にして作られていることが伺えます。
Top Articles
Latest Posts
Article information

Author: Greg Kuvalis

Last Updated: 28/08/2023

Views: 6421

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Greg Kuvalis

Birthday: 1996-12-20

Address: 53157 Trantow Inlet, Townemouth, FL 92564-0267

Phone: +68218650356656

Job: IT Representative

Hobby: Knitting, Amateur radio, Skiing, Running, Mountain biking, Slacklining, Electronics

Introduction: My name is Greg Kuvalis, I am a witty, spotless, beautiful, charming, delightful, thankful, beautiful person who loves writing and wants to share my knowledge and understanding with you.